References
authorRadek Czajka <rczajka@rczajka.pl>
Sun, 8 Nov 2020 23:53:59 +0000 (00:53 +0100)
committerRadek Czajka <rczajka@rczajka.pl>
Sun, 8 Nov 2020 23:53:59 +0000 (00:53 +0100)
21 files changed:
requirements/requirements.txt
src/catalogue/locale/pl/LC_MESSAGES/django.mo
src/catalogue/locale/pl/LC_MESSAGES/django.po
src/catalogue/models/book.py
src/catalogue/tasks.py
src/catalogue/templates/catalogue/book_text.html
src/catalogue/views.py
src/references/__init__.py [new file with mode: 0644]
src/references/admin.py [new file with mode: 0644]
src/references/apps.py [new file with mode: 0644]
src/references/migrations/0001_initial.py [new file with mode: 0644]
src/references/migrations/__init__.py [new file with mode: 0644]
src/references/models.py [new file with mode: 0644]
src/references/tests.py [new file with mode: 0644]
src/references/views.py [new file with mode: 0644]
src/wolnelektury/settings/apps.py
src/wolnelektury/settings/static.py
src/wolnelektury/static/js/book_text/references.js [new file with mode: 0644]
src/wolnelektury/static/scss/book_text.scss
src/wolnelektury/static/scss/book_text/references.scss [new file with mode: 0644]
src/wolnelektury/static/scss/book_text/settings.scss

index 8770c1e..f92bb3b 100644 (file)
@@ -48,7 +48,7 @@ mutagen>=1.31
 sorl-thumbnail==12.5.0
 
 # home-brewed & dependencies
-librarian==1.8.3
+librarian==1.10
 
 # celery tasks
 celery[redis]==4.4.7
index 8479571..6012b35 100644 (file)
Binary files a/src/catalogue/locale/pl/LC_MESSAGES/django.mo and b/src/catalogue/locale/pl/LC_MESSAGES/django.mo differ
index 5777659..a5ddcbc 100644 (file)
@@ -5,7 +5,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: WolneLektury\n"
 "Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: 2020-10-16 09:51+0200\n"
+"PO-Revision-Date: 2020-11-09 00:53+0100\n"
 "Last-Translator: Radek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>\n"
 "Language-Team: Fundacja Nowoczesna Polska <fundacja@nowoczesnapolska.org."
 "pl>\n"
@@ -253,11 +253,11 @@ msgstr "Utwór \"%s\" nie istnieje."
 msgid "Book %s already exists"
 msgstr "Książka %s już istnieje"
 
-#: catalogue/models/book.py:813
+#: catalogue/models/book.py:853
 msgid "This work needs modernisation"
 msgstr "Ten utwór wymaga uwspółcześnienia"
 
-#: catalogue/models/book.py:892 catalogue/models/bookmedia.py:27
+#: catalogue/models/book.py:932 catalogue/models/bookmedia.py:27
 #, python-format
 msgid "%s file"
 msgstr "plik %s"
@@ -372,7 +372,7 @@ msgid "tags"
 msgstr "tagi"
 
 #: catalogue/templates/catalogue/book_detail.html:24
-#: catalogue/templates/catalogue/book_text.html:25
+#: catalogue/templates/catalogue/book_text.html:34
 msgid "Other versions"
 msgstr "Inne wersje"
 
@@ -381,8 +381,8 @@ msgid "See also"
 msgstr "Zobacz też"
 
 #: catalogue/templates/catalogue/book_detail.html:41
-#: catalogue/templates/catalogue/book_text.html:38
-#: catalogue/templates/catalogue/book_text.html:57
+#: catalogue/templates/catalogue/book_text.html:47
+#: catalogue/templates/catalogue/book_text.html:66
 #: catalogue/templates/catalogue/tagged_object_list.html:20
 msgid "Themes"
 msgstr "Motywy"
@@ -553,40 +553,44 @@ msgstr "mniej"
 msgid "For now this work is only available for our subscribers."
 msgstr "Jak na razie ten utwór jest dostępny wyłącznie dla naszych Przyjaciół."
 
-#: catalogue/templates/catalogue/book_text.html:32
+#: catalogue/templates/catalogue/book_text.html:41
 msgid "Table of contents"
 msgstr "Spis treści"
 
