Some dictionary filtering features.
authorRadek Czajka <radekczajka@nowoczesnapolska.org.pl>
Thu, 16 Oct 2014 11:45:51 +0000 (13:45 +0200)
committerRadek Czajka <radekczajka@nowoczesnapolska.org.pl>
Wed, 22 Oct 2014 08:57:01 +0000 (10:57 +0200)
apps/catalogue/fields.py
apps/catalogue/tasks.py
apps/dictionary/migrations/0002_auto_20141006_1422.py [new file with mode: 0644]
apps/dictionary/models.py
apps/dictionary/templates/dictionary/note_list.html
apps/dictionary/tests.py
apps/dictionary/urls.py
apps/dictionary/views.py
apps/wolnelektury_core/static/scss/dictionary/dictionary.scss [new file with mode: 0644]
apps/wolnelektury_core/static/scss/main.scss
lib/librarian

index 590f265..0ff2ca9 100644 (file)
@@ -10,8 +10,11 @@ from catalogue import app_settings
 from catalogue.constants import LANGUAGES_3TO2
 from catalogue.utils import remove_zip, truncate_html_words
 from celery.task import Task, task
+from celery.utils.log import get_task_logger
 from waiter.utils import clear_cache
 
+task_logger = get_task_logger(__name__)
+
 
 class EbookFieldFile(FieldFile):
     """Represents contents of an ebook file field."""
@@ -82,6 +85,7 @@ class BuildEbook(Task):
 
     def run(self, obj, field_name):
         """Just run `build` on FieldFile, can't pass it directly to Celery."""
+        task_logger.info("%s -> %s" % (obj.slug, field_name))
         ret = self.build(getattr(obj, field_name))
         obj.flush_includes()
         return ret
index e392ae2..a2b8be0 100644 (file)
@@ -5,10 +5,13 @@
 from datetime import datetime
 from traceback import print_exc
 from celery.task import task
+from celery.utils.log import get_task_logger
 from django.conf import settings
 from wolnelektury.utils import localtime_to_utc
 from waiter.models import WaitedFile
 
+task_logger = get_task_logger(__name__)
+
 
 # TODO: move to model?
 def touch_tag(tag):
@@ -38,7 +41,7 @@ def build_custom_pdf(book_id, customizations, file_name, waiter_id=None):
         from django.core.files.storage import DefaultStorage
         from catalogue.models import Book
 
-        print "will gen %s" % DefaultStorage().path(file_name)
+        task_logger.info(DefaultStorage().path(file_name))
         if not DefaultStorage().exists(file_name):
             kwargs = {
                 'cover': True,
diff --git a/apps/dictionary/migrations/0002_auto_20141006_1422.py b/apps/dictionary/migrations/0002_auto_20141006_1422.py
new file mode 100644 (file)
index 0000000..a9bad32
--- /dev/null
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('catalogue', '0004_remove_booktags_count_related_info'),
+        ('dictionary', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='NoteSource',
+            fields=[
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+                ('anchor', models.CharField(max_length=64)),
+                ('book', models.ForeignKey(to='catalogue.Book')),
+                ('note', models.ForeignKey(to='dictionary.Note')),
+            ],
+            options={
+                'ordering': ['book'],
+            },
+            bases=(models.Model,),
+        ),
+        migrations.RemoveField(
+            model_name='note',
+            name='anchor',
+        ),
+        migrations.RemoveField(
+            model_name='note',
+            name='book',
+        ),
+        migrations.AddField(
+            model_name='note',
+            name='fn_type',
+            field=models.CharField(default='', max_length=10, db_index=True),
+            preserve_default=False,
+        ),
+        migrations.AddField(
+            model_name='note',
+            name='language',
+            field=models.CharField(default='', max_length=10, db_index=True),
+            preserve_default=False,
+        ),
+        migrations.AddField(
+            model_name='note',
+            name='qualifier',
+            field=models.CharField(default='', max_length=128, db_index=True, blank=True),
+            preserve_default=False,
+        ),
+    ]
index 2256acf..73fbb0d 100644 (file)
@@ -2,33 +2,68 @@
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
-from django.db import models
+from django.db import models, transaction
 from celery.task import task
 from sortify import sortify
+from celery.utils.log import get_task_logger
+
+task_logger = get_task_logger(__name__)
 
 from catalogue.models import Book
 
 
 class Note(models.Model):
     """Represents a single annotation from a book."""
