1 # -*- coding: utf-8 -*-
2 from datetime import date, timedelta
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
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, ajax
28 from catalogue.models import Book, Chunk, Project
29 from fileupload.views import UploadView, PackageView
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', {
57 request.session.get("wiki_last_books", {}).items(),
58 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("wiki_editor", args=[book.slug]))
119 form = forms.DocumentCreateForm(initial={
121 "title": slug.replace('-', ' ').title(),
124 return render(request, "catalogue/document_create_missing.html", {
132 @permission_required('catalogue.add_book')
133 @active_tab('upload')
135 if request.method == "POST":
136 form = forms.DocumentsUploadForm(request.POST, request.FILES)
138 from slughifi import slughifi
140 if request.user.is_authenticated():
141 creator = request.user
145 zip = form.cleaned_data['zip']
150 existing = [book.slug for book in Book.objects.all()]
151 for filename in zip.namelist():
152 if filename[-1] == '/':
154 title = os.path.basename(filename)[:-4]
155 slug = slughifi(title)
156 if not (slug and filename.endswith('.xml')):
157 skipped_list.append(filename)
159 error_list.append((filename, slug, _('Slug already used for %s' % slugs[slug])))
160 elif slug in existing:
161 error_list.append((filename, slug, _('Slug already used in repository.')))
164 zip.read(filename).decode('utf-8') # test read
165 ok_list.append((filename, slug, title))
166 except UnicodeDecodeError:
167 error_list.append((filename, title, _('File should be UTF-8 encoded.')))
168 slugs[slug] = filename
171 for filename, slug, title in ok_list:
173 text=zip.read(filename).decode('utf-8'),
179 return render(request, "catalogue/document_upload.html", {
182 "skipped_list": skipped_list,
183 "error_list": error_list,
188 form = forms.DocumentsUploadForm()
190 return render(request, "catalogue/document_upload.html", {
198 def book_xml(request, slug):
199 book = get_object_or_404(Book, slug=slug)
200 if not book.accessible(request):
201 return HttpResponseForbidden("Not authorized.")
202 xml = book.materialize()
204 response = http.HttpResponse(xml, content_type='application/xml', mimetype='application/wl+xml')
205 response['Content-Disposition'] = 'attachment; filename=%s.xml' % slug
210 def book_txt(request, slug):
211 book = get_object_or_404(Book, slug=slug)
212 if not book.accessible(request):
213 return HttpResponseForbidden("Not authorized.")
215 doc = book.wldocument()
216 text = doc.as_text().get_string()
217 response = http.HttpResponse(text, content_type='text/plain', mimetype='text/plain')
218 response['Content-Disposition'] = 'attachment; filename=%s.txt' % slug
223 def book_html(request, slug):
224 book = get_object_or_404(Book, slug=slug)
225 if not book.accessible(request):
226 return HttpResponseForbidden("Not authorized.")
228 doc = book.wldocument(parse_dublincore=False)
231 html = html.get_string() if html is not None else ''
232 # response = http.HttpResponse(html, content_type='text/html', mimetype='text/html')
235 # for fragment in book.fragments.all().iterator():
236 # for theme in fragment.tags.filter(category='theme').iterator():
237 # book_themes.setdefault(theme, []).append(fragment)
239 # book_themes = book_themes.items()
240 # book_themes.sort(key=lambda s: s[0].sort_key)
241 return render_to_response(
242 '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(), book.slug + '.pdf', 'application/pdf')
261 def book_epub(request, slug):
262 book = get_object_or_404(Book, slug=slug)
263 if not book.accessible(request):
264 return HttpResponseForbidden("Not authorized.")
266 # TODO: move to celery
267 doc = book.wldocument()
268 # TODO: error handling
269 epub = doc.as_epub().get_string()
270 response = HttpResponse(mimetype='application/epub+zip')
271 response['Content-Disposition'] = 'attachment; filename=%s' % book.slug + '.epub'
277 def revision(request, slug, chunk=None):
279 doc = Chunk.get(slug, chunk)
280 except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
282 if not doc.book.accessible(request):
283 return HttpResponseForbidden("Not authorized.")
284 return http.HttpResponse(str(doc.revision()))
287 def book(request, slug):
288 book = get_object_or_404(Book, slug=slug)
289 if not book.accessible(request):
290 return HttpResponseForbidden("Not authorized.")
292 if request.user.has_perm('catalogue.change_book'):
293 if request.method == "POST":
294 form = forms.BookForm(request.POST, instance=book)
297 return http.HttpResponseRedirect(book.get_absolute_url())
299 form = forms.BookForm(instance=book)
302 form = forms.ReadonlyBookForm(instance=book)
305 publish_error = book.publishable_error()
306 publishable = publish_error is None
308 return render(request, "catalogue/book_detail.html", {
310 "publishable": publishable,
311 "publishable_error": publish_error,
313 "editable": editable,
317 @permission_required('catalogue.add_chunk')
318 def chunk_add(request, slug, chunk):
320 doc = Chunk.get(slug, chunk)
321 except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
323 if not doc.book.accessible(request):
324 return HttpResponseForbidden("Not authorized.")
326 if request.method == "POST":
327 form = forms.ChunkAddForm(request.POST, instance=doc)
329 if request.user.is_authenticated():
330 creator = request.user
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:
409 username = request.POST.get('user')
410 logger.info("username: %s" % username)
411 logger.info(request.POST)
414 user = User.objects.get(username=username)
415 except User.DoesNotExist, e:
421 status = request.POST.get('status')
423 books_affected = set()
425 if status == 'publish':
426 c.head.publishable = True
428 elif status == 'unpublish':
429 c.head.publishable = False
432 books_affected.add(c.book)
433 for b in books_affected:
436 project_id = request.POST.get('project')
439 project = Project.objects.get(pk=int(project_id))
440 except (Project.DoesNotExist, ValueError), e:
444 book.project = project
450 return HttpResponse("", content_type="text/plain")
455 @permission_required('catalogue.change_book')
456 def book_append(request, slug):
457 book = get_object_or_404(Book, slug=slug)
458 if not book.accessible(request):
459 return HttpResponseForbidden("Not authorized.")
461 if request.method == "POST":
462 form = forms.BookAppendForm(book, request.POST)
464 append_to = form.cleaned_data['append_to']
465 append_to.append(book)
466 return http.HttpResponseRedirect(append_to.get_absolute_url())
468 form = forms.BookAppendForm(book)
469 return render(request, "catalogue/book_append_to.html", {
479 def publish(request, slug):
480 book = get_object_or_404(Book, slug=slug)
481 if not book.accessible(request):
482 return HttpResponseForbidden("Not authorized.")
485 book.publish(request.user)
486 except NotAuthorizedError:
487 return http.HttpResponseRedirect(reverse('apiclient_oauth'))
488 except BaseException, e:
489 return http.HttpResponse(e)
491 return http.HttpResponseRedirect(book.get_absolute_url())
494 class GalleryMixin(object):
495 def get_directory(self):
496 return "%s%s/" % (settings.IMAGE_DIR, self.object.gallery)
499 def get_object(request, slug):
500 book = get_object_or_404(Book, slug=slug)
506 class GalleryView(GalleryMixin, UploadView):
508 def breadcrumbs(self):
510 (u'moduły', reverse('catalogue_document_list')),
511 (self.object.title, self.object.get_absolute_url()),
516 class GalleryPackageView(GalleryMixin, PackageView):
518 def get_redirect_url(self, slug):
519 return reverse('catalogue_book_gallery', kwargs={'slug': slug})
523 def lessons_for_cybernauts(request):
524 books = Book.objects.filter(for_cybernauts=True)
528 changes = book.get_current_changes()
529 time_changed = max(change.created_at for change in changes)
530 xml_url = reverse('catalogue_book_xml', args=[book.slug])
534 'time_changed': time_changed.isoformat(),
536 except Book.NoTextError:
538 return {'lessons': data}