stage name translation
[redakcja.git] / apps / catalogue / views.py
index e07bd15..a11eadb 100644 (file)
@@ -1,52 +1,48 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
-from datetime import datetime, 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
-from StringIO import StringIO
-from urllib import unquote
-from urlparse import urlsplit, urlunsplit
+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.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.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, Q
-from django.db import transaction
 from django import http
 from django import http
-from django.http import Http404, HttpResponse, HttpResponseForbidden
-from django.shortcuts import get_object_or_404, render, render_to_response, redirect
-from django.utils.encoding import iri_to_uri
+from django.http import Http404, HttpResponse
+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.utils.http import urlquote_plus
-from django.utils.translation import ugettext_lazy as _
 from django.views.decorators.http import require_POST
 from django.views.decorators.http import require_POST
-from django.template import RequestContext
 
 from catalogue import forms
 
 from catalogue import forms
-from catalogue import helpers
-from catalogue.helpers import active_tab, sstdocument
+from catalogue.forms import TagMultipleForm, TagSingleForm
+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
 #
 from django.views.decorators.cache import never_cache
 
 #
 # Quick hack around caching problems, TODO: use ETags
 #
 from django.views.decorators.cache import never_cache
-#from fnpdjango.utils.text.slughifi import slughifi
+# from fnpdjango.utils.text.slughifi import slughifi
 
 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,51 +54,29 @@ def user(request, username):
 @never_cache
 def my(request):
     return render(request, 'catalogue/my_page.html', {
 @never_cache
 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),
+        'last_books': sorted(
+            request.session.get("wiki_last_books", {}).items(), key=lambda x: x[1]['time'], reverse=True),
 
         "logout_to": '/',
         })
 
 
 
         "logout_to": '/',
         })
 
 
-@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
@@ -134,7 +108,7 @@ def create_missing(request):
                 cover_url = ''
 
             doc.commit(
                 cover_url = ''
 
             doc.commit(
-                text = '''<section xmlns="http://nowoczesnapolska.org.pl/sst#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+                text='''<section xmlns="http://nowoczesnapolska.org.pl/sst#" xmlns:dc="http://purl.org/dc/elements/1.1/">
                 <metadata>
                     <dc:publisher>''' + form.cleaned_data['publisher'] + '''</dc:publisher>
                     <dc:description>''' + form.cleaned_data['description'] + '''</dc:description>
                 <metadata>
                     <dc:publisher>''' + form.cleaned_data['publisher'] + '''</dc:publisher>
                     <dc:description>''' + form.cleaned_data['description'] + '''</dc:description>
@@ -169,103 +143,18 @@ def create_missing(request):
 
         form = forms.DocumentCreateForm(initial={'owner_organization': org})
 
 
         form = forms.DocumentCreateForm(initial={'owner_organization': org})
 
-    return render(request, "catalogue/document_create_missing.html", {
-        "form": form,
-
-        "logout_to": '/',
-    })
+        # tag_forms = [
+        #     (TagMultipleForm if category.multiple else TagSingleForm)(category=category, prefix=category.dc_tag)
+        #     for category in Category.objects.all()]
 
 
-
-@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", {
+    return render(request, "catalogue/document_create_missing.html", {
         "form": form,
         "form": form,
+        # "tag_forms": tag_forms,
 
         "logout_to": '/',
     })
 
 
 
         "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
@@ -293,8 +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
@@ -327,46 +221,99 @@ 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(
     
     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])),
-        )
+        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:
     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)
+        ctx.cover_logo = 'http://%s%s' % (request.get_host(), doc.owner_organization.logo.url)
+    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
 
     from catalogue.ebook_utils import serve_file
-    return serve_file(pdf_file.get_filename(),
-                '%d.pdf' % doc.pk, 'application/pdf')
+    return serve_file(pdf_file.get_filename(), '%d.pdf' % doc.pk, 'application/pdf')
 
 
 @never_cache
 
 
 @never_cache
