project sell info and initial synchro support
authorRadek Czajka <rczajka@rczajka.pl>
Wed, 3 Apr 2024 08:47:05 +0000 (10:47 +0200)
committerRadek Czajka <rczajka@rczajka.pl>
Wed, 3 Apr 2024 08:47:05 +0000 (10:47 +0200)
src/cover/templates/cover/image_detail.html
src/documents/migrations/0016_project_can_sell_project_private_notes.py [new file with mode: 0644]
src/documents/models/project.py
src/documents/templates/documents/book_detail.html
src/documents/templates/documents/synchro.html [new file with mode: 0644]
src/documents/urls.py
src/documents/views.py

index c357ff0..6a67eec 100644 (file)
           {% for book in object.book_set.all %}
             <li>
               <a href="{{ book.get_absolute_url }}" title="{{ book }}">
-                <img src="{{ book.cover.url }}?{{ object.etag }}" alt="{{ book }}">
+                {% if book.cover %}
+                  <img src="{{ book.cover.url }}?{{ object.etag }}" alt="{{ book }}">
+                {% else %}
+                  {{ book }}
+                {% endif %}
               </a>
             </li>
           {% endfor %}
diff --git a/src/documents/migrations/0016_project_can_sell_project_private_notes.py b/src/documents/migrations/0016_project_can_sell_project_private_notes.py
new file mode 100644 (file)
index 0000000..edb298c
--- /dev/null
@@ -0,0 +1,23 @@
+# Generated by Django 4.1.9 on 2024-04-03 10:26
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("documents", "0015_project_logo_alt"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="project",
+            name="can_sell",
+            field=models.BooleanField(default=True, verbose_name="Do sprzedaży"),
+        ),
+        migrations.AddField(
+            model_name="project",
+            name="private_notes",
+            field=models.TextField(blank=True, verbose_name="Prywatne notatki"),
+        ),
+    ]
index 7e9cf30..9efee5a 100644 (file)
@@ -13,6 +13,8 @@ class Project(models.Model):
     logo = models.FileField(upload_to='projects', blank=True)
     logo_mono = models.FileField(upload_to='logo_mono', help_text='white on transparent', blank=True)
     logo_alt = models.CharField(max_length=255, blank=True)
+    private_notes = models.TextField(blank=True, verbose_name='Prywatne notatki')
+    can_sell = models.BooleanField(default=True, verbose_name='Do sprzedaży')
 
     class Meta:
         app_label = 'documents'
index 2d58734..b9dd6ab 100644 (file)
   <div class='card mt-4'>
 
     <div class="card-header">
-      <h2>{% trans "Publication" %}</h2>
+      <h2>
+        {% trans "Publication" %}
+        |
+        {% if book.project %}
+          {% if book.project.can_sell %}
+            <span title="Książka do sprzedaży">💶</span>
+          {% else %}
+            <span title="Książka nie do sprzedaży">🙅</span>
+          {% endif %}
+        {% else %}
+          <span title="Brak informacji o możliwości sprzedaży">❓</span>
+        {% endif %}
+        {% if has_audio %}
+          | 🎧
+          {% if can_sell_audio %}
+            <span title="Audiobook do sprzedaży">💶</span>
+          {% else %}
+            <span title="Audiobook nie do sprzedaży">🙅</span>
+          {% endif %}
+        {% endif %}
+      </h2>
+
     </div>
     <div class="card-body">
       <div class="row">
               <a href="{% url 'documents_book_mobi' book.slug %}" rel="nofollow">{% trans "MOBI version" %}</a><br/>
             </p>
 
+            <p><a href="./synchro">Sprawdź synchronizację</a></p>
+
             {% isbn_status book %}
 
             {% if user.is_authenticated %}
