You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
350 lines
13 KiB
350 lines
13 KiB
# 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
|
|
|