diff --git a/dienst2/settings.py b/dienst2/settings.py index 04b37887..7def1e6f 100644 --- a/dienst2/settings.py +++ b/dienst2/settings.py @@ -83,6 +83,21 @@ 'django.template.loaders.app_directories.Loader', ) +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ] + } + }, +] + MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', diff --git a/dienst2/templatetags/query_string.py b/dienst2/templatetags/query_string.py new file mode 100644 index 00000000..03bd50e8 --- /dev/null +++ b/dienst2/templatetags/query_string.py @@ -0,0 +1,152 @@ +# As found on https://djangosnippets.org/snippets/2413/ +import re +from django.template import Library, Node, TemplateSyntaxError +from django.http import QueryDict +from django.utils.encoding import smart_str + +register = Library() + +@register.tag +def query_string(parser, token): + """ + Template tag for creating and modifying query strings. + + Syntax: + {% query_string [] [modifier]* [as ] %} + + modifier is where op in {=, +, -} + + Parameters: + - base_querystring: literal query string, e.g. '?tag=python&tag=django&year=2011', + or context variable bound to either + - a literal query string, + - a python dict with potentially lists as values, or + - a django QueryDict object + May be '' or None or missing altogether. + - modifiers may be repeated and have the form . + They are processed in the order they appear. + name is taken as is for a parameter name. + op is one of {=, +, -}. + = replace all existing values of name with value(s) + + add value(s) to existing values for name + - remove value(s) from existing values if present + value is either a literal parameter value + or a context variable. If it is a context variable + it may also be bound to a list. + - as : bind result to context variable instead of injecting in output + (same as in url tag). + + Examples: + 1. {% query_string '?tag=a&m=1&m=3&tag=b' tag+'c' m=2 tag-'b' as myqs %} + + Result: myqs == '?m=2&tag=a&tag=c' + + 2. context = {'qs': {'tag': ['a', 'b'], 'year': 2011, 'month': 2}, + 'tags': ['c', 'd'], + 'm': 4,} + + {% query_string qs tag+tags month=m %} + + Result: '?tag=a&tag=b&tag=c&tag=d&year=2011&month=4 + """ + # matches 'tagname1+val1' or 'tagname1=val1' but not 'anyoldvalue' + mod_re = re.compile(r"^(\w+)(=|\+|-)(.*)$") + bits = token.split_contents() + qdict = None + mods = [] + asvar = None + bits = bits[1:] + if len(bits) >= 2 and bits[-2] == 'as': + asvar = bits[-1] + bits = bits[:-2] + if len(bits) >= 1: + first = bits[0] + if not mod_re.match(first): + qdict = parser.compile_filter(first) + bits = bits[1:] + for bit in bits: + match = mod_re.match(bit) + if not match: + raise TemplateSyntaxError("Malformed arguments to query_string tag") + name, op, value = match.groups() + mods.append((name, op, parser.compile_filter(value))) + return QueryStringNode(qdict, mods, asvar) + +class QueryStringNode(Node): + def __init__(self, qdict, mods, asvar): + self.qdict = qdict + self.mods = mods + self.asvar = asvar + def render(self, context): + mods = [(smart_str(k, 'ascii'), op, v.resolve(context)) + for k, op, v in self.mods] + if self.qdict: + qdict = self.qdict.resolve(context) + else: + qdict = None + # Internally work only with QueryDict + qdict = self._get_initial_query_dict(qdict) + #assert isinstance(qdict, QueryDict) + for k, op, v in mods: + qdict.setlist(k, self._process_list(qdict.getlist(k), op, v)) + qstring = qdict.urlencode() + if qstring: + qstring = '?' + qstring + if self.asvar: + context[self.asvar] = qstring + return '' + else: + return qstring + def _get_initial_query_dict(self, qdict): + if not qdict: + qdict = QueryDict(None, mutable=True) + elif isinstance(qdict, QueryDict): + qdict = qdict.copy() + elif isinstance(qdict, basestring): + if qdict.startswith('?'): + qdict = qdict[1:] + qdict = QueryDict(qdict, mutable=True) + else: + # Accept any old dict or list of pairs. + try: + pairs = qdict.items() + except: + pairs = qdict + qdict = QueryDict(None, mutable=True) + # Enter each pair into QueryDict object: + try: + for k, v in pairs: + # Convert values to unicode so that detecting + # membership works for numbers. + if isinstance(v, (list, tuple)): + for e in v: + qdict.appendlist(k,unicode(e)) + else: + qdict.appendlist(k, unicode(v)) + except: + # Wrong data structure, qdict remains empty. + pass + return qdict + def _process_list(self, current_list, op, val): + if not val: + if op == '=': + return [] + else: + return current_list + # Deal with lists only. + if not isinstance(val, (list, tuple)): + val = [val] + val = [unicode(v) for v in val] + # Remove + if op == '-': + for v in val: + while v in current_list: + current_list.remove(v) + # Replace + elif op == '=': + current_list = val + # Add + elif op == '+': + for v in val: + current_list.append(v) + return current_list diff --git a/ldb/filters.py b/ldb/filters.py new file mode 100644 index 00000000..833ab28b --- /dev/null +++ b/ldb/filters.py @@ -0,0 +1,9 @@ +import django_filters +from ldb.models import CommitteeMembership + + +class CommitteeMembershipFilter(django_filters.FilterSet): + + class Meta: + model = CommitteeMembership + fields = ('board', 'committee') diff --git a/ldb/templates/ldb/base.html b/ldb/templates/ldb/base.html index 8e96a368..886ee235 100644 --- a/ldb/templates/ldb/base.html +++ b/ldb/templates/ldb/base.html @@ -4,7 +4,7 @@
  • Zoeken
  • Persoon toevoegen
  • Organisatie toevoegen
  • -
  • Commissies
  • +
  • Commissies
  • Exporteren
  • {% if user.is_staff %}
  • Beheer
  • {% endif %} diff --git a/ldb/templates/ldb/committeemembership_filter.html b/ldb/templates/ldb/committeemembership_filter.html new file mode 100644 index 00000000..aacdb87f --- /dev/null +++ b/ldb/templates/ldb/committeemembership_filter.html @@ -0,0 +1,75 @@ +{% extends 'ldb/base.html' %} +{% load i18n static bootstrap3 query_string %} + +{% block title %}{% trans 'Committees' %}{% endblock %} + +{% block content %} + + +
    +
    +

    + {% trans 'Filter' %} +

    +
    +
    +
    + {% bootstrap_form filter.form layout='inline' %} + +
    +
    +
    + +
    +
    +

    + {% trans 'Search results' %} {{ filter.count }} +

    +
    + + + + + + + + + + + + {% for obj in object_list %} + + + + + + + + {% empty %} + + + + {% endfor %} + +
    {% trans "Board" %}{% trans "Committee" %}{% trans "Name" %}{% trans "Position" %}{% trans "RAS" %}
    {{ obj.board }}{{ obj.committee }}{{ obj.person }}{{ obj.position }}{{ obj.ras_months }}
    {% trans "No people found." %}
    +
    + + {% if is_paginated %} + + {% endif %} +{% endblock %} diff --git a/ldb/urls.py b/ldb/urls.py index 8e495815..3791ba62 100644 --- a/ldb/urls.py +++ b/ldb/urls.py @@ -6,7 +6,7 @@ from ldb.api import * from ldb.export import ExportResource from ldb.views import PersonDetailView, PersonDeleteView, OrganizationDetailView, OrganizationDeleteView, \ - index_old, PersonEditView, OrganizationEditView, ResultsView + index_old, PersonEditView, OrganizationEditView, ResultsView, CommitteeMembershipFilterView api = Api(api_name='v2') @@ -49,5 +49,5 @@ name='ldb_organizations_delete'), url(r'^organizations/(?P\d+)/edit/$', OrganizationEditView.as_view()), url(r'^organizations/create/$', OrganizationEditView.as_view(), name='ldb_organizations_create'), - + url(r'^committees/$', CommitteeMembershipFilterView.as_view(), name='ldb_committees'), ) diff --git a/ldb/views.py b/ldb/views.py index 3690fb82..fddc4636 100644 --- a/ldb/views.py +++ b/ldb/views.py @@ -5,11 +5,13 @@ from django.template import RequestContext from django.views.generic import DetailView, DeleteView, TemplateView, UpdateView from django.views.generic.detail import SingleObjectMixin +from django_filters.views import FilterView from haystack.inputs import Raw from haystack.query import SearchQuerySet from haystack.query import SQ from dienst2.extras import convert_free_search +from ldb.filters import CommitteeMembershipFilter from ldb.forms import PersonForm, MemberFormSet, StudentFormSet, AlumnusFormSet, EmployeeFormSet, \ CommitteeMembershipFormSet from ldb.models import Organization, Person, CommitteeMembership @@ -188,3 +190,19 @@ def get_object(self, **kwargs): return super(PersonEditView, self).get_object(**kwargs) except AttributeError: return None + + +class CommitteeMembershipFilterView(FilterView): + filterset_class = CommitteeMembershipFilter + paginate_by = 50 + + def get(self, request, *args, **kwargs): + filterset_class = self.get_filterset_class() + self.filterset = self.get_filterset(filterset_class) + self.object_list = self.filterset.qs\ + .select_related('person')\ + .prefetch_related('committee')\ + .order_by('board', 'committee__name', 'person__firstname') + context = self.get_context_data(filter=self.filterset, + object_list=self.object_list) + return self.render_to_response(context)