#-- encoding: utf-8 --
#
# This file is part of I4P.
#
# I4P is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# I4P is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero Public License for more details.
#
# You should have received a copy of the GNU Affero Public License
# along with I4P. If not, see <http://www.gnu.org/licenses/>.
#
"""
Filter framework
"""
from itertools import chain
from django import forms
from django.contrib.sites.models import Site
from django.db.models import Q
from django.forms.widgets import SelectMultiple, CheckboxInput
from django.utils.encoding import force_unicode
from django.utils.html import conditional_escape
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext as _
from tagging.models import TaggedItem, Tag
from apps.i4p_base.models import I4P_COUNTRIES
from apps.project_sheet.models import ProjectMember, Objective
from .models import I4pProjectTranslation, I4pProject, SiteTopic
ORDER_CHOICES = (
('lte', '<='),
('gte', '>=')
)
[docs]class FilterSet(object):
"""
Filters container
"""
def __init__(self, filters):
self.forms = filters
def is_valid(self):
res = True
for filt in self:
res &= filt.is_valid()
return res
def apply_to(self, queryset, model_class=None):
qs = queryset
for filter in self.forms:
qs = filter.apply_to(qs, model_class)
return qs
def __iter__(self):
return self.forms.__iter__()
[docs]class FilterForm(forms.Form):
"""
Filter base class
"""
def apply_to(self, queryset, model_class=None):
raise NotImplementedError
[docs]class ThemesFilterForm(FilterForm):
"""
Implements a filter on I4pProjectTranslation themes (tags)
"""
themes = forms.CharField(required=False, label=_("Themes contain"))
def clean_themes(self):
data = self.cleaned_data.get('themes', '')
themes = []
if data:
for tag_ids in data.split(','):
try:
tag_id = int(tag_ids)
tag = Tag.objects.filter(id=tag_id)
themes.extend(tag)
except ValueError:
raise forms.ValidationError("Must be an integer id");
except Tag.DoesNotExist:
raise forms.ValidationError("Unknown theme");
return themes
# TODO model_class is useless
def apply_to(self, queryset, model_class=None):
qs = queryset
if qs.query.model == I4pProjectTranslation:
tags = self.cleaned_data.get("themes")
if tags:
qs = TaggedItem.objects.get_union_by_model(queryset, tags)
return qs
[docs]class WithMembersFilterForm(FilterForm):
"""
Implements a filter on I4pProject including or not members
"""
with_members = forms.BooleanField(required=False, label=_('With associated leaders'))
without_members = forms.BooleanField(required=False, label=_('Without associated leaders'))
def apply_to(self, queryset, model_class=None):
qs = queryset
if qs.query.model == I4pProject:
with_members = self.cleaned_data.get("with_members")
without_members = self.cleaned_data.get("without_members")
if with_members != without_members:
projects_with_members = ProjectMember.objects.values_list('project__id', flat=True).distinct()
if with_members:
qs = qs.filter(id__in=projects_with_members)
else:
qs = qs.exclude(id__in=projects_with_members)
return qs
def __init__(self, *args, **kwargs):
super(FilterForm, self).__init__(*args, **kwargs)
self.fields['with_members'].widget.attrs['class'] = 'styled'
self.fields['without_members'].widget.attrs['class'] = 'styled'
[docs]class MyCheckboxSelectMultiple(SelectMultiple):
"""
Implements custom multi select checkbox widget
FIXME: find source, maybe djangosnippets.org
"""
def render(self, name, value, attrs=None, choices=()):
if value is None: value = []
has_id = attrs and 'id' in attrs
final_attrs = self.build_attrs(attrs, name=name)
output = [u'<ul>']
# Normalize to strings
str_values = set([force_unicode(v) for v in value])
for i, (option_value, option_label) in enumerate(chain(self.choices, choices)):
if has_id:
final_attrs = dict(final_attrs, id='%s_%s' % (attrs['id'], i))
checkbox = CheckboxInput(final_attrs, check_test=lambda value: value in str_values)
option_value = force_unicode(option_value)
rendered_checkbox = checkbox.render(name, option_value)
option_label = conditional_escape(force_unicode(option_label))
output.append(u'<li id="filter_val_%s" title="%s">%s<p>%s</p></li>' % (option_value.lower(),
option_label.title(),
rendered_checkbox,
option_label))
output.append(u'</ul>')
return mark_safe(u'\n'.join(output))
def id_for_label(self, id_):
# See the comment for RadioSelect.id_for_label()
if id_:
id_ += '_0'
return id_
id_for_label = classmethod(id_for_label)
[docs]class ProjectStatusFilterForm(FilterForm):
"""
Implements a filter on I4pProject status
"""
status = forms.TypedMultipleChoiceField(required=False, coerce=str,
choices=I4pProject.STATUS_CHOICES,
widget=MyCheckboxSelectMultiple)
def apply_to(self, queryset, model_class):
qs = queryset
if model_class == I4pProject:
data = self.cleaned_data.get("status")
if data :
q_objects = None
for val in data:
lookup = {"status" : val}
if q_objects :
q_objects |= Q(**lookup)
else :
q_objects = Q(**lookup)
qs = qs.filter(q_objects)
return qs
def __init__(self, *args, **kwargs):
super(FilterForm, self).__init__(*args, **kwargs)
self.fields['status'].widget.attrs['class'] = 'styled'
[docs]class BestOfFilterForm(FilterForm):
"""
Implements a filter on I4pProject best of
"""
best_of = forms.BooleanField(required=False)
def apply_to(self, queryset, model_class):
qs = queryset
if model_class == I4pProject:
val = self.cleaned_data.get("best_of")
if val :
qs = qs.filter(best_of=val)
return qs
def __init__(self, *args, **kwargs):
super(FilterForm, self).__init__(*args, **kwargs)
self.fields['best_of'].widget.attrs['class'] = 'styled'
[docs]class TopicFilterForm(FilterForm):
"""
Implements a filter on I4pProject topics
"""
topics = forms.ModelMultipleChoiceField(required=False,
queryset=SiteTopic.objects.all())
def apply_to(self, queryset, model_class):
qs = queryset
if model_class == I4pProject:
val = self.cleaned_data.get("topics")
if val:
site = Site.objects.get_current()
sitetopic = SiteTopic.objects.get(id=val,
site=site)
qs = sitetopic.projects.filter(id__in=qs)
return qs
def __init__(self, *args, **kwargs):
super(FilterForm, self).__init__(*args, **kwargs)
self.fields['topics'].queryset = SiteTopic.objects.filter(site=Site.objects.get_current())
[docs]class ProjectProgressFilterForm(FilterForm):
"""
Implements a filter on I4pProjectTranslation progression
"""
progress = forms.TypedMultipleChoiceField(required=False, coerce=str,
choices=I4pProjectTranslation.PROGRESS_CHOICES,
widget=MyCheckboxSelectMultiple)
def apply_to(self, queryset, model_class):
qs = queryset
if model_class == I4pProjectTranslation:
data = self.cleaned_data.get("progress")
if data :
q_objects = None
for val in data:
lookup = {"completion_progress" : val}
if q_objects :
q_objects |= Q(**lookup)
else :
q_objects = Q(**lookup)
qs = qs.filter(q_objects)
return qs
def __init__(self, *args, **kwargs):
super(FilterForm, self).__init__(*args, **kwargs)
self.fields['progress'].widget.attrs['class'] = 'styled'
[docs]def current_countries():
"""
Build the list of countries used in project location
"""
result = [("", _("World"))]
project_countries = I4pProject.on_site.exclude(location__country='').\
exclude(location__country__isnull=True).\
distinct().\
order_by('location__country').\
values_list('location__country', flat=True)
for country in project_countries:
for code, name in I4P_COUNTRIES:
if country == code:
result.append((code, name))
break
return result
[docs]class ProjectLocationFilterForm(FilterForm):
"""
Implements a filter on I4pProject location
"""
country = forms.ChoiceField(required=False, choices=current_countries())
def apply_to(self, queryset, model_class):
qs = queryset
if model_class == I4pProject:
data = self.cleaned_data.get("country")
if data:
print data
q_objects = None
# Dirty Fix: switch depending on type. Loop if list.
if isinstance(data, unicode):
lookup = {"location__country" : data}
q_objects = Q(**lookup)
else:
for val in data:
lookup = {"location__country" : val}
if q_objects :
q_objects |= Q(**lookup)
else :
q_objects = Q(**lookup)
qs = qs.filter(q_objects)
return qs
[docs]class ProjectObjectiveFilterForm(FilterForm):
"""
Implements a filter on I4pProject objective
"""
objectives = forms.ModelMultipleChoiceField(required=False,
queryset=Objective.objects.all())
def apply_to(self, queryset, model_class):
qs = queryset
if model_class == I4pProject:
data = self.cleaned_data.get("objectives")
if data:
qs = qs.filter(objectives__in=[d.id for d in data])
return qs
[docs]class NameBaselineFilterForm(FilterForm):
"""
Simulates a full-text search in either the baseline or the title.
"""
text = forms.CharField(max_length=100, required=False)
def apply_to(self, queryset, model_class):
if model_class == I4pProjectTranslation:
text = self.cleaned_data.get("text")
filters = Q(title__icontains=text) | Q(baseline__icontains=text)
queryset = queryset.filter(filters)
return queryset