From f1c914d321a7cf046d00ad4325ce1e5bee10485f Mon Sep 17 00:00:00 2001 From: Jeffrey de Lange Date: Sun, 20 Sep 2015 19:12:21 +0200 Subject: [PATCH 1/5] Created angular free version of committees page Work on #50 --- ldb/filters.py | 9 +++++ ldb/templates/ldb/base.html | 2 +- .../ldb/committeemembership_filter.html | 40 +++++++++++++++++++ ldb/urls.py | 4 +- ldb/views.py | 6 +++ 5 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 ldb/filters.py create mode 100644 ldb/templates/ldb/committeemembership_filter.html 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..347bbc28 --- /dev/null +++ b/ldb/templates/ldb/committeemembership_filter.html @@ -0,0 +1,40 @@ +{% extends 'ldb/base.html' %} +{% load i18n static bootstrap3 %} + +{% block title %}{% trans 'Committees' %}{% endblock %} + +{% block content %} + + +
    + {% bootstrap_form filter.form layout='inline' %} + +
    + + + + + + + + + + + + + {% 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." %}
    +{% 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..0b1cb699 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,7 @@ def get_object(self, **kwargs): return super(PersonEditView, self).get_object(**kwargs) except AttributeError: return None + + +class CommitteeMembershipFilterView(FilterView): + filterset_class = CommitteeMembershipFilter From 8323a178723c70a11f5e63baca1cd3b5278ab5ce Mon Sep 17 00:00:00 2001 From: Jeffrey de Lange Date: Sun, 20 Sep 2015 19:25:38 +0200 Subject: [PATCH 2/5] Added select/prefetch related to committee view --- ldb/views.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ldb/views.py b/ldb/views.py index 0b1cb699..2c1fd659 100644 --- a/ldb/views.py +++ b/ldb/views.py @@ -194,3 +194,11 @@ def get_object(self, **kwargs): class CommitteeMembershipFilterView(FilterView): filterset_class = CommitteeMembershipFilter + + 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') + context = self.get_context_data(filter=self.filterset, + object_list=self.object_list) + return self.render_to_response(context) From 0abf9729bb698da9403ac6a3766c7a189af376bb Mon Sep 17 00:00:00 2001 From: Jeffrey de Lange Date: Wed, 23 Sep 2015 10:15:00 +0200 Subject: [PATCH 3/5] Added order by for committees --- ldb/views.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ldb/views.py b/ldb/views.py index 2c1fd659..6c60c190 100644 --- a/ldb/views.py +++ b/ldb/views.py @@ -198,7 +198,10 @@ class CommitteeMembershipFilterView(FilterView): 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') + 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) From 10c90c456b778aab12ac7e5baf32d58a651e1039 Mon Sep 17 00:00:00 2001 From: Jeffrey de Lange Date: Thu, 24 Sep 2015 19:25:52 +0200 Subject: [PATCH 4/5] Added query string templatetag --- dienst2/templatetags/query_string.py | 152 +++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 dienst2/templatetags/query_string.py 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 From dca13e491dea554b9d266a76d4991321df37b419 Mon Sep 17 00:00:00 2001 From: Jeffrey de Lange Date: Thu, 24 Sep 2015 19:26:02 +0200 Subject: [PATCH 5/5] Added pagination on committees page --- dienst2/settings.py | 15 ++++ .../ldb/committeemembership_filter.html | 83 +++++++++++++------ ldb/views.py | 1 + 3 files changed, 75 insertions(+), 24 deletions(-) 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/ldb/templates/ldb/committeemembership_filter.html b/ldb/templates/ldb/committeemembership_filter.html index 347bbc28..aacdb87f 100644 --- a/ldb/templates/ldb/committeemembership_filter.html +++ b/ldb/templates/ldb/committeemembership_filter.html @@ -1,18 +1,33 @@ {% extends 'ldb/base.html' %} -{% load i18n static bootstrap3 %} +{% load i18n static bootstrap3 query_string %} {% block title %}{% trans 'Committees' %}{% endblock %} {% block content %} -
    - {% bootstrap_form filter.form layout='inline' %} - -
    +
    +
    +

    + {% trans 'Filter' %} +

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

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

    +
    +
    + @@ -20,21 +35,41 @@ - - - {% for obj in object_list %} - - - - - - - - {% empty %} - - - - {% endfor %} - -
    {% trans "Board" %} {% trans "Committee" %} {% trans "Position" %} {% trans "RAS" %}
    {{ obj.board }}{{ obj.committee }}{{ obj.person }}{{ obj.position }}{{ obj.ras_months }}
    {% trans "No people found." %}
    + + + {% for obj in object_list %} + + {{ obj.board }} + {{ obj.committee }} + {{ obj.person }} + {{ obj.position }} + {{ obj.ras_months }} + + {% empty %} + + {% trans "No people found." %} + + {% endfor %} + + + + + {% if is_paginated %} + + {% endif %} {% endblock %} diff --git a/ldb/views.py b/ldb/views.py index 6c60c190..fddc4636 100644 --- a/ldb/views.py +++ b/ldb/views.py @@ -194,6 +194,7 @@ def get_object(self, **kwargs): class CommitteeMembershipFilterView(FilterView): filterset_class = CommitteeMembershipFilter + paginate_by = 50 def get(self, request, *args, **kwargs): filterset_class = self.get_filterset_class()