1 # -*- coding: utf-8 -*-
2 from datetime import datetime, date, timedelta
5 from StringIO import StringIO
6 from urllib import unquote
7 from urlparse import urlsplit, urlunsplit
9 from django.conf import settings
10 from django.contrib import auth
11 from django.contrib.auth.models import User
12 from django.contrib.auth.decorators import login_required, permission_required
13 from django.core.urlresolvers import reverse
14 from django.db.models import Count, Q
15 from django.db import transaction
16 from django import http
17 from django.http import Http404, HttpResponse, HttpResponseForbidden
18 from django.shortcuts import get_object_or_404, render, render_to_response
19 from django.utils.encoding import iri_to_uri
20 from django.utils.http import urlquote_plus
21 from django.utils.translation import ugettext_lazy as _
22 from django.views.decorators.http import require_POST
23 from django.template import RequestContext
25 from apiclient import NotAuthorizedError
26 from catalogue import forms
27 from catalogue import helpers
28 from catalogue.helpers import active_tab
29 from catalogue.models import Book, Chunk, BookPublishRecord, ChunkPublishRecord, Project
30 from fileupload.views import UploadView, PackageView
33 # Quick hack around caching problems, TODO: use ETags
35 from django.views.decorators.cache import never_cache
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', {
57 'last_books': sorted(request.session.get("wiki_last_books", {}).items(),
58 key=lambda x: x[1]['time'], reverse=True),
66 return render(request, 'catalogue/user_list.html', {
67 'users': User.objects.all().annotate(count=Count('chunk')).order_by(
68 '-count', 'last_name', 'first_name'),
72 @active_tab('activity')
73 def activity(request, isodate=None):
76 day = helpers.parse_isodate(isodate)
83 next_day = day + timedelta(1)
84 prev_day = day - timedelta(1)
86 return render(request, 'catalogue/activity.html', locals())
90 def logout_then_redirect(request):
92 return http.HttpResponseRedirect(urlquote_plus(request.GET.get('next', '/'), safe='/?='))
95 @permission_required('catalogue.add_book')
97 def create_missing(request, slug=None):
100 slug = slug.replace(' ', '-')
102 if request.method == "POST":
103 form = forms.DocumentCreateForm(request.POST, request.FILES)
106 if request.user.is_authenticated():
107 creator = request.user
111 text=form.cleaned_data['text'],
113 slug=form.cleaned_data['slug'],
114 title=form.cleaned_data['title'],
115 gallery=form.cleaned_data['gallery'],
118 return http.HttpResponseRedirect(reverse("wiki_editor", args=[book.slug]))
120 form = forms.DocumentCreateForm(initial={
122 "title": slug.replace('-', ' ').title(),
125 return render(request, "catalogue/document_create_missing.html", {
133 @permission_required('catalogue.add_book')
134 @active_tab('upload')
136 if request.method == "POST":
137 form = forms.DocumentsUploadForm(request.POST, request.FILES)
141 if request.user.is_authenticated():
142 creator = request.user
146 zip = form.cleaned_data['zip']
151 existing = [book.slug for book in Book.objects.all()]
152 for filename in zip.namelist():
153 if filename[-1] == '/':
155 title = os.path.basename(filename)[:-4]
156 slug = slughifi(title)
157 if not (slug and filename.endswith('.xml')):
158 skipped_list.append(filename)
160 error_list.append((filename, slug, _('Slug already used for %s' % slugs[slug])))
161 elif slug in existing:
162 error_list.append((filename, slug, _('Slug already used in repository.')))
165 zip.read(filename).decode('utf-8') # test read
166 ok_list.append((filename, slug, title))
167 except UnicodeDecodeError:
168 error_list.append((filename, title, _('File should be UTF-8 encoded.')))
169 slugs[slug] = filename
172 for filename, slug, title in ok_list:
174 text=zip.read(filename).decode('utf-8'),
180 return render(request, "catalogue/document_upload.html", {
183 "skipped_list": skipped_list,
184 "error_list": error_list,
189 form = forms.DocumentsUploadForm()
191 return render(request, "catalogue/document_upload.html", {
199 def book_xml(request, slug):
200 book = get_object_or_404(Book, slug=slug)
201 if not book.accessible(request):
202 return HttpResponseForbidden("Not authorized.")
203 xml = book.materialize()
205 response = http.HttpResponse(xml, content_type='application/xml', mimetype='application/wl+xml')
206 response['Content-Disposition'] = 'attachment; filename=%s.xml' % slug
211 def book_txt(request, slug):
212 book = get_object_or_404(Book, slug=slug)
213 if not book.accessible(request):
214 return HttpResponseForbidden("Not authorized.")
216 doc = book.wldocument()
217 text = doc.as_text().get_string()
218 response = http.HttpResponse(text, content_type='text/plain', mimetype='text/plain')
219 response['Content-Disposition'] = 'attachment; filename=%s.txt' % slug
224 def book_html(request, slug):
225 book = get_object_or_404(Book, slug=slug)
226 if not book.accessible(request):
227 return HttpResponseForbidden("Not authorized.")
229 doc = book.wldocument(parse_dublincore=False)
232 html = html.get_string() if html is not None else ''
233 # response = http.HttpResponse(html, content_type='text/html', mimetype='text/html')
236 # for fragment in book.fragments.all().iterator():
237 # for theme in fragment.tags.filter(category='theme').iterator():
238 # book_themes.setdefault(theme, []).append(fragment)
240 # book_themes = book_themes.items()
241 # book_themes.sort(key=lambda s: s[0].sort_key)
242 return render_to_response('catalogue/book_text.html', locals(),
243 context_instance=RequestContext(request))
247 def book_pdf(request, slug):
248 book = get_object_or_404(Book, slug=slug)
249 if not book.accessible(request):
250 return HttpResponseForbidden("Not authorized.")
252 # TODO: move to celery
253 doc = book.wldocument()
254 # TODO: error handling
255 pdf_file = doc.as_pdf()
256 from catalogue.ebook_utils import serve_file
257 return serve_file(pdf_file.get_filename(),
258 book.slug + '.pdf', 'application/pdf')
262 def book_epub(request, slug):
263 book = get_object_or_404(Book, slug=slug)
264 if not book.accessible(request):
265 return HttpResponseForbidden("Not authorized.")
267 # TODO: move to celery
268 doc = book.wldocument()
269 # TODO: error handling
270 epub = doc.as_epub().get_string()
271 response = HttpResponse(mimetype='application/epub+zip')
272 response['Content-Disposition'] = 'attachment; filename=%s' % book.slug + '.epub'
278 def revision(request, slug, chunk=None):
280 doc = Chunk.get(slug, chunk)
281 except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
283 if not doc.book.accessible(request):
284 return HttpResponseForbidden("Not authorized.")
285 return http.HttpResponse(str(doc.revision()))
288 def book(request, slug):
289 book = get_object_or_404(Book, slug=slug)
290 if not book.accessible(request):
291 return HttpResponseForbidden("Not authorized.")
293 if request.user.has_perm('catalogue.change_book'):
294 if request.method == "POST":
295 form = forms.BookForm(request.POST, instance=book)
298 return http.HttpResponseRedirect(book.get_absolute_url())
300 form = forms.BookForm(instance=book)
303 form = forms.ReadonlyBookForm(instance=book)
306 publish_error = book.publishable_error()
307 publishable = publish_error is None
309 return render(request, "catalogue/book_detail.html", {
311 "publishable": publishable,
312 "publishable_error": publish_error,
314 "editable": editable,
318 @permission_required('catalogue.add_chunk')
319 def chunk_add(request, slug, chunk):
321 doc = Chunk.get(slug, chunk)
322 except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
324 if not doc.book.accessible(request):
325 return HttpResponseForbidden("Not authorized.")
327 if request.method == "POST":
328 form = forms.ChunkAddForm(request.POST, instance=doc)
330 if request.user.is_authenticated():
331 creator = request.user
334 doc.split(creator=creator,
335 slug=form.cleaned_data['slug'],
336 title=form.cleaned_data['title'],
337 gallery_start=form.cleaned_data['gallery_start'],
338 user=form.cleaned_data['user'],
339 stage=form.cleaned_data['stage']
342 return http.HttpResponseRedirect(doc.book.get_absolute_url())
344 form = forms.ChunkAddForm(initial={
345 "slug": str(doc.number + 1),
346 "title": "cz. %d" % (doc.number + 1, ),
349 return render(request, "catalogue/chunk_add.html", {
356 def chunk_edit(request, slug, chunk):
358 doc = Chunk.get(slug, chunk)
359 except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
361 if not doc.book.accessible(request):
362 return HttpResponseForbidden("Not authorized.")
364 if request.method == "POST":
365 form = forms.ChunkForm(request.POST, instance=doc)
368 go_next = request.GET.get('next', None)
370 go_next = urlquote_plus(unquote(iri_to_uri(go_next)), safe='/?=&')
372 go_next = doc.book.get_absolute_url()
373 return http.HttpResponseRedirect(go_next)
375 form = forms.ChunkForm(instance=doc)
377 referer = request.META.get('HTTP_REFERER')
379 parts = urlsplit(referer)
380 parts = ['', ''] + list(parts[2:])
381 go_next = urlquote_plus(urlunsplit(parts))
385 return render(request, "catalogue/chunk_edit.html", {
392 @transaction.commit_on_success
394 def chunk_mass_edit(request):
395 if request.method == 'POST':
396 ids = map(int, filter(lambda i: i.strip()!='', request.POST.get('ids').split(',')))
397 chunks = map(lambda i: Chunk.objects.get(id=i), ids)
399 stage = request.POST.get('stage')
402 stage = Chunk.tag_model.objects.get(slug=stage)
403 except Chunk.DoesNotExist, e:
406 for c in chunks: c.stage = stage
408 username = request.POST.get('user')
409 logger.info("username: %s" % username)
410 logger.info(request.POST)
413 user = User.objects.get(username=username)
414 except User.DoesNotExist, e:
417 for c in chunks: c.user = user
419 status = request.POST.get('status')
421 books_affected = set()
423 if status == 'publish':
424 c.head.publishable = True
426 elif status == 'unpublish':
427 c.head.publishable = False
430 books_affected.add(c.book)
431 for b in books_affected:
434 project_id = request.POST.get('project')
437 project = Project.objects.get(pk=int(project_id))
438 except (Project.DoesNotExist, ValueError), e:
442 book.project = project
445 for c in chunks: c.save()
447 return HttpResponse("", content_type="text/plain")
452 @permission_required('catalogue.change_book')
453 def book_append(request, slug):
454 book = get_object_or_404(Book, slug=slug)
455 if not book.accessible(request):
456 return HttpResponseForbidden("Not authorized.")
458 if request.method == "POST":
459 form = forms.BookAppendForm(book, request.POST)
461 append_to = form.cleaned_data['append_to']
462 append_to.append(book)
463 return http.HttpResponseRedirect(append_to.get_absolute_url())
465 form = forms.BookAppendForm(book)
466 return render(request, "catalogue/book_append_to.html", {
476 def publish(request, slug):
477 book = get_object_or_404(Book, slug=slug)
478 if not book.accessible(request):
479 return HttpResponseForbidden("Not authorized.")
482 book.publish(request.user)
483 except NotAuthorizedError:
484 return http.HttpResponseRedirect(reverse('apiclient_oauth'))
485 except BaseException, e:
486 return http.HttpResponse(e)
488 return http.HttpResponseRedirect(book.get_absolute_url())
491 class GalleryMixin(object):
492 def get_directory(self):
493 return "%s%s/" % (settings.IMAGE_DIR, self.object.gallery)
494 def get_object(self, request, slug):
495 book = get_object_or_404(Book, slug=slug)
500 class GalleryView(GalleryMixin, UploadView):
502 def breadcrumbs(self):
504 (u'moduły', reverse('catalogue_document_list')),
505 (self.object.title, self.object.get_absolute_url()),
510 class GalleryPackageView(GalleryMixin, PackageView):
512 def get_redirect_url(self, slug):
513 return reverse('catalogue_book_gallery', kwargs = dict(slug=slug))