# -*- coding: utf-8 -*- from itertools import chain from django import forms from django.utils.translation import get_language, ugettext as _ 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 from haystack.query import SearchQuerySet, RelatedSearchQuerySet from functions.model_utils import EnumChoices from exposition.models import Exposition from conference.models import Conference from theme.models import Theme, Tag class CountModelMultipleChoiceField(forms.ModelMultipleChoiceField): def label_from_instance(self, obj): return u'{label} ({count})'.format(label=smart_text(obj.name), count=obj.count) ### Делаем выборку по темам, сразу заполняя перевод и кол-во событиый ## 1-й рабочий способ (без заполнения перевода) # """ # SELECT # `theme_theme`.`id`, # ( # SELECT COUNT(`exposition_exposition_theme`.`exposition_id`) # FROM `exposition_exposition_theme` # WHERE (`theme_theme`.`id` = `exposition_exposition_theme`.`theme_id`) # ) AS `e_count`, # ( # SELECT COUNT(`conference_conference_theme`.`conference_id`) # FROM `conference_conference_theme` # WHERE (`theme_theme`.`id` = `conference_conference_theme`.`theme_id`) # ) AS `c_count`, # `theme_theme_translation`.`name` as `name` # FROM # `theme_theme` # GROUP BY `theme_theme`.`id` # ORDER BY NULL # """ ## 2-й рабочий способ (с заполнением перевода) ## аттрибут перевода 'name' присвоен на 'name_t', чтобы не ругалось на неправильно заполненый перевод # theme_sql = \ # """ # SELECT # `theme_theme_translation`.`name` AS `name_t`, # `theme_theme_translation`.`master_id` AS `id`, # ( # SELECT COUNT(`exposition_exposition_theme`.`exposition_id`) # FROM `exposition_exposition_theme` # WHERE (`theme_theme_translation`.`master_id` = `exposition_exposition_theme`.`theme_id`) # ) AS `e_count`, # ( # SELECT COUNT(`conference_conference_theme`.`conference_id`) # FROM `conference_conference_theme` # WHERE (`theme_theme_translation`.`master_id` = `conference_conference_theme`.`theme_id`) # ) AS `c_count` # FROM # `theme_theme_translation` # WHERE (`theme_theme_translation`.`language_code` = 'ru') # GROUP BY `theme_theme_translation`.`name` # ORDER BY NULL # """ # qs = Theme.objects.raw(theme_sql) extra_theme_expo_count = '''SELECT COUNT(`exposition_exposition_theme`.`exposition_id`) FROM `exposition_exposition_theme` WHERE (`theme_theme`.`id` = `exposition_exposition_theme`.`theme_id`) ''' extra_theme_conf_count = '''SELECT COUNT(`conference_conference_theme`.`conference_id`) FROM `conference_conference_theme` WHERE (`theme_theme`.`id` = `conference_conference_theme`.`theme_id`) ''' extra_tag_expo_count = '''SELECT COUNT(`exposition_exposition_tag`.`exposition_id`) FROM `exposition_exposition_tag` WHERE (`theme_tag`.`id` = `exposition_exposition_tag`.`tag_id`) ''' extra_tag_conf_count = '''SELECT COUNT(`conference_conference_tag`.`conference_id`) FROM `conference_conference_tag` WHERE (`theme_tag`.`id` = `conference_conference_tag`.`tag_id`) ''' class FilterForm(forms.Form): TYPES = EnumChoices( EXPO=(1, _(u'Выставки')), CONF=(2, _(_(u'Конференции'))), ) model = forms.TypedMultipleChoiceField(label=_(u'Тип события'), coerce=int, choices=TYPES, required=False, widget=forms.CheckboxSelectMultiple()) theme = CountModelMultipleChoiceField(label=_(u'Тематики'), queryset=Theme.objects.none(), required=False, widget=forms.CheckboxSelectMultiple()) tag = CountModelMultipleChoiceField(label=_(u'Теги'), queryset=Tag.objects.none(), required=False, widget=forms.CheckboxSelectMultiple()) def __init__(self, *args, **kwargs): super(FilterForm, self).__init__(*args, **kwargs) self._is_valid = False self._models = None self.fields['theme'].queryset = self.get_theme_choices() self.fields['tag'].queryset = self.get_tag_choices() def is_valid(self): # if getattr(self, '_is_valid', None) is None: self._is_valid = super(FilterForm, self).is_valid() # нам нужно сбрасывать сохраненные модели, # т.к. после валидации нужно вернуть только выбранные self._models = None return self._is_valid @property def models(self): if self._models is None and self._is_valid: val = self.cleaned_data.get('model') self._models = [] if self.TYPES.EXPO in val: self._models.append(Exposition) if self.TYPES.CONF in val: self._models.append(Conference) return self._models or [Exposition, Conference] def get_theme_choices(self): # 3-й рабочий способ (с родным заполением перевода) # в ходе поиска решения, был найден и 4-й рабочий способ используя RawSQL, но он для Django >= 1.8 # https://docs.djangoproject.com/en/1.9/ref/models/expressions/#raw-sql-expressions # from django.db.models.expressions import RawSQL if getattr(self, '_theme_choices', None) is None: if Exposition in self.models and Conference in self.models: count_query = '({q1}) + ({q2})'.format( q1=extra_theme_expo_count, q2=extra_theme_conf_count) filter_types = Q(types=Theme.types.conference) | Q(types=Theme.types.exposition) elif Exposition in self.models: count_query = extra_theme_expo_count filter_types = Q(types=Theme.types.exposition) elif Conference in self.models: count_query = extra_theme_conf_count filter_types = Q(types=Theme.types.conference) self._theme_choices = Theme.objects.language()\ .filter(filter_types)\ .extra(select={'count': count_query})\ .order_by('-count', '-name') return self._theme_choices def get_tag_choices(self): extra_tag_select = {} if getattr(self, '_tag_choices', None) is None: if Exposition in self.models and Conference in self.models: count_query = '({q1}) + ({q2})'.format( q1=extra_tag_expo_count, q2=extra_tag_conf_count) elif Exposition in self.models: count_query = extra_tag_expo_count elif Conference in self.models: count_query = extra_tag_conf_count self._tag_choices = Tag.objects.language()\ .extra(select={'count': count_query})\ .order_by('-count', '-name') return self._tag_choices def filter(self): qs = self.default_filter() d = self.cleaned_data if d.get('theme'): qs = qs.filter(theme__in=d.get('theme')) if d.get('tag'): qs = qs.filter(tag__in=d.get('tag')) return qs def default_filter(self): qs = RelatedSearchQuerySet().models(*self.models).load_all() for model in self.models: qs = qs.load_all_queryset(model, model.enable.all() # не реализовано в hvad <_< # .only( # 'canceled', 'name', 'main_title', 'expohit', 'logo', # 'quality_label', 'services', 'visitors', 'members', # 'data_begin', 'data_end', 'country__url', 'country__name', # 'city__url', 'place__name' # ) ) return qs