diff --git a/events/common.py b/events/common.py index 0581b6b5..f2fce56b 100644 --- a/events/common.py +++ b/events/common.py @@ -6,32 +6,32 @@ from django.utils.translation import ugettext as _ from functions.model_utils import EnumChoices -members_mapping = OrderedDict( - N200={'min': None, 'max': 200, 'value': 1, 'label': _(u'до 200')}, - N200500={'min': 201, 'max': 500, 'value': 2, 'label': _(u'201-500')}, - N5001000={'min': 501, 'max': 1000, 'value': 3, 'label': _(u'501-1000')}, - N10002000={'min': 1001, 'max': 2000, 'value': 4, 'label': _(u'1001-2000')}, - N2000={'min': 2001, 'max': None, 'value': 5, 'label': _(u'более 2000')}, -) - -visitors_mapping = OrderedDict( - N5={'min': None, 'max': 5000, 'value': 1, 'label': _(u'до 5 000')}, - N510={'min': 5001, 'max': 10000, 'value': 2, 'label': _(u'5 001 - 10 000')}, - N1030={'min': 10001, 'max': 30000, 'value': 3, 'label': _(u'10 001 - 30 000')}, - N3050={'min': 30001, 'max': 50000, 'value': 4, 'label': _(u'30 001 - 50 000')}, - N50100={'min': 50001, 'max': 100000, 'value': 5, 'label': _(u'50 001 - 100 000')}, - N100={'min': 100001, 'max': None, 'value': 6, 'label': _(u'более 100 000')}, -) - -price_mapping = OrderedDict( - N1={'min': None, 'max': 100, 'value': 1, 'label': _(u'до 100 евро')}, - N12={'min': 101, 'max': 200, 'value': 2, 'label': _(u'100-200 евро')}, - N24={'min': 201, 'max': 400, 'value': 3, 'label': _(u'200-400 евро')}, - N4={'min': 401, 'max': None, 'value': 4, 'label': _(u'более 400 евро')}, -) +members_mapping = OrderedDict([ + ('N200', {'min': None, 'max': 200, 'value': 1, 'label': _(u'до 200')}), + ('N200500', {'min': 200, 'max': 400, 'value': 2, 'label': _(u'200-500')}), + ('N5001000', {'min': 500, 'max': 500, 'value': 3, 'label': _(u'500-1000')}), + ('N10002000', {'min': 1000, 'max': 2000, 'value': 4, 'label': _(u'1000-2000')}), + ('N2000', {'min': 2000, 'max': None, 'value': 5, 'label': _(u'более 2000')}), +]) + +visitors_mapping = OrderedDict([ + ('N5', {'min': None, 'max': 5000, 'value': 1, 'label': _(u'до 5 000')}), + ('N510', {'min': 5000, 'max': 10000, 'value': 2, 'label': _(u'5 000 - 10 000')}), + ('N1030', {'min': 10000, 'max': 30000, 'value': 3, 'label': _(u'10 000 - 30 000')}), + ('N3050', {'min': 30000, 'max': 50000, 'value': 4, 'label': _(u'30 000 - 50 000')}), + ('N50100', {'min': 50000, 'max': 100000, 'value': 5, 'label': _(u'50 000 - 100 000')}), + ('N100', {'min': 100000, 'max': None, 'value': 6, 'label': _(u'более 100 000')}), +]) + +price_mapping = OrderedDict([ + ('N1', {'min': None, 'max': 100, 'value': 1, 'label': _(u'до 100 евро')}), + ('N12', {'min': 100, 'max': 200, 'value': 2, 'label': _(u'100-200 евро')}), + ('N24', {'min': 200, 'max': 400, 'value': 3, 'label': _(u'200-400 евро')}), + ('N4', {'min': 400, 'max': None, 'value': 4, 'label': _(u'более 400 евро')}), +]) def get_choices_kwargs(mapping): - kwargs = {} + kwargs = OrderedDict() for key, val in mapping.iteritems(): kwargs[key] = (val.get('value'), val.get('label')) return kwargs diff --git a/events/forms.py b/events/forms.py index 7867505c..e52143ba 100644 --- a/events/forms.py +++ b/events/forms.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from itertools import chain from collections import namedtuple +from datetime import datetime try: from collections import ChainMap @@ -13,8 +14,10 @@ from django.utils.encoding import smart_text, force_text from django.utils.html import format_html from django.utils.safestring import mark_safe from django.db.models import Count, Sum, Q, ForeignKey, ManyToManyField +from django.db.models.sql.where import ExtraWhere, AND, OR from django.db import connection from django.core.exceptions import ValidationError + from haystack.query import SearchQuerySet, RelatedSearchQuerySet, SQ from functions.model_utils import EnumChoices @@ -47,20 +50,27 @@ class FilterCheckboxSelectMultiple(forms.CheckboxSelectMultiple): cb = forms.CheckboxInput(final_attrs, check_test=lambda value: value in str_values) option_value = force_text(option_value) rendered_cb = cb.render(name, option_value) - option_label = force_text(option_label) - output.append(format_html(u'
  • {1}{2}
  • ', + option_label = mark_safe(force_text(option_label)) + output.append(format_html(u'
  • {1}{2}
  • ', label_for, rendered_cb, option_label)) output.append(u'') return mark_safe('\n'.join(output)) -class CountModelMultipleChoiceField(forms.ModelMultipleChoiceField): +class WidgetDefaultMixin(object): + def widget_attrs(self, widget): + attrs = super(WidgetDefaultMixin, self).widget_attrs(widget) + attrs['class'] = 'default' + return attrs + + +class CountModelMultipleChoiceField(WidgetDefaultMixin, forms.ModelMultipleChoiceField): # widget = forms.CheckboxSelectMultiple widget = FilterCheckboxSelectMultiple def label_from_instance(self, obj): if obj.get('count', None) is None: return smart_text(obj.get('name')) - return u'{label} ({count})'.format(label=smart_text(obj.get('name')), count=obj.get('count')) + return u'{label} ({count})'.format(label=smart_text(obj.get('name')), count=obj.get('count')) def prepare_value(self, value): if isinstance(value, dict): @@ -90,13 +100,9 @@ class CountModelMultipleChoiceField(forms.ModelMultipleChoiceField): self.run_validators(value) return pks - def widget_attrs(self, widget): - """ - Given a Widget instance (*not* a Widget class), returns a dictionary of - any HTML attributes that should be added to the Widget, based on this - Field. - """ - return {'class': 'default'} + +class FilterTypedMultipleChoiceField(WidgetDefaultMixin, forms.TypedMultipleChoiceField): + pass fields_mapping = { @@ -121,7 +127,7 @@ class FilterForm(forms.Form): # MEMBERS = MEMBERS # VISITORS = VISITORS # PRICE = PRICE - model = forms.TypedMultipleChoiceField( + model = FilterTypedMultipleChoiceField( label=_(u'Тип события'), coerce=int, choices=TYPES, required=False, widget=FilterCheckboxSelectMultiple()) theme = CountModelMultipleChoiceField( @@ -136,16 +142,16 @@ class FilterForm(forms.Form): city = CountModelMultipleChoiceField( label=_(u'Города'), required=False, queryset=City.objects.language().values('pk', 'name')) - price = forms.TypedMultipleChoiceField( + price = FilterTypedMultipleChoiceField( label=_(u'Стоимость'), coerce=int, choices=PRICE, required=False, widget=FilterCheckboxSelectMultiple(), help_text=_(u'За 1 м2 необорудованной площади')) - members = forms.TypedMultipleChoiceField( + members = FilterTypedMultipleChoiceField( label=_(u'Участники'), coerce=int, choices=MEMBERS, required=False, widget=FilterCheckboxSelectMultiple()) - visitors = forms.TypedMultipleChoiceField( + visitors = FilterTypedMultipleChoiceField( label=_(u'Посетители'), coerce=int, choices=VISITORS, required=False, widget=FilterCheckboxSelectMultiple()) @@ -171,6 +177,7 @@ class FilterForm(forms.Form): # нам нужно сбрасывать сохраненный выбор моделей, # т.к. после валидации нужно вернуть только выбранные self._models = None + self.cleaned_data = getattr(self, 'cleaned_data', {}) return self._is_valid @property @@ -213,16 +220,6 @@ class FilterForm(forms.Form): self._lookup_kwargs['price_choice__in'] = d.get('price') return self._lookup_kwargs - # @property - # def lookup_args(self): - # if self._lookup_args is None: - # d = self.cleaned_data - # self._lookup_args = {} - # self._local_fields = [] - # if d.get('members'): - # self._local_fields.append('city') - # return self._lookup_args - def filter(self, qs=None): qs = qs or self.default_filter() # lookup_kwargs = dict(ChainMap({}, *(lookup_kwargs or self.lookup_kwargs).values())) @@ -234,38 +231,43 @@ class FilterForm(forms.Form): qs = qs.load_all() for model in self.models: qs = qs.load_all_queryset(model, model.enable.all()) + qs = qs.filter(data_end__gte=datetime.now()) return qs def recalculate_choices(self): print(self._is_valid) - if self._is_valid and self.lookup_kwargs: - for field in ['theme', 'tag', 'city', 'country']: - # field_qs = self.default_filter(load_all=False) - # if not field_lookup_kwargs: - # continue - # field_qs = (x.id.split('.')[1:] for x in self.filter(qs=field_qs, lookup_kwargs=field_lookup_kwargs) if x.id) - # if field == 'theme': - # self.fields[field].queryset = self.get_theme_choices(field_qs) - values = ['pk', 'name'] - order_by = [] - select = self.make_count_select(field) - qs = self.fields[field].queryset - for key in select.iterkeys(): - values.append(key) - if key == 'selected': - order_by.insert(0, '-' + key) - else: - order_by.append('-' + key) - - qs = qs.extra(select=select) - qs = qs.values(*values).order_by(*order_by) - # if 'count' in values: - # qs = qs.exclude(count=0) - self.fields[field].queryset = qs - print(self.fields[field].queryset.query) - - for field in ['members', 'visitors', 'price']: - self.fields[field].choices = self.make_local_field_count(field) or self.fields[field].choices + # if self._is_valid and self.lookup_kwargs: + + for field in ['theme', 'tag', 'city', 'country']: + # field_qs = self.default_filter(load_all=False) + # if not field_lookup_kwargs: + # continue + # field_qs = (x.id.split('.')[1:] for x in self.filter(qs=field_qs, lookup_kwargs=field_lookup_kwargs) if x.id) + # if field == 'theme': + # self.fields[field].queryset = self.get_theme_choices(field_qs) + values = ['pk', 'name'] + order_by = [] + select = self.make_count_select(field) + qs = self.fields[field].queryset + for key in select.iterkeys(): + values.append(key) + if key == 'selected': + order_by.insert(0, '-' + key) + else: + order_by.append('-' + key) + + qs = qs.extra(select=select) + if 'count' in values: + having = [''' `count` > 0 '''] + if 'selected' in values: + having.append(''' `selected` = 1 ''') + qs.query.having.add(ExtraWhere(having, []), OR) + qs = qs.values(*values).order_by(*order_by) + self.fields[field].queryset = qs + print(self.fields[field].queryset.query) + + for field in ['members', 'visitors', 'price']: + self.fields[field].choices = self.make_local_field_count(field) or self.fields[field].choices # for field in self.fields: # field = self.fields[field] @@ -326,7 +328,6 @@ class FilterForm(forms.Form): return joins, where def make_local_field_count(self, field): - # if 'members' not in self.lookup_kwargs: sql = '' selects = [] l_field = fields_mapping.get(field, field) @@ -366,6 +367,7 @@ class FilterForm(forms.Form): selects.append(select + ''.join(joins) + ' where ' + ' and '.join(where) + group_by) sql = ' union '.join(selects) + print(sql) choices = [] if sql: with connection.cursor() as c: @@ -374,18 +376,15 @@ class FilterForm(forms.Form): data = [mapper(*raw) for raw in c.fetchall()] for key, val in _values_mapping: count = sum([getattr(x, key, 0) or 0 for x in data]) - choices.append((val.get('value'), val.get('label') + ' ({count})'.format(count=count))) + choices.append((val.get('value'), val.get('label') + ' ({count})'.format(count=count))) return choices - # cursor.execute() - - #""" SELECT sum(case when (`exposition_exposition`.`members` < 200) then 1 else 0 end) as 'N200', sum(case when (`exposition_exposition`.`members` >= 200 AND `exposition_exposition`.`members` <= 500) then 2 else 0 end) as 'N200500', sum(case when (`exposition_exposition`.`members` >= 500 AND `exposition_exposition`.`members` <= 1000) then 3 else 0 end) as 'N5001000', sum(case when (`exposition_exposition`.`members` >= 1000 AND `exposition_exposition`.`members` <= 2000) then 4 else 0 end) as 'N10002000', sum(case when (`exposition_exposition`.`members` >= 2000) then 5 else 0 end) as 'N2000'FROM `exposition_exposition_translation` INNER JOIN `exposition_exposition` ON (`exposition_exposition_translation`.`master_id` = `exposition_exposition`.`id`) WHERE `exposition_exposition_translation`.`language_code` = 'ru'union SELECT sum(case when (`conference_conference`.`members` < 200) then 1 else 0 end) as 'N200', sum(case when (`conference_conference`.`members` >= 200 AND `conference_conference`.`members` <= 500) then 1 else 0 end) as 'N200500', sum(case when (`conference_conference`.`members` >= 500 AND `conference_conference`.`members` <= 1000) then 1 else 0 end) as 'N5001000', sum(case when (`conference_conference`.`members` >= 1000 AND `conference_conference`.`members` <= 2000) then 1 else 0 end) as 'N10002000', sum(case when (`conference_conference`.`members` >= 2000) then 1 else 0 end) as 'N2000'FROM `conference_conference_translation` INNER JOIN `conference_conference` ON (`conference_conference_translation`.`master_id` = `conference_conference`.`id`) WHERE `conference_conference_translation`.`language_code` = 'ru' """ - def make_default_where(self, **kwargs): - return ''' `{db_table}`.`is_published` = True '''.format(**kwargs) + return ''' (`{db_table}`.`is_published` = True) AND (`{db_table}`.`data_end` >= '{date_today}') '''\ + .format(date_today=datetime.now().strftime('%Y-%m-%d'), **kwargs) def make_count_select(self, field): - count_selects = [] + selects = [] case = None count = None print('looking {} {}'.format(field, self.lookup_kwargs)) @@ -447,12 +446,12 @@ class FilterForm(forms.Form): where.extend(_where) where.append(self.make_default_where(db_table=model._meta.db_table)) - count_selects.append(select + ''.join(joins) + ' where ' + ' and '.join(where) + group_by) + selects.append(select + ''.join(joins) + ' where ' + ' and '.join(where) + group_by) - if len(count_selects) == 2: - count = '({}) + ({})'.format(*count_selects) - elif len(count_selects) == 1: - count = count_selects[0] + if len(selects) == 2: + count = ''' IFNULL(({0}), 0) + IFNULL(({1}), 0) '''.format(*selects) + elif len(selects) == 1: + count = selects[0] d = {} if case is not None: d['selected'] = case diff --git a/events/management/commands/update_events_filter_fields.py b/events/management/commands/update_events_filter_fields.py index affedc86..3368c5ce 100644 --- a/events/management/commands/update_events_filter_fields.py +++ b/events/management/commands/update_events_filter_fields.py @@ -66,6 +66,6 @@ class Command(NoArgsCommand): if _min: q &= Q(**{from_field + '__gte': _min}) if _max: - q &= Q(**{from_field + '__lte': _max}) + q &= Q(**{from_field + '__lt': _max}) model.objects.filter(q).exclude(**exclude_kwargs).update(**update_kwargs)