From abf6d3751b6862ad5181b48f28fdc1c55f89fa65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20G=C3=B3mez=20Garc=C3=ADa?= Date: Sun, 21 Jun 2015 12:21:59 +0200 Subject: [PATCH] Fixed report & created "basic" report class --- server/src/uds/REST/methods/reports.py | 1 + server/src/uds/core/reports/tools/__init__.py | 38 +++++ .../core/reports/tools/geraldo_graphics.py | 31 ++++ server/src/uds/core/reports/tools/report.py | 88 ++++++++++++ server/src/uds/reports/stats/login.py | 136 ++++++++++++------ 5 files changed, 252 insertions(+), 42 deletions(-) create mode 100644 server/src/uds/core/reports/tools/__init__.py create mode 100644 server/src/uds/core/reports/tools/geraldo_graphics.py create mode 100644 server/src/uds/core/reports/tools/report.py diff --git a/server/src/uds/REST/methods/reports.py b/server/src/uds/REST/methods/reports.py index d9983fdde..4f86e199e 100644 --- a/server/src/uds/REST/methods/reports.py +++ b/server/src/uds/REST/methods/reports.py @@ -116,6 +116,7 @@ class Reports(model.BaseModelHandler): } except Exception as e: + logger.exception('Generating report') return self.invalidRequestException(six.text_type(e)) report.__dict__.update() diff --git a/server/src/uds/core/reports/tools/__init__.py b/server/src/uds/core/reports/tools/__init__.py new file mode 100644 index 000000000..6f8596eae --- /dev/null +++ b/server/src/uds/core/reports/tools/__init__.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2015 Virtual Cable S.L. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of Virtual Cable S.L. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +''' +.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com +''' +from __future__ import unicode_literals + +from .geraldo_graphics import UDSImage +from .report import UDSGeraldoReport + +__updated__ = '2015-06-21' diff --git a/server/src/uds/core/reports/tools/geraldo_graphics.py b/server/src/uds/core/reports/tools/geraldo_graphics.py new file mode 100644 index 000000000..760aa6633 --- /dev/null +++ b/server/src/uds/core/reports/tools/geraldo_graphics.py @@ -0,0 +1,31 @@ +from geraldo.base import BAND_WIDTH, BAND_HEIGHT, Element +from geraldo.utils import cm, black +from geraldo import Image + +import logging + +logger = logging.getLogger(__name__) + + +class UDSImage(Image): + def _get_height(self): + logger.debug('get height called') + ret = self._height or (self.image and self.image.size[1] or 0) + return ret * cm / 118 + + def _set_height(self, value): + logger.debug('set height called') + self._height = value / cm * 118 + + height = property(_get_height, _set_height) + + def _get_width(self): + logger.debug('get width called') + ret = self._width or (self.image and self.image.size[0] or 0) + return ret * cm / 118 + + def _set_width(self, value): + logger.debug('set width called') + self._width = value / cm * 118 + + width = property(_get_width, _set_width) diff --git a/server/src/uds/core/reports/tools/report.py b/server/src/uds/core/reports/tools/report.py new file mode 100644 index 000000000..ab29d0363 --- /dev/null +++ b/server/src/uds/core/reports/tools/report.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2015 Virtual Cable S.L. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of Virtual Cable S.L. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +''' +.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com +''' +from __future__ import unicode_literals + +from django.utils.translation import ugettext, ugettext_lazy as _ +from uds.core.reports import stock + +from geraldo import Report, landscape, ReportBand, ObjectValue, SystemField, BAND_WIDTH, Label, SubReport, Rect +from uds.core.reports.tools.geraldo_graphics import UDSImage +from reportlab.lib.pagesizes import A4 +from reportlab.lib.units import cm, mm +from reportlab.lib.enums import TA_RIGHT, TA_CENTER +from reportlab.lib import colors + +import logging + +logger = logging.getLogger(__name__) + +__updated__ = '2015-06-21' + + +class UDSGeraldoReport(Report): + author = 'UDS Enterprise' + title = 'UDS Report' + + print_if_empty = True + page_size = A4 + margin_left = 0.5 * cm + margin_top = 0.5 * cm + margin_right = 0.5 * cm + margin_bottom = 0.5 * cm + + class band_page_header(ReportBand): + height = 1.8 * cm + elements = [ + SystemField(expression='%(report_title)s', top=0.5 * cm, left=0, width=BAND_WIDTH, + style={'fontName': 'Helvetica-Bold', 'fontSize': 14, 'alignment': TA_CENTER}), + + SystemField(expression=_('Page %(page_number)d of %(page_count)d'), top=0.1 * cm, + width=BAND_WIDTH, style={'alignment': TA_RIGHT}), + UDSImage(filename=stock.getStockImagePath(stock.LOGO), left=0.0 * cm, top=0.0 * cm, width=2.0 * cm, height=2.0 * cm), + ] + borders = {'bottom': True} + + class band_page_footer(ReportBand): + height = 0.5 * cm + elements = [ + Label(text=_('Generated by UDS'), top=0.1 * cm), + SystemField(expression=_('Printed in %(now:%Y, %b %d)s at %(now:%H:%M)s'), top=0.1 * cm, + width=BAND_WIDTH, style={'alignment': TA_RIGHT}), + ] + borders = {'top': True} + + @staticmethod + def viewSize(): + return ( + UDSGeraldoReport.page_size - UDSGeraldoReport.mar + ) diff --git a/server/src/uds/reports/stats/login.py b/server/src/uds/reports/stats/login.py index 9ef7f7552..6117ce400 100644 --- a/server/src/uds/reports/stats/login.py +++ b/server/src/uds/reports/stats/login.py @@ -38,6 +38,7 @@ import django.template.defaultfilters as filters from uds.core.ui.UserInterface import gui from uds.core.reports import stock +from uds.core.reports.tools import UDSImage, UDSGeraldoReport from uds.models import StatsEvents from uds.core.util.stats import events @@ -45,15 +46,17 @@ import StringIO import cairo import pycha.line +import pycha.bar from .base import StatsReport from uds.core.util import tools from geraldo.generators.pdf import PDFGenerator -from geraldo import Report, landscape, ReportBand, ObjectValue, SystemField, BAND_WIDTH, Label, Image, SubReport +from geraldo import Report, landscape, ReportBand, ObjectValue, SystemField, BAND_WIDTH, Label, SubReport, Rect from reportlab.lib.pagesizes import A4 -from reportlab.lib.units import cm +from reportlab.lib.units import cm, mm from reportlab.lib.enums import TA_RIGHT, TA_CENTER +from reportlab.lib import colors from PIL import Image as PILImage import datetime @@ -61,53 +64,39 @@ import logging logger = logging.getLogger(__name__) -__updated__ = '2015-05-04' +__updated__ = '2015-06-21' -# Width & height +# several constants as Width height, margins, .. WIDTH, HEIGHT = 1800, 1000 -GERARLDO_WIDTH = 800 -GERALDO_HEIGHT = GERARLDO_WIDTH * HEIGHT / WIDTH +GERALDO_WIDTH = 120 * mm +GERALDO_HEIGHT = GERALDO_WIDTH * HEIGHT / WIDTH -class AccessReport(Report): - author = 'UDS Enterprise' - - print_if_empty = True - page_size = A4 - margin_left = 2 * cm - margin_top = 0.5 * cm - margin_right = 0.5 * cm - margin_bottom = 0.5 * cm +class AccessReport(UDSGeraldoReport): class band_detail(ReportBand): height = 10 * cm auto_expand_height = True elements = ( - Label(text='Users', top=0.6 * cm, left=10 * cm, style={'fontName': 'Helvetica-Bold'}), - Image(get_image=lambda x: x.instance['image'], left=1.0 * cm, top=1.0 * cm, width=GERARLDO_WIDTH, height=GERALDO_HEIGHT, stretch=True), + Label(text=_('Users access by date'), top=0.6 * cm, left=0, width=BAND_WIDTH, + style={'fontName': 'Helvetica-Bold', 'fontSize': 10, 'alignment': TA_CENTER}), + UDSImage(left=4 * cm, top=1 * cm, + width=GERALDO_WIDTH, height=GERALDO_HEIGHT, + get_image=lambda x: x.instance['image']), + + Label(text=_('Users access by day of week'), top=GERALDO_HEIGHT + 1.2 * cm, left=0, width=BAND_WIDTH, + style={'fontName': 'Helvetica-Bold', 'fontSize': 10, 'alignment': TA_CENTER}), + UDSImage(left=4 * cm, top=GERALDO_HEIGHT + 1.6 * cm, + width=GERALDO_WIDTH, height=GERALDO_HEIGHT, + get_image=lambda x: x.instance['image2']), + + Label(text=_('Users access by hour'), top=2 * GERALDO_HEIGHT + 2 * cm, left=0, width=BAND_WIDTH, + style={'fontName': 'Helvetica-Bold', 'fontSize': 10, 'alignment': TA_CENTER}), + UDSImage(left=4 * cm, top=2 * GERALDO_HEIGHT + 2.4 * cm, + width=GERALDO_WIDTH, height=GERALDO_HEIGHT, + get_image=lambda x: x.instance['image3']), ) - class band_page_header(ReportBand): - height = 1.8 * cm - elements = [ - SystemField(expression='%(report_title)s', top=0.5 * cm, left=0, width=BAND_WIDTH, - style={'fontName': 'Helvetica-Bold', 'fontSize': 14, 'alignment': TA_CENTER}), - - SystemField(expression=_('Page %(page_number)d of %(page_count)d'), top=0.1 * cm, - width=BAND_WIDTH, style={'alignment': TA_RIGHT}), - Image(filename=stock.getStockImagePath(stock.LOGO), left=0.1 * cm, top=0.0 * cm, width=2.0 * cm, height=2.0 * cm), - ] - # borders = {'bottom': True} - - class band_page_footer(ReportBand): - height = 0.5 * cm - elements = [ - Label(text='Generated by UDS', top=0.1 * cm), - SystemField(expression=_('Printed in %(now:%Y, %b %d)s at %(now:%H:%M)s'), top=0.1 * cm, - width=BAND_WIDTH, style={'alignment': TA_RIGHT}), - ] - borders = {'top': True} - subreports = [ SubReport( queryset_string='%(object)s["data"]', @@ -188,6 +177,9 @@ class StatsReportLogin(StatsReport): else: xLabelFormat = 'SHORT_DATETIME_FORMAT' + # + # User access by date graph + # samplingIntervals = [] prevVal = None for val in range(start, end, (end - start) / (samplingPoints + 1)): @@ -212,7 +204,7 @@ class StatsReportLogin(StatsReport): surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, WIDTH, HEIGHT) - dataset = (('Users access to UDS', data),) + dataset = ((ugettext('Users access to UDS'), data),) options = { 'encoding': 'utf-8', @@ -255,7 +247,7 @@ class StatsReportLogin(StatsReport): 'right': 48, 'bottom': 48, }, - 'title': _('Users usage of UDS') + 'title': _('Users access to UDS') } chart = pycha.line.LineChart(surface, options) @@ -264,18 +256,78 @@ class StatsReportLogin(StatsReport): img = PILImage.frombuffer("RGBA", (surface.get_width(), surface.get_height()), surface.get_data(), "raw", "BGRA", 0, 1) + # + # User access by day of week + # + dataWeek = [0] * 7 + dataHour = [0] * 24 + for val in events.statsManager().getEvents(events.OT_AUTHENTICATOR, events.ET_LOGIN, since=start, to=end): + s = datetime.datetime.fromtimestamp(val.stamp) + dataWeek[s.weekday()] += 1 + dataHour[s.hour] += 1 + + dataset = ((ugettext('Users access to UDS'), [(i, dataWeek[i]) for i in range(0, 7)]),) + + options['axis'] = { + 'x': { + 'ticks': [ + dict(v=i, label='Day {}'.format(i)) for i in range(0, 7) + ], + 'range': (0, 6), + 'showLines': True, + }, + 'y': { + 'tickCount': 10, + 'showLines': True, + }, + 'tickFontSize': 16, + } + + chart = pycha.bar.VerticalBarChart(surface, options) + chart.addDataset(dataset) + chart.render() + + img2 = PILImage.frombuffer("RGBA", (surface.get_width(), surface.get_height()), surface.get_data(), "raw", "BGRA", 0, 1) + + + # Hourly chart + dataset = ((ugettext('Users access to UDS'), [(i, dataHour[i]) for i in range(0, 24)]),) + + options['axis'] = { + 'x': { + 'ticks': [ + dict(v=i, label='{}:00'.format(i)) for i in range(0, 24) + ], + 'range': (0, 24), + 'showLines': True, + }, + 'y': { + 'tickCount': 10, + 'showLines': True, + }, + 'tickFontSize': 16, + } + + chart = pycha.bar.VerticalBarChart(surface, options) + chart.addDataset(dataset) + chart.render() + + img3 = PILImage.frombuffer("RGBA", (surface.get_width(), surface.get_height()), surface.get_data(), "raw", "BGRA", 0, 1) + output = StringIO.StringIO() queryset = [ - {'image': img, 'data': reportData} + {'image': img, 'image2': img2, 'image3': img3, 'data': reportData} ] logger.debug(queryset) try: report = AccessReport(queryset=queryset) + report.title = ugettext('User access to UDS') # report = UsersReport(queryset=users) report.generate_by(PDFGenerator, filename=output) return output.getvalue() - except: + except Exception: logger.exception('Errool') + return None