1461: Этап №5: Фильтрация событий

обновлен порядок для посетителей, учасников и стоимости;
обновлен sql запрос;
добавлено исключение невыбранных вариантов, в которых кол-во событий равно нулю;
remotes/origin/stage5
Alexander Burdeiny 10 years ago
parent 13c6efd2b2
commit 8422f466e1
  1. 48
      events/common.py
  2. 131
      events/forms.py
  3. 2
      events/management/commands/update_events_filter_fields.py

@ -6,32 +6,32 @@ from django.utils.translation import ugettext as _
from functions.model_utils import EnumChoices from functions.model_utils import EnumChoices
members_mapping = OrderedDict( members_mapping = OrderedDict([
N200={'min': None, 'max': 200, 'value': 1, 'label': _(u'до 200')}, ('N200', {'min': None, 'max': 200, 'value': 1, 'label': _(u'до 200')}),
N200500={'min': 201, 'max': 500, 'value': 2, 'label': _(u'201-500')}, ('N200500', {'min': 200, 'max': 400, 'value': 2, 'label': _(u'200-500')}),
N5001000={'min': 501, 'max': 1000, 'value': 3, 'label': _(u'501-1000')}, ('N5001000', {'min': 500, 'max': 500, 'value': 3, 'label': _(u'500-1000')}),
N10002000={'min': 1001, 'max': 2000, 'value': 4, 'label': _(u'1001-2000')}, ('N10002000', {'min': 1000, 'max': 2000, 'value': 4, 'label': _(u'1000-2000')}),
N2000={'min': 2001, 'max': None, 'value': 5, 'label': _(u'более 2000')}, ('N2000', {'min': 2000, 'max': None, 'value': 5, 'label': _(u'более 2000')}),
) ])
visitors_mapping = OrderedDict( visitors_mapping = OrderedDict([
N5={'min': None, 'max': 5000, 'value': 1, 'label': _(u'до 5 000')}, ('N5', {'min': None, 'max': 5000, 'value': 1, 'label': _(u'до 5 000')}),
N510={'min': 5001, 'max': 10000, 'value': 2, 'label': _(u'5 001 - 10 000')}, ('N510', {'min': 5000, 'max': 10000, 'value': 2, 'label': _(u'5 000 - 10 000')}),
N1030={'min': 10001, 'max': 30000, 'value': 3, 'label': _(u'10 001 - 30 000')}, ('N1030', {'min': 10000, 'max': 30000, 'value': 3, 'label': _(u'10 000 - 30 000')}),
N3050={'min': 30001, 'max': 50000, 'value': 4, 'label': _(u'30 001 - 50 000')}, ('N3050', {'min': 30000, 'max': 50000, 'value': 4, 'label': _(u'30 000 - 50 000')}),
N50100={'min': 50001, 'max': 100000, 'value': 5, 'label': _(u'50 001 - 100 000')}, ('N50100', {'min': 50000, 'max': 100000, 'value': 5, 'label': _(u'50 000 - 100 000')}),
N100={'min': 100001, 'max': None, 'value': 6, 'label': _(u'более 100 000')}, ('N100', {'min': 100000, 'max': None, 'value': 6, 'label': _(u'более 100 000')}),
) ])
price_mapping = OrderedDict( price_mapping = OrderedDict([
N1={'min': None, 'max': 100, 'value': 1, 'label': _(u'до 100 евро')}, ('N1', {'min': None, 'max': 100, 'value': 1, 'label': _(u'до 100 евро')}),
N12={'min': 101, 'max': 200, 'value': 2, 'label': _(u'100-200 евро')}, ('N12', {'min': 100, 'max': 200, 'value': 2, 'label': _(u'100-200 евро')}),
N24={'min': 201, 'max': 400, 'value': 3, 'label': _(u'200-400 евро')}, ('N24', {'min': 200, 'max': 400, 'value': 3, 'label': _(u'200-400 евро')}),
N4={'min': 401, 'max': None, 'value': 4, 'label': _(u'более 400 евро')}, ('N4', {'min': 400, 'max': None, 'value': 4, 'label': _(u'более 400 евро')}),
) ])
def get_choices_kwargs(mapping): def get_choices_kwargs(mapping):
kwargs = {} kwargs = OrderedDict()
for key, val in mapping.iteritems(): for key, val in mapping.iteritems():
kwargs[key] = (val.get('value'), val.get('label')) kwargs[key] = (val.get('value'), val.get('label'))
return kwargs return kwargs

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from itertools import chain from itertools import chain
from collections import namedtuple from collections import namedtuple
from datetime import datetime
try: try:
from collections import ChainMap 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.html import format_html
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.db.models import Count, Sum, Q, ForeignKey, ManyToManyField 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.db import connection
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from haystack.query import SearchQuerySet, RelatedSearchQuerySet, SQ from haystack.query import SearchQuerySet, RelatedSearchQuerySet, SQ
from functions.model_utils import EnumChoices 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) cb = forms.CheckboxInput(final_attrs, check_test=lambda value: value in str_values)
option_value = force_text(option_value) option_value = force_text(option_value)
rendered_cb = cb.render(name, option_value) rendered_cb = cb.render(name, option_value)
option_label = force_text(option_label) option_label = mark_safe(force_text(option_label))
output.append(format_html(u'<li>{1}<label{0}>{2}</label></li>', output.append(format_html(u'<li>{1}<label{0}><span>{2}</span></label></li>',
label_for, rendered_cb, option_label)) label_for, rendered_cb, option_label))
output.append(u'</ul>') output.append(u'</ul>')
return mark_safe('\n'.join(output)) 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 = forms.CheckboxSelectMultiple
widget = FilterCheckboxSelectMultiple widget = FilterCheckboxSelectMultiple
def label_from_instance(self, obj): def label_from_instance(self, obj):
if obj.get('count', None) is None: if obj.get('count', None) is None:
return smart_text(obj.get('name')) return smart_text(obj.get('name'))
return u'{label} ({count})'.format(label=smart_text(obj.get('name')), count=obj.get('count')) return u'{label} <i>({count})</i>'.format(label=smart_text(obj.get('name')), count=obj.get('count'))
def prepare_value(self, value): def prepare_value(self, value):
if isinstance(value, dict): if isinstance(value, dict):
@ -90,13 +100,9 @@ class CountModelMultipleChoiceField(forms.ModelMultipleChoiceField):
self.run_validators(value) self.run_validators(value)
return pks return pks
def widget_attrs(self, widget):
""" class FilterTypedMultipleChoiceField(WidgetDefaultMixin, forms.TypedMultipleChoiceField):
Given a Widget instance (*not* a Widget class), returns a dictionary of pass
any HTML attributes that should be added to the Widget, based on this
Field.
"""
return {'class': 'default'}
fields_mapping = { fields_mapping = {
@ -121,7 +127,7 @@ class FilterForm(forms.Form):
# MEMBERS = MEMBERS # MEMBERS = MEMBERS
# VISITORS = VISITORS # VISITORS = VISITORS
# PRICE = PRICE # PRICE = PRICE
model = forms.TypedMultipleChoiceField( model = FilterTypedMultipleChoiceField(
label=_(u'Тип события'), coerce=int, label=_(u'Тип события'), coerce=int,
choices=TYPES, required=False, widget=FilterCheckboxSelectMultiple()) choices=TYPES, required=False, widget=FilterCheckboxSelectMultiple())
theme = CountModelMultipleChoiceField( theme = CountModelMultipleChoiceField(
@ -136,16 +142,16 @@ class FilterForm(forms.Form):
city = CountModelMultipleChoiceField( city = CountModelMultipleChoiceField(
label=_(u'Города'), required=False, label=_(u'Города'), required=False,
queryset=City.objects.language().values('pk', 'name')) queryset=City.objects.language().values('pk', 'name'))
price = forms.TypedMultipleChoiceField( price = FilterTypedMultipleChoiceField(
label=_(u'Стоимость'), coerce=int, label=_(u'Стоимость'), coerce=int,
choices=PRICE, choices=PRICE,
required=False, widget=FilterCheckboxSelectMultiple(), required=False, widget=FilterCheckboxSelectMultiple(),
help_text=_(u'За 1 м<sub>2</sub> необорудованной площади')) help_text=_(u'За 1 м<sub>2</sub> необорудованной площади'))
members = forms.TypedMultipleChoiceField( members = FilterTypedMultipleChoiceField(
label=_(u'Участники'), coerce=int, label=_(u'Участники'), coerce=int,
choices=MEMBERS, choices=MEMBERS,
required=False, widget=FilterCheckboxSelectMultiple()) required=False, widget=FilterCheckboxSelectMultiple())
visitors = forms.TypedMultipleChoiceField( visitors = FilterTypedMultipleChoiceField(
label=_(u'Посетители'), coerce=int, label=_(u'Посетители'), coerce=int,
choices=VISITORS, choices=VISITORS,
required=False, widget=FilterCheckboxSelectMultiple()) required=False, widget=FilterCheckboxSelectMultiple())
@ -171,6 +177,7 @@ class FilterForm(forms.Form):
# нам нужно сбрасывать сохраненный выбор моделей, # нам нужно сбрасывать сохраненный выбор моделей,
# т.к. после валидации нужно вернуть только выбранные # т.к. после валидации нужно вернуть только выбранные
self._models = None self._models = None
self.cleaned_data = getattr(self, 'cleaned_data', {})
return self._is_valid return self._is_valid
@property @property
@ -213,16 +220,6 @@ class FilterForm(forms.Form):
self._lookup_kwargs['price_choice__in'] = d.get('price') self._lookup_kwargs['price_choice__in'] = d.get('price')
return self._lookup_kwargs 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): def filter(self, qs=None):
qs = qs or self.default_filter() qs = qs or self.default_filter()
# lookup_kwargs = dict(ChainMap({}, *(lookup_kwargs or self.lookup_kwargs).values())) # lookup_kwargs = dict(ChainMap({}, *(lookup_kwargs or self.lookup_kwargs).values()))
@ -234,38 +231,43 @@ class FilterForm(forms.Form):
qs = qs.load_all() qs = qs.load_all()
for model in self.models: for model in self.models:
qs = qs.load_all_queryset(model, model.enable.all()) qs = qs.load_all_queryset(model, model.enable.all())
qs = qs.filter(data_end__gte=datetime.now())
return qs return qs
def recalculate_choices(self): def recalculate_choices(self):
print(self._is_valid) print(self._is_valid)
if self._is_valid and self.lookup_kwargs: # if self._is_valid and self.lookup_kwargs:
for field in ['theme', 'tag', 'city', 'country']:
# field_qs = self.default_filter(load_all=False) for field in ['theme', 'tag', 'city', 'country']:
# if not field_lookup_kwargs: # field_qs = self.default_filter(load_all=False)
# continue # if not field_lookup_kwargs:
# field_qs = (x.id.split('.')[1:] for x in self.filter(qs=field_qs, lookup_kwargs=field_lookup_kwargs) if x.id) # continue
# if field == 'theme': # field_qs = (x.id.split('.')[1:] for x in self.filter(qs=field_qs, lookup_kwargs=field_lookup_kwargs) if x.id)
# self.fields[field].queryset = self.get_theme_choices(field_qs) # if field == 'theme':
values = ['pk', 'name'] # self.fields[field].queryset = self.get_theme_choices(field_qs)
order_by = [] values = ['pk', 'name']
select = self.make_count_select(field) order_by = []
qs = self.fields[field].queryset select = self.make_count_select(field)
for key in select.iterkeys(): qs = self.fields[field].queryset
values.append(key) for key in select.iterkeys():
if key == 'selected': values.append(key)
order_by.insert(0, '-' + key) if key == 'selected':
else: order_by.insert(0, '-' + key)
order_by.append('-' + key) else:
order_by.append('-' + key)
qs = qs.extra(select=select)
qs = qs.values(*values).order_by(*order_by) qs = qs.extra(select=select)
# if 'count' in values: if 'count' in values:
# qs = qs.exclude(count=0) having = [''' `count` > 0 ''']
self.fields[field].queryset = qs if 'selected' in values:
print(self.fields[field].queryset.query) having.append(''' `selected` = 1 ''')
qs.query.having.add(ExtraWhere(having, []), OR)
for field in ['members', 'visitors', 'price']: qs = qs.values(*values).order_by(*order_by)
self.fields[field].choices = self.make_local_field_count(field) or self.fields[field].choices 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: # for field in self.fields:
# field = self.fields[field] # field = self.fields[field]
@ -326,7 +328,6 @@ class FilterForm(forms.Form):
return joins, where return joins, where
def make_local_field_count(self, field): def make_local_field_count(self, field):
# if 'members' not in self.lookup_kwargs:
sql = '' sql = ''
selects = [] selects = []
l_field = fields_mapping.get(field, field) 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) selects.append(select + ''.join(joins) + ' where ' + ' and '.join(where) + group_by)
sql = ' union '.join(selects) sql = ' union '.join(selects)
print(sql)
choices = [] choices = []
if sql: if sql:
with connection.cursor() as c: with connection.cursor() as c:
@ -374,18 +376,15 @@ class FilterForm(forms.Form):
data = [mapper(*raw) for raw in c.fetchall()] data = [mapper(*raw) for raw in c.fetchall()]
for key, val in _values_mapping: for key, val in _values_mapping:
count = sum([getattr(x, key, 0) or 0 for x in data]) 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') + ' <i>({count})</i>'.format(count=count)))
return choices 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): 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): def make_count_select(self, field):
count_selects = [] selects = []
case = None case = None
count = None count = None
print('looking {} {}'.format(field, self.lookup_kwargs)) print('looking {} {}'.format(field, self.lookup_kwargs))
@ -447,12 +446,12 @@ class FilterForm(forms.Form):
where.extend(_where) where.extend(_where)
where.append(self.make_default_where(db_table=model._meta.db_table)) 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: if len(selects) == 2:
count = '({}) + ({})'.format(*count_selects) count = ''' IFNULL(({0}), 0) + IFNULL(({1}), 0) '''.format(*selects)
elif len(count_selects) == 1: elif len(selects) == 1:
count = count_selects[0] count = selects[0]
d = {} d = {}
if case is not None: if case is not None:
d['selected'] = case d['selected'] = case

@ -66,6 +66,6 @@ class Command(NoArgsCommand):
if _min: if _min:
q &= Q(**{from_field + '__gte': _min}) q &= Q(**{from_field + '__gte': _min})
if _max: if _max:
q &= Q(**{from_field + '__lte': _max}) q &= Q(**{from_field + '__lt': _max})
model.objects.filter(q).exclude(**exclude_kwargs).update(**update_kwargs) model.objects.filter(q).exclude(**exclude_kwargs).update(**update_kwargs)

Loading…
Cancel
Save