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
 
-librarian==2.2
+librarian==2.3.1
 
 ## 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"
-"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"
@@ -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"
-"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."
@@ -73,44 +73,45 @@ msgstr "publiczna"
 msgid "scan gallery name"
 msgstr "nazwa galerii skanów"
 
-#: documents/models/book.py:34
+#: documents/models/book.py:33
 msgid "parent"
 msgstr "rodzic"
 
-#: documents/models/book.py:35
+#: documents/models/book.py:34
 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/templates/documents/book_detail.html:145
 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"
 
-#: documents/models/book.py:263
+#: documents/models/book.py:271
 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."
 
-#: 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"
 
-#: 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."
 
-#: 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"
 
-#: 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"
 
@@ -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:"
 
+#: 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ę"
@@ -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."
 
-#: documents/views.py:621
+#: documents/views.py:629
 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 io import BytesIO
 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, 
-            parse_dublincore=True, strict=False):
+                   parse_dublincore=True, strict=False, librarian2=False):
         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(
-                self.materialize(publishable=publishable, changes=changes).encode('utf-8'),
-                provider=RedakcjaDocProvider(publishable=publishable),
+                xml,
+                provider=provider,
                 parse_dublincore=parse_dublincore,
                 strict=strict)
 
index be17ec1..85da536 100644 (file)
@@ -129,4 +129,34 @@ Okładka w rozmiarze
 
 </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 %}
index bfcd013..38e69c1 100644 (file)
@@ -288,7 +288,9 @@ def book_epub(request, slug):
     # 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)
@@ -304,7 +306,7 @@ def book_mobi(request, slug):
     # 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)
@@ -345,8 +347,14 @@ def book(request, slug):
     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,
+        "doc": doc,
         "publishable": publishable,
         "publishable_error": publish_error,
         "form": form,
index 4259b31..46b80da 100644 (file)
@@ -93,6 +93,7 @@ INSTALLED_APPS = (
 
     'redakcja.api',
     'catalogue',
+    'depot',
     'documents',
     'cover',
     'dvcs',