-    book = models.ForeignKey(Book)
-    anchor = models.CharField(max_length=64)
     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)
+    language = models.CharField(max_length=10, db_index=True)
 
     class Meta:
         ordering = ['sort_key']
 
 
+class NoteSource(models.Model):
+    """Represents a single annotation from a book."""
+    note = models.ForeignKey(Note)
+    book = models.ForeignKey(Book)
+    anchor = models.CharField(max_length=64)
+
+    class Meta:
+        ordering = ['book']
+
+
 @task(ignore_result=True)
 def build_notes(book):
-    Note.objects.filter(book=book).delete()
-    if book.html_file:
-        from librarian import html
-        for anchor, text_str, html_str in html.extract_annotations(book.html_file.path):
-            Note.objects.create(book=book, anchor=anchor,
-                               html=html_str,
-                               sort_key=sortify(text_str).strip()[:128])
+    task_logger.info(book.slug)
+    with transaction.atomic():
+        book.notesource_set.all().delete()
+        if book.html_file:
+            from librarian import html
+            for anchor, fn_type, qualifier, 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,
+                    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
+                        )
+                note.notesource_set.create(book=book, anchor=anchor)
+
+        Note.objects.filter(notesource=None).delete()
+
 
 def notes_from_book(sender, instance, **kwargs):
     build_notes.delay(instance)
index 5bf9e3f..5109454 100755 (executable)
@@ -15,7 +15,7 @@
 <p>
 {% trans "By first letter" %}:
 {% if letter %}
-    <a href='{% url "dictionary_notes" %}'>{% trans "all" %}</a>
+    <a href='?'>{% trans "all" %}</a>
 {% else %}
     <strong>{% trans "all" %}</strong>
 {% endif %}
     {% if let == letter %}
         <strong>{{ let|upper }}</strong>
     {% else %}
-        <a href='{% url "dictionary_notes" let %}'>{{ let|upper }}</a>
+        <a href='?ltr={{ let }}'>{{ let|upper }}</a>
     {% endif %}
 {% endfor %}
 </p>
+
+<p>
+{% trans "By type" %}:
+{% if fn_type %}
+    <a href='?'>{% trans "all" %}</a>
+{% else %}
+    <strong>{% trans "all" %}</strong>
+{% endif %}
+
+{% for fnt in fn_types %}
+    |
+    {% if fnt == fn_type %}
+        <strong>{{ fnt }}</strong>
+    {% else %}
+        <a href='?type={{ fnt }}'>{{ fnt }}</a>
+    {% endif %}
+{% endfor %}
+</p>
+
+
+<p>
+{% trans "By qualifier" %}:
+{% if qualifier %}
+    <a href='?'>{% trans "all" %}</a>
+{% else %}
+    <strong>{% trans "all" %}</strong>
+{% endif %}
+
+{% for qual in qualifiers %}
+    |
+    {% if qual == qualifier %}
+        <strong>{{ qual }}</strong>
+    {% else %}
+        <a href='?qual={{ qual|urlencode }}'>{{ qual }}</a>
+    {% endif %}
+{% endfor %}
+</p>
+
+<p>
+{% trans "By language" %}:
+{% if language %}
+    <a href='?'>{% trans "all" %}</a>
+{% else %}
+    <strong>{% trans "all" %}</strong>
+{% endif %}
+
+{% for lang in languages %}
+    |
+    {% if lang == language %}
+        <strong>{{ lang }}</strong>
+    {% else %}
+        <a href='?lang={{ lang }}'>{{ lang }}</a>
+    {% endif %}
+{% endfor %}
+</p>
+
 <hr/>
 
 <p>
 {% paginate %}
 {% for obj in object_list %}
     <div class='dictionary-note'>
-    {{ obj.html|safe }}
-    <div class='dictionary-note-source'>
-    (<a href='{% url "book_text" obj.book.slug %}#{{ obj.anchor }}'>{{ obj.book.pretty_title }}</a>)
-    </div>
+        {{ obj.html|safe }}
+        {% for note_source in obj.notesource_set.all %}
+            <div class='dictionary-note-source'>
+                <a href='{% url "book_text" note_source.book.slug %}#{{ note_source.anchor }}'>{{ note_source.book.pretty_title }}</a>
+                (<a href='{{ note_source.book.extra_info.about }}'>źródło na Platformie Redakcyjnej</a>)
+            </div>
+        {% endfor %}
     </div>
 {% endfor %}
 {% paginate %}
