Merge branch 'preview'
authorJan Szejko <janek37@gmail.com>
Tue, 5 Jun 2018 14:46:14 +0000 (16:46 +0200)
committerJan Szejko <janek37@gmail.com>
Tue, 5 Jun 2018 15:11:50 +0000 (17:11 +0200)
# Conflicts:
# src/api/handlers.py
# src/catalogue/models/book.py
# src/catalogue/templates/catalogue/book_short.html

25 files changed:
src/ajaxable/utils.py
src/api/emitters.py
src/api/handlers.py
src/api/migrations/0003_bookuserdata.py [new file with mode: 0644]
src/api/models.py
src/api/urls.py
src/catalogue/constants.py
src/catalogue/fields.py
src/catalogue/forms.py
src/catalogue/management/commands/update_preview_status.py [new file with mode: 0644]
src/catalogue/migrations/0024_auto_20180510_1407.py [new file with mode: 0644]
src/catalogue/migrations/0025_merge.py [new file with mode: 0644]
src/catalogue/models/book.py
src/catalogue/templates/catalogue/book_detail.html
src/catalogue/templates/catalogue/book_short.html
src/catalogue/templates/catalogue/book_text.html
src/catalogue/templatetags/catalogue_tags.py
src/catalogue/urls.py
src/catalogue/utils.py
src/catalogue/views.py
src/oai/handlers.py
src/opds/views.py
src/wolnelektury/templates/post_test.html [new file with mode: 0644]
src/wolnelektury/urls.py
src/wolnelektury/views.py

index 80d398f..82de847 100755 (executable)
@@ -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():
index 36babb2..531ae19 100644 (file)
@@ -69,3 +69,11 @@ class SsiXmlEmitter(SsiEmitterMixin, XMLEmitter):
                 '</resource><resource>'.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')
index 893cd7e..3e4e093 100644 (file)
@@ -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 (file)
index 0000000..11a2f8f
--- /dev/null
@@ -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)),
+            ],
+        ),
+    ]
index 2f74283..cc71a06 100644 (file)
@@ -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)
index 29e7a75..006abed 100644 (file)
@@ -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<slug>[^/]+)/$', collection_resource, name="api_collection"),
 
+    # epub preview
+    url(r'^epub/(?P<slug>[a-z0-9-]+)/$', epub_resource, name='api_epub'),
+
+    # reading data
+    url(r'^reading/(?P<slug>[a-z0-9-]+)/$', reading_resource, name='api_reading'),
+    url(r'^reading/(?P<slug>[a-z0-9-]+)/(?P<state>[a-z]+)/$', reading_resource, name='api_reading'),
+    url(r'^shelf/(?P<state>[a-z]+)/$', shelf_resource, name='api_shelf'),
+
     # objects details
     url(r'^books/(?P<book>[a-z0-9-]+)/$', book_resource, name="api_book"),
     url(r'^(?P<category>[a-z0-9-]+)/(?P<slug>[a-z0-9-]+)/$',
index e6773d0..c16e3f7 100644 (file)
@@ -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',
index 911f857..1ed34e2 100644 (file)
@@ -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
index c1348a1..bcbfe5e 100644 (file)
@@ -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 (file)
index 0000000..cd2e8d8
--- /dev/null
@@ -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 (file)
index 0000000..819bf26
--- /dev/null
@@ -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 (file)
index 0000000..66c8c8f
--- /dev/null
@@ -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 = [
+    ]
index a1ca5bb..004c27e 100644 (file)
@@ -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()
 
 
index 4baec4b..26fe167 100644 (file)
@@ -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 %}
     </div>
   {% endif %}
-  <div class="white-box"><a href="{{ book.xml_file.url }}">{% trans "Source XML file" %}</a></div>
+  {% if book|status:user != 'closed' %}
+    <div class="white-box"><a href="{{ book.xml_url }}">{% trans "Source XML file" %}</a></div>
+  {% endif %}
   {% if extra_info.about and not hide_about %}
     <div class="white-box">
       {% trans "Book on" %} <a href="{{ extra_info.about }}">{% trans "Editor's Platform" %}</a>
index fe4871d..675e761 100644 (file)
       </div>
       {% book_shelf_tags book.pk %}
 
-      <ul class="book-box-tools">
-        <li class="book-box-read">
-          {% if book.html_file %}
-            <a href="{% url 'book_text' book.slug %}" class="downarrow">{% trans "Read online" %}</a>
-          {% endif %}
-          {% if book.print_on_demand %}
-            <a href="{{ book.ridero_link }}" class="downarrow print tlite-tooltip" title="{% trans "Cena książki w druku cyfrowym jest zależna od liczby stron.<br>Przed zakupem upewnij się, że cena druku na żądanie jest dla Ciebie odpowiednia.<br>Wszystkie nasze zasoby w wersji elektronicznej są zawsze dostępne bezpłatnie." %}">{% trans "Print on demand –" %}
-                <img src="{% static 'img/ridero.png' %}" style="height: 0.8em;"/></a>
-          {% endif %}
-        </li>
-        <li class="book-box-download">
-          <a class="downarrow">{% trans "Download" %}:</a>
-          <div class="book-box-formats">
-            {% if book.pdf_file %}
-              <span><a href="{{ book.pdf_file.url}}">PDF</a></span>
-            {% endif %}
-            {% if book.epub_file %}
-              <span><a href="{{ book.epub_file.url}}">EPUB</a></span>
+      {% if book|status:user != 'closed' %}
+        <ul class="book-box-tools">
+          <li class="book-box-read">
+            {% if book.html_file %}
+              <a href="{% url 'book_text' book.slug %}" class="downarrow">{% trans "Read online" %}</a>
             {% endif %}
