Added custom error pages

This commit is contained in:
Raphael Michel
2015-09-17 23:44:07 +02:00
parent 59e4b19e3f
commit c8830cc880
20 changed files with 129 additions and 37 deletions

View File

@@ -0,0 +1,13 @@
{% extends "error.html" %}
{% load i18n %}
{% block title %}{% trans "Bad Request" %}{% endblock %}
{% block content %}
<i class="fa fa-frown-o big-icon"></i>
<h1>{% trans "Bad Request" %}</h1>
<p>{% trans "We were unable to parse your request." %}</p>
<p>{{ exception }}</p>
<p>
<a href="javascript:history.back()">{% trans "Take a step back" %}</a>
&middot; <a href="javascript:location.reload()">{% trans "Try again" %}</a>
</p>
{% endblock %}

View File

@@ -0,0 +1,13 @@
{% extends "error.html" %}
{% load i18n %}
{% block title %}{% trans "Permission denied" %}{% endblock %}
{% block content %}
<i class="fa fa-lock big-icon"></i>
<h1>{% trans "Permission denied" %}</h1>
<p>{% trans "You do not have access to this page." %}</p>
<p>{{ exception }}</p>
<p>
<a href="javascript:history.back()">{% trans "Take a step back" %}</a>
&middot; <a href="javascript:location.reload()">{% trans "Try again" %}</a>
</p>
{% endblock %}

View File

@@ -0,0 +1,12 @@
{% extends "error.html" %}
{% load i18n %}
{% block title %}{% trans "Not found" %}{% endblock %}
{% block content %}
<i class="fa fa-meh-o big-icon"></i>
<h1>{% trans "Not found" %}</h1>
<p>{% trans "I'm afraid we could not find the the resource you requested." %}</p>
<p>{{ exception }}</p>
<p>
<a href="javascript:history.back()">{% trans "Take a step back" %}</a>
</p>
{% endblock %}

View File

@@ -0,0 +1,14 @@
{% extends "error.html" %}
{% load i18n %}
{% block title %}{% trans "Internal Server Error" %}{% endblock %}
{% block content %}
<i class="fa fa-bolt big-icon"></i>
<h1>{% trans "Internal Server Error" %}</h1>
<p>{% trans "We had trouble processing your request." %}</p>
<p>{% trans "If this problem persists, please contact us." %}</p>
<p>{{ exception }}</p>
<p>
<a href="javascript:history.back()">{% trans "Take a step back" %}</a>
&middot; <a href="javascript:location.reload()">{% trans "Try again" %}</a>
</p>
{% endblock %}

View File

@@ -0,0 +1,18 @@
{% load compress %}
{% load i18n %}
{% load staticfiles %}
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}{% endblock %}</title>
{% compress css %}
<link rel="stylesheet" type="text/less" href="{% static "pretixbase/less/error.less" %}" />
{% endcompress %}
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div class="container">
{% block content %}{% endblock %}
</div>
</body>
</html>

View File

@@ -6,7 +6,7 @@
<head> <head>
<title>{{ settings.PRETIX_INSTANCE_NAME }}</title> <title>{{ settings.PRETIX_INSTANCE_NAME }}</title>
{% compress css %} {% compress css %}
<link rel="stylesheet" type="text/less" href="{% static "pretixpresale/less/cachedfiles.less" %}" /> <link rel="stylesheet" type="text/less" href="{% static "pretixbase/less/cachedfiles.less" %}" />
{% endcompress %} {% endcompress %}
{% compress js %} {% compress js %}
<script type="text/javascript" src="{% static "jquery/js/jquery-2.1.1.min.js" %}"></script> <script type="text/javascript" src="{% static "jquery/js/jquery-2.1.1.min.js" %}"></script>

View File

View File

@@ -7,7 +7,7 @@ from pretix.base.models import CachedFile
class DownloadView(TemplateView): class DownloadView(TemplateView):
template_name = "pretixpresale/cachedfiles/pending.html" template_name = "pretixbase/cachedfiles/pending.html"
@cached_property @cached_property
def object(self): def object(self):

View File

