Add depot for building packages.
authorRadek Czajka <rczajka@rczajka.pl>
Mon, 20 Dec 2021 11:52:55 +0000 (12:52 +0100)
committerRadek Czajka <rczajka@rczajka.pl>
Mon, 20 Dec 2021 11:52:55 +0000 (12:52 +0100)
15 files changed:
requirements/requirements.txt
src/depot/__init__.py [new file with mode: 0644]
src/depot/admin.py [new file with mode: 0644]
src/depot/apps.py [new file with mode: 0644]
src/depot/migrations/0001_initial.py [new file with mode: 0644]
src/depot/migrations/__init__.py [new file with mode: 0644]
src/depot/models.py [new file with mode: 0644]
src/depot/tests.py [new file with mode: 0644]
src/depot/views.py [new file with mode: 0644]
src/documents/locale/pl/LC_MESSAGES/django.mo
src/documents/locale/pl/LC_MESSAGES/django.po
src/documents/models/book.py
src/documents/templates/documents/book_detail.html
src/documents/views.py
src/redakcja/settings/__init__.py

index db084ea..56201a6 100644 (file)
@@ -10,7 +10,7 @@ python-slugify
 python-docx==0.8.10
 Wikidata==0.6.1
 
 python-docx==0.8.10
 Wikidata==0.6.1
 
-librarian==2.2
+librarian==2.3.1
 
 ## Django
 Django==3.1.13
 
 ## Django
 Django==3.1.13
diff --git a/src/depot/__init__.py b/src/depot/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/depot/admin.py b/src/depot/admin.py
new file mode 100644 (file)
index 0000000..35f721b
--- /dev/null
@@ -0,0 +1,10 @@
+from django.contrib import admin
+from . import models
+
+
+@admin.register(models.Package)
+class PackageAdmin(admin.ModelAdmin):
+    filter_horizontal = ['books']
+    pass
+    
+# Register your models here.
diff --git a/src/depot/apps.py b/src/depot/apps.py
new file mode 100644 (file)
index 0000000..1e16d99
--- /dev/null
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class DepotConfig(AppConfig):
+    name = 'depot'
diff --git a/src/depot/migrations/0001_initial.py b/src/depot/migrations/0001_initial.py
new file mode 100644 (file)
index 0000000..9fbf24e
--- /dev/null
@@ -0,0 +1,29 @@
+# Generated by Django 3.1.13 on 2021-12-17 15:36
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        ('documents', '0006_auto_20210706_0130'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Package',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('created_at', models.DateTimeField(auto_now_add=True)),
+                ('placed_at', models.DateTimeField(blank=True, null=True)),
+                ('finished_at', models.DateTimeField(blank=True, null=True)),
+                ('definition_json', models.TextField(blank=True)),
+                ('status_json', models.TextField(blank=True)),
+                ('logo', models.FileField(blank=True, upload_to='depot/logo')),
+                ('file', models.FileField(blank=True, upload_to='depot/package/')),
+                ('books', models.ManyToManyField(to='documents.Book')),
+            ],
+        ),
+    ]
diff --git a/src/depot/migrations/__init__.py b/src/depot/migrations/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/depot/models.py b/src/depot/models.py
new file mode 100644 (file)
index 0000000..12bd9c2
--- /dev/null
@@ -0,0 +1,111 @@
+import json
+import os
+import tempfile
+import zipfile
+from datetime import datetime
+from django.db import models
+from librarian.cover import make_cover
+from librarian.builders import EpubBuilder, MobiBuilder
+
+
+class Package(models.Model):
+    created_at = models.DateTimeField(auto_now_add=True)
+    placed_at = models.DateTimeField(null=True, blank=True)
+    finished_at = models.DateTimeField(null=True, blank=True)
+    definition_json = models.TextField(blank=True)
+    books = models.ManyToManyField('documents.Book')
+    status_json = models.TextField(blank=True)
+    logo = models.FileField(blank=True, upload_to='depot/logo')
+    file = models.FileField(blank=True, upload_to='depot/package/')
+
+    def save(self, *args, **kwargs):
+        try:
+            self.set_status(self.get_status())
+        except:
+            pass
+        
+        try:
+            self.set_definition(self.get_definition())
+        except:
+            pass
+
+        super().save(*args, **kwargs)
+    
+    def get_status(self):
+        return json.loads(self.status_json)
+
+    def set_status(self, status):
+        self.status_json = json.dumps(status, indent=4)
+
+    def get_definition(self):
+        return json.loads(self.definition_json)
+
+    def set_definition(self, definition):
+        self.definition_json = json.dumps(definition, indent=4)
+
+    def build(self):
+        f = tempfile.NamedTemporaryFile(prefix='depot-', suffix='.zip', mode='wb', delete=False)
+        with zipfile.ZipFile(f, 'w') as z:
+            for book in self.books.all():
+                self.build_for(book, z)
+        f.close()
+        with open(f.name, 'rb') as ff:
+            self.file.save('package-{}.zip'.format(datetime.now().isoformat(timespec='seconds')), ff)
+        os.unlink(f.name)
+
+    def build_for(self, book, z):
+        wldoc2 = book.wldocument(librarian2=True)
+        slug = wldoc2.meta.url.slug
+        for item in self.get_definition():
+            wldoc = book.wldocument()
+            wldoc2 = book.wldocument(librarian2=True)
+            base_url = 'file://' + book.gallery_path() + '/'
+
+            ext = item['type']
+
+            if item['type'] == 'cover':
+                kwargs = {}
+                if self.logo:
+                    kwargs['cover_logo'] = self.logo.path
+                for k in 'format', 'width', 'height', 'cover_class':
+                    if k in item:
+                        kwargs[k] = item[k]
+                cover = make_cover(wldoc.book_info, **kwargs)
+                output = cover.output_file()
+                ext = cover.ext()
+
+            elif item['type'] == 'pdf':
+                cover_kwargs = {}
+                if 'cover_class' in item:
+                    cover_kwargs['cover_class'] = item['cover_class']
+                if self.logo:
+                    cover_kwargs['cover_logo'] = self.logo.path
+                cover = lambda *args, **kwargs: make_cover(*args, **kwargs, **cover_kwargs)
+                output = wldoc.as_pdf(cover=cover, base_url=base_url)
+
+            elif item['type'] == 'epub':
+                cover_kwargs = {}
+                if 'cover_class' in item:
+                    cover_kwargs['cover_class'] = item['cover_class']
+                if self.logo:
+                    cover_kwargs['cover_logo'] = self.logo.path
+                cover = lambda *args, **kwargs: make_cover(*args, **kwargs, **cover_kwargs)
+
+                output = EpubBuilder(
+                    cover=cover,
+                    base_url=base_url,
+#                    fundraising=[]
+                ).build(wldoc2)
+
+            elif item['type'] == 'mobi':
+                output = MobiBuilder(
+                    cover=cover,
+                    base_url=base_url,
+                ).build(wldoc2)
+
+            fname = f'{slug}/{slug}.{ext}'
+
+            z.writestr(
+                fname,
+                output.get_bytes()
+            )
diff --git a/src/depot/tests.py b/src/depot/tests.py
new file mode 100644 (file)
index 0000000..7ce503c
--- /dev/null
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/src/depot/views.py b/src/depot/views.py
new file mode 100644 (file)
index 0000000..91ea44a
--- /dev/null
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.
index dde8ac1..453940f 100644 (file)
Binary files a/src/documents/locale/pl/LC_MESSAGES/django.mo and b/src/documents/locale/pl/LC_MESSAGES/django.mo differ
index f1b4912..b9c500f 100644 (file)
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: Platforma Redakcyjna\n"
 "Report-Msgid-Bugs-To: \n"
 msgstr ""
 "Project-Id-Version: Platforma Redakcyjna\n"
 "Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: 2020-04-20 16:54+0200\n"
