Preview access for book funders.
authorRadek Czajka <rczajka@rczajka.pl>
Thu, 7 Apr 2022 09:59:32 +0000 (11:59 +0200)
committerRadek Czajka <rczajka@rczajka.pl>
Thu, 7 Apr 2022 09:59:32 +0000 (11:59 +0200)
src/catalogue/models/book.py
src/catalogue/templatetags/catalogue_tags.py
src/catalogue/views.py
src/funding/admin.py
src/funding/forms.py
src/funding/migrations/0006_funding_user.py [new file with mode: 0644]
src/funding/models.py
src/funding/templates/funding/email/base.txt
src/funding/templates/funding/email/published.txt
src/funding/urls.py
src/funding/views.py

index d3eddfd..9de4950 100644 (file)
@@ -230,7 +230,12 @@ class Book(models.Model):
         if not self.preview:
             return True
         Membership = apps.get_model('club', 'Membership')
         if not self.preview:
             return True
         Membership = apps.get_model('club', 'Membership')
-        return Membership.is_active_for(user)
+        if Membership.is_active_for(user):
+            return True
+        Funding = apps.get_model('funding', 'Funding')
+        if Funding.objects.filter(user=user, offer__book=self):
+            return True
+        return False
 
     def save(self, force_insert=False, force_update=False, **kwargs):
         from sortify import sortify
 
     def save(self, force_insert=False, force_update=False, **kwargs):
         from sortify import sortify
index 8124b34..93950d7 100644 (file)
@@ -509,7 +509,7 @@ def strip_tag(html, tag_name):
 def status(book, user):
     if not book.preview:
         return 'open'
 def status(book, user):
     if not book.preview:
         return 'open'
-    elif Membership.is_active_for(user):
+    elif book.is_accessible_to(user):
         return 'preview'
     else:
         return 'closed'
         return 'preview'
     else:
         return 'closed'
index fe4b2bf..037345b 100644 (file)
@@ -19,7 +19,7 @@ from django.views.decorators.cache import never_cache
 
 from ajaxable.utils import AjaxableFormView
 from club.forms import ScheduleForm
 
 from ajaxable.utils import AjaxableFormView
 from club.forms import ScheduleForm
-from club.models import Club, Membership
+from club.models import Club
 from annoy.models import DynamicTextInsert
 from pdcounter import views as pdcounter_views
 from picture.models import Picture, PictureArea
 from annoy.models import DynamicTextInsert
 from pdcounter import views as pdcounter_views
 from picture.models import Picture, PictureArea
@@ -328,7 +328,7 @@ def player(request, slug):
 def book_text(request, slug):
     book = get_object_or_404(Book, slug=slug)
 
 def book_text(request, slug):
     book = get_object_or_404(Book, slug=slug)
 
-    if book.preview and not Membership.is_active_for(request.user):
+    if not book.is_accessible_to(request.user):
         return HttpResponseRedirect(book.get_absolute_url())
 
     if not book.has_html_file():
         return HttpResponseRedirect(book.get_absolute_url())
 
     if not book.has_html_file():
@@ -427,7 +427,7 @@ class CustomPDFFormView(AjaxableFormView):
 
     def validate_object(self, obj, request):
         book = obj
 
     def validate_object(self, obj, request):
         book = obj
-        if book.preview and not Membership.is_active_for(request.user):
+        if not book.is_accessible_to(request.user):
             return HttpResponseRedirect(book.get_absolute_url())
         return super(CustomPDFFormView, self).validate_object(obj, request)
 
             return HttpResponseRedirect(book.get_absolute_url())
         return super(CustomPDFFormView, self).validate_object(obj, request)
 
index 27109e6..f2b1020 100644 (file)
@@ -62,6 +62,7 @@ class FundingAdmin(admin.ModelAdmin):
     list_display = ['payed_at', 'offer', 'amount', 'name', 'email', 'perk_names']
     search_fields = ['name', 'email', 'offer__title', 'offer__author']
     list_filter = [PayedFilter, 'offer', PerksFilter]
     list_display = ['payed_at', 'offer', 'amount', 'name', 'email', 'perk_names']
     search_fields = ['name', 'email', 'offer__title', 'offer__author']
     list_filter = [PayedFilter, 'offer', PerksFilter]