diff --git a/src/documents/templates/documents/synchro.html b/src/documents/templates/documents/synchro.html
new file mode 100644 (file)
index 0000000..bc44673
--- /dev/null
@@ -0,0 +1,49 @@
+{% extends "documents/base.html" %}
+
+
+{% block content %}
+
+  <div class="row mb-4">
+    <div class="col-4">
+    </div>
+    <div class="col-8">
+      {% if not length_ok %}
+        <div class="alert alert-warning">
+          Liczba znalezionych części dokumentu nie zgadza się z liczbą części audiobooka.
+          Konieczna jest korekta za pomocą atrybutów <code>forcesplit</code> i <code>nosplit</code>.
+        </div>
+      {% else %}
+        <form method="post" action="">
+          {% csrf_token %}
+          <button class="btn btn-primary">
+            Zaplanuj synchronizację
+          </button>
+        </form>
+      {% endif %}
+
+    </div>
+  </div>
+
+  <table class="table">
+    <thead>
+      <tr>
+        <th>Nagłówek cięcia</th>
+        <th>Audiobook</th>
+      </tr>
+    </thead>
+    {% for h, m in table %}
+      <tr>
+        <td>
+          {% if h %}
+            <a target="_blank" href="{% url 'wiki_editor' book.slug %}#CodeMirrorPerspective">
+              {{ h.0 }} (linia {{ h.2 }})
+            </a>
+          {% else %}
+            —
+          {% endif %}
+        </td>
+        <td>{{ m|default_if_none:'—' }}</td>
+      </tr>
+    {% endfor %}
+  </table>
+{% endblock %}
index d9114cc..e7b68c2 100644 (file)
@@ -45,6 +45,7 @@ urlpatterns = [
     path('book/<slug:slug>/mobi', views.book_mobi, name="documents_book_mobi"),
     path('book/<slug:slug>/pdf', views.book_pdf, name="documents_book_pdf"),
     path('book/<slug:slug>/pdf-mobile', views.book_pdf, kwargs={'mobile': True}, name="documents_book_pdf_mobile"),
+    path('book/<slug:slug>/synchro', views.synchro, name="documents_book_synchro"),
 
     path('chunk_add/<slug:slug>/<slug:chunk>/',
         views.chunk_add, name="documents_chunk_add"),
index 905e885..8f40303 100644 (file)
@@ -2,7 +2,9 @@
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from collections import defaultdict
+from copy import deepcopy
 from datetime import datetime, date, timedelta
+from itertools import zip_longest
 import logging
 import os
 from urllib.parse import quote_plus, unquote, urlsplit, urlunsplit
@@ -22,8 +24,11 @@ from django.utils.encoding import iri_to_uri
 from django.utils.translation import gettext_lazy as _
 from django.views.decorators.http import require_POST
 from django_cas_ng.decorators import user_passes_test
+import requests
 
 from librarian import epubcheck
+from librarian.html import raw_printable_text
+
 from apiclient import api_call, NotAuthorizedError
 from . import forms
 from . import helpers
@@ -404,6 +409,14 @@ def book(request, slug):
         except:
             pass
 
+    if book.catalogue_book_id:
+        audio_items = requests.get(f'https://audio.wolnelektury.pl/archive/book/{book.catalogue_book_id}.json').json()['items']
+        has_audio = bool(audio_items)
+        can_sell_audio = has_audio and all(x['project']['can_sell'] for x in audio_items)
+    else:
+        has_audio = None
+        can_sell_audio = None
+        
     return render(request, "documents/book_detail.html", {
         "book": book,
         "doc": doc,
@@ -413,6 +426,8 @@ def book(request, slug):
         "form": form,
         "publish_options_form": publish_options_form,
         "editable": editable,
+        "has_audio": has_audio,
+        "can_sell_audio": can_sell_audio,
     })
 
 
@@ -747,3 +762,81 @@ def mark_final(request):
 
 def mark_final_completed(request):
     return render(request, 'documents/mark_final_completed.html')
+
+
+def synchro(request, slug):
+    book = get_object_or_404(Book, slug=slug)
+    if not book.accessible(request):
+        return HttpResponseForbidden("Not authorized.")
+
+    document = book.wldocument(librarian2=True)
+    slug = document.meta.url.slug
+    print(f'https://audio.wolnelektury.pl/archive/book/{slug}.json')
+    error = None
+    try:
+        items = requests.get(f'https://audio.wolnelektury.pl/archive/book/{slug}.json').json()['items']
+    except:
+        error = 'Błąd połączenia z repozytorium audio.'
+        items = []
+    else:
+        mp3 = [
+            item['part'] for item in items
+        ]
+
+    split_on = (
+        'naglowek_rozdzial',
+        'naglowek_scena',
+        )
+    
+    if split_on:
+        documents = []
+        headers = [('Początek', 0, 0)]
+        present = True
+        n = 0
+        while present:
+            present = False
+            n += 1
+            newdoc = deepcopy(document)
+            newdoc.tree.getroot().document = newdoc
+            
+            master = newdoc.tree.getroot()[-1]
+            i = 0
+            for item in list(master):
+                #chunkno, sourceline = 0, self.sourceline
+                #if builder.splits:
+                #    chunkno, sourceline = len(builder.splits), sourceline - builder.splits[-1]
+
+                if 'forcesplit' in item.attrib or (item.tag in split_on and 'nosplit' not in item.attrib):
+                    # TODO: clear
+                    i += 1
+                    if n > 1 and i == n:
+                        headers.append((
+                            raw_printable_text(item),
+                            0,
+                            item.sourceline,
+                        ))
+                if i != n and not (n == 1 and not i):
+                    master.remove(item)
+                else:
+                    present = True
+                if present:
+                    documents.append(newdoc)
+    else:
+        documents = [document]
+        headers = [(
+            document.meta.title, 0 ,0
+        )]
+
+    length_ok = len(headers) == len(mp3)
+    table = zip_longest(headers, mp3)
+
+    
+    return render(request, 'documents/synchro.html', {
+        'book': book,
+        'documents': documents,
+        'headers': headers,
+        'mp3': mp3,
+        'length_ok': length_ok,
+        'table': table,
+        'error': error,
+    })