index e88fe50..526819a 100755 (executable)
@@ -23,6 +23,8 @@ class DictionaryTests(WLTestCase):
         BOOK_TEXT = """<utwor>
         <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>
         </opowiadanie></utwor>
         """
 
@@ -30,16 +32,21 @@ class DictionaryTests(WLTestCase):
 
         self.assertEqual(
             len(self.client.get('/przypisy/').context['object_list']),
-            1,
+            2,
             'There should be a note on the note list.')
 
         self.assertEqual(
-            len(self.client.get('/przypisy/a/').context['object_list']),
+            len(self.client.get('/przypisy/?ltr=r').context['object_list']),
             0,
             'There should not be a note for the letter A.')
 
         self.assertEqual(
-            len(self.client.get('/przypisy/r/').context['object_list']),
-            1,
+            len(self.client.get('/przypisy/?ltr=r').context['object_list']),
+            2,
+            'There should be a note for 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.')
 
index 63e4bbd..2320f47 100755 (executable)
@@ -7,6 +7,5 @@ from dictionary.views import NotesView
 
 urlpatterns = patterns('dictionary.views',
     url(r'^$', NotesView.as_view(), name='dictionary_notes'),
-    url(r'(?P<letter>[a-z]|0-9)/$', NotesView.as_view(), name='dictionary_notes'),
 )
 
index 76c89cd..fa9712d 100755 (executable)
@@ -4,22 +4,55 @@
 #
 from dictionary.models import Note
 from django.views.generic.list import ListView
+from django.db.models import Count
 
 
 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.kwargs.get('letter')
+        self.letter = self.request.GET.get('ltr')
+
+        self.qualifiers = Note.objects.order_by('qualifier').filter(qualifier__startswith='f').values_list(
+            'qualifier', flat=True).distinct()
+        self.qualifier = self.request.GET.get('qual')
+
+        self.languages = Note.objects.order_by('language').values_list(
+            'language', flat=True).distinct()
+        self.language = self.request.GET.get('lang')
+
+        self.fn_types = Note.objects.order_by('fn_type').values_list(
+            'fn_type', flat=True).distinct()
+        self.fn_type = self.request.GET.get('type')
 
         objects = Note.objects.select_related('book').all()
+
         if self.letter == "0-9":
             objects = objects.filter(sort_key__regex=r"^[0-9]")
         elif self.letter:
             objects = objects.filter(sort_key__startswith=self.letter)
+
+        if self.qualifier:
+            objects = objects.filter(qualifier=self.qualifier)
+
+        if self.language:
+            objects = objects.filter(language=self.language)
+
+        if self.fn_type:
+            objects = objects.filter(fn_type=self.fn_type)
+
         return objects
 
+        # TODO: wewn. wyszukiwarka, czy wg definiendum?
+        # TODO: filtr języka
+
     def get_context_data(self, **kwargs):
         context = super(NotesView, self).get_context_data(**kwargs)
-        context['letter'] = self.letter
         context['letters'] = self.letters
+        context['letter'] = self.letter
+        context['qualifiers'] = self.qualifiers
+        context['qualifier'] = self.qualifier
+        context['languages'] = self.languages
+        context['language'] = self.language
+        context['fn_types'] = self.fn_types
+        context['fn_type'] = self.fn_type
         return context
diff --git a/apps/wolnelektury_core/static/scss/dictionary/dictionary.scss b/apps/wolnelektury_core/static/scss/dictionary/dictionary.scss
new file mode 100644 (file)
index 0000000..a900599
--- /dev/null
@@ -0,0 +1,9 @@
+.dictionary-note {
+    @include white-box;
+    @include size(padding, 8px);
+    @include size(margin, 10px 0);
+}
+.dictionary-note-source {
+    @include white-box;
+    @include size(padding, 8px);
+}
index 467e607..bab1fb4 100644 (file)
@@ -20,6 +20,8 @@
 @import "main/search";
 @import "main/tag";
 
+@import "dictionary/dictionary";
+
 @import "funding/funding";
 
 @import "polls/polls";
index a91e41e..a3b6840 160000 (submodule)
@@ -1 +1 @@
-Subproject commit a91e41e489588ebad550cd9e22d157062effa2ff
+Subproject commit a3b6840527ec52ce8b6d74819633d8c85e3973ba