-def book_epub(request, slug):
-    book = get_object_or_404(Book, slug=slug)
-    if not book.accessible(request):
-        return HttpResponseForbidden("Not authorized.")
+def book_epub(request, pk, rev_pk):
+    from librarian.utils import Context
+    from librarian.document import Document as SST
+    from librarian.formats.epub import EpubFormat
 
 
-    # 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
+    doc = get_object_or_404(Document, pk=pk)
+    rev = get_object_or_404(Revision, pk=rev_pk)
+    # Test
+
+    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')
+
+    from catalogue.ebook_utils import serve_file
+    return serve_file(epub_file.get_filename(), '%d.epub' % doc.pk, 'application/epub+zip')
 
 
 @never_cache
 
 
 @never_cache
-def revision(request, slug, chunk=None):
+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:
     try:
-        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()))
+        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:
+#         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()))
 
 
 @login_required
 
 
 @login_required
@@ -374,7 +321,7 @@ def book_schedule(request, pk):
     book = get_object_or_404(Document, pk=pk, deleted=False)
     if request.method == 'POST':
         Plan.objects.filter(document=book).delete()
     book = get_object_or_404(Document, pk=pk, deleted=False)
     if request.method == 'POST':
         Plan.objects.filter(document=book).delete()
-        for i, s in enumerate(STAGES):
+        for i, (s, name) in enumerate(STAGES):
             user_id = request.POST.get('s%d-user' % i)
             deadline = request.POST.get('s%d-deadline' % i) or None
             Plan.objects.create(document=book, stage=s, user_id=user_id, deadline=deadline)
             user_id = request.POST.get('s%d-user' % i)
             deadline = request.POST.get('s%d-deadline' % i) or None
             Plan.objects.create(document=book, stage=s, user_id=user_id, deadline=deadline)
@@ -386,7 +333,7 @@ def book_schedule(request, pk):
     for p in Plan.objects.filter(document=book):
         current[p.stage] = (getattr(p.user, 'pk', None), (p.deadline.isoformat() if p.deadline else None))
 
     for p in Plan.objects.filter(document=book):
         current[p.stage] = (getattr(p.user, 'pk', None), (p.deadline.isoformat() if p.deadline else None))
 
-    schedule = [(i, s, current.get(s, ())) for (i, s) in enumerate(STAGES)]
+    schedule = [(i, s, current.get(s, ())) for i, (s, name) in enumerate(STAGES)]
     
     if book.owner_organization:
         people = [m.user for m in book.owner_organization.membership_set.exclude(status='pending')]
     
     if book.owner_organization:
         people = [m.user for m in book.owner_organization.membership_set.exclude(status='pending')]
@@ -402,7 +349,8 @@ def book_schedule(request, pk):
 @login_required
 def book_owner(request, pk):
     doc = get_object_or_404(Document, pk=pk, deleted=False)
 @login_required
 def book_owner(request, pk):
     doc = get_object_or_404(Document, pk=pk, deleted=False)
-    if not (doc.owner_user == request.user or doc.owner_organization.is_member(request.user)):
+    user_is_owner = doc.owner_organization and doc.owner_organization.is_member(request.user)
+    if not (doc.owner_user == request.user or user_is_owner):
         raise Http404
 
     error = ''
         raise Http404
 
     error = ''
@@ -447,193 +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):
@@ -648,9 +409,19 @@ def publish(request, pk):
     if form.is_valid():
         rev = Revision.objects.get(pk=form.cleaned_data['revision'])
         # FIXME: check if in tree
     if form.is_valid():
         rev = Revision.objects.get(pk=form.cleaned_data['revision'])
         # FIXME: check if in tree
