diff --git a/src/pretix/base/migrations/0002_auto_20150524_1148.py b/src/pretix/base/migrations/0002_auto_20150524_1148.py new file mode 100644 index 0000000000..1654d8cec4 --- /dev/null +++ b/src/pretix/base/migrations/0002_auto_20150524_1148.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('pretixbase', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='organizer', + name='slug', + field=models.SlugField(verbose_name='Slug'), + ), + ] diff --git a/src/pretix/base/models.py b/src/pretix/base/models.py index ed6100e5fe..32cef5eecf 100644 --- a/src/pretix/base/models.py +++ b/src/pretix/base/models.py @@ -298,7 +298,7 @@ class Organizer(Versionable): name = models.CharField(max_length=200, verbose_name=_("Name")) slug = models.SlugField(max_length=50, - unique=True, db_index=True, + db_index=True, verbose_name=_("Slug")) permitted = models.ManyToManyField(User, through='OrganizerPermission', related_name="organizers") diff --git a/src/pretix/control/middleware.py b/src/pretix/control/middleware.py index e5aea9e817..2509466b1a 100644 --- a/src/pretix/control/middleware.py +++ b/src/pretix/control/middleware.py @@ -7,7 +7,7 @@ from django.contrib.auth import REDIRECT_FIELD_NAME from django.http import HttpResponseNotFound from django.utils.translation import ugettext as _ -from pretix.base.models import Event +from pretix.base.models import Event, Organizer class PermissionMiddleware: @@ -53,6 +53,16 @@ class PermissionMiddleware: permitted__id__exact=request.user.id, organizer__slug=url.kwargs['organizer'], ).select_related('organizer')[0] + request.organizer = request.event.organizer except IndexError: return HttpResponseNotFound(_("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( + slug=url.kwargs['organizer'], + 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.")) diff --git a/src/pretix/control/permissions.py b/src/pretix/control/permissions.py index f9df2cd0f9..8a77970011 100644 --- a/src/pretix/control/permissions.py +++ b/src/pretix/control/permissions.py @@ -1,7 +1,7 @@ from django.http import HttpResponseForbidden from django.utils.translation import ugettext as _ -from pretix.base.models import EventPermission +from pretix.base.models import EventPermission, OrganizerPermission def event_permission_required(permission): @@ -14,17 +14,22 @@ def event_permission_required(permission): if not request.user.is_authenticated(): # NOQA # just a double check, should not ever happen return HttpResponseForbidden() - perm = EventPermission.objects.get( - event=request.event, - user=request.user - ) - allowed = False try: - allowed = getattr(perm, permission) - except AttributeError: + perm = EventPermission.objects.get( + event=request.event, + user=request.user + ) + except: pass - if allowed: - return function(request, *args, **kw) + else: + allowed = not permission + try: + if permission: + allowed = getattr(perm, permission) + except AttributeError: + pass + if allowed: + return function(request, *args, **kw) return HttpResponseForbidden(_('You do not have permission to view this content.')) return wrapper return decorator @@ -41,3 +46,47 @@ class EventPermissionRequiredMixin: def as_view(cls, **initkwargs): view = super(EventPermissionRequiredMixin, cls).as_view(**initkwargs) return event_permission_required(cls.permission)(view) + + +def organizer_permission_required(permission): + """ + This view decorator rejects all requests with a 403 response which are not from + users having the given permission for the event the request is associated with. + """ + def decorator(function): + def wrapper(request, *args, **kw): + if not request.user.is_authenticated(): # NOQA + # just a double check, should not ever happen + return HttpResponseForbidden() + try: + perm = OrganizerPermission.objects.get( + organizer=request.organizer, + user=request.user + ) + except: + pass + else: + allowed = not permission + try: + if permission: + allowed = getattr(perm, permission) + except AttributeError: + pass + if allowed or request.user.is_superuser: + return function(request, *args, **kw) + return HttpResponseForbidden(_('You do not have permission to view this content.')) + return wrapper + return decorator + + +class OrganizerPermissionRequiredMixin: + """ + This mixin is equivalent to the event_permission_required view decorator but + is in a form suitable for class-based views. + """ + permission = '' + + @classmethod + def as_view(cls, **initkwargs): + view = super(OrganizerPermissionRequiredMixin, cls).as_view(**initkwargs) + return organizer_permission_required(cls.permission)(view) diff --git a/src/pretix/control/templates/pretixcontrol/base.html b/src/pretix/control/templates/pretixcontrol/base.html index 062d0598d7..58b6e0d0b0 100644 --- a/src/pretix/control/templates/pretixcontrol/base.html +++ b/src/pretix/control/templates/pretixcontrol/base.html @@ -76,6 +76,12 @@ {% trans "Events" %} +
  • + + + {% trans "Organizers" %} + +
  • {% endblock %} diff --git a/src/pretix/control/templates/pretixcontrol/items/category.html b/src/pretix/control/templates/pretixcontrol/items/category.html index 8d410974d8..e384235a4a 100644 --- a/src/pretix/control/templates/pretixcontrol/items/category.html +++ b/src/pretix/control/templates/pretixcontrol/items/category.html @@ -6,11 +6,6 @@

    {% trans "Product category" %}

    {% csrf_token %} - {% if "success" in request.GET %} -
    - {% trans "Your changes have been saved." %} -
    - {% endif %}
    {% trans "General information" %} {% bootstrap_field form.name layout="horizontal" %} diff --git a/src/pretix/control/templates/pretixcontrol/organizers/create.html b/src/pretix/control/templates/pretixcontrol/organizers/create.html new file mode 100644 index 0000000000..b27c3bdf91 --- /dev/null +++ b/src/pretix/control/templates/pretixcontrol/organizers/create.html @@ -0,0 +1,20 @@ +{% extends "pretixcontrol/base.html" %} +{% load i18n %} +{% load bootstrap3 %} +{% block title %}{% trans "Create Organizer" %}{% endblock %} +{% block content %} +

    {% trans "Create Organizer" %}

    + + {% csrf_token %} +
    + {% trans "General information" %} + {% bootstrap_field form.name layout="horizontal" %} + {% bootstrap_field form.slug layout="horizontal" %} +
    +
    + +
    + +{% endblock %} diff --git a/src/pretix/control/templates/pretixcontrol/organizers/detail.html b/src/pretix/control/templates/pretixcontrol/organizers/detail.html new file mode 100644 index 0000000000..cee093c0e5 --- /dev/null +++ b/src/pretix/control/templates/pretixcontrol/organizers/detail.html @@ -0,0 +1,20 @@ +{% extends "pretixcontrol/base.html" %} +{% load i18n %} +{% load bootstrap3 %} +{% block title %}{% trans "Organizer" %}{% endblock %} +{% block content %} +

    {% trans "Organizer" %}

    +
    + {% csrf_token %} +
    + {% trans "General information" %} + {% bootstrap_field form.name layout="horizontal" %} + {% bootstrap_field form.slug layout="horizontal" %} +
    +
    + +
    +
    +{% endblock %} diff --git a/src/pretix/control/templates/pretixcontrol/organizers/index.html b/src/pretix/control/templates/pretixcontrol/organizers/index.html new file mode 100644 index 0000000000..69e8f34eb9 --- /dev/null +++ b/src/pretix/control/templates/pretixcontrol/organizers/index.html @@ -0,0 +1,29 @@ +{% extends "pretixcontrol/base.html" %} +{% load i18n %} +{% block title %}{% trans "Organizers" %}{% endblock %} +{% block content %} +

    {% trans "Organizers" %}

    +

    {% trans "The list below shows all organizer accounts you have administrative access to." %}

    + {% if request.user.is_superuser %} + + + {% trans "Create a new organizer" %} + + {% endif %} + + + + + + + + {% for o in organizers %} + + + + {% endfor %} + +
    {% trans "Organizer name" %}
    + {{ o.name }} +
    +{% endblock %} diff --git a/src/pretix/control/urls.py b/src/pretix/control/urls.py index 115faf80d7..5110540c9b 100644 --- a/src/pretix/control/urls.py +++ b/src/pretix/control/urls.py @@ -1,13 +1,16 @@ from django.conf.urls import url, include -from pretix.control.views import main, event, item, auth, orders, user +from pretix.control.views import main, event, item, auth, orders, user, organizer urlpatterns = [ url(r'^logout$', auth.logout, name='auth.logout'), url(r'^login$', auth.login, name='auth.login'), url(r'^$', main.index, name='index'), url(r'^settings$', user.UserSettings.as_view(), name='user.settings'), + url(r'^organizers/$', organizer.OrganizerList.as_view(), name='organizers'), + url(r'^organizers/add$', organizer.OrganizerCreate.as_view(), name='organizers.add'), + url(r'^organizer/(?P[^/]+)/edit$', organizer.OrganizerUpdate.as_view(), name='organizer.edit'), url(r'^events/$', main.EventList.as_view(), name='events'), url(r'^event/(?P[^/]+)/(?P[^/]+)/', include([ url(r'^$', event.index, name='event.index'), diff --git a/src/pretix/control/views/organizer.py b/src/pretix/control/views/organizer.py new file mode 100644 index 0000000000..63867e1de4 --- /dev/null +++ b/src/pretix/control/views/organizer.py @@ -0,0 +1,86 @@ +from django.contrib import messages +from django.core.urlresolvers import reverse +from django.http import HttpResponseForbidden +from django.utils.translation import ugettext_lazy as _ +from django.views.generic import ListView, UpdateView, CreateView +from pretix.base.forms import VersionedModelForm + +from pretix.base.models import Organizer, OrganizerPermission +from pretix.control.permissions import OrganizerPermissionRequiredMixin + + +class OrganizerList(ListView): + model = Organizer + context_object_name = 'organizers' + template_name = 'pretixcontrol/organizers/index.html' + + def get_queryset(self): + if self.request.user.is_superuser: + return Organizer.objects.current.all() + else: + return Organizer.objects.current.filter( + permitted__id__exact=self.request.user.pk + ) + + +class OrganizerForm(VersionedModelForm): + class Meta: + model = Organizer + fields = ['name', 'slug'] + + +class OrganizerUpdateForm(OrganizerForm): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['slug'].widget.attrs['disabled'] = 'disabled' + + def clean_slug(self): + return self.instance.slug + + +class OrganizerUpdate(OrganizerPermissionRequiredMixin, UpdateView): + model = Organizer + form_class = OrganizerUpdateForm + template_name = 'pretixcontrol/organizers/detail.html' + permission = None + context_object_name = 'organizer' + + def get_object(self, queryset=None) -> Organizer: + return self.request.organizer + + def form_valid(self, form): + messages.success(self.request, _('Your changes have been saved.')) + return super().form_valid(form) + + def get_success_url(self) -> str: + return reverse('control:organizer.edit', kwargs={ + 'organizer': self.request.organizer.slug, + }) + + +class OrganizerCreate(CreateView): + model = Organizer + form_class = OrganizerForm + template_name = 'pretixcontrol/organizers/create.html' + context_object_name = 'organizer' + + def dispatch(self, request, *args, **kwargs): + if not request.user.is_superuser: + return HttpResponseForbidden() # TODO + return super().dispatch(request, *args, **kwargs) + + def get_object(self, queryset=None) -> Organizer: + return self.request.organizer + + def form_valid(self, form): + messages.success(self.request, _('The new organizer has been created.')) + ret = super().form_valid(form) + OrganizerPermission.objects.create( + organizer=form.instance, user=self.request.user, + can_create_events=True + ) + return ret + + def get_success_url(self) -> str: + return reverse('control:organizers')