-#: catalogue/templates/catalogue/book_text.html:44
+#: catalogue/templates/catalogue/book_text.html:53
 msgid "Edit. note"
 msgstr "Nota red."
 
-#: catalogue/templates/catalogue/book_text.html:50
+#: catalogue/templates/catalogue/book_text.html:59
 msgid "Infobox"
 msgstr "Informacje"
 
-#: catalogue/templates/catalogue/book_text.html:55
+#: catalogue/templates/catalogue/book_text.html:64
 msgid "Numbering"
 msgstr "Numeracja"
 
-#: catalogue/templates/catalogue/book_text.html:59
+#: catalogue/templates/catalogue/book_text.html:68
 msgid "Footnotes"
 msgstr "Przypisy"
 
-#: catalogue/templates/catalogue/book_text.html:82
+#: catalogue/templates/catalogue/book_text.html:70
+msgid "References"
+msgstr "Odniesienia"
+
+#: catalogue/templates/catalogue/book_text.html:93
 #: catalogue/templates/catalogue/viewer_base.html:54
 msgid "Close"
 msgstr "Zamknij"
 
-#: catalogue/templates/catalogue/book_text.html:83
+#: catalogue/templates/catalogue/book_text.html:94
 msgid "Please wait..."
 msgstr "Proszę czekać…"
 
-#: catalogue/templates/catalogue/book_text.html:127
+#: catalogue/templates/catalogue/book_text.html:149
 msgid "Other versions of the book"
 msgstr "Inne wersje utworu"
 
-#: catalogue/templates/catalogue/book_text.html:128
+#: catalogue/templates/catalogue/book_text.html:150
 msgid "Close the other version"
 msgstr "Zamknij drugą wersję"
 
index 8b5ac63..5a0f169 100644 (file)
@@ -634,9 +634,49 @@ class Book(models.Model):
             child.parent_cover_changed()
 
         book.update_popularity()
+        tasks.update_references.delay(book.id)
+
         cls.published.send(sender=cls, instance=book)
         return book
 
+    def get_master(self):
+        master_tags = [
+            'opowiadanie',
+            'powiesc',
+            'dramat_wierszowany_l',
+            'dramat_wierszowany_lp',
+            'dramat_wspolczesny', 'liryka_l', 'liryka_lp',
+            'wywiad',
+        ]
+        from librarian.parser import WLDocument
+        wld = WLDocument.from_file(self.xml_file.path, parse_dublincore=False)
+        root = wld.edoc.getroot()
+        for master in root.iter():
+            if master.tag in master_tags:
+                return master
+    
+    def update_references(self):
+        from references.models import Entity, Reference
+        master = self.get_master()
+        found = set()
+        for i, sec in enumerate(master):
+            for ref in sec.findall('.//ref'):
+                href = ref.attrib.get('href', '')
+                if not href or href in found:
+                    continue
+                found.add(href)
+                entity, created = Entity.objects.get_or_create(
+                    uri=href
+                )
+                ref, created = Reference.objects.get_or_create(
+                    book=self,
+                    entity=entity
+                )
+                ref.first_section = 'sec%d' % (i + 1)
+                entity.populate()
+                entity.save()
+        Reference.objects.filter(book=self).exclude(entity__uri__in=found).delete()
+    
     @classmethod
     @transaction.atomic
     def repopulate_ancestors(cls):
index 781dd6e..499e8e6 100644 (file)
@@ -65,3 +65,10 @@ def build_custom_pdf(book_id, customizations, file_name, waiter_id=None):
 def update_counters():
     from .helpers import update_counters
     update_counters()
+
+
+@task(ignore_result=True)
+def update_references(book_id):
+    from catalogue.models import Book
+    Book.objects.get(id=book_id).update_references()
+
index 7d32a22..a771c17 100644 (file)
@@ -1,5 +1,5 @@
 {% extends "catalogue/viewer_base.html" %}
-{% load i18n %}
+{% load i18n l10n %}
 {% load catalogue_tags %}
 {% load chunks %}
 {% load thumbnail %}
@@ -9,6 +9,15 @@
 {% block title %}{{ book.pretty_title }}{% endblock %}
 
 
