# bijoe - BI dashboard # Copyright (C) 2015 Entr'ouvert # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . from __future__ import unicode_literals from collections import OrderedDict import hashlib import json from django.conf import settings from django.utils.encoding import force_text from django.utils.text import slugify from django.views.generic.edit import CreateView, DeleteView, UpdateView from django.views.generic.list import MultipleObjectMixin from django.views.generic import DetailView, ListView, View, TemplateView from django.shortcuts import redirect from django.core.urlresolvers import reverse, reverse_lazy from django.http import HttpResponse, Http404 from django.core.exceptions import PermissionDenied from django.views.decorators.clickjacking import xframe_options_exempt from rest_framework import generics from rest_framework.response import Response from ..utils import get_warehouses from ..engine import Engine from . import models, forms, signature from .utils import Visualization from .. import views class WarehouseView(views.AuthorizationMixin, TemplateView): template_name = 'bijoe/warehouse.html' def get_context_data(self, **kwargs): ctx = super(WarehouseView, self).get_context_data(**kwargs) try: warehouse = [warehouse for warehouse in get_warehouses() if warehouse.name == self.kwargs['warehouse']][0] except IndexError: raise Http404 ctx['warehouse'] = Engine(warehouse) ctx['cubes'] = sorted(ctx['warehouse'].cubes, key=lambda cube: cube.label.strip().lower()) return ctx class CubeDisplayMixin(object): def get_context_data(self, **kwargs): ctx = super(CubeDisplayMixin, self).get_context_data(**kwargs) ctx['warehouse'] = self.warehouse ctx['cube'] = self.cube ctx['visualization'] = self.visualization return ctx class CubeMixin(object): def visualization(self, request, cube): self.form = forms.CubeForm(cube=self.cube, data=request.GET or request.POST) if self.form.is_valid(): return Visualization.from_form(self.cube, self.form) def dispatch(self, request, *args, **kwargs): try: self.warehouse = Engine([warehouse for warehouse in get_warehouses() if warehouse.name == self.kwargs['warehouse']][0]) except IndexError: raise Http404 try: self.cube = self.warehouse[self.kwargs['cube']] except KeyError: raise Http404 self.visualization = self.visualization(request, cube) return super(CubeMixin, self).dispatch(request, *args, **kwargs) class CubeView(views.AuthorizationMixin, CubeDisplayMixin, CubeMixin, TemplateView): template_name = 'bijoe/cube.html' def post(self, request, *args, **kwargs): if 'ods' in self.request.POST: return self.ods(self.visualization) return self.get(request, *args, **kwargs) def get_context_data(self, **kwargs): ctx = super(CubeView, self).get_context_data(**kwargs) ctx['form'] = self.form return ctx class CreateVisualizationView(views.AuthorizationMixin, CubeMixin, CreateView): model = models.Visualization form_class = forms.VisualizationForm template_name = 'bijoe/create_visualization.html' success_url = '/visualization/{id}/' def get(self, request, *args, **kwargs): if not self.visualization: return redirect('homepage') return super(CreateVisualizationView, self).get(request, *args, **kwargs) def form_valid(self, form): if not self.visualization: return redirect('homepage') form.instance.parameters = self.visualization.to_json() return super(CreateVisualizationView, self).form_valid(form) class VisualizationView(views.AuthorizationMixin, CubeDisplayMixin, DetailView): model = models.Visualization template_name = 'bijoe/visualization.html' def get_object(self): named_visualization = super(VisualizationView, self).get_object() if not hasattr(self, 'visualization'): self.visualization = Visualization.from_json(named_visualization.parameters) self.cube = self.visualization.cube self.warehouse = self.cube.engine return named_visualization def post(self, request, *args, **kwargs): named_visualization = self.get_object() if 'ods' in self.request.POST: return self.ods(self.visualization) form = forms.CubeForm(cube=self.cube, data=request.POST) if form.is_valid(): self.visualization = Visualization.from_form(self.cube, form) named_visualization.parameters = self.visualization.to_json() if 'save' in request.POST: named_visualization.save() return self.get(request, *args, **kwargs) def get_context_data(self, **kwargs): ctx = super(VisualizationView, self).get_context_data(**kwargs) initial = { 'representation': self.visualization.representation, 'measure': self.visualization.measure.name, 'loop': self.visualization.loop and self.visualization.loop.name, 'drilldown_x': self.visualization.drilldown_x and self.visualization.drilldown_x.name, 'drilldown_y': self.visualization.drilldown_y and self.visualization.drilldown_y.name, } for key, value in self.visualization.filters.iteritems() or []: if isinstance(value, list): value = tuple(value) initial['filter__%s' % key] = value ctx['form'] = forms.CubeForm(cube=self.cube, initial=initial) path = reverse('visualization-iframe', args=self.args, kwargs=self.kwargs) signature = hashlib.sha1(path + settings.SECRET_KEY).hexdigest() path += '?signature=' + signature ctx['iframe_url'] = path return ctx class SignatureAuthorizationMixin(views.AuthorizationMixin): def authorize(self, request): if request.user.is_authenticated() and request.user.is_superuser: return True if 'signature' in request.GET: signature = hashlib.sha1(request.path + settings.SECRET_KEY).hexdigest() if request.GET['signature'] == signature: return True return False class VisualizationIframeView(SignatureAuthorizationMixin, VisualizationView): template_name = 'bijoe/cube_raw.html' class DeleteVisualizationView(views.AuthorizationMixin, DeleteView): model = models.Visualization template_name = 'bijoe/delete_visualization.html' success_url = reverse_lazy('homepage') class VisualizationsView(views.AuthorizationMixin, ListView): template_name = 'bijoe/visualizations.html' model = models.Visualization context_object_name = 'visualizations' paginate_by = settings.PAGE_LENGTH def get_queryset(self): return self.model.all_visualizations() def get_context_data(self, **kwargs): ctx = super(VisualizationsView, self).get_context_data(**kwargs) ctx['request'] = self.request return ctx class RenameVisualization(views.AuthorizationMixin, UpdateView): model = models.Visualization form_class = forms.VisualizationForm template_name = 'bijoe/rename_visualization.html' success_url = '/visualization/{id}/' class VisualizationsJSONView(MultipleObjectMixin, View): model = models.Visualization def get(self, request, *args, **kwargs): if not request.user.is_authenticated() or not request.user.is_superuser: known_services = getattr(settings, 'KNOWN_SERVICES', []) if known_services: key = None for l in known_services.itervalues(): for service in l.itervalues(): if service['verif_orig'] == request.GET.get('orig', ''): key = service['secret'] if key is None or not signature.check_query(request.META['QUERY_STRING'], key): raise PermissionDenied('signature is missing or wrong') data = [] for visualization in self.get_queryset(): path = reverse('visualization-iframe', kwargs={'pk': visualization.pk}) sig = hashlib.sha1(path + settings.SECRET_KEY).hexdigest() path += '?signature=' + sig data_uri = reverse('visualization-json', kwargs={'pk': visualization.pk}) data.append({ 'name': visualization.name, 'slug': visualization.slug, 'path': request.build_absolute_uri(path), 'data-url': request.build_absolute_uri(data_uri), }) response = HttpResponse(content_type='application/json') response.write(json.dumps(data)) return response class CubeIframeView(CubeView): template_name = 'bijoe/cube_raw.html' class VisualizationODSView(views.AuthorizationMixin, DetailView): model = models.Visualization def get(self, request, *args, **kwargs): instance = self.get_object() visualization = Visualization.from_json(instance.parameters) response = HttpResponse(content_type='application/vnd.oasis.opendocument.spreadsheet') response['Content-Disposition'] = 'attachment; filename=%s.ods' % slugify(instance.name) workbook = visualization.ods() workbook.save(response) return response class VisualizationGeoJSONView(generics.GenericAPIView): permission_classes = () queryset = models.Visualization.objects.all() def get(self, request, pk, format=None): instance = self.get_object() visualization = Visualization.from_json(instance.parameters) visualization.measure = visualization.cube.measures['geolocation'] geojson = [] for row in visualization.data(): properties = {} for cell in row.dimensions: properties[cell.dimension.label] = '%s' % (cell,) geojson.append({ 'type': 'Feature', 'geometry': { 'type': 'MultiPoint', 'coordinates': [[coord for coord in point] for point in row.measures[0].value], }, 'properties': properties, }) return Response(geojson) class VisualizationJSONView(generics.GenericAPIView): permission_classes = () queryset = models.Visualization.objects.all() def get(self, request, pk, format=None): def cell_value(cell): if cell.measure.type == 'duration' and cell.value is not None: return cell.value.total_seconds() return cell.value def labels(axis): return [x.label.strip() for x in axis] instance = self.get_object() loop = [] all_visualizations = Visualization.from_json(instance.parameters) for visualization in all_visualizations: drilldowns = visualization.drilldown if len(drilldowns) == 2: (x_axis, y_axis), grid = visualization.table_2d() axis = { 'x_labels': labels(x_axis), 'y_labels': labels(y_axis), } data = [] for y in y_axis: data.append([cell_value(grid[(x.id, y.id)]) for x in x_axis]) elif len(drilldowns) == 1: x_axis, grid = visualization.table_1d() if visualization.drilldown_x: axis = {'x_labels': labels(x_axis)} else: axis = {'y_labels': labels(x_axis)} data = [cell_value(grid[x.id]) for x in x_axis] elif len(drilldowns) == 0: data = cell_value(visualization.data()[0].measures[0]) axis = {} loop.append({ 'data': data, 'axis': axis }) if not all_visualizations.loop: data = loop[0]['data'] axis = loop[0]['axis'] else: axis = loop[0]['axis'] axis['loop'] = [x.label for x in all_visualizations.loop.members(all_visualizations.filters.items())] data = [x['data'] for x in loop] unit = 'seconds' if all_visualizations.measure.type == 'duration' else None measure = all_visualizations.measure.type return Response({ 'data': data, 'axis': axis, 'format': '1', 'unit': unit, # legacy, prefer measure. 'measure': measure, }) warehouse = WarehouseView.as_view() cube = CubeView.as_view() cube_iframe = xframe_options_exempt(CubeIframeView.as_view()) visualizations = VisualizationsView.as_view() visualizations_json = VisualizationsJSONView.as_view() create_visualization = CreateVisualizationView.as_view() delete_visualization = DeleteVisualizationView.as_view() rename_visualization = RenameVisualization.as_view() visualization = VisualizationView.as_view() visualization_iframe = xframe_options_exempt(VisualizationIframeView.as_view()) visualization_geojson = VisualizationGeoJSONView.as_view() visualization_ods = VisualizationODSView.as_view() visualization_json = VisualizationJSONView.as_view() cube_iframe.mellon_no_passive = True visualization_iframe.mellon_no_passive = True