escape author for history
[redakcja.git] / apps / catalogue / views.py
index 72ed9cb..b8b6b89 100644 (file)
@@ -1,29 +1,38 @@
 # -*- 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.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.contrib.sites.models import Site
 from django.core.urlresolvers import reverse
 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.forms import TagMultipleForm, TagSingleForm
 from catalogue.helpers import active_tab
 from catalogue.helpers import active_tab
+from catalogue.models import Category
+from librarian import BuildError
+from redakcja.utlis import send_notify_email
 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
@@ -34,12 +43,6 @@ from django.views.decorators.cache import never_cache
 logger = logging.getLogger("fnp.catalogue")
 
 
 logger = logging.getLogger("fnp.catalogue")
 
 
-@active_tab('all')
-@never_cache
-def document_list(request):
-    return render(request, 'catalogue/document_list.html')
-
-
 @never_cache
 def user(request, username):
     user = get_object_or_404(User, username=username)
 @never_cache
 def user(request, username):
     user = get_object_or_404(User, username=username)
@@ -58,44 +61,22 @@ 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):
     if request.method == "POST":
         form = forms.DocumentCreateForm(request.POST, request.FILES)
 @login_required
 @active_tab('create')
 def create_missing(request):
     if request.method == "POST":
         form = forms.DocumentCreateForm(request.POST, request.FILES)
-        if form.is_valid():
+        # tag_forms = [
+        #     (TagMultipleForm if category.multiple else TagSingleForm)(
+        #         category=category, data=request.POST, prefix=category.dc_tag)
+        #     for category in Category.objects.all()]
+        if form.is_valid():  # and all(tag_form.is_valid() for tag_form in tag_forms):
             
             if request.user.is_authenticated():
                 creator = request.user
             
             if request.user.is_authenticated():
                 creator = request.user
@@ -162,103 +143,18 @@ def create_missing(request):
 
         form = forms.DocumentCreateForm(initial={'owner_organization': org})
 
 
         form = forms.DocumentCreateForm(initial={'owner_organization': org})
 
+        # tag_forms = [
+        #     (TagMultipleForm if category.multiple else TagSingleForm)(category=category, prefix=category.dc_tag)
+        #     for category in Category.objects.all()]
+
     return render(request, "catalogue/document_create_missing.html", {
         "form": form,
     return render(request, "catalogue/document_create_missing.html", {
         "form": form,
+        # "tag_forms": tag_forms,
 
         "logout_to": '/',
     })
 
 
 
         "logout_to": '/',
     })
 
 
-# @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 +182,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 +221,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 +232,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 +251,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 +262,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 +395,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):
@@ -651,7 +411,17 @@ def publish(request, pk):
         # FIXME: check if in tree
         # if PublishRecord.objects.filter(revision=rev, document=doc).exists():
         #     return http.HttpResponse('exists')
         # FIXME: check if in tree
         # if PublishRecord.objects.filter(revision=rev, document=doc).exists():
         #     return http.HttpResponse('exists')
+        if not doc.published:
+            site = Site.objects.get_current()
+            send_notify_email(
+                'New published document in MIL/PEER',
+                '''New published document in MIL/PEER: %s. View it in browser: https://%s%s.
+
+--
+MIL/PEER team.''' % (doc.meta()['title'], site.domain, reverse('catalogue_html', args=[doc.pk])))
         PublishRecord.objects.create(revision=rev, document=doc, user=request.user)
         PublishRecord.objects.create(revision=rev, document=doc, user=request.user)
+        doc.published = True
+        doc.save()
         if request.is_ajax():
             return http.HttpResponse('ok')
         else:
         if request.is_ajax():
             return http.HttpResponse('ok')
         else:
@@ -696,12 +466,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)