Change field length.
[redakcja.git] / src / documents / views.py
index 05f8853..97d2ad6 100644 (file)
@@ -2,10 +2,12 @@
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from collections import defaultdict
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from collections import defaultdict
+from copy import deepcopy
 from datetime import datetime, date, timedelta
 from datetime import datetime, date, timedelta
+from itertools import zip_longest
 import logging
 import os
 import logging
 import os
-from urllib.parse import unquote, urlsplit, urlunsplit
+from urllib.parse import quote_plus, unquote, urlsplit, urlunsplit
 
 from django.conf import settings
 from django.contrib import auth
 
 from django.conf import settings
 from django.contrib import auth
@@ -19,17 +21,21 @@ from django.http import Http404, HttpResponse, HttpResponseForbidden
 from django.http.response import HttpResponseRedirect
 from django.shortcuts import get_object_or_404, render
 from django.utils.encoding import iri_to_uri
 from django.http.response import HttpResponseRedirect
 from django.shortcuts import get_object_or_404, render
 from django.utils.encoding import iri_to_uri
-from django.utils.http import urlquote_plus
-from django.utils.translation import ugettext_lazy as _
+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
 from django.views.decorators.http import require_POST
 from django_cas_ng.decorators import user_passes_test
+import requests
 
 
-from apiclient import NotAuthorizedError
+from librarian import epubcheck
+from librarian.html import raw_printable_text
+
+from apiclient import api_call, NotAuthorizedError
 from . import forms
 from . import helpers
 from .helpers import active_tab
 from .models import (Book, Chunk, Image, BookPublishRecord, 
         ChunkPublishRecord, ImagePublishRecord, Project)
 from . import forms
 from . import helpers
 from .helpers import active_tab
 from .models import (Book, Chunk, Image, BookPublishRecord, 
         ChunkPublishRecord, ImagePublishRecord, Project)
+import catalogue.models
 from fileupload.views import UploadView
 
 #
 from fileupload.views import UploadView
 
 #
@@ -66,9 +72,17 @@ def my(request):
         key=lambda x: x[1]['time'], reverse=True)
     for k, v in last_books:
         v['time'] = datetime.fromtimestamp(v['time'])
         key=lambda x: x[1]['time'], reverse=True)
     for k, v in last_books:
         v['time'] = datetime.fromtimestamp(v['time'])
+    try:
+        resp = api_call(request.user, 'username/')
+    except NotAuthorizedError:
+        wllogin = None
+    else:
+        wllogin = resp['username']
+
     return render(request, 'documents/my_page.html', {
         'last_books': last_books,
         "logout_to": '/',
     return render(request, 'documents/my_page.html', {
         'last_books': last_books,
         "logout_to": '/',
+        "wllogin": wllogin,
         })
 
 
         })
 
 
@@ -100,7 +114,7 @@ def activity(request, isodate=None):
 @never_cache
 def logout_then_redirect(request):
     auth.logout(request)
 @never_cache
 def logout_then_redirect(request):
     auth.logout(request)
-    return http.HttpResponseRedirect(urlquote_plus(request.GET.get('next', '/'), safe='/?='))
+    return http.HttpResponseRedirect(quote_plus(request.GET.get('next', '/'), safe='/?='))
 
 
 @permission_required('documents.add_book')
 
 
 @permission_required('documents.add_book')
@@ -224,7 +238,7 @@ def book_xml(request, slug):
 
 @never_cache
 def book_xml_dc(request, slug):
 
 @never_cache
 def book_xml_dc(request, slug):
-    book = get_object_or_404(Book, catalogue_book_id=slug)
+    book = get_object_or_404(Book, dc_slug=slug)
     return serve_xml(request, book, slug)
 
 
     return serve_xml(request, book, slug)
 
 
@@ -263,6 +277,7 @@ def book_html(request, slug):
     return render(request, 'documents/book_text.html', locals())
 
 
     return render(request, 'documents/book_text.html', locals())
 
 
+@login_required
 @never_cache
 def book_pdf(request, slug, mobile=False):
     book = get_object_or_404(Book, slug=slug)
 @never_cache
 def book_pdf(request, slug, mobile=False):
     book = get_object_or_404(Book, slug=slug)
@@ -273,12 +288,13 @@ def book_pdf(request, slug, mobile=False):
     doc = book.wldocument()
     # TODO: error handling
     customizations = ['26pt', 'nothemes', 'nomargins', 'notoc'] if mobile else None
     doc = book.wldocument()
     # TODO: error handling
     customizations = ['26pt', 'nothemes', 'nomargins', 'notoc'] if mobile else None
-    pdf_file = doc.as_pdf(cover=True, ilustr_path=book.gallery_path(), customizations=customizations)
+    pdf_file = doc.as_pdf(cover=True, base_url=request.build_absolute_uri(book.gallery_path()), customizations=customizations)
     from .ebook_utils import serve_file
     return serve_file(pdf_file.get_filename(),
                 book.slug + '.pdf', 'application/pdf')
 
 
     from .ebook_utils import serve_file
     return serve_file(pdf_file.get_filename(),
                 book.slug + '.pdf', 'application/pdf')
 
 
