X-Git-Url: https://git.mdrn.pl/wolnelektury.git/blobdiff_plain/94bfc24e8c353a678b72f37c86adf9a4a7a78ffd..47f8090f9e8c6e91ae95578ca43664b6b18fe795:/apps/catalogue/views.py diff --git a/apps/catalogue/views.py b/apps/catalogue/views.py index b61d5e803..b4fb0924c 100644 --- a/apps/catalogue/views.py +++ b/apps/catalogue/views.py @@ -1,7 +1,14 @@ # -*- coding: utf-8 -*- +# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# import tempfile import zipfile +import sys +import pprint +import traceback +from django.conf import settings from django.template import RequestContext from django.shortcuts import render_to_response, get_object_or_404 from django.http import HttpResponse, HttpResponseRedirect, Http404 @@ -37,10 +44,10 @@ def main_page(request): if request.user.is_authenticated(): shelves = models.Tag.objects.filter(category='set', user=request.user) new_set_form = forms.NewSetForm() - extra_where = 'NOT catalogue_tag.category = "set"' + extra_where = "NOT catalogue_tag.category = 'set'" tags = models.Tag.objects.usage_for_model(models.Book, counts=True, extra={'where': [extra_where]}) fragment_tags = models.Tag.objects.usage_for_model(models.Fragment, counts=True, - extra={'where': ['catalogue_tag.category = "theme"'] + [extra_where]}) + extra={'where': ["catalogue_tag.category = 'theme'"] + [extra_where]}) categories = split_tags(tags) form = forms.SearchForm() @@ -79,10 +86,12 @@ def tagged_object_list(request, tags=''): theme_is_set = len([tag for tag in tags if tag.category == 'theme']) > 0 if theme_is_set: model = models.Fragment + only_author = len(tags) == 1 and tags[0].category == 'author' + pd_counter = only_author and tags[0].goes_to_pd() user_is_owner = (len(shelf) and request.user.is_authenticated() and request.user == shelf[0].user) - extra_where = 'catalogue_tag.category NOT IN ("set", "book")' + extra_where = "catalogue_tag.category NOT IN ('set', 'book')" related_tags = models.Tag.objects.related_for_model(tags, model, counts=True, extra={'where': [extra_where]}) categories = split_tags(related_tags) @@ -95,7 +104,14 @@ def tagged_object_list(request, tags=''): queryset_or_model=model, tags=tags, template_name='catalogue/tagged_object_list.html', - extra_context = {'categories': categories, 'shelf_is_set': shelf_is_set, 'user_is_owner': user_is_owner }, + extra_context = { + 'categories': categories, + 'shelf_is_set': shelf_is_set, + 'only_author': only_author, + 'pd_counter': pd_counter, + 'user_is_owner': user_is_owner, + 'formats_form': forms.DownloadFormatsForm(), + }, ) @@ -111,12 +127,16 @@ def book_fragments(request, book_slug, theme_slug): def book_detail(request, slug): - book = get_object_or_404(models.Book, slug=slug) + try: + book = models.Book.objects.get(slug=slug) + except models.Book.DoesNotExist: + return book_stub_detail(request, slug) + book_tag = get_object_or_404(models.Tag, slug = 'l-' + slug) tags = list(book.tags.filter(~Q(category='set'))) categories = split_tags(tags) book_children = book.children.all().order_by('parent_number') - extra_where = 'catalogue_tag.category = "theme"' + extra_where = "catalogue_tag.category = 'theme'" book_themes = models.Tag.objects.related_for_model(book_tag, models.Fragment, counts=True, extra={'where': [extra_where]}) extra_info = book.get_extra_info_value() @@ -125,6 +145,15 @@ def book_detail(request, slug): context_instance=RequestContext(request)) +def book_stub_detail(request, slug): + book = get_object_or_404(models.BookStub, slug=slug) + pd_counter = book.pd + form = forms.SearchForm() + + return render_to_response('catalogue/book_stub_detail.html', locals(), + context_instance=RequestContext(request)) + + def book_text(request, slug): book = get_object_or_404(models.Book, slug=slug) book_themes = {} @@ -141,40 +170,67 @@ def book_text(request, slug): # ========== # = Search = # ========== +def _word_starts_with(name, prefix): + """returns a Q object gettings models having `name` contain a word + starting with `prefix` + """ + kwargs = {} + if settings.DATABASE_ENGINE in ('mysql', 'postgresql_psycopg2', 'postgresql'): + # we must escape `prefix` so that it only matches literally + for special in r'\^$.*+?|(){}[]': + prefix = prefix.replace(special, '\\' + special) + + # we could use a [[:<:]] (word start), + # but we want both `xy` and `(xy` to catch `(xyz)` + kwargs['%s__iregex' % name] = u"(^|[^[:alpha:]])%s" % prefix + else: + # don't know how to do a generic regex + # checking for simple icontain instead + kwargs['%s__icontains' % name] = prefix + return Q(**kwargs) + + def _tags_starting_with(prefix, user): - books = models.Book.objects.filter(title__icontains=prefix) - tags = models.Tag.objects.filter(name__icontains=prefix) + book_stubs = models.BookStub.objects.filter(_word_starts_with('title', prefix)) + books = models.Book.objects.filter(_word_starts_with('title', prefix)) + book_stubs = filter(lambda x: x not in books, book_stubs) + tags = models.Tag.objects.filter(_word_starts_with('name', prefix)) if user.is_authenticated(): tags = tags.filter(~Q(category='book') & (~Q(category='set') | Q(user=user))) else: tags = tags.filter(~Q(category='book') & ~Q(category='set')) - return list(books) + list(tags) + return list(books) + list(tags) + list(book_stubs) def search(request): tags = request.GET.get('tags', '') prefix = request.GET.get('q', '') - # Prefix must have at least 2 characters - if len(prefix) < 2: - return HttpResponse('') try: tag_list = models.Tag.get_tag_list(tags) except: tag_list = [] + + # Prefix must have at least 2 characters + if len(prefix) < 2: + return render_to_response('catalogue/search_no_hits.html', {'query':prefix, 'tags':tag_list}, + context_instance=RequestContext(request)) result = _tags_starting_with(prefix, request.user) if len(result) > 0: tag = result[0] - if isinstance(tag, models.Book): + if isinstance(tag, models.Book) or isinstance(tag, models.BookStub): return HttpResponseRedirect(tag.get_absolute_url()) else: tag_list.append(tag) - - return HttpResponseRedirect(reverse('catalogue.views.tagged_object_list', - kwargs={'tags': '/'.join(tag.slug for tag in tag_list)} - )) + + return HttpResponseRedirect(reverse('catalogue.views.tagged_object_list', + kwargs={'tags': '/'.join(tag.slug for tag in tag_list)} + )) + else: + return render_to_response('catalogue/search_no_hits.html', {'query':prefix, 'tags':tag_list}, + context_instance=RequestContext(request)) def tags_starting_with(request): @@ -240,12 +296,28 @@ def remove_from_shelf(request, shelf, book): book = get_object_or_404(models.Book, slug=book) shelf = get_object_or_404(models.Tag, slug=shelf, category='set', user=request.user) - models.Tag.objects.remove_tag(book, shelf) - - shelf.book_count -= 1 - shelf.save() - - return HttpResponse('Usunieto') + if shelf in book.tags: + models.Tag.objects.remove_tag(book, shelf) + + shelf.book_count -= 1 + shelf.save() + + return HttpResponse('Usunięto') + else: + return HttpResponse('Książki nie ma na półce') + + +def collect_books(books): + """ + Returns all real books in collection. + """ + result = [] + for book in books: + if len(book.children.all()) == 0: + result.append(book) + else: + result += collect_books(book.children.all()) + return result @cache.never_cache @@ -256,31 +328,34 @@ def download_shelf(request, slug): be used for large dynamic PDF files. """ shelf = get_object_or_404(models.Tag, slug=slug, category='set') - + + formats = [] + form = forms.DownloadFormatsForm(request.GET) + if form.is_valid(): + formats = form.cleaned_data['formats'] + if len(formats) == 0: + formats = ['pdf', 'odt', 'txt', 'mp3', 'ogg'] + # Create a ZIP archive temp = temp = tempfile.TemporaryFile() archive = zipfile.ZipFile(temp, 'w') - # Collect all books to include in ZIP archive - def collect_books(books): - result = [] - for book in books: - if len(book.children.all()) == 0: - result.append(book) - else: - result += collect_books(book.children.all()) - return result - for book in collect_books(models.Book.tagged.with_all(shelf)): - if book.pdf_file: + if 'pdf' in formats and book.pdf_file: filename = book.pdf_file.path archive.write(filename, str('%s.pdf' % book.slug)) - if book.odt_file: + if 'odt' in formats and book.odt_file: filename = book.odt_file.path archive.write(filename, str('%s.odt' % book.slug)) - if book.txt_file: + if 'txt' in formats and book.txt_file: filename = book.txt_file.path archive.write(filename, str('%s.txt' % book.slug)) + if 'mp3' in formats and book.mp3_file: + filename = book.mp3_file.path + archive.write(filename, str('%s.mp3' % book.slug)) + if 'ogg' in formats and book.ogg_file: + filename = book.ogg_file.path + archive.write(filename, str('%s.ogg' % book.slug)) archive.close() response = HttpResponse(content_type='application/zip', mimetype='application/x-zip-compressed') @@ -292,6 +367,30 @@ def download_shelf(request, slug): return response +@cache.never_cache +def shelf_book_formats(request, shelf): + """" + Returns a list of formats of books in shelf. + """ + shelf = get_object_or_404(models.Tag, slug=shelf, category='set') + + formats = {'pdf': False, 'odt': False, 'txt': False, 'mp3': False, 'ogg': False} + + for book in collect_books(models.Book.tagged.with_all(shelf)): + if book.pdf_file: + formats['pdf'] = True + if book.odt_file: + formats['odt'] = True + if book.txt_file: + formats['txt'] = True + if book.mp3_file: + formats['mp3'] = True + if book.ogg_file: + formats['ogg'] = True + + return HttpResponse(LazyEncoder().encode(formats)) + + @login_required @require_POST @cache.never_cache @@ -369,10 +468,22 @@ def import_book(request): """docstring for import_book""" book_import_form = forms.BookImportForm(request.POST, request.FILES) if book_import_form.is_valid(): - # try: - book_import_form.save() - # except: - # return HttpResponse("Error importing book: %r" % (sys.exc_info(),)) + try: + book_import_form.save() + except: + info = sys.exc_info() + exception = pprint.pformat(info[1]) + tb = '\n'.join(traceback.format_tb(info[2])) + return HttpResponse("An error occurred: %s\n\n%s" % (exception, tb), mimetype='text/plain') return HttpResponse("Book imported successfully") else: - return HttpResponse("Error importing file: %r" % book_import_form.errors) \ No newline at end of file + return HttpResponse("Error importing file: %r" % book_import_form.errors) + + + +def clock(request): + """ Provides server time for jquery.countdown, + in a format suitable for Date.parse() + """ + from datetime import datetime + return HttpResponse(datetime.now().strftime('%Y/%m/%d %H:%M:%S'))