Adding covers: make source_url field not required
[redakcja.git] / apps / catalogue / views.py
index fad8a69..ebc3575 100644 (file)
@@ -1,33 +1,32 @@
-from datetime import datetime
+from datetime import datetime, date, timedelta
 import logging
 import os
 from StringIO import StringIO
 from urllib import unquote
 from urlparse import urlsplit, urlunsplit
 
 import logging
 import os
 from StringIO import StringIO
 from urllib import unquote
 from urlparse import urlsplit, urlunsplit
 
+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, permission_required
 from django.core.urlresolvers import reverse
 from django.db.models import Count, Q
 from django.contrib import auth
 from django.contrib.auth.models import User
 from django.contrib.auth.decorators import login_required, permission_required
 from django.core.urlresolvers import reverse
 from django.db.models import Count, Q
+from django.db import transaction
 from django import http
 from django import http
-from django.http import Http404
-from django.shortcuts import get_object_or_404, render
+from django.http import Http404, HttpResponse, HttpResponseForbidden
+from django.shortcuts import get_object_or_404, render, render_to_response
 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.views.decorators.http import require_POST
 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.views.decorators.http import require_POST
-from django.views.generic.simple import direct_to_template
-
-import librarian.html
-import librarian.text
+from django.template import RequestContext
 
 from apiclient import NotAuthorizedError
 from catalogue import forms
 from catalogue import helpers
 from catalogue.helpers import active_tab
 
 from apiclient import NotAuthorizedError
 from catalogue import forms
 from catalogue import helpers
 from catalogue.helpers import active_tab
-from catalogue.models import Book, Chunk, BookPublishRecord, ChunkPublishRecord
-from catalogue import xml_tools
+from catalogue.models import Book, Chunk, BookPublishRecord, ChunkPublishRecord, Project
+from fileupload.views import UploadView
 
 #
 # Quick hack around caching problems, TODO: use ETags
 
 #
 # Quick hack around caching problems, TODO: use ETags
@@ -56,20 +55,34 @@ def my(request):
     return render(request, 'catalogue/my_page.html', {
         'last_books': sorted(request.session.get("wiki_last_books", {}).items(),
                         key=lambda x: x[1]['time'], reverse=True),
     return render(request, 'catalogue/my_page.html', {
         'last_books': sorted(request.session.get("wiki_last_books", {}).items(),
                         key=lambda x: x[1]['time'], reverse=True),
+
+        "logout_to": '/',
         })
 
 
 @active_tab('users')
 def users(request):
         })
 
 
 @active_tab('users')
 def users(request):
-    return direct_to_template(request, 'catalogue/user_list.html', extra_context={
+    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')
         'users': User.objects.all().annotate(count=Count('chunk')).order_by(
             '-count', 'last_name', 'first_name'),
     })
 
 
 @active_tab('activity')
-def activity(request):
-    return render(request, 'catalogue/activity.html')
+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
 
 
 @never_cache
@@ -98,18 +111,22 @@ def create_missing(request, slug=None):
                 creator=creator,
                 slug=form.cleaned_data['slug'],
                 title=form.cleaned_data['title'],
                 creator=creator,
                 slug=form.cleaned_data['slug'],
                 title=form.cleaned_data['title'],
+                gallery=form.cleaned_data['gallery'],
             )
 
             )
 
-            return http.HttpResponseRedirect(reverse("wiki_editor", args=[book.slug]))
+            return http.HttpResponseRedirect(reverse("catalogue_book", args=[book.slug]))
     else:
         form = forms.DocumentCreateForm(initial={
                 "slug": slug,
                 "title": slug.replace('-', ' ').title(),
     else:
         form = forms.DocumentCreateForm(initial={
                 "slug": slug,
                 "title": slug.replace('-', ' ').title(),
+                "gallery": slug,
         })
 
         })
 
-    return direct_to_template(request, "catalogue/document_create_missing.html", extra_context={
+    return render(request, "catalogue/document_create_missing.html", {
         "slug": slug,
         "form": form,
         "slug": slug,
         "form": form,
+
+        "logout_to": '/',
     })
 
 
     })
 
 
@@ -160,23 +177,30 @@ def upload(request):
                         title=title,
                     )
 
                         title=title,
                     )
 
-            return direct_to_template(request, "catalogue/document_upload.html", extra_context={
+            return render(request, "catalogue/document_upload.html", {
                 "form": form,
                 "ok_list": ok_list,
                 "skipped_list": skipped_list,
                 "error_list": error_list,
                 "form": form,
                 "ok_list": ok_list,
                 "skipped_list": skipped_list,
                 "error_list": error_list,
+
+                "logout_to": '/',
             })
     else:
         form = forms.DocumentsUploadForm()
 
             })
     else:
         form = forms.DocumentsUploadForm()
 