+{% block extrahead %}
+   <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
+   integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
+         crossorigin=""/>
+    <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"
+   integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
+   crossorigin=""></script>
+{% endblock %}
+
 {% block menu %}
   <li>
     <a href="{{ book.get_absolute_url }}" id="menu-book" data-box="book-short">
      data-setting="always-hide-line-numbers"><span>{% trans "Numbering" %}</span></a>
   <a href="#" class="settings-switch" id="settings-themes"
      data-setting="always-hide-themes"><span>{% trans "Themes" %}</span></a>
-    <a href="#" class="settings-switch" id="settings-annotations"
-       data-setting="no-annotations"><span>{% trans "Footnotes" %}</span></a>
+  <a href="#" class="settings-switch" id="settings-annotations"
+     data-setting="no-annotations"><span>{% trans "Footnotes" %}</span></a>
+  <a href="#" class="settings-switch" id="settings-references"
+     data-setting="no-references"><span>{% trans "References" %}</span></a>
 
 {% endblock menu %}
 
     <div id="other-text-waiter">{% trans "Please wait..." %}</div>
     <div id="other-text-body" style="display: none;"></div>
   </article>
+
+  <div id="reference-box">
+    <div id="reference-map"></div>
+    <a id="reference-close" href="#">x</a>
+    <div id="reference-images">
+    </div>
+    <a id="reference-link" target="_blank"></a>
+  </div>
+
+
+
 {% endblock big-pane %}
 
 {% block footer %}
       {% include 'annoy/dynamic_insert.html' %}
     {% endfor %}
   </div>
+
+  {% localize off %}
+  <script type="application/json" id="interesting-references">
+   {
+       {% for ref in book.reference_set.all %}
+       {% if ref.entity.is_interesting %}
+       "{{ ref.entity.uri }}": {
+           {% if ref.entity.lat and ref.entity.lon %}
+           "location": [{{ ref.entity.lat }}, {{ ref.entity.lon }}],
+           {% endif %}
+           "images": {{ ref.entity.images|safe }},
+           "label": "{{ ref.entity.label }}",
+           "description": "{{ ref.entity.description }}",
+           "wikipedia_link": "{{ ref.entity.wikipedia_link }}"
+       }{% if not forloop.last %},{% endif %}
+       {% endif %}
+       {% endfor %}
+  }
+  </script>
+  {% endlocalize %}
 {% endblock footer %}
index aeed403..bca75e7 100644 (file)
@@ -351,7 +351,7 @@ def import_book(request):
                 _("An error occurred: %(exception)s\n\n%(tb)s") % {
                     'exception': exception, 'tb': tb
                 },
-                mimetype='text/plain'
+                content_type='text/plain'
             )
         return HttpResponse(_("Book imported successfully"))
     return HttpResponse(_("Error importing file: %r") % book_import_form.errors)