+    search_fields = ['user']
     actions = [export_as_csv_action(
         fields=[
             'id', 'offer', 'name', 'email', 'amount', 'payed_at',
     actions = [export_as_csv_action(
         fields=[
             'id', 'offer', 'name', 'email', 'amount', 'payed_at',
index ef79774..f56c087 100644 (file)
@@ -29,8 +29,9 @@ class FundingForm(NewsletterForm):
 W przypadku podania danych zostaną one wykorzystane w sposób podany powyżej, a w przypadku wyrażenia dodatkowej zgody 
 adres e-mail zostanie wykorzystany także w celu przesyłania newslettera Wolnych Lektur.'''
 
 W przypadku podania danych zostaną one wykorzystane w sposób podany powyżej, a w przypadku wyrażenia dodatkowej zgody 
 adres e-mail zostanie wykorzystany także w celu przesyłania newslettera Wolnych Lektur.'''
 
-    def __init__(self, offer, *args, **kwargs):
+    def __init__(self, request, offer, *args, **kwargs):
         self.offer = offer
         self.offer = offer
+        self.user = request.user if request.user.is_authenticated else None
         super(FundingForm, self).__init__(*args, **kwargs)
         self.fields['amount'].widget.form_instance = self
 
         super(FundingForm, self).__init__(*args, **kwargs)
         self.fields['amount'].widget.form_instance = self
 
@@ -57,6 +58,7 @@ adres e-mail zostanie wykorzystany także w celu przesyłania newslettera Wolnyc
             email=self.cleaned_data['email'],
             amount=self.cleaned_data['amount'],
             language_code=get_language(),
             email=self.cleaned_data['email'],
             amount=self.cleaned_data['amount'],
             language_code=get_language(),
+            user=self.user,
         )
         funding.perks.set(funding.offer.get_perks(funding.amount))
         return funding
         )
         funding.perks.set(funding.offer.get_perks(funding.amount))
         return funding
diff --git a/src/funding/migrations/0006_funding_user.py b/src/funding/migrations/0006_funding_user.py
new file mode 100644 (file)
index 0000000..c295b2e
--- /dev/null
@@ -0,0 +1,21 @@
+# Generated by Django 2.2.27 on 2022-04-07 08:38
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('funding', '0005_auto_20210120_1358'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='funding',
+            name='user',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
+        ),
+    ]
index 289ec33..0e3fa42 100644 (file)
@@ -270,6 +270,7 @@ class Funding(models.Model):
     offer = models.ForeignKey(Offer, models.PROTECT, verbose_name=_('offer'))
     name = models.CharField(_('name'), max_length=127, blank=True)
     email = models.EmailField(_('email'), blank=True, db_index=True)
     offer = models.ForeignKey(Offer, models.PROTECT, verbose_name=_('offer'))
     name = models.CharField(_('name'), max_length=127, blank=True)
     email = models.EmailField(_('email'), blank=True, db_index=True)
+    user = models.ForeignKey(settings.AUTH_USER_MODEL, models.SET_NULL, blank=True, null=True)
     amount = models.DecimalField(_('amount'), decimal_places=2, max_digits=10)
     payed_at = models.DateTimeField(_('payed at'), null=True, blank=True, db_index=True)
     perks = models.ManyToManyField(Perk, verbose_name=_('perks'), blank=True)
     amount = models.DecimalField(_('amount'), decimal_places=2, max_digits=10)
     payed_at = models.DateTimeField(_('payed at'), null=True, blank=True, db_index=True)
     perks = models.ManyToManyField(Perk, verbose_name=_('perks'), blank=True)
index 101cec6..f147469 100644 (file)
@@ -2,11 +2,11 @@
 {% block body %}
 {% endblock %}{% get_current_language as LANGUAGE_CODE %}
 {% if LANGUAGE_CODE == 'pl' %}Chcesz wiedzieć więcej o działaniach realizowanych w ramach projektu Wolne Lektury?
 {% block body %}
 {% endblock %}{% get_current_language as LANGUAGE_CODE %}
 {% if LANGUAGE_CODE == 'pl' %}Chcesz wiedzieć więcej o działaniach realizowanych w ramach projektu Wolne Lektury?
-Zapisz się na nasz newsletter http://{{ site.domain }}{% url 'subscribe' %}, a już nigdy nie przegapisz żadnej publikacji nowej książki czy kolejnej zbiórki.{% endif %}
+Zapisz się na nasz newsletter https://{{ site.domain }}{% url 'subscribe' %}, a już nigdy nie przegapisz żadnej publikacji nowej książki czy kolejnej zbiórki.{% endif %}
 {% blocktrans %}Cheers,
 Wolne Lektury team{% endblocktrans %}
 
 -- 
 {% blocktrans %}If you don't want to receive any more updates, please visit this page:{% endblocktrans %}
 {% blocktrans %}Cheers,
 Wolne Lektury team{% endblocktrans %}
 
 -- 
 {% blocktrans %}If you don't want to receive any more updates, please visit this page:{% endblocktrans %}
-http://{{site.domain}}{{ funding.get_disable_notifications_url }}
+https://{{site.domain}}{{ funding.get_disable_notifications_url }}
 {% endautoescape %}
 {% endautoescape %}
index 672efa2..fbc71ad 100644 (file)
@@ -4,14 +4,23 @@
 {% blocktrans %}we have just published the book you contributed to:{% endblocktrans %}
 
   {{ author }} – {{ offer.book.title }}
 {% blocktrans %}we have just published the book you contributed to:{% endblocktrans %}
 
   {{ author }} – {{ offer.book.title }}
-  http://{{ site.domain }}{{ offer.book.get_absolute_url }}
+  https://{{ site.domain }}{{ offer.book.get_absolute_url }}
 
 
+{% if book.preview %}
+Dzięki Tobie, niedługo będzie bezpłatnie dla wszystkich w wielu formatach. Tymczasem możesz przeczytać ją już teraz jako prapremierę.
+{% if funding.user %}Wystarczy, że zalogujesz się na Wolnych Lekturach (jako {{ funding.user.username }}).
+{% else %}Wystarczy, że zalogujesz się na Wolnych Lekturach i wejdziesz pod ten adres, aby uzyskać wcześniejszy dostęp:
+https://{{ site.domain }}{% url 'funding_claim' funding.notify_key %}
+{% endif %}
+{% else %}
 {% blocktrans %}Thanks to you, it is now available for free,
 in various formats, to everyone.{% endblocktrans %}
 {% blocktrans %}Thanks to you, it is now available for free,
 in various formats, to everyone.{% endblocktrans %}
+{% endif %}
+
 {% if current %}
 {% blocktrans %}If you'd like to help liberate another book, or invite your friends
 to do so, we're currently raising money for:{% endblocktrans %}
 
   {{ current.author }} – {{ current.title }}
 {% if current %}
 {% blocktrans %}If you'd like to help liberate another book, or invite your friends
 to do so, we're currently raising money for:{% endblocktrans %}
 
   {{ current.author }} – {{ current.title }}
-  http://{{ site.domain }}{% url 'funding_current' %}
+  https://{{ site.domain }}{% url 'funding_current' %}
 {% endif %}{% endblock %}
 {% endif %}{% endblock %}
index d728937..fd9708f 100644 (file)
@@ -18,6 +18,7 @@ urlpatterns = [
     path('niepowodzenie/', banner_exempt(views.NoThanksView.as_view()), name='funding_nothanks'),
 
     path('wylacz_email/', banner_exempt(views.DisableNotifications.as_view()), name='funding_disable_notifications'),
     path('niepowodzenie/', banner_exempt(views.NoThanksView.as_view()), name='funding_nothanks'),
 
     path('wylacz_email/', banner_exempt(views.DisableNotifications.as_view()), name='funding_disable_notifications'),
+    path('przylacz/<key>/', banner_exempt(views.claim), name='funding_claim'),
 
     path('getpaid/', include('getpaid.urls')),
 ]
 
     path('getpaid/', include('getpaid.urls')),
 ]
index 5d4669e..750f021 100644 (file)
@@ -1,9 +1,10 @@
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
-from django.http import Http404
+from django.http import Http404, HttpResponseRedirect
 from django.shortcuts import get_object_or_404, redirect, render
 from django.urls import reverse
 from django.shortcuts import get_object_or_404, redirect, render
 from django.urls import reverse
+from django.contrib.auth.decorators import login_required
 from django.views.decorators.csrf import csrf_exempt
 from django.views.generic import TemplateView, FormView, ListView
 from getpaid.models import Payment
 from django.views.decorators.csrf import csrf_exempt
 from django.views.generic import TemplateView, FormView, ListView
 from getpaid.models import Payment
@@ -92,9 +93,9 @@ class OfferDetailView(FormView):
         if form_class is None:
             form_class = self.get_form_class()
         if self.request.method == 'POST':
         if form_class is None:
             form_class = self.get_form_class()
         if self.request.method == 'POST':
-            return form_class(self.object, self.request.POST)
+            return form_class(self.request, self.object, self.request.POST)
         else:
         else:
-            return form_class(self.object, initial={'amount': app_settings.DEFAULT_AMOUNT})
+            return form_class(self.request, self.object, initial={'amount': app_settings.DEFAULT_AMOUNT})
 
     def get_context_data(self, **kwargs):
         ctx = super(OfferDetailView, self).get_context_data(**kwargs)
 
     def get_context_data(self, **kwargs):
         ctx = super(OfferDetailView, self).get_context_data(**kwargs)
@@ -157,3 +158,16 @@ class DisableNotifications(TemplateView):
     def post(self, *args, **kwargs):
         self.object.disable_notifications()
         return redirect(self.request.get_full_path())
     def post(self, *args, **kwargs):
         self.object.disable_notifications()
         return redirect(self.request.get_full_path())
+
+
+@login_required
+def claim(request, key):
+    funding = get_object_or_404(Funding, notify_key=key)
+    if funding.user is None:
+        funding.user = request.user
+        funding.save()
+    return HttpResponseRedirect(
+        funding.offer.book.get_absolute_url() if funding.offer.book is not None
+        else funding.offer.get_absolute_url()
+    )
+