-            {% if book.mobi_file %}
-              <span><a href="{{ book.mobi_file.url}}">MOBI</a></span>
+            {% if book.print_on_demand %}
+              <a href="{{ book.ridero_link }}" class="downarrow print tlite-tooltip" title="{% trans "Cena książki w druku cyfrowym jest zależna od liczby stron.<br>Przed zakupem upewnij się, że cena druku na żądanie jest dla Ciebie odpowiednia.<br>Wszystkie nasze zasoby w wersji elektronicznej są zawsze dostępne bezpłatnie." %}">{% trans "Print on demand –" %}
+                  <img src="{% static 'img/ridero.png' %}" style="height: 0.8em;"/></a>
             {% endif %}
-            {% if book.has_audio %}
-              <span><a href="{% url 'download_zip_mp3' book.slug %}">MP3</a></span>
-            {% endif %}
-            <a class="read-more-show hide" href="#">{% trans "more" %}</a>
-            <span class="read-more-content">
-              {% if  book.fb2_file %}
-                <span><a href="{{ book.fb2_file.url}}">FB2</a></span>
+          </li>
+          <li class="book-box-download">
+            <a class="downarrow">{% trans "Download" %}:</a>
+            <div class="book-box-formats">
+              {% if book.pdf_file %}
+                <span><a href="{{ book.pdf_url}}">PDF</a></span>
               {% endif %}
-              {% if  book.txt_file %}
-                <span><a href="{{ book.txt_file.url}}">TXT</a></span>
+              {% if book.epub_file %}
+                <span><a href="{{ book.epub_url}}">EPUB</a></span>
               {% endif %}
-              {% download_audio book mp3=False %}
-              <br>
-              {% custom_pdf_link_li book %}
-              <a class="read-more-hide hide" href="#">{% trans "less" %}</a>
-            </span>
-          </div>
-        </li>
-      </ul>
+              {% if book.mobi_file %}
+                <span><a href="{{ book.mobi_url}}">MOBI</a></span>
+              {% endif %}
+              {% if book.has_audio %}
+                <span><a href="{% url 'download_zip_mp3' book.slug %}">MP3</a></span>
+              {% endif %}
+              <a class="read-more-show hide" href="#">{% trans "more" %}</a>
+              <span class="read-more-content">
+                {% if  book.fb2_file %}
+                  <span><a href="{{ book.fb2_url}}">FB2</a></span>
+                {% endif %}
+                {% if  book.txt_file %}
+                  <span><a href="{{ book.txt_url}}">TXT</a></span>
+                {% endif %}
+                {% download_audio book mp3=False %}
+                <br>
+                {% custom_pdf_link_li book %}
+                <a class="read-more-hide hide" href="#">{% trans "less" %}</a>
+              </span>
+            </div>
+          </li>
+        </ul>
+      {% else %}
+        <p class="book-box-tools">{% trans "For now this work is only available for our subscribers." %}</p>
+      {% endif %}
       {% block book-box-extra-info %}{% endblock %}
       {% block box-append %}{% endblock %}
     </div>
index 93e755f..f697e4e 100644 (file)
@@ -60,7 +60,7 @@
 
 {% block big-pane %}
   <article id="main-text">
-    <!--#include file='{{ book.html_file.url }}'-->
+    <!--#include file='{{ book.html_url }}'-->
   </article>
 
   <article id="other-text">
@@ -85,7 +85,7 @@
           {% for other_version in book.other_versions %}
             <li>
               <a class="display-other"
-                 data-other="{{ other_version.html_file.url }}"
+                 data-other="{{ other_version.html_url }}"
                  href="{% url 'book_text' other_version.slug %}">
                 {% cache 86400 book_mini_box other_version.pk %}
                   {% include 'catalogue/book_mini_box.html' with book=other_version no_link=True %}
   </div>
 
   <div class="box" id="book-short">
-    {% cache 86400 catalogue_book_short book.pk %}
+    {% cache 86400 catalogue_book_short book.pk book|status:user %}
       {% include 'catalogue/book_short.html' %}
     {% endcache %}
   </div>
index 9f5c04b..6906f60 100644 (file)
@@ -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'
index addb0cb..50a4a4a 100644 (file)
@@ -64,6 +64,7 @@ urlpatterns += patterns(
 
     url(r'^audiobooki/(?P<type>mp3|ogg|daisy|all).xml$', AudiobookFeed(), name='audiobook_feed'),
 
+    url(r'^pobierz/(?P<slug>%s).(?P<format_>[a-z0-9]*)$' % SLUG, 'embargo_link', name='embargo_link'),
 
     # zip
     url(r'^zip/pdf\.zip$', 'download_zip', {'format': 'pdf', 'slug': None}, 'download_zip_pdf'),
index 2f3b949..2145312 100644 (file)
@@ -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
index 0a5ab58..c545cdf 100644 (file)
@@ -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()
index 6d0a9b2..6fbcd42 100644 (file)
@@ -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')
index 189ff0f..0c31cd2 100644 (file)
@@ -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 (file)
index 0000000..e0f7109
--- /dev/null
@@ -0,0 +1,8 @@
+{% extends "base/base.html" %}
+
+{% block body %}
+  <form method="post" action="{{ action }}">
+    {% csrf_token %}
+    <input type="submit" value="send post"/>
+  </form>
+{% endblock body %}
index fa01108..bf95306 100644 (file)
@@ -90,4 +90,5 @@ urlpatterns += [
 
 urlpatterns += [
     url(r'^error-test/$', views.exception_test),
+    # url(r'^post-test/$', views.post_test),
 ]
index 28ce223..a5db0a8 100644 (file)
@@ -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/'})