From a96fc8ad63b1cb8ece6fdaff1e8dccc1e4f08e0d Mon Sep 17 00:00:00 2001 From: Alexander Burdeiny Date: Thu, 19 May 2016 00:18:46 +0300 Subject: [PATCH] =?UTF-8?q?1328:=20=D0=AD=D1=82=D0=B0=D0=BF=20=E2=84=963?= =?UTF-8?q?=20-=20=D0=A1=D1=82=D0=B0=D1=82=D0=B8=D1=81=D1=82=D0=B8=D0=BA?= =?UTF-8?q?=D0=B0=20=D1=80=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB=D0=BE=D0=B2=20See?= =?UTF-8?q?ms=20to=20be=20done.=20Tested=20localy.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- conference/admin.py | 54 +- conference/models.py | 3 + conference/urls.py | 23 +- conference/views.py | 22 +- exposition/admin.py | 51 +- exposition/models.py | 3 + exposition/urls.py | 29 +- exposition/views.py | 33 +- functions/admin_views.py | 10 +- proj/admin_urls.py | 12 +- proj/settings.py | 4 + proj/urls.py | 37 +- service/admin.py | 2 +- stats_collector/__init__.py | 0 stats_collector/admin.py | 344 ++++++++++ stats_collector/admin_urls.py | 9 + stats_collector/forms.py | 83 +++ stats_collector/management/__init__.py | 0 .../management/commands/__init__.py | 0 .../management/commands/stats_daily.py | 31 + .../management/commands/stats_hourly.py | 25 + .../management/commands/stats_test.py | 87 +++ stats_collector/migrations/0001_initial.py | 605 ++++++++++++++++++ stats_collector/migrations/__init__.py | 0 stats_collector/mixin.py | 79 +++ stats_collector/models.py | 102 +++ stats_collector/tests.py | 16 + stats_collector/utils.py | 15 + stats_collector/views.py | 1 + templates/admin/base.html | 44 +- .../admin/conference/conference_list.html | 4 +- .../admin/exposition/exposition_list.html | 2 +- templates/admin/includes/admin_nav.html | 12 +- templates/admin/stats/content_type_form.html | 9 + templates/admin/stats/event_params_form.html | 40 ++ templates/admin/stats/event_stat.html | 152 +++++ templates/admin/stats/section_stat.html | 191 ++++++ 37 files changed, 2027 insertions(+), 107 deletions(-) create mode 100644 stats_collector/__init__.py create mode 100644 stats_collector/admin.py create mode 100644 stats_collector/admin_urls.py create mode 100644 stats_collector/forms.py create mode 100644 stats_collector/management/__init__.py create mode 100644 stats_collector/management/commands/__init__.py create mode 100644 stats_collector/management/commands/stats_daily.py create mode 100644 stats_collector/management/commands/stats_hourly.py create mode 100644 stats_collector/management/commands/stats_test.py create mode 100644 stats_collector/migrations/0001_initial.py create mode 100644 stats_collector/migrations/__init__.py create mode 100644 stats_collector/mixin.py create mode 100644 stats_collector/models.py create mode 100644 stats_collector/tests.py create mode 100644 stats_collector/utils.py create mode 100644 stats_collector/views.py create mode 100644 templates/admin/stats/content_type_form.html create mode 100644 templates/admin/stats/event_params_form.html create mode 100644 templates/admin/stats/event_stat.html create mode 100644 templates/admin/stats/section_stat.html diff --git a/conference/admin.py b/conference/admin.py index 01a24f89..fdbea666 100644 --- a/conference/admin.py +++ b/conference/admin.py @@ -1,26 +1,41 @@ # -*- coding: utf-8 -*- -from django.shortcuts import render_to_response -from django.http import HttpResponseRedirect, HttpResponse -from django.core.context_processors import csrf + +import json +import random + +from city.models import City from django.conf import settings +from django.contrib.auth.decorators import login_required +from django.contrib.contenttypes.models import ContentType +from django.core.context_processors import csrf from django.forms.formsets import BaseFormSet, formset_factory from django.forms.models import modelformset_factory -from django.contrib.contenttypes.models import ContentType -from django.contrib.auth.decorators import login_required -#models and forms -from models import Conference, TimeTable, Statistic -from forms import ConferenceChangeForm, ConferenceCreateForm, ConferenceDeleteForm, TimeTableForm, StatisticForm, ConferenceFilterForm -from theme.models import Tag -from city.models import City +from django.http import HttpResponse, HttpResponseRedirect +from django.shortcuts import render_to_response +from django.utils import translation +from exposition.admin import get_by_lang +from file.forms import FileForm, FileModelForm from file.models import FileModel, TmpFile -from file.forms import FileModelForm, FileForm -from photologue.forms import PhotoForm -#python -import random -#custom views -from functions.custom_views import objects_list, delete_object +from forms import ( + ConferenceChangeForm, + ConferenceCreateForm, + ConferenceDeleteForm, + ConferenceFilterForm, + StatisticForm, + TimeTableForm +) +from functions.admin_views import ( + AdminListView, + AdminView, + stat_paginate_results, + upload_photo +) +from functions.custom_views import delete_object, objects_list from functions.views_help import get_referer -from functions.admin_views import AdminListView, AdminView, upload_photo +from haystack.query import SearchQuerySet +from models import Conference, Statistic, TimeTable +from photologue.forms import PhotoForm +from theme.models import Tag def conference_all(request): @@ -289,6 +304,7 @@ class ConferenceView(AdminView): class ConferenceListView(AdminListView): + paginate_func = staticmethod(stat_paginate_results) template_name = 'admin/conference/conference_list.html' form_class = ConferenceFilterForm model = Conference @@ -298,10 +314,6 @@ def upload_conference_photo(request, conf_id): return upload_photo(request, conf_id, Conference) -from django.utils import translation -from haystack.query import SearchQuerySet -import json -from exposition.admin import get_by_lang def search_conf(request): term = request.GET['term'].capitalize() diff --git a/conference/models.py b/conference/models.py index 65a92e05..e9e3ad43 100644 --- a/conference/models.py +++ b/conference/models.py @@ -202,6 +202,9 @@ class Conference(TranslatableModel, EventMixin, ExpoMixin): def clicks(self): return self.paid_new.tickets.clicks() + self.paid_new.participation.clicks() + self.paid_new.official.clicks() + def get_objectstat_views(self): + return sum(self.objectstats_set.all().values_list('value', flat=True)) + class Statistic(TranslatableModel): conference = models.ForeignKey(Conference, related_name='statistic') diff --git a/conference/urls.py b/conference/urls.py index db94bb3e..3b1c1757 100644 --- a/conference/urls.py +++ b/conference/urls.py @@ -1,10 +1,25 @@ # -*- coding: utf-8 -*- -from django.conf.urls import patterns, include, url -from views import ConferenceDetail, ConferenceList, ConferenceByCity, ConferenceByCountry, ConferenceByTheme,\ - ConferenceCountryCatalog, ConferenceCityCatalog, ConferenceTagCatalog, ConferenceThemeCatalog, ConferenceMembers,\ - ConferenceVisitors, ConferenceServiceView, ConferenceThankView, ConferenceByTag, ConferencePhotoView +from django.conf.urls import include, patterns, url from exposition.views import ExpositionSearchView +from .views import ( + ConferenceByCity, + ConferenceByCountry, + ConferenceByTag, + ConferenceByTheme, + ConferenceCityCatalog, + ConferenceCountryCatalog, + ConferenceDetail, + ConferenceList, + ConferenceMembers, + ConferencePhotoView, + ConferenceServiceView, + ConferenceTagCatalog, + ConferenceThankView, + ConferenceThemeCatalog, + ConferenceVisitors +) + urlpatterns = patterns('', diff --git a/conference/views.py b/conference/views.py index 6802cc6c..b192cb53 100644 --- a/conference/views.py +++ b/conference/views.py @@ -29,12 +29,17 @@ from note.models import Note from service.models import Service from service.order_forms import AdvertiseForm from service.views import order_forms +from stats_collector.mixin import ( + ConfSectionKindMixin, + ConfSectionMixin, + ObjectStatMixin +) from theme.models import Tag, Theme MONTHES = settings.MONTHES -class ConferenceBy(JitterCacheMixin, MetadataMixin, ListView): +class ConferenceBy(ConfSectionMixin, JitterCacheMixin, MetadataMixin, ListView): cache_range = settings.CACHE_RANGE template_name = 'client/conference/conference_by.html' title1 = '' @@ -55,6 +60,7 @@ class ConferenceByCountry(ConferenceBy): title1 = _(u'По странам') title2 = _(u'Коференции мира по странам') catalog = 'country/' + stat_kind = 'country' def get_queryset(self): return self.model.objects.conference_countries_with_count() @@ -68,6 +74,7 @@ class ConferenceByTag(ConferenceBy): title1 = _(u'По тегам') title2 = _(u'Коференции мира по тегам') catalog = 'tag/' + stat_kind = 'tag' def get_queryset(self): return self.model.active.conference_themes_with_count() @@ -79,6 +86,7 @@ class ConferenceByTheme(ConferenceBy): title1 = _(u'По тематикам') title2 = _(u'Коференции мира по тематикам') catalog = 'theme/' + stat_kind = 'theme' def get_queryset(self): return self.model.active.conference_themes_with_count() @@ -93,6 +101,7 @@ class ConferenceByCity(ConferenceBy): title1 = _(u'По городам') title2 = _(u'Коференции мира по городам') catalog = 'city/' + stat_kind = 'city' def get_queryset(self): return self.model.used.conference_cities_with_count() @@ -102,7 +111,7 @@ class ConferenceByCity(ConferenceBy): # .order_by('translations__name').distinct() -class ConferenceCatalog(JitterCacheMixin, MetadataMixin, ListView): +class ConferenceCatalog(ConfSectionKindMixin, JitterCacheMixin, MetadataMixin, ListView): cache_range = settings.CACHE_RANGE model = Conference paginate_by = settings.CLIENT_PAGINATION @@ -171,6 +180,7 @@ class ConferenceCatalog(JitterCacheMixin, MetadataMixin, ListView): class ConferenceCountryCatalog(ConferenceCatalog): catalog_url = '/conference/country/' + stat_kind = 'country' def get_filtered_qs(self): #this method used in parent get_queryset @@ -189,6 +199,7 @@ class ConferenceCountryCatalog(ConferenceCatalog): class ConferenceCityCatalog(ConferenceCatalog): catalog_url = '/conference/city/' + stat_kind = 'city' def get_filtered_qs(self): #this method used in parent get_queryset slug = self.kwargs.get('slug') @@ -209,6 +220,7 @@ class ConferenceThemeCatalog(ConferenceCatalog): catalog_url = '/conference/theme/' country = None city = None + stat_kind = 'theme' def get_filtered_qs(self): #this method used in parent get_queryset @@ -246,6 +258,7 @@ class ConferenceThemeCatalog(ConferenceCatalog): class ConferenceTagCatalog(ConferenceCatalog): catalog_url = '/conference/tag/' + stat_kind = 'tag' def get_filtered_qs(self): #this method used in parent get_queryset slug = self.kwargs.get('slug') @@ -308,7 +321,6 @@ class ConferenceMembers(MetadataMixin, ListView): return context - class ConferenceThankView(MetadataMixin, DetailView): model = Conference slug_field = 'url' @@ -383,7 +395,7 @@ class ConferenceServiceView(FormMixin, DetailView): return self.initial.copy() -class ConferenceDetail(JitterCacheMixin, MetadataMixin, DetailView): +class ConferenceDetail(ObjectStatMixin, JitterCacheMixin, MetadataMixin, DetailView): cache_range = settings.CACHE_RANGE model = Conference slug_field = 'url' @@ -410,7 +422,7 @@ class ConferenceDetail(JitterCacheMixin, MetadataMixin, DetailView): return context -class ConferenceList(MetadataMixin, JitterCacheMixin, ListView): +class ConferenceList(ConfSectionMixin, MetadataMixin, JitterCacheMixin, ListView): cache_range = settings.CACHE_RANGE model = Conference paginate_by = settings.CLIENT_PAGINATION diff --git a/exposition/admin.py b/exposition/admin.py index f694e6e8..971fcd62 100644 --- a/exposition/admin.py +++ b/exposition/admin.py @@ -1,28 +1,41 @@ # -*- coding: utf-8 -*- + import json -from django.shortcuts import render_to_response -from django.http import HttpResponseRedirect, HttpResponse -from django.core.context_processors import csrf +import random + +from city.models import City from django.conf import settings +from django.contrib.auth.decorators import login_required +from django.contrib.contenttypes.models import ContentType +from django.core.context_processors import csrf from django.forms.formsets import BaseFormSet, formset_factory from django.forms.models import modelformset_factory -from django.contrib.contenttypes.models import ContentType -from django.contrib.auth.decorators import login_required -# models and forms -from haystack.query import SearchQuerySet -from models import Exposition, TimeTable, Statistic, TmpTimeTable -from forms import ExpositionCreateForm, ExpositionDeleteForm, TimeTableForm, StatisticForm, ExpositionFilterForm -from theme.models import Tag -from city.models import City +from django.http import HttpResponse, HttpResponseRedirect +from django.shortcuts import render_to_response +from django.utils import translation +from file.forms import FileForm, FileModelForm from file.models import FileModel, TmpFile -from file.forms import FileModelForm, FileForm -from photologue.forms import PhotoForm -# python -import random -# custom views -from functions.custom_views import objects_list, delete_object +from forms import ( + ExpositionCreateForm, + ExpositionDeleteForm, + ExpositionFilterForm, + StatisticForm, + TimeTableForm +) +from functions.admin_views import ( + AdminListView, + AdminView, + stat_paginate_results, + upload_photo +) +from functions.custom_views import delete_object, objects_list from functions.views_help import get_referer -from functions.admin_views import AdminListView, AdminView, upload_photo +from haystack.query import SearchQuerySet +from models import Exposition, Statistic, TimeTable, TmpTimeTable +from photologue.forms import PhotoForm +from theme.models import Tag + + def exposition_all(request): @@ -330,6 +343,7 @@ class ExpositionView(AdminView): return context class ExpositionListView(AdminListView): + paginate_func = staticmethod(stat_paginate_results) template_name = 'admin/exposition/exposition_list.html' form_class = ExpositionFilterForm model = Exposition @@ -347,7 +361,6 @@ def get_by_lang(item, field, lang='en'): :return: """ return getattr(item, field+'_'+lang) -from django.utils import translation def search_expo(request): term = request.GET['term'].capitalize() diff --git a/exposition/models.py b/exposition/models.py index 2668150b..7a1febe6 100644 --- a/exposition/models.py +++ b/exposition/models.py @@ -290,6 +290,9 @@ class Exposition(TranslatableModel, EventMixin, ExpoMixin): def clicks(self): return self.paid_new.tickets.clicks() + self.paid_new.participation.clicks() + self.paid_new.official.clicks() + def get_objectstat_views(self): + return sum(self.objectstats_set.all().values_list('value', flat=True)) + class Statistic(TranslatableModel): exposition = models.ForeignKey(Exposition, related_name='statistic') diff --git a/exposition/urls.py b/exposition/urls.py index 9b8d39db..a59253d0 100644 --- a/exposition/urls.py +++ b/exposition/urls.py @@ -1,12 +1,27 @@ # -*- coding: utf-8 -*- -from django.conf.urls import patterns, include, url -from views import ExpositionStatistic, ExpositionPrice, ExpositionProgramme, ExpositionSearchView, \ - ExpositionByCountry, ExpositionByTheme, ExpositionByCity, ExpositionByTag, ExpoPhotoView - -from views import ExpositionServiceView -from views import ExpoCountryCatalog, ExpoCityCatalog, ExpoThemeCatalog, ExpoTagCatalog, ExpoList, ExpoDetail,\ - ExpoVisitors, ExpoMembers, ExpositionThankView +from django.conf.urls import include, patterns, url +from .views import ( + ExpoCityCatalog, + ExpoCountryCatalog, + ExpoDetail, + ExpoList, + ExpoMembers, + ExpoPhotoView, + ExpositionByCity, + ExpositionByCountry, + ExpositionByTag, + ExpositionByTheme, + ExpositionPrice, + ExpositionProgramme, + ExpositionSearchView, + ExpositionServiceView, + ExpositionStatistic, + ExpositionThankView, + ExpoTagCatalog, + ExpoThemeCatalog, + ExpoVisitors +) urlpatterns = patterns('', url(r'^expo/add-note/(?P.*)/$', 'exposition.views.add_note'), diff --git a/exposition/views.py b/exposition/views.py index afe75814..a874a1ad 100644 --- a/exposition/views.py +++ b/exposition/views.py @@ -4,6 +4,7 @@ import datetime import json from accounts.models import User +from article.models import Article from city.models import City from country.models import Country from django.conf import settings @@ -28,21 +29,26 @@ from note.models import Note from service.models import Service from service.order_forms import AdvertiseForm from service.views import order_forms +# from stats_collector.models import SectionStats +from stats_collector.mixin import ( + ExpoSectionKindMixin, + ExpoSectionMixin, + ObjectStatMixin +) from theme.models import Tag, Theme -from article.models import Article -class ExpositionBy(JitterCacheMixin, MetadataMixin, ListView): +class ExpositionBy(ExpoSectionMixin, JitterCacheMixin, MetadataMixin, ListView): template_name = 'exposition/exposition_by.html' title1 = '' title2 = '' + """ abstact class """ def get_context_data(self, **kwargs): context = super(ExpositionBy, self).get_context_data(**kwargs) context.update({'title1': self.title1, 'title2': self.title2, 'catalog': self.catalog}) - return context @@ -52,6 +58,7 @@ class ExpositionByCountry(ExpositionBy): title1 = _(u'По странам') title2 = _(u'Выставки мира по странам') catalog = 'country/' + stat_kind = 'country' def get_queryset(self): return self.model.objects.expo_countries_with_count() @@ -67,6 +74,7 @@ class ExpositionByTheme(ExpositionBy): title1 = _(u'По тематикам') title2 = _(u'Выставки мира по тематикам') catalog = 'theme/' + stat_kind = 'theme' def get_queryset(self): return self.model.active.expo_themes_with_count() @@ -75,24 +83,26 @@ class ExpositionByTheme(ExpositionBy): # .filter(exposition_themes__theme__isnull=False, translations__language_code=lang)\ # .order_by('translations__name').distinct() + class ExpositionByTag(ExpositionBy): cache_range = [60*30, 60*60] model = Tag title1 = _(u'По тегам') title2 = _(u'Выставки мира по тегам') catalog = 'tag/' + stat_kind = 'tag' def get_queryset(self): return self.model.active.expo_themes_with_count() - class ExpositionByCity(ExpositionBy): cache_range = [60*30, 60*60] model = City title1 = _(u'По городам') title2 = _(u'Выставки мира по городам') catalog = 'city/' + stat_kind = 'city' def get_queryset(self): return self.model.used.expo_cities_with_count() @@ -129,6 +139,7 @@ def exposition_add_calendar(request, id): return HttpResponse(json.dumps(args), content_type='application/json') + def exposition_visit(request, id): args = {'success': False} user = request.user @@ -147,10 +158,9 @@ def exposition_visit(request, id): args['success'] = True return HttpResponse(json.dumps(args), content_type='application/json') -#------------------------------------------------------------------------------ -class ExpoDetail(JitterCacheMixin, MetadataMixin, DetailView): +class ExpoDetail(ObjectStatMixin, JitterCacheMixin, MetadataMixin, DetailView): cache_range = [60*30, 60*60] model = Exposition slug_field = 'url' @@ -243,6 +253,7 @@ def visit_redirect(request, slug): redirect = obj.get_permanent_url() + 'price/' return HttpResponsePermanentRedirect(redirect) + class ExpositionServiceView(JitterCacheMixin, MetadataMixin, FormMixin, DetailView): cache_range = [60*30, 60*60] model = Exposition @@ -313,7 +324,7 @@ class ExpositionServiceView(JitterCacheMixin, MetadataMixin, FormMixin, DetailVi return self.initial.copy() -class ExpoList(MetadataMixin, JitterCacheMixin, ListView): +class ExpoList(ExpoSectionMixin, MetadataMixin, JitterCacheMixin, ListView): cache_range = [60*30, 60*60] model = Exposition paginate_by = settings.CLIENT_PAGINATION @@ -349,8 +360,7 @@ class ExpoList(MetadataMixin, JitterCacheMixin, ListView): return context - -class ExpoCatalog(JitterCacheMixin, MetadataMixin, ListView): +class ExpoCatalog(ExpoSectionKindMixin, JitterCacheMixin, MetadataMixin, ListView): model = Exposition paginate_by = settings.CLIENT_PAGINATION template_name = 'client/exposition/catalog.html' @@ -425,6 +435,7 @@ class ExpoCatalog(JitterCacheMixin, MetadataMixin, ListView): class ExpoCountryCatalog(ExpoCatalog): catalog_url = '/expo/country/' + stat_kind = 'country' def get_filtered_qs(self): #this method used in parent get_queryset slug = self.kwargs.get('slug') @@ -442,6 +453,7 @@ class ExpoCountryCatalog(ExpoCatalog): class ExpoCityCatalog(ExpoCatalog): catalog_url = '/expo/city/' + stat_kind = 'city' def get_filtered_qs(self): #this method used in parent get_queryset slug = self.kwargs.get('slug') @@ -450,6 +462,7 @@ class ExpoCityCatalog(ExpoCatalog): qs = self.model.enable.upcoming().filter(city=city) self.filter_object = city return qs + def get_context_data(self, **kwargs): context = super(ExpoCityCatalog, self).get_context_data(**kwargs) city = self.kwargs['city'] @@ -461,6 +474,7 @@ class ExpoCityCatalog(ExpoCatalog): class ExpoThemeCatalog(ExpoCatalog): template_name = 'client/exposition/catalog_theme.html' catalog_url = '/expo/theme/' + stat_kind = 'theme' country = None city = None @@ -501,6 +515,7 @@ class ExpoThemeCatalog(ExpoCatalog): class ExpoTagCatalog(ExpoCatalog): catalog_url = '/expo/tag/' + stat_kind = 'tag' def get_filtered_qs(self): #this method used in parent get_queryset diff --git a/functions/admin_views.py b/functions/admin_views.py index 8a8ce342..a4ef8305 100644 --- a/functions/admin_views.py +++ b/functions/admin_views.py @@ -90,9 +90,15 @@ def paginate_results(qs, page=None): return result +def stat_paginate_results(qs, page=None): + result = paginate_results(qs, page=page) + result.object_list = result.object_list.prefetch_related('objectstats_set') + return result + from hvad.models import TranslatableModel class AdminListView(FormView): + paginate_func = staticmethod(paginate_results) def get_form(self, form_class): if self.request.GET: @@ -120,7 +126,7 @@ class AdminListView(FormView): """ qs = form.filter() - result = paginate_results(qs, page=self.request.GET.get('page')) + result = self.paginate_func(qs, page=self.request.GET.get('page')) context = self.get_context_data(form=form) context.update({'object_list': result}) return self.render_to_response(context) @@ -131,7 +137,7 @@ class AdminListView(FormView): qs = self.model.objects.language().all().order_by('name') else: qs = self.model.objects.all().order_by('-modified') - result = paginate_results(qs, page=self.request.GET.get('page')) + result = self.paginate_func(qs, page=self.request.GET.get('page')) context['object_list'] = result return context diff --git a/proj/admin_urls.py b/proj/admin_urls.py index f566e642..d65b49c4 100644 --- a/proj/admin_urls.py +++ b/proj/admin_urls.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -from django.conf.urls import patterns, include, url -from proj.admin import AdminIndex -from functions import required +from django.conf.urls import include, patterns, url +from django.contrib import admin from django.contrib.admin.views.decorators import staff_member_required - +from functions import required +from proj.admin import AdminIndex urlpatterns = required( @@ -36,6 +36,7 @@ urlpatterns = required( url(r'^import_xls/', include('import_xls.admin_urls')), url(r'^translator_catalog/', include('specialist_catalog.admin_urls')), url(r'^newsletters/', include('emencia.django.newsletter.admin_urls')), + url(r'^stats/', include('stats_collector.admin_urls')), url(r'^language/add/', 'directories.admin.language_add'), url(r'^currency/add/', 'directories.admin.currency_add'), @@ -57,5 +58,8 @@ urlpatterns = required( # url(r'^ckeditor/', include('ckeditor.urls')), url(r'^tinymce/', include('tinymce.urls')), + + # url(r'^stats/', include('statsy.urls')), + ) ) diff --git a/proj/settings.py b/proj/settings.py index 515f9b7e..390ea646 100644 --- a/proj/settings.py +++ b/proj/settings.py @@ -315,6 +315,7 @@ SOCIAL_AUTH_LINKEDIN_SCOPE = ['email'] INSTALLED_APPS = ( + 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', @@ -325,6 +326,7 @@ INSTALLED_APPS = ( 'django.contrib.sitemaps', 'haystack', #custom modules + 'stats_collector', 'emencia.django.newsletter', 'accounts', 'article', @@ -370,6 +372,8 @@ INSTALLED_APPS = ( 'social.apps.django_app.default', # social auth 'core', 'specialist_catalog', + 'graphos', + # 'statsy', ) CRONJOBS = [ diff --git a/proj/urls.py b/proj/urls.py index 7e9a6380..bddbffa4 100644 --- a/proj/urls.py +++ b/proj/urls.py @@ -1,12 +1,34 @@ # -*- coding: utf-8 -*- +import debug_toolbar +from core.simple_index_view import ( + AboutView, + AdvertisingView, + AdvertisingViewLanding +) from django.conf import settings -from django.conf.urls import patterns, include, url -from core.simple_index_view import AdvertisingView, AboutView, AdvertisingViewLanding -from views import MainPageView +from django.conf.urls import include, patterns, url +from django.conf.urls.static import static from django.contrib.sitemaps import views -import debug_toolbar -from solid_i18n.urls import solid_i18n_patterns from django.views.generic.base import TemplateView +from sitemaps import ( + Additional, + BlogsSiteMap, + ConfCard, + ConfCity, + ConfCountry, + ConfTag, + ConfTheme, + ExpoCard, + ExpoCity, + ExpoCountry, + ExpoTag, + ExpoTheme, + Important, + NewsSiteMap +) +from solid_i18n.urls import solid_i18n_patterns +from views import MainPageView + class Robot(TemplateView): template_name = 'client/robots.txt' @@ -16,8 +38,6 @@ class YandexCheck(TemplateView): template_name = 'client/simple_pages/yandex_check.html' -from sitemaps import ExpoCard, ExpoCity, ExpoCountry, ExpoTheme, ExpoTag, ConfCard, ConfCity, ConfCountry, ConfTheme,\ - ConfTag, NewsSiteMap, BlogsSiteMap, Additional, Important sitemaps = { @@ -84,6 +104,7 @@ if settings.DEBUG: (r'media/(?P.*)', 'serve', {'document_root': settings.MEDIA_ROOT}), ) + urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) import debug_toolbar urlpatterns += patterns('', @@ -105,5 +126,3 @@ urlpatterns += solid_i18n_patterns('', url(r'^', include('settings.old_urls')), url(r'^', include('accounts.user_catalog_urls')), ) - - diff --git a/service/admin.py b/service/admin.py index 752890b4..b994c805 100644 --- a/service/admin.py +++ b/service/admin.py @@ -130,7 +130,7 @@ class LinkedServiceUpdateView(UpdateView): model = LinkedService template_name = "admin/service/linked_service.html" success_url = "/admin/service/control/all/" - + def get_object(self, queryset=None): url = self.kwargs['url'] service = get_object_or_404(Service, url=url) diff --git a/stats_collector/__init__.py b/stats_collector/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/stats_collector/admin.py b/stats_collector/admin.py new file mode 100644 index 00000000..13a2e280 --- /dev/null +++ b/stats_collector/admin.py @@ -0,0 +1,344 @@ +# 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 graphos.renderers import flot, gchart +from graphos.sources.model import ModelDataSource +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): + if form.cleaned_data['date_begin']: + self.qs = self.qs.filter(created_at__gte=form.cleaned_data['date_begin']) + if form.cleaned_data['date_end']: + self.qs = self.qs.filter(created_at__lte=form.cleaned_data['date_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 = 'admin/stats/section_stat.html' + ajax_template_name = '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 + + 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 = '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): + 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', + ] + 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(*self.qs.values_list(*val_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(e_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 diff --git a/stats_collector/admin_urls.py b/stats_collector/admin_urls.py new file mode 100644 index 00000000..ff469ced --- /dev/null +++ b/stats_collector/admin_urls.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +from django.conf.urls import include, patterns, url + +from .admin import StatSectionsView, EventStatView + +urlpatterns = patterns('', + url(r'^events/$', EventStatView.as_view()), + url(r'^$', StatSectionsView.as_view()), +) diff --git a/stats_collector/forms.py b/stats_collector/forms.py new file mode 100644 index 00000000..a846d275 --- /dev/null +++ b/stats_collector/forms.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +from datetime import timedelta + +from conference.models import Conference +from django import forms +from django.contrib.contenttypes.models import ContentType +from django.utils.translation import ugettext_lazy as _ +from exposition.models import Exposition + +_all = (('', _(u'Все')), ) + +section_choices = ( + ('expo', _(u'Конференции')), + ('conf', _(u'Выставки')), +) + +kind_choices = ( + ('city', _(u'Город')), + ('country', _(u'Страна')), + ('tag', _(u'Тег')), + ('theme', _(u'Тема')), +) + + +class BaseDateFilter(forms.Form): + date_begin = forms.DateField(label=_(u'Дата начала'), input_formats=['%Y-%m-%d', '%d.%m.%Y'], required=False) + date_end = forms.DateField(label=_(u'Дата окончания'), input_formats=['%Y-%m-%d', '%d.%m.%Y'], required=False) + + def clean(self): + if not self.cleaned_data['date_begin'] and self.cleaned_data['date_end']: + self.cleaned_data['date_begin'] = self.cleaned_data['date_end'] - timedelta(days=14) + return self.cleaned_data + + +class DateFilter(BaseDateFilter): + section = forms.ChoiceField(label=_(u'Раздел'), choices=_all + section_choices, required=False) + kind = forms.ChoiceField(label=_(u'Тип'), choices=_all + kind_choices, required=False) + + +class ByContentObjectsFilter(forms.Form): + content_type = forms.IntegerField(label=_(u'Тип объекта'), widget=forms.HiddenInput(), required=False) + ct_objects = forms.TypedMultipleChoiceField(label=_(u'Объект'), required=False, coerce=int) + + def __init__(self, *args, **kwargs): + choices = kwargs.pop('choices') + super(ByContentObjectsFilter, self).__init__(*args, **kwargs) + self.fields['ct_objects'].choices = choices + + def clean_content_type(self): + pk = self.cleaned_data['content_type'] + if pk: + return ContentType.objects.get_for_id(pk) + return None + + +event_choices = ( + ('conference', _(u'Конференции')), + ('exposition', _(u'Выставки')) +) + + +class EventsDateFilter(BaseDateFilter): + event = forms.ChoiceField(label=_(u'Событие'), choices=_all + event_choices, required=False) + + +class EventsParamsFilter(forms.Form): + country = forms.TypedMultipleChoiceField(label=_(u'Страна'), required=False, coerce=int) + city = forms.TypedMultipleChoiceField(label=_(u'Город'), required=False, coerce=int) + theme = forms.TypedMultipleChoiceField(label=_(u'Тема'), required=False, coerce=int) + tag = forms.TypedMultipleChoiceField(label=_(u'Тег'), required=False, coerce=int) + + def __init__(self, *args, **kwargs): + country_choices = kwargs.pop('country_choices') + city_choices = kwargs.pop('city_choices') + theme_choices = kwargs.pop('theme_choices') + tag_choices = kwargs.pop('tag_choices') + + super(EventsParamsFilter, self).__init__(*args, **kwargs) + + self.fields['country'].choices = country_choices + self.fields['city'].choices = city_choices + self.fields['theme'].choices = theme_choices + self.fields['tag'].choices = tag_choices diff --git a/stats_collector/management/__init__.py b/stats_collector/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/stats_collector/management/commands/__init__.py b/stats_collector/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/stats_collector/management/commands/stats_daily.py b/stats_collector/management/commands/stats_daily.py new file mode 100644 index 00000000..4eff3623 --- /dev/null +++ b/stats_collector/management/commands/stats_daily.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +from optparse import make_option +from datetime import timedelta + +from django.core.cache import cache +from django.core.management.base import BaseCommand +from django.utils import timezone +from stats_collector.models import ObjectStats, SectionStats +from stats_collector.utils import unique_items_iterator + + +class Command(BaseCommand): + def handle(self, *args, **options): + prev_day = timezone.now().date() - timedelta(days=1) + for model in [ObjectStats, SectionStats]: + key = model.make_key(prev_day) + + # pop items from redis + l = cache._client.lrange(key, 0, -1) + cache._client.ltrim(key, len(l), -1) + + unique_items = {} + for key, val in unique_items_iterator(l): + _val = unique_items.get(key, 0) + unique_items[key] = _val + val + + if unique_items: + model_objects = [] + for k, v in unique_items.items(): + model_objects.append(model.create_instance(*k + (v, ), created_at=prev_day)) + model.objects.bulk_create(model_objects) diff --git a/stats_collector/management/commands/stats_hourly.py b/stats_collector/management/commands/stats_hourly.py new file mode 100644 index 00000000..78ee225c --- /dev/null +++ b/stats_collector/management/commands/stats_hourly.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +from optparse import make_option +from django.core.management.base import BaseCommand + +from django.core.cache import cache +from stats_collector.models import ObjectStats, SectionStats +from stats_collector.utils import unique_items_iterator + + +class Command(BaseCommand): + def handle(self, *args, **options): + pipe = cache._client.pipeline() + for model in [ObjectStats, SectionStats]: + key = model.make_key() + # pop items from redis + l = cache._client.lrange(key, 0, -1) + # cache._client.ltrim(key, len(l), -1) + pipe.ltrim(key, len(l), -1) + unique_items = {} + for k, v in unique_items_iterator(l): + _val = unique_items.get(k, 0) + unique_items[k] = _val + v + for k, v in unique_items.items(): + pipe.rpush(key, model.dump_item(k + (v,))) + pipe.execute() diff --git a/stats_collector/management/commands/stats_test.py b/stats_collector/management/commands/stats_test.py new file mode 100644 index 00000000..bc83fff4 --- /dev/null +++ b/stats_collector/management/commands/stats_test.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +from datetime import timedelta +from optparse import make_option +from random import randint + +from city.models import City +from conference.models import Conference +from country.models import Country +from django.contrib.contenttypes.models import ContentType +from django.core.cache import cache +from django.core.management.base import BaseCommand +from django.db.models.fields import related +from django.utils import timezone +from exposition.models import Exposition +from stats_collector.models import ObjectStats, SectionStats +from stats_collector.utils import unique_items_iterator +from theme.models import Tag, Theme + +today = timezone.now().date() + + +class Command(BaseCommand): + def handle(self, *args, **options): + self.create_section_test_data() + self.create_objects_test_data() + + def create_section_test_data(self): + total_objects = [Conference.objects.all()[:30], Exposition.objects.all()[:30]] + + bulk_objects = [] + for day in xrange(92): + date = today - timedelta(days=day) + for model, field in [(City, 'city'), (Country, 'country'), (Theme, 'theme'), (Tag, 'tag')]: + for _objects in total_objects: + obj = _objects[randint(0, 29)] + field_value = getattr(obj, field) + if isinstance(obj._meta.get_field_by_name(field)[0], related.ManyToManyField): + _field_value = field_value.all() + if len(_field_value) == 0: + continue + field_value = _field_value[randint(0, len(_field_value) - 1)] + bulk_objects.append( + SectionStats( + section=obj.__class__.__name__.lower()[:4], + kind=field, + content_object=field_value, + created_at=date, + value=randint(11, 898), + ) + ) + bulk_objects.append( + SectionStats( + section=obj.__class__.__name__.lower()[:4], + kind=field, + created_at=date, + value=randint(11, 898), + ) + ) + bulk_objects.append( + SectionStats( + section=obj.__class__.__name__.lower()[:4], + created_at=date, + value=randint(11, 898), + ) + ) + SectionStats.objects.bulk_create(bulk_objects) + + def create_objects_test_data(self): + total_objects = { + 'conference': Conference.objects.all()[:30], + 'exposition': Exposition.objects.all()[:30] + } + + bulk_objects = [] + for day in xrange(92): + date = today - timedelta(days=day) + # for model, field in [(City, 'city'), (Country, 'country'), (Theme, 'theme'), (Tag, 'tag')]: + for attr, _objects in total_objects.items(): + for obj in _objects: + bulk_objects.append( + ObjectStats( + created_at=date, + value=randint(11, 898), + **{attr: obj} + ) + ) + ObjectStats.objects.bulk_create(bulk_objects) diff --git a/stats_collector/migrations/0001_initial.py b/stats_collector/migrations/0001_initial.py new file mode 100644 index 00000000..7931a964 --- /dev/null +++ b/stats_collector/migrations/0001_initial.py @@ -0,0 +1,605 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'SectionStats' + db.create_table(u'stats_collector_sectionstats', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('created_at', self.gf('django.db.models.fields.DateField')(db_index=True, null=True, blank=True)), + ('value', self.gf('django.db.models.fields.PositiveIntegerField')(null=True, blank=True)), + ('content_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['contenttypes.ContentType'], null=True, blank=True)), + ('object_id', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), + ('section', self.gf('django.db.models.fields.CharField')(max_length=32, null=True, blank=True)), + ('kind', self.gf('django.db.models.fields.CharField')(max_length=64, null=True, blank=True)), + )) + db.send_create_signal(u'stats_collector', ['SectionStats']) + + # Adding index on 'SectionStats', fields ['content_type', 'object_id'] + db.create_index(u'stats_collector_sectionstats', ['content_type_id', 'object_id']) + + # Adding model 'ObjectStats' + db.create_table(u'stats_collector_objectstats', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('created_at', self.gf('django.db.models.fields.DateField')(db_index=True, null=True, blank=True)), + ('value', self.gf('django.db.models.fields.PositiveIntegerField')(null=True, blank=True)), + ('exposition', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['exposition.Exposition'], null=True, blank=True)), + ('conference', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['conference.Conference'], null=True, blank=True)), + )) + db.send_create_signal(u'stats_collector', ['ObjectStats']) + + + def backwards(self, orm): + # Removing index on 'SectionStats', fields ['content_type', 'object_id'] + db.delete_index(u'stats_collector_sectionstats', ['content_type_id', 'object_id']) + + # Deleting model 'SectionStats' + db.delete_table(u'stats_collector_sectionstats') + + # Deleting model 'ObjectStats' + db.delete_table(u'stats_collector_objectstats') + + + models = { + u'accounts.user': { + 'Meta': {'ordering': "['-rating']", 'object_name': 'User'}, + 'blocked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'company': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'users'", 'null': 'True', 'to': u"orm['company.Company']"}), + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'date_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'date_registered': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '255', 'db_index': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_admin': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'organiser': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['organiser.Organiser']", 'unique': 'True', 'null': 'True', 'on_delete': 'models.PROTECT', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'position': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'rating': ('django.db.models.fields.IntegerField', [], {'default': '100'}), + 'translator': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user'", 'null': 'True', 'on_delete': 'models.PROTECT', 'to': u"orm['translator.Translator']", 'blank': 'True', 'unique': 'True'}), + 'url': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'blank': 'True'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}) + }, + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'city.city': { + 'Meta': {'unique_together': '()', 'object_name': 'City', 'index_together': '()'}, + 'code_IATA': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['directories.Iata']", 'null': 'True', 'blank': 'True'}), + 'country': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'cities'", 'null': 'True', 'on_delete': 'models.PROTECT', 'to': u"orm['country.Country']"}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'inflect': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'logo': ('django.db.models.fields.files.ImageField', [], {'max_length': '255', 'blank': 'True'}), + 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'old_url': ('django.db.models.fields.CharField', [], {'max_length': '55'}), + 'phone_code': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'population': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'services': ('django.db.models.fields.BigIntegerField', [], {'default': 'None'}), + 'url': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}) + }, + u'company.company': { + 'Meta': {'ordering': "['-rating', 'id']", 'unique_together': '()', 'object_name': 'Company', 'index_together': '()'}, + 'address': ('functions.custom_fields.LocationField', [], {'blank': 'True'}), + 'blocked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'city': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'companies'", 'null': 'True', 'on_delete': 'models.PROTECT', 'to': u"orm['city.City']"}), + 'country': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'companies'", 'null': 'True', 'on_delete': 'models.PROTECT', 'to': u"orm['country.Country']"}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_company'", 'null': 'True', 'to': u"orm['accounts.User']"}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'facebook': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + 'fax': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'foundation': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'linkedin': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + 'logo': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'blank': 'True'}), + 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'phone': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'rating': ('django.db.models.fields.IntegerField', [], {'default': '100'}), + 'staff_number': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}), + 'tag': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'companies'", 'null': 'True', 'symmetrical': 'False', 'to': u"orm['theme.Tag']"}), + 'theme': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'companies'", 'null': 'True', 'symmetrical': 'False', 'to': u"orm['theme.Theme']"}), + 'twitter': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + 'url': ('django.db.models.fields.SlugField', [], {'max_length': '255'}), + 'vk': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + 'web_page': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}) + }, + u'conference.conference': { + 'Meta': {'unique_together': '()', 'object_name': 'Conference', 'index_together': '()'}, + 'audience': ('django.db.models.fields.BigIntegerField', [], {'default': 'None'}), + 'canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'canceled_by_administrator': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'city': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'conference_city'", 'on_delete': 'models.PROTECT', 'to': u"orm['city.City']"}), + 'company': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'conference_companies'", 'null': 'True', 'symmetrical': 'False', 'to': u"orm['company.Company']"}), + 'country': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'conference_country'", 'on_delete': 'models.PROTECT', 'to': u"orm['country.Country']"}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'currency': ('functions.custom_fields.EnumField', [], {'default': "'RUB'", 'values': "('RUB', 'USD', 'EUR', 'RMB', 'GBP', 'AED', 'SGD', 'TRY', 'CZK', 'CHF', 'SEK', 'LKR', 'UAH', 'IDR', 'PLN', 'JPY')"}), + 'data_begin': ('django.db.models.fields.DateField', [], {}), + 'data_end': ('django.db.models.fields.DateField', [], {}), + 'discount': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'expohit': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'foundation_year': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_published': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'link': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'logo': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'blank': 'True'}), + 'main': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['expobanner.MainPage']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), + 'main_page': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'db_index': 'True'}), + 'max_price': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'members': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'min_price': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'moved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'old_url': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255'}), + 'org': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'organiser': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'conference_organisers'", 'null': 'True', 'symmetrical': 'False', 'to': u"orm['organiser.Organiser']"}), + 'paid_new': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['expobanner.Paid']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), + 'periodic': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'photogallery': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['photologue.Gallery']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), + 'place': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'conference_place'", 'null': 'True', 'on_delete': 'models.PROTECT', 'to': u"orm['place_conference.PlaceConference']"}), + 'place_alt': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'quality_label': ('django.db.models.fields.BigIntegerField', [], {'default': 'None'}), + 'rating': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'}), + 'services': ('django.db.models.fields.BigIntegerField', [], {'default': 'None'}), + 'tag': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'conference_tags'", 'null': 'True', 'symmetrical': 'False', 'to': u"orm['theme.Tag']"}), + 'tax': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'theme': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'conference_themes'", 'symmetrical': 'False', 'to': u"orm['theme.Theme']"}), + 'url': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255'}), + 'users': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'conference_users'", 'null': 'True', 'symmetrical': 'False', 'to': u"orm['accounts.User']"}), + 'views': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'visitors': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'web_page': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'country.area': { + 'Meta': {'ordering': "['translations__name']", 'unique_together': '()', 'object_name': 'Area', 'index_together': '()'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + u'country.country': { + 'Meta': {'ordering': "['translations__name']", 'unique_together': '()', 'object_name': 'Country', 'index_together': '()'}, + 'area': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['country.Area']"}), + 'big_cities': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'cities'", 'null': 'True', 'symmetrical': 'False', 'to': u"orm['city.City']"}), + 'capital': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'capital'", 'null': 'True', 'on_delete': 'models.PROTECT', 'to': u"orm['city.City']"}), + 'country_code': ('django.db.models.fields.CharField', [], {'max_length': '2'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'currency': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['directories.Currency']", 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'inflect': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'language': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['directories.Language']", 'null': 'True', 'blank': 'True'}), + 'latitude': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'logo': ('django.db.models.fields.files.ImageField', [], {'max_length': '255', 'blank': 'True'}), + 'longitude': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'old_url': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '55'}), + 'phone_code': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'population': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'services': ('django.db.models.fields.BigIntegerField', [], {'default': 'None'}), + 'teritory': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'time_delivery': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'timezone': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'url': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}) + }, + u'directories.currency': { + 'Meta': {'unique_together': '()', 'object_name': 'Currency', 'index_together': '()'}, + 'code': ('django.db.models.fields.CharField', [], {'max_length': '3'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + u'directories.iata': { + 'Meta': {'object_name': 'Iata'}, + 'airport': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'code': ('django.db.models.fields.CharField', [], {'max_length': '4'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + u'directories.language': { + 'Meta': {'unique_together': '()', 'object_name': 'Language', 'index_together': '()'}, + 'code': ('django.db.models.fields.CharField', [], {'max_length': '2'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + u'expobanner.banner': { + 'Meta': {'ordering': "['sort']", 'object_name': 'Banner'}, + 'alt': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'cookie': ('django.db.models.fields.CharField', [], {'default': "'expo_b_default_popup'", 'max_length': '30', 'null': 'True', 'blank': 'True'}), + 'country': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['country.Country']", 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'flash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'fr': ('django.db.models.fields.DateField', [], {'default': 'datetime.datetime(2016, 5, 18, 0, 0)'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'banners'", 'null': 'True', 'to': u"orm['expobanner.BannerGroup']"}), + 'html': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'img': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'link': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'often': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '1'}), + 'paid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'popup': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'sort': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '500'}), + 'stat_pswd': ('django.db.models.fields.CharField', [], {'max_length': '16'}), + 'text': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'theme': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['theme.Theme']", 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'to': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'urls': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'url_banners'", 'null': 'True', 'symmetrical': 'False', 'to': u"orm['expobanner.URL']"}) + }, + u'expobanner.bannergroup': { + 'Meta': {'ordering': "['name']", 'object_name': 'BannerGroup'}, + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'height': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}), + 'speed': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '2000'}), + 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0'}) + }, + u'expobanner.mainpage': { + 'Meta': {'ordering': "['-public']", 'object_name': 'MainPage'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'link': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['expobanner.Banner']"}), + 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'position': ('django.db.models.fields.PositiveIntegerField', [], {'default': '2', 'null': 'True', 'blank': 'True'}), + 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'stat_pswd': ('django.db.models.fields.CharField', [], {'max_length': '16'}) + }, + u'expobanner.paid': { + 'Meta': {'ordering': "['-public']", 'object_name': 'Paid'}, + 'catalog': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'paid_catalog'", 'to': u"orm['expobanner.Banner']"}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'kind': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '1', 'db_index': 'True'}), + 'logo': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'blank': 'True'}), + 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'official': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'paid_official'", 'to': u"orm['expobanner.Banner']"}), + 'organiser': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'participation': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'paid_participation'", 'to': u"orm['expobanner.Banner']"}), + 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'stat_pswd': ('django.db.models.fields.CharField', [], {'max_length': '16'}), + 'tickets': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'paid_tickets'", 'to': u"orm['expobanner.Banner']"}) + }, + u'expobanner.top': { + 'Meta': {'ordering': "['position']", 'object_name': 'Top'}, + 'catalog': ('django.db.models.fields.CharField', [], {'max_length': '16'}), + 'cities': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'top_in_set'", 'null': 'True', 'symmetrical': 'False', 'to': u"orm['city.City']"}), + 'country': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['country.Country']", 'null': 'True', 'blank': 'True'}), + 'excluded_cities': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['city.City']", 'null': 'True', 'blank': 'True'}), + 'excluded_tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['theme.Tag']", 'null': 'True', 'blank': 'True'}), + 'fr': ('django.db.models.fields.DateField', [], {'default': 'datetime.datetime(2016, 5, 18, 0, 0)'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'link': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['expobanner.Banner']"}), + 'months': ('functions.custom_fields.MonthMultiSelectField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'position': ('django.db.models.fields.PositiveIntegerField', [], {'default': '2', 'null': 'True', 'blank': 'True'}), + 'stat_pswd': ('django.db.models.fields.CharField', [], {'max_length': '16'}), + 'theme': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['theme.Theme']", 'null': 'True', 'blank': 'True'}), + 'to': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'years': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}) + }, + u'expobanner.url': { + 'Meta': {'ordering': "['-created_at']", 'object_name': 'URL'}, + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'regex': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'sites': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'site_urls'", 'null': 'True', 'symmetrical': 'False', 'to': u"orm['sites.Site']"}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '2048'}) + }, + u'exposition.exposition': { + 'Meta': {'unique_together': '()', 'object_name': 'Exposition', 'index_together': '()'}, + 'application_deadline': ('django.db.models.fields.DateField', [], {'null': 'True'}), + 'area': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'audience': ('django.db.models.fields.BigIntegerField', [], {'default': 'None'}), + 'canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'canceled_by_administrator': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'city': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'exposition_city'", 'on_delete': 'models.PROTECT', 'to': u"orm['city.City']"}), + 'company': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'exposition_companies'", 'null': 'True', 'symmetrical': 'False', 'to': u"orm['company.Company']"}), + 'country': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'exposition_country'", 'on_delete': 'models.PROTECT', 'to': u"orm['country.Country']"}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'exposition_creator'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['accounts.User']"}), + 'currency': ('functions.custom_fields.EnumField', [], {'default': "'RUB'", 'values': "('RUB', 'USD', 'EUR', 'RMB', 'GBP', 'AED', 'SGD', 'TRY', 'CZK', 'CHF', 'SEK', 'LKR', 'UAH', 'IDR', 'PLN', 'JPY')"}), + 'data_begin': ('django.db.models.fields.DateField', [], {}), + 'data_end': ('django.db.models.fields.DateField', [], {}), + 'discount': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'expohit': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'foundation_year': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_published': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'logo': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'blank': 'True'}), + 'main': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['expobanner.MainPage']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), + 'main_page': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'db_index': 'True'}), + 'max_closed_area': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'max_closed_equipped_area': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'max_open_area': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'members': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'min_area': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'min_closed_area': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'min_closed_equipped_area': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'min_open_area': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'min_stand_size': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'moved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'old_url': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255'}), + 'org': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'organiser': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'exposition_organisers'", 'null': 'True', 'symmetrical': 'False', 'to': u"orm['organiser.Organiser']"}), + 'paid_new': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['expobanner.Paid']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), + 'periodic': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'photogallery': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['photologue.Gallery']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), + 'place': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'exposition_place'", 'null': 'True', 'on_delete': 'models.PROTECT', 'to': u"orm['place_exposition.PlaceExposition']"}), + 'place_alt': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'price_catalog': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'quality_label': ('django.db.models.fields.BigIntegerField', [], {'default': 'None'}), + 'rating': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'registration_link': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}), + 'registration_payment': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'services': ('django.db.models.fields.BigIntegerField', [], {'default': 'None'}), + 'tag': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'exposition_tags'", 'null': 'True', 'symmetrical': 'False', 'to': u"orm['theme.Tag']"}), + 'tax': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'theme': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'exposition_themes'", 'symmetrical': 'False', 'to': u"orm['theme.Theme']"}), + 'top': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['expobanner.Top']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), + 'url': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255'}), + 'users': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'exposition_users'", 'null': 'True', 'symmetrical': 'False', 'to': u"orm['accounts.User']"}), + 'views': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'visitors': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'web_page': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}) + }, + u'file.filemodel': { + 'Meta': {'unique_together': '()', 'object_name': 'FileModel', 'index_together': '()'}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']", 'null': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'file_path': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + 'file_type': ('functions.custom_fields.EnumField', [], {'default': "'PDF'", 'values': "('PDF', 'DOC', 'TXT', 'OTHER', 'JPG', 'BMP', 'PNG', 'GIF')", 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'img_height': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'img_width': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'purpose': ('functions.custom_fields.EnumField', [], {'default': "'photo'", 'values': "['photo', 'flat', 'logo', 'map', 'scheme teritory', 'diplom', 'preview', 'preview2']"}) + }, + u'note.note': { + 'Meta': {'object_name': 'Note'}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']", 'null': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['accounts.User']"}) + }, + u'organiser.organiser': { + 'Meta': {'unique_together': '()', 'object_name': 'Organiser', 'index_together': '()'}, + 'active': ('django.db.models.fields.NullBooleanField', [], {'default': '0', 'null': 'True', 'blank': 'True'}), + 'address': ('functions.custom_fields.LocationField', [], {'null': 'True', 'blank': 'True'}), + 'city': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['city.City']", 'null': 'True', 'on_delete': 'models.PROTECT', 'blank': 'True'}), + 'country': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['country.Country']", 'null': 'True', 'on_delete': 'models.PROTECT', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'events_number': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'facebook': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + 'fax': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'foundation': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'linkedin': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + 'logo': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'blank': 'True'}), + 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'phone': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'place_conference': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'organiser_place_conference'", 'null': 'True', 'symmetrical': 'False', 'to': u"orm['place_conference.PlaceConference']"}), + 'place_exposition': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'organiser_place_exposition'", 'null': 'True', 'symmetrical': 'False', 'to': u"orm['place_exposition.PlaceExposition']"}), + 'rating': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'staff_number': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'tag': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['theme.Tag']", 'null': 'True', 'blank': 'True'}), + 'theme': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['theme.Theme']", 'null': 'True', 'blank': 'True'}), + 'twitter': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + 'url': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'blank': 'True'}), + 'vk': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + 'web_page': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}) + }, + u'photologue.gallery': { + 'Meta': {'ordering': "['-date_added']", 'unique_together': '()', 'object_name': 'Gallery', 'index_together': '()'}, + 'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'photos': ('sortedm2m.fields.SortedManyToManyField', [], {'blank': 'True', 'related_name': "'galleries'", 'null': 'True', 'symmetrical': 'False', 'to': u"orm['photologue.Photo']"}), + 'sites': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['sites.Site']", 'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '200'}), + 'tags': ('photologue.models.TagField', [], {'max_length': '255', 'blank': 'True'}) + }, + u'photologue.photo': { + 'Meta': {'ordering': "['sort']", 'unique_together': '()', 'object_name': 'Photo', 'index_together': '()'}, + 'crop_from': ('django.db.models.fields.CharField', [], {'default': "'center'", 'max_length': '10', 'blank': 'True'}), + 'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'date_taken': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'effect': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'photo_related'", 'null': 'True', 'to': u"orm['photologue.PhotoEffect']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}), + 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'sites': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['sites.Site']", 'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '200'}), + 'sort': ('django.db.models.fields.PositiveIntegerField', [], {'default': '10', 'null': 'True', 'db_index': 'True'}), + 'tags': ('photologue.models.TagField', [], {'max_length': '255', 'blank': 'True'}), + 'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['photologue.UserMark']", 'null': 'True', 'symmetrical': 'False'}), + 'view_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}) + }, + u'photologue.photoeffect': { + 'Meta': {'object_name': 'PhotoEffect'}, + 'background_color': ('django.db.models.fields.CharField', [], {'default': "'#FFFFFF'", 'max_length': '7'}), + 'brightness': ('django.db.models.fields.FloatField', [], {'default': '1.0'}), + 'color': ('django.db.models.fields.FloatField', [], {'default': '1.0'}), + 'contrast': ('django.db.models.fields.FloatField', [], {'default': '1.0'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'filters': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}), + 'reflection_size': ('django.db.models.fields.FloatField', [], {'default': '0'}), + 'reflection_strength': ('django.db.models.fields.FloatField', [], {'default': '0.6'}), + 'sharpness': ('django.db.models.fields.FloatField', [], {'default': '1.0'}), + 'transpose_method': ('django.db.models.fields.CharField', [], {'max_length': '15', 'blank': 'True'}) + }, + u'photologue.usermark': { + 'Meta': {'object_name': 'UserMark'}, + 'height': ('django.db.models.fields.PositiveSmallIntegerField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'left': ('django.db.models.fields.PositiveSmallIntegerField', [], {}), + 'top': ('django.db.models.fields.PositiveSmallIntegerField', [], {}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'marks'", 'to': u"orm['accounts.User']"}), + 'width': ('django.db.models.fields.PositiveSmallIntegerField', [], {}) + }, + u'place_conference.placeconference': { + 'Meta': {'unique_together': '()', 'object_name': 'PlaceConference', 'index_together': '()'}, + 'address': ('functions.custom_fields.LocationField', [], {}), + 'amount_halls': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'banquet_hall': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + 'catering': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + 'city': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'place_conferences'", 'on_delete': 'models.PROTECT', 'to': u"orm['city.City']"}), + 'conference_call': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + 'country': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['country.Country']", 'on_delete': 'models.PROTECT'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'exp_hall_area': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'exposition_hall': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + 'fax': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'foundation_year': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'hotel': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'logo': ('django.db.models.fields.files.ImageField', [], {'max_length': '255', 'blank': 'True'}), + 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'multimedia_equipment': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + 'phone': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'total_capacity': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'translate_equipment': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + 'type': ('functions.custom_fields.EnumField', [], {'default': "'Convention centre'", 'values': "['Convention centre', 'Exposition centre']"}), + 'url': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}), + 'video_link': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'views': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'virtual_tour': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + 'web_page': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + 'wifi': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}) + }, + u'place_exposition.placeexposition': { + 'Meta': {'ordering': "['-rating', 'id']", 'unique_together': '()', 'object_name': 'PlaceExposition', 'index_together': '()'}, + 'address': ('functions.custom_fields.LocationField', [], {}), + 'bank': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + 'business_centre': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + 'cafe': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + 'children_room': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + 'city': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'place_expositions'", 'on_delete': 'models.PROTECT', 'to': u"orm['city.City']"}), + 'closed_area': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'conference_centre': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + 'country': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['country.Country']", 'on_delete': 'models.PROTECT'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'disabled_service': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'event_in_year': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'fax': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'foundation_year': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_published': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'logo': ('django.db.models.fields.files.ImageField', [], {'max_length': '255', 'blank': 'True'}), + 'mobile_application': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'online_registration': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + 'open_area': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'parking': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + 'partner': ('django.db.models.fields.NullBooleanField', [], {'default': '0', 'null': 'True', 'blank': 'True'}), + 'phone': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'photogallery': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['photologue.Gallery']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), + 'press_centre': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + 'rating': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'terminals': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + 'total_area': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'total_halls': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'total_pavilions': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'type': ('functions.custom_fields.EnumField', [], {'default': "'Exposition complex'", 'values': "['Exposition complex', 'Convention centre', 'Exposition centre']"}), + 'url': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255'}), + 'views': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'virtual_tour': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + 'web_page': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + 'wifi': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}) + }, + u'sites.site': { + 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, + 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'stats_collector.objectstats': { + 'Meta': {'ordering': "('created_at',)", 'object_name': 'ObjectStats'}, + 'conference': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['conference.Conference']", 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'exposition': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['exposition.Exposition']", 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'value': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + u'stats_collector.sectionstats': { + 'Meta': {'ordering': "('created_at',)", 'object_name': 'SectionStats', 'index_together': "[('content_type', 'object_id')]"}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'kind': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}), + 'object_id': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'section': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}), + 'value': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + u'theme.tag': { + 'Meta': {'unique_together': '()', 'object_name': 'Tag', 'index_together': '()'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'inflect': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'old_url': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255'}), + 'theme': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tags'", 'on_delete': 'models.PROTECT', 'to': u"orm['theme.Theme']"}), + 'url': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255'}) + }, + u'theme.theme': { + 'Meta': {'unique_together': '()', 'object_name': 'Theme', 'index_together': '()'}, + 'blogtheme_linking': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['theme.ThemeBlog']", 'null': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'inflect': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'main_page': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'db_index': 'True'}), + 'old_url': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255'}), + 'types': ('django.db.models.fields.BigIntegerField', [], {'default': 'None'}), + 'url': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255'}) + }, + u'theme.themeblog': { + 'Meta': {'unique_together': '()', 'object_name': 'ThemeBlog', 'index_together': '()'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'inflect': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'url': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255'}) + }, + u'translator.translator': { + 'Meta': {'unique_together': '()', 'object_name': 'Translator', 'index_together': '()'}, + 'birth': ('django.db.models.fields.DateField', [], {}), + 'car': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'gender': ('functions.custom_fields.EnumField', [], {'default': "'male'", 'values': "('male', 'female')"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + } + } + + complete_apps = ['stats_collector'] \ No newline at end of file diff --git a/stats_collector/migrations/__init__.py b/stats_collector/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/stats_collector/mixin.py b/stats_collector/mixin.py new file mode 100644 index 00000000..cdafdd89 --- /dev/null +++ b/stats_collector/mixin.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +from django.contrib.contenttypes.models import ContentType + +from .models import SectionStats, ObjectStats + + +class SectionBaseMixin(object): + """ + Section stat base mixin + """ + section = None + stat_kind = None + + def render_to_response(self, context, **response_kwargs): + try: + SectionStats.cache_count_add( + self.section, self.stat_kind, None, None, 1) + # ['section', 'kind', 'content_type', 'object_id', 'value'] + except: + pass + return super(SectionBaseMixin, self).render_to_response(context, **response_kwargs) + + +class ExpoSectionBaseMixin(object): + section = 'expo' + + +class ExpoSectionMixin(ExpoSectionBaseMixin, SectionBaseMixin): + pass + + +class ConfSectionBaseMixin(object): + section = 'conf' + + +class ConfSectionMixin(ConfSectionBaseMixin, SectionBaseMixin): + pass + + +class SectionKindBaseMixin(object): + section = None + stat_kind = None + + def render_to_response(self, context, **response_kwargs): + if self.stat_kind is not None and self.stat_kind in self.kwargs: + try: + obj = self.kwargs.get(self.stat_kind) + content_type = ContentType.objects.get_for_model(obj) + SectionStats.cache_count_add( + self.section, self.stat_kind, content_type.pk, obj.pk, 1) + # ['section', 'kind', 'content_type', 'object_id', 'value'] + except: + pass + # except Exception as e: + # print(e) + return super(SectionKindBaseMixin, self).render_to_response(context, **response_kwargs) + + +class ExpoSectionKindMixin(ExpoSectionBaseMixin, SectionKindBaseMixin): + pass + + +class ConfSectionKindMixin(ConfSectionBaseMixin, SectionKindBaseMixin): + pass + + +class ObjectStatMixin(object): + def render_to_response(self, context, **response_kwargs): + try: + obj = self.object + if obj.__class__.__name__.lower() == 'conference': + ObjectStats.cache_count_add(obj.pk, None, 1) + else: + ObjectStats.cache_count_add(None, obj.pk, 1) + # ['conference_id', 'exposition_id', 'value'] + except: + pass + return super(ObjectStatMixin, self).render_to_response(context, **response_kwargs) + diff --git a/stats_collector/models.py b/stats_collector/models.py new file mode 100644 index 00000000..d04d00cd --- /dev/null +++ b/stats_collector/models.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +import json +import time +from collections import namedtuple +from datetime import timedelta + +from conference.models import Conference +from django.contrib.contenttypes.generic import GenericForeignKey +from django.contrib.contenttypes.models import ContentType +from django.core.cache import cache +from django.core.serializers.json import DjangoJSONEncoder +from django.db import models +from django.utils import timezone +from django_pandas.managers import DataFrameManager +from exposition.models import Exposition + + +class StatsSuperBase(models.Model): + _strftime_format = '%d.%m.%Y' + _cache_key = 'section_stats_{date}' + + created_at = models.DateField('created at', blank=True, null=True, db_index=True) + value = models.PositiveIntegerField('value', blank=True, null=True) + + class Meta: + verbose_name = 'Stats Object' + verbose_name_plural = 'Stats Objects' + ordering = ('created_at',) + abstract = True + + @classmethod + def make_key(cls, date=None): + if date is None: + date = timezone.now().date() + return cls._cache_key.format(date=date.isoformat()) + + @classmethod + def dump_item(self, item): + return json.dumps(item, cls=DjangoJSONEncoder) + + @classmethod + def create_item(cls, *args, **kwargs): + return cls._collector_class(*args, **kwargs) + + @classmethod + def create_instance(cls, *args, **kwargs): + created_at = kwargs.pop('created_at', None) + item = cls.create_item(*args, **kwargs) + return cls(created_at=created_at, **item._asdict()) + + @classmethod + def create_cache_item(cls, *args, **kwargs): + return cls.dump_item(cls.create_item(*args, **kwargs)) + + @classmethod + def cache_count_add(cls, *args, **kwargs): + item = cls.create_cache_item(*args, **kwargs) + key = cls.make_key() + cache._client.rpush(key, item) + return item + + @property + def created_at_(self): + return self.created_at.strftime(self._strftime_format) + + +# class StatsBase(StatsSuperBase): + + +class SectionStats(StatsSuperBase): + _collector_class = namedtuple( + 'SectionCollector', + ['section', 'kind', 'content_type_id', 'object_id', 'value'] + ) + + content_type = models.ForeignKey(ContentType, blank=True, null=True) + object_id = models.IntegerField(blank=True, null=True) + content_object = GenericForeignKey('content_type', 'object_id') + + class Meta(StatsSuperBase.Meta): + index_together = [ + ("content_type", "object_id"), + ] + + section = models.CharField('section', blank=True, null=True, max_length=32) + kind = models.CharField('kind', blank=True, null=True, max_length=64) + + def __unicode__(self): + return u'{0}:{1} {2}'.format(self.section, self.kind, self.created_at.strftime(self._strftime_format)) + + +class ObjectStats(StatsSuperBase): + _cache_key = 'obj_stats_{date}' + _collector_class = namedtuple( + 'ObjectCollector', + ['conference_id', 'exposition_id', 'value'] + ) + exposition = models.ForeignKey(Exposition, null=True, blank=True) + conference = models.ForeignKey(Conference, null=True, blank=True) + + def __unicode__(self): + return u'{0}'.format(self.created_at.strftime(self._strftime_format)) diff --git a/stats_collector/tests.py b/stats_collector/tests.py new file mode 100644 index 00000000..501deb77 --- /dev/null +++ b/stats_collector/tests.py @@ -0,0 +1,16 @@ +""" +This file demonstrates writing tests using the unittest module. These will pass +when you run "manage.py test". + +Replace this with more appropriate tests for your application. +""" + +from django.test import TestCase + + +class SimpleTest(TestCase): + def test_basic_addition(self): + """ + Tests that 1 + 1 always equals 2. + """ + self.assertEqual(1 + 1, 2) diff --git a/stats_collector/utils.py b/stats_collector/utils.py new file mode 100644 index 00000000..64b0d580 --- /dev/null +++ b/stats_collector/utils.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +import json +from collections import Counter + + +def items_iterator(objects_list, model): + for string_item, count in Counter(objects_list).items(): + item = model._collector_class(*json.loads(string_item)) + yield item._replace(value=item.value * count) + + +def unique_items_iterator(objects_list): + for string_item, count in Counter(objects_list).items(): + item = json.loads(string_item) + yield tuple(item[:-1]), item[-1] * count diff --git a/stats_collector/views.py b/stats_collector/views.py new file mode 100644 index 00000000..60f00ef0 --- /dev/null +++ b/stats_collector/views.py @@ -0,0 +1 @@ +# Create your views here. diff --git a/templates/admin/base.html b/templates/admin/base.html index 55d78941..33964661 100644 --- a/templates/admin/base.html +++ b/templates/admin/base.html @@ -9,8 +9,6 @@ - - {# css harizma #} @@ -29,12 +27,20 @@ + + {# select2 #} + + {# The fav icon #} - + {# datetimepicker #} - + + + {% block main_js_exlude %} + + {% endblock main_js_exlude %} {% block scripts %} {% endblock %} @@ -56,27 +62,25 @@ -{# Side navigation #} -{% block sidebar %} - {% if user.is_superuser %} - {% include 'admin/includes/admin_nav.html' %} - {% else %} - {% include 'admin/includes/staff_nav.html' %} - {% endif %} - - -{% endblock %} - + {# Side navigation #} + {% block sidebar %} + {% if user.is_superuser %} + {% include 'admin/includes/admin_nav.html' %} + {% else %} + {% include 'admin/includes/staff_nav.html' %} + {% endif %} + {% endblock %} -
-{# Main content #} -{% block body %} -{% endblock %} +
+ {# Main content #} + {% block body %} + {% endblock %}
+ {% block bot_scripts %} {% endblock %} - \ No newline at end of file + diff --git a/templates/admin/conference/conference_list.html b/templates/admin/conference/conference_list.html index 7d6fe645..a88ab8f8 100644 --- a/templates/admin/conference/conference_list.html +++ b/templates/admin/conference/conference_list.html @@ -54,7 +54,7 @@ td a{ {{ item.name }} - {{ item.data_begin|date:"Y-m-d" }} + {{ item.data_begin|date:"Y-m-d" }}
{{ item.get_objectstat_views }} Копировать -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/admin/exposition/exposition_list.html b/templates/admin/exposition/exposition_list.html index b12c548f..15d884ab 100644 --- a/templates/admin/exposition/exposition_list.html +++ b/templates/admin/exposition/exposition_list.html @@ -51,7 +51,7 @@ td a{ {{ item.name }} - {{ item.data_begin|date:"Y-m-d" }} + {{ item.data_begin|date:"Y-m-d" }}
{{ item.get_objectstat_views }}
Копировать