From: Jan Szejko Date: Tue, 5 Jun 2018 14:46:14 +0000 (+0200) Subject: Merge branch 'preview' X-Git-Url: https://git.mdrn.pl/wolnelektury.git/commitdiff_plain/73b6a1639ba8f9f5fc7615c136364fdba73e5bb2?hp=cbf4353403bf60dd0966e060a78893685080ae0b Merge branch 'preview' # Conflicts: # src/api/handlers.py # src/catalogue/models/book.py # src/catalogue/templates/catalogue/book_short.html --- diff --git a/src/ajaxable/utils.py b/src/ajaxable/utils.py index 80d398f73..82de84771 100755 --- a/src/ajaxable/utils.py +++ b/src/ajaxable/utils.py @@ -77,6 +77,11 @@ class AjaxableFormView(object): def __call__(self, request, *args, **kwargs): """A view displaying a form, or JSON if request is AJAX.""" obj = self.get_object(request, *args, **kwargs) + + response = self.validate_object(obj, request) + if response: + return response + form_args, form_kwargs = self.form_args(request, obj) if self.form_prefix: form_kwargs['prefix'] = self.form_prefix @@ -150,6 +155,9 @@ class AjaxableFormView(object): context.update(self.extra_context(request, obj)) return render_to_response(template, context, context_instance=RequestContext(request)) + def validate_object(self, obj, request): + return None + def redirect_or_refresh(self, request, path, message=None): """If the form is AJAX, refresh the page. If not, go to `path`.""" if request.is_ajax(): diff --git a/src/api/emitters.py b/src/api/emitters.py index 36babb2bf..531ae19bb 100644 --- a/src/api/emitters.py +++ b/src/api/emitters.py @@ -69,3 +69,11 @@ class SsiXmlEmitter(SsiEmitterMixin, XMLEmitter): ''.join(self.construct().get_ssis('xml')) Emitter.register('xml', SsiXmlEmitter, 'text/xml; charset=utf-8') + + +# hack +class EpubEmitter(Emitter): + def render(self, request): + return self.data + +Emitter.register('epub', EpubEmitter, 'application/epub+zip') diff --git a/src/api/handlers.py b/src/api/handlers.py index 893cd7e1b..3e4e0931a 100644 --- a/src/api/handlers.py +++ b/src/api/handlers.py @@ -6,15 +6,18 @@ import json from django.contrib.sites.models import Site from django.core.urlresolvers import reverse +from django.http.response import HttpResponse from django.utils.functional import lazy from django.db import models from piston.handler import AnonymousBaseHandler, BaseHandler from piston.utils import rc from sorl.thumbnail import default +from api.models import BookUserData from catalogue.forms import BookImportForm from catalogue.models import Book, Tag, BookMedia, Fragment, Collection from catalogue.models.tag import prefetch_relations +from catalogue.utils import is_subscribed from picture.models import Picture from picture.forms import PictureImportForm @@ -161,7 +164,8 @@ class BookDetailHandler(BaseHandler, BookDetails): """ allowed_methods = ['GET'] fields = ['title', 'parent', 'children'] + Book.formats + [ - 'media', 'url', 'cover', 'cover_thumb', 'simple_thumb', 'simple_cover', 'fragment_data', 'audio_length'] + [ + 'media', 'url', 'cover', 'cover_thumb', 'simple_thumb', 'simple_cover', 'fragment_data', 'audio_length', + 'preview'] + [ category_plural[c] for c in book_tag_categories] @piwik_track @@ -278,6 +282,18 @@ class BooksHandler(BookDetailHandler): return rc.NOT_FOUND +class EpubHandler(BookDetailHandler): + def read(self, request, slug): + if not is_subscribed(request.user): + return rc.FORBIDDEN + try: + book = Book.objects.get(slug=slug) + except Book.DoesNotExist: + return rc.NOT_FOUND + response = HttpResponse(book.get_media('epub')) + return response + + class EBooksHandler(AnonymousBooksHandler): fields = ('author', 'href', 'title', 'cover') + tuple(Book.ebook_formats) + ('slug',) @@ -309,20 +325,21 @@ class FilterBooksHandler(AnonymousBooksHandler): fields = book_tag_categories + [ 'href', 'title', 'url', 'cover', 'cover_thumb', 'simple_thumb', 'has_audio', 'slug', 'key'] + def parse_bool(self, s): + if s in ('true', 'false'): + return s == 'true' + else: + return None + def read(self, request): key_sep = '$' search_string = request.GET.get('search') - is_lektura = request.GET.get('lektura') - is_audiobook = request.GET.get('audiobook') + is_lektura = self.parse_bool(request.GET.get('lektura')) + is_audiobook = self.parse_bool(request.GET.get('audiobook')) + preview = self.parse_bool(request.GET.get('preview')) after = request.GET.get('after') count = int(request.GET.get('count', 50)) - if is_lektura in ('true', 'false'): - is_lektura = is_lektura == 'true' - else: - is_lektura = None - if is_audiobook in ('true', 'false'): - is_audiobook = is_audiobook == 'true' books = Book.objects.distinct().order_by('slug') if is_lektura is not None: books = books.filter(has_audience=is_lektura) @@ -331,6 +348,8 @@ class FilterBooksHandler(AnonymousBooksHandler): books = books.filter(media__type='mp3') else: books = books.exclude(media__type='mp3') + if preview is not None: + books = books.filter(preview=preview) for key in request.GET: if key in category_singular: category = category_singular[key] @@ -394,18 +413,18 @@ def add_tag_getters(): setattr(BookDetails, plural, _tags_getter(singular)) setattr(BookDetails, singular, _tag_getter(singular)) + add_tag_getters() # add fields for files in Book def _file_getter(book_format): - field = "%s_file" % book_format - @classmethod - def get_file(cls, book): - f = getattr(book, field) - if f: - return MEDIA_BASE + f.url + @staticmethod + def get_file(book): + f_url = book.media_url(book_format) + if f_url: + return MEDIA_BASE + f_url else: return '' return get_file @@ -415,6 +434,7 @@ def add_file_getters(): for book_format in Book.formats: setattr(BookDetails, book_format, _file_getter(book_format)) + add_file_getters() @@ -606,7 +626,7 @@ class FragmentsHandler(BaseHandler, FragmentDetails): """ try: - tags, ancestors = read_tags(tags, allowed=self.categories) + tags, ancestors = read_tags(tags, request, allowed=self.categories) except ValueError: return rc.NOT_FOUND fragments = Fragment.tagged.with_all(tags).select_related('book') @@ -632,3 +652,63 @@ class PictureHandler(BaseHandler): return rc.CREATED else: return rc.NOT_FOUND + + +class UserDataHandler(BaseHandler): + model = BookUserData + fields = ('state',) + allowed_methods = ('GET', 'POST') + + def read(self, request, slug): + try: + book = Book.objects.get(slug=slug) + except Book.DoesNotExist: + return rc.NOT_FOUND + if not request.user.is_authenticated(): + return rc.FORBIDDEN + try: + data = BookUserData.objects.get(book=book, user=request.user) + except BookUserData.DoesNotExist: + return {'state': 'not_started'} + return data + + def create(self, request, slug, state): + try: + book = Book.objects.get(slug=slug) + except Book.DoesNotExist: + return rc.NOT_FOUND + if not request.user.is_authenticated(): + return rc.FORBIDDEN + if state not in ('reading', 'complete'): + return rc.NOT_FOUND + data, created = BookUserData.objects.get_or_create(book=book, user=request.user) + data.state = state + data.save() + return data + + +class UserShelfHandler(BookDetailHandler): + fields = book_tag_categories + [ + 'href', 'title', 'url', 'cover', 'cover_thumb', 'simple_thumb', 'slug', 'key'] + + def parse_bool(self, s): + if s in ('true', 'false'): + return s == 'true' + else: + return None + + def read(self, request, state): + if not request.user.is_authenticated(): + return rc.FORBIDDEN + if state not in ('reading', 'complete'): + return rc.NOT_FOUND + after = request.GET.get('after') + count = int(request.GET.get('count', 50)) + ids = BookUserData.objects.filter(user=request.user, complete=state == 'complete')\ + .values_list('book_id', flat=True) + books = Book.objects.filter(id__in=list(ids)).distinct().order_by('slug') + if after: + books = books.filter(slug__gt=after) + if count: + books = books[:count] + return books diff --git a/src/api/migrations/0003_bookuserdata.py b/src/api/migrations/0003_bookuserdata.py new file mode 100644 index 000000000..11a2f8ff0 --- /dev/null +++ b/src/api/migrations/0003_bookuserdata.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('catalogue', '0024_auto_20180510_1407'), + ('api', '0002_auto_20151221_1225'), + ] + + operations = [ + migrations.CreateModel( + name='BookUserData', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('complete', models.BooleanField(default=False)), + ('book', models.ForeignKey(to='catalogue.Book')), + ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/src/api/models.py b/src/api/models.py index 2f742834f..cc71a06f3 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -2,6 +2,7 @@ # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later. # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # +from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType from django.db import models from django.db.models.signals import pre_delete @@ -38,3 +39,17 @@ def _pre_delete_handler(sender, instance, **kwargs): content_type=content_type, object_id=instance.id, created_at=instance.created_at, category=category, slug=instance.slug) pre_delete.connect(_pre_delete_handler) + + +class BookUserData(models.Model): + book = models.ForeignKey(Book) + user = models.ForeignKey(User) + complete = models.BooleanField(default=False) + + def get_state(self): + return 'complete' if self.complete else 'reading' + + def set_state(self, state): + self.complete = state == 'complete' + + state = property(get_state, set_state) diff --git a/src/api/urls.py b/src/api/urls.py index 29e7a752f..006abed60 100644 --- a/src/api/urls.py +++ b/src/api/urls.py @@ -20,6 +20,10 @@ ebook_list_resource = Resource(handler=handlers.EBooksHandler) # book_list_resource = Resource(handler=handlers.BooksHandler) book_resource = Resource(handler=handlers.BookDetailHandler) filter_book_resource = Resource(handler=handlers.FilterBooksHandler) +epub_resource = Resource(handler=handlers.EpubHandler) + +reading_resource = CsrfExemptResource(handler=handlers.UserDataHandler) +shelf_resource = Resource(handler=handlers.UserShelfHandler) collection_resource = Resource(handler=handlers.CollectionDetailHandler) collection_list_resource = Resource(handler=handlers.CollectionsHandler) @@ -69,6 +73,14 @@ urlpatterns = [ url(r'^collections/$', collection_list_resource, name="api_collections"), url(r'^collections/(?P[^/]+)/$', collection_resource, name="api_collection"), + # epub preview + url(r'^epub/(?P[a-z0-9-]+)/$', epub_resource, name='api_epub'), + + # reading data + url(r'^reading/(?P[a-z0-9-]+)/$', reading_resource, name='api_reading'), + url(r'^reading/(?P[a-z0-9-]+)/(?P[a-z]+)/$', reading_resource, name='api_reading'), + url(r'^shelf/(?P[a-z]+)/$', shelf_resource, name='api_shelf'), + # objects details url(r'^books/(?P[a-z0-9-]+)/$', book_resource, name="api_book"), url(r'^(?P[a-z0-9-]+)/(?P[a-z0-9-]+)/$', diff --git a/src/catalogue/constants.py b/src/catalogue/constants.py index e6773d05b..c16e3f7bb 100644 --- a/src/catalogue/constants.py +++ b/src/catalogue/constants.py @@ -26,6 +26,16 @@ EBOOK_FORMATS_WITH_COVERS = ['pdf', 'epub', 'mobi'] EBOOK_FORMATS = EBOOK_FORMATS_WITHOUT_CHILDREN + EBOOK_FORMATS_WITH_CHILDREN +EBOOK_CONTENT_TYPES = { + 'html': 'text/html', + 'pdf': 'application/pdf', + 'txt': 'text/plain', + 'epub': 'application/epub+zip', + 'mobi': 'application/x-mobipocket-ebook', + 'fb2': 'text/xml', + 'xml': 'text/xml', +} + LANGUAGES_3TO2 = { 'deu': 'de', 'ger': 'de', diff --git a/src/catalogue/fields.py b/src/catalogue/fields.py index 911f857c3..1ed34e2c5 100644 --- a/src/catalogue/fields.py +++ b/src/catalogue/fields.py @@ -28,6 +28,14 @@ class EbookFieldFile(FieldFile): """Builds the ebook in a delayed task.""" return self.field.builder.delay(self.instance, self.field.attname) + def get_url(self): + return self.instance.media_url(self.field.attname.split('_')[0]) + + def set_readable(self, readable): + import os + permissions = 0o644 if readable else 0o600 + os.chmod(self.path, permissions) + class EbookField(models.FileField): """Represents an ebook file field, attachable to a model.""" @@ -91,10 +99,15 @@ class BuildEbook(Task): obj.flush_includes() return ret + def set_file_permissions(self, fieldfile): + if fieldfile.instance.preview: + fieldfile.set_readable(False) + def build(self, fieldfile): book = fieldfile.instance out = self.transform(book.wldocument(), fieldfile) fieldfile.save(None, File(open(out.get_filename())), save=False) + self.set_file_permissions(fieldfile) if book.pk is not None: type(book).objects.filter(pk=book.pk).update(**{ fieldfile.field.attname: fieldfile @@ -169,6 +182,7 @@ class BuildHtml(BuildEbook): lang = None fieldfile.save(None, ContentFile(html_output.get_string()), save=False) + self.set_file_permissions(fieldfile) type(book).objects.filter(pk=book.pk).update(**{ fieldfile.field.attname: fieldfile }) @@ -235,9 +249,14 @@ class BuildHtml(BuildEbook): return wldoc.as_html(options={'gallery': "'%s'" % gallery}) +class BuildCover(BuildEbook): + def set_file_permissions(self, fieldfile): + pass + + @BuildEbook.register('cover_thumb') @task(ignore_result=True) -class BuildCoverThumb(BuildEbook): +class BuildCoverThumb(BuildCover): @classmethod def transform(cls, wldoc, fieldfile): from librarian.cover import WLCover @@ -246,7 +265,7 @@ class BuildCoverThumb(BuildEbook): @BuildEbook.register('cover_api_thumb') @task(ignore_result=True) -class BuildCoverApiThumb(BuildEbook): +class BuildCoverApiThumb(BuildCover): @classmethod def transform(cls, wldoc, fieldfile): from librarian.cover import WLNoBoxCover @@ -255,7 +274,7 @@ class BuildCoverApiThumb(BuildEbook): @BuildEbook.register('simple_cover') @task(ignore_result=True) -class BuildSimpleCover(BuildEbook): +class BuildSimpleCover(BuildCover): @classmethod def transform(cls, wldoc, fieldfile): from librarian.cover import WLNoBoxCover diff --git a/src/catalogue/forms.py b/src/catalogue/forms.py index c1348a1e4..bcbfe5e83 100644 --- a/src/catalogue/forms.py +++ b/src/catalogue/forms.py @@ -16,6 +16,7 @@ class BookImportForm(forms.Form): book_xml_file = forms.FileField(required=False) book_xml = forms.CharField(required=False) gallery_url = forms.CharField(required=False) + days = forms.IntegerField(required=False) def clean(self): from django.core.files.base import ContentFile @@ -30,7 +31,8 @@ class BookImportForm(forms.Form): def save(self, **kwargs): return Book.from_xml_file(self.cleaned_data['book_xml_file'], overwrite=True, - remote_gallery_url=self.cleaned_data['gallery_url'], **kwargs) + remote_gallery_url=self.cleaned_data['gallery_url'], + days=self.cleaned_data['days'], **kwargs) FORMATS = [(f, f.upper()) for f in Book.ebook_formats] @@ -98,7 +100,7 @@ class CustomPDFForm(forms.Form): def save(self, *args, **kwargs): if not self.cleaned_data['cust'] and self.book.pdf_file: # Don't build with default options, just redirect to the standard file. - return {"redirect": self.book.pdf_file.url} + return {"redirect": self.book.pdf_url()} url = WaitedFile.order( self.cleaned_data['path'], lambda p, waiter_id: build_custom_pdf.delay(self.book.id, self.cleaned_data['cust'], p, waiter_id), diff --git a/src/catalogue/management/commands/update_preview_status.py b/src/catalogue/management/commands/update_preview_status.py new file mode 100644 index 000000000..cd2e8d8f2 --- /dev/null +++ b/src/catalogue/management/commands/update_preview_status.py @@ -0,0 +1,19 @@ +# -*- 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. +# +from datetime import date +from django.core.management.base import BaseCommand + +from catalogue.models import Book + + +class Command(BaseCommand): + def handle(self, *args, **options): + for book in Book.objects.filter(preview=True, preview_until__lt=date.today()): + book.preview = False + book.save() + for format_ in Book.formats: + media_file = book.get_media(format_) + if media_file: + media_file.set_readable(True) diff --git a/src/catalogue/migrations/0024_auto_20180510_1407.py b/src/catalogue/migrations/0024_auto_20180510_1407.py new file mode 100644 index 000000000..819bf26ec --- /dev/null +++ b/src/catalogue/migrations/0024_auto_20180510_1407.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('catalogue', '0023_book_abstract'), + ] + + operations = [ + migrations.AddField( + model_name='book', + name='preview', + field=models.BooleanField(default=False, verbose_name='preview'), + ), + migrations.AddField( + model_name='book', + name='preview_until', + field=models.DateField(null=True, verbose_name='preview until', blank=True), + ), + ] diff --git a/src/catalogue/migrations/0025_merge.py b/src/catalogue/migrations/0025_merge.py new file mode 100644 index 000000000..66c8c8f4d --- /dev/null +++ b/src/catalogue/migrations/0025_merge.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('catalogue', '0024_book_audio_length'), + ('catalogue', '0024_auto_20180510_1407'), + ] + + operations = [ + ] diff --git a/src/catalogue/models/book.py b/src/catalogue/models/book.py index a1ca5bbed..004c27e89 100644 --- a/src/catalogue/models/book.py +++ b/src/catalogue/models/book.py @@ -3,6 +3,7 @@ # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # from collections import OrderedDict +from datetime import date, timedelta from random import randint import os.path import re @@ -72,6 +73,8 @@ class Book(models.Model): print_on_demand = models.BooleanField(_('print on demand'), default=False) recommended = models.BooleanField(_('recommended'), default=False) audio_length = models.CharField(_('audio length'), blank=True, max_length=8) + preview = models.BooleanField(_('preview'), default=False) + preview_until = models.DateField(_('preview until'), blank=True, null=True) # files generated during publication cover = EbookField( @@ -265,6 +268,37 @@ class Book(models.Model): def get_daisy(self): return self.get_media("daisy") + def media_url(self, format_): + media = self.get_media(format_) + if media: + if self.preview: + return reverse('embargo_link', kwargs={'slug': self.slug, 'format_': format_}) + else: + return media.url + else: + return None + + def html_url(self): + return self.media_url('html') + + def pdf_url(self): + return self.media_url('pdf') + + def epub_url(self): + return self.media_url('epub') + + def mobi_url(self): + return self.media_url('mobi') + + def txt_url(self): + return self.media_url('txt') + + def fb2_url(self): + return self.media_url('fb2') + + def xml_url(self): + return self.media_url('xml') + def has_description(self): return len(self.description) > 0 has_description.short_description = _('description') @@ -336,7 +370,7 @@ class Book(models.Model): format_) field_name = "%s_file" % format_ - books = Book.objects.filter(parent=None).exclude(**{field_name: ""}) + books = Book.objects.filter(parent=None).exclude(**{field_name: ""}).exclude(preview=True) paths = [(pretty_file_name(b), getattr(b, field_name).path) for b in books.iterator()] return create_zip(paths, app_settings.FORMAT_ZIPS[format_]) @@ -359,6 +393,7 @@ class Book(models.Model): index.index.rollback() raise e + # will make problems in conjunction with paid previews def download_pictures(self, remote_gallery_url): gallery_path = self.gallery_path() # delete previous files, so we don't include old files in ebooks @@ -399,7 +434,7 @@ class Book(models.Model): @classmethod def from_text_and_meta(cls, raw_file, book_info, overwrite=False, dont_build=None, search_index=True, - search_index_tags=True, remote_gallery_url=None): + search_index_tags=True, remote_gallery_url=None, days=0): if dont_build is None: dont_build = set() dont_build = set.union(set(dont_build), set(app_settings.DONT_BUILD)) @@ -422,6 +457,9 @@ class Book(models.Model): if created: book_shelves = [] old_cover = None + book.preview = bool(days) + if book.preview: + book.preview_until = date.today() + timedelta(days) else: if not overwrite: raise Book.AlreadyExists(_('Book %s already exists') % book_slug) @@ -431,6 +469,8 @@ class Book(models.Model): # Save XML file book.xml_file.save('%s.xml' % book.slug, raw_file, save=False) + if book.preview: + book.xml_file.set_readable(False) book.language = book_info.language book.title = book_info.title @@ -748,6 +788,7 @@ def add_file_fields(): default='' ).contribute_to_class(Book, field_name) + add_file_fields() diff --git a/src/catalogue/templates/catalogue/book_detail.html b/src/catalogue/templates/catalogue/book_detail.html index 4baec4b8e..26fe1678c 100644 --- a/src/catalogue/templates/catalogue/book_detail.html +++ b/src/catalogue/templates/catalogue/book_detail.html @@ -13,7 +13,7 @@ {% block bodyid %}book-detail{% endblock %} {% block body %} - {% cache 86400 book_wide book.pk %} {# book|status:user #} + {% cache 86400 book_wide book.pk book|status:user %} {% include 'catalogue/book_wide.html' %} {% endcache %} @@ -62,7 +62,9 @@ {% trans "in" %} {% source_name extra_info.source_url %} {% endif %} - + {% if book|status:user != 'closed' %} + + {% endif %} {% if extra_info.about and not hide_about %}
{% trans "Book on" %} {% trans "Editor's Platform" %} diff --git a/src/catalogue/templates/catalogue/book_short.html b/src/catalogue/templates/catalogue/book_short.html index fe4871d81..675e761c4 100644 --- a/src/catalogue/templates/catalogue/book_short.html +++ b/src/catalogue/templates/catalogue/book_short.html @@ -97,47 +97,51 @@
{% book_shelf_tags book.pk %} - + {% else %} +