-    return direct_to_template(request, "catalogue/document_upload.html", extra_context={
+    return render(request, "catalogue/document_upload.html", {
         "form": form,
         "form": form,
+
+        "logout_to": '/',
     })
 
 
 @never_cache
 def book_xml(request, slug):
     })
 
 
 @never_cache
 def book_xml(request, slug):
-    xml = get_object_or_404(Book, slug=slug).materialize()
+    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
 
     response = http.HttpResponse(xml, content_type='application/xml', mimetype='application/wl+xml')
     response['Content-Disposition'] = 'attachment; filename=%s.xml' % slug
@@ -185,11 +209,12 @@ def book_xml(request, slug):
 
 @never_cache
 def book_txt(request, slug):
 
 @never_cache
 def book_txt(request, slug):
-    xml = get_object_or_404(Book, slug=slug).materialize()
-    output = StringIO()
-    # errors?
-    librarian.text.transform(StringIO(xml), output)
-    text = output.getvalue()
+    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
     response = http.HttpResponse(text, content_type='text/plain', mimetype='text/plain')
     response['Content-Disposition'] = 'attachment; filename=%s.txt' % slug
     return response
@@ -197,13 +222,55 @@ def book_txt(request, slug):
 
 @never_cache
 def book_html(request, slug):
 
 @never_cache
 def book_html(request, slug):
-    xml = get_object_or_404(Book, slug=slug).materialize()
-    output = StringIO()
-    # errors?
-    librarian.html.transform(StringIO(xml), output, parse_dublincore=False,
-                             flags=['full-page'])
-    html = output.getvalue()
-    response = http.HttpResponse(html, content_type='text/html', mimetype='text/html')
+    book = get_object_or_404(Book, slug=slug)
+    if not book.accessible(request):
+        return HttpResponseForbidden("Not authorized.")
+
+    doc = book.wldocument(parse_dublincore=False)
+    html = doc.as_html()
+
+    html = html.get_string() if html is not None else ''
+    # response = http.HttpResponse(html, content_type='text/html', mimetype='text/html')
+    # return response
+    # book_themes = {}
+    # for fragment in book.fragments.all().iterator():
+    #     for theme in fragment.tags.filter(category='theme').iterator():
+    #         book_themes.setdefault(theme, []).append(fragment)
+
+    # book_themes = book_themes.items()
+    # book_themes.sort(key=lambda s: s[0].sort_key)
+    return render_to_response('catalogue/book_text.html', locals(),
+        context_instance=RequestContext(request))
+
+
+@never_cache
+def book_pdf(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()
+    # TODO: error handling
+    pdf_file = doc.as_pdf()
+    from catalogue.ebook_utils import serve_file
+    return serve_file(pdf_file.get_filename(),
+                book.slug + '.pdf', 'application/pdf')
+
+
+@never_cache
+def book_epub(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()
+    # TODO: error handling
+    epub = doc.as_epub().get_string()
+    response = HttpResponse(mimetype='application/epub+zip')
+    response['Content-Disposition'] = 'attachment; filename=%s' % book.slug + '.epub'
+    response.write(epub)
     return response
 
 
     return response
 
 
@@ -213,11 +280,15 @@ def revision(request, slug, chunk=None):
         doc = Chunk.get(slug, chunk)
     except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
         raise Http404
         doc = Chunk.get(slug, chunk)
     except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
         raise Http404
+    if not doc.book.accessible(request):
+        return HttpResponseForbidden("Not authorized.")
     return http.HttpResponse(str(doc.revision()))
 
 
 def book(request, slug):
     book = get_object_or_404(Book, slug=slug)
     return http.HttpResponse(str(doc.revision()))
 
 
 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":
 
     if request.user.has_perm('catalogue.change_book'):
         if request.method == "POST":
@@ -227,25 +298,18 @@ def book(request, slug):
                 return http.HttpResponseRedirect(book.get_absolute_url())
         else:
             form = forms.BookForm(instance=book)
                 return http.HttpResponseRedirect(book.get_absolute_url())
         else:
             form = forms.BookForm(instance=book)
-            editable = True
+        editable = True
     else:
         form = forms.ReadonlyBookForm(instance=book)
         editable = False
 
     else:
         form = forms.ReadonlyBookForm(instance=book)
         editable = False
 
+    publish_error = book.publishable_error()
+    publishable = publish_error is None
 
 
-    try:
-        book.assert_publishable()
-    except AssertionError, e:
-        publishable = False
-        publishable_error = e
-    else:
-        publishable = True
-        publishable_error = None
-
-    return direct_to_template(request, "catalogue/book_detail.html", extra_context={
+    return render(request, "catalogue/book_detail.html", {
         "book": book,
         "publishable": publishable,
         "book": book,
         "publishable": publishable,
-        "publishable_error": publishable_error,
+        "publishable_error": publish_error,
         "form": form,
         "editable": editable,
     })
         "form": form,
         "editable": editable,
     })
@@ -257,6 +321,8 @@ def chunk_add(request, slug, chunk):
         doc = Chunk.get(slug, chunk)
     except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
         raise Http404
         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 request.method == "POST":
         form = forms.ChunkAddForm(request.POST, instance=doc)
@@ -268,6 +334,9 @@ def chunk_add(request, slug, chunk):
             doc.split(creator=creator,
                 slug=form.cleaned_data['slug'],
                 title=form.cleaned_data['title'],
             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())
             )
 
             return http.HttpResponseRedirect(doc.book.get_absolute_url())
@@ -277,17 +346,21 @@ def chunk_add(request, slug, chunk):
                 "title": "cz. %d" % (doc.number + 1, ),
         })
 
                 "title": "cz. %d" % (doc.number + 1, ),
         })
 
