1 from datetime import datetime, date, timedelta
4 from StringIO import StringIO
5 from urllib import unquote
6 from urlparse import urlsplit, urlunsplit
8 from django.conf import settings
9 from django.contrib import auth
10 from django.contrib.auth.models import User
11 from django.contrib.auth.decorators import login_required, permission_required
12 from django.core.urlresolvers import reverse
13 from django.db.models import Count, Q
14 from django.db import transaction
15 from django import http
16 from django.http import Http404, HttpResponse, HttpResponseForbidden
17 from django.shortcuts import get_object_or_404, render, render_to_response
18 from django.utils.encoding import iri_to_uri
19 from django.utils.http import urlquote_plus
20 from django.utils.translation import ugettext_lazy as _
21 from django.views.decorators.http import require_POST
22 from django.template import RequestContext
24 from apiclient import NotAuthorizedError
25 from catalogue import forms
26 from catalogue import helpers
27 from catalogue.helpers import active_tab
28 from catalogue.models import Book, Chunk, BookPublishRecord, ChunkPublishRecord, Project
29 from fileupload.views import UploadView
32 # Quick hack around caching problems, TODO: use ETags
34 from django.views.decorators.cache import never_cache
36 logger = logging.getLogger("fnp.catalogue")
41 def document_list(request):
42 return render(request, 'catalogue/document_list.html')
46 def user(request, username):
47 user = get_object_or_404(User, username=username)
48 return render(request, 'catalogue/user_page.html', {"viewed_user": user})
55 return render(request, 'catalogue/my_page.html', {
56 'last_books': sorted(request.session.get("wiki_last_books", {}).items(),
57 key=lambda x: x[1]['time'], reverse=True),
65 return render(request, 'catalogue/user_list.html', {
66 'users': User.objects.all().annotate(count=Count('chunk')).order_by(
67 '-count', 'last_name', 'first_name'),
71 @active_tab('activity')
72 def activity(request, isodate=None):
75 day = helpers.parse_isodate(isodate)
82 next_day = day + timedelta(1)
83 prev_day = day - timedelta(1)
85 return render(request, 'catalogue/activity.html', locals())
89 def logout_then_redirect(request):
91 return http.HttpResponseRedirect(urlquote_plus(request.GET.get('next', '/'), safe='/?='))
94 @permission_required('catalogue.add_book')
96 def create_missing(request, slug=None):
99 slug = slug.replace(' ', '-')
101 if request.method == "POST":
102 form = forms.DocumentCreateForm(request.POST, request.FILES)
105 if request.user.is_authenticated():
106 creator = request.user
110 text=form.cleaned_data['text'],
112 slug=form.cleaned_data['slug'],
113 title=form.cleaned_data['title'],
114 gallery=form.cleaned_data['gallery'],
117 return http.HttpResponseRedirect(reverse("catalogue_book", args=[book.slug]))
119 form = forms.DocumentCreateForm(initial={
121 "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", {
355 def chunk_edit(request, slug, chunk):
357 doc = Chunk.get(slug, chunk)
358 except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
360 if not doc.book.accessible(request):
361 return HttpResponseForbidden("Not authorized.")
363 if request.method == "POST":
364 form = forms.ChunkForm(request.POST, instance=doc)
367 go_next = request.GET.get('next', None)
369 go_next = urlquote_plus(unquote(iri_to_uri(go_next)), safe='/?=&')
371 go_next = doc.book.get_absolute_url()
372 return http.HttpResponseRedirect(go_next)
374 form = forms.ChunkForm(instance=doc)
376 referer = request.META.get('HTTP_REFERER')
378 parts = urlsplit(referer)
379 parts = ['', ''] + list(parts[2:])
380 go_next = urlquote_plus(urlunsplit(parts))
384 return render(request, "catalogue/chunk_edit.html", {
391 @transaction.commit_on_success
392 def chunk_mass_edit(request):
393 if request.method == 'POST':
394 ids = map(int, filter(lambda i: i.strip()!='', request.POST.get('ids').split(',')))
395 chunks = map(lambda i: Chunk.objects.get(id=i), ids)
397 stage = request.POST.get('stage')
400 stage = Chunk.tag_model.objects.get(slug=stage)
401 except Chunk.DoesNotExist, e:
404 for c in chunks: c.stage = stage
406 username = request.POST.get('user')
407 logger.info("username: %s" % username)
408 logger.info(request.POST)
411 user = User.objects.get(username=username)
412 except User.DoesNotExist, e:
415 for c in chunks: c.user = user
417 status = request.POST.get('status')
419 books_affected = set()
421 if status == 'publish':
422 c.head.publishable = True
424 elif status == 'unpublish':
425 c.head.publishable = False
428 books_affected.add(c.book)
429 for b in books_affected:
432 project_id = request.POST.get('project')
435 project = Project.objects.get(pk=int(project_id))
436 except (Project.DoesNotExist, ValueError), e:
440 book.project = project
443 for c in chunks: c.save()
445 return HttpResponse("", content_type="text/plain")
450 @permission_required('catalogue.change_book')
451 def book_append(request, slug):
452 book = get_object_or_404(Book, slug=slug)
453 if not book.accessible(request):
454 return HttpResponseForbidden("Not authorized.")
456 if request.method == "POST":
457 form = forms.BookAppendForm(book, request.POST)
459 append_to = form.cleaned_data['append_to']
460 append_to.append(book)
461 return http.HttpResponseRedirect(append_to.get_absolute_url())
463 form = forms.BookAppendForm(book)
464 return render(request, "catalogue/book_append_to.html", {
474 def publish(request, slug):
475 book = get_object_or_404(Book, slug=slug)
476 if not book.accessible(request):
477 return HttpResponseForbidden("Not authorized.")
480 book.publish(request.user)
481 except NotAuthorizedError:
482 return http.HttpResponseRedirect(reverse('apiclient_oauth'))
483 except BaseException, e:
484 return http.HttpResponse(e)
486 return http.HttpResponseRedirect(book.get_absolute_url())
489 class GalleryView(UploadView):
490 def get_object(self, request, slug):
491 book = get_object_or_404(Book, slug=slug)
496 def breadcrumbs(self):
498 (_('books'), reverse('catalogue_document_list')),
499 (self.object.title, self.object.get_absolute_url()),
500 (_('scan gallery'),),
503 def get_directory(self):
504 return "%s%s/" % (settings.IMAGE_DIR, self.object.gallery)