more error reporting
[redakcja.git] / apps / catalogue / views.py
index 72ed9cb..bde9dc0 100644 (file)
@@ -1,29 +1,34 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
-from datetime import date, timedelta
+#
+# This file is part of MIL/PEER, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
 import logging
 import os
 import shutil
 import logging
 import os
 import shutil
+import subprocess
+from tempfile import NamedTemporaryFile
 
 from django.conf import settings
 from django.contrib import auth
 from django.contrib.auth.models import User
 from django.contrib.auth.decorators import login_required
 from django.core.urlresolvers import reverse
 
 from django.conf import settings
 from django.contrib import auth
 from django.contrib.auth.models import User
 from django.contrib.auth.decorators import login_required
 from django.core.urlresolvers import reverse
-from django.db.models import Count
 from django import http
 from django import http
-from django.http import Http404
+from django.http import Http404, HttpResponse
 from django.shortcuts import get_object_or_404, render, redirect
 from django.shortcuts import get_object_or_404, render, redirect
+from django.utils.encoding import force_str
 from django.utils.http import urlquote_plus
 from django.views.decorators.http import require_POST
 
 from catalogue import forms
 from django.utils.http import urlquote_plus
 from django.views.decorators.http import require_POST
 
 from catalogue import forms
-from catalogue import helpers
 from catalogue.helpers import active_tab
 from catalogue.helpers import active_tab
+from librarian import BuildError
 from .constants import STAGES
 from .models import Document, Plan
 from dvcs.models import Revision
 from organizations.models import Organization
 from .constants import STAGES
 from .models import Document, Plan
 from dvcs.models import Revision
 from organizations.models import Organization
-from fileupload.views import UploadView, PackageView
+from fileupload.views import UploadView
 
 #
 # Quick hack around caching problems, TODO: use ETags
 
 #
 # Quick hack around caching problems, TODO: use ETags
@@ -58,38 +63,12 @@ def my(request):
         })
 
 
         })
 
 
-@active_tab('users')
-def users(request):
-    return render(request, 'catalogue/user_list.html', {
-        'users': User.objects.all().annotate(count=Count('chunk')).order_by(
-            '-count', 'last_name', 'first_name'),
-    })
-
-
-@active_tab('activity')
-def activity(request, isodate=None):
-    today = date.today()
-    try:
-        day = helpers.parse_isodate(isodate)
-    except ValueError:
-        day = today
-
-    if day > today:
-        raise Http404
-    if day != today:
-        next_day = day + timedelta(1)
-    prev_day = day - timedelta(1)
-
-    return render(request, 'catalogue/activity.html', locals())
-
-
 @never_cache
 def logout_then_redirect(request):
     auth.logout(request)
     return http.HttpResponseRedirect(urlquote_plus(request.GET.get('next', '/'), safe='/?='))
 
 
 @never_cache
 def logout_then_redirect(request):
     auth.logout(request)
     return http.HttpResponseRedirect(urlquote_plus(request.GET.get('next', '/'), safe='/?='))
 
 
-# @permission_required('catalogue.add_book')
 @login_required
 @active_tab('create')
 def create_missing(request):
 @login_required
 @active_tab('create')
 def create_missing(request):
@@ -169,96 +148,6 @@ def create_missing(request):
     })
 
 
     })
 
 
