From 7c63490d050ee070e2dd5d4a971553371f2c334b Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Wed, 25 Mar 2020 09:59:02 +0100 Subject: [PATCH 01/16] Migration dependency fix. --- src/catalogue/migrations/0006_author_book.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/catalogue/migrations/0006_author_book.py b/src/catalogue/migrations/0006_author_book.py index 7cea032d..f7e32dcb 100644 --- a/src/catalogue/migrations/0006_author_book.py +++ b/src/catalogue/migrations/0006_author_book.py @@ -10,6 +10,7 @@ class Migration(migrations.Migration): dependencies = [ ('catalogue', '0005_auto_20200322_2114'), + ('documents', '0002_auto_20200322_2131'), ] operations = [ -- 2.20.1 From 5c0ed2a6cb007fccf2cc4b58199f285707f7e974 Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Wed, 25 Mar 2020 10:21:32 +0100 Subject: [PATCH 02/16] Simple docx import. --- requirements/requirements.txt | 1 + src/documents/docx.py | 86 +++++++++++++++++++++++++++++++++++ src/documents/forms.py | 27 ++++++++++- 3 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 src/documents/docx.py diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 6553dfe9..9b00c430 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -7,6 +7,7 @@ Pillow oauth2 httplib2 # oauth2 dependency python-slugify +python-docx==0.8.10 librarian==1.8.1 diff --git a/src/documents/docx.py b/src/documents/docx.py new file mode 100644 index 00000000..76811a30 --- /dev/null +++ b/src/documents/docx.py @@ -0,0 +1,86 @@ +import sys +import docx +from lxml import etree + + +DEBUG = False + +DC = "{http://purl.org/dc/elements/1.1/}" +RDF = "{http://www.w3.org/1999/02/22-rdf-syntax-ns#}" + +ABOUT = "http://redakcja.wolnelektury.pl/documents/book/test-icm/" +WLURI = "http://wolnelektury.pl/katalog/lektura/test-icm/" + + + +META_STYLES = { + "Author": DC + "creator", + "Title": DC + "title", + "Publisher": DC + "publisher", + "Year": DC + "date", + "Editor": DC + "contributor.editor", + "Copyright holder": DC + "rights", +} + + +P_STYLES = { + "Normal": "akap", + "Autor": "autor_utworu", + "Title": "nazwa_utworu", + "Subtitle": "podtytul", + "Heading 1": "naglowek_czesc", + "Heading 2": "naglowek_rozdzial", + "Heading 3": "naglowek_podrozdzial", + "Heading 4": "srodtytul", + "Heading 5": "srodtytul", + +} + + +def wyroznienie(r): + if r.font.italic is not None or r.font.bold is not None or r.font.underline is not None: return r.font.italic or r.font.bold or r.font.underline + if r.style.font.italic is not None or r.style.font.bold is not None or r.style.font.underline is not None: return r.style.font.italic or r.style.font.bold or r.style.font.underline + return False + + +def xml_from_docx(f): + d = docx.Document(f) + + t = etree.Element("utwor") + rdf = etree.SubElement(t, RDF + "RDF") + meta = etree.SubElement(rdf, RDF + "Description") + meta.attrib[RDF + "about"] = ABOUT + + etree.SubElement(meta, DC + "language").text = "pol" + etree.SubElement(meta, DC + "identifier.url").text = WLURI + + m = etree.SubElement(t, "powiesc") + md = {} + + for p in d.paragraphs: + can_ignore = False + if p.style.name == 'Title': + md['title'] = p.text + if p.style.name in META_STYLES: + item = etree.SubElement(meta, META_STYLES[p.style.name]) + item.text = p.text + can_ignore = True + if p.style.name not in P_STYLES and not can_ignore: + print(p.style.name, file=sys.stderr) + if p.style.name in P_STYLES or not can_ignore: + tag = P_STYLES.get(p.style.name, "akap") + a = etree.SubElement(m, tag) + + for r in p.runs: + if wyroznienie(r): + etree.SubElement(a, "wyroznienie").text = r.text + else: + if len(a): + a[-1].tail = (a[-1].tail or '') + r.text + else: + a.text = (a.text or '') + r.text + + if DEBUG and p.style.name not in P_STYLES: + a.text += f" [{p.style.name}]" + + return etree.tostring(t, pretty_print=True, encoding='unicode'), md diff --git a/src/documents/forms.py b/src/documents/forms.py index f5f2901d..bb064eec 100644 --- a/src/documents/forms.py +++ b/src/documents/forms.py @@ -5,9 +5,10 @@ from django.db.models import Count from django import forms from django.utils.translation import ugettext_lazy as _ from django.conf import settings - +from slugify import slugify from .constants import MASTERS from .models import Book, Chunk, Image, User +from .docx import xml_from_docx class DocumentCreateForm(forms.ModelForm): """ @@ -15,6 +16,7 @@ class DocumentCreateForm(forms.ModelForm): """ file = forms.FileField(required=False) text = forms.CharField(required=False, widget=forms.Textarea) + docx = forms.FileField(required=False) class Meta: model = Book @@ -23,8 +25,10 @@ class DocumentCreateForm(forms.ModelForm): def __init__(self, *args, **kwargs): super(DocumentCreateForm, self).__init__(*args, **kwargs) self.fields['slug'].widget.attrs={'class': 'autoslug'} + self.fields['slug'].required = False self.fields['gallery'].widget.attrs={'class': 'autoslug'} self.fields['title'].widget.attrs={'class': 'autoslug-source'} + self.fields['title'].required = False def clean(self): super(DocumentCreateForm, self).clean() @@ -36,8 +40,27 @@ class DocumentCreateForm(forms.ModelForm): except UnicodeDecodeError: raise forms.ValidationError(_("Text file must be UTF-8 encoded.")) + docx = self.cleaned_data['docx'] + if docx is not None: + try: + text, meta = xml_from_docx(docx) + except Exception as e: + raise forms.ValidationError(e) + else: + self.cleaned_data['text'] = text + if not self.cleaned_data['title']: + self.cleaned_data['title'] = meta.get('title', '') + if not self.cleaned_data['slug']: + self.cleaned_data['slug'] = slugify(meta.get('title', '')) + + if not self.cleaned_data["title"]: + self._errors["title"] = self.error_class([_("Title not set")]) + + if not self.cleaned_data["slug"]: + self._errors["slug"] = self.error_class([_("Slug not set")]) + if not self.cleaned_data["text"]: - self._errors["file"] = self.error_class([_("You must either enter text or upload a file")]) + self._errors["text"] = self.error_class([_("You must either enter text or upload a file")]) return self.cleaned_data -- 2.20.1 From 4bb5959d246fa0528b83fc3e21f9e5782ec34236 Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Sat, 11 Apr 2020 11:14:41 +0200 Subject: [PATCH 03/16] Wikidata in catalogue. --- requirements/requirements.txt | 1 + src/catalogue/admin.py | 16 ++-- src/catalogue/constants.py | 9 ++ .../migrations/0008_auto_20200410_1741.py | 71 +++++++++++++++ .../migrations/0009_auto_20200411_1114.py | 38 ++++++++ src/catalogue/models.py | 73 +++++++++++----- src/catalogue/wikidata.py | 87 +++++++++++++++++++ 7 files changed, 267 insertions(+), 28 deletions(-) create mode 100644 src/catalogue/constants.py create mode 100644 src/catalogue/migrations/0008_auto_20200410_1741.py create mode 100644 src/catalogue/migrations/0009_auto_20200411_1114.py create mode 100644 src/catalogue/wikidata.py diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 9b00c430..313d092d 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -8,6 +8,7 @@ oauth2 httplib2 # oauth2 dependency python-slugify python-docx==0.8.10 +Wikidata==0.6.1 librarian==1.8.1 diff --git a/src/catalogue/admin.py b/src/catalogue/admin.py index 189cd718..bd783b99 100644 --- a/src/catalogue/admin.py +++ b/src/catalogue/admin.py @@ -1,17 +1,21 @@ from django.contrib import admin from . import models +from .wikidata import WikidataAdminMixin +class AuthorAdmin(WikidataAdminMixin, admin.ModelAdmin): + list_display = "first_name", "last_name", "notes" + search_fields = ["first_name", "last_name", "wikidata"] + prepopulated_fields = {"slug": ("first_name", "last_name")} -class AuthorAdmin(admin.ModelAdmin): - search_fields = ['name'] admin.site.register(models.Author, AuthorAdmin) -class BookAdmin(admin.ModelAdmin): - raw_id_fields = ['authors'] - autocomplete_fields = ['translators'] +class BookAdmin(WikidataAdminMixin, admin.ModelAdmin): + list_display = "title", "notes" + autocomplete_fields = ["authors", "translators"] + prepopulated_fields = {"slug": ("title",)} -admin.site.register(models.Book, BookAdmin) +admin.site.register(models.Book, BookAdmin) diff --git a/src/catalogue/constants.py b/src/catalogue/constants.py new file mode 100644 index 00000000..f8180f76 --- /dev/null +++ b/src/catalogue/constants.py @@ -0,0 +1,9 @@ +class WIKIDATA: + AUTHOR = "P50" + LANGUAGE = "P407" + DATE_OF_DEATH = "P570" + LAST_NAME = "P734" + GIVEN_NAME = "P735" + TRANSLATOR = "P655" + BASED_ON = "P629" + TITLE = "P1476" diff --git a/src/catalogue/migrations/0008_auto_20200410_1741.py b/src/catalogue/migrations/0008_auto_20200410_1741.py new file mode 100644 index 00000000..f83652ea --- /dev/null +++ b/src/catalogue/migrations/0008_auto_20200410_1741.py @@ -0,0 +1,71 @@ +# Generated by Django 3.0.4 on 2020-04-10 17:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('catalogue', '0007_auto_20200322_2326'), + ] + + operations = [ + migrations.RenameField( + model_name='author', + old_name='name', + new_name='last_name', + ), + migrations.RemoveField( + model_name='book', + name='translator', + ), + migrations.RemoveField( + model_name='book', + name='uri', + ), + migrations.AddField( + model_name='author', + name='first_name', + field=models.CharField(blank=True, max_length=255), + ), + migrations.AddField( + model_name='author', + name='notes', + field=models.TextField(blank=True), + ), + migrations.AddField( + model_name='author', + name='priority', + field=models.PositiveSmallIntegerField(choices=[(0, 'Low'), (1, 'Medium'), (2, 'High')], default=0), + ), + migrations.AddField( + model_name='author', + name='slug', + field=models.SlugField(blank=True, null=True, unique=True), + ), + migrations.AddField( + model_name='author', + name='wikidata', + field=models.CharField(blank=True, max_length=255), + ), + migrations.AddField( + model_name='book', + name='slug', + field=models.SlugField(blank=True, max_length=255, null=True, unique=True), + ), + migrations.AddField( + model_name='book', + name='translators', + field=models.ManyToManyField(blank=True, related_name='translated_book_set', related_query_name='translated_book', to='catalogue.Author'), + ), + migrations.AddField( + model_name='book', + name='wikidata', + field=models.CharField(blank=True, max_length=255), + ), + migrations.AlterField( + model_name='book', + name='authors', + field=models.ManyToManyField(blank=True, to='catalogue.Author'), + ), + ] diff --git a/src/catalogue/migrations/0009_auto_20200411_1114.py b/src/catalogue/migrations/0009_auto_20200411_1114.py new file mode 100644 index 00000000..f1dc1391 --- /dev/null +++ b/src/catalogue/migrations/0009_auto_20200411_1114.py @@ -0,0 +1,38 @@ +# Generated by Django 3.0.4 on 2020-04-11 11:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('catalogue', '0008_auto_20200410_1741'), + ] + + operations = [ + migrations.AlterField( + model_name='author', + name='last_name', + field=models.CharField(blank=True, max_length=255), + ), + migrations.AlterField( + model_name='author', + name='wikidata', + field=models.CharField(blank=True, help_text='If you have a Wikidata ID, like "Q1337", enter it and save.', max_length=255, null=True, unique=True), + ), + migrations.AlterField( + model_name='book', + name='language', + field=models.CharField(blank=True, max_length=3), + ), + migrations.AlterField( + model_name='book', + name='title', + field=models.CharField(blank=True, max_length=255), + ), + migrations.AlterField( + model_name='book', + name='wikidata', + field=models.CharField(blank=True, help_text='If you have a Wikidata ID, like "Q1337", enter it and save.', max_length=255, null=True, unique=True), + ), + ] diff --git a/src/catalogue/models.py b/src/catalogue/models.py index 4a236b62..c2ed3812 100644 --- a/src/catalogue/models.py +++ b/src/catalogue/models.py @@ -1,38 +1,67 @@ from django.db import models from django.utils.translation import gettext_lazy as _ +from .constants import WIKIDATA +from .wikidata import WikidataMixin -class Author(models.Model): - name = models.CharField(max_length=255) +class Author(WikidataMixin, models.Model): + slug = models.SlugField(null=True, blank=True, unique=True) + first_name = models.CharField(max_length=255, blank=True) + last_name = models.CharField(max_length=255, blank=True) year_of_death = models.SmallIntegerField(null=True, blank=True) - status = models.PositiveSmallIntegerField(null=True, blank=True, choices=[ - (1, _('Alive')), - (2, _('Dead')), - (3, _('Long dead')), - (4, _('Unknown')), - ]) + status = models.PositiveSmallIntegerField( + null=True, + blank=True, + choices=[ + (1, _("Alive")), + (2, _("Dead")), + (3, _("Long dead")), + (4, _("Unknown")), + ], + ) + notes = models.TextField(blank=True) + priority = models.PositiveSmallIntegerField( + default=0, choices=[(0, _("Low")), (1, _("Medium")), (2, _("High"))] + ) - def __str__(self): - return self.name + class Wikidata: + first_name = WIKIDATA.GIVEN_NAME + last_name = WIKIDATA.LAST_NAME + year_of_death = WIKIDATA.DATE_OF_DEATH + notes = "description" + def __str__(self): + return f"{self.first_name} {self.last_name}" -class Book(models.Model): - uri = models.CharField(max_length=255) +class Book(WikidataMixin, models.Model): + slug = models.SlugField(max_length=255, blank=True, null=True, unique=True) authors = models.ManyToManyField(Author, blank=True) - translators = models.ManyToManyField(Author, related_name='translated_book_set', related_query_name='translated_book', blank=True) - title = models.CharField(max_length=255) - language = models.CharField(max_length=3) - based_on = models.ForeignKey('self', models.PROTECT, related_name='translation', null=True, blank=True) - + translators = models.ManyToManyField( + Author, + related_name="translated_book_set", + related_query_name="translated_book", + blank=True, + ) + title = models.CharField(max_length=255, blank=True) + language = models.CharField(max_length=3, blank=True) + based_on = models.ForeignKey( + "self", models.PROTECT, related_name="translation", null=True, blank=True + ) scans_source = models.CharField(max_length=255, blank=True) text_source = models.CharField(max_length=255, blank=True) notes = models.TextField(blank=True) - priority = models.PositiveSmallIntegerField(default=0, choices=[ - (0, _('Low')), - (1, _('Medium')), - (2, _('High')), - ]) + priority = models.PositiveSmallIntegerField( + default=0, choices=[(0, _("Low")), (1, _("Medium")), (2, _("High"))] + ) + + class Wikidata: + authors = WIKIDATA.AUTHOR + translators = WIKIDATA.TRANSLATOR + title = WIKIDATA.TITLE + language = WIKIDATA.LANGUAGE + based_on = WIKIDATA.BASED_ON + notes = "description" def __str__(self): return self.title diff --git a/src/catalogue/wikidata.py b/src/catalogue/wikidata.py new file mode 100644 index 00000000..4e63c095 --- /dev/null +++ b/src/catalogue/wikidata.py @@ -0,0 +1,87 @@ +from datetime import date +from django.db import models +from django.db.models.signals import m2m_changed +from django.utils.translation import gettext_lazy as _ +from django.dispatch import receiver +from wikidata.client import Client +from wikidata.datavalue import DatavalueError + + +class WikidataMixin(models.Model): + wikidata = models.CharField( + max_length=255, + null=True, + blank=True, + unique=True, + help_text=_('If you have a Wikidata ID, like "Q1337", enter it and save.'), + ) + + class Meta: + abstract = True + + def save(self, **kwargs): + super().save() + if self.wikidata and hasattr(self, "Wikidata"): + Wikidata = type(self).Wikidata + client = Client() + # Probably should getlist + entity = client.get(self.wikidata) + for attname in dir(Wikidata): + if attname.startswith("_"): + continue + wd = getattr(Wikidata, attname) + + model_field = self._meta.get_field(attname) + if isinstance(model_field, models.ManyToManyField): + if getattr(self, attname).all().exists(): + continue + else: + if getattr(self, attname): + continue + + wdvalue = None + if wd == "description": + wdvalue = entity.description.get("pl", str(entity.description)) + elif wd == "label": + wdvalue = entity.label.get("pl", str(entity.label)) + else: + try: + wdvalue = entity.get(client.get(wd)) + except DatavalueError: + pass + + self.set_field_from_wikidata(attname, wdvalue) + + kwargs.update(force_insert=False, force_update=True) + super().save(**kwargs) + + def set_field_from_wikidata(self, attname, wdvalue): + if not wdvalue: + return + # Find out what this model field is + model_field = self._meta.get_field(attname) + if isinstance(model_field, models.ForeignKey): + rel_model = model_field.related_model + if issubclass(rel_model, WikidataMixin): + # welp, we can try and find by WD identifier. + wdvalue, created = rel_model.objects.get_or_create(wikidata=wdvalue.id) + setattr(self, attname, wdvalue) + elif isinstance(model_field, models.ManyToManyField): + rel_model = model_field.related_model + if issubclass(rel_model, WikidataMixin): + wdvalue, created = rel_model.objects.get_or_create(wikidata=wdvalue.id) + getattr(self, attname).set([wdvalue]) + else: + # How to get original title? + if isinstance(wdvalue, date): + if isinstance(model_field, models.IntegerField): + wdvalue = wdvalue.year + elif not isinstance(wdvalue, str): + wdvalue = wdvalue.label.get("pl", str(wdvalue.label)) + setattr(self, attname, wdvalue) + + +class WikidataAdminMixin: + def save_related(self, request, form, formsets, change): + super().save_related(request, form, formsets, change) + form.instance.save() -- 2.20.1 From 0c5403e0b84d85ce6f515e2b8e5bc6190647645f Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Wed, 15 Apr 2020 13:53:07 +0200 Subject: [PATCH 04/16] Importing catalogue from pdcounter dump. --- .../commands/import_catalogue_from_wl_dump.py | 63 +++++++++++++++++++ .../migrations/0010_auto_20200415_1336.py | 33 ++++++++++ src/catalogue/models.py | 4 ++ 3 files changed, 100 insertions(+) create mode 100644 src/catalogue/management/commands/import_catalogue_from_wl_dump.py create mode 100644 src/catalogue/migrations/0010_auto_20200415_1336.py diff --git a/src/catalogue/management/commands/import_catalogue_from_wl_dump.py b/src/catalogue/management/commands/import_catalogue_from_wl_dump.py new file mode 100644 index 00000000..226fc476 --- /dev/null +++ b/src/catalogue/management/commands/import_catalogue_from_wl_dump.py @@ -0,0 +1,63 @@ +import json +import sys +from django.core.management import BaseCommand +from slugify import slugify +import wikidata +from catalogue.models import Book, Author + + +class Command(BaseCommand): + def add_arguments(self, parser): + parser.add_argument('path') + + def handle(self, path, **kwargs): + with open(path) as f: + data = json.load(f) + for item in data: + if item['model'] == 'pdcounter.bookstub': + continue + notes = [] + slug = item['fields']['slug'] + book, created = Book.objects.get_or_create(slug=slug) + if item['fields']['translator'] and not book.translators.exists(): + notes.append('tłum.: ' + item['fields']['translator']) + book.title = book.title or item['fields']['title'] + book.pd_year = book.pd_year or item['fields']['pd'] + notes = '\n'.join(notes) + if notes and notes not in book.notes: + book.notes = '\n'.join([notes, book.notes]) + book.save() + + if not book.authors.exists(): + author_name = item['fields']['author'] + name_pieces = author_name.rsplit(' ', 1) + if len(name_pieces) == 1: + first_name, last_name = name_pieces, '' + else: + first_name, last_name = name_pieces + + author, created = Author.objects.get_or_create(first_name=first_name, last_name=last_name) + if not author.slug: + print(author.slug, author_name) + author.slug = slugify(author_name) + author.save() + book.authors.set([author]) + elif item['model'] == 'pdcounter.author': + slug = item['fields']['slug'] + author, created = Author.objects.get_or_create(slug=slug) + if not author.first_name and not author.last_name: + author_name = item['fields']['name'] + name_pieces = author_name.rsplit(' ', 1) + if len(name_pieces) == 1: + author.first_name, author.last_name = name_pieces, '' + else: + author.first_name, author.last_name = name_pieces + author.year_of_death = author.year_of_death or item['fields']['death'] + author.notes = author.notes or item['fields']['description'] + author.gazeta_link = author.gazeta_link or item['fields']['gazeta_link'] + wiki_link = item['fields']['wiki_link'] + assert not wiki_link # Welp + else: + print(item) + break + diff --git a/src/catalogue/migrations/0010_auto_20200415_1336.py b/src/catalogue/migrations/0010_auto_20200415_1336.py new file mode 100644 index 00000000..dcb8fd6d --- /dev/null +++ b/src/catalogue/migrations/0010_auto_20200415_1336.py @@ -0,0 +1,33 @@ +# Generated by Django 3.0.4 on 2020-04-15 13:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('catalogue', '0009_auto_20200411_1114'), + ] + + operations = [ + migrations.AddField( + model_name='author', + name='culturepl_link', + field=models.CharField(blank=True, max_length=255), + ), + migrations.AddField( + model_name='author', + name='description', + field=models.TextField(blank=True), + ), + migrations.AddField( + model_name='author', + name='gazeta_link', + field=models.CharField(blank=True, max_length=255), + ), + migrations.AddField( + model_name='book', + name='pd_year', + field=models.IntegerField(blank=True, null=True), + ), + ] diff --git a/src/catalogue/models.py b/src/catalogue/models.py index c2ed3812..530483c8 100644 --- a/src/catalogue/models.py +++ b/src/catalogue/models.py @@ -20,6 +20,9 @@ class Author(WikidataMixin, models.Model): ], ) notes = models.TextField(blank=True) + gazeta_link = models.CharField(max_length=255, blank=True) + culturepl_link = models.CharField(max_length=255, blank=True) + description = models.TextField(blank=True) priority = models.PositiveSmallIntegerField( default=0, choices=[(0, _("Low")), (1, _("Medium")), (2, _("High"))] ) @@ -54,6 +57,7 @@ class Book(WikidataMixin, models.Model): priority = models.PositiveSmallIntegerField( default=0, choices=[(0, _("Low")), (1, _("Medium")), (2, _("High"))] ) + pd_year = models.IntegerField(null=True, blank=True) class Wikidata: authors = WIKIDATA.AUTHOR -- 2.20.1 From bbf66a1eb672e6029f7d58780d3689005215d03c Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Wed, 15 Apr 2020 17:12:13 +0200 Subject: [PATCH 05/16] Fixes. --- .../commands/import_catalogue_from_wl_dump.py | 2 +- .../migrations/0011_auto_20200415_1517.py | 18 ++++++++++++++++++ src/catalogue/models.py | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 src/catalogue/migrations/0011_auto_20200415_1517.py diff --git a/src/catalogue/management/commands/import_catalogue_from_wl_dump.py b/src/catalogue/management/commands/import_catalogue_from_wl_dump.py index 226fc476..4d69eb4d 100644 --- a/src/catalogue/management/commands/import_catalogue_from_wl_dump.py +++ b/src/catalogue/management/commands/import_catalogue_from_wl_dump.py @@ -15,7 +15,6 @@ class Command(BaseCommand): data = json.load(f) for item in data: if item['model'] == 'pdcounter.bookstub': - continue notes = [] slug = item['fields']['slug'] book, created = Book.objects.get_or_create(slug=slug) @@ -55,6 +54,7 @@ class Command(BaseCommand): author.year_of_death = author.year_of_death or item['fields']['death'] author.notes = author.notes or item['fields']['description'] author.gazeta_link = author.gazeta_link or item['fields']['gazeta_link'] + author.save() wiki_link = item['fields']['wiki_link'] assert not wiki_link # Welp else: diff --git a/src/catalogue/migrations/0011_auto_20200415_1517.py b/src/catalogue/migrations/0011_auto_20200415_1517.py new file mode 100644 index 00000000..fe5dea3a --- /dev/null +++ b/src/catalogue/migrations/0011_auto_20200415_1517.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.4 on 2020-04-15 15:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('catalogue', '0010_auto_20200415_1336'), + ] + + operations = [ + migrations.AlterField( + model_name='book', + name='language', + field=models.CharField(blank=True, max_length=255), + ), + ] diff --git a/src/catalogue/models.py b/src/catalogue/models.py index 530483c8..250a115e 100644 --- a/src/catalogue/models.py +++ b/src/catalogue/models.py @@ -47,7 +47,7 @@ class Book(WikidataMixin, models.Model): blank=True, ) title = models.CharField(max_length=255, blank=True) - language = models.CharField(max_length=3, blank=True) + language = models.CharField(max_length=255, blank=True) based_on = models.ForeignKey( "self", models.PROTECT, related_name="translation", null=True, blank=True ) -- 2.20.1 From 2caa6415139fe58938a62dca695639ba0cc86dda Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Wed, 15 Apr 2020 18:00:55 +0200 Subject: [PATCH 06/16] Collections in catalogue. --- src/catalogue/admin.py | 48 +++++++++++++++++-- .../commands/import_catalogue_from_wl_dump.py | 25 +++++----- src/catalogue/migrations/0012_collection.py | 23 +++++++++ .../migrations/0013_auto_20200415_1755.py | 39 +++++++++++++++ src/catalogue/models.py | 31 +++++++++++- src/catalogue/wikidata.py | 8 ++++ 6 files changed, 156 insertions(+), 18 deletions(-) create mode 100644 src/catalogue/migrations/0012_collection.py create mode 100644 src/catalogue/migrations/0013_auto_20200415_1755.py diff --git a/src/catalogue/admin.py b/src/catalogue/admin.py index bd783b99..95da4994 100644 --- a/src/catalogue/admin.py +++ b/src/catalogue/admin.py @@ -4,18 +4,60 @@ from .wikidata import WikidataAdminMixin class AuthorAdmin(WikidataAdminMixin, admin.ModelAdmin): - list_display = "first_name", "last_name", "notes" + list_display = ["first_name", "last_name", 'status', "year_of_death", "priority", "wikidata_link"] + list_filter = ['year_of_death', 'priority', 'collections', 'status'] search_fields = ["first_name", "last_name", "wikidata"] prepopulated_fields = {"slug": ("first_name", "last_name")} + autocomplete_fields = ['collections'] admin.site.register(models.Author, AuthorAdmin) class BookAdmin(WikidataAdminMixin, admin.ModelAdmin): - list_display = "title", "notes" - autocomplete_fields = ["authors", "translators"] + list_display = ["title", 'authors_str', 'translators_str', 'language', 'pd_year', 'priority', 'wikidata_link'] + search_fields = ["title", 'wikidata'] + autocomplete_fields = ["authors", "translators", "based_on", 'collections'] prepopulated_fields = {"slug": ("title",)} + list_filter = ['language', 'pd_year', 'collections'] + readonly_fields = ['wikidata_link'] + fieldsets = [ + (None, {'fields': [ + ('wikidata', 'wikidata_link'), + ]}), + ('Identification', {'fields': [ + 'title', 'slug', 'authors', 'translators', 'language', + 'based_on', + 'pd_year', + ]}), + ('Plan', {'fields': [ + 'scans_source', + 'text_source', + 'priority', + 'collections', + 'notes', + ]}), + ] admin.site.register(models.Book, BookAdmin) + + +class AuthorInline(admin.TabularInline): + model = models.Author.collections.through + autocomplete_fields = ['author'] + + +class BookInline(admin.TabularInline): + model = models.Book.collections.through + autocomplete_fields = ['book'] + + +class CollectionAdmin(admin.ModelAdmin): + list_display = ['name'] + autocomplete_fields = [] + prepopulated_fields = {'slug': ('name',)} + search_fields = ['name'] + inlines = [AuthorInline, BookInline] + +admin.site.register(models.Collection, CollectionAdmin) diff --git a/src/catalogue/management/commands/import_catalogue_from_wl_dump.py b/src/catalogue/management/commands/import_catalogue_from_wl_dump.py index 4d69eb4d..ea3f8054 100644 --- a/src/catalogue/management/commands/import_catalogue_from_wl_dump.py +++ b/src/catalogue/management/commands/import_catalogue_from_wl_dump.py @@ -6,6 +6,15 @@ import wikidata from catalogue.models import Book, Author +def parse_name(name): + name_pieces = name.rsplit(' ', 1) + if len(name_pieces) == 1: + return name_pieces[0], '' + else: + return name_pieces + + + class Command(BaseCommand): def add_arguments(self, parser): parser.add_argument('path') @@ -28,16 +37,9 @@ class Command(BaseCommand): book.save() if not book.authors.exists(): - author_name = item['fields']['author'] - name_pieces = author_name.rsplit(' ', 1) - if len(name_pieces) == 1: - first_name, last_name = name_pieces, '' - else: - first_name, last_name = name_pieces - + first_name, last_name = parse_name(item['fields']['author']) author, created = Author.objects.get_or_create(first_name=first_name, last_name=last_name) if not author.slug: - print(author.slug, author_name) author.slug = slugify(author_name) author.save() book.authors.set([author]) @@ -45,12 +47,7 @@ class Command(BaseCommand): slug = item['fields']['slug'] author, created = Author.objects.get_or_create(slug=slug) if not author.first_name and not author.last_name: - author_name = item['fields']['name'] - name_pieces = author_name.rsplit(' ', 1) - if len(name_pieces) == 1: - author.first_name, author.last_name = name_pieces, '' - else: - author.first_name, author.last_name = name_pieces + author.first_name, author.last_name = parse_name(item['fields']['name']) author.year_of_death = author.year_of_death or item['fields']['death'] author.notes = author.notes or item['fields']['description'] author.gazeta_link = author.gazeta_link or item['fields']['gazeta_link'] diff --git a/src/catalogue/migrations/0012_collection.py b/src/catalogue/migrations/0012_collection.py new file mode 100644 index 00000000..3e5409e5 --- /dev/null +++ b/src/catalogue/migrations/0012_collection.py @@ -0,0 +1,23 @@ +# Generated by Django 3.0.4 on 2020-04-15 17:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('catalogue', '0011_auto_20200415_1517'), + ] + + operations = [ + migrations.CreateModel( + name='Collection', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('slug', models.SlugField(max_length=255, unique=True)), + ('authors', models.ManyToManyField(blank=True, to='catalogue.Author')), + ('books', models.ManyToManyField(blank=True, to='catalogue.Book')), + ], + ), + ] diff --git a/src/catalogue/migrations/0013_auto_20200415_1755.py b/src/catalogue/migrations/0013_auto_20200415_1755.py new file mode 100644 index 00000000..3de73880 --- /dev/null +++ b/src/catalogue/migrations/0013_auto_20200415_1755.py @@ -0,0 +1,39 @@ +# Generated by Django 3.0.4 on 2020-04-15 17:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('catalogue', '0012_collection'), + ] + + operations = [ + migrations.AlterModelOptions( + name='author', + options={'ordering': ('last_name', 'first_name', 'year_of_death')}, + ), + migrations.AlterModelOptions( + name='book', + options={'ordering': ('title',)}, + ), + migrations.RemoveField( + model_name='collection', + name='authors', + ), + migrations.RemoveField( + model_name='collection', + name='books', + ), + migrations.AddField( + model_name='author', + name='collections', + field=models.ManyToManyField(blank=True, to='catalogue.Collection'), + ), + migrations.AddField( + model_name='book', + name='collections', + field=models.ManyToManyField(blank=True, to='catalogue.Collection'), + ), + ] diff --git a/src/catalogue/models.py b/src/catalogue/models.py index 250a115e..e41ba35e 100644 --- a/src/catalogue/models.py +++ b/src/catalogue/models.py @@ -26,6 +26,10 @@ class Author(WikidataMixin, models.Model): priority = models.PositiveSmallIntegerField( default=0, choices=[(0, _("Low")), (1, _("Medium")), (2, _("High"))] ) + collections = models.ManyToManyField('Collection', blank=True) + + class Meta: + ordering = ('last_name', 'first_name', 'year_of_death') class Wikidata: first_name = WIKIDATA.GIVEN_NAME @@ -58,6 +62,10 @@ class Book(WikidataMixin, models.Model): default=0, choices=[(0, _("Low")), (1, _("Medium")), (2, _("High"))] ) pd_year = models.IntegerField(null=True, blank=True) + collections = models.ManyToManyField('Collection', blank=True) + + class Meta: + ordering = ('title',) class Wikidata: authors = WIKIDATA.AUTHOR @@ -68,4 +76,25 @@ class Book(WikidataMixin, models.Model): notes = "description" def __str__(self): - return self.title + txt = self.title + astr = self.authors_str() + if astr: + txt = f'{astr} – {txt}' + tstr = self.translators_str() + if tstr: + txt = f'{txt} (tłum. {tstr})' + return txt + + def authors_str(self): + return ', '.join(str(author) for author in self.authors.all()) + + def translators_str(self): + return ', '.join(str(author) for author in self.translators.all()) + + +class Collection(models.Model): + name = models.CharField(max_length=255) + slug = models.SlugField(max_length=255, unique=True) + + def __str__(self): + return self.name diff --git a/src/catalogue/wikidata.py b/src/catalogue/wikidata.py index 4e63c095..c6885705 100644 --- a/src/catalogue/wikidata.py +++ b/src/catalogue/wikidata.py @@ -1,6 +1,7 @@ from datetime import date from django.db import models from django.db.models.signals import m2m_changed +from django.utils.html import format_html from django.utils.translation import gettext_lazy as _ from django.dispatch import receiver from wikidata.client import Client @@ -85,3 +86,10 @@ class WikidataAdminMixin: def save_related(self, request, form, formsets, change): super().save_related(request, form, formsets, change) form.instance.save() + + def wikidata_link(self, obj): + if obj.wikidata: + return format_html('{wd}', wd=obj.wikidata) + else: + return '' + wikidata_link.admin_order_field = 'wikidata' -- 2.20.1 From 73da85af22d6ef3decdb1ffa5af819a9fbb19e32 Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Fri, 17 Apr 2020 18:53:07 +0200 Subject: [PATCH 07/16] Importing catalogue from WL dump. --- src/catalogue/admin.py | 91 +++++--- .../commands/import_catalogue_from_wl_dump.py | 196 ++++++++++++++---- .../migrations/0014_book_gazeta_link.py | 18 ++ .../migrations/0015_auto_20200416_1120.py | 23 ++ .../migrations/0016_auto_20200417_1421.py | 23 ++ .../migrations/0017_auto_20200417_1638.py | 25 +++ src/catalogue/models.py | 25 ++- src/catalogue/wikidata.py | 12 +- 8 files changed, 336 insertions(+), 77 deletions(-) create mode 100644 src/catalogue/migrations/0014_book_gazeta_link.py create mode 100644 src/catalogue/migrations/0015_auto_20200416_1120.py create mode 100644 src/catalogue/migrations/0016_auto_20200417_1421.py create mode 100644 src/catalogue/migrations/0017_auto_20200417_1638.py diff --git a/src/catalogue/admin.py b/src/catalogue/admin.py index 95da4994..91656f13 100644 --- a/src/catalogue/admin.py +++ b/src/catalogue/admin.py @@ -4,60 +4,95 @@ from .wikidata import WikidataAdminMixin class AuthorAdmin(WikidataAdminMixin, admin.ModelAdmin): - list_display = ["first_name", "last_name", 'status', "year_of_death", "priority", "wikidata_link"] - list_filter = ['year_of_death', 'priority', 'collections', 'status'] + list_display = [ + "first_name", + "last_name", + "status", + "year_of_death", + "priority", + "wikidata_link", + "slug", + ] + list_filter = ["year_of_death", "priority", "collections", "status"] search_fields = ["first_name", "last_name", "wikidata"] prepopulated_fields = {"slug": ("first_name", "last_name")} - autocomplete_fields = ['collections'] + autocomplete_fields = ["collections"] admin.site.register(models.Author, AuthorAdmin) class BookAdmin(WikidataAdminMixin, admin.ModelAdmin): - list_display = ["title", 'authors_str', 'translators_str', 'language', 'pd_year', 'priority', 'wikidata_link'] - search_fields = ["title", 'wikidata'] - autocomplete_fields = ["authors", "translators", "based_on", 'collections'] + list_display = [ + "title", + "authors_str", + "translators_str", + "language", + "pd_year", + "priority", + "wikidata_link", + ] + search_fields = ["title", "wikidata"] + autocomplete_fields = ["authors", "translators", "based_on", "collections"] prepopulated_fields = {"slug": ("title",)} - list_filter = ['language', 'pd_year', 'collections'] - readonly_fields = ['wikidata_link'] + list_filter = ["language", "pd_year", "collections"] + readonly_fields = ["wikidata_link"] fieldsets = [ - (None, {'fields': [ - ('wikidata', 'wikidata_link'), - ]}), - ('Identification', {'fields': [ - 'title', 'slug', 'authors', 'translators', 'language', - 'based_on', - 'pd_year', - ]}), - ('Plan', {'fields': [ - 'scans_source', - 'text_source', - 'priority', - 'collections', - 'notes', - ]}), + (None, {"fields": [("wikidata", "wikidata_link")]}), + ( + "Identification", + { + "fields": [ + "title", + "slug", + "authors", + "translators", + "language", + "based_on", + "pd_year", + ] + }, + ), + ( + "Plan", + { + "fields": [ + "scans_source", + "text_source", + "priority", + "collections", + "notes", + ] + }, + ), ] + def get_queryset(self, request): + qs = super().get_queryset(request) + if request.resolver_match.view_name.endswith("changelist"): + qs = qs.prefetch_related("authors", "translators") + return qs + admin.site.register(models.Book, BookAdmin) class AuthorInline(admin.TabularInline): model = models.Author.collections.through - autocomplete_fields = ['author'] + autocomplete_fields = ["author"] class BookInline(admin.TabularInline): model = models.Book.collections.through - autocomplete_fields = ['book'] + autocomplete_fields = ["book"] class CollectionAdmin(admin.ModelAdmin): - list_display = ['name'] + list_display = ["name"] autocomplete_fields = [] - prepopulated_fields = {'slug': ('name',)} - search_fields = ['name'] + prepopulated_fields = {"slug": ("name",)} + search_fields = ["name"] inlines = [AuthorInline, BookInline] + admin.site.register(models.Collection, CollectionAdmin) diff --git a/src/catalogue/management/commands/import_catalogue_from_wl_dump.py b/src/catalogue/management/commands/import_catalogue_from_wl_dump.py index ea3f8054..2ce39f06 100644 --- a/src/catalogue/management/commands/import_catalogue_from_wl_dump.py +++ b/src/catalogue/management/commands/import_catalogue_from_wl_dump.py @@ -1,4 +1,5 @@ import json +from urllib.request import urlopen import sys from django.core.management import BaseCommand from slugify import slugify @@ -7,54 +8,177 @@ from catalogue.models import Book, Author def parse_name(name): - name_pieces = name.rsplit(' ', 1) + name_pieces = name.rsplit(" ", 1) if len(name_pieces) == 1: - return name_pieces[0], '' + return name_pieces[0], "" else: return name_pieces +def find_wikidata(link, lang): + link = link.rstrip() + title = link.rsplit("/", 1)[-1] + title = link.split("#", 1)[0] + title = title.replace(" ", "_") + data = json.load( + urlopen( + f"https://www.wikidata.org/w/api.php?action=wbgetentities&sites={lang}wiki&titles={title}&format=json" + ) + ) + wikidata_id = list(data["entities"].keys())[0] + if not wikidata_id.startswith("Q"): + return None + return wikidata_id + class Command(BaseCommand): def add_arguments(self, parser): - parser.add_argument('path') + parser.add_argument("path") def handle(self, path, **kwargs): with open(path) as f: data = json.load(f) - for item in data: - if item['model'] == 'pdcounter.bookstub': - notes = [] - slug = item['fields']['slug'] - book, created = Book.objects.get_or_create(slug=slug) - if item['fields']['translator'] and not book.translators.exists(): - notes.append('tłum.: ' + item['fields']['translator']) - book.title = book.title or item['fields']['title'] - book.pd_year = book.pd_year or item['fields']['pd'] - notes = '\n'.join(notes) - if notes and notes not in book.notes: - book.notes = '\n'.join([notes, book.notes]) - book.save() - - if not book.authors.exists(): - first_name, last_name = parse_name(item['fields']['author']) - author, created = Author.objects.get_or_create(first_name=first_name, last_name=last_name) - if not author.slug: - author.slug = slugify(author_name) + + for pass_n in (1, 2): + for item in data: + if item["model"] == "pdcounter.bookstub": + if pass_n != 2: + continue + notes = [] + print(item["fields"]["author"], item["fields"]["title"]) + slug = item["fields"]["slug"] + book, created = Book.objects.get_or_create(slug=slug) + if item["fields"]["translator"] and not book.translators.exists(): + notes.append("tłum.: " + item["fields"]["translator"]) + book.title = book.title or item["fields"]["title"] + book.pd_year = book.pd_year or item["fields"]["pd"] + notes = "\n".join(notes) + if notes and notes not in book.notes: + book.notes = "\n".join([notes, book.notes]) + book.save() + + if not book.authors.exists(): + first_name, last_name = parse_name(item["fields"]["author"]) + author_slug = slugify(item["fields"]["author"]) + author = ( + Author.objects.filter(slug=author_slug).first() + or Author.objects.filter( + first_name=first_name, last_name=last_name + ).first() + or Author() + ) + author.slug = author.slug or author_slug + author.first_name = author.first_name or first_name + author.last_name = author.last_name or last_name + author.save() + book.authors.set([author]) + elif item["model"] == "pdcounter.author": + if pass_n != 1: + continue + slug = item["fields"]["slug"] + author, created = Author.objects.get_or_create(slug=slug) + if not author.first_name and not author.last_name: + author.first_name, author.last_name = parse_name( + item["fields"]["name"] + ) + author.year_of_death = ( + author.year_of_death or item["fields"]["death"] + ) + author.notes = author.notes or item["fields"]["description"] + author.gazeta_link = ( + author.gazeta_link or item["fields"]["gazeta_link"] + ) author.save() - book.authors.set([author]) - elif item['model'] == 'pdcounter.author': - slug = item['fields']['slug'] - author, created = Author.objects.get_or_create(slug=slug) - if not author.first_name and not author.last_name: - author.first_name, author.last_name = parse_name(item['fields']['name']) - author.year_of_death = author.year_of_death or item['fields']['death'] - author.notes = author.notes or item['fields']['description'] - author.gazeta_link = author.gazeta_link or item['fields']['gazeta_link'] + wiki_link = item["fields"]["wiki_link"] + assert not wiki_link # Welp + elif item["model"] == "catalogue.book": + if pass_n != 2: + continue + if item["fields"]["parent"]: + continue + print(item["fields"]["slug"]) + slug = item["fields"]["slug"] + book, created = Book.objects.get_or_create(slug=slug) + book.title = book.title or item["fields"]["title"] + book.language = book.language or item["fields"]["language"] + book.gazeta_link = book.gazeta_link or item["fields"]["gazeta_link"] + if item["fields"]["wiki_link"]: + book.wikidata = ( + book.wikidata + or find_wikidata(item["fields"]["wiki_link"], "pl") + or "" + ) + + extra_info = json.loads(item["fields"]["extra_info"]) + if book.pd_year is None and extra_info.get( + "released_to_public_domain_at" + ): + book.pd_year = int( + extra_info["released_to_public_domain_at"].split("-", 1)[0] + ) + + book.save() + + if not book.authors.exists(): + authors = [] + for astr in extra_info.get("authors", []): + parts = astr.split(", ") + if len(parts) == 1: + first_name = parts[0] + last_name = "" + else: + last_name, first_name = parts + aslug = slugify(f"{first_name} {last_name}".strip()) + author = ( + Author.objects.filter(slug=aslug).first() + or Author.objects.filter( + first_name=first_name, last_name=last_name + ).first() + or Author.objects.filter(name_de=astr).first() + or Author.objects.filter(name_lt=astr).first() + ) + # Not trying to create the author or set properties, because here we don't know the dc:creator@xml:lang property. + if author is not None: + authors.append(author) + book.authors.set(authors) + elif item["model"] == "catalogue.tag": + if pass_n != 1: + continue + if item["fields"]["category"] != "author": + continue + slug = item["fields"]["slug"] + author, created = Author.objects.get_or_create(slug=slug) + author.name_de = author.name_de or item["fields"]["name_de"] or "" + author.name_lt = author.name_lt or item["fields"]["name_lt"] or "" + if not author.first_name and not author.last_name: + author.first_name, author.last_name = parse_name( + item["fields"]["name_pl"] + ) + author.culturepl_link = ( + author.culturepl_link or item["fields"]["culturepl_link"] or "" + ) + author.gazeta_link = ( + author.gazeta_link or item["fields"]["gazeta_link"] or "" + ) + author.description = ( + author.description or item["fields"]["description_pl"] or "" + ) + author.description_de = ( + author.description_de or item["fields"]["description_de"] or "" + ) + author.description_lt = ( + author.description_lt or item["fields"]["description_lt"] or "" + ) + + if not author.wikidata: + for field, value in item["fields"].items(): + if field.startswith("wiki_link_") and value: + wd = find_wikidata(value, field.rsplit("_", 1)[-1]) + if wd: + author.wikidata = wd + break author.save() - wiki_link = item['fields']['wiki_link'] - assert not wiki_link # Welp - else: - print(item) - break + else: + print(item) + break diff --git a/src/catalogue/migrations/0014_book_gazeta_link.py b/src/catalogue/migrations/0014_book_gazeta_link.py new file mode 100644 index 00000000..4b0a496f --- /dev/null +++ b/src/catalogue/migrations/0014_book_gazeta_link.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.4 on 2020-04-15 22:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('catalogue', '0013_auto_20200415_1755'), + ] + + operations = [ + migrations.AddField( + model_name='book', + name='gazeta_link', + field=models.CharField(blank=True, max_length=255), + ), + ] diff --git a/src/catalogue/migrations/0015_auto_20200416_1120.py b/src/catalogue/migrations/0015_auto_20200416_1120.py new file mode 100644 index 00000000..996e5a6c --- /dev/null +++ b/src/catalogue/migrations/0015_auto_20200416_1120.py @@ -0,0 +1,23 @@ +# Generated by Django 3.0.4 on 2020-04-16 11:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('catalogue', '0014_book_gazeta_link'), + ] + + operations = [ + migrations.AddField( + model_name='author', + name='name_de', + field=models.CharField(blank=True, max_length=255), + ), + migrations.AddField( + model_name='author', + name='name_lt', + field=models.CharField(blank=True, max_length=255), + ), + ] diff --git a/src/catalogue/migrations/0016_auto_20200417_1421.py b/src/catalogue/migrations/0016_auto_20200417_1421.py new file mode 100644 index 00000000..ca4b7145 --- /dev/null +++ b/src/catalogue/migrations/0016_auto_20200417_1421.py @@ -0,0 +1,23 @@ +# Generated by Django 3.0.4 on 2020-04-17 14:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('catalogue', '0015_auto_20200416_1120'), + ] + + operations = [ + migrations.AddField( + model_name='author', + name='description_de', + field=models.TextField(blank=True), + ), + migrations.AddField( + model_name='author', + name='description_lt', + field=models.TextField(blank=True), + ), + ] diff --git a/src/catalogue/migrations/0017_auto_20200417_1638.py b/src/catalogue/migrations/0017_auto_20200417_1638.py new file mode 100644 index 00000000..c4a983a4 --- /dev/null +++ b/src/catalogue/migrations/0017_auto_20200417_1638.py @@ -0,0 +1,25 @@ +# Generated by Django 3.0.4 on 2020-04-17 16:38 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('catalogue', '0016_auto_20200417_1421'), + ] + + operations = [ + migrations.AlterField( + model_name='author', + name='wikidata', + field=models.CharField(blank=True, default='', help_text='If you have a Wikidata ID, like "Q1337", enter it and save.', max_length=255), + preserve_default=False, + ), + migrations.AlterField( + model_name='book', + name='wikidata', + field=models.CharField(blank=True, default='', help_text='If you have a Wikidata ID, like "Q1337", enter it and save.', max_length=255), + preserve_default=False, + ), + ] diff --git a/src/catalogue/models.py b/src/catalogue/models.py index e41ba35e..b45cefed 100644 --- a/src/catalogue/models.py +++ b/src/catalogue/models.py @@ -8,6 +8,10 @@ class Author(WikidataMixin, models.Model): slug = models.SlugField(null=True, blank=True, unique=True) first_name = models.CharField(max_length=255, blank=True) last_name = models.CharField(max_length=255, blank=True) + + name_de = models.CharField(max_length=255, blank=True) + name_lt = models.CharField(max_length=255, blank=True) + year_of_death = models.SmallIntegerField(null=True, blank=True) status = models.PositiveSmallIntegerField( null=True, @@ -22,14 +26,18 @@ class Author(WikidataMixin, models.Model): notes = models.TextField(blank=True) gazeta_link = models.CharField(max_length=255, blank=True) culturepl_link = models.CharField(max_length=255, blank=True) + description = models.TextField(blank=True) + description_de = models.TextField(blank=True) + description_lt = models.TextField(blank=True) + priority = models.PositiveSmallIntegerField( default=0, choices=[(0, _("Low")), (1, _("Medium")), (2, _("High"))] ) - collections = models.ManyToManyField('Collection', blank=True) + collections = models.ManyToManyField("Collection", blank=True) class Meta: - ordering = ('last_name', 'first_name', 'year_of_death') + ordering = ("last_name", "first_name", "year_of_death") class Wikidata: first_name = WIKIDATA.GIVEN_NAME @@ -62,10 +70,11 @@ class Book(WikidataMixin, models.Model): default=0, choices=[(0, _("Low")), (1, _("Medium")), (2, _("High"))] ) pd_year = models.IntegerField(null=True, blank=True) - collections = models.ManyToManyField('Collection', blank=True) + gazeta_link = models.CharField(max_length=255, blank=True) + collections = models.ManyToManyField("Collection", blank=True) class Meta: - ordering = ('title',) + ordering = ("title",) class Wikidata: authors = WIKIDATA.AUTHOR @@ -79,17 +88,17 @@ class Book(WikidataMixin, models.Model): txt = self.title astr = self.authors_str() if astr: - txt = f'{astr} – {txt}' + txt = f"{astr} – {txt}" tstr = self.translators_str() if tstr: - txt = f'{txt} (tłum. {tstr})' + txt = f"{txt} (tłum. {tstr})" return txt def authors_str(self): - return ', '.join(str(author) for author in self.authors.all()) + return ", ".join(str(author) for author in self.authors.all()) def translators_str(self): - return ', '.join(str(author) for author in self.translators.all()) + return ", ".join(str(author) for author in self.translators.all()) class Collection(models.Model): diff --git a/src/catalogue/wikidata.py b/src/catalogue/wikidata.py index c6885705..88686b6e 100644 --- a/src/catalogue/wikidata.py +++ b/src/catalogue/wikidata.py @@ -11,9 +11,7 @@ from wikidata.datavalue import DatavalueError class WikidataMixin(models.Model): wikidata = models.CharField( max_length=255, - null=True, blank=True, - unique=True, help_text=_('If you have a Wikidata ID, like "Q1337", enter it and save.'), ) @@ -89,7 +87,11 @@ class WikidataAdminMixin: def wikidata_link(self, obj): if obj.wikidata: - return format_html('{wd}', wd=obj.wikidata) + return format_html( + '{wd}', + wd=obj.wikidata, + ) else: - return '' - wikidata_link.admin_order_field = 'wikidata' + return "" + + wikidata_link.admin_order_field = "wikidata" -- 2.20.1 From be8ef0d8acf821727d6a023e72eca0c81ee96d84 Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Fri, 17 Apr 2020 18:59:11 +0200 Subject: [PATCH 08/16] Longer slug. --- .../migrations/0018_auto_20200417_1859.py | 18 ++++++++++++++++++ src/catalogue/models.py | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 src/catalogue/migrations/0018_auto_20200417_1859.py diff --git a/src/catalogue/migrations/0018_auto_20200417_1859.py b/src/catalogue/migrations/0018_auto_20200417_1859.py new file mode 100644 index 00000000..46860486 --- /dev/null +++ b/src/catalogue/migrations/0018_auto_20200417_1859.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.4 on 2020-04-17 18:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('catalogue', '0017_auto_20200417_1638'), + ] + + operations = [ + migrations.AlterField( + model_name='author', + name='slug', + field=models.SlugField(blank=True, max_length=255, null=True, unique=True), + ), + ] diff --git a/src/catalogue/models.py b/src/catalogue/models.py index b45cefed..6a27cd9d 100644 --- a/src/catalogue/models.py +++ b/src/catalogue/models.py @@ -5,7 +5,7 @@ from .wikidata import WikidataMixin class Author(WikidataMixin, models.Model): - slug = models.SlugField(null=True, blank=True, unique=True) + slug = models.SlugField(max_length=255, null=True, blank=True, unique=True) first_name = models.CharField(max_length=255, blank=True) last_name = models.CharField(max_length=255, blank=True) -- 2.20.1 From c4426f18fdb50c224e8a5211a1766dcf261eaff8 Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Fri, 17 Apr 2020 19:38:14 +0200 Subject: [PATCH 09/16] bugfix --- .../management/commands/import_catalogue_from_wl_dump.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/catalogue/management/commands/import_catalogue_from_wl_dump.py b/src/catalogue/management/commands/import_catalogue_from_wl_dump.py index 2ce39f06..29d32753 100644 --- a/src/catalogue/management/commands/import_catalogue_from_wl_dump.py +++ b/src/catalogue/management/commands/import_catalogue_from_wl_dump.py @@ -18,7 +18,7 @@ def parse_name(name): def find_wikidata(link, lang): link = link.rstrip() title = link.rsplit("/", 1)[-1] - title = link.split("#", 1)[0] + title = title.split("#", 1)[0] title = title.replace(" ", "_") data = json.load( urlopen( -- 2.20.1 From acf3c41fb0ffe8e451b1e465b00e9998f2b027ac Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Mon, 20 Apr 2020 17:05:10 +0200 Subject: [PATCH 10/16] Link catalogue to documents. --- src/catalogue/admin.py | 3 + src/catalogue/constants.py | 3 + src/catalogue/locale/pl/LC_MESSAGES/django.mo | Bin 0 -> 1066 bytes src/catalogue/locale/pl/LC_MESSAGES/django.po | 71 ++++ .../commands/import_catalogue_from_wl_dump.py | 3 + src/catalogue/models.py | 23 ++ .../templates/catalogue/author_detail.html | 89 +++++ .../templates/catalogue/book_detail.html | 80 +++++ .../templates/catalogue/catalogue.html | 93 +++++ src/catalogue/urls.py | 12 + src/catalogue/utils.py | 49 +++ src/catalogue/views.py | 43 +++ src/catalogue/wikidata.py | 3 + src/documents/locale/pl/LC_MESSAGES/django.mo | Bin 7938 -> 7892 bytes src/documents/locale/pl/LC_MESSAGES/django.po | 317 +++++++++--------- src/documents/models/book.py | 6 + .../templates/documents/book_detail.html | 15 +- src/redakcja/urls.py | 5 +- 18 files changed, 652 insertions(+), 163 deletions(-) create mode 100644 src/catalogue/locale/pl/LC_MESSAGES/django.mo create mode 100644 src/catalogue/locale/pl/LC_MESSAGES/django.po create mode 100644 src/catalogue/templates/catalogue/author_detail.html create mode 100644 src/catalogue/templates/catalogue/book_detail.html create mode 100644 src/catalogue/templates/catalogue/catalogue.html create mode 100644 src/catalogue/urls.py create mode 100644 src/catalogue/utils.py create mode 100644 src/catalogue/views.py diff --git a/src/catalogue/admin.py b/src/catalogue/admin.py index 91656f13..00617cb6 100644 --- a/src/catalogue/admin.py +++ b/src/catalogue/admin.py @@ -1,3 +1,6 @@ +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# from django.contrib import admin from . import models from .wikidata import WikidataAdminMixin diff --git a/src/catalogue/constants.py b/src/catalogue/constants.py index f8180f76..88d0e338 100644 --- a/src/catalogue/constants.py +++ b/src/catalogue/constants.py @@ -1,3 +1,6 @@ +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# class WIKIDATA: AUTHOR = "P50" LANGUAGE = "P407" diff --git a/src/catalogue/locale/pl/LC_MESSAGES/django.mo b/src/catalogue/locale/pl/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..eb7cf8594ea29e7f4edea762c8f12fecb203be27 GIT binary patch literal 1066 zcmY+CPfrs;7{*7%e-#gMBpzNc5Ky-6E=93Wh)__lXdwlRx3N2Hr`^spyIX1-4up?j zq6bCe!NiM+2aFewrr*MN;DZ>SDJ8{8-u&jB_j#Y0ot-}eeQy{>KjJ204{-zW8({{W zV{8x{0xyEI;8kz|>;rRMu7fkkx4QM$;05G|;3e=Acp3cC&A)-)5kC;Ov0j1L_`d~> z?y$?xpy~e#u7XG4b?`4}^!+%5(GP%Ez(LUHZ-KDxe3#LgwFKgHn4Z&d9uwGg2HHA4 z*Ff`~Y-FrB9ucp{B^~~kMC@T8cLiJIF%Ps~7c48d#~w?+#8!5wsq0kYyMj1v%Zl`% zqLu6f1+pS&c-@(tygxiaLd7B^8536?MUW?1PAi{0W0lhlwkAATud+>5QM#d69C8&U zSw7TdQH-q>&)O1UB(+LYVnft)7+Y(RFFk9a?nhQZV>$oK+7P?ah^#D+MVegOwJkek zxi&fX(o^moa`%Twz9U=vv!hpVo~F$Dz*yE8YHoJ72mA|T&fu=mUtM6 zII~$;wx)YJbJ86VT1%>^J*oUOO}~_JA|KR49$3pdtVU^4wN4skQZv}rR5D{KlW}OC zY)aFZ8gcBoj7y`VWTvy3)VP^<*6mrxrMTD&HsR+43TLuqPt{h!JR_U(6} U@RVdvrD!Rxnk;S~)T>GM4-CTs-v9sr literal 0 HcmV?d00001 diff --git a/src/catalogue/locale/pl/LC_MESSAGES/django.po b/src/catalogue/locale/pl/LC_MESSAGES/django.po new file mode 100644 index 00000000..3b3d06bb --- /dev/null +++ b/src/catalogue/locale/pl/LC_MESSAGES/django.po @@ -0,0 +1,71 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-04-20 16:44+0200\n" +"PO-Revision-Date: 2020-04-20 16:52+0200\n" +"Language: pl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n" +"%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n" +"%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" +"Last-Translator: \n" +"Language-Team: \n" +"X-Generator: Poedit 2.2.4\n" + +#: models.py:22 +msgid "Alive" +msgstr "Żyje" + +#: models.py:23 +msgid "Dead" +msgstr "Zmarły" + +#: models.py:24 +msgid "Long dead" +msgstr "Dawno zmarły" + +#: models.py:25 +msgid "Unknown" +msgstr "Nieznany" + +#: models.py:37 models.py:83 +msgid "Low" +msgstr "Niski" + +#: models.py:37 models.py:83 +msgid "Medium" +msgstr "Średni" + +#: models.py:37 models.py:83 +msgid "High" +msgstr "Wysoki" + +#: templates/catalogue/catalogue.html:7 templates/catalogue/catalogue.html:13 +msgid "Catalogue" +msgstr "Katalog" + +#: templates/catalogue/catalogue.html:67 +msgid "trans." +msgstr "tłum." + +#: templates/catalogue/catalogue.html:90 +#, python-format +msgid "%(c)s author" +msgid_plural "%(c)s authors" +msgstr[0] "%(c)s autor" +msgstr[1] "%(c)s autorów" +msgstr[2] "%(c)s autorów" +msgstr[3] "" + +#: wikidata.py:18 +msgid "If you have a Wikidata ID, like \"Q1337\", enter it and save." +msgstr "" +"Jeśli masz identyfikator Wikidanych , jak „Q1337”, wklej go i zapisz." diff --git a/src/catalogue/management/commands/import_catalogue_from_wl_dump.py b/src/catalogue/management/commands/import_catalogue_from_wl_dump.py index 29d32753..e137f5a6 100644 --- a/src/catalogue/management/commands/import_catalogue_from_wl_dump.py +++ b/src/catalogue/management/commands/import_catalogue_from_wl_dump.py @@ -1,3 +1,6 @@ +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# import json from urllib.request import urlopen import sys diff --git a/src/catalogue/models.py b/src/catalogue/models.py index 6a27cd9d..0863c1e3 100644 --- a/src/catalogue/models.py +++ b/src/catalogue/models.py @@ -1,6 +1,9 @@ +from django.apps import apps from django.db import models +from django.urls import reverse from django.utils.translation import gettext_lazy as _ from .constants import WIKIDATA +from .utils import UnrelatedManager from .wikidata import WikidataMixin @@ -48,6 +51,17 @@ class Author(WikidataMixin, models.Model): def __str__(self): return f"{self.first_name} {self.last_name}" + def get_absolute_url(self): + return reverse("catalogue_author", args=[self.slug]) + + @property + def pd_year(self): + if self.year_of_death: + return self.year_of_death + 71 + elif self.year_of_death == 0: + return 0 + else: + return None class Book(WikidataMixin, models.Model): slug = models.SlugField(max_length=255, blank=True, null=True, unique=True) @@ -73,6 +87,8 @@ class Book(WikidataMixin, models.Model): gazeta_link = models.CharField(max_length=255, blank=True) collections = models.ManyToManyField("Collection", blank=True) + objects = UnrelatedManager() + class Meta: ordering = ("title",) @@ -94,12 +110,19 @@ class Book(WikidataMixin, models.Model): txt = f"{txt} (tłum. {tstr})" return txt + def get_absolute_url(self): + return reverse("catalogue_book", args=[self.slug]) + def authors_str(self): return ", ".join(str(author) for author in self.authors.all()) def translators_str(self): return ", ".join(str(author) for author in self.translators.all()) + def get_document_books(self): + DBook = apps.get_model("documents", "Book") + return DBook.objects.filter(dc_slug=self.slug) + class Collection(models.Model): name = models.CharField(max_length=255) diff --git a/src/catalogue/templates/catalogue/author_detail.html b/src/catalogue/templates/catalogue/author_detail.html new file mode 100644 index 00000000..177f3be9 --- /dev/null +++ b/src/catalogue/templates/catalogue/author_detail.html @@ -0,0 +1,89 @@ +{% extends "documents/base.html" %} + +{% load i18n %} +{% load pagination_tags %} + + +{% block titleextra %}{% trans "Catalogue" %}{% endblock %} + + +{% block content %} +
+
+

{% trans "Catalogue" %}

+
+
+ + + + + + + + + {% for book in author.book_set.all %} + + + + + + + + {% endfor %} + {% for book in author.translated_book_set.all %} + + + + + + + + {% endfor %} +
+ + {{ author }} + + + {{ author.pd_year|default_if_none:"-" }} + + {{ author.wikidata_link }} + + {{ author.get_priority_display }} +
+   + + {{ book.title }} + + + {{ book.pd_year|default_if_none:"-" }} + + {{ book.wikidata_link }} + + {{ book.get_priorty_display }} + + {% for b in book.document_books %} + + {{ b }} + + {% endfor %} +
+   + + {{ book.title }} + ({% trans "trans." %}) + + {{ book.pd_year|default_if_none:"-" }} + + {{ book.wikidata_link }} + + {{ book.get_priorty_display }} + + {% for b in book.document_books %} + + {{ b }} + + {% endfor %} +
+
+
+{% endblock content %} diff --git a/src/catalogue/templates/catalogue/book_detail.html b/src/catalogue/templates/catalogue/book_detail.html new file mode 100644 index 00000000..2c59c359 --- /dev/null +++ b/src/catalogue/templates/catalogue/book_detail.html @@ -0,0 +1,80 @@ +{% extends "documents/base.html" %} + +{% load i18n %} +{% load pagination_tags %} + + +{% block titleextra %}{% trans "Catalogue" %}{% endblock %} + + +{% block content %} +
+
+

{% trans "Catalogue" %}

+
+
+ + {% for author in book.authors.all %} + + + + + + + {% endfor %} + {% for author in book.translator_set.all %} + + + + + + + {% endfor %} + + + + + + + +
+ + {{ author }} + + + {{ author.pd_year|default_if_none:"-" }} + + {{ author.wikidata_link }} + + {{ author.get_priority_display }} +
+ + {{ author }} + + + {{ author.pd_year|default_if_none:"-" }} + + {{ author.wikidata_link }} + + {{ author.get_priority_display }} +
+   + + {{ book.title }} + + + {{ book.pd_year|default_if_none:"-" }} + + {{ book.wikidata_link }} + + {{ book.get_priorty_display }} + + {% for b in book.get_document_books %} + + {{ b }} + + {% endfor %} +
+
+
+{% endblock content %} diff --git a/src/catalogue/templates/catalogue/catalogue.html b/src/catalogue/templates/catalogue/catalogue.html new file mode 100644 index 00000000..715ba8f1 --- /dev/null +++ b/src/catalogue/templates/catalogue/catalogue.html @@ -0,0 +1,93 @@ +{% extends "documents/base.html" %} + +{% load i18n %} +{% load pagination_tags %} + + +{% block titleextra %}{% trans "Catalogue" %}{% endblock %} + + +{% block content %} +
+
+

{% trans "Catalogue" %}

+
+
+ + {% autopaginate authors 20 as authors_page %} + {% for author in authors_page %} + + + + + + + {% for book in author.book_set.all %} + + + + + + + + {% endfor %} + {% for book in author.translated_book_set.all %} + + + + + + + + {% endfor %} + {% endfor %} +
+ + {{ author }} + + + {{ author.pd_year|default_if_none:"-" }} + + {{ author.wikidata_link }} + + {{ author.get_priority_display }} +
+   + + {{ book.title }} + + + {{ book.pd_year|default_if_none:"-" }} + + {{ book.wikidata_link }} + + {{ book.get_priorty_display }} + + {% for b in book.document_books %} + + {{ b }} + + {% endfor %} +
+   + + {{ book.title }} + ({% trans "trans." %}) + + {{ book.pd_year|default_if_none:"-" }} + + {{ book.wikidata_link }} + + {{ book.get_priorty_display }} + + {% for b in book.document_books %} + + {{ b }} + + {% endfor %} +
+ {% paginate %} + {% blocktrans count c=authors|length %}{{c}} author{% plural %}{{c}} authors{% endblocktrans %} +
+
+{% endblock content %} diff --git a/src/catalogue/urls.py b/src/catalogue/urls.py new file mode 100644 index 00000000..10ef6d00 --- /dev/null +++ b/src/catalogue/urls.py @@ -0,0 +1,12 @@ +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from django.urls import path +from . import views + + +urlpatterns = [ + path("", views.CatalogueView.as_view(), name="catalogue"), + path("author//", views.AuthorView.as_view(), name="catalogue_author"), + path("book//", views.BookView.as_view(), name="catalogue_book"), +] diff --git a/src/catalogue/utils.py b/src/catalogue/utils.py new file mode 100644 index 00000000..00f71878 --- /dev/null +++ b/src/catalogue/utils.py @@ -0,0 +1,49 @@ +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from collections import defaultdict +from django.db.models import QuerySet +from django.db.models.manager import BaseManager + + +class UnrelatedQuerySet(QuerySet): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._prefetch_unrelated_lookups = {} + self._prefetch_unrelated_done = False + + def _clone(self): + c = super()._clone() + c._prefetch_unrelated_lookups = self._prefetch_unrelated_lookups.copy() + return c + + def prefetch_unrelated(self, attribute, field, other_model, other_field): + clone = self._clone() + clone._prefetch_unrelated_lookups[field] = (attribute, other_model, other_field) + return clone + + def _fetch_all(self): + prefetch_done = self._prefetch_done + super()._fetch_all() + if self._prefetch_unrelated_lookups and not prefetch_done: + self._prefetch_unrelated_objects() + + def _prefetch_unrelated_objects(self): + for ( + field, + (attribute, other_model, other_field), + ) in self._prefetch_unrelated_lookups.items(): + values = set([getattr(obj, field) for obj in self._result_cache]) + other_objects = other_model._default_manager.filter( + **{f"{other_field}__in": values} + ) + results = defaultdict(list) + for other_obj in other_objects: + results[getattr(other_obj, other_field)].append(other_obj) + for obj in self._result_cache: + setattr(obj, attribute, results.get(getattr(obj, field))) + self._prefetch_unrelated_done = True + + +class UnrelatedManager(BaseManager.from_queryset(UnrelatedQuerySet)): + pass diff --git a/src/catalogue/views.py b/src/catalogue/views.py new file mode 100644 index 00000000..0b29b6f4 --- /dev/null +++ b/src/catalogue/views.py @@ -0,0 +1,43 @@ +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from django.db.models import Prefetch +from django.views.generic import DetailView, TemplateView +from . import models +import documents.models + + +class CatalogueView(TemplateView): + template_name = "catalogue/catalogue.html" + + def get_context_data(self): + ctx = super().get_context_data() + documents_books_queryset = models.Book.objects.prefetch_unrelated( + "document_books", "slug", documents.models.Book, "dc_slug" + ) + ctx["authors"] = models.Author.objects.all().prefetch_related( + Prefetch("book_set", queryset=documents_books_queryset), + Prefetch("translated_book_set", queryset=documents_books_queryset), + ) + return ctx + + +class AuthorView(TemplateView): + model = models.Author + template_name = "catalogue/author_detail.html" + + def get_context_data(self, slug): + ctx = super().get_context_data() + documents_books_queryset = models.Book.objects.prefetch_unrelated( + "document_books", "slug", documents.models.Book, "dc_slug" + ) + authors = models.Author.objects.filter(slug=slug).prefetch_related( + Prefetch("book_set", queryset=documents_books_queryset), + Prefetch("translated_book_set", queryset=documents_books_queryset), + ) + ctx["author"] = authors.first() + return ctx + + +class BookView(DetailView): + model = models.Book diff --git a/src/catalogue/wikidata.py b/src/catalogue/wikidata.py index 88686b6e..d97d3af7 100644 --- a/src/catalogue/wikidata.py +++ b/src/catalogue/wikidata.py @@ -1,3 +1,6 @@ +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# from datetime import date from django.db import models from django.db.models.signals import m2m_changed diff --git a/src/documents/locale/pl/LC_MESSAGES/django.mo b/src/documents/locale/pl/LC_MESSAGES/django.mo index 395b34ef304645b1683209c7a5347b78c983321b..dde8ac158d040540d6924e8ea53596d130a77c76 100644 GIT binary patch delta 2922 zcmZA3drZ}39LMqJ98r{uauLCRJSAQbxynreDN=-xB1Oe3Xn_|FU~mA@=r|-+MwT+S zY&mDSR^#z#Ur=~FQHaEihRsuM%R5&s0q4I_oreY zW}`B;6eDpRYGU=M_U#zK_@;*wb$ATb;ajK9BR~?0Lmj;}Tx*^Gyb5JucL``rtDup$uiPfS~xet|@=a4y> zL#UO%jxIcly8kMw|6%JN(|G=RX8+iRp)99_atgYz9ksGoQ5}DVO7RtB^5z; vSY z-ohlDM5p&*25MsEsEMydEodVu6HVddUmbK)p^gutGI1DHf6O*Kfm-nyRJ#G|6;!)x zs0j|)@=eqLcTo2QMvUDTkD5q=HPOq7ZcIUSl#d#q$XaTzSD+@g1+}6&d%X#j(l*qi z=t7cUdQmAqj?BUIqXzy2HO_g|INs0gh0FHB*QgE$ZT$@#b{O*;rc!^IWzEBXQ7fFw z^3*;L)$t0{qbWyCXcH>sb*OfGF&2A}g?r5#oT%YxWE+@|Q5_GUUc>9wyQq$0*|7_+W{R%FiR5GLVC)C9gjt$Yx}8Q=WCi3YxdnuwEkMGZnw14ZIo%*8^i zM!lvdP#yN8Ch#FD)fZ6re~W52gqqL@YEwEXXkzggrT0ILlUZ1d>aY%(i)psy7m$xR z%9m32KF-7eREIw!A2Z5V8pd!@6DmbzvI;ek22{U$Z2imVJwU}NTQQ&Bw8;ukH!MeG zqylMTHlSAML9MU{^*7@sRQrC^%Fd!T;YGY32QdTxMzu>~6}p}sOa4`{f(q@{Rj8C# zV=;E)BD{q8IEreQ&!~F#rRcxUT8%U@Zp^{wk!@o>MYX?*TF`Z5E@mW-{Og7QcA74@ zP%B$t%ju{a^H3RBhPrW!t?#gQp?3d^s0No;*Jpnbbg{X;Vp?*gSQ5h*m|06?fZVxIG-KggcD%-SJFL+;H6txKEi*YI&6S?LBrDVB zj_hF~5(=xX(~ I#q~P>1A1x!k^lez delta 2950 zcmYk;drZ}39LMqJ00M#s0WX+xIhH|!mxB~2H~c1oP)uk#!SQcs47d)AJcFuX5bXewdL!OKU2(y z?w8?A+%Zw^G5g3cra5dYbfJ^$UR1??oP}pG48Ov=@COV;2eVQAVAS<&jKCPAidl-P zpMm_D3O+PmJx(H8_Wn_z7x&LDT@(?fsjmow;QVp_A!cyHJnJ zjp3M&iCAjycUpVUqb=$u6NPW1w)``!!)vIH^66C*+KQ@QZLeEUE7^yd;B%<0eHk^e zKGZWmgW9Q&k-3^LP&;vTI{WV;GeUtnj$pbn2K5XVq6;%{EtcEs*HA0^6t!cc$Xv}B z>d^(!SOYjQ3FC1tmY^omfSTy;2=-qqXs19c??!d-Dyri*Q7eDfmY+k_8$hja5LNGn zbrjWp47Jk#?DaIBquNEH+9jacWqHVGCTpw(sD?$Tj>=ILw^?iL{hg?Z9Yn3@u)W`n z+QH+ftv`uuiaCXPL?0n@GGC&`^;{#Pfv%$l`rY0bw&j1JI{ep`Phq=$c5o1JDdoc~ zD*OT>6KNmf2GF!=LhTW(weg(Ax zFRJ77w)_fqa(%;IH`1FX)`_a$h1#j(NEOqETJZ;{6<&g zbYn3Nmti)lycyM?19ggzp|<`6=HURQU?9C`Vmj&!>_OE(WIcvdH776)&qTBT+Tvjf z)F6Npr4=}lIhiO_gJrh72(^+@dtHHQSBqM43##2wTmHKB9n{-!4z(i#_WH7i4CldI zMK$~rH9!d4pav1B_cRLih@w$jo@DQ*pz5zcb-c>nFF{RgGipIqsJE&CwIl7Q344x^ z(J4QP+KJPs0YA51L=Etbz5dQ#51}5-uc$-#H}bV$_7lDdA)D&^kGT_TiG#%Bgnlm^ zB3P@(JVHik*92xW>c@zlZ!)1TnaR{nW}m&#n{zjjPh=5VO#)FxEF{W^T%w3jN+k3< zKxrLupD#x*Px&&&JYijhI(*ud-NfxF&t7cDHev^{$(HGrRLUl{_;UOvur^{f@c?nB zEfe8d#U4VhozjC7_&&9*IveS>Oy6=%L@Tk4(DU9)JW43JiLHcQSv~7T#Qnr%Y9OOC zrgN1bs&N&!wR)d%miYDj zGW|#I-m+Qm mJN(<~(!HTkIgVhnac_N7O\n" "Language-Team: Fundacja Nowoczesna Polska \n" @@ -18,89 +18,97 @@ 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 2.0.6\n" +"X-Generator: Poedit 2.2.4\n" -#: forms.py:38 +#: forms.py:41 msgid "Text file must be UTF-8 encoded." msgstr "Plik powinien mieć kodowanie UTF-8." -#: forms.py:41 +#: forms.py:57 +msgid "Title not set" +msgstr "Brak tytułu" + +#: forms.py:60 +msgid "Slug not set" +msgstr "Brak slugu" + +#: forms.py:63 msgid "You must either enter text or upload a file" msgstr "Proszę wpisać tekst albo wybrać plik do załadowania" -#: forms.py:50 +#: forms.py:72 msgid "ZIP file" msgstr "Plik ZIP" -#: forms.py:51 +#: forms.py:73 msgid "Directories are documents in chunks" msgstr "Katalogi zawierają dokumenty w częściach" -#: forms.py:75 forms.py:176 +#: forms.py:97 forms.py:198 msgid "Assigned to" msgstr "Przypisane do" -#: forms.py:96 forms.py:110 +#: forms.py:118 forms.py:132 msgid "Chunk with this slug already exists" msgstr "Część z tym slugiem już istnieje" -#: forms.py:119 +#: forms.py:141 msgid "Append to" msgstr "Dołącz do" -#: models/book.py:26 models/chunk.py:21 models/image.py:20 +#: models/book.py:27 models/chunk.py:21 models/image.py:20 msgid "title" msgstr "tytuł" -#: models/book.py:27 models/chunk.py:22 models/image.py:21 +#: models/book.py:28 models/chunk.py:22 models/image.py:21 msgid "slug" msgstr "slug" -#: models/book.py:28 models/image.py:22 +#: models/book.py:29 models/image.py:22 msgid "public" msgstr "publiczna" -#: models/book.py:29 +#: models/book.py:30 msgid "scan gallery name" msgstr "nazwa galerii skanów" -#: models/book.py:33 +#: models/book.py:34 msgid "parent" msgstr "rodzic" -#: models/book.py:34 +#: models/book.py:35 msgid "parent number" msgstr "numeracja rodzica" -#: models/book.py:52 models/chunk.py:19 models/publish_log.py:15 +#: models/book.py:53 models/chunk.py:19 models/publish_log.py:15 msgid "book" msgstr "książka" -#: models/book.py:53 views.py:619 +#: models/book.py:54 views.py:619 msgid "books" msgstr "książki" -#: models/book.py:257 +#: models/book.py:263 msgid "No chunks in the book." msgstr "Książka nie ma części." -#: models/book.py:261 +#: models/book.py:267 msgid "Not all chunks have publishable revisions." msgstr "Niektóre części nie są gotowe do publikacji." -#: models/book.py:268 models/image.py:83 +#: models/book.py:274 models/image.py:83 msgid "Invalid XML" msgstr "Nieprawidłowy XML" -#: models/book.py:270 models/image.py:85 +#: models/book.py:276 models/image.py:85 msgid "No Dublin Core found." msgstr "Brak sekcji Dublin Core." -#: models/book.py:272 models/image.py:87 +#: models/book.py:278 models/image.py:87 msgid "Invalid Dublin Core" msgstr "Nieprawidłowy Dublin Core" -#: models/book.py:275 models/image.py:91 +#: models/book.py:281 models/image.py:91 msgid "rdf:about is not" msgstr "rdf:about jest różny od" @@ -140,8 +148,8 @@ msgstr "nazwa" msgid "notes" msgstr "notatki" -#: models/project.py:17 templates/catalogue/book_list/book_list.html:66 -#: templates/catalogue/image_table.html:60 +#: models/project.py:17 templates/documents/book_list/book_list.html:66 +#: templates/documents/image_table.html:60 msgid "project" msgstr "projekt" @@ -154,7 +162,7 @@ msgid "time" msgstr "czas" #: models/publish_log.py:17 models/publish_log.py:45 -#: templates/catalogue/wall.html:20 +#: templates/documents/wall.html:20 msgid "user" msgstr "użytkownik" @@ -186,185 +194,178 @@ msgstr "zapis publikacji obrazu" msgid "image publish records" msgstr "zapisy publikacji obrazów" -#: templates/catalogue/active_users_list.html:5 +#: templates/documents/active_users_list.html:5 msgid "Active users" msgstr "Aktywni użytkownicy" -#: templates/catalogue/active_users_list.html:11 +#: templates/documents/active_users_list.html:11 msgid "Users active in the year" msgstr "Użytkownicy aktywni w roku" -#: templates/catalogue/activity.html:6 templates/catalogue/activity.html:15 -#: templatetags/catalogue.py:30 +#: templates/documents/activity.html:6 templates/documents/activity.html:15 +#: templatetags/documents.py:30 msgid "Activity" msgstr "Aktywność" -#: templates/catalogue/base.html:13 +#: templates/documents/base.html:13 msgid "Platforma Redakcyjna" msgstr "Platforma Redakcyjna" -#: templates/catalogue/book_append_to.html:5 -#: templates/catalogue/book_append_to.html:14 +#: templates/documents/book_append_to.html:5 +#: templates/documents/book_append_to.html:14 msgid "Append book" msgstr "Dołącz książkę" -#: templates/catalogue/book_detail.html:23 -#: templates/catalogue/book_edit.html:13 templates/catalogue/chunk_edit.html:22 -#: templates/catalogue/image_detail.html:22 +#: templates/documents/book_detail.html:24 +#: templates/documents/book_edit.html:13 templates/documents/chunk_edit.html:22 +#: templates/documents/image_detail.html:22 msgid "Save" msgstr "Zapisz" -#: templates/catalogue/book_detail.html:30 +#: templates/documents/book_detail.html:31 msgid "Edit gallery" msgstr "Edytuj galerię" -#: templates/catalogue/book_detail.html:33 +#: templates/documents/book_detail.html:34 msgid "Append to other book" msgstr "Dołącz do innej książki" -#: templates/catalogue/book_detail.html:40 +#: templates/documents/book_detail.html:49 msgid "Chunks" msgstr "Części" -#: templates/catalogue/book_detail.html:58 -#: templates/catalogue/image_detail.html:47 templatetags/wall.py:108 +#: templates/documents/book_detail.html:67 +#: templates/documents/image_detail.html:47 templatetags/wall.py:108 #: templatetags/wall.py:129 msgid "Publication" msgstr "Publikacja" -#: templates/catalogue/book_detail.html:69 -#: templates/catalogue/image_detail.html:51 +#: templates/documents/book_detail.html:85 +#: templates/documents/image_detail.html:51 msgid "Last published" msgstr "Ostatnio opublikowano" -#: templates/catalogue/book_detail.html:79 +#: templates/documents/book_detail.html:95 msgid "Full XML" msgstr "Pełny XML" -#: templates/catalogue/book_detail.html:80 +#: templates/documents/book_detail.html:96 msgid "HTML version" msgstr "Wersja HTML" -#: templates/catalogue/book_detail.html:81 +#: templates/documents/book_detail.html:97 msgid "TXT version" msgstr "Wersja TXT" -#: templates/catalogue/book_detail.html:82 +#: templates/documents/book_detail.html:98 msgid "PDF version" msgstr "Wersja PDF" -#: templates/catalogue/book_detail.html:83 +#: templates/documents/book_detail.html:99 msgid "PDF version for mobiles" msgstr "Wersja PDF na telefony" -#: templates/catalogue/book_detail.html:84 +#: templates/documents/book_detail.html:100 msgid "EPUB version" msgstr "Wersja EPUB" -#: templates/catalogue/book_detail.html:85 +#: templates/documents/book_detail.html:101 msgid "MOBI version" msgstr "Wersja MOBI" -#: templates/catalogue/book_detail.html:99 -#: templates/catalogue/image_detail.html:70 +#: templates/documents/book_detail.html:115 +#: templates/documents/image_detail.html:70 msgid "Publish" msgstr "Opublikuj" -#: templates/catalogue/book_detail.html:103 -#: templates/catalogue/image_detail.html:74 +#: templates/documents/book_detail.html:119 +#: templates/documents/image_detail.html:74 msgid "Log in to publish." msgstr "Zaloguj się, aby opublikować." -#: templates/catalogue/book_detail.html:106 -#: templates/catalogue/image_detail.html:77 +#: templates/documents/book_detail.html:122 +#: templates/documents/image_detail.html:77 msgid "This book can't be published yet, because:" msgstr "Ta książka nie może jeszcze zostać opublikowana. Powód:" -#: templates/catalogue/book_edit.html:5 +#: templates/documents/book_edit.html:5 msgid "Edit book" msgstr "Edytuj książkę" -#: templates/catalogue/book_html.html:12 templates/catalogue/book_text.html:15 +#: templates/documents/book_html.html:12 templates/documents/book_text.html:15 msgid "Table of contents" msgstr "Spis treści" -#: templates/catalogue/book_html.html:13 templates/catalogue/book_text.html:17 +#: templates/documents/book_html.html:13 templates/documents/book_text.html:17 msgid "Edit. note" msgstr "Nota red." -#: templates/catalogue/book_html.html:14 +#: templates/documents/book_html.html:14 msgid "Infobox" msgstr "Informacje" -#: templates/catalogue/book_list/book.html:8 -#: templates/catalogue/book_list/book.html:36 +#: templates/documents/book_list/book.html:8 +#: templates/documents/book_list/book.html:35 msgid "Book settings" msgstr "Ustawienia książki" -#: templates/catalogue/book_list/book.html:9 -#: templates/catalogue/book_list/chunk.html:7 -#: templates/catalogue/chunk_edit.html:6 templates/catalogue/chunk_edit.html:12 +#: templates/documents/book_list/book.html:9 +#: templates/documents/book_list/chunk.html:7 +#: templates/documents/chunk_edit.html:6 templates/documents/chunk_edit.html:12 msgid "Chunk settings" msgstr "Ustawienia części" -#: templates/catalogue/book_list/book.html:12 -#: templates/catalogue/book_list/chunk.html:9 -#: templates/catalogue/image_short.html:9 -msgid "Edit:" -msgstr "Edytuj:" - -#: templates/catalogue/book_list/book.html:21 -#: templates/catalogue/book_list/book.html:43 -#: templates/catalogue/image_short.html:18 templatetags/book_list.py:82 +#: templates/documents/book_list/book.html:20 +#: templates/documents/book_list/book.html:42 +#: templates/documents/image_short.html:17 templatetags/book_list.py:82 #: templatetags/book_list.py:150 msgid "published" msgstr "opublikowane" -#: templates/catalogue/book_list/book.html:24 -#: templates/catalogue/book_list/book.html:46 -#: templates/catalogue/book_list/chunk.html:28 -#: templates/catalogue/image_short.html:21 templatetags/book_list.py:80 +#: templates/documents/book_list/book.html:23 +#: templates/documents/book_list/book.html:45 +#: templates/documents/image_short.html:20 templatetags/book_list.py:80 #: templatetags/book_list.py:148 msgid "publishable" msgstr "do publikacji" -#: templates/catalogue/book_list/book.html:27 -#: templates/catalogue/book_list/chunk.html:33 -#: templates/catalogue/image_short.html:24 templatetags/book_list.py:81 +#: templates/documents/book_list/book.html:26 +#: templates/documents/book_list/chunk.html:26 +#: templates/documents/image_short.html:23 templatetags/book_list.py:81 #: templatetags/book_list.py:149 msgid "changed" msgstr "zmienione" -#: templates/catalogue/book_list/book_list.html:29 -#: templates/catalogue/image_table.html:25 +#: templates/documents/book_list/book_list.html:29 +#: templates/documents/image_table.html:25 msgid "Search in book titles" msgstr "Szukaj w tytułach książek" -#: templates/catalogue/book_list/book_list.html:34 -#: templates/catalogue/image_table.html:30 +#: templates/documents/book_list/book_list.html:34 +#: templates/documents/image_table.html:30 msgid "stage" msgstr "etap" -#: templates/catalogue/book_list/book_list.html:36 -#: templates/catalogue/book_list/book_list.html:47 -#: templates/catalogue/book_list/book_list.html:68 -#: templates/catalogue/image_table.html:32 -#: templates/catalogue/image_table.html:43 -#: templates/catalogue/image_table.html:62 +#: templates/documents/book_list/book_list.html:36 +#: templates/documents/book_list/book_list.html:47 +#: templates/documents/book_list/book_list.html:68 +#: templates/documents/image_table.html:32 +#: templates/documents/image_table.html:43 +#: templates/documents/image_table.html:62 msgid "none" msgstr "brak" -#: templates/catalogue/book_list/book_list.html:45 -#: templates/catalogue/image_table.html:41 +#: templates/documents/book_list/book_list.html:45 +#: templates/documents/image_table.html:41 msgid "editor" msgstr "redaktor" -#: templates/catalogue/book_list/book_list.html:58 -#: templates/catalogue/image_table.html:52 +#: templates/documents/book_list/book_list.html:58 +#: templates/documents/image_table.html:52 msgid "status" msgstr "status" -#: templates/catalogue/book_list/book_list.html:92 +#: templates/documents/book_list/book_list.html:92 #, python-format msgid "%(c)s book" msgid_plural "%(c)s books" @@ -372,73 +373,73 @@ msgstr[0] "%(c)s książka" msgstr[1] "%(c)s książki" msgstr[2] "%(c)s książek" -#: templates/catalogue/book_list/book_list.html:96 +#: templates/documents/book_list/book_list.html:96 msgid "No books found." msgstr "Nie znaleziono książek." -#: templates/catalogue/book_list/book_list.html:105 -#: templates/catalogue/image_table.html:87 +#: templates/documents/book_list/book_list.html:105 +#: templates/documents/image_table.html:87 msgid "Set stage" msgstr "Ustaw etap" -#: templates/catalogue/book_list/book_list.html:106 -#: templates/catalogue/image_table.html:88 +#: templates/documents/book_list/book_list.html:106 +#: templates/documents/image_table.html:88 msgid "Set user" msgstr "Przypisz redaktora" -#: templates/catalogue/book_list/book_list.html:108 -#: templates/catalogue/image_table.html:90 +#: templates/documents/book_list/book_list.html:108 +#: templates/documents/image_table.html:90 msgid "Project" msgstr "Projekt" -#: templates/catalogue/book_list/book_list.html:109 -#: templates/catalogue/image_table.html:91 +#: templates/documents/book_list/book_list.html:109 +#: templates/documents/image_table.html:91 msgid "More users" msgstr "Więcej użytkowników" -#: templates/catalogue/book_text.html:7 +#: templates/documents/book_text.html:7 msgid "Redakcja" msgstr "" -#: templates/catalogue/chunk_add.html:6 templates/catalogue/chunk_add.html:12 -#: templates/catalogue/chunk_edit.html:29 +#: templates/documents/chunk_add.html:6 templates/documents/chunk_add.html:12 +#: templates/documents/chunk_edit.html:29 msgid "Split chunk" msgstr "Podziel część" -#: templates/catalogue/chunk_add.html:19 +#: templates/documents/chunk_add.html:19 msgid "Insert empty chunk after" msgstr "Wstaw pustą część po" -#: templates/catalogue/chunk_add.html:23 +#: templates/documents/chunk_add.html:23 msgid "Add chunk" msgstr "Dodaj część" -#: templates/catalogue/chunk_edit.html:19 +#: templates/documents/chunk_edit.html:19 msgid "Book" msgstr "Książka" -#: templates/catalogue/document_create_missing.html:6 -#: templates/catalogue/document_create_missing.html:12 +#: templates/documents/document_create_missing.html:6 +#: templates/documents/document_create_missing.html:12 msgid "Create a new book" msgstr "Utwórz nową książkę" -#: templates/catalogue/document_create_missing.html:21 +#: templates/documents/document_create_missing.html:21 msgid "Create book" msgstr "Utwórz książkę" -#: templates/catalogue/document_list.html:7 +#: templates/documents/document_list.html:7 msgid "Book list" msgstr "Lista książek" -#: templates/catalogue/document_upload.html:6 +#: templates/documents/document_upload.html:6 msgid "Bulk document upload" msgstr "Hurtowe dodawanie dokumentów" -#: templates/catalogue/document_upload.html:14 +#: templates/documents/document_upload.html:14 msgid "Bulk documents upload" msgstr "Hurtowe dodawanie dokumentów" -#: templates/catalogue/document_upload.html:19 +#: templates/documents/document_upload.html:19 msgid "" "Please submit a ZIP with UTF-8 encoded XML files. Files not ending with " ".xml will be ignored." @@ -446,69 +447,57 @@ msgstr "" "Proszę wskazać archiwum ZIP z plikami XML w kodowaniu UTF-8. Pliki nie " "kończące się na .xml zostaną zignorowane." -#: templates/catalogue/document_upload.html:26 -#: templates/catalogue/upload_pdf.html:16 templatetags/catalogue.py:37 +#: templates/documents/document_upload.html:26 +#: templates/documents/upload_pdf.html:16 templatetags/documents.py:37 msgid "Upload" msgstr "Załaduj" -#: templates/catalogue/document_upload.html:34 +#: templates/documents/document_upload.html:34 msgid "" "There have been some errors. No files have been added to the repository." msgstr "Wystąpiły błędy. Żadne pliki nie zostały dodane do repozytorium." -#: templates/catalogue/document_upload.html:35 +#: templates/documents/document_upload.html:35 msgid "Offending files" msgstr "Błędne pliki" -#: templates/catalogue/document_upload.html:43 +#: templates/documents/document_upload.html:43 msgid "Correct files" msgstr "Poprawne pliki" -#: templates/catalogue/document_upload.html:54 +#: templates/documents/document_upload.html:54 msgid "Files have been successfully uploaded to the repository." msgstr "Pliki zostały dodane do repozytorium." -#: templates/catalogue/document_upload.html:55 +#: templates/documents/document_upload.html:55 msgid "Uploaded files" msgstr "Dodane pliki" -#: templates/catalogue/document_upload.html:65 +#: templates/documents/document_upload.html:65 msgid "Skipped files" msgstr "Pominięte pliki" -#: templates/catalogue/document_upload.html:66 +#: templates/documents/document_upload.html:66 msgid "Files skipped due to no .xml extension" msgstr "Pliki pominięte z powodu braku rozszerzenia .xml." -#: templates/catalogue/head_login.html:10 -msgid "Admin" -msgstr "Administracja" - -#: templates/catalogue/head_login.html:15 -msgid "Log Out" -msgstr "Wyloguj" - -#: templates/catalogue/head_login.html:21 -msgid "Log In" -msgstr "Zaloguj" - -#: templates/catalogue/image_detail.html:34 +#: templates/documents/image_detail.html:34 msgid "Editor" msgstr "Edytor" -#: templates/catalogue/image_detail.html:38 +#: templates/documents/image_detail.html:38 msgid "Proceed to the editor." msgstr "Przejdź do edytora." -#: templates/catalogue/image_list.html:8 +#: templates/documents/image_list.html:8 msgid "Image list" msgstr "Lista obrazów" -#: templates/catalogue/image_short.html:6 +#: templates/documents/image_short.html:6 msgid "Image settings" msgstr "Ustawienia obrazu" -#: templates/catalogue/image_table.html:79 +#: templates/documents/image_table.html:79 #, python-format msgid "%(c)s image" msgid_plural "%(c)s images" @@ -516,36 +505,36 @@ msgstr[0] "%(c)s obraz" msgstr[1] "%(c)s obrazy" msgstr[2] "%(c)s obrazów" -#: templates/catalogue/image_table.html:81 +#: templates/documents/image_table.html:81 msgid "No images found." msgstr "Nie znaleziono obrazów." -#: templates/catalogue/my_page.html:15 templatetags/catalogue.py:28 +#: templates/documents/my_page.html:17 templatetags/documents.py:28 msgid "My page" msgstr "Moja strona" -#: templates/catalogue/my_page.html:25 +#: templates/documents/my_page.html:27 msgid "Your last edited documents" msgstr "Twoje ostatnie edycje" -#: templates/catalogue/my_page.html:45 templates/catalogue/user_page.html:18 +#: templates/documents/my_page.html:47 templates/documents/user_page.html:18 msgid "Recent activity for" msgstr "Ostatnia aktywność dla:" -#: templates/catalogue/upload_pdf.html:5 templates/catalogue/upload_pdf.html:11 +#: templates/documents/upload_pdf.html:5 templates/documents/upload_pdf.html:11 msgid "PDF file upload" msgstr "Ładowanie pliku PDF" -#: templates/catalogue/user_list.html:7 templates/catalogue/user_list.html:14 -#: templatetags/catalogue.py:33 +#: templates/documents/user_list.html:7 templates/documents/user_list.html:14 +#: templatetags/documents.py:33 msgid "Users" msgstr "Użytkownicy" -#: templates/catalogue/wall.html:30 +#: templates/documents/wall.html:30 msgid "not logged in" msgstr "nie zalogowany" -#: templates/catalogue/wall.html:35 +#: templates/documents/wall.html:35 msgid "No activity recorded." msgstr "Nie zanotowano aktywności." @@ -557,19 +546,19 @@ msgstr "nie opublikowane" msgid "empty" msgstr "puste" -#: templatetags/catalogue.py:31 +#: templatetags/documents.py:31 msgid "All" msgstr "Wszystkie" -#: templatetags/catalogue.py:32 +#: templatetags/documents.py:32 msgid "Images" msgstr "Obrazy" -#: templatetags/catalogue.py:36 +#: templatetags/documents.py:36 msgid "Add" msgstr "Dodaj" -#: templatetags/catalogue.py:39 +#: templatetags/documents.py:39 msgid "Covers" msgstr "Okładki" @@ -598,6 +587,18 @@ msgstr "Plik powinien mieć kodowanie UTF-8." msgid "scan gallery" msgstr "galeria skanów" +#~ msgid "Edit:" +#~ msgstr "Edytuj:" + +#~ msgid "Admin" +#~ msgstr "Administracja" + +#~ msgid "Log Out" +#~ msgstr "Wyloguj" + +#~ msgid "Log In" +#~ msgstr "Zaloguj" + #~ msgid "Active users since" #~ msgstr "Użytkownicy aktywni od" diff --git a/src/documents/models/book.py b/src/documents/models/book.py index e7c34814..24d0cea0 100644 --- a/src/documents/models/book.py +++ b/src/documents/models/book.py @@ -1,6 +1,7 @@ # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # +from django.apps import apps from django.contrib.sites.models import Site from django.db import models, transaction from django.template.loader import render_to_string @@ -89,6 +90,11 @@ class Book(models.Model): def gallery_url(self): return '%s%s%s/' % (settings.MEDIA_URL, settings.IMAGE_DIR, self.gallery) + @property + def catalogue_book(self): + CBook = apps.get_model('catalogue', 'Book') + return CBook.objects.filter(slug=self.dc_slug).first() + # Creating & manipulating # ======================= diff --git a/src/documents/templates/documents/book_detail.html b/src/documents/templates/documents/book_detail.html index 3dbaf213..d9b06c7f 100644 --- a/src/documents/templates/documents/book_detail.html +++ b/src/documents/templates/documents/book_detail.html @@ -13,7 +13,8 @@

{{ book.title }}

- +
+
{% if editable %}
{% csrf_token %}{% endif %} @@ -32,8 +33,16 @@

{% trans "Append to other book" %}

{% endif %} + + +
+
+ W katalogu: + {{ book.catalogue_book }} +
+
- +
@@ -73,7 +82,7 @@ Okładka w rozmiarze
-

{% trans "Last published" %}: +

{% trans "Last published" %}: {% if book.last_published %} {{ book.last_published }} {% else %} diff --git a/src/redakcja/urls.py b/src/redakcja/urls.py index af8b4f26..fe2193e7 100644 --- a/src/redakcja/urls.py +++ b/src/redakcja/urls.py @@ -1,7 +1,8 @@ # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # -from django.conf.urls import include, url +from django.conf.urls import url +from django.urls import include, path from django.contrib import admin from django.conf import settings from django.conf.urls.static import static @@ -18,7 +19,7 @@ urlpatterns = [ # Admin panel url(r'^admin/doc/', include('django.contrib.admindocs.urls')), url(r'^admin/', admin.site.urls), - + path('catalogue/', include('catalogue.urls')), url(r'^$', RedirectView.as_view(url='/documents/', permanent=False)), url(r'^documents/', include('documents.urls')), url(r'^apiclient/', include('apiclient.urls')), -- 2.20.1 From e22b691a1c2ac0e1aba750771cd17f032f47c172 Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Mon, 20 Apr 2020 17:09:05 +0200 Subject: [PATCH 11/16] Add link. --- src/documents/templates/documents/book_detail.html | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/documents/templates/documents/book_detail.html b/src/documents/templates/documents/book_detail.html index d9b06c7f..be17ec14 100644 --- a/src/documents/templates/documents/book_detail.html +++ b/src/documents/templates/documents/book_detail.html @@ -37,8 +37,12 @@

- W katalogu: - {{ book.catalogue_book }} + {% with cbook=book.catalogue_book %} + {% if cbook %} + W katalogu: + {{ cbook }} + {% endif %} + {% endwith %}
-- 2.20.1 From 6b985a0a184c49324857d01b2f22fd23ebd5f5a4 Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Mon, 20 Apr 2020 17:12:25 +0200 Subject: [PATCH 12/16] Add wikidata links. --- src/catalogue/wikidata.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/catalogue/wikidata.py b/src/catalogue/wikidata.py index d97d3af7..4fc8bbd8 100644 --- a/src/catalogue/wikidata.py +++ b/src/catalogue/wikidata.py @@ -82,19 +82,19 @@ class WikidataMixin(models.Model): wdvalue = wdvalue.label.get("pl", str(wdvalue.label)) setattr(self, attname, wdvalue) - -class WikidataAdminMixin: - def save_related(self, request, form, formsets, change): - super().save_related(request, form, formsets, change) - form.instance.save() - - def wikidata_link(self, obj): - if obj.wikidata: + def wikidata_link(self): + if self.wikidata: return format_html( '{wd}', - wd=obj.wikidata, + wd=self.wikidata, ) else: return "" wikidata_link.admin_order_field = "wikidata" + + +class WikidataAdminMixin: + def save_related(self, request, form, formsets, change): + super().save_related(request, form, formsets, change) + form.instance.save() -- 2.20.1 From 8851e7c8a2e324d6576656f45a08dc90d2c3dd98 Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Tue, 27 Oct 2020 14:26:52 +0100 Subject: [PATCH 13/16] Add more properties in catalogue. --- src/catalogue/admin.py | 29 ++++- src/catalogue/constants.py | 1 + src/catalogue/locale/pl/LC_MESSAGES/django.mo | Bin 1066 -> 1619 bytes src/catalogue/locale/pl/LC_MESSAGES/django.po | 101 ++++++++++++++---- .../migrations/0019_auto_20201027_1331.py | 23 ++++ .../migrations/0020_auto_20201027_1416.py | 64 +++++++++++ .../migrations/0021_auto_20201027_1422.py | 37 +++++++ .../migrations/0022_auto_20201027_1424.py | 17 +++ src/catalogue/models.py | 43 ++++++++ 9 files changed, 293 insertions(+), 22 deletions(-) create mode 100644 src/catalogue/migrations/0019_auto_20201027_1331.py create mode 100644 src/catalogue/migrations/0020_auto_20201027_1416.py create mode 100644 src/catalogue/migrations/0021_auto_20201027_1422.py create mode 100644 src/catalogue/migrations/0022_auto_20201027_1424.py diff --git a/src/catalogue/admin.py b/src/catalogue/admin.py index 00617cb6..608d625d 100644 --- a/src/catalogue/admin.py +++ b/src/catalogue/admin.py @@ -2,6 +2,7 @@ # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # from django.contrib import admin +from django.utils.translation import gettext_lazy as _ from . import models from .wikidata import WikidataAdminMixin @@ -12,11 +13,13 @@ class AuthorAdmin(WikidataAdminMixin, admin.ModelAdmin): "last_name", "status", "year_of_death", + "gender", + "nationality", "priority", "wikidata_link", "slug", ] - list_filter = ["year_of_death", "priority", "collections", "status"] + list_filter = ["year_of_death", "priority", "collections", "status", "gender", "nationality"] search_fields = ["first_name", "last_name", "wikidata"] prepopulated_fields = {"slug": ("first_name", "last_name")} autocomplete_fields = ["collections"] @@ -36,14 +39,14 @@ class BookAdmin(WikidataAdminMixin, admin.ModelAdmin): "wikidata_link", ] search_fields = ["title", "wikidata"] - autocomplete_fields = ["authors", "translators", "based_on", "collections"] + autocomplete_fields = ["authors", "translators", "based_on", "collections", "epochs", "genres", "kinds"] prepopulated_fields = {"slug": ("title",)} list_filter = ["language", "pd_year", "collections"] readonly_fields = ["wikidata_link"] fieldsets = [ (None, {"fields": [("wikidata", "wikidata_link")]}), ( - "Identification", + _("Identification"), { "fields": [ "title", @@ -57,7 +60,17 @@ class BookAdmin(WikidataAdminMixin, admin.ModelAdmin): }, ), ( - "Plan", + _("Features"), + { + "fields": [ + "epochs", + "genres", + "kinds", + ] + }, + ), + ( + _("Plan"), { "fields": [ "scans_source", @@ -99,3 +112,11 @@ class CollectionAdmin(admin.ModelAdmin): admin.site.register(models.Collection, CollectionAdmin) + + +class CategoryAdmin(admin.ModelAdmin): + search_fields = ["name"] + +admin.site.register(models.Epoch, CategoryAdmin) +admin.site.register(models.Genre, CategoryAdmin) +admin.site.register(models.Kind, CategoryAdmin) diff --git a/src/catalogue/constants.py b/src/catalogue/constants.py index 88d0e338..5bb775ac 100644 --- a/src/catalogue/constants.py +++ b/src/catalogue/constants.py @@ -2,6 +2,7 @@ # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # class WIKIDATA: + GENDER = "P21" AUTHOR = "P50" LANGUAGE = "P407" DATE_OF_DEATH = "P570" diff --git a/src/catalogue/locale/pl/LC_MESSAGES/django.mo b/src/catalogue/locale/pl/LC_MESSAGES/django.mo index eb7cf8594ea29e7f4edea762c8f12fecb203be27..da3bb2932f8f48077a71790affb0a4df7cf24a48 100644 GIT binary patch delta 938 zcmYk4J!lj`6vy9YFB;=%Vxlo7Cq(=L<>W3V2ui9GKjH^R;7Ft3+}`ANZq6Cm-9zsX z0s%W4S!`_VEmFkRPSe?lg`GB5f|V%*3;n;{QGCpMzj-tB=DmHhuSPx=*FR4bo)csi zT7vFCPeR{&FpzsgL^GfOhr!3-9&iIJfKR~@@R{>pfcxRU1TTS`AfNvR`rr<@3;YQp zew}_}vKtEp?D9qtWPcLm1hXI~o&!h0Q{Y~(>eiP)j;lF;+4*&l<5%7K9k<>DxA1Wa zJcImH!tI>kDhwyS<=AlRcR@}NfcwA>xC}mW{#y`(KEQDNCy*0=0l70@K@9p1!}~u# z?#M4D^3yPG=YwO8(;x>Nb36@lVA*jI#GobTUv~aY(7jsLp)s6c;bM+J`5C!`eD^eT z0xI~TB9y<3{{c=k1!cj@WQFnkAR8x#LH>eC=mEFJ;CE*^1RaN>!$j#(Z=rNWili6G zn69aINY`7^Bx+3sB2m_)4R57c?+QZ=qm6xNNc57>Mh-DGt<{-{DX`tH3^?k)X-x8g z4Z_^U)Rrcaxs6FH)5=VoZhAMytCjNng}Lg&#regvm1?Dumi^PKTNVpZ!0-4!t}Yuyiywt`lMs^?#cEWE&k0EY1@?= i!G9-`>%*$hHQe;~**7-ss&wM}Gix0+sLp delta 401 zcmXxfJxjw-6b9g%uYPER+RY#s6&I!9dWjvXlMafCsNl!q5HQ#fynz}il|sRv;6{es3}qveNEaDLz7Z-{W6Xim(1I(l z3|FBEw{!R4GWx^(`3*GC_hA9v!y*D^ESSM=Buy-9Z2)D5UaBvqdB@ag8UbqVQlKJ)dBWpg>%`WQCLiDj%(|o3|m2(v>NVi5}rn>D?H)( f%CXi8N1cl#j#S%fR(7KBN(QpS($DM5bC3N3?Xfhr diff --git a/src/catalogue/locale/pl/LC_MESSAGES/django.po b/src/catalogue/locale/pl/LC_MESSAGES/django.po index 3b3d06bb..2bf50562 100644 --- a/src/catalogue/locale/pl/LC_MESSAGES/django.po +++ b/src/catalogue/locale/pl/LC_MESSAGES/django.po @@ -7,8 +7,10 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-20 16:44+0200\n" -"PO-Revision-Date: 2020-04-20 16:52+0200\n" +"POT-Creation-Date: 2020-10-27 14:25+0100\n" +"PO-Revision-Date: 2020-10-27 14:25+0100\n" +"Last-Translator: \n" +"Language-Team: \n" "Language: pl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -16,47 +18,111 @@ msgstr "" "Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n" "%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n" "%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" -"Last-Translator: \n" -"Language-Team: \n" -"X-Generator: Poedit 2.2.4\n" +"X-Generator: Poedit 2.3\n" + +#: catalogue/admin.py:49 +msgid "Identification" +msgstr "Identyfikacja" -#: models.py:22 +#: catalogue/admin.py:63 +msgid "Features" +msgstr "Cechy" + +#: catalogue/admin.py:73 +msgid "Plan" +msgstr "Plan" + +#: catalogue/models.py:26 msgid "Alive" msgstr "Żyje" -#: models.py:23 +#: catalogue/models.py:27 msgid "Dead" msgstr "Zmarły" -#: models.py:24 +#: catalogue/models.py:28 msgid "Long dead" msgstr "Dawno zmarły" -#: models.py:25 +#: catalogue/models.py:29 msgid "Unknown" msgstr "Nieznany" -#: models.py:37 models.py:83 +#: catalogue/models.py:41 catalogue/models.py:120 msgid "Low" msgstr "Niski" -#: models.py:37 models.py:83 +#: catalogue/models.py:41 catalogue/models.py:120 msgid "Medium" msgstr "Średni" -#: models.py:37 models.py:83 +#: catalogue/models.py:41 catalogue/models.py:120 msgid "High" msgstr "Wysoki" -#: templates/catalogue/catalogue.html:7 templates/catalogue/catalogue.html:13 +#: catalogue/models.py:46 +msgid "author" +msgstr "autor" + +#: catalogue/models.py:47 +msgid "authors" +msgstr "autorzy" + +#: catalogue/models.py:83 +msgid "epoch" +msgstr "epoka" + +#: catalogue/models.py:84 +msgid "epochs" +msgstr "epoki" + +#: catalogue/models.py:89 +msgid "genre" +msgstr "gatunek" + +#: catalogue/models.py:90 +msgid "genres" +msgstr "gatunki" + +#: catalogue/models.py:95 +msgid "kind" +msgstr "rodzaj" + +#: catalogue/models.py:96 +msgid "kinds" +msgstr "rodzaje" + +#: catalogue/models.py:130 +msgid "book" +msgstr "książka" + +#: catalogue/models.py:131 +msgid "books" +msgstr "książki" + +#: catalogue/models.py:170 +msgid "collection" +msgstr "kolekcja" + +#: catalogue/models.py:171 +msgid "collections" +msgstr "kolekcje" + +#: catalogue/templates/catalogue/author_detail.html:7 +#: catalogue/templates/catalogue/author_detail.html:13 +#: catalogue/templates/catalogue/book_detail.html:7 +#: catalogue/templates/catalogue/book_detail.html:13 +#: catalogue/templates/catalogue/catalogue.html:7 +#: catalogue/templates/catalogue/catalogue.html:13 msgid "Catalogue" msgstr "Katalog" -#: templates/catalogue/catalogue.html:67 +#: catalogue/templates/catalogue/author_detail.html:66 +#: catalogue/templates/catalogue/catalogue.html:67 msgid "trans." msgstr "tłum." -#: templates/catalogue/catalogue.html:90 +#: catalogue/templates/catalogue/catalogue.html:90 #, python-format msgid "%(c)s author" msgid_plural "%(c)s authors" @@ -65,7 +131,6 @@ msgstr[1] "%(c)s autorów" msgstr[2] "%(c)s autorów" msgstr[3] "" -#: wikidata.py:18 +#: catalogue/wikidata.py:18 msgid "If you have a Wikidata ID, like \"Q1337\", enter it and save." -msgstr "" -"Jeśli masz identyfikator Wikidanych , jak „Q1337”, wklej go i zapisz." +msgstr "Jeśli masz identyfikator Wikidanych , jak „Q1337”, wklej go i zapisz." diff --git a/src/catalogue/migrations/0019_auto_20201027_1331.py b/src/catalogue/migrations/0019_auto_20201027_1331.py new file mode 100644 index 00000000..bda87224 --- /dev/null +++ b/src/catalogue/migrations/0019_auto_20201027_1331.py @@ -0,0 +1,23 @@ +# Generated by Django 3.0.4 on 2020-10-27 13:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('catalogue', '0018_auto_20200417_1859'), + ] + + operations = [ + migrations.AddField( + model_name='author', + name='gender', + field=models.CharField(blank=True, max_length=255), + ), + migrations.AddField( + model_name='author', + name='nationality', + field=models.CharField(blank=True, max_length=255), + ), + ] diff --git a/src/catalogue/migrations/0020_auto_20201027_1416.py b/src/catalogue/migrations/0020_auto_20201027_1416.py new file mode 100644 index 00000000..274c4797 --- /dev/null +++ b/src/catalogue/migrations/0020_auto_20201027_1416.py @@ -0,0 +1,64 @@ +# Generated by Django 3.0.4 on 2020-10-27 14:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('catalogue', '0019_auto_20201027_1331'), + ] + + operations = [ + migrations.CreateModel( + name='Epoch', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('wikidata', models.CharField(blank=True, help_text='If you have a Wikidata ID, like "Q1337", enter it and save.', max_length=255)), + ('name', models.CharField(max_length=255)), + ('slug', models.SlugField(max_length=255, unique=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Genre', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('wikidata', models.CharField(blank=True, help_text='If you have a Wikidata ID, like "Q1337", enter it and save.', max_length=255)), + ('name', models.CharField(max_length=255)), + ('slug', models.SlugField(max_length=255, unique=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Kind', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('wikidata', models.CharField(blank=True, help_text='If you have a Wikidata ID, like "Q1337", enter it and save.', max_length=255)), + ('name', models.CharField(max_length=255)), + ('slug', models.SlugField(max_length=255, unique=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='book', + name='epochs', + field=models.ManyToManyField(blank=True, to='catalogue.Epoch'), + ), + migrations.AddField( + model_name='book', + name='genres', + field=models.ManyToManyField(blank=True, to='catalogue.Genre'), + ), + migrations.AddField( + model_name='book', + name='kinds', + field=models.ManyToManyField(blank=True, to='catalogue.Kind'), + ), + ] diff --git a/src/catalogue/migrations/0021_auto_20201027_1422.py b/src/catalogue/migrations/0021_auto_20201027_1422.py new file mode 100644 index 00000000..0f77d780 --- /dev/null +++ b/src/catalogue/migrations/0021_auto_20201027_1422.py @@ -0,0 +1,37 @@ +# Generated by Django 3.0.4 on 2020-10-27 14:22 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('catalogue', '0020_auto_20201027_1416'), + ] + + operations = [ + migrations.AlterModelOptions( + name='author', + options={'ordering': ('last_name', 'first_name', 'year_of_death'), 'verbose_name': 'author', 'verbose_name_plural': 'authors'}, + ), + migrations.AlterModelOptions( + name='book', + options={'ordering': ('title',), 'verbose_name': 'book', 'verbose_name_plural': 'books'}, + ), + migrations.AlterModelOptions( + name='collection', + options={'verbose_name': 'collection', 'verbose_name_plural': 'collections'}, + ), + migrations.AlterModelOptions( + name='epoch', + options={'verbose_name': 'author', 'verbose_name_plural': 'authors'}, + ), + migrations.AlterModelOptions( + name='genre', + options={'verbose_name': 'genre', 'verbose_name_plural': 'genres'}, + ), + migrations.AlterModelOptions( + name='kind', + options={'verbose_name': 'kind', 'verbose_name_plural': 'kinds'}, + ), + ] diff --git a/src/catalogue/migrations/0022_auto_20201027_1424.py b/src/catalogue/migrations/0022_auto_20201027_1424.py new file mode 100644 index 00000000..434e7451 --- /dev/null +++ b/src/catalogue/migrations/0022_auto_20201027_1424.py @@ -0,0 +1,17 @@ +# Generated by Django 3.0.4 on 2020-10-27 14:24 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('catalogue', '0021_auto_20201027_1422'), + ] + + operations = [ + migrations.AlterModelOptions( + name='epoch', + options={'verbose_name': 'epoch', 'verbose_name_plural': 'epochs'}, + ), + ] diff --git a/src/catalogue/models.py b/src/catalogue/models.py index 0863c1e3..682e97bc 100644 --- a/src/catalogue/models.py +++ b/src/catalogue/models.py @@ -2,6 +2,7 @@ from django.apps import apps from django.db import models from django.urls import reverse from django.utils.translation import gettext_lazy as _ +from wikidata.client import Client from .constants import WIKIDATA from .utils import UnrelatedManager from .wikidata import WikidataMixin @@ -15,6 +16,8 @@ class Author(WikidataMixin, models.Model): name_de = models.CharField(max_length=255, blank=True) name_lt = models.CharField(max_length=255, blank=True) + gender = models.CharField(max_length=255, blank=True) + nationality = models.CharField(max_length=255, blank=True) year_of_death = models.SmallIntegerField(null=True, blank=True) status = models.PositiveSmallIntegerField( null=True, @@ -40,12 +43,15 @@ class Author(WikidataMixin, models.Model): collections = models.ManyToManyField("Collection", blank=True) class Meta: + verbose_name = _('author') + verbose_name_plural = _('authors') ordering = ("last_name", "first_name", "year_of_death") class Wikidata: first_name = WIKIDATA.GIVEN_NAME last_name = WIKIDATA.LAST_NAME year_of_death = WIKIDATA.DATE_OF_DEATH + gender = WIKIDATA.GENDER notes = "description" def __str__(self): @@ -63,6 +69,33 @@ class Author(WikidataMixin, models.Model): else: return None + +class Category(WikidataMixin, models.Model): + name = models.CharField(max_length=255) + slug = models.SlugField(max_length=255, unique=True) + + class Meta: + abstract = True + + +class Epoch(Category): + class Meta: + verbose_name = _('epoch') + verbose_name_plural = _('epochs') + + +class Genre(Category): + class Meta: + verbose_name = _('genre') + verbose_name_plural = _('genres') + + +class Kind(Category): + class Meta: + verbose_name = _('kind') + verbose_name_plural = _('kinds') + + class Book(WikidataMixin, models.Model): slug = models.SlugField(max_length=255, blank=True, null=True, unique=True) authors = models.ManyToManyField(Author, blank=True) @@ -72,6 +105,9 @@ class Book(WikidataMixin, models.Model): related_query_name="translated_book", blank=True, ) + epochs = models.ManyToManyField(Epoch, blank=True) + kinds = models.ManyToManyField(Kind, blank=True) + genres = models.ManyToManyField(Genre, blank=True) title = models.CharField(max_length=255, blank=True) language = models.CharField(max_length=255, blank=True) based_on = models.ForeignKey( @@ -91,6 +127,8 @@ class Book(WikidataMixin, models.Model): class Meta: ordering = ("title",) + verbose_name = _('book') + verbose_name_plural = _('books') class Wikidata: authors = WIKIDATA.AUTHOR @@ -128,5 +166,10 @@ class Collection(models.Model): name = models.CharField(max_length=255) slug = models.SlugField(max_length=255, unique=True) + class Meta: + verbose_name = _('collection') + verbose_name_plural = _('collections') + def __str__(self): return self.name + -- 2.20.1 From 0dedc63c56d5230c48c9abadbdbba18a099a5fa1 Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Tue, 27 Oct 2020 14:32:24 +0100 Subject: [PATCH 14/16] Fixes #4124: export cataloge books to csv. --- requirements/requirements.txt | 2 +- src/catalogue/admin.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 313d092d..6f5cf03a 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -14,7 +14,7 @@ librarian==1.8.1 ## Django Django==3.0.4 -fnpdjango==0.4.3 +fnpdjango==0.4.5 django-pipeline==1.7.0 # + django-cas-ng==4.1.1 # + sorl-thumbnail==12.6.3 # + diff --git a/src/catalogue/admin.py b/src/catalogue/admin.py index 608d625d..90759b0f 100644 --- a/src/catalogue/admin.py +++ b/src/catalogue/admin.py @@ -3,6 +3,7 @@ # from django.contrib import admin from django.utils.translation import gettext_lazy as _ +from fnpdjango.actions import export_as_csv_action from . import models from .wikidata import WikidataAdminMixin @@ -43,6 +44,7 @@ class BookAdmin(WikidataAdminMixin, admin.ModelAdmin): prepopulated_fields = {"slug": ("title",)} list_filter = ["language", "pd_year", "collections"] readonly_fields = ["wikidata_link"] + actions = [export_as_csv_action()] fieldsets = [ (None, {"fields": [("wikidata", "wikidata_link")]}), ( -- 2.20.1 From 44020b80d6652124efe5827ff1d3444f22c81cc6 Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Tue, 27 Oct 2020 15:23:49 +0100 Subject: [PATCH 15/16] Add work rates. --- src/catalogue/admin.py | 17 ++++- .../migrations/0023_workrate_worktype.py | 36 +++++++++++ .../migrations/0024_workrate_rate.py | 19 ++++++ .../migrations/0025_auto_20201027_1450.py | 28 +++++++++ .../migrations/0026_auto_20201027_1459.py | 24 +++++++ .../migrations/0027_auto_20201027_1501.py | 43 +++++++++++++ src/catalogue/models.py | 62 +++++++++++++++++++ 7 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 src/catalogue/migrations/0023_workrate_worktype.py create mode 100644 src/catalogue/migrations/0024_workrate_rate.py create mode 100644 src/catalogue/migrations/0025_auto_20201027_1450.py create mode 100644 src/catalogue/migrations/0026_auto_20201027_1459.py create mode 100644 src/catalogue/migrations/0027_auto_20201027_1501.py diff --git a/src/catalogue/admin.py b/src/catalogue/admin.py index 90759b0f..35345216 100644 --- a/src/catalogue/admin.py +++ b/src/catalogue/admin.py @@ -43,7 +43,7 @@ class BookAdmin(WikidataAdminMixin, admin.ModelAdmin): autocomplete_fields = ["authors", "translators", "based_on", "collections", "epochs", "genres", "kinds"] prepopulated_fields = {"slug": ("title",)} list_filter = ["language", "pd_year", "collections"] - readonly_fields = ["wikidata_link"] + readonly_fields = ["wikidata_link", "estimated_costs"] actions = [export_as_csv_action()] fieldsets = [ (None, {"fields": [("wikidata", "wikidata_link")]}), @@ -80,6 +80,8 @@ class BookAdmin(WikidataAdminMixin, admin.ModelAdmin): "priority", "collections", "notes", + ("estimated_chars", "estimated_verses", "estimate_source"), + "estimated_costs", ] }, ), @@ -122,3 +124,16 @@ class CategoryAdmin(admin.ModelAdmin): admin.site.register(models.Epoch, CategoryAdmin) admin.site.register(models.Genre, CategoryAdmin) admin.site.register(models.Kind, CategoryAdmin) + + + +class WorkRateInline(admin.TabularInline): + model = models.WorkRate + autocomplete_fields = ['kinds', 'genres', 'epochs', 'collections'] + + +class WorkTypeAdmin(admin.ModelAdmin): + inlines = [WorkRateInline] + +admin.site.register(models.WorkType, WorkTypeAdmin) + diff --git a/src/catalogue/migrations/0023_workrate_worktype.py b/src/catalogue/migrations/0023_workrate_worktype.py new file mode 100644 index 00000000..88556961 --- /dev/null +++ b/src/catalogue/migrations/0023_workrate_worktype.py @@ -0,0 +1,36 @@ +# Generated by Django 3.0.4 on 2020-10-27 14:37 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('catalogue', '0022_auto_20201027_1424'), + ] + + operations = [ + migrations.CreateModel( + name='WorkType', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ], + ), + migrations.CreateModel( + name='WorkRate', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('priority', models.IntegerField(default=1)), + ('collections', models.ManyToManyField(to='catalogue.Collection')), + ('epochs', models.ManyToManyField(to='catalogue.Epoch')), + ('genres', models.ManyToManyField(to='catalogue.Genre')), + ('kinds', models.ManyToManyField(to='catalogue.Kind')), + ('work_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='catalogue.WorkType')), + ], + options={ + 'ordering': ('priority',), + }, + ), + ] diff --git a/src/catalogue/migrations/0024_workrate_rate.py b/src/catalogue/migrations/0024_workrate_rate.py new file mode 100644 index 00000000..7c1fd829 --- /dev/null +++ b/src/catalogue/migrations/0024_workrate_rate.py @@ -0,0 +1,19 @@ +# Generated by Django 3.0.4 on 2020-10-27 14:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('catalogue', '0023_workrate_worktype'), + ] + + operations = [ + migrations.AddField( + model_name='workrate', + name='rate', + field=models.DecimalField(decimal_places=2, default=0, max_digits=6), + preserve_default=False, + ), + ] diff --git a/src/catalogue/migrations/0025_auto_20201027_1450.py b/src/catalogue/migrations/0025_auto_20201027_1450.py new file mode 100644 index 00000000..d9ce0873 --- /dev/null +++ b/src/catalogue/migrations/0025_auto_20201027_1450.py @@ -0,0 +1,28 @@ +# Generated by Django 3.0.4 on 2020-10-27 14:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('catalogue', '0024_workrate_rate'), + ] + + operations = [ + migrations.AddField( + model_name='book', + name='estimate_source', + field=models.CharField(blank=True, max_length=2048), + ), + migrations.AddField( + model_name='book', + name='estimated_chars', + field=models.IntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='book', + name='estimated_verses', + field=models.IntegerField(blank=True, null=True), + ), + ] diff --git a/src/catalogue/migrations/0026_auto_20201027_1459.py b/src/catalogue/migrations/0026_auto_20201027_1459.py new file mode 100644 index 00000000..28a375cd --- /dev/null +++ b/src/catalogue/migrations/0026_auto_20201027_1459.py @@ -0,0 +1,24 @@ +# Generated by Django 3.0.4 on 2020-10-27 14:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('catalogue', '0025_auto_20201027_1450'), + ] + + operations = [ + migrations.RenameField( + model_name='workrate', + old_name='rate', + new_name='per_normpage', + ), + migrations.AddField( + model_name='workrate', + name='per_verse', + field=models.DecimalField(decimal_places=2, default=0, max_digits=6), + preserve_default=False, + ), + ] diff --git a/src/catalogue/migrations/0027_auto_20201027_1501.py b/src/catalogue/migrations/0027_auto_20201027_1501.py new file mode 100644 index 00000000..c5c8ca8a --- /dev/null +++ b/src/catalogue/migrations/0027_auto_20201027_1501.py @@ -0,0 +1,43 @@ +# Generated by Django 3.0.4 on 2020-10-27 15:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('catalogue', '0026_auto_20201027_1459'), + ] + + operations = [ + migrations.AlterField( + model_name='workrate', + name='collections', + field=models.ManyToManyField(blank=True, to='catalogue.Collection'), + ), + migrations.AlterField( + model_name='workrate', + name='epochs', + field=models.ManyToManyField(blank=True, to='catalogue.Epoch'), + ), + migrations.AlterField( + model_name='workrate', + name='genres', + field=models.ManyToManyField(blank=True, to='catalogue.Genre'), + ), + migrations.AlterField( + model_name='workrate', + name='kinds', + field=models.ManyToManyField(blank=True, to='catalogue.Kind'), + ), + migrations.AlterField( + model_name='workrate', + name='per_normpage', + field=models.DecimalField(blank=True, decimal_places=2, max_digits=6, null=True), + ), + migrations.AlterField( + model_name='workrate', + name='per_verse', + field=models.DecimalField(blank=True, decimal_places=2, max_digits=6, null=True), + ), + ] diff --git a/src/catalogue/models.py b/src/catalogue/models.py index 682e97bc..bb738a26 100644 --- a/src/catalogue/models.py +++ b/src/catalogue/models.py @@ -1,3 +1,4 @@ +import decimal from django.apps import apps from django.db import models from django.urls import reverse @@ -77,6 +78,8 @@ class Category(WikidataMixin, models.Model): class Meta: abstract = True + def __str__(self): + return self.name class Epoch(Category): class Meta: @@ -123,6 +126,10 @@ class Book(WikidataMixin, models.Model): gazeta_link = models.CharField(max_length=255, blank=True) collections = models.ManyToManyField("Collection", blank=True) + estimated_chars = models.IntegerField(null=True, blank=True) + estimated_verses = models.IntegerField(null=True, blank=True) + estimate_source = models.CharField(max_length=2048, blank=True) + objects = UnrelatedManager() class Meta: @@ -161,6 +168,15 @@ class Book(WikidataMixin, models.Model): DBook = apps.get_model("documents", "Book") return DBook.objects.filter(dc_slug=self.slug) + def estimated_costs(self): + return "\n".join( + "{}: {} zł".format( + work_type.name, + work_type.calculate(self) or '—' + ) + for work_type in WorkType.objects.all() + ) + class Collection(models.Model): name = models.CharField(max_length=255) @@ -173,3 +189,49 @@ class Collection(models.Model): def __str__(self): return self.name + +class WorkType(models.Model): + name = models.CharField(max_length=255) + + def get_rate_for(self, book): + for workrate in self.workrate_set.all(): + if workrate.matches(book): + return workrate + + def calculate(self, book): + workrate = self.get_rate_for(book) + if workrate is not None: + return workrate.calculate(book) + + + +class WorkRate(models.Model): + priority = models.IntegerField(default=1) + per_normpage = models.DecimalField(decimal_places=2, max_digits=6, null=True, blank=True) + per_verse = models.DecimalField(decimal_places=2, max_digits=6, null=True, blank=True) + work_type = models.ForeignKey(WorkType, models.CASCADE) + epochs = models.ManyToManyField(Epoch, blank=True) + kinds = models.ManyToManyField(Kind, blank=True) + genres = models.ManyToManyField(Genre, blank=True) + collections = models.ManyToManyField(Collection, blank=True) + + class Meta: + ordering = ('priority',) + + def matches(self, book): + for category in 'epochs', 'kinds', 'genres', 'collections': + oneof = getattr(self, category).all() + if oneof: + if not set(oneof).intersection( + getattr(book, category).all()): + return False + return True + + def calculate(self, book): + if self.per_verse: + if book.estimated_verses: + return book.estimated_verses * self.per_verse + elif self.per_normpage: + if book.estimated_chars: + return (decimal.Decimal(book.estimated_chars) / 1800 * self.per_normpage).quantize(decimal.Decimal('1.00'), rounding=decimal.ROUND_HALF_UP) + -- 2.20.1 From 2421e92f4731766547e99bf3ccae992f7b6411ff Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Tue, 27 Oct 2020 15:26:28 +0100 Subject: [PATCH 16/16] Minor display fix. --- src/redakcja/static/css/html.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/redakcja/static/css/html.css b/src/redakcja/static/css/html.css index 85d48f00..98e41cd2 100644 --- a/src/redakcja/static/css/html.css +++ b/src/redakcja/static/css/html.css @@ -87,6 +87,7 @@ font-size: 1em; margin: 1.5em 0 0; line-height: 1.5em; + font-weight: bold; } .htmlview p { -- 2.20.1