+"PO-Revision-Date: 2021-12-17 11:37+0100\n"
 "Last-Translator: Radek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>\n"
 "Language-Team: Fundacja Nowoczesna Polska <fundacja@nowoczesnapolska.org."
 "pl>\n"
 "Last-Translator: Radek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>\n"
 "Language-Team: Fundacja Nowoczesna Polska <fundacja@nowoczesnapolska.org."
 "pl>\n"
@@ -17,7 +17,7 @@ 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"
 "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.2.4\n"
+"X-Generator: Poedit 3.0\n"
 
 #: documents/forms.py:41
 msgid "Text file must be UTF-8 encoded."
 
 #: documents/forms.py:41
 msgid "Text file must be UTF-8 encoded."
@@ -73,44 +73,45 @@ msgstr "publiczna"
 msgid "scan gallery name"
 msgstr "nazwa galerii skanów"
 
 msgid "scan gallery name"
 msgstr "nazwa galerii skanów"
 
-#: documents/models/book.py:34
+#: documents/models/book.py:33
 msgid "parent"
 msgstr "rodzic"
 
 msgid "parent"
 msgstr "rodzic"
 
-#: documents/models/book.py:35
+#: documents/models/book.py:34
 msgid "parent number"
 msgstr "numeracja rodzica"
 
 msgid "parent number"
 msgstr "numeracja rodzica"
 
-#: documents/models/book.py:53 documents/models/chunk.py:19
+#: documents/models/book.py:60 documents/models/chunk.py:19
 #: documents/models/publish_log.py:15
 #: documents/models/publish_log.py:15