-# @permission_required('catalogue.add_book')
-# @active_tab('upload')
-# def upload(request):
-#     if request.method == "POST":
-#         form = forms.DocumentsUploadForm(request.POST, request.FILES)
-#         if form.is_valid():
-#             import slughifi
-#
-#             if request.user.is_authenticated():
-#                 creator = request.user
-#             else:
-#                 creator = None
-#
-#             zip = form.cleaned_data['zip']
-#             skipped_list = []
-#             ok_list = []
-#             error_list = []
-#             slugs = {}
-#             existing = [book.slug for book in Book.objects.all()]
-#             for filename in zip.namelist():
-#                 if filename[-1] == '/':
-#                     continue
-#                 title = os.path.basename(filename)[:-4]
-#                 slug = slughifi(title)
-#                 if not (slug and filename.endswith('.xml')):
-#                     skipped_list.append(filename)
-#                 elif slug in slugs:
-#                     error_list.append((filename, slug, _('Slug already used for %s' % slugs[slug])))
-#                 elif slug in existing:
-#                     error_list.append((filename, slug, _('Slug already used in repository.')))
-#                 else:
-#                     try:
-#                         zip.read(filename).decode('utf-8') # test read
-#                         ok_list.append((filename, slug, title))
-#                     except UnicodeDecodeError:
-#                         error_list.append((filename, title, _('File should be UTF-8 encoded.')))
-#                     slugs[slug] = filename
-#
-#             if not error_list:
-#                 for filename, slug, title in ok_list:
-#                     book = Book.create(
-#                         text=zip.read(filename).decode('utf-8'),
-#                         creator=creator,
-#                         slug=slug,
-#                         title=title,
-#                     )
-#
-#             return render(request, "catalogue/document_upload.html", {
-#                 "form": form,
-#                 "ok_list": ok_list,
-#                 "skipped_list": skipped_list,
-#                 "error_list": error_list,
-#
-#                 "logout_to": '/',
-#             })
-#     else:
-#         form = forms.DocumentsUploadForm()
-#
-#     return render(request, "catalogue/document_upload.html", {
-#         "form": form,
-#
-#         "logout_to": '/',
-#     })
-
-
-# @never_cache
-# def book_xml(request, slug):
-#     book = get_object_or_404(Book, slug=slug)
-#     if not book.accessible(request):
-#         return HttpResponseForbidden("Not authorized.")
-#     xml = book.materialize()
-#
-#     response = http.HttpResponse(xml, content_type='application/xml', mimetype='application/wl+xml')
-#     response['Content-Disposition'] = 'attachment; filename=%s.xml' % slug
-#     return response
-
-
-# @never_cache
-# def book_txt(request, slug):
-#     book = get_object_or_404(Book, slug=slug)
-#     if not book.accessible(request):
-#         return HttpResponseForbidden("Not authorized.")
-#
-#     doc = book.wldocument()
-#     text = doc.as_text().get_string()
-#     response = http.HttpResponse(text, content_type='text/plain', mimetype='text/plain')
-#     response['Content-Disposition'] = 'attachment; filename=%s.txt' % slug
-#     return response
-
-
 @never_cache
 def book_html(request, pk, rev_pk=None, preview=False):
     from librarian.document import Document as SST
 @never_cache
 def book_html(request, pk, rev_pk=None, preview=False):
     from librarian.document import Document as SST
@@ -286,9 +175,13 @@ def book_html(request, pk, rev_pk=None, preview=False):
 
     was_published = revision == published_revision or doc.publish_log.filter(revision=revision).exists()
 
 
     was_published = revision == published_revision or doc.publish_log.filter(revision=revision).exists()
 
-    sst = SST.from_string(revision.materialize())
-    html = HtmlFormat(sst).build(
-        files_path='http://%s/media/dynamic/uploads/%s/' % (request.get_host(), pk)).get_string()
+    try:
+        sst = SST.from_string(revision.materialize())
+    except ValueError as e:
+        html = e
+    else:
+        html = HtmlFormat(sst).build(
+            files_path='http://%s/media/dynamic/uploads/%s/' % (request.get_host(), pk)).get_string()
 
     # response = http.HttpResponse(html, content_type='text/html', mimetype='text/html')
     # return response
 
     # response = http.HttpResponse(html, content_type='text/html', mimetype='text/html')
     # return response
@@ -321,7 +214,10 @@ def book_pdf(request, pk, rev_pk):
     rev = get_object_or_404(Revision, pk=rev_pk)
     # Test
 
     rev = get_object_or_404(Revision, pk=rev_pk)
     # Test
 
