From: Radek Czajka Date: Sun, 8 Nov 2020 23:53:59 +0000 (+0100) Subject: References X-Git-Url: https://git.mdrn.pl/wolnelektury.git/commitdiff_plain/d66f9960d12d739c44a89d1d4e013f52783097e7 References --- diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 8770c1e9c..f92bb3bbb 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -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 diff --git a/src/catalogue/locale/pl/LC_MESSAGES/django.mo b/src/catalogue/locale/pl/LC_MESSAGES/django.mo index 8479571f2..6012b3589 100644 Binary files a/src/catalogue/locale/pl/LC_MESSAGES/django.mo and b/src/catalogue/locale/pl/LC_MESSAGES/django.mo differ diff --git a/src/catalogue/locale/pl/LC_MESSAGES/django.po b/src/catalogue/locale/pl/LC_MESSAGES/django.po index 57776599c..a5ddcbca5 100644 --- a/src/catalogue/locale/pl/LC_MESSAGES/django.po +++ b/src/catalogue/locale/pl/LC_MESSAGES/django.po @@ -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 \n" "Language-Team: Fundacja Nowoczesna Polska \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ę" diff --git a/src/catalogue/models/book.py b/src/catalogue/models/book.py index 8b5ac637f..5a0f1696f 100644 --- a/src/catalogue/models/book.py +++ b/src/catalogue/models/book.py @@ -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): diff --git a/src/catalogue/tasks.py b/src/catalogue/tasks.py index 781dd6e3c..499e8e659 100644 --- a/src/catalogue/tasks.py +++ b/src/catalogue/tasks.py @@ -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() + diff --git a/src/catalogue/templates/catalogue/book_text.html b/src/catalogue/templates/catalogue/book_text.html index 7d32a2280..a771c1778 100644 --- a/src/catalogue/templates/catalogue/book_text.html +++ b/src/catalogue/templates/catalogue/book_text.html @@ -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 %} + + +{% endblock %} + {% block menu %}
  • @@ -55,8 +64,10 @@ data-setting="always-hide-line-numbers">{% trans "Numbering" %} {% trans "Themes" %} - {% trans "Footnotes" %} + {% trans "Footnotes" %} + {% trans "References" %} {% endblock menu %} @@ -83,6 +94,17 @@
    {% trans "Please wait..." %}
    + +
    +
    + x +
    +
    + +
    + + + {% endblock big-pane %} {% block footer %} @@ -153,4 +175,24 @@ {% include 'annoy/dynamic_insert.html' %} {% endfor %} + + {% localize off %} + + {% endlocalize %} {% endblock footer %} diff --git a/src/catalogue/views.py b/src/catalogue/views.py index aeed403ab..bca75e78f 100644 --- a/src/catalogue/views.py +++ b/src/catalogue/views.py @@ -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 index 000000000..e69de29bb diff --git a/src/references/admin.py b/src/references/admin.py new file mode 100644 index 000000000..191d0e689 --- /dev/null +++ b/src/references/admin.py @@ -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 index 000000000..c7121da48 --- /dev/null +++ b/src/references/apps.py @@ -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 index 000000000..62b912558 --- /dev/null +++ b/src/references/migrations/0001_initial.py @@ -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 index 000000000..e69de29bb diff --git a/src/references/models.py b/src/references/models.py new file mode 100644 index 000000000..70493d347 --- /dev/null +++ b/src/references/models.py @@ -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 index 000000000..7ce503c2d --- /dev/null +++ b/src/references/tests.py @@ -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 index 000000000..91ea44a21 --- /dev/null +++ b/src/references/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/src/wolnelektury/settings/apps.py b/src/wolnelektury/settings/apps.py index 413504de5..1d7737282 100644 --- a/src/wolnelektury/settings/apps.py +++ b/src/wolnelektury/settings/apps.py @@ -17,6 +17,7 @@ INSTALLED_APPS_OUR = [ 'newtagging', 'opds', 'pdcounter', + 'references', 'reporting', 'sponsors', 'stats', diff --git a/src/wolnelektury/settings/static.py b/src/wolnelektury/settings/static.py index 28734105a..1f42d55a0 100644 --- a/src/wolnelektury/settings/static.py +++ b/src/wolnelektury/settings/static.py @@ -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 index 000000000..dcbb3c843 --- /dev/null +++ b/src/wolnelektury/static/js/book_text/references.js @@ -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 © OpenStreetMap contributors' + }).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 = $(""); + $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); diff --git a/src/wolnelektury/static/scss/book_text.scss b/src/wolnelektury/static/scss/book_text.scss index 566ee4804..535ae96a1 100644 --- a/src/wolnelektury/static/scss/book_text.scss +++ b/src/wolnelektury/static/scss/book_text.scss @@ -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 index 000000000..0b5ebea57 --- /dev/null +++ b/src/wolnelektury/static/scss/book_text/references.scss @@ -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; + } +} diff --git a/src/wolnelektury/static/scss/book_text/settings.scss b/src/wolnelektury/static/scss/book_text/settings.scss index cd6997c40..66bc7e4b8 100644 --- a/src/wolnelektury/static/scss/book_text/settings.scss +++ b/src/wolnelektury/static/scss/book_text/settings.scss @@ -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;} +}