-    return direct_to_template(request, "catalogue/chunk_add.html", extra_context={
+    return render(request, "catalogue/chunk_add.html", {
         "chunk": doc,
         "form": form,
     })
 
 
         "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
 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():
     if request.method == "POST":
         form = forms.ChunkForm(request.POST, instance=doc)
         if form.is_valid():
@@ -301,20 +374,87 @@ def chunk_edit(request, slug, chunk):
     else:
         form = forms.ChunkForm(instance=doc)
 
     else:
         form = forms.ChunkForm(instance=doc)
 
-    parts = urlsplit(request.META['HTTP_REFERER'])
-    parts = ['', ''] + list(parts[2:])
-    go_next = urlquote_plus(urlunsplit(parts))
+    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 direct_to_template(request, "catalogue/chunk_edit.html", extra_context={
+    return render(request, "catalogue/chunk_edit.html", {
         "chunk": doc,
         "form": form,
         "go_next": go_next,
     })
 
 
         "chunk": doc,
         "form": form,
         "go_next": go_next,
     })
 
 
+@transaction.commit_on_success
+@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)
 @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():
     if request.method == "POST":
         form = forms.BookAppendForm(book, request.POST)
         if form.is_valid():
@@ -323,9 +463,11 @@ def book_append(request, slug):
             return http.HttpResponseRedirect(append_to.get_absolute_url())
     else:
         form = forms.BookAppendForm(book)
             return http.HttpResponseRedirect(append_to.get_absolute_url())
     else:
         form = forms.BookAppendForm(book)
-    return direct_to_template(request, "catalogue/book_append_to.html", extra_context={
+    return render(request, "catalogue/book_append_to.html", {
         "book": book,
         "form": form,
         "book": book,
         "form": form,
+
+        "logout_to": '/',
     })
 
 
     })
 
 
@@ -333,6 +475,9 @@ def book_append(request, slug):
 @login_required
 def publish(request, slug):
     book = get_object_or_404(Book, slug=slug)
 @login_required
 def publish(request, slug):
     book = get_object_or_404(Book, slug=slug)
+    if not book.accessible(request):
+        return HttpResponseForbidden("Not authorized.")
+
     try:
         book.publish(request.user)
     except NotAuthorizedError:
     try:
         book.publish(request.user)
     except NotAuthorizedError:
@@ -341,3 +486,21 @@ def publish(request, slug):
         return http.HttpResponse(e)
     else:
         return http.HttpResponseRedirect(book.get_absolute_url())
         return http.HttpResponse(e)
     else:
         return http.HttpResponseRedirect(book.get_absolute_url())
+
+
+class GalleryView(UploadView):
+    def get_object(self, request, slug):
+        book = get_object_or_404(Book, slug=slug)
+        if not book.gallery:
+            raise Http404
+        return book
+
+    def breadcrumbs(self):
+        return [
+            (_('books'), reverse('catalogue_document_list')),
+            (self.object.title, self.object.get_absolute_url()),
+            (_('scan gallery'),),
+        ]
+
+    def get_directory(self):
+        return "%s%s/" % (settings.IMAGE_DIR, self.object.gallery)