-    sst = SST.from_string(rev.materialize())
+    try:
+        sst = SST.from_string(rev.materialize())
+    except ValueError as e:
+        return HttpResponse(content=force_str(e.message), content_type='text/plain', status='400')
     
     ctx = Context(
         files_path='http://%s/media/dynamic/uploads/%s/' % (request.get_host(), pk),
     
     ctx = Context(
         files_path='http://%s/media/dynamic/uploads/%s/' % (request.get_host(), pk),
@@ -329,7 +225,10 @@ def book_pdf(request, pk, rev_pk):
     )
     if doc.owner_organization is not None and doc.owner_organization.logo:
         ctx.cover_logo = 'http://%s%s' % (request.get_host(), doc.owner_organization.logo.url)
     )
     if doc.owner_organization is not None and doc.owner_organization.logo:
         ctx.cover_logo = 'http://%s%s' % (request.get_host(), doc.owner_organization.logo.url)
-    pdf_file = PdfFormat(sst).build(ctx)
+    try:
+        pdf_file = PdfFormat(sst).build(ctx)
+    except BuildError as e:
+        return HttpResponse(content=force_str(e.message), content_type='text/plain', status='400')
 
     from catalogue.ebook_utils import serve_file
     return serve_file(pdf_file.get_filename(), '%d.pdf' % doc.pk, 'application/pdf')
 
     from catalogue.ebook_utils import serve_file
     return serve_file(pdf_file.get_filename(), '%d.pdf' % doc.pk, 'application/pdf')
@@ -345,7 +244,10 @@ def book_epub(request, pk, rev_pk):
     rev = get_object_or_404(Revision, pk=rev_pk)
     # Test
 
     rev = get_object_or_404(Revision, pk=rev_pk)
     # Test
 
-    sst = SST.from_string(rev.materialize())
+    try:
+        sst = SST.from_string(rev.materialize())
+    except ValueError as e:
+        return HttpResponse(content=force_str(e.message), content_type='text/plain', status='400')
 
     ctx = Context(
         files_path='http://%s/media/dynamic/uploads/%s/' % (request.get_host(), pk),
 
     ctx = Context(
         files_path='http://%s/media/dynamic/uploads/%s/' % (request.get_host(), pk),
@@ -353,12 +255,49 @@ def book_epub(request, pk, rev_pk):
     )
     if doc.owner_organization is not None and doc.owner_organization.logo:
         ctx.cover_logo = 'http://%s%s' % (request.get_host(), doc.owner_organization.logo.url)
     )
     if doc.owner_organization is not None and doc.owner_organization.logo:
         ctx.cover_logo = 'http://%s%s' % (request.get_host(), doc.owner_organization.logo.url)
-    epub_file = EpubFormat(sst).build(ctx)
+    try:
+        epub_file = EpubFormat(sst).build(ctx)
+    except BuildError as e:
+        return HttpResponse(content=force_str(e.message), content_type='text/plain', status='400')
 
     from catalogue.ebook_utils import serve_file
     return serve_file(epub_file.get_filename(), '%d.epub' % doc.pk, 'application/epub+zip')
 
 
 
     from catalogue.ebook_utils import serve_file
     return serve_file(epub_file.get_filename(), '%d.epub' % doc.pk, 'application/epub+zip')
 
 
