diff --git a/doc/development/api/customview.rst b/doc/development/api/customview.rst index 36a9553d7..8a1b8a576 100644 --- a/doc/development/api/customview.rst +++ b/doc/development/api/customview.rst @@ -68,35 +68,34 @@ Frontend views Including a custom view into the participant-facing frontend is a little bit different as there is no path prefix like ``control/``. -First, define your URL in your ``urls.py``, but this time in the ``event_patterns`` section:: +First, define your URL in your ``urls.py``, but this time in the ``event_patterns`` section and wrapped by +``event_url``:: - from django.conf.urls import url + from pretix.multidomain import event_url from . import views event_patterns = [ - url(r'^mypluginname/', views.frontend_view, name='frontend'), + event_url(r'^mypluginname/', views.frontend_view, name='frontend'), ] -You can then implement a view as you would normally do, but you need to apply a decorator to your -view if you want pretix's default behavior:: - - from pretix.presale.utils import event_view - - @event_view - def some_event_view(request, *args, **kwargs): - ... - -This decorator will check the URL arguments for their ``event`` and ``organizer`` parameters and -correctly ensure that: +You can then implement a view as you would normally do. It will be automatically ensured that: * The requested event exists -* The requested event is activated (can be overridden by decorating with ``@event_view(require_live=False)``) +* The requested event is active (you can disable this check using ``event_url(…, require_live=True)``) * The event is accessed via the domain it should be accessed * The ``request.event`` attribute contains the correct ``Event`` object * The ``request.organizer`` attribute contains the correct ``Organizer`` object +* Your plugin is enabled * The locale is set correctly +.. versionchanged:: 1.7 + + The ``event_url()`` wrapper has been added in 1.7 to replace the former ``@event_view`` decorator. The + ``event_url()`` wrapper is optional and using ``url()`` still works, but you will not be able to set the + ``require_live`` setting any more via the decorator. The ``@event_view`` decorator is now deprecated and + does nothing. + REST API viewsets ----------------- diff --git a/src/pretix/multidomain/__init__.py b/src/pretix/multidomain/__init__.py index 92bad9922..18ee01d55 100644 --- a/src/pretix/multidomain/__init__.py +++ b/src/pretix/multidomain/__init__.py @@ -1,4 +1,5 @@ from django.apps import AppConfig +from django.urls import RegexURLPattern class PretixMultidomainConfig(AppConfig): @@ -7,3 +8,12 @@ class PretixMultidomainConfig(AppConfig): default_app_config = 'pretix.multidomain.PretixMultidomainConfig' + + +def event_url(regex, view, kwargs=None, name=None, require_live=True): + if callable(view): + r = RegexURLPattern(regex, view, kwargs, name) + r._require_live = require_live + return r + else: + raise TypeError('view must be a callable.') diff --git a/src/pretix/multidomain/maindomain_urlconf.py b/src/pretix/multidomain/maindomain_urlconf.py index 45be79040..adb76c285 100644 --- a/src/pretix/multidomain/maindomain_urlconf.py +++ b/src/pretix/multidomain/maindomain_urlconf.py @@ -5,6 +5,7 @@ from django.apps import apps from django.conf.urls import include, url from django.views.generic import TemplateView +from pretix.multidomain.plugin_handler import plugin_event_urls from pretix.presale.urls import ( event_patterns, locale_patterns, organizer_patterns, ) @@ -27,8 +28,9 @@ for app in apps.get_app_configs(): if hasattr(urlmod, 'urlpatterns'): single_plugin_patterns += urlmod.urlpatterns if hasattr(urlmod, 'event_patterns'): + patterns = plugin_event_urls(urlmod.event_patterns, plugin=app.name) single_plugin_patterns.append(url(r'^(?P[^/]+)/(?P[^/]+)/', - include(urlmod.event_patterns))) + include(patterns))) raw_plugin_patterns.append( url(r'', include((single_plugin_patterns, app.label))) ) diff --git a/src/pretix/multidomain/middlewares.py b/src/pretix/multidomain/middlewares.py index 11518d83a..416cbd771 100644 --- a/src/pretix/multidomain/middlewares.py +++ b/src/pretix/multidomain/middlewares.py @@ -50,6 +50,7 @@ class MultiDomainMiddleware(MiddlewareMixin): cache.set('pretix_multidomain_organizer_{}'.format(domain), orga.pk if orga else False, 3600) if orga: + request.organizer_domain = True request.organizer = orga if isinstance(orga, Organizer) else Organizer.objects.get(pk=orga) request.urlconf = "pretix.multidomain.subdomain_urlconf" else: diff --git a/src/pretix/multidomain/plugin_handler.py b/src/pretix/multidomain/plugin_handler.py new file mode 100644 index 000000000..82db4e2a8 --- /dev/null +++ b/src/pretix/multidomain/plugin_handler.py @@ -0,0 +1,11 @@ +from pretix.presale.utils import _event_view + + +def plugin_event_urls(urllist, plugin): + for entry in urllist: + if hasattr(entry, 'url_patterns'): + plugin_event_urls(entry.url_patterns, plugin) + elif hasattr(entry, 'callback'): + entry.callback = _event_view(entry.callback, require_plugin=plugin, + require_live=getattr(entry, '_require_live', True)) + return urllist diff --git a/src/pretix/multidomain/subdomain_urlconf.py b/src/pretix/multidomain/subdomain_urlconf.py index eefb5b484..b34c767e5 100644 --- a/src/pretix/multidomain/subdomain_urlconf.py +++ b/src/pretix/multidomain/subdomain_urlconf.py @@ -4,6 +4,7 @@ import warnings from django.apps import apps from django.conf.urls import include, url +from pretix.multidomain.plugin_handler import plugin_event_urls from pretix.presale.urls import ( event_patterns, locale_patterns, organizer_patterns, ) @@ -22,9 +23,11 @@ for app in apps.get_app_configs(): if importlib.util.find_spec(app.name + '.urls'): urlmod = importlib.import_module(app.name + '.urls') if hasattr(urlmod, 'event_patterns'): + patterns = plugin_event_urls(urlmod.event_patterns, plugin=app.name) raw_plugin_patterns.append( - url(r'^(?P[^/]+)/', include((urlmod.event_patterns, app.label))) + url(r'^(?P[^/]+)/', include((patterns, app.label))) ) + elif importlib.util.find_spec(app.name + '.subdomain_urls'): # noqa warnings.warn('Please put your config in an \'urls\' module using the event_patterns ' 'attribute. Support for subdomain_urls in plugins will be dropped in the future.', diff --git a/src/pretix/plugins/paypal/urls.py b/src/pretix/plugins/paypal/urls.py index f1faa3a44..17b7ba841 100644 --- a/src/pretix/plugins/paypal/urls.py +++ b/src/pretix/plugins/paypal/urls.py @@ -1,12 +1,14 @@ from django.conf.urls import include, url -from .views import abort, event_webbook, refund, success, webhook +from pretix.multidomain import event_url + +from .views import abort, refund, success, webhook event_patterns = [ url(r'^paypal/', include([ url(r'^abort/$', abort, name='abort'), url(r'^return/$', success, name='return'), - url(r'^webhook/$', event_webbook, name='webhook'), + event_url(r'^webhook/$', webhook, name='webhook', require_live=False), ])), ] diff --git a/src/pretix/plugins/paypal/views.py b/src/pretix/plugins/paypal/views.py index 3568b3ad4..e883bb6a8 100644 --- a/src/pretix/plugins/paypal/views.py +++ b/src/pretix/plugins/paypal/views.py @@ -55,7 +55,6 @@ def success(request, *args, **kwargs): return redirect(eventreverse(request.event, 'presale:event.checkout', kwargs={'step': 'confirm'})) -@event_view(require_live=False) def abort(request, *args, **kwargs): messages.error(request, _('It looks like you canceled the PayPal payment')) @@ -151,9 +150,6 @@ def webhook(request, *args, **kwargs): return HttpResponse(status=200) -event_webbook = csrf_exempt(event_view(require_live=False)(webhook)) - - @event_permission_required('can_view_orders') @require_POST def refund(request, **kwargs): diff --git a/src/pretix/plugins/stripe/urls.py b/src/pretix/plugins/stripe/urls.py index f5d7b7c87..0e6502d86 100644 --- a/src/pretix/plugins/stripe/urls.py +++ b/src/pretix/plugins/stripe/urls.py @@ -1,10 +1,12 @@ from django.conf.urls import include, url -from .views import ReturnView, event_webbook, refund, webhook +from pretix.multidomain import event_url + +from .views import ReturnView, refund, webhook event_patterns = [ url(r'^stripe/', include([ - url(r'^webhook/$', event_webbook, name='webhook'), + event_url(r'^webhook/$', webhook, name='webhook', require_live=False), url(r'^return/(?P[^/]+)/(?P[^/]+)/$', ReturnView.as_view(), name='return'), ])), ] diff --git a/src/pretix/plugins/stripe/views.py b/src/pretix/plugins/stripe/views.py index b01c15f09..27ba09104 100644 --- a/src/pretix/plugins/stripe/views.py +++ b/src/pretix/plugins/stripe/views.py @@ -8,7 +8,6 @@ from django.db import transaction from django.http import Http404, HttpResponse from django.shortcuts import get_object_or_404, redirect from django.urls import reverse -from django.utils.decorators import method_decorator from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ from django.views import View @@ -22,7 +21,6 @@ from pretix.control.permissions import event_permission_required from pretix.multidomain.urlreverse import eventreverse from pretix.plugins.stripe.models import ReferencedStripeObject from pretix.plugins.stripe.payment import StripeCC -from pretix.presale.utils import event_view logger = logging.getLogger('pretix.plugins.stripe') @@ -59,9 +57,6 @@ def webhook(request, *args, **kwargs): return HttpResponse("Unable to detect event", status=200) -event_webbook = csrf_exempt(event_view(require_live=False)(webhook)) - - def charge_webhook(event, event_json, charge_id): prov = StripeCC(event) prov._init_api() @@ -198,7 +193,6 @@ class StripeOrderView: return self.request.event.get_payment_providers()[self.order.payment_provider] -@method_decorator(event_view, name='dispatch') class ReturnView(StripeOrderView, View): def get(self, request, *args, **kwargs): prov = self.pprov diff --git a/src/pretix/presale/utils.py b/src/pretix/presale/utils.py index e4b2934b1..67ae5423e 100644 --- a/src/pretix/presale/utils.py +++ b/src/pretix/presale/utils.py @@ -1,3 +1,4 @@ +import warnings from importlib import import_module from urllib.parse import urljoin @@ -16,10 +17,13 @@ from pretix.presale.signals import process_request, process_response SessionStore = import_module(settings.SESSION_ENGINE).SessionStore -def _detect_event(request, require_live=True): +def _detect_event(request, require_live=True, require_plugin=None): + if hasattr(request, '_event_detected'): + return + url = resolve(request.path_info) try: - if hasattr(request, 'organizer'): + if hasattr(request, 'organizer_domain'): # We are on an organizer's custom domain if 'organizer' in url.kwargs and url.kwargs['organizer']: if url.kwargs['organizer'] != request.organizer.slug: @@ -83,6 +87,10 @@ def _detect_event(request, require_live=True): if not can_access: raise PermissionDenied(_('The selected ticket shop is currently not available.')) + if require_plugin: + if require_plugin not in request.event.get_plugins(): + raise Http404(_('This feature is not enabled.')) + for receiver, response in process_request.send(request.event, request=request): if response: return response @@ -92,11 +100,13 @@ def _detect_event(request, require_live=True): except Organizer.DoesNotExist: raise Http404(_('The selected organizer was not found.')) + request._event_detected = True -def event_view(function=None, require_live=True): + +def _event_view(function=None, require_live=True, require_plugin=None): def event_view_wrapper(func, require_live=require_live): def wrap(request, *args, **kwargs): - ret = _detect_event(request, require_live=require_live) + ret = _detect_event(request, require_live=require_live, require_plugin=require_plugin) if ret: return ret else: @@ -104,8 +114,24 @@ def event_view(function=None, require_live=True): for receiver, r in process_response.send(request.event, request=request, response=response): response = r return response + + for attrname in dir(func): + # Preserve flags like csrf_exempt + if not attrname.startswith('__'): + setattr(wrap, attrname, getattr(func, attrname)) return wrap if function: return event_view_wrapper(function, require_live=require_live) return event_view_wrapper + + +def event_view(function=None, require_live=True): + warnings.warn('The event_view decorator is deprecated since it will be automatically applied by the URL routing ' + 'layer when you use event_urls.', + DeprecationWarning) + + def noop(fn): + return fn + + return function or noop diff --git a/src/tests/plugins/paypal/test_webhook.py b/src/tests/plugins/paypal/test_webhook.py index 0f796082a..440a9b5e0 100644 --- a/src/tests/plugins/paypal/test_webhook.py +++ b/src/tests/plugins/paypal/test_webhook.py @@ -16,7 +16,7 @@ def env(): user = User.objects.create_user('dummy@dummy.dummy', 'dummy') o = Organizer.objects.create(name='Dummy', slug='dummy') event = Event.objects.create( - organizer=o, name='Dummy', slug='dummy', + organizer=o, name='Dummy', slug='dummy', plugins='pretix.plugins.paypal', date_from=now(), live=True ) t = Team.objects.create(organizer=event.organizer, can_view_orders=True, can_change_orders=True) diff --git a/src/tests/plugins/stripe/test_webhook.py b/src/tests/plugins/stripe/test_webhook.py index 3dd7f8a2f..1743f7a85 100644 --- a/src/tests/plugins/stripe/test_webhook.py +++ b/src/tests/plugins/stripe/test_webhook.py @@ -16,7 +16,7 @@ def env(): user = User.objects.create_user('dummy@dummy.dummy', 'dummy') o = Organizer.objects.create(name='Dummy', slug='dummy') event = Event.objects.create( - organizer=o, name='Dummy', slug='dummy', + organizer=o, name='Dummy', slug='dummy', plugins='pretix.plugins.stripe', date_from=now(), live=True ) t = Team.objects.create(organizer=event.organizer, can_view_orders=True, can_change_orders=True)