@@ -3,7 +3,7 @@ from urllib.parse import urlparse
from django.conf import settings from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME from django.contrib.auth import REDIRECT_FIELD_NAME
from django.core.urlresolvers import get_script_prefix, resolve 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.shortcuts import resolve_url
from django.utils.encoding import force_str from django.utils.encoding import force_str
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
@@ -12,7 +12,6 @@ from pretix.base.models import Event, EventPermission, Organizer
class PermissionMiddleware: class PermissionMiddleware:
""" """
This middleware enforces all requests to the control app to require login. This middleware enforces all requests to the control app to require login.
Additionally, it enforces all requests to "control:event." URLs Additionally, it enforces all requests to "control:event." URLs
@@ -43,6 +42,7 @@ class PermissionMiddleware:
(not login_netloc or login_netloc == current_netloc)): (not login_netloc or login_netloc == current_netloc)):
path = request.get_full_path() path = request.get_full_path()
from django.contrib.auth.views import redirect_to_login from django.contrib.auth.views import redirect_to_login
return redirect_to_login( return redirect_to_login(
path, resolved_login_url, REDIRECT_FIELD_NAME) path, resolved_login_url, REDIRECT_FIELD_NAME)
@@ -61,8 +61,8 @@ class PermissionMiddleware:
) )
request.organizer = request.event.organizer request.organizer = request.event.organizer
except IndexError: except IndexError:
return HttpResponseNotFound(_("The selected event was not found or you " raise Http404(_("The selected event was not found or you "
"have no permission to administrate it.")) "have no permission to administrate it."))
elif 'organizer' in url.kwargs: elif 'organizer' in url.kwargs:
try: try:
request.organizer = Organizer.objects.current.filter( request.organizer = Organizer.objects.current.filter(
@@ -70,5 +70,5 @@ class PermissionMiddleware:
permitted__id__exact=request.user.id, permitted__id__exact=request.user.id,
)[0] )[0]
except IndexError: except IndexError:
return HttpResponseNotFound(_("The selected organizer was not found or you " return Http404(_("The selected organizer was not found or you "
"have no permission to administrate it.")) "have no permission to administrate it."))

View File