+@never_cache
+def book_mobi(request, pk, rev_pk):
+    from librarian.utils import Context
+    from librarian.document import Document as SST
+    from librarian.formats.epub import EpubFormat
+
+    doc = get_object_or_404(Document, pk=pk)
+    rev = get_object_or_404(Revision, pk=rev_pk)
+
+    try:
+        sst = SST.from_string(rev.materialize())
+    except ValueError as e:
+        return HttpResponse(content=force_str(e.message), content_type='text/plain', status='400')
+
+    ctx = Context(
+        files_path='http://%s/media/dynamic/uploads/%s/' % (request.get_host(), pk),
+        source_url='http://%s%s' % (request.get_host(), reverse('catalogue_html', args=[doc.pk])),
+    )
+    if doc.owner_organization is not None and doc.owner_organization.logo:
+        ctx.cover_logo = 'http://%s%s' % (request.get_host(), doc.owner_organization.logo.url)
+    try:
+        epub_file = EpubFormat(sst).build(ctx)
+    except BuildError as e:
+        return HttpResponse(content=force_str(e.message), content_type='text/plain', status='400')
+
+    output_file = NamedTemporaryFile(prefix='librarian', suffix='.mobi', delete=False)
+    output_file.close()
+    subprocess.check_call(
+        ['ebook-convert', epub_file.get_filename(), output_file.name, '--no-inline-toc'])
+
+    from catalogue.ebook_utils import serve_file
+    return serve_file(output_file.name, '%d.mobi' % doc.pk, 'application/epub+zip')
+
+
 # @never_cache
 # def revision(request, slug, chunk=None):
 #     try:
 # @never_cache
 # def revision(request, slug, chunk=None):
 #     try:
@@ -449,192 +388,6 @@ def book_delete(request, pk):
     })
 
 
     })
 
 