-        #if PublishRecord.objects.filter(revision=rev, document=doc).exists():
-        #    return http.HttpResponse('exists')
-        pr = PublishRecord.objects.create(revision=rev, document=doc, user=request.user)
+        # 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)
+        doc.published = True
+        doc.save()
         if request.is_ajax():
             return http.HttpResponse('ok')
         else:
         if request.is_ajax():
             return http.HttpResponse('ok')
         else:
@@ -668,10 +439,6 @@ def publish(request, pk):
 @require_POST
 @login_required
 def unpublish(request, pk):
 @require_POST
 @login_required
 def unpublish(request, pk):
-    from wiki import forms
-    from .models import PublishRecord
-    from dvcs.models import Revision
-
     # FIXME: check permissions
 
     doc = get_object_or_404(Document, pk=pk, deleted=False)
     # FIXME: check permissions
 
     doc = get_object_or_404(Document, pk=pk, deleted=False)
@@ -682,11 +449,10 @@ def unpublish(request, pk):
         return redirect('catalogue_html', doc.pk)
 
 
         return redirect('catalogue_html', doc.pk)
 
 
-
 class GalleryMixin(object):
     def get_directory(self):
 class GalleryMixin(object):
     def get_directory(self):
-        #return "%s%s/" % (settings.IMAGE_DIR, 'org%d' % self.org.pk if self.org is not None else self.request.user.pk)
-        return "uploads/%d/" % (self.doc.pk)
+        # return "%s%s/" % (settings.IMAGE_DIR, 'org%d' % self.org.pk if self.org is not None else self.request.user.pk)
+        return "uploads/%d/" % self.doc.pk
 
 
 class GalleryView(GalleryMixin, UploadView):
 
 
 class GalleryView(GalleryMixin, UploadView):
@@ -700,23 +466,12 @@ 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)
     if request.method == "POST":
         form = forms.DocumentForkForm(request.POST, request.FILES)
         if form.is_valid():
 @login_required
 def fork(request, pk):
     doc = get_object_or_404(Document, pk=pk, deleted=False)
     if request.method == "POST":
         form = forms.DocumentForkForm(request.POST, request.FILES)
         if form.is_valid():
-            
-            if request.user.is_authenticated():
-                creator = request.user
-            else:
-                creator = None
-
             try:
                 org = request.user.membership_set.get(
                     organization=int(form.cleaned_data['owner_organization'])).organization
             try:
                 org = request.user.membership_set.get(
                     organization=int(form.cleaned_data['owner_organization'])).organization
@@ -726,10 +481,10 @@ def fork(request, pk):
 
             new_doc = Document.objects.create(revision=doc.revision, **kwargs)
 
 
             new_doc = Document.objects.create(revision=doc.revision, **kwargs)
 
-            if os.path.isdir(settings.MEDIA_ROOT + "uploads/%d" % (doc.pk)):
+            if os.path.isdir(settings.MEDIA_ROOT + "uploads/%d" % doc.pk):
                 shutil.copytree(
                 shutil.copytree(
-                    settings.MEDIA_ROOT + "uploads/%d" % (doc.pk),
-                    settings.MEDIA_ROOT + "uploads/%d" % (new_doc.pk)
+                    settings.MEDIA_ROOT + "uploads/%d" % doc.pk,
+                    settings.MEDIA_ROOT + "uploads/%d" % new_doc.pk
                 )
 
             new_doc.assigned_to = request.user
                 )
 
             new_doc.assigned_to = request.user
@@ -751,6 +506,7 @@ def upcoming(request):
         'objects_list': Document.objects.filter(deleted=False).filter(publish_log=None),
     })
 
         'objects_list': Document.objects.filter(deleted=False).filter(publish_log=None),
     })
 
+
 def finished(request):
     return render(request, "catalogue/finished.html", {
         'objects_list': Document.objects.filter(deleted=False).exclude(publish_log=None),
 def finished(request):
     return render(request, "catalogue/finished.html", {
         'objects_list': Document.objects.filter(deleted=False).exclude(publish_log=None),