diff --git a/src/references/__init__.py b/src/references/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/references/admin.py b/src/references/admin.py
new file mode 100644 (file)
index 0000000..191d0e6
--- /dev/null
@@ -0,0 +1,5 @@
+from django.contrib import admin
+from . import models
+
+
+admin.site.register(models.Entity)
diff --git a/src/references/apps.py b/src/references/apps.py
new file mode 100644 (file)
index 0000000..c7121da
--- /dev/null
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class ReferencesConfig(AppConfig):
+    name = 'references'
diff --git a/src/references/migrations/0001_initial.py b/src/references/migrations/0001_initial.py
new file mode 100644 (file)
index 0000000..62b9125
--- /dev/null
@@ -0,0 +1,41 @@
+# Generated by Django 2.2.16 on 2020-11-08 15:38
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        ('catalogue', '0030_collection_role'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Entity',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('uri', models.CharField(db_index=True, max_length=255, unique=True)),
+                ('label', models.CharField(blank=True, max_length=1024)),
+                ('description', models.CharField(blank=True, max_length=2048)),
+                ('wikipedia_link', models.CharField(blank=True, max_length=1024)),
+                ('lat', models.FloatField(blank=True, null=True)),
+                ('lon', models.FloatField(blank=True, null=True)),
+                ('images', models.TextField(blank=True)),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Reference',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('first_section', models.CharField(max_length=255)),
+                ('book', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='catalogue.Book')),
+                ('entity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='references.Entity')),
+            ],
+            options={
+                'unique_together': {('book', 'entity')},
+            },
+        ),
+    ]
diff --git a/src/references/migrations/__init__.py b/src/references/migrations/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/references/models.py b/src/references/models.py
new file mode 100644 (file)
index 0000000..70493d3
--- /dev/null
@@ -0,0 +1,62 @@
+import json
+from django.db import models
+from wikidata.client import Client
+
+
+class Entity(models.Model):
+    WIKIDATA_PREFIX = 'https://www.wikidata.org/wiki/'
+    WIKIDATA_IMAGE = 'P18'
+    WIKIDATA_COORDINATE_LOCATION = 'P625'
+    WIKIDATA_EARTH = 'Q2'
+
+    uri = models.CharField(max_length=255, unique=True, db_index=True)
+    label = models.CharField(max_length=1024, blank=True)
+    description = models.CharField(max_length=2048, blank=True)
+    wikipedia_link = models.CharField(max_length=1024, blank=True)
+    lat = models.FloatField(null=True, blank=True)
+    lon = models.FloatField(null=True, blank=True)
+    images = models.TextField(blank=True)
+
+    @property
+    def is_interesting(self):
+        return self.wikipedia_link or (self.lat and self.lon) or len(self.images)>2
+    
+    def populate(self):
+        if self.uri.startswith(self.WIKIDATA_PREFIX):
+            self.populate_from_wikidata(
+                self.uri[len(self.WIKIDATA_PREFIX):]
+            )
+
+    def populate_from_wikidata(self, wikidata_id):
+        client = Client()
+        entity = client.get(wikidata_id)
+
+        self.label = entity.label.get('pl', entity.label) or ''
+        self.description = entity.description.get('pl', entity.description) or ''
+        sitelinks = entity.attributes.get('sitelinks', {})
+        self.wikipedia_link = sitelinks.get('plwiki', sitelinks.get('enwiki', {})).get('url') or ''
+
+        location_prop = client.get(self.WIKIDATA_COORDINATE_LOCATION)
+        location = entity.get(location_prop)
+        if location and location.globe.id == self.WIKIDATA_EARTH:
+            self.lat = location.latitude
+            self.lon = location.longitude
+
+        images = entity.getlist(client.get(self.WIKIDATA_IMAGE))
+        self.images = json.dumps([
+            {
+                "url": image.image_url,
+                "page": image.page_url,
+                "resolution": image.image_resolution,
+            } for image in images
+        ])
+
+
+class Reference(models.Model):
+    book = models.ForeignKey('catalogue.Book', models.CASCADE)
+    entity = models.ForeignKey(Entity, models.CASCADE)
+    first_section = models.CharField(max_length=255)
+
+    class Meta:
+        unique_together = (('book', 'entity'),)
+
diff --git a/src/references/tests.py b/src/references/tests.py
new file mode 100644 (file)
index 0000000..7ce503c
--- /dev/null
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/src/references/views.py b/src/references/views.py
new file mode 100644 (file)
index 0000000..91ea44a
--- /dev/null
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.
index 413504d..1d77372 100644 (file)
@@ -17,6 +17,7 @@ INSTALLED_APPS_OUR = [
     'newtagging',
     'opds',
     'pdcounter',
+    'references',
     'reporting',
     'sponsors',
     'stats',
index 2873410..1f42d55 100644 (file)
@@ -156,6 +156,7 @@ PIPELINE = {
                 'js/book_text/info.js',
                 'js/book_text/menu.js',
                 'js/book_text/note.js',
+                'js/book_text/references.js',
                 'js/book_text/settings.js',
                 'js/book_text/toc.js',
                 'js/locale.js',
diff --git a/src/wolnelektury/static/js/book_text/references.js b/src/wolnelektury/static/js/book_text/references.js
new file mode 100644 (file)
index 0000000..dcbb3c8
--- /dev/null
@@ -0,0 +1,90 @@
+(function($){$(function(){
+
+    var interestingReferences = $("#interesting-references").text();
+    if (interestingReferences) {
+        interestingReferences = $.parseJSON(interestingReferences);
+    }
+    if (interestingReferences) {
+        $("settings-references").show();
+    }
+
+    
+    
+    var map_enabled = false;
+    var marker = L.marker([0,0]);
+    var map = null;
+
+    function enable_map() {
+        if (map_enabled) return;
+
+        map = L.map('reference-map').setView([0, 0], 11);
+        L.tileLayer('https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png?lang=pl', {
+            attribution: 'Map data &copy; <a href="http://openstreetmap.org/copyright">OpenStreetMap contributors</a>'
+        }).addTo(map);
+
+        map_enabled = true;
+    }
+    function disable_map() {
+        $("#reference-map").hide('slow');
+    }
+    
+
+    $("#reference-close").on("click", function() {
+        $("#reference-box").hide();
+    });
+    
+    $('a.reference').each(function() {
+        $this = $(this);
+        uri = $this.attr('data-uri');
+        console.log('check ' + uri);
+        if (interestingReferences.hasOwnProperty(uri)) {
+            $this.addClass('interesting');
+            ref = interestingReferences[uri];
+
+            $this.attr('href', ref.wikipedia_link);
+            $this.attr('target', '_blank');
+        }
+    });
+
+
+    $('a.reference.interesting').on('click', function(event) {
+        event.preventDefault();
+
+        $("#reference-box").show();
+
+        $this = $(this);
+        uri = $this.attr('data-uri');
+        ref = interestingReferences[uri];
+
+        if (ref.location) {
+            enable_map();
+
+            marker.setLatLng(ref.location);
+            //marker.setContent(ref.label);
+            marker.bindTooltip(ref.label).openTooltip();
+            map.addLayer(marker);
+            map.panTo(ref.location, {
+                animate: true,
+                duration: 1,
+            });
+        } else {
+            disable_map();
+            if (map) {
+                map.removeLayer(marker);
+            }
+        }
+
+        $("#reference-images img").remove();
+        if (ref.images) {
+            $.each(ref.images, function(i, e) {
+                $i = $("<a target='_blank'><img></a>");
+                $i.attr('href', e.page);
+                $('img', $i).attr('src', e.url);
+                $("#reference-images").append($i);
+            })
+        }
+
+        $("#reference-link").text(ref.label);
+        $("#reference-link").attr('href', ref.wikipedia_link);
+    });
+})})(jQuery);
index 566ee48..535ae96 100644 (file)
@@ -8,6 +8,7 @@
 @import "book_text/note";
 @import "book_text/numbering";
 @import "book_text/other";
+@import "book_text/references";
 @import "book_text/settings";
 @import "book_text/themes";
 @import "book_text/toc";
diff --git a/src/wolnelektury/static/scss/book_text/references.scss b/src/wolnelektury/static/scss/book_text/references.scss
new file mode 100644 (file)
index 0000000..0b5ebea
--- /dev/null
@@ -0,0 +1,49 @@
+a.reference.interesting::after {
+    content: "📌";
+}
+.no-references .reference.interesting:after {
+    display: none;
+}
+
+#reference-box {
+    display: none;
+    width: 300px;
+    position: fixed;
+    top: 0;
+    right: 0;
+    z-index:100;
+    background: #eee;
+    #reference-map {
+        height:400px;
+        width: 300px;
+    }
+    #reference-images {
+        padding: 10px;
+        white-space: nowrap;
+        overflow-x: auto;
+        a {
+            display: inline-block;
+            vertical-align: middle;
+            margin: 0 10px 0 0;
+            img {
+                margin: 0;
+                height: 100px;
+            }
+        }
+    }
+    #reference-link {
+        display: block;
+        font-size: 1.5em;
+        padding: 10px;
+    }
+
+    #reference-close {
+        font-size: 30px;
+        position: absolute;
+        top: 10px;
+        right: 10px;
+        z-index: 1000;
+        font-family: sans-serif;
+        color: black;
+    }
+}
index cd6997c..66bc7e4 100644 (file)
@@ -45,4 +45,8 @@
     #settings-line-numbers span {@include switch-off;}
 }
 
+#settings-references span {@include switch-on;}
+.no-references {
+    #settings-references span {@include switch-off;}
+}