-# def book(request, slug):
-#     book = get_object_or_404(Book, slug=slug)
-#     if not book.accessible(request):
-#         return HttpResponseForbidden("Not authorized.")
-#
-#     if request.user.has_perm('catalogue.change_book'):
-#         if request.method == "POST":
-#             form = forms.BookForm(request.POST, instance=book)
-#             if form.is_valid():
-#                 form.save()
-#                 return http.HttpResponseRedirect(book.get_absolute_url())
-#         else:
-#             form = forms.BookForm(instance=book)
-#         editable = True
-#     else:
-#         form = forms.ReadonlyBookForm(instance=book)
-#         editable = False
-#
-#     publish_error = book.publishable_error()
-#     publishable = publish_error is None
-#
-#     return render(request, "catalogue/book_detail.html", {
-#         "book": book,
-#         "publishable": publishable,
-#         "publishable_error": publish_error,
-#         "form": form,
-#         "editable": editable,
-#     })
-
-
-# @permission_required('catalogue.add_chunk')
-# def chunk_add(request, slug, chunk):
-#     try:
-#         doc = Chunk.get(slug, chunk)
-#     except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
-#         raise Http404
-#     if not doc.book.accessible(request):
-#         return HttpResponseForbidden("Not authorized.")
-#
-#     if request.method == "POST":
-#         form = forms.ChunkAddForm(request.POST, instance=doc)
-#         if form.is_valid():
-#             if request.user.is_authenticated():
-#                 creator = request.user
-#             else:
-#                 creator = None
-#             doc.split(creator=creator,
-#                 slug=form.cleaned_data['slug'],
-#                 title=form.cleaned_data['title'],
-#                 gallery_start=form.cleaned_data['gallery_start'],
-#                 user=form.cleaned_data['user'],
-#                 stage=form.cleaned_data['stage']
-#             )
-#
-#             return http.HttpResponseRedirect(doc.book.get_absolute_url())
-#     else:
-#         form = forms.ChunkAddForm(initial={
-#                 "slug": str(doc.number + 1),
-#                 "title": "cz. %d" % (doc.number + 1, ),
-#         })
-#
-#     return render(request, "catalogue/chunk_add.html", {
-#         "chunk": doc,
-#         "form": form,
-#     })
-
-
-# @login_required
-# def chunk_edit(request, slug, chunk):
-#     try:
-#         doc = Chunk.get(slug, chunk)
-#     except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
-#         raise Http404
-#     if not doc.book.accessible(request):
-#         return HttpResponseForbidden("Not authorized.")
-#
-#     if request.method == "POST":
-#         form = forms.ChunkForm(request.POST, instance=doc)
-#         if form.is_valid():
-#             form.save()
-#             go_next = request.GET.get('next', None)
-#             if go_next:
-#                 go_next = urlquote_plus(unquote(iri_to_uri(go_next)), safe='/?=&')
-#             else:
-#                 go_next = doc.book.get_absolute_url()
-#             return http.HttpResponseRedirect(go_next)
-#     else:
-#         form = forms.ChunkForm(instance=doc)
-#
-#     referer = request.META.get('HTTP_REFERER')
-#     if referer:
-#         parts = urlsplit(referer)
-#         parts = ['', ''] + list(parts[2:])
-#         go_next = urlquote_plus(urlunsplit(parts))
-#     else:
-#         go_next = ''
-#
-#     return render(request, "catalogue/chunk_edit.html", {
-#         "chunk": doc,
-#         "form": form,
-#         "go_next": go_next,
-#     })
-
-
-# @transaction.atomic
-# @login_required
-# def chunk_mass_edit(request):
-#     if request.method == 'POST':
-#         ids = map(int, filter(lambda i: i.strip()!='', request.POST.get('ids').split(',')))
-#         chunks = map(lambda i: Chunk.objects.get(id=i), ids)
-#
-#         stage = request.POST.get('stage')
-#         if stage:
-#             try:
-#                 stage = Chunk.tag_model.objects.get(slug=stage)
-#             except Chunk.DoesNotExist, e:
-#                 stage = None
-#
-#             for c in chunks: c.stage = stage
-#
-#         username = request.POST.get('user')
-#         logger.info("username: %s" % username)
-#         logger.info(request.POST)
-#         if username:
-#             try:
-#                 user = User.objects.get(username=username)
-#             except User.DoesNotExist, e:
-#                 user = None
-#
-#             for c in chunks: c.user = user
-#
-#         status = request.POST.get('status')
-#         if status:
-#             books_affected = set()
-#             for c in chunks:
-#                 if status == 'publish':
-#                     c.head.publishable = True
-#                     c.head.save()
-#                 elif status == 'unpublish':
-#                     c.head.publishable = False
-#                     c.head.save()
-#                 c.touch()  # cache
-#                 books_affected.add(c.book)
-#             for b in books_affected:
-#                 b.touch()  # cache
-#
-#         project_id = request.POST.get('project')
-#         if project_id:
-#             try:
-#                 project = Project.objects.get(pk=int(project_id))
-#             except (Project.DoesNotExist, ValueError), e:
-#                 project = None
-#             for c in chunks:
-#                 book = c.book
-#                 book.project = project
-#                 book.save()
-#
-#         for c in chunks: c.save()
-#
-#         return HttpResponse("", content_type="text/plain")
-#     else:
-#         raise Http404
-
-
-# @permission_required('catalogue.change_book')
-# def book_append(request, slug):
-#     book = get_object_or_404(Book, slug=slug)
-#     if not book.accessible(request):
-#         return HttpResponseForbidden("Not authorized.")
-#
-#     if request.method == "POST":
-#         form = forms.BookAppendForm(book, request.POST)
-#         if form.is_valid():
-#             append_to = form.cleaned_data['append_to']
-#             append_to.append(book)
-#             return http.HttpResponseRedirect(append_to.get_absolute_url())
-#     else:
-#         form = forms.BookAppendForm(book)
-#     return render(request, "catalogue/book_append_to.html", {
-#         "book": book,
-#         "form": form,
-#
-#         "logout_to": '/',
-#     })
-
-
 @require_POST
 @login_required
 def publish(request, pk):
 @require_POST
 @login_required
 def publish(request, pk):
@@ -696,12 +449,6 @@ class GalleryView(GalleryMixin, UploadView):
         self.doc = Document.objects.get(pk=pk, deleted=False)
 
 
         self.doc = Document.objects.get(pk=pk, deleted=False)
 
 
-class GalleryPackageView(GalleryMixin, PackageView):
-
-    def get_redirect_url(self, slug):
-        return reverse('catalogue_book_gallery', kwargs=dict(slug=slug))
-
-
 @login_required
 def fork(request, pk):
     doc = get_object_or_404(Document, pk=pk, deleted=False)
 @login_required
 def fork(request, pk):
     doc = get_object_or_404(Document, pk=pk, deleted=False)