1 # -*- coding: utf-8 -*-
3 # This file is part of MIL/PEER, licensed under GNU Affero GPLv3 or later.
4 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
10 from django.conf import settings
11 from django.contrib import auth
12 from django.contrib.auth.models import User
13 from django.contrib.auth.decorators import login_required
14 from django.core.urlresolvers import reverse
15 from django import http
16 from django.http import Http404
17 from django.shortcuts import get_object_or_404, render, redirect
18 from django.utils.encoding import force_str
19 from django.utils.http import urlquote_plus
20 from django.views.decorators.http import require_POST
22 from catalogue import forms
23 from catalogue.helpers import active_tab
24 from librarian import BuildError
25 from .constants import STAGES
26 from .models import Document, Plan
27 from dvcs.models import Revision
28 from organizations.models import Organization
29 from fileupload.views import UploadView
32 # Quick hack around caching problems, TODO: use ETags
34 from django.views.decorators.cache import never_cache
35 # from fnpdjango.utils.text.slughifi import slughifi
37 logger = logging.getLogger("fnp.catalogue")
42 def document_list(request):
43 return render(request, 'catalogue/document_list.html')
47 def user(request, username):
48 user = get_object_or_404(User, username=username)
49 return render(request, 'catalogue/user_page.html', {"viewed_user": user})
56 return render(request, 'catalogue/my_page.html', {
58 request.session.get("wiki_last_books", {}).items(), key=lambda x: x[1]['time'], reverse=True),
65 def logout_then_redirect(request):
67 return http.HttpResponseRedirect(urlquote_plus(request.GET.get('next', '/'), safe='/?='))
72 def create_missing(request):
73 if request.method == "POST":
74 form = forms.DocumentCreateForm(request.POST, request.FILES)
77 if request.user.is_authenticated():
78 creator = request.user
82 title = form.cleaned_data['title']
84 org = request.user.membership_set.get(
85 organization=int(form.cleaned_data['owner_organization'])).organization
86 kwargs = {'owner_organization': org}
88 kwargs = {'owner_user': request.user}
90 doc = Document.objects.create(**kwargs)
92 cover = request.FILES.get('cover')
94 uppath = 'uploads/%d/' % doc.pk
95 path = settings.MEDIA_ROOT + uppath
96 if not os.path.isdir(path):
98 dest_path = path + cover.name # UNSAFE
99 with open(dest_path, 'w') as destination:
100 for chunk in cover.chunks():
101 destination.write(chunk)
102 cover_url = 'http://milpeer.eu/media/dynamic/' + uppath + cover.name
107 text='''<section xmlns="http://nowoczesnapolska.org.pl/sst#" xmlns:dc="http://purl.org/dc/elements/1.1/">
109 <dc:publisher>''' + form.cleaned_data['publisher'] + '''</dc:publisher>
110 <dc:description>''' + form.cleaned_data['description'] + '''</dc:description>
111 <dc:language>''' + form.cleaned_data['language'] + '''</dc:language>
112 <dc:rights>''' + form.cleaned_data['rights'] + '''</dc:rights>
113 <dc:audience>''' + form.cleaned_data['audience'] + '''</dc:audience>
114 <dc:relation.coverImage.url>''' + cover_url + '''</dc:relation.coverImage.url>
116 <header>''' + title + '''</header>
117 <div class="p"> </div>
121 doc.assigned_to = request.user
124 return http.HttpResponseRedirect(reverse("wiki_editor", args=[doc.pk]))
126 org_pk = request.GET.get('organization')
129 org = Organization.objects.get(pk=org_pk)
130 except Organization.DoesNotExist:
133 if not org.is_member(request.user):
140 form = forms.DocumentCreateForm(initial={'owner_organization': org})
142 return render(request, "catalogue/document_create_missing.html", {
150 def book_html(request, pk, rev_pk=None, preview=False):
151 from librarian.document import Document as SST
152 from librarian.formats.html import HtmlFormat
154 doc = get_object_or_404(Document, pk=pk, deleted=False)
157 published_revision = doc.publish_log.all()[0].revision
159 published_revision = None
163 revision = doc.revision
165 if published_revision is not None:
166 revision = published_revision
168 # No published version, fallback to preview mode.
170 revision = doc.revision
172 revision = get_object_or_404(Revision, pk=rev_pk)
174 was_published = revision == published_revision or doc.publish_log.filter(revision=revision).exists()
176 sst = SST.from_string(revision.materialize())
177 html = HtmlFormat(sst).build(
178 files_path='http://%s/media/dynamic/uploads/%s/' % (request.get_host(), pk)).get_string()
180 # response = http.HttpResponse(html, content_type='text/html', mimetype='text/html')
183 # for fragment in book.fragments.all().iterator():
184 # for theme in fragment.tags.filter(category='theme').iterator():
185 # book_themes.setdefault(theme, []).append(fragment)
187 # book_themes = book_themes.items()
188 # book_themes.sort(key=lambda s: s[0].sort_key)
189 return render(request, 'catalogue/book_text.html', {
192 'revision': revision,
193 'published_revision': published_revision,
194 'specific': rev_pk is not None,
196 'can_edit': doc.can_edit(request.user) if doc else None,
197 'was_published': was_published,
202 def book_pdf(request, pk, rev_pk):
203 from librarian.utils import Context
204 from librarian.document import Document as SST
205 from librarian.formats.pdf import PdfFormat
207 doc = get_object_or_404(Document, pk=pk)
208 rev = get_object_or_404(Revision, pk=rev_pk)
211 sst = SST.from_string(rev.materialize())
214 files_path='http://%s/media/dynamic/uploads/%s/' % (request.get_host(), pk),
215 source_url='http://%s%s' % (request.get_host(), reverse('catalogue_html', args=[doc.pk])),
217 if doc.owner_organization is not None and doc.owner_organization.logo:
218 ctx.cover_logo = 'http://%s%s' % (request.get_host(), doc.owner_organization.logo.url)
219 pdf_file = PdfFormat(sst).build(ctx)
221 from catalogue.ebook_utils import serve_file
222 return serve_file(pdf_file.get_filename(), '%d.pdf' % doc.pk, 'application/pdf')
226 def book_epub(request, pk, rev_pk):
227 from librarian.utils import Context
228 from librarian.document import Document as SST
229 from librarian.formats.epub import EpubFormat
231 doc = get_object_or_404(Document, pk=pk)
232 rev = get_object_or_404(Revision, pk=rev_pk)
235 sst = SST.from_string(rev.materialize())
238 files_path='http://%s/media/dynamic/uploads/%s/' % (request.get_host(), pk),
239 source_url='http://%s%s' % (request.get_host(), reverse('catalogue_html', args=[doc.pk])),
241 if doc.owner_organization is not None and doc.owner_organization.logo:
242 ctx.cover_logo = 'http://%s%s' % (request.get_host(), doc.owner_organization.logo.url)
244 epub_file = EpubFormat(sst).build(ctx)
245 except BuildError as e:
246 from django.http import HttpResponse
247 return HttpResponse(content=force_str(e.message), content_type='text/plain', status='400')
249 from catalogue.ebook_utils import serve_file
250 return serve_file(epub_file.get_filename(), '%d.epub' % doc.pk, 'application/epub+zip')
254 # def revision(request, slug, chunk=None):
256 # doc = Chunk.get(slug, chunk)
257 # except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
259 # if not doc.book.accessible(request):
260 # return HttpResponseForbidden("Not authorized.")
261 # return http.HttpResponse(str(doc.revision()))
265 def book_schedule(request, pk):
266 book = get_object_or_404(Document, pk=pk, deleted=False)
267 if request.method == 'POST':
268 Plan.objects.filter(document=book).delete()
269 for i, s in enumerate(STAGES):
270 user_id = request.POST.get('s%d-user' % i)
271 deadline = request.POST.get('s%d-deadline' % i) or None
272 Plan.objects.create(document=book, stage=s, user_id=user_id, deadline=deadline)
274 book.set_stage(request.POST.get('stage', ''))
275 return redirect('catalogue_user')
278 for p in Plan.objects.filter(document=book):
279 current[p.stage] = (getattr(p.user, 'pk', None), (p.deadline.isoformat() if p.deadline else None))
281 schedule = [(i, s, current.get(s, ())) for (i, s) in enumerate(STAGES)]
283 if book.owner_organization:
284 people = [m.user for m in book.owner_organization.membership_set.exclude(status='pending')]
286 people = [book.owner_user]
287 return render(request, 'catalogue/book_schedule.html', {
289 'schedule': schedule,
295 def book_owner(request, pk):
296 doc = get_object_or_404(Document, pk=pk, deleted=False)
297 user_is_owner = doc.owner_organization and doc.owner_organization.is_member(request.user)
298 if not (doc.owner_user == request.user or user_is_owner):
303 if request.method == 'POST':
305 new_org_pk = request.POST.get('owner_organization')
307 doc.owner_organization = None
308 doc.owner_user = request.user
311 org = Organization.objects.get(pk=new_org_pk)
312 if not org.is_member(request.user):
313 error = 'Bad organization'
315 doc.owner_organization = org
316 doc.owner_user = None
319 return redirect('catalogue_user')
321 return render(request, 'catalogue/book_owner.html', {
328 def book_delete(request, pk):
329 doc = get_object_or_404(Document, pk=pk, deleted=False)
330 if not (doc.owner_user == request.user or doc.owner_organization.is_member(request.user)):
333 if request.method == 'POST':
336 return redirect('catalogue_user')
338 return render(request, 'catalogue/book_delete.html', {
345 def publish(request, pk):
346 from wiki import forms
347 from .models import PublishRecord
348 from dvcs.models import Revision
350 # FIXME: check permissions
352 doc = get_object_or_404(Document, pk=pk, deleted=False)
353 form = forms.DocumentTextPublishForm(request.POST, prefix="textpublish")
355 rev = Revision.objects.get(pk=form.cleaned_data['revision'])
356 # FIXME: check if in tree
357 # if PublishRecord.objects.filter(revision=rev, document=doc).exists():
358 # return http.HttpResponse('exists')
359 PublishRecord.objects.create(revision=rev, document=doc, user=request.user)
360 if request.is_ajax():
361 return http.HttpResponse('ok')
363 return redirect('catalogue_html', doc.pk)
365 if request.is_ajax():
366 return http.HttpResponse('error')
369 return redirect('catalogue_preview_rev', doc.pk, form.cleaned_data['revision'])
371 return redirect('catalogue_preview', doc.pk)
376 def unpublish(request, pk):
377 # FIXME: check permissions
379 doc = get_object_or_404(Document, pk=pk, deleted=False)
380 doc.publish_log.all().delete()
381 if request.is_ajax():
382 return http.HttpResponse('ok')
384 return redirect('catalogue_html', doc.pk)
387 class GalleryMixin(object):
388 def get_directory(self):
389 # return "%s%s/" % (settings.IMAGE_DIR, 'org%d' % self.org.pk if self.org is not None else self.request.user.pk)
390 return "uploads/%d/" % self.doc.pk
393 class GalleryView(GalleryMixin, UploadView):
395 def breadcrumbs(self):
397 (self.doc.meta()['title'], '/documents/%d/' % self.doc.pk),
400 def get_object(self, request, pk=None):
401 self.doc = Document.objects.get(pk=pk, deleted=False)
405 def fork(request, pk):
406 doc = get_object_or_404(Document, pk=pk, deleted=False)
407 if request.method == "POST":
408 form = forms.DocumentForkForm(request.POST, request.FILES)
411 org = request.user.membership_set.get(
412 organization=int(form.cleaned_data['owner_organization'])).organization
413 kwargs = {'owner_organization': org}
415 kwargs = {'owner_user': request.user}
417 new_doc = Document.objects.create(revision=doc.revision, **kwargs)
419 if os.path.isdir(settings.MEDIA_ROOT + "uploads/%d" % doc.pk):
421 settings.MEDIA_ROOT + "uploads/%d" % doc.pk,
422 settings.MEDIA_ROOT + "uploads/%d" % new_doc.pk
425 new_doc.assigned_to = request.user
428 return http.HttpResponseRedirect(reverse("wiki_editor", args=[new_doc.pk]))
430 form = forms.DocumentForkForm()
432 return render(request, "catalogue/document_fork.html", {
439 def upcoming(request):
440 return render(request, "catalogue/upcoming.html", {
441 'objects_list': Document.objects.filter(deleted=False).filter(publish_log=None),
445 def finished(request):
446 return render(request, "catalogue/finished.html", {
447 'objects_list': Document.objects.filter(deleted=False).exclude(publish_log=None),