добавил дату и кучу юзкейсов для неё

remotes/origin/stage5
Alexander Burdeiny 10 years ago
parent 54085c6d1b
commit 854559346c
  1. 377
      events/forms.py
  2. 1
      events/urls.py
  3. 14
      events/views.py
  4. 8
      proj/middleware.py
  5. 7
      templates/client/includes/events/filter_form.html

@ -1,7 +1,15 @@
# -*- coding: utf-8 -*-
import re
from itertools import chain
from collections import namedtuple
from datetime import datetime
from datetime import timedelta
from datetime import date
from datetime import MAXYEAR, MINYEAR
from dateutil import relativedelta
import calendar
# from calendar import TimeEncoding, month_name
try:
from collections import ChainMap
@ -10,12 +18,15 @@ except ImportError:
from django import forms
from django.utils.translation import get_language, ugettext as _
from django.utils.translation import string_concat
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 import connection
from django.core.exceptions import ValidationError
from django.core.cache import cache
from django.conf import settings
from haystack.query import SearchQuerySet, RelatedSearchQuerySet, SQ
@ -65,7 +76,6 @@ class WidgetDefaultMixin(object):
class CountModelMultipleChoiceField(WidgetDefaultMixin, forms.ModelMultipleChoiceField):
# widget = forms.CheckboxSelectMultiple
widget = FilterCheckboxSelectMultiple
def label_from_instance(self, obj):
if obj.get('count', None) is None:
@ -105,6 +115,41 @@ class FilterTypedMultipleChoiceField(WidgetDefaultMixin, forms.TypedMultipleChoi
pass
class MonthChoiceField(FilterTypedMultipleChoiceField):
year = None
additional_choices = []
default_month = None
def make_year_choice(self, value):
return (str(value), _(u'{year} год').format(year=value))
def valid_value(self, value):
valid = super(MonthChoiceField, self).valid_value(value)
if not valid:
if self.year and (value == self.year or check_year(value) == self.year):
choice = self.make_year_choice(value)
self.additional_choices.append(choice)
self.choices.insert(0, choice)
return True
value = str(value)
match = year_month_regex.match(value)
month = None
year = self.year or datetime.today().year
if match:
year, month = match.group('year'), match.group('month')
elif value in settings.MONTHES.keys():
month = value
self.default_month = month
if month in settings.MONTHES.keys():
choice = (value, string_concat(settings.MONTHES[month]['name'], ' ', year))
self.additional_choices.append(choice)
self.choices.insert(0, choice)
# print(choice, self.choices)
return super(MonthChoiceField, self).valid_value(value)
return valid
fields_mapping = {
'members': 'members_choice',
'visitors': 'visitors_choice',
@ -118,32 +163,47 @@ values_mapping = {
}
monthes_abr_to_num = {v.lower(): k for k, v in enumerate(calendar.month_abbr)}
year_month_regex = re.compile(r'^(?P<year>\d{4})(?P<month>\w{3})$')
def check_year(value):
year = None
try:
year = int(value)
except (ValueError, ):
return
else:
if not MAXYEAR >= year >= MINYEAR:
return
return year
class FilterForm(forms.Form):
# TYPES = TYPES
# MEMBERS = MEMBERS
# VISITORS = VISITORS
# PRICE = PRICE
_month_choices = None
_month = None
event_type = FilterTypedMultipleChoiceField(
label=_(u'Тип события'), coerce=int,
choices=TYPES, required=False, widget=FilterCheckboxSelectMultiple())
theme = CountModelMultipleChoiceField(
label=_(u'Тематики'), required=False,
label=_(u'Тематики'), required=False, cache_choices=True,
queryset=Theme.objects.language().values('pk', 'name'))
tag = CountModelMultipleChoiceField(
label=_(u'Теги'), required=False,
label=_(u'Теги'), required=False, cache_choices=True,
queryset=Tag.objects.language().values('pk', 'name'))
country = CountModelMultipleChoiceField(
label=_(u'Страны'), required=False,
label=_(u'Страны'), required=False, cache_choices=True,
queryset=Country.objects.language().values('pk', 'name'))
city = CountModelMultipleChoiceField(
label=_(u'Города'), required=False,
label=_(u'Города'), required=False, cache_choices=True,
queryset=City.objects.language().values('pk', 'name'))
price = FilterTypedMultipleChoiceField(
label=_(u'Стоимость'), coerce=int,
choices=PRICE,
required=False, widget=FilterCheckboxSelectMultiple(),
help_text=_(u'За 1 м<sub>2</sub> необорудованной площади'))
# help_text=_(u'За 1 м<sub>2</sub> необорудованной площади')
)
members = FilterTypedMultipleChoiceField(
label=_(u'Участники'), coerce=int,
choices=MEMBERS,
@ -152,6 +212,22 @@ class FilterForm(forms.Form):
label=_(u'Посетители'), coerce=int,
choices=VISITORS,
required=False, widget=FilterCheckboxSelectMultiple())
year = forms.RegexField(r'^(?P<year>\d{4})$', max_length=4, min_length=4,
required=False, widget=forms.HiddenInput())
default_month = forms.CharField(max_length=3, min_length=3,
required=False, widget=forms.HiddenInput())
default_year = forms.RegexField(r'^(?P<year>\d{4})$', max_length=4, min_length=4,
required=False, widget=forms.HiddenInput())
month = MonthChoiceField(
label=_(u'Период'), coerce=str,
choices=monthes_abr_to_num.iteritems(),
required=False, widget=FilterCheckboxSelectMultiple())
date_from = forms.DateField(required=False, input_formats=('%d.%m.%Y',),
widget=forms.DateInput(attrs={'class': 'date', 'id': 'dateFrom',
'placeholder': _(u'дд.мм.гггг')}))
date_to = forms.DateField(required=False, input_formats=('%d.%m.%Y',),
widget=forms.DateInput(attrs={'class': 'date', 'id': 'dateTo',
'placeholder': _(u'дд.мм.гггг')}))
def __init__(self, *args, **kwargs):
super(FilterForm, self).__init__(*args, **kwargs)
@ -159,7 +235,86 @@ class FilterForm(forms.Form):
self._models = None
self._lookup_kwargs = None
self._lookup_args = None
self._related_fields = []
self._local_fields = []
self.lang = get_language()
self.data = self.data.copy()
self.fill_default_choices_from_cache()
self.fields['month'].choices = self.month_choices()
def fill_default_choices_from_cache(self):
timeout = timedelta(hours=1).seconds
for field in ['theme', 'tag', 'country', 'city']:
key ='filter_{field}_{lang}'.format(field=field, lang=self.lang)
choices = cache.get(key)
if choices is None:
choices = list(self.fields[field].choices)
cache.set(key, choices, timeout)
else:
self.fields[field].choice_cache = choices
@classmethod
def month_choices(cls):
month = datetime.today().month
if cls._month != month or cls._month_choices is None:
year = datetime.today().year
depth = 7 # на сколько сколько месяцев вперед делать выборку (включая текущий)
monthes = dict([(v.get('value'), {'abr': k, 'name': v.get('name')}) for k, v in settings.MONTHES.iteritems()])
choices = []
for month_num in xrange(month, month + depth):
_year = year
if month_num > 12:
_year = year + 1
month_num -= 12
month_values = monthes.get(month_num)
# month_values['name'] = string_concat(month_values['name'], ' ', str(_year))
choices.append((str(_year) + month_values['abr'], string_concat(month_values['name'], ' ', str(_year))))
cls._month_choices = choices
cls._month = month
return cls._month_choices
def clean_year(self, value=None):
year = value or self.cleaned_data.get('year')
year = check_year(year)
self.fields['month'].year = year
return year
def clean_default_month(self):
v = self.cleaned_data.get('default_month')
if v and v in settings.MONTHES.keys():
self.fields['month'].default_month = v
return v
return
def clean_default_year(self):
y = self.cleaned_data.get('default_year')
y = check_year(y)
return y
def clean_month(self):
values = self.cleaned_data.get('month', [])
field = self.fields['month']
new_values = []
for val in values:
if len(val) == 3 and list(filter(lambda x: x[0] == str(field.year or datetime.today().year) + val, field.choices)):
new_values.append(str(field.year or datetime.today().year) + val)
field.choices = filter(lambda x: x[0] != val, field.choices)
else:
new_values.append(val)
if new_values != values:
self.data.setlist('month', new_values)
return new_values
def _post_clean(self):
# нужно для того, чтобы год зашел в поле month
self.get_date_begin_periods()
if not self.cleaned_data.get('default_month') and self.fields['month'].default_month:
self.data['default_month'] = self.fields['month'].default_month
year = self.cleaned_data.get('year')
# if year:
# for val, name in self.fields['month'].additional_choices:
# self.
# additional_choices
def is_valid(self):
# if getattr(self, '_is_valid', None) is None:
@ -215,6 +370,7 @@ class FilterForm(forms.Form):
def filter(self, qs=None):
qs = qs or self.default_filter()
# lookup_kwargs = dict(ChainMap({}, *(lookup_kwargs or self.lookup_kwargs).values()))
qs = self.make_data_begin_filter(qs)
return qs.filter(**self.lookup_kwargs).order_by('data_begin')
def default_filter(self, load_all=True, _models=None):
@ -227,6 +383,12 @@ class FilterForm(forms.Form):
qs = qs.filter(data_begin__gte=datetime.now().date())
return qs
def make_data_begin_filter(self, qs):
params = self.make_date_begin_sqs_params()
if params is not None:
qs = qs.filter(params)
return qs
def recalculate_choices(self):
for field in ['theme', 'tag', 'city', 'country']:
# field_qs = self.default_filter(load_all=False)
@ -255,12 +417,18 @@ class FilterForm(forms.Form):
qs.query.having.add(ExtraWhere(having, [], OR), AND)
qs = qs.values(*values).order_by(*order_by)
self.fields[field].queryset = qs
# print(self.fields[field].queryset.query)
# для того чтобы взяло чойсы из новых результатов
self.fields[field].cache_choices = False
self.fields[field].choice_cache = None
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
self.make_event_type_choices_count()
# self.fields['month'].choices = self.month_choices()
# for field in self.fields:
# field = self.fields[field]
# if hasattr(field, 'queryset'):
@ -271,11 +439,15 @@ class FilterForm(forms.Form):
choices = []
for _type, label in TYPES:
qs = self.default_filter(load_all=False, _models=[types.get(_type)])
qs = self.make_data_begin_filter(qs)
count = qs.filter(**self.lookup_kwargs).count()
choices.append((_type, label + ' <i>({count})</i>'.format(count=count)))
self.fields['event_type'].choices = choices
def make_ids_in_sql_format(self, values):
# stance(values, (list, tuple, set)):
# return tuple(values) if len(values) > 1 else '({})'.format(*values)
# return
return tuple(values) if len(values) > 1 else '({})'.format(*values)
def make_joins_from_selected(self, field, model):
@ -364,6 +536,7 @@ class FilterForm(forms.Form):
joins.extend(_joins)
where.extend(_where)
where.append(self.make_default_where(db_table=model._meta.db_table))
self.make_date_begin_where(where, db_table=model._meta.db_table)
selects.append(select + ''.join(joins) + ' where ' + ' and '.join(where) + group_by)
@ -381,8 +554,7 @@ class FilterForm(forms.Form):
choices.append((val.get('value'), val.get('label') + ' <i>({count})</i>'.format(count=count)))
finally:
c.close()
# some bug with these!
# AttributeError: __exit__
# some bug with these! AttributeError: __exit__
# with connection.cursor() as c:
return choices
@ -390,6 +562,183 @@ class FilterForm(forms.Form):
return ''' (`{db_table}`.`is_published` = True) AND (`{db_table}`.`data_begin` >= '{date_today}') '''\
.format(date_today=datetime.now().strftime('%Y-%m-%d'), **kwargs)
def get_date_begin_periods(self):
periods = getattr(self, '_periods', None)
if periods is None:
periods = {}
cleaned_year = self.cleaned_data.get('year')
cleaned_year_str = str(cleaned_year)
year = cleaned_year or datetime.today().year
current_month = datetime.today().month
for value in self.cleaned_data['month']:
match = year_month_regex.match(value)
if match:
_year, month = int(match.group('year')), match.group('month')
elif cleaned_year and cleaned_year == self.clean_year(value=value):
periods.setdefault(cleaned_year, [])
continue
else:
_year = year
month = value
month_num = monthes_abr_to_num.get(month)
periods.setdefault(_year, []).append(month_num)
if cleaned_year\
and not cleaned_year_str in self.cleaned_data.get('month', [])\
and not periods.get(cleaned_year)\
and not self.fields['month'].default_month\
and not self.cleaned_data.get('default_year'):
periods[cleaned_year] = []
if self.fields['month'].valid_value(cleaned_year):
self.cleaned_data.setdefault('month', []).append(cleaned_year_str)
self.data.update({'month': cleaned_year_str})
self.data['default_year'] = cleaned_year_str
elif cleaned_year\
and not cleaned_year_str in self.cleaned_data.get('month', [])\
and not periods.get(cleaned_year)\
and not self.fields['month'].default_month\
and self.cleaned_data.get('default_year'):
self.fields['month'].choices.insert(0, self.fields['month'].make_year_choice(cleaned_year))
elif cleaned_year and cleaned_year_str in self.cleaned_data.get('month', []) and periods.get(cleaned_year):
periods[cleaned_year] = []
cleaned = self.data.getlist('month', [])
cleaned.remove(cleaned_year_str)
self.cleaned_data.setdefault('month', []).remove(cleaned_year_str)
self.data.setlist('month', cleaned)
elif cleaned_year and periods.get(cleaned_year) and not self.fields['month'].default_month:
self.fields['month'].valid_value(cleaned_year)
elif cleaned_year and self.fields['month'].default_month\
and not list(filter(lambda x: x[0] == cleaned_year_str + self.fields['month'].default_month, self.fields['month'].choices)):
self.fields['month'].valid_value(self.fields['month'].default_month)
self._periods = periods
return periods
def make_date_begin_where(self, where, **kwargs):
_where = []
# years = {}
# current_month = datetime.today().month
# year = datetime.today().year
# for month in self.cleaned_data['month']:
# month_num = monthes_abr_to_num.get(month)
# _year = year
# if month_num < current_month:
# _year += 1
# years.setdefault(_year, []).append(month_num)
# kwargs.update(years)
add_years = set()
for year, monthes in self.get_date_begin_periods().iteritems():
if monthes:
_where.append(
''' (month(`{db_table}`.`data_begin`) in {monthes} and year(`{db_table}`.`data_begin`) = {year}) '''\
.format(year=year, monthes=self.make_ids_in_sql_format(monthes), **kwargs)
)
else:
add_years.add(year)
if add_years:
_where.append(
''' (year(`{db_table}`.`data_begin`) in {year}) '''\
.format(year=self.make_ids_in_sql_format(add_years), **kwargs)
)
if len(_where) == 1:
where.extend(_where)
elif len(_where) > 1:
where.append(''' ({}) '''.format(' OR '.join(_where)))
return
# def get_prev_month(self, date):
# year = date.year
# month = date.month
# day = date.day
# last_day = False
# if day == calendar.monthrange(year, month)[1]:
# last_day = True
# if date.month == 1:
# year -= 1
# month = 12
# if last_day:
# day = calendar.monthrange(year, month)[1]
# return date.replace(month=month, year=year, day=day)
# def get_next_month(self, date):
# year = date.year
# month = date.month
# day = date.day
# last_day = False
# if day == calendar.monthrange(year, month)[1]:
# last_day = True
# if date.month == 12:
# year += 1
# month = 1
# if last_day:
# day = calendar.monthrange(year, month)[1]
# return date.replace(month=month, year=year, day=day)
def get_start_of_period(self, year, periods, month=None):
if month is None:
return year, 1
self.checked_monthes.setdefault(year, set()).add(month)
if month == 12:
prev_month = month - 1
prev_year = year
elif month == 1:
prev_month = 12
prev_year = year - 1
else:
prev_month = month - 1
prev_year = year
if prev_month in periods.get(prev_year, []):
return self.get_start_of_period(prev_year, periods, prev_month)
return year, month
def get_end_of_period(self, year, periods, month=None):
if month is None:
return year, 12
self.checked_monthes.setdefault(year, set()).add(month)
if month == 12:
next_month = 1
next_year = year + 1
elif month == 1:
next_month = month + 1
next_year = year
else:
next_month = month + 1
next_year = year
if next_month in periods.get(next_year, []):
return self.get_end_of_period(next_year, periods, next_month)
return year, month
def make_date_begin_sqs_params(self):
periods = []
self.checked_monthes = {}
cleaned_periods = self.get_date_begin_periods()
# print(cleaned_periods)
for year, monthes in cleaned_periods.iteritems():
for month in monthes:
if month in self.checked_monthes.get(year, []):
continue
start_year, start_month = self.get_start_of_period(year, cleaned_periods, month)
end_year, end_month = self.get_end_of_period(year, cleaned_periods, month)
_first_day, _last_day = calendar.monthrange(end_year, end_month)
periods.append((
date.today().replace(day=1, month=start_month, year=start_year),
date.today().replace(day=_last_day, month=end_month, year=end_year)
))
else:
start_year, start_month = self.get_start_of_period(year, cleaned_periods)
end_year, end_month = self.get_end_of_period(year, cleaned_periods)
_first_day, _last_day = calendar.monthrange(end_year, end_month)
periods.append((
date.today().replace(day=1, month=start_month, year=start_year),
date.today().replace(day=_last_day, month=end_month, year=end_year)
))
params = None
for start, end in periods:
if params is None:
params = SQ(data_begin__range=(start, end))
else:
params |= SQ(data_begin__range=(start, end))
return params
def make_count_select(self, field):
selects = []
case = None
@ -452,7 +801,7 @@ class FilterForm(forms.Form):
joins.extend(_joins)
where.extend(_where)
where.append(self.make_default_where(db_table=model._meta.db_table))
self.make_date_begin_where(where, db_table=model._meta.db_table)
selects.append(select + ''.join(joins) + ' where ' + ' and '.join(where) + group_by)
if len(selects) == 2:

@ -7,4 +7,5 @@ from .views import FilterListView
urlpatterns = patterns('',
url(r'^$', FilterListView.as_view(), name='main'),
url(r'^results/$', FilterListView.as_view(), {'with_form': False}, name='results'),
url(r'^form/$', FilterListView.as_view(), {'with_results': False}, name='form'),
)

@ -36,6 +36,7 @@ class FilterListView(ContextMixin, FormMixin, ListView):
qs = self.form.default_filter()
if self.kwargs.get('with_form', True):
self.form.recalculate_choices()
print(self.form.data, self.form.cleaned_data, self.form.get_date_begin_periods())
# import pdb; pdb.set_trace()
return qs
@ -46,10 +47,11 @@ class FilterListView(ContextMixin, FormMixin, ListView):
# ajax
if request.is_ajax():
ctx = RequestContext(request, self.get_context_data(object_list=self.get_queryset()))
data = {
'success': True,
'results': render_to_string(self._ajax_results_template_name, ctx),
}
data = {'success': True}
if kwargs.get('with_results', True):
data.update({
'results': render_to_string(self._ajax_results_template_name, ctx),
})
if kwargs.get('with_form', True):
data.update({
'form': render_to_string(self._ajax_form_template_name, ctx),
@ -66,9 +68,9 @@ class FilterListView(ContextMixin, FormMixin, ListView):
context = super(FilterListView, self).get_context_data(**kwargs)
# get params for paginator
get = self.request.GET.copy()
get = self.form.data.copy()
if 'page' in get:
del get['page']
context['GETparams'] = get.urlencode()
context['GETparams'] = get.urlencode() if hasattr(get, 'urlencode') else ''
return context

@ -10,7 +10,7 @@ from country.models import Country
from city.models import City
class RedirectFallbackMiddleware(object):
class ExpoRedirectFallbackMiddleware(object):
def process_response(self, request, response):
if response.status_code != 404:
return response # No need to check for a redirect for non-404 responses.
@ -26,9 +26,9 @@ class RedirectFallbackMiddleware(object):
check = re.compile(regex)
match = check.match(full_path)
if match:
response = handler(**match.groupdict())
if response is not None:
return response
_response = handler(**match.groupdict())
if _response is not None:
return _response
return response

@ -1,6 +1,6 @@
{% load i18n static %}
<form action="{% url 'events:main' %}" method="get" class="filter_form">
<form action="{% url 'events:main' %}" method="get" class="filter_form" data-formurl="{% url 'events:form' %}" data-resultsurl="{% url 'events:results' %}">
<div class="filter_form_header">{% trans 'Выбрать по критериям:' %}</div>
<div class="filter_form_clear_all">
@ -12,8 +12,11 @@
<div class="filters">
{# {% csrf_token %} #}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% for field in form %}
{% for field in form.visible_fields %}
{% if field.errors %}error{% endif %}
<label for="{{ field.auto_id }}" class="filter_block_label">
{{ field.label }}

Loading…
Cancel
Save