diff --git a/src/pretix/base/templates/400.html b/src/pretix/base/templates/400.html new file mode 100644 index 0000000000..df162b1d97 --- /dev/null +++ b/src/pretix/base/templates/400.html @@ -0,0 +1,13 @@ +{% extends "error.html" %} +{% load i18n %} +{% block title %}{% trans "Bad Request" %}{% endblock %} +{% block content %} + +

{% trans "Bad Request" %}

+

{% trans "We were unable to parse your request." %}

+

{{ exception }}

+

+ {% trans "Take a step back" %} + · {% trans "Try again" %} +

+{% endblock %} \ No newline at end of file diff --git a/src/pretix/base/templates/403.html b/src/pretix/base/templates/403.html new file mode 100644 index 0000000000..eff0694c1a --- /dev/null +++ b/src/pretix/base/templates/403.html @@ -0,0 +1,13 @@ +{% extends "error.html" %} +{% load i18n %} +{% block title %}{% trans "Permission denied" %}{% endblock %} +{% block content %} + +

{% trans "Permission denied" %}

+

{% trans "You do not have access to this page." %}

+

{{ exception }}

+

+ {% trans "Take a step back" %} + · {% trans "Try again" %} +

+{% endblock %} \ No newline at end of file diff --git a/src/pretix/base/templates/404.html b/src/pretix/base/templates/404.html new file mode 100644 index 0000000000..b6b5f5fd1c --- /dev/null +++ b/src/pretix/base/templates/404.html @@ -0,0 +1,12 @@ +{% extends "error.html" %} +{% load i18n %} +{% block title %}{% trans "Not found" %}{% endblock %} +{% block content %} + +

{% trans "Not found" %}

+

{% trans "I'm afraid we could not find the the resource you requested." %}

+

{{ exception }}

+

+ {% trans "Take a step back" %} +

+{% endblock %} \ No newline at end of file diff --git a/src/pretix/base/templates/500.html b/src/pretix/base/templates/500.html new file mode 100644 index 0000000000..5aec75859e --- /dev/null +++ b/src/pretix/base/templates/500.html @@ -0,0 +1,14 @@ +{% extends "error.html" %} +{% load i18n %} +{% block title %}{% trans "Internal Server Error" %}{% endblock %} +{% block content %} + +

{% trans "Internal Server Error" %}

+

{% trans "We had trouble processing your request." %}

+

{% trans "If this problem persists, please contact us." %}

+

{{ exception }}

+

+ {% trans "Take a step back" %} + · {% trans "Try again" %} +

+{% 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";