From f3dbec099e62eec49b0a9e1d293e7f0dc12361c1 Mon Sep 17 00:00:00 2001
From: Radek Czajka <radekczajka@nowoczesnapolska.org.pl>
Date: Thu, 23 Oct 2014 16:03:31 +0200
Subject: [PATCH] Nicer dictionary filtering.

---
 .../locale/pl/LC_MESSAGES/django.mo           | Bin 788 -> 962 bytes
 .../locale/pl/LC_MESSAGES/django.po           |  33 ++++++++--
 .../migrations/0003_auto_20141023_1445.py     |  36 +++++++++++
 apps/dictionary/models.py                     |  31 +++++++--
 .../templates/dictionary/note_list.html       |  17 ++---
 apps/dictionary/templatetags/__init__.py      |   0
 apps/dictionary/templatetags/set_get.py       |  20 ++++++
 apps/dictionary/tests.py                      |  14 ++--
 apps/dictionary/views.py                      |  61 +++++++++++-------
 lib/librarian                                 |   2 +-
 10 files changed, 164 insertions(+), 50 deletions(-)
 create mode 100644 apps/dictionary/migrations/0003_auto_20141023_1445.py
 create mode 100644 apps/dictionary/templatetags/__init__.py
 create mode 100644 apps/dictionary/templatetags/set_get.py

