+{% endblock %}
\ No newline at end of file
diff --git a/src/pretix/base/templates/error.html b/src/pretix/base/templates/error.html
new file mode 100644
index 0000000000..b26764b9c9
--- /dev/null
+++ b/src/pretix/base/templates/error.html
@@ -0,0 +1,18 @@
+{% load compress %}
+{% load i18n %}
+{% load staticfiles %}
+
+
+
+ {% block title %}{% endblock %}
+ {% compress css %}
+
+ {% endcompress %}
+
+
+
+
+ {% block content %}{% endblock %}
+
+
+
diff --git a/src/pretix/presale/templates/pretixpresale/cachedfiles/pending.html b/src/pretix/base/templates/pretixbase/cachedfiles/pending.html
similarity index 95%
rename from src/pretix/presale/templates/pretixpresale/cachedfiles/pending.html
rename to src/pretix/base/templates/pretixbase/cachedfiles/pending.html
index 3ab082ec9d..23764cb940 100644
--- a/src/pretix/presale/templates/pretixpresale/cachedfiles/pending.html
+++ b/src/pretix/base/templates/pretixbase/cachedfiles/pending.html
@@ -6,7 +6,7 @@
{{ settings.PRETIX_INSTANCE_NAME }}
{% compress css %}
-
+
{% endcompress %}
{% compress js %}
diff --git a/src/pretix/base/views/__init__.py b/src/pretix/base/views/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/pretix/presale/views/cachedfiles.py b/src/pretix/base/views/cachedfiles.py
similarity index 91%
rename from src/pretix/presale/views/cachedfiles.py
rename to src/pretix/base/views/cachedfiles.py
index e1d39c039d..31684f7861 100644
--- a/src/pretix/presale/views/cachedfiles.py
+++ b/src/pretix/base/views/cachedfiles.py
@@ -7,7 +7,7 @@ from pretix.base.models import CachedFile
class DownloadView(TemplateView):
- template_name = "pretixpresale/cachedfiles/pending.html"
+ template_name = "pretixbase/cachedfiles/pending.html"
@cached_property
def object(self):
diff --git a/src/pretix/control/middleware.py b/src/pretix/control/middleware.py
index 47855a9f46..bd58d285b9 100644
--- a/src/pretix/control/middleware.py
+++ b/src/pretix/control/middleware.py
@@ -3,7 +3,7 @@ from urllib.parse import urlparse
from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.core.urlresolvers import get_script_prefix, resolve
-from django.http import HttpResponseNotFound
+from django.http import Http404
from django.shortcuts import resolve_url
from django.utils.encoding import force_str
from django.utils.translation import ugettext as _
@@ -12,7 +12,6 @@ from pretix.base.models import Event, EventPermission, Organizer
class PermissionMiddleware:
-
"""
This middleware enforces all requests to the control app to require login.
Additionally, it enforces all requests to "control:event." URLs
@@ -43,6 +42,7 @@ class PermissionMiddleware:
(not login_netloc or login_netloc == current_netloc)):
path = request.get_full_path()
from django.contrib.auth.views import redirect_to_login
+
return redirect_to_login(
path, resolved_login_url, REDIRECT_FIELD_NAME)
@@ -61,8 +61,8 @@ class PermissionMiddleware:
)
request.organizer = request.event.organizer
except IndexError:
- return HttpResponseNotFound(_("The selected event was not found or you "
- "have no permission to administrate it."))
+ raise Http404(_("The selected event was not found or you "
+ "have no permission to administrate it."))
elif 'organizer' in url.kwargs:
try:
request.organizer = Organizer.objects.current.filter(
@@ -70,5 +70,5 @@ class PermissionMiddleware:
permitted__id__exact=request.user.id,
)[0]
except IndexError:
- return HttpResponseNotFound(_("The selected organizer was not found or you "
- "have no permission to administrate it."))
+ return Http404(_("The selected organizer was not found or you "
+ "have no permission to administrate it."))
diff --git a/src/pretix/control/permissions.py b/src/pretix/control/permissions.py
index ca9fb639d9..1b31daa72f 100644
--- a/src/pretix/control/permissions.py
+++ b/src/pretix/control/permissions.py
@@ -1,4 +1,4 @@
-from django.http import HttpResponseForbidden
+from django.core.exceptions import PermissionDenied
from django.utils.translation import ugettext as _
from pretix.base.models import EventPermission, OrganizerPermission
@@ -13,7 +13,7 @@ def event_permission_required(permission):
def wrapper(request, *args, **kw):
if not request.user.is_authenticated(): # NOQA
# just a double check, should not ever happen
- return HttpResponseForbidden()
+ raise PermissionDenied()
try:
perm = EventPermission.objects.current.get(
event=request.event,
@@ -30,7 +30,7 @@ def event_permission_required(permission):
pass
if allowed:
return function(request, *args, **kw)
- return HttpResponseForbidden(_('You do not have permission to view this content.'))
+ raise PermissionDenied(_('You do not have permission to view this content.'))
return wrapper
return decorator
@@ -57,7 +57,7 @@ def organizer_permission_required(permission):
def wrapper(request, *args, **kw):
if not request.user.is_authenticated(): # NOQA
# just a double check, should not ever happen
- return HttpResponseForbidden()
+ raise PermissionDenied()
try:
perm = OrganizerPermission.objects.current.get(
organizer=request.organizer,
@@ -74,7 +74,7 @@ def organizer_permission_required(permission):
pass
if allowed or request.user.is_superuser:
return function(request, *args, **kw)
- return HttpResponseForbidden(_('You do not have permission to view this content.'))
+ raise PermissionDenied(_('You do not have permission to view this content.'))
return wrapper
return decorator
diff --git a/src/pretix/control/views/item.py b/src/pretix/control/views/item.py
index 0adac93f14..5054b7c335 100644
--- a/src/pretix/control/views/item.py
+++ b/src/pretix/control/views/item.py
@@ -4,7 +4,7 @@ from django.contrib import messages
from django.core.urlresolvers import resolve, reverse
from django.db import transaction
from django.forms.models import inlineformset_factory
-from django.http import Http404, HttpResponseForbidden, HttpResponseRedirect
+from django.http import Http404, HttpResponseRedirect
from django.shortcuts import redirect
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
@@ -380,7 +380,8 @@ class PropertyDelete(EventPermissionRequiredMixin, DeleteView):
messages.success(request, _('The selected property has been deleted.'))
return HttpResponseRedirect(success_url)
else:
- return HttpResponseForbidden()
+ messages.error(request, _('The selected property can not be deleted.'))
+ return HttpResponseRedirect(self.get_success_url())
def get_success_url(self) -> str:
return reverse('control:event.items.properties', kwargs={
diff --git a/src/pretix/control/views/organizer.py b/src/pretix/control/views/organizer.py
index 099119c1c8..4bb36da003 100644
--- a/src/pretix/control/views/organizer.py
+++ b/src/pretix/control/views/organizer.py
@@ -1,6 +1,6 @@
from django.contrib import messages
+from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse
-from django.http import HttpResponseForbidden
from django.utils.translation import ugettext_lazy as _
from django.views.generic import CreateView, ListView, UpdateView
@@ -52,7 +52,7 @@ class OrganizerCreate(CreateView):
def dispatch(self, request, *args, **kwargs):
if not request.user.is_superuser:
- return HttpResponseForbidden() # TODO
+ raise PermissionDenied() # TODO
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form):
diff --git a/src/pretix/presale/middleware.py b/src/pretix/presale/middleware.py
index bb2e7419fc..70685e46c1 100644
--- a/src/pretix/presale/middleware.py
+++ b/src/pretix/presale/middleware.py
@@ -1,5 +1,6 @@
from django.core.urlresolvers import resolve
-from django.http import HttpResponseNotFound
+from django.http import Http404
+from django.utils.translation import ugettext_lazy as _
from pretix.base.models import Event
@@ -30,4 +31,4 @@ class EventMiddleware:
organizer__slug=url.kwargs['organizer'],
).select_related('organizer')[0]
except IndexError:
- return HttpResponseNotFound('Unknown event') # TODO: Provide error message
+ return Http404(_('The selected event was not found.'))
diff --git a/src/pretix/presale/urls.py b/src/pretix/presale/urls.py
index 2676c08d00..dbc6481169 100644
--- a/src/pretix/presale/urls.py
+++ b/src/pretix/presale/urls.py
@@ -1,6 +1,5 @@
from django.conf.urls import include, url
-import pretix.presale.views.cachedfiles
import pretix.presale.views.cart
import pretix.presale.views.checkout
import pretix.presale.views.event
@@ -8,8 +7,6 @@ import pretix.presale.views.locale
import pretix.presale.views.order
urlpatterns = [
- url(r'^download/(?P[^/]+)/$', pretix.presale.views.cachedfiles.DownloadView.as_view(),
- name='cachedfile.download'),
url(r'^(?P[^/]+)/(?P[^/]+)/', include([
url(r'^$', pretix.presale.views.event.EventIndex.as_view(), name='event.index'),
url(r'^cart/add$', pretix.presale.views.cart.CartAdd.as_view(), name='event.cart.add'),
diff --git a/src/pretix/presale/views/order.py b/src/pretix/presale/views/order.py
index 4534675e3c..20b581e292 100644
--- a/src/pretix/presale/views/order.py
+++ b/src/pretix/presale/views/order.py
@@ -3,15 +3,18 @@ from datetime import timedelta
from django.contrib import messages
from django.core.urlresolvers import reverse
from django.db.models import Q
-from django.http import HttpResponseForbidden, HttpResponseNotFound
+from django.http import Http404
from django.shortcuts import redirect
from django.utils.functional import cached_property
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from django.views.generic import TemplateView, View
+
from pretix.base.models import CachedFile, CachedTicket, Order, OrderPosition
from pretix.base.services.tickets import generate
-from pretix.base.signals import register_payment_providers, register_ticket_outputs
+from pretix.base.signals import (
+ register_payment_providers, register_ticket_outputs,
+)
from pretix.presale.views import CartDisplayMixin, EventViewMixin
from pretix.presale.views.checkout import QuestionsViewMixin
@@ -49,7 +52,7 @@ class OrderDetails(EventViewMixin, OrderDetailMixin, CartDisplayMixin, TemplateV
def get(self, request, *args, **kwargs):
self.kwargs = kwargs
if not self.order:
- return HttpResponseNotFound(_('Unknown order code or order does belong to another user.'))
+ raise Http404(_('Unknown order code or order does belong to another user.'))
return super().get(request, *args, **kwargs)
@cached_property
@@ -101,7 +104,7 @@ class OrderPay(EventViewMixin, OrderDetailMixin, TemplateView):
def dispatch(self, request, *args, **kwargs):
self.request = request
if not self.order:
- return HttpResponseNotFound(_('Unknown order code or order does belong to another user.'))
+ raise Http404(_('Unknown order code or order does belong to another user.'))
if (self.order.status not in (Order.STATUS_PENDING, Order.STATUS_EXPIRED)
or not self.payment_provider.order_can_retry(self.order)
or not self.payment_provider.is_enabled):
@@ -144,7 +147,7 @@ class OrderPayDo(EventViewMixin, OrderDetailMixin, TemplateView):
def dispatch(self, request, *args, **kwargs):
self.request = request
if not self.order:
- return HttpResponseNotFound(_('Unknown order code or order does belong to another user.'))
+ raise Http404(_('Unknown order code or order does belong to another user.'))
if not self.payment_provider.order_can_retry(self.order) or not self.payment_provider.is_enabled:
messages.error(request, _('The payment for this order cannot be continued.'))
return redirect(self.get_order_url())
@@ -207,9 +210,11 @@ class OrderModify(EventViewMixin, OrderDetailMixin, QuestionsViewMixin, Template
self.request = request
self.kwargs = kwargs
if not self.order:
- return HttpResponseNotFound(_('Unknown order code or order does belong to another user.'))
+ messages.error(request, _('Unknown order code or order does belong to another user.'))
+ return redirect(self.get_order_url())
if not self.order.can_modify_answers:
- return HttpResponseForbidden(_('You cannot modify this order'))
+ messages.error(request, _('You cannot modify this order'))
+ return redirect(self.get_order_url())
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
@@ -226,9 +231,10 @@ class OrderCancel(EventViewMixin, OrderDetailMixin, TemplateView):
self.request = request
self.kwargs = kwargs
if not self.order:
- return HttpResponseNotFound(_('Unknown order code or order does belong to another user.'))
+ raise Http404(_('Unknown order code or order does belong to another user.'))
if self.order.status not in (Order.STATUS_PENDING, Order.STATUS_EXPIRED):
- return HttpResponseForbidden(_('You cannot cancel this order'))
+ messages.error(request, _('You cannot cancel this order'))
+ return redirect(self.get_order_url())
return super().dispatch(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
@@ -260,7 +266,7 @@ class OrderDownload(EventViewMixin, OrderDetailMixin, View):
messages.error(request, _('You requested an invalid ticket output type.'))
return redirect(self.get_order_url())
if not self.order:
- return HttpResponseNotFound(_('Unknown order code or order does belong to another user.'))
+ raise Http404(_('Unknown order code or order does belong to another user.'))
if self.order.status != Order.STATUS_PAID:
messages.error(request, _('Order is not paid.'))
return redirect(self.get_order_url())
diff --git a/src/pretix/urls.py b/src/pretix/urls.py
index 92140c8f6f..74a803617a 100644
--- a/src/pretix/urls.py
+++ b/src/pretix/urls.py
@@ -5,10 +5,13 @@ from django.apps import apps
from django.conf import settings
from django.conf.urls import include, url
+import pretix.base.views.cachedfiles
import pretix.control.urls
import pretix.presale.urls
urlpatterns = [
+ url(r'^download/(?P[^/]+)/$', pretix.base.views.cachedfiles.DownloadView.as_view(),
+ name='cachedfile.download'),
url(r'^control/', include(pretix.control.urls, namespace='control')),
# The pretixpresale namespace is configured at the bottom of this file, because it
# contains a wildcard-style URL which has to be configured _after_ debug settings.
diff --git a/src/static/pretixpresale/less/cachedfiles.less b/src/static/pretixbase/less/cachedfiles.less
similarity index 84%
rename from src/static/pretixpresale/less/cachedfiles.less
rename to src/static/pretixbase/less/cachedfiles.less
index 2e816d8a86..0d7289f1e2 100644
--- a/src/static/pretixpresale/less/cachedfiles.less
+++ b/src/static/pretixbase/less/cachedfiles.less
@@ -1,11 +1,9 @@
@import "../../bootstrap/less/bootstrap.less";
@import "../../fontawesome/less/font-awesome.less";
-@import "../../lightbox/css/lightbox.css";
+@import "colors.less";
@fa-font-path: "../../fontawesome/fonts";
-@brand-primary: #8E44B3;
-
body {
background: #ececec;
text-align: center;
diff --git a/src/static/pretixbase/less/colors.less b/src/static/pretixbase/less/colors.less
new file mode 100644
index 0000000000..eb59789e27
--- /dev/null
+++ b/src/static/pretixbase/less/colors.less
@@ -0,0 +1 @@
+@brand-primary: #8E44B3;
diff --git a/src/static/pretixbase/less/error.less b/src/static/pretixbase/less/error.less
new file mode 100644
index 0000000000..1e3cf7e933
--- /dev/null
+++ b/src/static/pretixbase/less/error.less
@@ -0,0 +1,16 @@
+@import "../../bootstrap/less/bootstrap.less";
+@import "../../fontawesome/less/font-awesome.less";
+@import "colors.less";
+
+@fa-font-path: "../../fontawesome/fonts";
+
+body {
+ background: #ececec;
+ text-align: center;
+ padding: 50px 0;
+}
+
+.big-icon {
+ font-size: 200px;
+ color: @brand-primary;
+}
diff --git a/src/static/pretixpresale/less/main.less b/src/static/pretixpresale/less/main.less
index 487937a0dc..43b9c2808f 100644
--- a/src/static/pretixpresale/less/main.less
+++ b/src/static/pretixpresale/less/main.less
@@ -2,8 +2,7 @@
@import "../../fontawesome/less/font-awesome.less";
@import "../../lightbox/css/lightbox.css";
@fa-font-path: "../../fontawesome/fonts";
-
-@brand-primary: #8E44B3;
+@import "../../pretixbase/less/colors.less";
@import "event.less";
@import "forms.less";