{% trans "For now this work is only available for our subscribers." %}

+ {% endif %} {% block book-box-extra-info %}{% endblock %} {% block box-append %}{% endblock %} diff --git a/src/catalogue/templates/catalogue/book_text.html b/src/catalogue/templates/catalogue/book_text.html index 93e755f4d..f697e4ee2 100644 --- a/src/catalogue/templates/catalogue/book_text.html +++ b/src/catalogue/templates/catalogue/book_text.html @@ -60,7 +60,7 @@ {% block big-pane %}
- +
@@ -85,7 +85,7 @@ {% for other_version in book.other_versions %}
  • {% cache 86400 book_mini_box other_version.pk %} {% include 'catalogue/book_mini_box.html' with book=other_version no_link=True %} @@ -110,7 +110,7 @@
    - {% cache 86400 catalogue_book_short book.pk %} + {% cache 86400 catalogue_book_short book.pk book|status:user %} {% include 'catalogue/book_short.html' %} {% endcache %}
    diff --git a/src/catalogue/templatetags/catalogue_tags.py b/src/catalogue/templatetags/catalogue_tags.py index 9f5c04b16..6906f605d 100644 --- a/src/catalogue/templatetags/catalogue_tags.py +++ b/src/catalogue/templatetags/catalogue_tags.py @@ -18,6 +18,7 @@ from ssify import ssi_variable from catalogue.helpers import get_audiobook_tags from catalogue.models import Book, BookMedia, Fragment, Tag, Source from catalogue.constants import LICENSES +from catalogue.utils import is_subscribed from picture.models import Picture register = template.Library() @@ -491,3 +492,13 @@ def strip_tag(html, tag_name): # docelowo może być warto zainstalować BeautifulSoup do takich rzeczy import re return re.sub(r"<.?%s\b[^>]*>" % tag_name, "", html) + + +@register.filter +def status(book, user): + if not book.preview: + return 'open' + elif is_subscribed(user): + return 'preview' + else: + return 'closed' diff --git a/src/catalogue/urls.py b/src/catalogue/urls.py index addb0cba0..50a4a4af9 100644 --- a/src/catalogue/urls.py +++ b/src/catalogue/urls.py @@ -64,6 +64,7 @@ urlpatterns += patterns( url(r'^audiobooki/(?Pmp3|ogg|daisy|all).xml$', AudiobookFeed(), name='audiobook_feed'), + url(r'^pobierz/(?P%s).(?P[a-z0-9]*)$' % SLUG, 'embargo_link', name='embargo_link'), # zip url(r'^zip/pdf\.zip$', 'download_zip', {'format': 'pdf', 'slug': None}, 'download_zip_pdf'), diff --git a/src/catalogue/utils.py b/src/catalogue/utils.py index 2f3b94964..2145312ad 100644 --- a/src/catalogue/utils.py +++ b/src/catalogue/utils.py @@ -354,3 +354,7 @@ def gallery_path(slug): def gallery_url(slug): return '%s%s%s/' % (settings.MEDIA_URL, settings.IMAGE_DIR, slug) + + +def is_subscribed(user): + return user.is_authenticated() # TEMPORARY diff --git a/src/catalogue/views.py b/src/catalogue/views.py index 0a5ab5867..c545cdf79 100644 --- a/src/catalogue/views.py +++ b/src/catalogue/views.py @@ -26,7 +26,7 @@ from catalogue import constants from catalogue import forms from catalogue.helpers import get_top_level_related_tags from catalogue.models import Book, Collection, Tag, Fragment -from catalogue.utils import split_tags +from catalogue.utils import split_tags, is_subscribed from catalogue.models.tag import prefetch_relations from wolnelektury.utils import is_crawler @@ -307,6 +307,9 @@ def player(request, slug): def book_text(request, slug): book = get_object_or_404(Book, slug=slug) + if book.preview and not is_subscribed(request.user): + return HttpResponseRedirect(book.get_absolute_url()) + if not book.has_html_file(): raise Http404 return render_to_response('catalogue/book_text.html', {'book': book}, context_instance=RequestContext(request)) @@ -352,6 +355,18 @@ def tag_info(request, tag_id): return HttpResponse(tag.description) +def embargo_link(request, format_, slug): + book = get_object_or_404(Book, slug=slug) + if format_ not in Book.formats: + raise Http404 + media_file = book.get_media(format_) + if not book.preview: + return HttpResponseRedirect(media_file.url) + if not is_subscribed(request.user): + return HttpResponseRedirect(book.get_absolute_url()) + return HttpResponse(media_file, content_type=constants.EBOOK_CONTENT_TYPES[format_]) + + def download_zip(request, format, slug=None): if format in Book.ebook_formats: url = Book.zip_format(format) @@ -379,8 +394,15 @@ class CustomPDFFormView(AjaxableFormView): """Override to parse view args and give additional args to the form.""" return (obj,), {} + def validate_object(self, obj, request): + book = obj + if book.preview and not is_subscribed(request.user): + return HttpResponseRedirect(book.get_absolute_url()) + return super(CustomPDFFormView, self).validate_object(obj, request) + def get_object(self, request, slug, *args, **kwargs): - return get_object_or_404(Book, slug=slug) + book = get_object_or_404(Book, slug=slug) + return book def context_description(self, request, obj): return obj.pretty_title() diff --git a/src/oai/handlers.py b/src/oai/handlers.py index 6d0a9b216..6fbcd4271 100644 --- a/src/oai/handlers.py +++ b/src/oai/handlers.py @@ -70,7 +70,7 @@ class Catalogue(common.ResumptionOAIPMH): year_zero = timezone.make_aware(datetime(1990, 1, 1, 0, 0, 0), timezone.utc) try: - earliest_change = Book.objects.order_by('changed_at')[0].changed_at + earliest_change = Book.objects.filter(preview=False).order_by('changed_at')[0].changed_at except IndexError: earliest_change = year_zero @@ -134,7 +134,7 @@ class Catalogue(common.ResumptionOAIPMH): raise error.NoSetHierarchyError("Wolne Lektury does not support sets.") # books = Book.tagged.with_all([tag]) else: - books = Book.objects.all() + books = Book.objects.filter(preview=False) deleted = Deleted.objects.exclude(slug__exact=u'') books = books.order_by('changed_at') diff --git a/src/opds/views.py b/src/opds/views.py index 189ff0f5a..0c31cd200 100644 --- a/src/opds/views.py +++ b/src/opds/views.py @@ -201,7 +201,7 @@ class AcquisitionFeed(Feed): return u'' def item_enclosure_url(self, book): - return full_url(book.epub_file.url) if book.epub_file else None + return full_url(book.epub_url()) if book.epub_file else None def item_enclosure_length(self, book): return book.epub_file.size if book.epub_file else None diff --git a/src/wolnelektury/templates/post_test.html b/src/wolnelektury/templates/post_test.html new file mode 100644 index 000000000..e0f71099b --- /dev/null +++ b/src/wolnelektury/templates/post_test.html @@ -0,0 +1,8 @@ +{% extends "base/base.html" %} + +{% block body %} +
    + {% csrf_token %} + +
    +{% endblock body %} diff --git a/src/wolnelektury/urls.py b/src/wolnelektury/urls.py index fa011086a..bf95306d9 100644 --- a/src/wolnelektury/urls.py +++ b/src/wolnelektury/urls.py @@ -90,4 +90,5 @@ urlpatterns += [ urlpatterns += [ url(r'^error-test/$', views.exception_test), + # url(r'^post-test/$', views.post_test), ] diff --git a/src/wolnelektury/views.py b/src/wolnelektury/views.py index 28ce22374..a5db0a83f 100644 --- a/src/wolnelektury/views.py +++ b/src/wolnelektury/views.py @@ -196,4 +196,8 @@ def exception_test(request): if msg: raise Exception('Exception test: %s' % msg) else: - raise Exception('Exception test') \ No newline at end of file + raise Exception('Exception test') + + +def post_test(request): + return render(request, 'post_test.html', {'action': '/api/reading/jego-zasady/complete/'})