diff --git a/apps/dictionary/locale/pl/LC_MESSAGES/django.mo b/apps/dictionary/locale/pl/LC_MESSAGES/django.mo
index 8290bd9ff00464cfe669fd34239d947893601d3d..2dbef9a045833f100998fd2d17891054e24c1492 100644
GIT binary patch
delta 385
zcmbQjc8I<Ho)F7a1|Z-7Vi_Qg0b*_-o&&@nZ~}<6fcPX3^8@i0AO^{^FfuUk0BHdr
z%?_kxfix$ORt3_`Kw2M2KLcW8APxlbrGSDU{W(y&21tuCFt{=F02v^I76ECHBh~|H
zkU=|uG*B<ta3ICN2E?o&1`sd-F$)lb)Pn%?#8!I+r%HvK#Ju#<#Pn1KAiJ<MF()%E
zGqniBDXA<-op?CI)I`_NK-b7v!O+agz)0J`$iRRrz+X2gwJftZGe1w)C9x#cO2Np$
z5Lun^<^)DNM#%_W_td=9qQsK?A}fV}{M3}p5(Ps&Q$3T(bD2_%!c$X@HkGC;WF47V
aRhga00OMzugI$pg(uNQPI;V6Jvj_l&lS%gg

delta 214
zcmX@aK83CRo)F7a1|VPsVi_QI0b+I_&H-W&=m26ZAnpWWejwfg#GF8U1c;e|_#6<Q
zVPs&q1f&Cjcs>(E-A5qJ0pv3R6@k=q0coHz1{NR&>4O3`2B10y)`>^$C#y213mEGf
z8tEFEDHvE<85w9B7*3wUD8Xx}YhVcyG&Hj^G}wHQ(T;JlAG73SBW5q*fTF6(g3RJd
PhVtU7%Hopj%v1&ddaEFo

diff --git a/apps/dictionary/locale/pl/LC_MESSAGES/django.po b/apps/dictionary/locale/pl/LC_MESSAGES/django.po
index 0fc8f2955..73c52d131 100644
--- a/apps/dictionary/locale/pl/LC_MESSAGES/django.po
+++ b/apps/dictionary/locale/pl/LC_MESSAGES/django.po
@@ -7,8 +7,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: WolneLektury-dictionary\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-12-16 09:20+0100\n"
-"PO-Revision-Date: 2011-09-16 16:10+0100\n"
+"POT-Creation-Date: 2014-10-23 16:02+0200\n"
+"PO-Revision-Date: 2014-10-23 16:03+0100\n"
 "Last-Translator: Radek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
 "Language: pl\n"
@@ -17,22 +17,41 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
 "|| n%100>=20) ? 1 : 2);\n"
+"X-Generator: Poedit 1.5.4\n"
 
-#: templates/dictionary/note_list.html:7
-#: templates/dictionary/note_list.html:11
+#: templates/dictionary/note_list.html:8
+#: templates/dictionary/note_list.html:12
 msgid "Footnotes"
 msgstr "Przypisy"
 
-#: templates/dictionary/note_list.html:16
+#: templates/dictionary/note_list.html:17
 msgid "By first letter"
 msgstr "Pierwsza litera"
 
-#: templates/dictionary/note_list.html:18
-#: templates/dictionary/note_list.html:20
+#: templates/dictionary/note_list.html:19
+#: templates/dictionary/note_list.html:21
+#: templates/dictionary/note_list.html:37
+#: templates/dictionary/note_list.html:39
+#: templates/dictionary/note_list.html:56
+#: templates/dictionary/note_list.html:58
+#: templates/dictionary/note_list.html:74
+#: templates/dictionary/note_list.html:76
 msgid "all"
 msgstr "wszystkie"
 
 #: templates/dictionary/note_list.html:35
+msgid "By type"
+msgstr "Według typu"
+
+#: templates/dictionary/note_list.html:54
+msgid "By qualifier"
+msgstr "Według kwalifikatora"
+
+#: templates/dictionary/note_list.html:72
+msgid "By language"
+msgstr "Według języka"
+
+#: templates/dictionary/note_list.html:92
 #, python-format
 msgid "%(c)s footnote found"
 msgid_plural "%(c)s footnotes found"
diff --git a/apps/dictionary/migrations/0003_auto_20141023_1445.py b/apps/dictionary/migrations/0003_auto_20141023_1445.py
new file mode 100644
index 000000000..ee4beeebe
--- /dev/null
+++ b/apps/dictionary/migrations/0003_auto_20141023_1445.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('dictionary', '0002_auto_20141006_1422'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Qualifier',
+            fields=[
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+                ('qualifier', models.CharField(unique=True, max_length=128, db_index=True)),
+                ('name', models.CharField(max_length=255)),
+            ],
+            options={
+                'ordering': ['qualifier'],
+            },
+            bases=(models.Model,),
+        ),
+        migrations.RemoveField(
+            model_name='note',
+            name='qualifier',
+        ),
+        migrations.AddField(
+            model_name='note',
+            name='qualifiers',
+            field=models.ManyToManyField(to='dictionary.Qualifier', null=True),
+            preserve_default=True,
+        ),
+    ]
diff --git a/apps/dictionary/models.py b/apps/dictionary/models.py
index 73fbb0d47..d0d8b69ac 100644
--- a/apps/dictionary/models.py
+++ b/apps/dictionary/models.py
@@ -12,12 +12,23 @@ task_logger = get_task_logger(__name__)
 from catalogue.models import Book
 
 
+class Qualifier(models.Model):
+    qualifier = models.CharField(max_length=128, db_index=True, unique=True)
+    name = models.CharField(max_length=255)
+
+    class Meta:
+        ordering = ['qualifier']
+
+    def __unicode__(self):
+        return self.name or self.qualifier
+
+
 class Note(models.Model):
     """Represents a single annotation from a book."""
     html = models.TextField()
     sort_key = models.CharField(max_length=128, db_index=True)
     fn_type = models.CharField(max_length=10, db_index=True)
-    qualifier = models.CharField(max_length=128, db_index=True, blank=True)
+    qualifiers = models.ManyToManyField(Qualifier, null=True)
     language = models.CharField(max_length=10, db_index=True)
 
     class Meta:
@@ -41,25 +52,35 @@ def build_notes(book):
         book.notesource_set.all().delete()
         if book.html_file:
             from librarian import html
-            for anchor, fn_type, qualifier, text_str, html_str in \
+            from librarian.fn_qualifiers import FN_QUALIFIERS
+
+            for anchor, fn_type, qualifiers, text_str, html_str in \
                     html.extract_annotations(book.html_file.path):
                 sort_key = sortify(text_str).strip()[:128]
-                qualifier = (qualifier or '')[:128]
+
                 language = book.language
                 note = None
                 notes = Note.objects.filter(sort_key=sort_key,
-                    qualifier=qualifier, fn_type=fn_type,
+                    fn_type=fn_type,
                     language=language, html=html_str)
                 if notes:
                     note = notes[0]
                 else:
                     note = Note.objects.create(
                         sort_key=sort_key,
-                        qualifier=qualifier,
                         html=html_str,
                         fn_type=fn_type,
                         language=language
                         )
+
+                qualifier_objects = []
+                for qualifier in qualifiers:
+                    obj, created = Qualifier.objects.get_or_create(
+                        qualifier=qualifier, defaults={
+                            'name': FN_QUALIFIERS.get(qualifier, '')
+                        })
+                    qualifier_objects.append(obj)
+                note.qualifiers = qualifier_objects
                 note.notesource_set.create(book=book, anchor=anchor)
 
         Note.objects.filter(notesource=None).delete()
diff --git a/apps/dictionary/templates/dictionary/note_list.html b/apps/dictionary/templates/dictionary/note_list.html
index 5109454b8..330b2a7f8 100755
--- a/apps/dictionary/templates/dictionary/note_list.html
+++ b/apps/dictionary/templates/dictionary/note_list.html
@@ -1,5 +1,6 @@
 {% extends "base.html" %}
 {% load i18n pagination_tags %}
+{% load set_get from set_get %}
 
 
 {% block bodyid %}footnotes{% endblock %}
@@ -15,7 +16,7 @@
 <p>
 {% trans "By first letter" %}:
 {% if letter %}
-    <a href='?'>{% trans "all" %}</a>
+    <a href='?{% set_get "page" "ltr" %}'>{% trans "all" %}</a>
 {% else %}
     <strong>{% trans "all" %}</strong>
 {% endif %}
@@ -25,7 +26,7 @@
     {% if let == letter %}
         <strong>{{ let|upper }}</strong>
     {% else %}
-        <a href='?ltr={{ let }}'>{{ let|upper }}</a>
+        <a href='?{% set_get "page" ltr=let %}'>{{ let|upper }}</a>
     {% endif %}
 {% endfor %}
 </p>
@@ -33,7 +34,7 @@
 <p>
 {% trans "By type" %}:
 {% if fn_type %}
-    <a href='?'>{% trans "all" %}</a>
+    <a href='?{% set_get "page" "type" %}'>{% trans "all" %}</a>
 {% else %}
     <strong>{% trans "all" %}</strong>
 {% endif %}
@@ -43,7 +44,7 @@
     {% if fnt == fn_type %}
         <strong>{{ fnt }}</strong>
     {% else %}
-        <a href='?type={{ fnt }}'>{{ fnt }}</a>
+        <a href='?{% set_get "page" type=fnt %}'>{{ fnt }}</a>
     {% endif %}
 {% endfor %}
 </p>
@@ -52,7 +53,7 @@
 <p>
 {% trans "By qualifier" %}:
 {% if qualifier %}
-    <a href='?'>{% trans "all" %}</a>
+    <a href='?{% set_get "page" "qual" %}'>{% trans "all" %}</a>
 {% else %}
     <strong>{% trans "all" %}</strong>
 {% endif %}
@@ -62,7 +63,7 @@
     {% if qual == qualifier %}
         <strong>{{ qual }}</strong>
     {% else %}
-        <a href='?qual={{ qual|urlencode }}'>{{ qual }}</a>
+        <a href='?{% set_get "page" qual=qual.qualifier %}'>{{ qual }}</a>
     {% endif %}
 {% endfor %}
 </p>
@@ -70,7 +71,7 @@
 <p>
 {% trans "By language" %}:
 {% if language %}
-    <a href='?'>{% trans "all" %}</a>
+    <a href='?{% set_get "page" "lang" %}'>{% trans "all" %}</a>
 {% else %}
     <strong>{% trans "all" %}</strong>
 {% endif %}
@@ -80,7 +81,7 @@
     {% if lang == language %}
         <strong>{{ lang }}</strong>
     {% else %}
-        <a href='?lang={{ lang }}'>{{ lang }}</a>
+        <a href='?{% set_get "page" lang=lang %}'>{{ lang }}</a>
     {% endif %}
 {% endfor %}
 </p>
diff --git a/apps/dictionary/templatetags/__init__.py b/apps/dictionary/templatetags/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/apps/dictionary/templatetags/set_get.py b/apps/dictionary/templatetags/set_get.py
new file mode 100644
index 000000000..9afe3e87f
--- /dev/null
+++ b/apps/dictionary/templatetags/set_get.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from django import template
+from django.utils.http import urlencode
+
+register = template.Library()
+
+
+@register.simple_tag(takes_context=True)
+def set_get(context, *omit, **kwargs):
+    request = context['request']
+    query = request.GET.dict()
+    for k in omit:
+        if k in query:
+            del query[k]
+    for k, v in kwargs.items():
+        query[k] = v
+    return urlencode(query)
diff --git a/apps/dictionary/tests.py b/apps/dictionary/tests.py
index 526819a01..0c83cf183 100755
--- a/apps/dictionary/tests.py
+++ b/apps/dictionary/tests.py
@@ -24,7 +24,7 @@ class DictionaryTests(WLTestCase):
         <opowiadanie>
             <akap><pe><slowo_obce>rose</slowo_obce> --- kind of a flower.</pe></akap>
             <akap><pe><slowo_obce>rose</slowo_obce> --- kind of a flower.</pe></akap>
-            <akap><pe><slowo_obce>rose</slowo_obce> (color) --- #FF007F.</pe></akap>
+            <akap><pe><slowo_obce>rose</slowo_obce> (techn.) --- #FF007F.</pe></akap>
         </opowiadanie></utwor>
         """
 
@@ -33,20 +33,20 @@ class DictionaryTests(WLTestCase):
         self.assertEqual(
             len(self.client.get('/przypisy/').context['object_list']),
             2,
-            'There should be a note on the note list.')
+            'There should be two notes on the note list.')
 
         self.assertEqual(
-            len(self.client.get('/przypisy/?ltr=r').context['object_list']),
+            len(self.client.get('/przypisy/?ltr=a').context['object_list']),
             0,
             'There should not be a note for the letter A.')
 
         self.assertEqual(
             len(self.client.get('/przypisy/?ltr=r').context['object_list']),
             2,
-            'There should be a note for the letter R.')
+            'Both notes start with the letter R.')
 
         self.assertEqual(
-            len(self.client.get('/przypisy/?qual=color').context['object_list']),
-            2,
-            'There should be a note for the letter R.')
+            len(self.client.get('/przypisy/?qual=techn.').context['object_list']),
+            1,
+            'There should be a note qualified with \'techn.\' qualifier.')
 
diff --git a/apps/dictionary/views.py b/apps/dictionary/views.py
index fa9712dda..42aab6c36 100755
--- a/apps/dictionary/views.py
+++ b/apps/dictionary/views.py
@@ -2,48 +2,65 @@
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
-from dictionary.models import Note
+from dictionary.models import Note, Qualifier
 from django.views.generic.list import ListView
-from django.db.models import Count
+from django.db.models import Count, Q
 
 
 class NotesView(ListView):
     def get_queryset(self):
-        self.letters = ["0-9"] + [chr(a) for a in range(ord('a'), ord('z')+1)]
-        self.letter = self.request.GET.get('ltr')
+        objects = Note.objects.select_related('book').all()
+        filters = {}
 
-        self.qualifiers = Note.objects.order_by('qualifier').filter(qualifier__startswith='f').values_list(
-            'qualifier', flat=True).distinct()
-        self.qualifier = self.request.GET.get('qual')
+        try:
+            self.qualifier = Qualifier.objects.get(qualifier=self.request.GET.get('qual'))
+        except Qualifier.DoesNotExist:
+            self.qualifier = None
+        else:
+            filters['qualifier'] = Q(qualifiers=self.qualifier)
 
-        self.languages = Note.objects.order_by('language').values_list(
-            'language', flat=True).distinct()
         self.language = self.request.GET.get('lang')
+        if self.language:
+            filters['language'] = Q(language=self.language)
 
-        self.fn_types = Note.objects.order_by('fn_type').values_list(
-            'fn_type', flat=True).distinct()
         self.fn_type = self.request.GET.get('type')
+        if self.fn_type:
+            filters['fn_type'] = Q(fn_type=self.fn_type)
 
-        objects = Note.objects.select_related('book').all()
-
+        self.letter = self.request.GET.get('ltr')
         if self.letter == "0-9":
             objects = objects.filter(sort_key__regex=r"^[0-9]")
+            #filters['letter'] = Q(sort_key__regex=r"^[0-9]")
         elif self.letter:
             objects = objects.filter(sort_key__startswith=self.letter)
+            #filters['letter'] = Q(sort_key__startswith=self.letter)
 
-        if self.qualifier:
-            objects = objects.filter(qualifier=self.qualifier)
+        self.letters = ["0-9"] + [chr(a) for a in range(ord('a'), ord('z')+1)]
 
-        if self.language:
-            objects = objects.filter(language=self.language)
+        nobj = objects
+        for key, fltr in filters.items():
+            if key != 'qualifier':
+                nobj = nobj.filter(fltr)
+        self.qualifiers = Qualifier.objects.filter(note__in=nobj).distinct()
 
-        if self.fn_type:
-            objects = objects.filter(fn_type=self.fn_type)
+        nobj = objects
+        for key, fltr in filters.items():
+            if key != 'language':
+                nobj = nobj.filter(fltr)
+        self.languages = nobj.order_by('language').values_list(
+            'language', flat=True).distinct()
 
-        return objects
+        nobj = objects
+        for key, fltr in filters.items():
+            if key != 'fn_type':
+                nobj = nobj.filter(fltr)
+        self.fn_types = nobj.order_by('fn_type').values_list(
+            'fn_type', flat=True).distinct()
 
-        # TODO: wewn. wyszukiwarka, czy wg definiendum?
-        # TODO: filtr języka
+        for f in filters.values():
+            objects = objects.filter(f)
+
+        return objects
 
     def get_context_data(self, **kwargs):
         context = super(NotesView, self).get_context_data(**kwargs)
diff --git a/lib/librarian b/lib/librarian
index a04f11bae..a3be47950 160000
--- a/lib/librarian
+++ b/lib/librarian
@@ -1 +1 @@
-Subproject commit a04f11baee3eb7d090867c2d5639a120ec3217b8
+Subproject commit a3be479506edf42dc58feb22b26e4f5da1e49edd
-- 
2.20.1