+#: documents/templates/documents/book_detail.html:145
 msgid "book"
 msgstr "książka"
 
 msgid "book"
 msgstr "książka"
 
-#: documents/models/book.py:54 documents/views.py:619
+#: documents/models/book.py:61 documents/views.py:627
 msgid "books"
 msgstr "książki"
 
 msgid "books"
 msgstr "książki"
 
-#: documents/models/book.py:263
+#: documents/models/book.py:271
 msgid "No chunks in the book."
 msgstr "Książka nie ma części."
 
 msgid "No chunks in the book."
 msgstr "Książka nie ma części."
 
-#: documents/models/book.py:267
+#: documents/models/book.py:275
 msgid "Not all chunks have publishable revisions."
 msgstr "Niektóre części nie są gotowe do publikacji."
 
 msgid "Not all chunks have publishable revisions."
 msgstr "Niektóre części nie są gotowe do publikacji."
 
-#: documents/models/book.py:274 documents/models/image.py:83
+#: documents/models/book.py:282 documents/models/image.py:83
 msgid "Invalid XML"
 msgstr "Nieprawidłowy XML"
 
 msgid "Invalid XML"
 msgstr "Nieprawidłowy XML"
 
-#: documents/models/book.py:276 documents/models/image.py:85
+#: documents/models/book.py:284 documents/models/image.py:85
 msgid "No Dublin Core found."
 msgstr "Brak sekcji Dublin Core."
 
 msgid "No Dublin Core found."
 msgstr "Brak sekcji Dublin Core."
 
-#: documents/models/book.py:278 documents/models/image.py:87
+#: documents/models/book.py:286 documents/models/image.py:87
 msgid "Invalid Dublin Core"
 msgstr "Nieprawidłowy Dublin Core"
 
 msgid "Invalid Dublin Core"
 msgstr "Nieprawidłowy Dublin Core"
 
-#: documents/models/book.py:281 documents/models/image.py:91
+#: documents/models/book.py:289 documents/models/image.py:91
 msgid "rdf:about is not"
 msgstr "rdf:about jest różny od"
 
 msgid "rdf:about is not"
 msgstr "rdf:about jest różny od"
 
@@ -294,6 +295,26 @@ msgstr "Zaloguj się, aby opublikować."
 msgid "This book can't be published yet, because:"
 msgstr "Ta książka nie może jeszcze zostać opublikowana. Powód:"
 
 msgid "This book can't be published yet, because:"
 msgstr "Ta książka nie może jeszcze zostać opublikowana. Powód:"
 
+#: documents/templates/documents/book_detail.html:138
+msgid "Statistics"
+msgstr "Statystyki"
+
+#: documents/templates/documents/book_detail.html:147
+msgid "characters"
+msgstr "znaki"
+
+#: documents/templates/documents/book_detail.html:148
+msgid "words"
+msgstr "słowa"
+
+#: documents/templates/documents/book_detail.html:149
+msgid "characters (with footnotes)"
+msgstr "znaki (z przypisami)"
+
+#: documents/templates/documents/book_detail.html:150
+msgid "words (with footnotes)"
+msgstr "słowa (z przypisami)"
+
 #: documents/templates/documents/book_edit.html:5
 msgid "Edit book"
 msgstr "Edytuj książkę"
 #: documents/templates/documents/book_edit.html:5
 msgid "Edit book"
 msgstr "Edytuj książkę"
@@ -603,7 +624,7 @@ msgstr "Dokument o tym slugu już istnieje w repozytorium."
 msgid "File should be UTF-8 encoded."
 msgstr "Plik powinien mieć kodowanie UTF-8."
 
 msgid "File should be UTF-8 encoded."
 msgstr "Plik powinien mieć kodowanie UTF-8."
 
-#: documents/views.py:621
+#: documents/views.py:629
 msgid "scan gallery"
 msgstr "galeria skanów"
 
 msgid "scan gallery"
 msgstr "galeria skanów"
 
index 4255530..0e696e4 100644 (file)
@@ -17,6 +17,7 @@ from documents.models import BookPublishRecord, ChunkPublishRecord, Project
 from documents.signals import post_publish
 from documents.xml_tools import compile_text, split_xml
 from cover.models import Image
 from documents.signals import post_publish
 from documents.xml_tools import compile_text, split_xml
 from cover.models import Image
+from io import BytesIO
 import os
 import shutil
 import re
 import os
 import shutil
 import re
@@ -407,13 +408,21 @@ class Book(models.Model):
         return compile_text(change.materialize() for change in changes)
 
     def wldocument(self, publishable=True, changes=None, 
         return compile_text(change.materialize() for change in changes)
 
     def wldocument(self, publishable=True, changes=None, 
-            parse_dublincore=True, strict=False):
+                   parse_dublincore=True, strict=False, librarian2=False):
         from documents.ebook_utils import RedakcjaDocProvider
         from librarian.parser import WLDocument
         from documents.ebook_utils import RedakcjaDocProvider
         from librarian.parser import WLDocument
