From 3fceb197b7f492cf5cfcb4034b6e045638f128d4 Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Mon, 18 Mar 2019 12:39:06 +0100 Subject: [PATCH 1/1] Move checking membership to Membership. --- src/api/fields.py | 5 ++--- src/api/tests/tests.py | 4 +--- src/catalogue/api/views.py | 5 ++--- src/catalogue/templatetags/catalogue_tags.py | 5 ++--- src/catalogue/views.py | 8 ++++---- src/club/models.py | 11 ++++++++++- src/club/permissions.py | 7 +++++++ src/paypal/permissions.py | 7 ------- src/paypal/tests.py | 7 +++++++ src/paypal/urls.py | 7 ++++--- 10 files changed, 39 insertions(+), 27 deletions(-) create mode 100644 src/club/permissions.py delete mode 100644 src/paypal/permissions.py diff --git a/src/api/fields.py b/src/api/fields.py index 66fd7d2e9..278af6aea 100644 --- a/src/api/fields.py +++ b/src/api/fields.py @@ -1,11 +1,10 @@ -# -*- 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 rest_framework import serializers from sorl.thumbnail import default from django.core.urlresolvers import reverse -from paypal.rest import user_is_subscribed +from club.models import Membership class AbsoluteURLField(serializers.ReadOnlyField): @@ -45,7 +44,7 @@ class UserPremiumField(serializers.ReadOnlyField): super(UserPremiumField, self).__init__(*args, source='*', **kwargs) def to_representation(self, value): - return user_is_subscribed(value) + return Membership.is_active_for(value) class ThumbnailField(serializers.FileField): diff --git a/src/api/tests/tests.py b/src/api/tests/tests.py index eccdd04a8..c37fef33b 100644 --- a/src/api/tests/tests.py +++ b/src/api/tests/tests.py @@ -1,4 +1,3 @@ -# -*- 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. # @@ -445,11 +444,10 @@ class AuthorizedTests(ApiTest): self.signed('/api/epub/grandchild/').status_code, 403) - with patch('api.fields.user_is_subscribed', return_value=True): + with patch('club.models.Membership.is_active_for', return_value=True): self.assertEqual( self.signed_json('/api/username/'), {"username": "test", "premium": True}) - with patch('paypal.permissions.user_is_subscribed', return_value=True): with patch('django.core.files.storage.Storage.open', return_value=BytesIO(b"")): self.assertEqual( diff --git a/src/catalogue/api/views.py b/src/catalogue/api/views.py index 7c4b88825..5a064c1c5 100644 --- a/src/catalogue/api/views.py +++ b/src/catalogue/api/views.py @@ -1,4 +1,3 @@ -# -*- 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. # @@ -8,7 +7,6 @@ from rest_framework.generics import ListAPIView, RetrieveAPIView, get_object_or_ from rest_framework.permissions import DjangoModelPermissionsOrAnonReadOnly from rest_framework.response import Response from rest_framework import status -from paypal.permissions import IsSubscribed from api.handlers import read_tags from api.utils import vary_on_auth from .helpers import books_after, order_books @@ -16,6 +14,7 @@ from . import serializers from catalogue.forms import BookImportForm from catalogue.models import Book, Collection, Tag, Fragment, BookMedia from catalogue.models.tag import prefetch_relations +from club.permissions import IsClubMember from wolnelektury.utils import re_escape @@ -226,7 +225,7 @@ class FilterBookList(ListAPIView): class EpubView(RetrieveAPIView): queryset = Book.objects.all() lookup_field = 'slug' - permission_classes = [IsSubscribed] + permission_classes = [IsClubMember] def get(self, *args, **kwargs): return HttpResponse(self.get_object().get_media('epub')) diff --git a/src/catalogue/templatetags/catalogue_tags.py b/src/catalogue/templatetags/catalogue_tags.py index 70676dd1a..ef0ea5dcc 100644 --- a/src/catalogue/templatetags/catalogue_tags.py +++ b/src/catalogue/templatetags/catalogue_tags.py @@ -1,4 +1,3 @@ -# -*- 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. # @@ -19,7 +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 paypal.rest import user_is_subscribed +from club.models import Membership from picture.models import Picture register = template.Library() @@ -499,7 +498,7 @@ def strip_tag(html, tag_name): def status(book, user): if not book.preview: return 'open' - elif user_is_subscribed(user): + elif Membership.is_active_for(user): return 'preview' else: return 'closed' diff --git a/src/catalogue/views.py b/src/catalogue/views.py index 2c9f5dd11..2ea5eb019 100644 --- a/src/catalogue/views.py +++ b/src/catalogue/views.py @@ -17,8 +17,8 @@ from django.utils import translation from django.utils.translation import ugettext as _, ugettext_lazy from ajaxable.utils import AjaxableFormView +from club.models import Membership from pdcounter import views as pdcounter_views -from paypal.rest import user_is_subscribed from picture.models import Picture, PictureArea from ssify import ssi_included, ssi_expect, SsiVariable as Var from catalogue import constants @@ -306,7 +306,7 @@ def player(request, slug): def book_text(request, slug): book = get_object_or_404(Book, slug=slug) - if book.preview and not user_is_subscribed(request.user): + if book.preview and not Membership.is_active_for(request.user): return HttpResponseRedirect(book.get_absolute_url()) if not book.has_html_file(): @@ -361,7 +361,7 @@ def embargo_link(request, format_, slug): media_file = book.get_media(format_) if not book.preview: return HttpResponseRedirect(media_file.url) - if not user_is_subscribed(request.user): + if not Membership.is_active_for(request.user): return HttpResponseRedirect(book.get_absolute_url()) return HttpResponse(media_file, content_type=constants.EBOOK_CONTENT_TYPES[format_]) @@ -395,7 +395,7 @@ class CustomPDFFormView(AjaxableFormView): def validate_object(self, obj, request): book = obj - if book.preview and not user_is_subscribed(request.user): + if book.preview and not Membership_is_active_for(request.user): return HttpResponseRedirect(book.get_absolute_url()) return super(CustomPDFFormView, self).validate_object(obj, request) diff --git a/src/club/models.py b/src/club/models.py index 17771742c..ede1a8d30 100644 --- a/src/club/models.py +++ b/src/club/models.py @@ -88,7 +88,6 @@ class Schedule(models.Model): def get_payment_method(self): return method_by_slug[self.method] - def is_expired(self): return self.expires_at is not None and self.expires_at < now() @@ -125,6 +124,16 @@ class Membership(models.Model): def __str__(self): return u'tow. ' + str(self.user) + @classmethod + def is_active_for(self, user): + if user.is_anonymous: + return False + return Schedule.objects.filter( + models.Q(expires_at=None) | models.Q(expires_at__lt=now()), + membership__user=user, + is_active=True, + ).exists() + class ReminderEmail(models.Model): days_before = models.SmallIntegerField(_('days before')) diff --git a/src/club/permissions.py b/src/club/permissions.py new file mode 100644 index 000000000..a08880db1 --- /dev/null +++ b/src/club/permissions.py @@ -0,0 +1,7 @@ +from rest_framework.permissions import BasePermission +from .models import Membership + + +class IsClubMember(BasePermission): + def has_permission(self, request, view): + return Membership.is_active_for(request.user) diff --git a/src/paypal/permissions.py b/src/paypal/permissions.py deleted file mode 100644 index 9d0865b0e..000000000 --- a/src/paypal/permissions.py +++ /dev/null @@ -1,7 +0,0 @@ -from rest_framework.permissions import BasePermission -from .rest import user_is_subscribed - - -class IsSubscribed(BasePermission): - def has_permission(self, request, view): - return request.user.is_authenticated and user_is_subscribed(request.user) diff --git a/src/paypal/tests.py b/src/paypal/tests.py index 7de047af6..52c57473e 100644 --- a/src/paypal/tests.py +++ b/src/paypal/tests.py @@ -2,6 +2,7 @@ # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # from django.contrib.auth.models import User +from unittest import skip from unittest.mock import MagicMock, Mock, patch, DEFAULT from catalogue.test_utils import WLTestCase from .models import BillingAgreement, BillingPlan @@ -59,15 +60,18 @@ class PaypalTests(WLTestCase): def tearDownClass(cls): cls.user.delete() + @skip("Changing the flow.") def test_paypal_form(self): response = self.client.get('/paypal/form/') self.assertEqual(response.status_code, 200) + @skip("Changing the flow.") def test_paypal_form_unauthorized(self): """Legacy flow: only allow payment for logged-in users.""" response = self.client.post('/paypal/form/', {"amount": "0"}) self.assertEqual(response.status_code, 403) + @skip("Changing the flow.") def test_paypal_form_invalid(self): """Paypal form: error on bad input.""" self.client.login(username='test', password='test') @@ -78,6 +82,7 @@ class PaypalTests(WLTestCase): len(response.context['form'].errors['amount']), 1) + @skip("Changing the flow.") @patch.multiple('paypalrestsdk', BillingPlan=BillingPlanMock, BillingAgreement=BillingAgreementMock, @@ -99,6 +104,7 @@ class PaypalTests(WLTestCase): # No BillingAgreement created in our DB yet. self.assertEqual(BillingAgreement.objects.all().count(), 0) + @skip("Changing the flow.") @patch('paypalrestsdk.BillingPlan', BillingPlanMock) def test_paypal_form_error(self): """On PayPal error, plan does not get created.""" @@ -128,6 +134,7 @@ class PaypalTests(WLTestCase): # But now the plan should be created. self.assertEqual(BillingPlan.objects.all().count(), 1) + @skip("Changing the flow.") @patch.multiple('paypalrestsdk', BillingPlan=BillingPlanMock, BillingAgreement=BillingAgreementMock, diff --git a/src/paypal/urls.py b/src/paypal/urls.py index 38f636d2a..bd2699398 100644 --- a/src/paypal/urls.py +++ b/src/paypal/urls.py @@ -1,13 +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. # from django.conf.urls import url +from django.views.generic import RedirectView from . import views urlpatterns = ( - url(r'^form/$', views.paypal_form, name='paypal_form'), - url(r'^app-form/$', views.paypal_form, kwargs={'app': True}, name='paypal_app_form'), + url(r'^form/$', RedirectView.as_view(url='/towarzystwo/dolacz/')), + url(r'^app-form/$', RedirectView.as_view(url='/towarzystwo/dolacz/app/')), + url(r'^return/$', views.paypal_return, name='paypal_return'), url(r'^app-return/$', views.paypal_return, kwargs={'app': True}, name='paypal_app_return'), url(r'^cancel/$', views.paypal_cancel, name='paypal_cancel'), -- 2.20.1