@@ -1,4 +1,4 @@
from django.http import HttpResponseForbidden from django.core.exceptions import PermissionDenied
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from pretix.base.models import EventPermission, OrganizerPermission from pretix.base.models import EventPermission, OrganizerPermission
@@ -13,7 +13,7 @@ def event_permission_required(permission):
def wrapper(request, *args, **kw): def wrapper(request, *args, **kw):
if not request.user.is_authenticated(): # NOQA if not request.user.is_authenticated(): # NOQA
# just a double check, should not ever happen # just a double check, should not ever happen
return HttpResponseForbidden() raise PermissionDenied()
try: try:
perm = EventPermission.objects.current.get( perm = EventPermission.objects.current.get(
event=request.event, event=request.event,
@@ -30,7 +30,7 @@ def event_permission_required(permission):
pass pass
if allowed: if allowed:
return function(request, *args, **kw) 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 wrapper
return decorator return decorator
@@ -57,7 +57,7 @@ def organizer_permission_required(permission):
def wrapper(request, *args, **kw): def wrapper(request, *args, **kw):
if not request.user.is_authenticated(): # NOQA if not request.user.is_authenticated(): # NOQA
# just a double check, should not ever happen # just a double check, should not ever happen
return HttpResponseForbidden() raise PermissionDenied()
try: try:
perm = OrganizerPermission.objects.current.get( perm = OrganizerPermission.objects.current.get(
organizer=request.organizer, organizer=request.organizer,
@@ -74,7 +74,7 @@ def organizer_permission_required(permission):
pass pass
if allowed or request.user.is_superuser: if allowed or request.user.is_superuser:
return function(request, *args, **kw) 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 wrapper
return decorator return decorator

View File

@@ -4,7 +4,7 @@ from django.contrib import messages
from django.core.urlresolvers import resolve, reverse from django.core.urlresolvers import resolve, reverse
from django.db import transaction from django.db import transaction
from django.forms.models import inlineformset_factory 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.shortcuts import redirect
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _ 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.')) messages.success(request, _('The selected property has been deleted.'))
return HttpResponseRedirect(success_url) return HttpResponseRedirect(success_url)
else: 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: def get_success_url(self) -> str:
return reverse('control:event.items.properties', kwargs={ return reverse('control:event.items.properties', kwargs={

View File

@@ -1,6 +1,6 @@
from django.contrib import messages from django.contrib import messages
from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.http import HttpResponseForbidden
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.generic import CreateView, ListView, UpdateView from django.views.generic import CreateView, ListView, UpdateView
@@ -52,7 +52,7 @@ class OrganizerCreate(CreateView):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if not request.user.is_superuser: if not request.user.is_superuser:
return HttpResponseForbidden() # TODO raise PermissionDenied() # TODO
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
def form_valid(self, form): def form_valid(self, form):

View File

@@ -1,5 +1,6 @@
from django.core.urlresolvers import resolve 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 from pretix.base.models import Event
@@ -30,4 +31,4 @@ class EventMiddleware:
organizer__slug=url.kwargs['organizer'], organizer__slug=url.kwargs['organizer'],
).select_related('organizer')[0] ).select_related('organizer')[0]
except IndexError: except IndexError:
return HttpResponseNotFound('Unknown event') # TODO: Provide error message return Http404(_('The selected event was not found.'))

View File

@@ -1,6 +1,5 @@
from django.conf.urls import include, url from django.conf.urls import include, url
import pretix.presale.views.cachedfiles
import pretix.presale.views.cart import pretix.presale.views.cart
import pretix.presale.views.checkout import pretix.presale.views.checkout
import pretix.presale.views.event import pretix.presale.views.event
@@ -8,8 +7,6 @@ import pretix.presale.views.locale
import pretix.presale.views.order import pretix.presale.views.order
urlpatterns = [ urlpatterns = [
url(r'^download/(?P<id>[^/]+)/$', pretix.presale.views.cachedfiles.DownloadView.as_view(),
name='cachedfile.download'),
url(r'^(?P<organizer>[^/]+)/(?P<event>[^/]+)/', include([ url(r'^(?P<organizer>[^/]+)/(?P<event>[^/]+)/', include([
url(r'^$', pretix.presale.views.event.EventIndex.as_view(), name='event.index'), 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'), url(r'^cart/add$', pretix.presale.views.cart.CartAdd.as_view(), name='event.cart.add'),

View File

@@ -3,15 +3,18 @@ from datetime import timedelta
from django.contrib import messages from django.contrib import messages
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db.models import Q from django.db.models import Q
from django.http import HttpResponseForbidden, HttpResponseNotFound from django.http import Http404
from django.shortcuts import redirect from django.shortcuts import redirect
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.generic import TemplateView, View from django.views.generic import TemplateView, View
from pretix.base.models import CachedFile, CachedTicket, Order, OrderPosition from pretix.base.models import CachedFile, CachedTicket, Order, OrderPosition
from pretix.base.services.tickets import generate 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 import CartDisplayMixin, EventViewMixin
from pretix.presale.views.checkout import QuestionsViewMixin from pretix.presale.views.checkout import QuestionsViewMixin
@@ -49,7 +52,7 @@ class OrderDetails(EventViewMixin, OrderDetailMixin, CartDisplayMixin, TemplateV
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.kwargs = kwargs self.kwargs = kwargs
if not self.order: 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) return super().get(request, *args, **kwargs)
@cached_property @cached_property
@@ -101,7 +104,7 @@ class OrderPay(EventViewMixin, OrderDetailMixin, TemplateView):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
self.request = request self.request = request
if not self.order: 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) 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.order_can_retry(self.order)
or not self.payment_provider.is_enabled): or not self.payment_provider.is_enabled):
@@ -144,7 +147,7 @@ class OrderPayDo(EventViewMixin, OrderDetailMixin, TemplateView):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
self.request = request self.request = request
if not self.order: 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: 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.')) messages.error(request, _('The payment for this order cannot be continued.'))
return redirect(self.get_order_url()) return redirect(self.get_order_url())
@@ -207,9 +210,11 @@ class OrderModify(EventViewMixin, OrderDetailMixin, QuestionsViewMixin, Template
self.request = request self.request = request
self.kwargs = kwargs self.kwargs = kwargs
if not self.order: 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: 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) return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@@ -226,9 +231,10 @@ class OrderCancel(EventViewMixin, OrderDetailMixin, TemplateView):
self.request = request self.request = request
self.kwargs = kwargs self.kwargs = kwargs
if not self.order: 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): 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) return super().dispatch(request, *args, **kwargs)
def post(self, 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.')) messages.error(request, _('You requested an invalid ticket output type.'))
return redirect(self.get_order_url()) return redirect(self.get_order_url())
if not self.order: 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: if self.order.status != Order.STATUS_PAID:
messages.error(request, _('Order is not paid.')) messages.error(request, _('Order is not paid.'))
return redirect(self.get_order_url()) return redirect(self.get_order_url())

View File

@@ -5,10 +5,13 @@ from django.apps import apps
from django.conf import settings from django.conf import settings
from django.conf.urls import include, url from django.conf.urls import include, url
import pretix.base.views.cachedfiles
import pretix.control.urls import pretix.control.urls
import pretix.presale.urls import pretix.presale.urls
urlpatterns = [ urlpatterns = [
url(r'^download/(?P<id>[^/]+)/$', pretix.base.views.cachedfiles.DownloadView.as_view(),
name='cachedfile.download'),
url(r'^control/', include(pretix.control.urls, namespace='control')), url(r'^control/', include(pretix.control.urls, namespace='control')),
# The pretixpresale namespace is configured at the bottom of this file, because it # 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. # contains a wildcard-style URL which has to be configured _after_ debug settings.

View File

@@ -1,11 +1,9 @@
@import "../../bootstrap/less/bootstrap.less"; @import "../../bootstrap/less/bootstrap.less";
@import "../../fontawesome/less/font-awesome.less"; @import "../../fontawesome/less/font-awesome.less";
@import "../../lightbox/css/lightbox.css"; @import "colors.less";
@fa-font-path: "../../fontawesome/fonts"; @fa-font-path: "../../fontawesome/fonts";
@brand-primary: #8E44B3;
body { body {
background: #ececec; background: #ececec;
text-align: center; text-align: center;

View File

@@ -0,0 +1 @@
@brand-primary: #8E44B3;

View File

@@ -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;
}

View File

@@ -2,8 +2,7 @@
@import "../../fontawesome/less/font-awesome.less"; @import "../../fontawesome/less/font-awesome.less";
@import "../../lightbox/css/lightbox.css"; @import "../../lightbox/css/lightbox.css";
@fa-font-path: "../../fontawesome/fonts"; @fa-font-path: "../../fontawesome/fonts";
@import "../../pretixbase/less/colors.less";
@brand-primary: #8E44B3;
@import "event.less"; @import "event.less";
@import "forms.less"; @import "forms.less";