-
+        from librarian.document import WLDocument as WLDocument2
+
+        provider = RedakcjaDocProvider(publishable=publishable),
+        xml = self.materialize(publishable=publishable, changes=changes).encode('utf-8')
+        
+        if librarian2:
+            return WLDocument2(
+                BytesIO(xml),
+                provider=provider)
         return WLDocument.from_bytes(
         return WLDocument.from_bytes(
-                self.materialize(publishable=publishable, changes=changes).encode('utf-8'),
-                provider=RedakcjaDocProvider(publishable=publishable),
+                xml,
+                provider=provider,
                 parse_dublincore=parse_dublincore,
                 strict=strict)
 
                 parse_dublincore=parse_dublincore,
                 strict=strict)
 
index be17ec1..85da536 100644 (file)
@@ -129,4 +129,34 @@ Okładka w rozmiarze
 
 </div>
   </div>
 
 </div>
   </div>
+</div>
+</div>
+
+{% if doc %}
+  <div class="card mt-4">
+    <div class="card-header">
+      <h2>{% trans "Statistics" %}</h2>
+    </div>
+    <div class="card-body">
+      <table class="table">
+        <thead>
+          <tr>
+            <th>
+              {% trans "book" %}
+            </th>
+            <th>{% trans "characters" %}</th>
+            <th>{% trans "words" %}</th>
+            <th>{% trans "characters (with footnotes)" %}</th>
+            <th>{% trans "words (with footnotes)" %}</th>
+          </tr>
+        </thead>
+        <tbody>
+          {% with stats=book.wldocument.get_statistics %}
+            {% include 'documents/book_stats.html' with book=book stats=stats depth=0 %}
+          {% endwith %}
+        </tbody>
+      </table>
+    </div>
+  </div>
+{% endif %}
 {% endblock content %}
 {% endblock content %}
index bfcd013..38e69c1 100644 (file)
@@ -288,7 +288,9 @@ def book_epub(request, slug):
     # TODO: move to celery
     doc = book.wldocument()
     # TODO: error handling
     # TODO: move to celery
     doc = book.wldocument()
     # TODO: error handling
-    epub = doc.as_epub(base_url=request.build_absolute_uri(book.gallery_path())).get_bytes()
+
+    #### Problemas: images in children.
+    epub = doc.as_epub(base_url='file://' + book.gallery_path() + '/').get_bytes()
     response = HttpResponse(content_type='application/epub+zip')
     response['Content-Disposition'] = 'attachment; filename=%s' % book.slug + '.epub'
     response.write(epub)
     response = HttpResponse(content_type='application/epub+zip')
     response['Content-Disposition'] = 'attachment; filename=%s' % book.slug + '.epub'
     response.write(epub)
@@ -304,7 +306,7 @@ def book_mobi(request, slug):
     # TODO: move to celery
     doc = book.wldocument()
     # TODO: error handling
     # TODO: move to celery
     doc = book.wldocument()
     # TODO: error handling
-    mobi = doc.as_mobi(base_url=request.build_absolute_uri(book.gallery_path())).get_bytes()
+    mobi = doc.as_mobi(base_url='file://' + book.gallery_path() + '/').get_bytes()
     response = HttpResponse(content_type='application/x-mobipocket-ebook')
     response['Content-Disposition'] = 'attachment; filename=%s' % book.slug + '.mobi'
     response.write(mobi)
     response = HttpResponse(content_type='application/x-mobipocket-ebook')
     response['Content-Disposition'] = 'attachment; filename=%s' % book.slug + '.mobi'
     response.write(mobi)
@@ -345,8 +347,14 @@ def book(request, slug):
     publish_error = book.publishable_error()
     publishable = publish_error is None
 
     publish_error = book.publishable_error()
     publishable = publish_error is None
 
+    try:
+        doc = book.wldocument()
+    except:
+        doc = None
+    
     return render(request, "documents/book_detail.html", {
         "book": book,
     return render(request, "documents/book_detail.html", {
         "book": book,
+        "doc": doc,
         "publishable": publishable,
         "publishable_error": publish_error,
         "form": form,
         "publishable": publishable,
         "publishable_error": publish_error,
         "form": form,
index 4259b31..46b80da 100644 (file)
@@ -93,6 +93,7 @@ INSTALLED_APPS = (
 
     'redakcja.api',
     'catalogue',
 
     'redakcja.api',
     'catalogue',
+    'depot',
     'documents',
     'cover',
     'dvcs',
     'documents',
     'cover',
     'dvcs',