+@login_required
 @never_cache
 def book_epub(request, slug):
     book = get_object_or_404(Book, slug=slug)
 @never_cache
 def book_epub(request, slug):
     book = get_object_or_404(Book, slug=slug)
@@ -286,15 +302,50 @@ def book_epub(request, slug):
         return HttpResponseForbidden("Not authorized.")
 
     # TODO: move to celery
         return HttpResponseForbidden("Not authorized.")
 
     # TODO: move to celery
-    doc = book.wldocument()
+    doc = book.wldocument(librarian2=True)
     # TODO: error handling
     # TODO: error handling
-    epub = doc.as_epub(ilustr_path=book.gallery_path()).get_bytes()
+
+    from librarian.builders import EpubBuilder
+    epub = EpubBuilder(
+        base_url='file://' + book.gallery_path() + '/',
+        debug=True
+    ).build(doc).get_bytes()
     response = HttpResponse(content_type='application/epub+zip')
     response['Content-Disposition'] = 'attachment; filename=%s' % book.slug + '.epub'
     response.write(epub)
     return response
 
 
     response = HttpResponse(content_type='application/epub+zip')
     response['Content-Disposition'] = 'attachment; filename=%s' % book.slug + '.epub'
     response.write(epub)
     return response
 
 
+@login_required
+@never_cache
+def book_epubcheck(request, slug):
+    book = get_object_or_404(Book, slug=slug)
+    if not book.accessible(request):
+        return HttpResponseForbidden("Not authorized.")
+
+    # TODO: move to celery
+    doc = book.wldocument(librarian2=True)
+    # TODO: error handling
+
+    from librarian.builders import EpubBuilder
+    epub = EpubBuilder(
+        base_url='file://' + book.gallery_path() + '/',
+        debug=True
+    ).build(doc)
+    fname = epub.get_filename()
+
+    messages = epubcheck.epubcheck(fname)
+    for message in messages:
+        for location in message.get('locations', []):
+            if 'wl_chunk' in location:
+                location['wl_chunk'] = book[location['wl_chunk']]
+    return render(request, 'documents/book_epubcheck.html', {
+        'messages': messages,
+        'book': book,
+    })
+
+
+@login_required
 @never_cache
 def book_mobi(request, slug):
     book = get_object_or_404(Book, slug=slug)
 @never_cache
 def book_mobi(request, slug):
     book = get_object_or_404(Book, slug=slug)
@@ -302,9 +353,12 @@ def book_mobi(request, slug):
         return HttpResponseForbidden("Not authorized.")
 
     # TODO: move to celery
         return HttpResponseForbidden("Not authorized.")
 
     # TODO: move to celery
-    doc = book.wldocument()
+    doc = book.wldocument(librarian2=True)
     # TODO: error handling
     # TODO: error handling
-    mobi = doc.as_mobi(ilustr_path=book.gallery_path()).get_bytes()
+    from librarian.builders import MobiBuilder
+    mobi = MobiBuilder(
+        base_url='file://' + book.gallery_path() + '/'
+    ).build(doc).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,13 +399,41 @@ def book(request, slug):
     publish_error = book.publishable_error()
     publishable = publish_error is None
 
     publish_error = book.publishable_error()
     publishable = publish_error is None
 
+    stats = None
+    try:
+        doc = book.wldocument(librarian2=True)
+    except:
+        doc = None
+    else:
+        try:
+            stats = doc.get_statistics()
+        except:
+            pass
+
+    cbook_by_slug = None
+    if book.dc_slug:
+        audio_items = requests.get(f'https://audio.wolnelektury.pl/archive/book/{book.dc_slug}.json').json()['items']
+        has_audio = bool(audio_items)
+        can_sell_audio = has_audio and all(x['project']['can_sell'] for x in audio_items)
+
+        if book.catalogue_book is None or book.dc_slug != book.catalogue_book.slug:
+            cbook_by_slug = catalogue.models.Book.objects.filter(slug=book.dc_slug).first()
+    else:
+        has_audio = None
+        can_sell_audio = None
+
     return render(request, "documents/book_detail.html", {
         "book": book,
     return render(request, "documents/book_detail.html", {
         "book": book,
+        "doc": doc,
+        "stats": stats,
         "publishable": publishable,
         "publishable_error": publish_error,
         "form": form,
         "publish_options_form": publish_options_form,
         "editable": editable,
         "publishable": publishable,
         "publishable_error": publish_error,
         "form": form,
         "publish_options_form": publish_options_form,
         "editable": editable,
+        "has_audio": has_audio,
+        "can_sell_audio": can_sell_audio,
+        "cbook_by_slug": cbook_by_slug,
     })
 
 
     })
 
 
