# from exposition.models import Exposition import json import operator from datetime import date, timedelta import numpy import pandas from city.models import City # -*- coding: utf-8 -*- # from conference.models import Conference from country.models import Country from django.contrib.contenttypes.models import ContentType from django.core.serializers.json import DjangoJSONEncoder from django.db.models import Q from django.http import HttpResponse from django.template.loader import render_to_string from django.utils import timezone from django.utils.encoding import force_text from django.utils.translation import ugettext_lazy as _ from django.views.generic import FormView, TemplateView from theme.models import Tag, Theme from .forms import ( ByContentObjectsFilter, DateFilter, EventsDateFilter, EventsParamsFilter, event_choices, kind_choices ) from .models import ObjectStats, SectionStats kind_choices_dict = dict(kind_choices) class CustomJSEncoder(DjangoJSONEncoder): def default(self, o): # See "Date Time String Format" in the ECMA-262 specification. if isinstance(o, date): return 'Date({y}, {js_m}, {day})'.format( y=o.year, js_m=o.month - 1, day=o.day) else: return super(CustomJSEncoder, self).default(o) def pydate_to_js(o): return 'Date({y}, {js_m}, {day})'.format( y=o.year, js_m=o.month - 1, day=o.day) class StatBaseView(FormView): def get_template_names(self): if self.request.is_ajax(): return self.ajax_template_name return super(StatBaseView, self).get_template_names() def after_get(self): pass def get(self, request, *args, **kwargs): today = timezone.now().date() self.qs = self.qs.filter( created_at__gte=today - timedelta(days=7), created_at__lt=today ) self.after_get() return super(StatBaseView, self).get(request, *args, **kwargs) def form_valid(self, form): begin = form.cleaned_data['date_begin'] end = form.cleaned_data['date_end'] if (begin and end) and begin == end: self.qs = self.qs.filter(created_at=begin) else: if begin: self.qs = self.qs.filter(created_at__gte=begin) if end: self.qs = self.qs.filter(created_at__lte=end) def make_json_response(self, data): return HttpResponse(json.dumps(data), content_type='application/json') def get_dataframe(self): if self.df is not None: return self.df self.df = pandas.DataFrame(list(self.qs)) return self.df def get_pivot_table(self): if self.pt is not None: return self.pt df = self.get_dataframe() self.pt = df.pivot_table(values='value', index='created_at', columns=self.pt_columns, aggfunc=numpy.sum) return self.pt class StatSectionsView(StatBaseView): template_name = 'c_admin/stats/section_stat.html' ajax_template_name = 'c_admin/stats/content_type_form.html' form_class = DateFilter content_form_class = ByContentObjectsFilter def get_queryset(self): return SectionStats.objects\ .all()\ .values('created_at', 'section', 'kind', 'value', 'content_type', 'object_id') def dispatch(self, request, *args, **kwargs): self.qs = self.get_queryset() self.pt = self.df = self.content_form = self.ct_choices = None self.pt_columns = 'kind' return super(StatSectionsView, self).dispatch(request, *args, **kwargs) def form_valid(self, form): super(StatSectionsView, self).form_valid(form) if form.cleaned_data['section']: self.qs = self.qs.filter(section=form.cleaned_data['section']) if form.cleaned_data['kind']: self.qs = self.qs.filter(kind=form.cleaned_data['kind']) self.get_and_check_content_form() if self.request.is_ajax(): data = { 'success': True, 'html': render_to_string(self.ajax_template_name, self.get_context_data()) } return self.make_json_response(data) return self.render_to_response(self.get_context_data(form=form)) def form_invalid(self, form): if self.request.is_ajax(): data = { 'success': False, 'errors': form.errors } return self.make_json_response(data) return super(StatSectionsView, self).form_invalid(form) def get_ct_form(self): form = None check = False pairs = self.qs\ .filter(content_type__isnull=False, object_id__isnull=False)\ .values_list('content_type', 'object_id') if pairs: ct_id, object_ids = zip(*pairs) ct_id = list(set(ct_id)) object_ids = set(object_ids) if len(ct_id) == 1: ct_id = ct_id[0] form_kwargs = {} post_data = self.request.POST ct_model = ContentType.objects.get_for_id(ct_id) self.ct_choices = ct_model.model_class().objects.language().filter(pk__in=object_ids).values_list('pk', 'name').order_by('name') self.ct_choices_dict = dict(self.ct_choices) # detect if we need to validate form content_type = post_data.get('content_type', None) if content_type and int(content_type) == ct_id: check = True form_kwargs.update({ 'data': post_data, }) form_kwargs.update({ 'initial': {'content_type': ct_id}, 'choices': self.ct_choices, }) form = self.content_form_class(**form_kwargs) return form, check def get_and_check_content_form(self): self.content_form, check = self.get_ct_form() if check and self.content_form.is_valid() and self.content_form.cleaned_data['ct_objects']: self.qs = self.qs.filter( content_type=self.content_form.cleaned_data['content_type'], object_id__in=self.content_form.cleaned_data['ct_objects'] ) self.pt_columns = 'object_id' def get_col_map(self): if self.pt_columns == 'kind': return kind_choices_dict else: return self.ct_choices_dict def get_column_names(self): col_map = self.get_col_map() columns = [force_text(col_map[x]) for x in self.pt.columns.tolist()] return json.dumps(columns).replace('[', '').replace(']', '') def get_context_data(self, **kwargs): context = super(StatSectionsView, self).get_context_data(**kwargs) context['content_form'] = self.content_form if self.request.is_ajax(): return context if self.qs: pt = self.get_pivot_table() # change numpy.nan values to python's None (js null with json) # result = pt.where(pt.notnull(), None) result = pt.fillna(value=0) result_summary = result.sum().to_dict() context['columns'] = self.get_column_names() # if self.pt_columns != 'kind': # result.columns = list(map(lambda x: str(int(x)), result.columns.tolist())) data = list(map( # lambda x: {'date': pydate_to_js(x[0]), 'attrs': json.dumps(x[1:]).replace('[', '').replace(']', '')}, lambda x: json.dumps(list(x), cls=CustomJSEncoder), result.itertuples() )) data_summary = [] for key, val in result_summary.items(): data_summary.append({ 'name': self.get_col_map()[key], 'val': val }) context['data'] = data context['data_summary'] = data_summary return context class EventStatView(StatBaseView): form_class = EventsDateFilter params_form_cls = EventsParamsFilter pt_columns = ['content_type', 'object_id'] template_name = 'c_admin/stats/event_stat.html' def get_queryset(self): return ObjectStats.objects\ .all()\ .select_related()\ .values('created_at', 'value') def dispatch(self, request, *args, **kwargs): self.qs = self.get_queryset() self.pt = self.df = self.params_form = None return super(EventStatView, self).dispatch(request, *args, **kwargs) def after_get(self): self.params_form = self.get_params_form() def form_valid(self, form): super(EventStatView, self).form_valid(form) self.event_types = ['conference', 'exposition'] if form.cleaned_data['event']: params = {'{attr}_id__isnull'.format(attr=form.cleaned_data['event']): False} self.qs = self.qs.filter(**params) self.event_types = [form.cleaned_data['event'], ] self.params_form = self.get_params_form() self.check_params_form() return self.render_to_response(self.get_context_data(form=form)) def get_params_form(self): form_kwargs = {} country_ids = set() city_ids = set() theme_ids = set() tag_ids = set() val_list = [ 'exposition__country', 'conference__country', 'exposition__city', 'conference__city', 'exposition__theme', 'conference__theme', 'exposition__tag', 'conference__tag', ] values_list = self.qs.values_list(*val_list) if values_list: e_country_ids, c_country_ids, e_city_ids, c_city_ids, e_theme_ids, c_theme_ids, e_tag_ids, c_tag_ids = \ zip(*values_list) country_ids.update(e_country_ids) country_ids.update(c_country_ids) city_ids.update(e_city_ids) city_ids.update(c_city_ids) theme_ids.update(e_theme_ids) theme_ids.update(c_theme_ids) tag_ids.update(e_tag_ids) tag_ids.update(c_tag_ids) for ids_set in [country_ids, city_ids, theme_ids, tag_ids]: if None in ids_set: ids_set.remove(None) form_kwargs = { 'country_choices': Country.objects.language().filter(pk__in=country_ids).values_list('pk', 'name').order_by('name'), 'city_choices': City.objects.language().filter(pk__in=city_ids).values_list('pk', 'name').order_by('name'), 'theme_choices': Theme.objects.language().filter(pk__in=theme_ids).values_list('pk', 'name').order_by('name'), 'tag_choices': Tag.objects.language().filter(pk__in=tag_ids).values_list('pk', 'name').order_by('name'), 'data': self.request.POST } form = self.params_form_cls(**form_kwargs) return form def check_params_form(self): if self.params_form.is_valid(): query = [] for event_type in self.event_types: params = {} if self.params_form.cleaned_data['country']: params.update({ event_type + '__country_id__in': self.params_form.cleaned_data['country'] }) if self.params_form.cleaned_data['city']: params.update({ event_type + '__city_id__in': self.params_form.cleaned_data['city'] }) if self.params_form.cleaned_data['theme']: params.update({ event_type + '__theme__in': self.params_form.cleaned_data['theme'] }) if self.params_form.cleaned_data['tag']: params.update({ event_type + '__tag__in': self.params_form.cleaned_data['tag'] }) query.append(Q(**params)) if len(query) == 1: self.qs = self.qs.filter(query[0]) else: self.qs = self.qs.filter(reduce(operator.or_, query)) def get_context_data(self, **kwargs): context = super(EventStatView, self).get_context_data(**kwargs) context['params_form'] = self.params_form if self.qs: df = self.get_dataframe() pt = df.pivot_table(values='value', index='created_at', aggfunc=numpy.sum) result = pt.fillna(value=0) data = list(map( lambda x: json.dumps(list(x), cls=CustomJSEncoder), result.iteritems() )) context['data'] = data context['data_summary'] = int(result.sum()) return context