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
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

@ -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'<li>{1}<label{0}>{2}</label></li>',
option_label = mark_safe(force_text(option_label))
output.append(format_html(u'<li>{1}<label{0}><span>{2}</span></label></li>',
label_for, rendered_cb, option_label))
output.append(u'</ul>')
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} <i>({count})</i>'.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 м<sub>2</sub> необорудованной площади'))
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') + ' <i>({count})</i>'.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

@ -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)

Loading…
Cancel
Save