@@ -437,7 +519,7 @@ def chunk_edit(request, slug, chunk):
             form.save()
             go_next = request.GET.get('next', None)
             if go_next:
             form.save()
             go_next = request.GET.get('next', None)
             if go_next:
-                go_next = urlquote_plus(unquote(iri_to_uri(go_next)), safe='/?=&')
+                go_next = quote_plus(unquote(iri_to_uri(go_next)), safe='/?=&')
             else:
                 go_next = doc.book.get_absolute_url()
             return http.HttpResponseRedirect(go_next)
             else:
                 go_next = doc.book.get_absolute_url()
             return http.HttpResponseRedirect(go_next)
@@ -448,7 +530,7 @@ def chunk_edit(request, slug, chunk):
     if referer:
         parts = urlsplit(referer)
         parts = ['', ''] + list(parts[2:])
     if referer:
         parts = urlsplit(referer)
         parts = ['', ''] + list(parts[2:])
-        go_next = urlquote_plus(urlunsplit(parts))
+        go_next = quote_plus(urlunsplit(parts))
     else:
         go_next = ''
 
     else:
         go_next = ''
 
@@ -572,16 +654,18 @@ def publish(request, slug):
     if form.is_valid():
         days = form.cleaned_data['days']
         beta = form.cleaned_data['beta']
     if form.is_valid():
         days = form.cleaned_data['days']
         beta = form.cleaned_data['beta']
+        hidden = form.cleaned_data['hidden']
     else:
         days = 0
         beta = False
     else:
         days = 0
         beta = False
+        hidden = False
     book = get_object_or_404(Book, slug=slug)
     if not book.accessible(request):
         return HttpResponseForbidden("Not authorized.")
 
     try:
         protocol = 'https://' if request.is_secure() else 'http://'
     book = get_object_or_404(Book, slug=slug)
     if not book.accessible(request):
         return HttpResponseForbidden("Not authorized.")
 
     try:
         protocol = 'https://' if request.is_secure() else 'http://'
-        book.publish(request.user, host=protocol + request.get_host(), days=days, beta=beta)
+        book.publish(request.user, host=protocol + request.get_host(), days=days, beta=beta, hidden=hidden)
     except NotAuthorizedError:
         return http.HttpResponseRedirect(reverse('apiclient_oauth' if not beta else 'apiclient_beta_oauth'))
     except BaseException as e:
     except NotAuthorizedError:
         return http.HttpResponseRedirect(reverse('apiclient_oauth' if not beta else 'apiclient_beta_oauth'))
     except BaseException as e:
@@ -625,7 +709,7 @@ class GalleryView(UploadView):
         return "%s%s/" % (settings.IMAGE_DIR, self.object.gallery)
 
 
         return "%s%s/" % (settings.IMAGE_DIR, self.object.gallery)
 
 
-def active_users_list(request):
+def active_users_list(request, csv=False):
     year = int(request.GET.get('y', date.today().year))
     by_user = defaultdict(lambda: 0)
     by_email = defaultdict(lambda: 0)
     year = int(request.GET.get('y', date.today().year))
     by_user = defaultdict(lambda: 0)
     by_email = defaultdict(lambda: 0)
@@ -650,10 +734,24 @@ def active_users_list(request):
     for email, count in by_email.items():
         active_users.append((email, names_by_email[email], count))
     active_users.sort(key=lambda x: -x[2])
     for email, count in by_email.items():
         active_users.append((email, names_by_email[email], count))
     active_users.sort(key=lambda x: -x[2])
-    return render(request, 'documents/active_users_list.html', {
-        'users': active_users,
-        'year': year,
-    })
+    if csv:
+        return http.HttpResponse(
+            '\n'.join((
+                ','.join(
+                    (str(x[2]), x[0], ','.join(x[1]))
+                )
+                for x in active_users
+            )),
+            content_type='text/csv',
+            headers={
+                'Content-Disposition': f'attachment; filename=redakcja-{year}.csv',
+            }
+        )
+    else:
+        return render(request, 'documents/active_users_list.html', {
+            'users': active_users,
+            'year': year,
+        })
 
 
 @user_passes_test(lambda u: u.is_superuser)
 
 
 @user_passes_test(lambda u: u.is_superuser)
@@ -670,3 +768,91 @@ def mark_final(request):
 
 def mark_final_completed(request):
     return render(request, 'documents/mark_final_completed.html')
 
 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,
+    })
+
+
+@permission_required('documents.change_book')
+def attach_book_to_catalogue(request, pk):
+    dbook = get_object_or_404(Book, pk=pk)
+    if dbook.dc_slug:
+        cbook = get_object_or_404(catalogue.models.Book, slug=dbook.dc_slug)
+        dbook.catalogue_book = cbook
+        dbook.save()
+    return http.HttpResponseRedirect(dbook.get_absolute_url())