- {% if order.changable and request.eventperm.can_change_orders %}
+ {% if order.changable and 'can_change_orders' in request.eventpermset %}
{% trans "Change products" %}
diff --git a/src/pretix/control/templates/pretixcontrol/organizers/base.html b/src/pretix/control/templates/pretixcontrol/organizers/base.html
index 3de3a4108..1bd8d6bc1 100644
--- a/src/pretix/control/templates/pretixcontrol/organizers/base.html
+++ b/src/pretix/control/templates/pretixcontrol/organizers/base.html
@@ -5,11 +5,13 @@
{% block content %}
{% blocktrans with name=organizer.name %}Organizer: {{ name }}{% endblocktrans %}
-
-
- {% trans "Edit" %}
-
+ {% if 'can_change_organizer_settings' in request.orgapermset %}
+
+
+ {% trans "Edit" %}
+
+ {% endif %}
-
@@ -17,10 +19,10 @@
{% trans "Events" %}
- {% if request.orgaperm.can_change_permissions %}
-
+ {% if 'can_change_teams' in request.orgapermset %}
+
- {% trans "Permissions" %}
+ {% trans "Teams" %}
{% endif %}
diff --git a/src/pretix/control/templates/pretixcontrol/organizers/team_delete.html b/src/pretix/control/templates/pretixcontrol/organizers/team_delete.html
new file mode 100644
index 000000000..526c4aa50
--- /dev/null
+++ b/src/pretix/control/templates/pretixcontrol/organizers/team_delete.html
@@ -0,0 +1,29 @@
+{% extends "pretixcontrol/organizers/base.html" %}
+{% load i18n %}
+{% load bootstrap3 %}
+{% block inner %}
+
{% trans "Delete team:" %} {{ team.name }}
+ {% if not possible %}
+
{% blocktrans %}You cannot delete the team because there would be noone left who could change team permissions afterwards.{% endblocktrans %}
+
+ {% else %}
+
+ {% endif %}
+{% endblock %}
diff --git a/src/pretix/control/templates/pretixcontrol/organizers/team_edit.html b/src/pretix/control/templates/pretixcontrol/organizers/team_edit.html
new file mode 100644
index 000000000..147c71763
--- /dev/null
+++ b/src/pretix/control/templates/pretixcontrol/organizers/team_edit.html
@@ -0,0 +1,47 @@
+{% extends "pretixcontrol/organizers/base.html" %}
+{% load i18n %}
+{% load bootstrap3 %}
+{% block inner %}
+ {% if team %}
+
{% trans "Team:" %} {{ team.name }}
+ {% else %}
+
{% trans "Create a new team" %}
+
+ {% blocktrans trimmed %}
+ You will be able to add team members in the next step.
+ {% endblocktrans %}
+
+ {% endif %}
+
+{% endblock %}
diff --git a/src/pretix/control/templates/pretixcontrol/organizers/team_members.html b/src/pretix/control/templates/pretixcontrol/organizers/team_members.html
new file mode 100644
index 000000000..8ed1c9af9
--- /dev/null
+++ b/src/pretix/control/templates/pretixcontrol/organizers/team_members.html
@@ -0,0 +1,82 @@
+{% extends "pretixcontrol/organizers/base.html" %}
+{% load i18n %}
+{% load bootstrap3 %}
+{% block inner %}
+
+
+
+
+
+ {% trans "Team history" %}
+
+
+ {% include "pretixcontrol/includes/logs.html" with obj=team %}
+
+
+{% endblock %}
diff --git a/src/pretix/control/templates/pretixcontrol/organizers/teams.html b/src/pretix/control/templates/pretixcontrol/organizers/teams.html
index 0638718ef..ef5bcfda3 100644
--- a/src/pretix/control/templates/pretixcontrol/organizers/teams.html
+++ b/src/pretix/control/templates/pretixcontrol/organizers/teams.html
@@ -2,83 +2,57 @@
{% load i18n %}
{% load bootstrap3 %}
{% block inner %}
-
{% endif %}
-
+
{% trans "Sales estimate" %}
diff --git a/src/pretix/control/urls.py b/src/pretix/control/urls.py
index 40485f089..3d2be5ad2 100644
--- a/src/pretix/control/urls.py
+++ b/src/pretix/control/urls.py
@@ -35,7 +35,14 @@ urlpatterns = [
url(r'^organizers/add$', organizer.OrganizerCreate.as_view(), name='organizers.add'),
url(r'^organizer/(?P
[^/]+)/$', organizer.OrganizerDetail.as_view(), name='organizer'),
url(r'^organizer/(?P[^/]+)/edit$', organizer.OrganizerUpdate.as_view(), name='organizer.edit'),
- url(r'^organizer/(?P[^/]+)/teams$', organizer.OrganizerTeamView.as_view(), name='organizer.teams'),
+ url(r'^organizer/(?P[^/]+)/teams$', organizer.TeamListView.as_view(), name='organizer.teams'),
+ url(r'^organizer/(?P[^/]+)/team/add$', organizer.TeamCreateView.as_view(), name='organizer.team.add'),
+ url(r'^organizer/(?P[^/]+)/team/(?P[^/]+)/$', organizer.TeamMemberView.as_view(),
+ name='organizer.team'),
+ url(r'^organizer/(?P[^/]+)/team/(?P[^/]+)/edit$', organizer.TeamUpdateView.as_view(),
+ name='organizer.team.edit'),
+ url(r'^organizer/(?P[^/]+)/team/(?P[^/]+)/delete$', organizer.TeamDeleteView.as_view(),
+ name='organizer.team.delete'),
url(r'^events/$', main.EventList.as_view(), name='events'),
url(r'^events/add$', main.EventWizard.as_view(), name='events.add'),
url(r'^event/(?P[^/]+)/(?P[^/]+)/', include([
diff --git a/src/pretix/control/views/auth.py b/src/pretix/control/views/auth.py
index 8e4c02699..03811b336 100644
--- a/src/pretix/control/views/auth.py
+++ b/src/pretix/control/views/auth.py
@@ -9,6 +9,7 @@ from django.contrib.auth import (
)
from django.contrib.auth.tokens import default_token_generator
from django.core.exceptions import PermissionDenied
+from django.db import transaction
from django.shortcuts import redirect, render
from django.urls import reverse
from django.utils.functional import cached_property
@@ -23,9 +24,7 @@ from u2flib_server.utils import rand_bytes
from pretix.base.forms.auth import (
LoginForm, PasswordForgotForm, PasswordRecoverForm, RegistrationForm,
)
-from pretix.base.models import (
- EventPermission, OrganizerPermission, U2FDevice, User,
-)
+from pretix.base.models import TeamInvite, U2FDevice, User
from pretix.base.services.mail import SendMailException, mail
from pretix.helpers.urls import build_absolute_uri
@@ -108,35 +107,30 @@ def invite(request, token):
ctx = {}
try:
- perm = EventPermission.objects.get(invite_token=token)
- desc = perm.event.name
- except EventPermission.DoesNotExist:
- try:
- perm = OrganizerPermission.objects.get(invite_token=token)
- desc = perm.organizer.name
- except OrganizerPermission.DoesNotExist:
- messages.error(request, _('You used an invalid link. Please copy the link from your email to the address bar '
- 'and make sure it is correct and that the link has not been used before.'))
- return redirect('control:auth.login')
+ inv = TeamInvite.objects.get(token=token)
+ except TeamInvite.DoesNotExist:
+ messages.error(request, _('You used an invalid link. Please copy the link from your email to the address bar '
+ 'and make sure it is correct and that the link has not been used before.'))
+ return redirect('control:auth.login')
if request.user.is_authenticated:
- try:
- if isinstance(perm, EventPermission):
- EventPermission.objects.get(event=perm.event, user=request.user)
- else:
- OrganizerPermission.objects.get(organizer=perm.organizer, user=request.user)
+ if inv.team.members.filter(pk=request.user.pk).exists():
messages.error(request, _('You cannot accept the invitation for "{}" as you already are part of '
- 'this team.').format(desc))
+ 'this team.').format(inv.team.name))
+ return redirect('control:index')
+ else:
+ with transaction.atomic():
+ inv.team.members.add(request.user)
+ inv.team.log_action(
+ 'pretix.team.member.joined', data={
+ 'email': request.user.email,
+ 'invite_email': inv.email,
+ 'user': request.user.pk
+ }
+ )
+ inv.delete()
+ messages.success(request, _('You are now part of the team "{}".').format(inv.team.name))
return redirect('control:index')
- except (EventPermission.DoesNotExist, OrganizerPermission.DoesNotExist):
- pass
-
- perm.invite_token = None
- perm.invite_email = None
- perm.user = request.user
- perm.save()
- messages.success(request, _('You have now access to "{}".').format(desc))
- return redirect('control:index')
if request.method == 'POST':
form = RegistrationForm(data=request.POST)
@@ -151,14 +145,20 @@ def invite(request, token):
auth_login(request, user)
request.session['pretix_auth_login_time'] = int(time.time())
- perm.invite_token = None
- perm.invite_email = None
- perm.user = user
- perm.save()
- messages.success(request, _('Welcome to pretix! You have now access to "{}".').format(desc))
+ with transaction.atomic():
+ inv.team.members.add(request.user)
+ inv.team.log_action(
+ 'pretix.team.member.joined', data={
+ 'email': user.email,
+ 'invite_email': inv.email,
+ 'user': user.pk
+ }
+ )
+ inv.delete()
+ messages.success(request, _('Welcome to pretix! You are now part of the team "{}".').format(inv.team.name))
return redirect('control:index')
else:
- form = RegistrationForm(initial={'email': perm.invite_email})
+ form = RegistrationForm(initial={'email': inv.email})
ctx['form'] = form
return render(request, 'pretixcontrol/auth/invite.html', ctx)
diff --git a/src/pretix/control/views/dashboards.py b/src/pretix/control/views/dashboards.py
index 172e4c04c..80f304d38 100644
--- a/src/pretix/control/views/dashboards.py
+++ b/src/pretix/control/views/dashboards.py
@@ -13,7 +13,7 @@ from django.utils.formats import date_format
from django.utils.translation import ugettext_lazy as _
from pretix.base.models import (
- Event, Item, Order, OrderPosition, Voucher, WaitingListEntry,
+ Item, Order, OrderPosition, Voucher, WaitingListEntry,
)
from pretix.control.signals import (
event_dashboard_widgets, user_dashboard_widgets,
@@ -207,11 +207,12 @@ def event_index(request, organizer, event):
for r, result in event_dashboard_widgets.send(sender=request.event):
widgets.extend(result)
+ can_change_orders = request.user.has_event_permisson(request.organizer, request.event, 'can_change_orders')
qs = request.event.logentry_set.all().select_related('user', 'content_type').order_by('-datetime')
qs = qs.exclude(action_type__in=OVERVIEW_BLACKLIST)
- if not request.eventperm.can_view_orders:
+ if not request.user.has_event_permisson(request.organizer, request.event, 'can_view_orders'):
qs = qs.exclude(content_type=ContentType.objects.get_for_model(Order))
- if not request.eventperm.can_view_vouchers:
+ if not request.user.has_event_permisson(request.organizer, request.event, 'can_view_vouchers'):
qs = qs.exclude(content_type=ContentType.objects.get_for_model(Voucher))
a_qs = request.event.requiredaction_set.filter(done=False)
@@ -221,7 +222,7 @@ def event_index(request, organizer, event):
ctx = {
'widgets': rearrange(widgets),
'logs': qs[:5],
- 'actions': a_qs[:5] if request.eventperm.can_change_orders else [],
+ 'actions': a_qs[:5] if can_change_orders else [],
'has_domain': has_domain
}
@@ -242,7 +243,8 @@ def event_index(request, organizer, event):
def user_event_widgets(**kwargs):
user = kwargs.pop('user')
widgets = []
- events = Event.objects.filter(permitted__id__exact=user.pk).select_related("organizer").order_by('-date_from')
+
+ events = user.get_events_with_any_permission().select_related('organizer')
for event in events:
widgets.append({
'content': '{event}{df}{dt}
'.format(
diff --git a/src/pretix/control/views/event.py b/src/pretix/control/views/event.py
index d17c5a10c..21eebd13d 100644
--- a/src/pretix/control/views/event.py
+++ b/src/pretix/control/views/event.py
@@ -2,14 +2,12 @@ import re
from collections import OrderedDict
from datetime import timedelta
-from django import forms
from django.conf import settings
from django.contrib import messages
from django.contrib.contenttypes.models import ContentType
from django.core.files import File
from django.core.urlresolvers import reverse
from django.db import transaction
-from django.forms import modelformset_factory
from django.http import HttpResponse, HttpResponseBadRequest, JsonResponse
from django.shortcuts import get_object_or_404, redirect
from django.utils import translation
@@ -22,14 +20,12 @@ from django.views.generic.base import TemplateView, View
from django.views.generic.detail import SingleObjectMixin
from pytz import timezone
-from pretix.base.forms import I18nModelForm
from pretix.base.models import (
- CachedTicket, Event, EventPermission, Item, ItemVariation, LogEntry, Order,
- RequiredAction, User, Voucher,
+ CachedTicket, Event, Item, ItemVariation, LogEntry, Order, RequiredAction,
+ Voucher,
)
from pretix.base.services import tickets
from pretix.base.services.invoices import build_preview_invoice_pdf
-from pretix.base.services.mail import SendMailException, mail
from pretix.base.signals import (
event_live_issues, register_payment_providers, register_ticket_outputs,
)
@@ -50,7 +46,7 @@ class EventUpdate(EventPermissionRequiredMixin, UpdateView):
model = Event
form_class = EventUpdateForm
template_name = 'pretixcontrol/event/settings.html'
- permission = 'can_change_settings'
+ permission = 'can_change_event_settings'
@cached_property
def object(self) -> Event:
@@ -115,7 +111,7 @@ class EventUpdate(EventPermissionRequiredMixin, UpdateView):
class EventPlugins(EventPermissionRequiredMixin, TemplateView, SingleObjectMixin):
model = Event
context_object_name = 'event'
- permission = 'can_change_settings'
+ permission = 'can_change_event_settings'
template_name = 'pretixcontrol/event/plugins.html'
def get_object(self, queryset=None) -> Event:
@@ -178,7 +174,7 @@ class EventPlugins(EventPermissionRequiredMixin, TemplateView, SingleObjectMixin
class PaymentSettings(EventPermissionRequiredMixin, TemplateView, SingleObjectMixin):
model = Event
context_object_name = 'event'
- permission = 'can_change_settings'
+ permission = 'can_change_event_settings'
template_name = 'pretixcontrol/event/payment.html'
def get_object(self, queryset=None) -> Event:
@@ -264,7 +260,7 @@ class PaymentSettings(EventPermissionRequiredMixin, TemplateView, SingleObjectMi
class EventSettingsFormView(EventPermissionRequiredMixin, FormView):
model = Event
- permission = 'can_change_settings'
+ permission = 'can_change_event_settings'
def get_context_data(self, *args, **kwargs) -> dict:
context = super().get_context_data(*args, **kwargs)
@@ -300,7 +296,7 @@ class InvoiceSettings(EventSettingsFormView):
model = Event
form_class = InvoiceSettingsForm
template_name = 'pretixcontrol/event/invoicing.html'
- permission = 'can_change_settings'
+ permission = 'can_change_event_settings'
def get_success_url(self) -> str:
if 'preview' in self.request.POST:
@@ -315,7 +311,7 @@ class InvoiceSettings(EventSettingsFormView):
class InvoicePreview(EventPermissionRequiredMixin, View):
- permission = 'can_change_settings'
+ permission = 'can_change_event_settings'
def get(self, request, *args, **kwargs):
pdf = build_preview_invoice_pdf(request.event)
@@ -328,7 +324,7 @@ class DisplaySettings(EventSettingsFormView):
model = Event
form_class = DisplaySettingsForm
template_name = 'pretixcontrol/event/display.html'
- permission = 'can_change_settings'
+ permission = 'can_change_event_settings'
def get_success_url(self) -> str:
return reverse('control:event.settings.display', kwargs={
@@ -364,7 +360,7 @@ class MailSettings(EventSettingsFormView):
model = Event
form_class = MailSettingsForm
template_name = 'pretixcontrol/event/mail.html'
- permission = 'can_change_settings'
+ permission = 'can_change_event_settings'
def get_success_url(self) -> str:
return reverse('control:event.settings.mail', kwargs={
@@ -407,7 +403,7 @@ class MailSettings(EventSettingsFormView):
class MailSettingsPreview(EventPermissionRequiredMixin, View):
- permission = 'can_change_settings'
+ permission = 'can_change_event_settings'
# return the origin text if key is missing in dict
class SafeDict(dict):
@@ -513,7 +509,7 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
class TicketSettingsPreview(EventPermissionRequiredMixin, View):
- permission = 'can_change_settings'
+ permission = 'can_change_event_settings'
@cached_property
def output(self):
@@ -545,7 +541,7 @@ class TicketSettings(EventPermissionRequiredMixin, FormView):
model = Event
form_class = TicketSettingsForm
template_name = 'pretixcontrol/event/tickets.html'
- permission = 'can_change_settings'
+ permission = 'can_change_event_settings'
def get_context_data(self, *args, **kwargs) -> dict:
context = super().get_context_data(*args, **kwargs)
@@ -637,140 +633,12 @@ class TicketSettings(EventPermissionRequiredMixin, FormView):
return providers
-class EventPermissionForm(I18nModelForm):
- class Meta:
- model = EventPermission
- fields = (
- 'can_change_settings', 'can_change_items', 'can_change_permissions', 'can_view_orders',
- 'can_change_orders', 'can_view_vouchers', 'can_change_vouchers'
- )
-
-
-class EventPermissionCreateForm(EventPermissionForm):
- user = forms.EmailField(required=False, label=_('User'))
-
-
class EventPermissions(EventPermissionRequiredMixin, TemplateView):
- model = Event
- form_class = TicketSettingsForm
template_name = 'pretixcontrol/event/permissions.html'
- permission = 'can_change_permissions'
-
- @cached_property
- def formset(self):
- fs = modelformset_factory(
- EventPermission,
- form=EventPermissionForm,
- can_delete=True, can_order=False, extra=0
- )
- return fs(data=self.request.POST if self.request.method == "POST" else None,
- prefix="formset",
- queryset=EventPermission.objects.filter(event=self.request.event))
-
- @cached_property
- def add_form(self):
- return EventPermissionCreateForm(data=self.request.POST if self.request.method == "POST" else None,
- prefix="add")
-
- def get_context_data(self, **kwargs):
- ctx = super().get_context_data(**kwargs)
- ctx['formset'] = self.formset
- ctx['add_form'] = self.add_form
- return ctx
-
- def _send_invite(self, instance):
- try:
- mail(
- instance.invite_email,
- _('pretix account invitation'),
- 'pretixcontrol/email/invitation.txt',
- {
- 'user': self,
- 'event': self.request.event.name,
- 'url': build_absolute_uri('control:auth.invite', kwargs={
- 'token': instance.invite_token
- })
- },
- event=None,
- locale=self.request.LANGUAGE_CODE
- )
- except SendMailException:
- pass # Already logged
-
- @transaction.atomic
- def post(self, *args, **kwargs):
- if self.formset.is_valid() and self.add_form.is_valid():
- if self.add_form.has_changed():
- logdata = {
- k: v for k, v in self.add_form.cleaned_data.items()
- }
-
- try:
- self.add_form.instance.event = self.request.event
- self.add_form.instance.event_id = self.request.event.id
- self.add_form.instance.user = User.objects.get(email=self.add_form.cleaned_data['user'])
- self.add_form.instance.user_id = self.add_form.instance.user.id
- except User.DoesNotExist:
- self.add_form.instance.invite_email = self.add_form.cleaned_data['user']
- if EventPermission.objects.filter(invite_email=self.add_form.instance.invite_email,
- event=self.request.event).exists():
- messages.error(self.request, _('This user already has been invited for this event.'))
- return self.get(*args, **kwargs)
-
- self.add_form.save()
- self._send_invite(self.add_form.instance)
-
- self.request.event.log_action(
- 'pretix.event.permissions.invited', user=self.request.user, data=logdata
- )
- else:
- if EventPermission.objects.filter(user=self.add_form.instance.user,
- event=self.request.event).exists():
- messages.error(self.request, _('This user already has permissions for this event.'))
- return self.get(*args, **kwargs)
- self.add_form.save()
- logdata['user'] = self.add_form.instance.user_id
- self.request.event.log_action(
- 'pretix.event.permissions.added', user=self.request.user, data=logdata
- )
- for form in self.formset.forms:
- if form.has_changed():
- changedata = {
- k: form.cleaned_data.get(k) for k in form.changed_data
- }
- changedata['user'] = form.instance.user_id
- self.request.event.log_action(
- 'pretix.event.permissions.changed', user=self.request.user, data=changedata
- )
- if form.instance.user_id == self.request.user.pk:
- if not form.cleaned_data['can_change_permissions'] or form in self.formset.deleted_forms:
- messages.error(self.request, _('You cannot remove your own permission to view this page.'))
- return self.get(*args, **kwargs)
-
- for form in self.formset.deleted_forms:
- logdata = {
- k: v for k, v in form.cleaned_data.items()
- }
- self.request.event.log_action(
- 'pretix.event.permissions.deleted', user=self.request.user, data=logdata
- )
-
- self.formset.save()
- messages.success(self.request, _('Your changes have been saved.'))
- return redirect(self.get_success_url())
- else:
- messages.error(self.request, _('Your changes could not be saved.'))
- return self.get(*args, **kwargs)
-
- def get_success_url(self) -> str:
- return reverse('control:event.settings.permissions', kwargs={
- 'organizer': self.request.event.organizer.slug,
- 'event': self.request.event.slug
- })
class EventLive(EventPermissionRequiredMixin, TemplateView):
- permission = 'can_change_settings'
+ permission = 'can_change_event_settings'
template_name = 'pretixcontrol/event/live.html'
def get_context_data(self, **kwargs):
@@ -840,9 +708,9 @@ class EventLog(EventPermissionRequiredMixin, ListView):
def get_queryset(self):
qs = self.request.event.logentry_set.all().select_related('user', 'content_type').order_by('-datetime')
qs = qs.exclude(action_type__in=OVERVIEW_BLACKLIST)
- if not self.request.eventperm.can_view_orders:
+ if not self.request.user.has_event_permisson(self.request.organizer, self.request.event, 'can_view_orders'):
qs = qs.exclude(content_type=ContentType.objects.get_for_model(Order))
- if not self.request.eventperm.can_view_vouchers:
+ if not self.request.user.has_event_permisson(self.request.organizer, self.request.event, 'can_view_vouchers'):
qs = qs.exclude(content_type=ContentType.objects.get_for_model(Voucher))
if self.request.GET.get('user') == 'yes':
@@ -856,7 +724,7 @@ class EventLog(EventPermissionRequiredMixin, ListView):
def get_context_data(self, **kwargs):
ctx = super().get_context_data()
- ctx['userlist'] = self.request.event.user_perms.select_related('user')
+ ctx['userlist'] = self.request.event.logentry_set.order_by().distinct().values('user__id', 'user__email')
return ctx
diff --git a/src/pretix/control/views/main.py b/src/pretix/control/views/main.py
index 2bd1e043f..7f735f4a8 100644
--- a/src/pretix/control/views/main.py
+++ b/src/pretix/control/views/main.py
@@ -7,7 +7,7 @@ from django.utils.translation import ugettext_lazy as _
from django.views.generic import ListView
from formtools.wizard.views import SessionWizardView
-from pretix.base.models import Event, EventPermission, OrganizerPermission
+from pretix.base.models import Event, Team
from pretix.control.forms.event import (
EventWizardBasicsForm, EventWizardCopyForm, EventWizardFoundationForm,
)
@@ -20,22 +20,13 @@ class EventList(ListView):
template_name = 'pretixcontrol/events/index.html'
def get_queryset(self):
- if self.request.user.is_superuser:
- return Event.objects.all().select_related("organizer").prefetch_related(
- "_settings_objects", "organizer___settings_objects"
- )
- else:
- return Event.objects.filter(
- permitted__id__exact=self.request.user.pk
- ).select_related("organizer").prefetch_related(
- "_settings_objects", "organizer___settings_objects"
- )
+ return self.request.user.get_events_with_any_permission().select_related('organizer').prefetch_related(
+ '_settings_objects', 'organizer___settings_objects'
+ )
def condition_copy(wizard):
- return EventPermission.objects.filter(
- user=wizard.request.user, can_change_settings=True, can_change_items=True
- ).exists()
+ return EventWizardCopyForm.copy_from_queryset(wizard.request.user).exists()
class EventWizard(SessionWizardView):
@@ -55,8 +46,7 @@ class EventWizard(SessionWizardView):
def get_context_data(self, form, **kwargs):
ctx = super().get_context_data(form, **kwargs)
- ctx['has_organizer'] = OrganizerPermission.objects.filter(user=self.request.user,
- can_create_events=True).exists()
+ ctx['has_organizer'] = self.request.user.teams.filter(can_create_events=True).exists()
return ctx
def get_form_kwargs(self, step=None):
@@ -81,7 +71,20 @@ class EventWizard(SessionWizardView):
event.organizer = foundation_data['organizer']
event.plugins = settings.PRETIX_PLUGINS_DEFAULT
form_dict['basics'].save()
- EventPermission.objects.create(event=event, user=self.request.user)
+
+ has_control_rights = self.request.user.teams.filter(
+ organizer=event.organizer, all_events=True, can_change_event_settings=True, can_change_items=True,
+ can_change_orders=True, can_change_vouchers=True
+ ).exists()
+ if not has_control_rights:
+ t = Team.objects.create(
+ organizer=event.organizer, name=_('Team {event}').format(event=event.name),
+ can_change_event_settings=True, can_change_items=True,
+ can_view_orders=True, can_change_orders=True, can_view_vouchers=True,
+ can_change_vouchers=True
+ )
+ t.members.add(self.request.user)
+ t.limit_events.add(event)
logdata = {}
for f in form_list:
diff --git a/src/pretix/control/views/organizer.py b/src/pretix/control/views/organizer.py
index dd30f1170..5d88aee09 100644
--- a/src/pretix/control/views/organizer.py
+++ b/src/pretix/control/views/organizer.py
@@ -3,16 +3,19 @@ from django.contrib import messages
from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse
from django.db import transaction
-from django.forms import modelformset_factory
-from django.shortcuts import redirect
+from django.db.models import Count
+from django.shortcuts import get_object_or_404, redirect
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
-from django.views.generic import CreateView, DetailView, ListView, UpdateView
+from django.views.generic import (
+ CreateView, DeleteView, DetailView, ListView, UpdateView,
+)
-from pretix.base.forms import I18nModelForm
-from pretix.base.models import Organizer, OrganizerPermission, User
+from pretix.base.models import Organizer, Team, TeamInvite, User
from pretix.base.services.mail import SendMailException, mail
-from pretix.control.forms.organizer import OrganizerForm, OrganizerUpdateForm
+from pretix.control.forms.organizer import (
+ OrganizerForm, OrganizerUpdateForm, TeamForm,
+)
from pretix.control.permissions import OrganizerPermissionRequiredMixin
from pretix.control.signals import nav_organizer
from pretix.helpers.urls import build_absolute_uri
@@ -28,25 +31,14 @@ class OrganizerList(ListView):
if self.request.user.is_superuser:
return Organizer.objects.all()
else:
- return Organizer.objects.filter(
- permitted__id__exact=self.request.user.pk
- )
+ return Organizer.objects.filter(pk__in=self.request.user.teams.values_list('organizer', flat=True))
-class OrganizerPermissionForm(I18nModelForm):
- class Meta:
- model = OrganizerPermission
- fields = (
- 'can_create_events', 'can_change_permissions'
- )
-
-
-class OrganizerPermissionCreateForm(OrganizerPermissionForm):
+class InviteForm(forms.Form):
user = forms.EmailField(required=False, label=_('User'))
class OrganizerDetailViewMixin:
-
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['nav_organizer'] = []
@@ -82,135 +74,12 @@ class OrganizerTeamView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMix
permission = 'can_change_permissions'
context_object_name = 'organizer'
- @cached_property
- def formset(self):
- fs = modelformset_factory(
- OrganizerPermission,
- form=OrganizerPermissionForm,
- can_delete=True, can_order=False, extra=0
- )
- return fs(
- data=(
- self.request.POST
- if self.request.method == "POST" and 'formset-TOTAL_FORMS' in self.request.POST
- else None
- ),
- prefix="formset",
- queryset=OrganizerPermission.objects.filter(organizer=self.request.organizer)
- )
-
- @cached_property
- def add_form(self):
- return OrganizerPermissionCreateForm(
- data=(
- self.request.POST
- if self.request.method == "POST" and 'formset-TOTAL_FORMS' in self.request.POST
- else None
- ),
- prefix="add"
- )
-
- def get_context_data(self, **kwargs):
- ctx = super().get_context_data(**kwargs)
- ctx['formset'] = self.formset
- ctx['add_form'] = self.add_form
- return ctx
-
- def _send_invite(self, instance):
- try:
- mail(
- instance.invite_email,
- _('pretix account invitation'),
- 'pretixcontrol/email/invitation_organizer.txt',
- {
- 'user': self,
- 'organizer': self.request.organizer.name,
- 'url': build_absolute_uri('control:auth.invite', kwargs={
- 'token': instance.invite_token
- })
- },
- event=None,
- locale=self.request.LANGUAGE_CODE
- )
- except SendMailException:
- pass # Already logged
-
- @transaction.atomic
- def post(self, *args, **kwargs):
- if self.formset.is_valid() and self.add_form.is_valid():
- if self.add_form.has_changed():
- logdata = {
- k: v for k, v in self.add_form.cleaned_data.items()
- }
-
- try:
- self.add_form.instance.organizer = self.request.organizer
- self.add_form.instance.organizer_id = self.request.organizer.id
- self.add_form.instance.user = User.objects.get(email=self.add_form.cleaned_data['user'])
- self.add_form.instance.user_id = self.add_form.instance.user.id
- except User.DoesNotExist:
- self.add_form.instance.invite_email = self.add_form.cleaned_data['user']
- if OrganizerPermission.objects.filter(invite_email=self.add_form.instance.invite_email,
- organizer=self.request.organizer).exists():
- messages.error(self.request, _('This user already has been invited for this team.'))
- return self.get(*args, **kwargs)
-
- self.add_form.save()
- self._send_invite(self.add_form.instance)
-
- self.request.organizer.log_action(
- 'pretix.organizer.permissions.invited', user=self.request.user, data=logdata
- )
- else:
- if OrganizerPermission.objects.filter(user=self.add_form.instance.user,
- organizer=self.request.organizer).exists():
- messages.error(self.request, _('This user already has permissions for this team.'))
- return self.get(*args, **kwargs)
- self.add_form.save()
- logdata['user'] = self.add_form.instance.user_id
- self.request.organizer.log_action(
- 'pretix.organizer.permissions.added', user=self.request.user, data=logdata
- )
- for form in self.formset.forms:
- if form.has_changed():
- changedata = {
- k: form.cleaned_data.get(k) for k in form.changed_data
- }
- changedata['user'] = form.instance.user_id
- self.request.organizer.log_action(
- 'pretix.organizer.permissions.changed', user=self.request.user, data=changedata
- )
- if form.instance.user_id == self.request.user.pk:
- if not form.cleaned_data['can_change_permissions'] or form in self.formset.deleted_forms:
- messages.error(self.request, _('You cannot remove your own permission to view this page.'))
- return self.get(*args, **kwargs)
-
- for form in self.formset.deleted_forms:
- logdata = {
- k: v for k, v in form.cleaned_data.items()
- }
- self.request.organizer.log_action(
- 'pretix.organizer.permissions.deleted', user=self.request.user, data=logdata
- )
-
- self.formset.save()
- messages.success(self.request, _('Your changes have been saved.'))
- return redirect(self.get_success_url())
- else:
- messages.error(self.request, _('Your changes could not be saved.'))
- return self.get(*args, **kwargs)
-
- def get_success_url(self) -> str:
- return reverse('control:organizer.teams', kwargs={
- 'organizer': self.request.organizer.slug,
- })
-
class OrganizerUpdate(OrganizerPermissionRequiredMixin, UpdateView):
model = Organizer
form_class = OrganizerUpdateForm
template_name = 'pretixcontrol/organizers/edit.html'
- permission = None
+ permission = 'can_change_organizer_settings'
context_object_name = 'organizer'
def get_object(self, queryset=None) -> Organizer:
@@ -243,14 +112,268 @@ class OrganizerCreate(CreateView):
raise PermissionDenied() # TODO
return super().dispatch(request, *args, **kwargs)
+ @transaction.atomic
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
+ t = Team.objects.create(
+ organizer=form.instance, name=_('Administrators'),
+ all_events=True, can_create_events=True, can_change_teams=True,
+ can_change_organizer_settings=True, can_change_event_settings=True, can_change_items=True,
+ can_view_orders=True, can_change_orders=True, can_view_vouchers=True, can_change_vouchers=True
)
+ t.members.add(self.request.user)
return ret
def get_success_url(self) -> str:
return reverse('control:organizers')
+
+
+class TeamListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
+ model = Team
+ template_name = 'pretixcontrol/organizers/teams.html'
+ permission = 'can_change_teams'
+ context_object_name = 'teams'
+
+ def get_queryset(self):
+ return self.request.organizer.teams.annotate(
+ memcount=Count('members', distinct=True),
+ eventcount=Count('limit_events', distinct=True),
+ invcount=Count('invites', distinct=True)
+ ).all()
+
+
+class TeamCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CreateView):
+ model = Team
+ template_name = 'pretixcontrol/organizers/team_edit.html'
+ permission = 'can_change_teams'
+ form_class = TeamForm
+
+ def get_form_kwargs(self):
+ kwargs = super().get_form_kwargs()
+ kwargs['organizer'] = self.request.organizer
+ return kwargs
+
+ def get_object(self, queryset=None):
+ return get_object_or_404(Team, organizer=self.request.organizer, pk=self.kwargs.get('team'))
+
+ def get_success_url(self):
+ return reverse('control:organizer.team', kwargs={
+ 'organizer': self.request.organizer.slug,
+ 'team': self.object.pk
+ })
+
+ def form_valid(self, form):
+ messages.success(self.request, _('The team has been created. You can now add members to the team.'))
+ form.instance.organizer = self.request.organizer
+ ret = super().form_valid(form)
+ form.instance.members.add(self.request.user)
+ form.instance.log_action('pretix.team.created', user=self.request.user, data={
+ k: getattr(self.object, k) if k != 'limit_events' else [e.id for e in getattr(self.object, k).all()]
+ for k in form.changed_data
+ })
+ return ret
+
+ def form_invalid(self, form):
+ messages.error(self.request, _('Your changes could not be saved.'))
+ return super().form_invalid(form)
+
+
+class TeamUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, UpdateView):
+ model = Team
+ template_name = 'pretixcontrol/organizers/team_edit.html'
+ permission = 'can_change_teams'
+ context_object_name = 'team'
+ form_class = TeamForm
+
+ def get_form_kwargs(self):
+ kwargs = super().get_form_kwargs()
+ kwargs['organizer'] = self.request.organizer
+ return kwargs
+
+ def get_object(self, queryset=None):
+ return get_object_or_404(Team, organizer=self.request.organizer, pk=self.kwargs.get('team'))
+
+ def get_success_url(self):
+ return reverse('control:organizer.team', kwargs={
+ 'organizer': self.request.organizer.slug,
+ 'team': self.object.pk
+ })
+
+ def form_valid(self, form):
+ if form.has_changed():
+ self.object.log_action('pretix.team.changed', user=self.request.user, data={
+ k: getattr(self.object, k) if k != 'limit_events' else [e.id for e in getattr(self.object, k).all()]
+ for k in form.changed_data
+ })
+ messages.success(self.request, _('Your changes have been saved.'))
+ return super().form_valid(form)
+
+ def form_invalid(self, form):
+ messages.error(self.request, _('Your changes could not be saved.'))
+ return super().form_invalid(form)
+
+
+class TeamDeleteView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, DeleteView):
+ model = Team
+ template_name = 'pretixcontrol/organizers/team_delete.html'
+ permission = 'can_change_teams'
+ context_object_name = 'team'
+
+ def get_object(self, queryset=None):
+ return get_object_or_404(Team, organizer=self.request.organizer, pk=self.kwargs.get('team'))
+
+ def get_success_url(self):
+ return reverse('control:organizer.teams', kwargs={
+ 'organizer': self.request.organizer.slug,
+ })
+
+ def get_context_data(self, *args, **kwargs) -> dict:
+ context = super().get_context_data(*args, **kwargs)
+ context['possible'] = self.is_allowed()
+ return context
+
+ def is_allowed(self) -> bool:
+ return self.request.organizer.teams.exclude(pk=self.kwargs.get('team')).filter(
+ can_change_teams=True, members__isnull=False
+ ).exists()
+
+ @transaction.atomic
+ def delete(self, request, *args, **kwargs):
+ success_url = self.get_success_url()
+ self.object = self.get_object()
+ if self.is_allowed():
+ self.object.log_action('pretix.team.deleted', user=self.request.user)
+ self.object.delete()
+ messages.success(request, _('The selected team has been deleted.'))
+ return redirect(success_url)
+ else:
+ messages.error(request, _('The selected team cannot be deleted.'))
+ return redirect(success_url)
+
+
+class TeamMemberView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, DetailView):
+ template_name = 'pretixcontrol/organizers/team_members.html'
+ context_object_name = 'team'
+ permission = 'can_change_teams'
+ model = Team
+
+ def get_object(self, queryset=None):
+ return get_object_or_404(Team, organizer=self.request.organizer, pk=self.kwargs.get('team'))
+
+ @cached_property
+ def add_form(self):
+ return InviteForm(data=self.request.POST if self.request.method == "POST" else None)
+
+ def get_context_data(self, **kwargs):
+ ctx = super().get_context_data(**kwargs)
+ ctx['add_form'] = self.add_form
+ return ctx
+
+ def _send_invite(self, instance):
+ try:
+ mail(
+ instance.email,
+ _('pretix account invitation'),
+ 'pretixcontrol/email/invitation.txt',
+ {
+ 'user': self,
+ 'organizer': self.request.organizer.name,
+ 'team': instance.team.name,
+ 'url': build_absolute_uri('control:auth.invite', kwargs={
+ 'token': instance.token
+ })
+ },
+ event=None,
+ locale=self.request.LANGUAGE_CODE
+ )
+ except SendMailException:
+ pass # Already logged
+
+ @transaction.atomic
+ def post(self, request, *args, **kwargs):
+ self.object = self.get_object()
+
+ if 'remove-member' in request.POST:
+ try:
+ user = User.objects.get(pk=request.POST.get('remove-member'))
+ except User.DoesNotExist:
+ pass
+ else:
+ other_admin_teams = self.request.organizer.teams.exclude(pk=self.object.pk).filter(
+ can_change_teams=True, members__isnull=False
+ ).exists()
+ if not other_admin_teams and self.object.can_change_teams and self.object.members.count() == 1:
+ messages.error(self.request, _('You cannot remove the last member from this team as noone would '
+ 'be left with the permission to change teams.'))
+ return redirect(self.get_success_url())
+ else:
+ self.object.members.remove(user)
+ self.object.log_action(
+ 'pretix.team.member.removed', user=self.request.user, data={
+ 'email': user.email,
+ 'user': user.pk
+ }
+ )
+ messages.success(self.request, _('The member has been removed from the team.'))
+ return redirect(self.get_success_url())
+
+ elif 'remove-invite' in request.POST:
+ try:
+ invite = self.object.invites.get(pk=request.POST.get('remove-invite'))
+ except TeamInvite.DoesNotExist:
+ messages.error(self.request, _('Invalid invite selected.'))
+ return redirect(self.get_success_url())
+ else:
+ invite.delete()
+ self.object.log_action(
+ 'pretix.team.invite.deleted', user=self.request.user, data={
+ 'email': invite.email
+ }
+ )
+ messages.success(self.request, _('The invite has been revoked.'))
+ return redirect(self.get_success_url())
+
+ elif self.add_form.is_valid() and self.add_form.has_changed():
+
+ try:
+ user = User.objects.get(email=self.add_form.cleaned_data['user'])
+ except User.DoesNotExist:
+ if self.object.invites.filter(email=self.add_form.cleaned_data['user']).exists():
+ messages.error(self.request, _('This user already has been invited for this team.'))
+ return self.get(request, *args, **kwargs)
+
+ invite = self.object.invites.create(email=self.add_form.cleaned_data['user'])
+ self._send_invite(invite)
+ self.object.log_action(
+ 'pretix.team.invite.created', user=self.request.user, data={
+ 'email': self.add_form.cleaned_data['user']
+ }
+ )
+ messages.success(self.request, _('The new member has been invited to the team.'))
+ return redirect(self.get_success_url())
+ else:
+ if self.object.members.filter(pk=user.pk).exists():
+ messages.error(self.request, _('This user already has permissions for this team.'))
+ return self.get(request, *args, **kwargs)
+
+ self.object.members.add(user)
+ self.object.log_action(
+ 'pretix.team.member.added', user=self.request.user,
+ data={
+ 'email': user.email,
+ 'user': user.pk,
+ }
+ )
+ messages.success(self.request, _('The new member has been added to the team.'))
+ return redirect(self.get_success_url())
+
+ else:
+ messages.error(self.request, _('Your changes could not be saved.'))
+ return self.get(request, *args, **kwargs)
+
+ def get_success_url(self) -> str:
+ return reverse('control:organizer.team', kwargs={
+ 'organizer': self.request.organizer.slug,
+ 'team': self.object.pk
+ })
diff --git a/src/pretix/control/views/waitinglist.py b/src/pretix/control/views/waitinglist.py
index 51cf723a8..6ed2ab0bf 100644
--- a/src/pretix/control/views/waitinglist.py
+++ b/src/pretix/control/views/waitinglist.py
@@ -44,7 +44,7 @@ class WaitingListView(EventPermissionRequiredMixin, ListView):
def post(self, request, *args, **kwargs):
if 'assign' in request.POST:
- if not request.eventperm.can_change_orders:
+ if not request.user.has_event_permisson(request.organizer, request.event, 'can_change_orders'):
messages.error(request, _('You do not have permission to do this'))
return redirect(reverse('control:event.orders.waitinglist', kwargs={
'event': request.event.slug,
diff --git a/src/pretix/plugins/banktransfer/signals.py b/src/pretix/plugins/banktransfer/signals.py
index a35a732b0..e9826e797 100644
--- a/src/pretix/plugins/banktransfer/signals.py
+++ b/src/pretix/plugins/banktransfer/signals.py
@@ -17,7 +17,7 @@ def register_payment_provider(sender, **kwargs):
@receiver(nav_event, dispatch_uid="payment_banktransfer_nav")
def control_nav_import(sender, request=None, **kwargs):
url = resolve(request.path_info)
- if not request.eventperm.can_change_orders:
+ if not request.user.has_event_permisson(request.organizer, request.event, 'can_change_orders'):
return []
return [
{
diff --git a/src/pretix/plugins/pretixdroid/signals.py b/src/pretix/plugins/pretixdroid/signals.py
index 7164ff39e..08c699189 100644
--- a/src/pretix/plugins/pretixdroid/signals.py
+++ b/src/pretix/plugins/pretixdroid/signals.py
@@ -11,7 +11,7 @@ from pretix.control.signals import nav_event
@receiver(nav_event, dispatch_uid="pretixdroid_nav")
def control_nav_import(sender, request=None, **kwargs):
url = resolve(request.path_info)
- if not request.eventperm.can_change_orders:
+ if not request.user.has_event_permisson(request.organizer, request.event, 'can_change_orders'):
return []
return [
{
diff --git a/src/pretix/plugins/sendmail/signals.py b/src/pretix/plugins/sendmail/signals.py
index 9cc1ac796..76f332d5f 100644
--- a/src/pretix/plugins/sendmail/signals.py
+++ b/src/pretix/plugins/sendmail/signals.py
@@ -9,7 +9,7 @@ from pretix.control.signals import nav_event
@receiver(nav_event, dispatch_uid="sendmail_nav")
def control_nav_import(sender, request=None, **kwargs):
url = resolve(request.path_info)
- if not request.eventperm.can_change_orders:
+ if not request.user.has_event_permisson(request.organizer, request.event, 'can_change_orders'):
return []
return [
{
diff --git a/src/pretix/plugins/statistics/signals.py b/src/pretix/plugins/statistics/signals.py
index 0ddaed768..390a35075 100644
--- a/src/pretix/plugins/statistics/signals.py
+++ b/src/pretix/plugins/statistics/signals.py
@@ -9,7 +9,7 @@ from pretix.control.signals import nav_event
@receiver(nav_event, dispatch_uid="statistics_nav")
def control_nav_import(sender, request=None, **kwargs):
url = resolve(request.path_info)
- if not request.eventperm.can_view_orders:
+ if not request.user.has_event_permisson(request.organizer, request.event, 'can_view_orders'):
return []
return [
{
diff --git a/src/pretix/presale/utils.py b/src/pretix/presale/utils.py
index b49cd5f4b..3b939df25 100644
--- a/src/pretix/presale/utils.py
+++ b/src/pretix/presale/utils.py
@@ -9,7 +9,7 @@ from django.shortcuts import redirect
from django.utils.translation import ugettext_lazy as _
from pretix.base.middleware import LocaleMiddleware
-from pretix.base.models import Event, EventPermission, Organizer
+from pretix.base.models import Event, Organizer
from pretix.multidomain.urlreverse import get_domain
from pretix.presale.signals import process_request, process_response
@@ -67,10 +67,7 @@ def _detect_event(request, require_live=True):
url.url_name == 'event.auth'
or (
request.user.is_authenticated
- and (
- request.user.is_superuser
- or EventPermission.objects.filter(event=request.event, user=request.user).exists()
- )
+ and request.user.has_event_permisson(request.organizer, request.event)
)
)
diff --git a/src/pretix/static/pretixcontrol/img/moved.svg b/src/pretix/static/pretixcontrol/img/moved.svg
new file mode 100644
index 000000000..597bd7b05
--- /dev/null
+++ b/src/pretix/static/pretixcontrol/img/moved.svg
@@ -0,0 +1,128 @@
+
+
+
+
diff --git a/src/pretix/static/pretixcontrol/js/ui/main.js b/src/pretix/static/pretixcontrol/js/ui/main.js
index 997e507ef..f6fedf3f1 100644
--- a/src/pretix/static/pretixcontrol/js/ui/main.js
+++ b/src/pretix/static/pretixcontrol/js/ui/main.js
@@ -201,6 +201,17 @@ $(function () {
dependency.on("change", update);
});
+ $("input[data-inverse-dependency]").each(function () {
+ var dependent = $(this),
+ dependency = $($(this).attr("data-inverse-dependency")),
+ update = function () {
+ var enabled = !dependency.prop('checked');
+ dependent.prop('disabled', !enabled).parents('.form-group').toggleClass('disabled', !enabled);
+ };
+ update();
+ dependency.on("change", update);
+ });
+
$("input[data-display-dependency]").each(function () {
var dependent = $(this),
dependency = $($(this).attr("data-display-dependency")),
diff --git a/src/pretix/static/pretixcontrol/scss/main.scss b/src/pretix/static/pretixcontrol/scss/main.scss
index 407a383be..9d140fef8 100644
--- a/src/pretix/static/pretixcontrol/scss/main.scss
+++ b/src/pretix/static/pretixcontrol/scss/main.scss
@@ -118,6 +118,21 @@ h1 .btn-sm {
margin-bottom: 10px;
}
+.section-moved {
+ margin: 20px 0;
+ text-align: center;
+
+ p {
+ font-size: 18px;
+ margin: 20px auto;
+ max-width: 800px;
+ }
+
+ .img-moved {
+ height: 200px;
+ width: auto;
+ }
+}
.empty-collection {
margin: 20px 0;
text-align: center;
@@ -230,3 +245,14 @@ body.loading #wrapper {
.fa-danger {
color: $brand-danger;
}
+
+.nearly-gone {
+ overflow: visible !important;
+ height: 0 !important;
+ width: 0 !important;
+ margin: 0 !important;
+ border: 0 !important;
+ padding: 0 !important;
+ display: block !important;
+
+}
\ No newline at end of file
diff --git a/src/requirements/dev.txt b/src/requirements/dev.txt
index fc6a8b811..cedbc5622 100644
--- a/src/requirements/dev.txt
+++ b/src/requirements/dev.txt
@@ -13,4 +13,6 @@ isort
pytest-mock==1.4.*
pytest-rerunfailures
pytest-warnings
+pytest-cache
+pytest-sugar
responses
diff --git a/src/tests/base/test_permissions.py b/src/tests/base/test_permissions.py
new file mode 100644
index 000000000..77566a479
--- /dev/null
+++ b/src/tests/base/test_permissions.py
@@ -0,0 +1,237 @@
+import pytest
+from django.utils.timezone import now
+
+from pretix.base.models import Event, Organizer, Team, User
+
+
+@pytest.fixture
+def organizer():
+ return Organizer.objects.create(name='Dummy', slug='dummy')
+
+
+@pytest.fixture
+def event(organizer):
+ event = Event.objects.create(
+ organizer=organizer, name='Dummy', slug='dummy',
+ date_from=now()
+ )
+ return event
+
+
+@pytest.fixture
+def user():
+ return User.objects.create_user('dummy@dummy.dummy', 'dummy')
+
+
+@pytest.mark.django_db
+def test_invalid_permission(event, user):
+ team = Team.objects.create(organizer=event.organizer)
+ with pytest.raises(ValueError):
+ team.has_permission('FOOOOOOBAR')
+
+
+@pytest.mark.django_db
+def test_any_event_permission_limited(event, user):
+ user._teamcache = {}
+ assert not user.has_event_permisson(event.organizer, event)
+
+ team = Team.objects.create(organizer=event.organizer)
+ user._teamcache = {}
+ assert not user.has_event_permisson(event.organizer, event)
+
+ team.members.add(user)
+ user._teamcache = {}
+ assert not user.has_event_permisson(event.organizer, event)
+ assert not team.permission_for_event(event)
+
+ team.limit_events.add(event)
+ user._teamcache = {}
+ assert team.permission_for_event(event)
+ assert user.has_event_permisson(event.organizer, event)
+
+
+@pytest.mark.django_db
+def test_any_event_permission_all(event, user):
+ user._teamcache = {}
+ assert not user.has_event_permisson(event.organizer, event)
+
+ team = Team.objects.create(organizer=event.organizer)
+ user._teamcache = {}
+ assert not user.has_event_permisson(event.organizer, event)
+
+ team.members.add(user)
+ user._teamcache = {}
+ assert not user.has_event_permisson(event.organizer, event)
+ assert not team.permission_for_event(event)
+
+ team.all_events = True
+ team.save()
+ user._teamcache = {}
+ assert team.permission_for_event(event)
+ assert user.has_event_permisson(event.organizer, event)
+
+
+@pytest.mark.django_db
+def test_specific_event_permission_limited(event, user):
+ user._teamcache = {}
+ assert not user.has_event_permisson(event.organizer, event, 'can_change_orders')
+
+ team = Team.objects.create(organizer=event.organizer, can_change_orders=True)
+ user._teamcache = {}
+ assert not user.has_event_permisson(event.organizer, event, 'can_change_orders')
+
+ team.members.add(user)
+ user._teamcache = {}
+ assert not user.has_event_permisson(event.organizer, event, 'can_change_orders')
+
+ team.limit_events.add(event)
+ user._teamcache = {}
+ assert user.has_event_permisson(event.organizer, event, 'can_change_orders')
+ assert not user.has_event_permisson(event.organizer, event, 'can_change_event_settings')
+
+ team.can_change_orders = False
+ team.save()
+ user._teamcache = {}
+ assert not user.has_event_permisson(event.organizer, event, 'can_change_orders')
+
+
+@pytest.mark.django_db
+def test_specific_event_permission_all(event, user):
+ user._teamcache = {}
+ assert not user.has_event_permisson(event.organizer, event, 'can_change_orders')
+
+ team = Team.objects.create(organizer=event.organizer, can_change_orders=True)
+ user._teamcache = {}
+ assert not user.has_event_permisson(event.organizer, event, 'can_change_orders')
+
+ team.members.add(user)
+ user._teamcache = {}
+ assert not user.has_event_permisson(event.organizer, event, 'can_change_orders')
+
+ team.all_events = True
+ team.save()
+ user._teamcache = {}
+ assert user.has_event_permisson(event.organizer, event, 'can_change_orders')
+
+ team.can_change_orders = False
+ team.save()
+ user._teamcache = {}
+ assert not user.has_event_permisson(event.organizer, event, 'can_change_orders')
+
+
+@pytest.mark.django_db
+def test_event_permissions_multiple_teams(event, user):
+ team1 = Team.objects.create(organizer=event.organizer, can_change_orders=True, all_events=True)
+ team2 = Team.objects.create(organizer=event.organizer, can_change_vouchers=True)
+ team3 = Team.objects.create(organizer=event.organizer, can_change_event_settings=True)
+ event2 = Event.objects.create(
+ organizer=event.organizer, name='Dummy', slug='dummy',
+ date_from=now()
+ )
+ team1.members.add(user)
+ team2.members.add(user)
+ team3.members.add(user)
+ team2.limit_events.add(event)
+ team3.limit_events.add(event2)
+
+ assert user.has_event_permisson(event.organizer, event, 'can_change_orders')
+ assert user.has_event_permisson(event.organizer, event, 'can_change_vouchers')
+ assert not user.has_event_permisson(event.organizer, event, 'can_change_event_settings')
+ assert user.get_event_permission_set(event.organizer, event) == {'can_change_orders', 'can_change_vouchers'}
+ assert user.get_event_permission_set(event.organizer, event2) == {'can_change_orders', 'can_change_event_settings',
+ 'can_change_settings'}
+
+
+@pytest.mark.django_db
+def test_any_organizer_permission(event, user):
+ user._teamcache = {}
+ assert not user.has_organizer_permisson(event.organizer)
+
+ team = Team.objects.create(organizer=event.organizer)
+ user._teamcache = {}
+ assert not user.has_organizer_permisson(event.organizer)
+
+ team.members.add(user)
+ user._teamcache = {}
+ assert user.has_organizer_permisson(event.organizer)
+
+
+@pytest.mark.django_db
+def test_specific_organizer_permission(event, user):
+ user._teamcache = {}
+ assert not user.has_organizer_permisson(event.organizer, 'can_create_events')
+
+ team = Team.objects.create(organizer=event.organizer, can_create_events=True)
+ user._teamcache = {}
+ assert not user.has_organizer_permisson(event.organizer, 'can_create_events')
+
+ team.members.add(user)
+ user._teamcache = {}
+ assert user.has_organizer_permisson(event.organizer, 'can_create_events')
+
+
+@pytest.mark.django_db
+def test_organizer_permissions_multiple_teams(event, user):
+ team1 = Team.objects.create(organizer=event.organizer, can_change_organizer_settings=True)
+ team2 = Team.objects.create(organizer=event.organizer, can_create_events=True)
+ team1.members.add(user)
+ team2.members.add(user)
+ orga2 = Organizer.objects.create(slug='d2', name='d2')
+ team3 = Team.objects.create(organizer=orga2, can_change_teams=True)
+ team3.members.add(user)
+
+ assert user.has_organizer_permisson(event.organizer, 'can_create_events')
+ assert user.has_organizer_permisson(event.organizer, 'can_change_organizer_settings')
+ assert not user.has_organizer_permisson(event.organizer, 'can_change_teams')
+ assert user.get_organizer_permission_set(event.organizer) == {'can_create_events', 'can_change_organizer_settings'}
+ assert user.get_organizer_permission_set(orga2) == {'can_change_teams'}
+
+
+@pytest.mark.django_db
+def test_superuser(event, user):
+ user.is_superuser = True
+ user.save()
+
+ assert user.has_organizer_permisson(event.organizer)
+ assert user.has_organizer_permisson(event.organizer, 'can_create_events')
+ assert user.has_event_permisson(event.organizer, event)
+ assert user.has_event_permisson(event.organizer, event, 'can_change_event_settings')
+
+ assert 'arbitrary' in user.get_event_permission_set(event.organizer, event)
+ assert 'arbitrary' in user.get_organizer_permission_set(event.organizer)
+
+ assert event in user.get_events_with_any_permission()
+
+
+@pytest.mark.django_db
+def test_list_of_events(event, user):
+ orga2 = Organizer.objects.create(slug='d2', name='d2')
+ event2 = Event.objects.create(
+ organizer=event.organizer, name='Dummy', slug='dummy2',
+ date_from=now()
+ )
+ event3 = Event.objects.create(
+ organizer=orga2, name='Dummy', slug='dummy3',
+ date_from=now()
+ )
+ event4 = Event.objects.create(
+ organizer=orga2, name='Dummy', slug='dummy4',
+ date_from=now()
+ )
+
+ assert not user.get_events_with_any_permission()
+
+ team1 = Team.objects.create(organizer=event.organizer, can_change_orders=True, all_events=True)
+ team2 = Team.objects.create(organizer=event.organizer, can_change_vouchers=True)
+ team3 = Team.objects.create(organizer=event.organizer, can_change_event_settings=True)
+ team1.members.add(user)
+ team2.members.add(user)
+ team3.members.add(user)
+ team2.limit_events.add(event)
+ team3.limit_events.add(event3)
+
+ events = list(user.get_events_with_any_permission())
+ assert event in events
+ assert event2 in events
+ assert event3 in events
+ assert event4 not in events
diff --git a/src/tests/control/test_events.py b/src/tests/control/test_events.py
index eb76fa449..028a1ea47 100644
--- a/src/tests/control/test_events.py
+++ b/src/tests/control/test_events.py
@@ -6,9 +6,7 @@ from i18nfield.strings import LazyI18nString
from pytz import timezone
from tests.base import SoupTest, extract_form_fields
-from pretix.base.models import (
- Event, EventPermission, Organizer, OrganizerPermission, User,
-)
+from pretix.base.models import Event, Organizer, Team, User
from pretix.testutils.mock import mocker_context
@@ -31,9 +29,12 @@ class EventsTest(SoupTest):
organizer=self.orga2, name='MRMCD14', slug='mrmcd14',
date_from=datetime.datetime(2014, 9, 5, tzinfo=datetime.timezone.utc),
)
- OrganizerPermission.objects.create(organizer=self.orga1, user=self.user)
- EventPermission.objects.create(event=self.event1, user=self.user, can_change_items=True,
- can_change_settings=True)
+
+ t = Team.objects.create(organizer=self.orga1, can_create_events=True, can_change_event_settings=True,
+ can_change_items=True)
+ t.members.add(self.user)
+ t.limit_events.add(self.event1)
+
self.client.login(email='dummy@dummy.dummy', password='dummy')
def test_event_list(self):
@@ -318,7 +319,7 @@ class EventsTest(SoupTest):
assert ev.settings.timezone == 'Europe/Berlin'
assert ev.organizer == self.orga1
assert ev.location == LazyI18nString({'de': 'Hamburg', 'en': 'Hamburg'})
- assert EventPermission.objects.filter(event=ev, user=self.user).exists()
+ assert Team.objects.filter(limit_events=ev, members=self.user).exists()
berlin_tz = timezone('Europe/Berlin')
assert ev.date_from == berlin_tz.localize(datetime.datetime(2016, 12, 27, 10, 0, 0)).astimezone(pytz.utc)
@@ -359,7 +360,7 @@ class EventsTest(SoupTest):
assert ev.settings.timezone == 'UTC'
assert ev.organizer == self.orga1
assert ev.location == LazyI18nString({'en': 'Hamburg'})
- assert EventPermission.objects.filter(event=ev, user=self.user).exists()
+ assert Team.objects.filter(limit_events=ev, members=self.user).exists()
assert ev.date_from == datetime.datetime(2016, 12, 27, 10, 0, 0, tzinfo=pytz.utc)
assert ev.date_to is None
assert ev.presale_start is None
diff --git a/src/tests/control/test_items.py b/src/tests/control/test_items.py
index 0d0bfd15d..825bc9995 100644
--- a/src/tests/control/test_items.py
+++ b/src/tests/control/test_items.py
@@ -5,8 +5,8 @@ from django.utils.timezone import now
from tests.base import SoupTest, extract_form_fields
from pretix.base.models import (
- Event, EventPermission, Item, ItemCategory, ItemVariation, Order,
- OrderPosition, Organizer, OrganizerPermission, Question, Quota, User,
+ Event, Item, ItemCategory, ItemVariation, Order, OrderPosition, Organizer,
+ Question, Quota, Team, User,
)
@@ -20,9 +20,9 @@ class ItemFormTest(SoupTest):
organizer=self.orga1, name='30C3', slug='30c3',
date_from=datetime.datetime(2013, 12, 26, tzinfo=datetime.timezone.utc),
)
- OrganizerPermission.objects.create(organizer=self.orga1, user=self.user)
- EventPermission.objects.create(event=self.event1, user=self.user, can_change_items=True,
- can_change_settings=True)
+ t = Team.objects.create(organizer=self.orga1, can_change_event_settings=True, can_change_items=True)
+ t.members.add(self.user)
+ t.limit_events.add(self.event1)
self.client.login(email='dummy@dummy.dummy', password='dummy')
diff --git a/src/tests/control/test_orders.py b/src/tests/control/test_orders.py
index a86da92df..574d461b8 100644
--- a/src/tests/control/test_orders.py
+++ b/src/tests/control/test_orders.py
@@ -7,8 +7,8 @@ from django.utils.timezone import now
from tests.base import SoupTest
from pretix.base.models import (
- Event, EventPermission, InvoiceAddress, Item, Order, OrderPosition,
- Organizer, Quota, User,
+ Event, InvoiceAddress, Item, Order, OrderPosition, Organizer, Quota, Team,
+ User,
)
from pretix.base.services.invoices import (
generate_cancellation, generate_invoice,
@@ -24,12 +24,9 @@ def env():
)
event.settings.set('ticketoutput_testdummy__enabled', True)
user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
- EventPermission.objects.create(
- event=event,
- user=user,
- can_view_orders=True,
- can_change_orders=True
- )
+ t = Team.objects.create(organizer=o, can_view_orders=True, can_change_orders=True)
+ t.members.add(user)
+ t.limit_events.add(event)
o = Order.objects.create(
code='FOO', event=event, email='dummy@dummy.test',
status=Order.STATUS_PENDING,
@@ -477,12 +474,9 @@ class OrderChangeTests(SoupTest):
price=Decimal("23.00"), attendee_name="Dieter"
)
user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
- EventPermission.objects.create(
- event=self.event,
- user=user,
- can_view_orders=True,
- can_change_orders=True
- )
+ t = Team.objects.create(organizer=o, can_view_orders=True, can_change_orders=True)
+ t.members.add(user)
+ t.limit_events.add(self.event)
self.client.login(email='dummy@dummy.dummy', password='dummy')
def test_change_item_success(self):
diff --git a/src/tests/control/test_permissions.py b/src/tests/control/test_permissions.py
index 95758e1ea..0f70540ec 100644
--- a/src/tests/control/test_permissions.py
+++ b/src/tests/control/test_permissions.py
@@ -3,7 +3,7 @@ from datetime import timedelta
import pytest
from django.utils.timezone import now
-from pretix.base.models import Event, EventPermission, Order, Organizer, User
+from pretix.base.models import Event, Order, Organizer, Team, User
@pytest.fixture
@@ -20,9 +20,15 @@ def env():
datetime=now(), expires=now() + timedelta(days=10),
total=0, payment_provider='banktransfer'
)
+ Team.objects.create(pk=1, organizer=o)
return event, user, o
+superuser_urls = [
+ "global/settings/",
+ "global/update/",
+]
+
event_urls = [
"",
"settings/",
@@ -31,6 +37,9 @@ event_urls = [
"settings/tickets",
"settings/permissions",
"settings/email",
+ "settings/invoice",
+ "settings/invoice/preview",
+ "settings/display",
"items/",
"items/add",
"items/1/",
@@ -65,6 +74,8 @@ event_urls = [
"orders/ABC/extend",
"orders/ABC/change",
"orders/ABC/contact",
+ "orders/ABC/comment",
+ "orders/ABC/locale",
"orders/ABC/",
"orders/",
"waitinglist/",
@@ -75,6 +86,11 @@ event_urls = [
organizer_urls = [
'organizer/abc/edit',
'organizer/abc/',
+ 'organizer/abc/teams',
+ 'organizer/abc/team/1/',
+ 'organizer/abc/team/1/edit',
+ 'organizer/abc/team/1/delete',
+ 'organizer/abc/team/add',
]
@@ -101,9 +117,29 @@ def test_logged_out(client, env, url):
assert "/control/login" in response['Location']
+@pytest.mark.django_db
+@pytest.mark.parametrize("url", superuser_urls)
+def test_superuser_required(perf_patch, client, env, url):
+ client.login(email='dummy@dummy.dummy', password='dummy')
+ response = client.get('/control/' + url)
+ assert response.status_code == 403
+ env[1].is_superuser = True
+ env[1].save()
+ response = client.get('/control/' + url)
+ assert response.status_code == 200
+
+
@pytest.mark.django_db
@pytest.mark.parametrize("url", event_urls)
def test_wrong_event(perf_patch, client, env, url):
+ event2 = Event.objects.create(
+ organizer=env[2], name='Dummy', slug='dummy2',
+ date_from=now(), plugins='pretix.plugins.banktransfer'
+ )
+ t = Team.objects.create(organizer=env[2], can_change_event_settings=True)
+ t.members.add(env[1])
+ t.limit_events.add(event2)
+
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.get('/control/event/dummy/dummy/' + url)
# These permission violations do not yield a 403 error, but
@@ -112,12 +148,15 @@ def test_wrong_event(perf_patch, client, env, url):
event_permission_urls = [
- ("can_change_settings", "settings/", 200),
- ("can_change_settings", "settings/plugins", 200),
- ("can_change_settings", "settings/payment", 200),
- ("can_change_settings", "settings/tickets", 200),
- ("can_change_settings", "settings/email", 200),
- ("can_change_permissions", "settings/permissions", 200),
+ ("can_change_event_settings", "live/", 200),
+ ("can_change_event_settings", "settings/", 200),
+ ("can_change_event_settings", "settings/plugins", 200),
+ ("can_change_event_settings", "settings/payment", 200),
+ ("can_change_event_settings", "settings/tickets", 200),
+ ("can_change_event_settings", "settings/email", 200),
+ ("can_change_event_settings", "settings/display", 200),
+ ("can_change_event_settings", "settings/invoice", 200),
+ ("can_change_event_settings", "settings/invoice/preview", 200),
# Lists are currently not access-controlled
# ("can_change_items", "items/", 200),
("can_change_items", "items/add", 200),
@@ -142,6 +181,7 @@ event_permission_urls = [
("can_change_items", "quotas/2/delete", 404),
("can_change_items", "quotas/add", 200),
("can_view_orders", "orders/overview/", 200),
+ ("can_view_orders", "orders/export/", 200),
("can_view_orders", "orders/", 200),
("can_view_orders", "orders/FOO/", 200),
("can_change_orders", "orders/FOO/extend", 200),
@@ -150,7 +190,10 @@ event_permission_urls = [
("can_change_orders", "orders/FOO/resend", 405),
("can_change_orders", "orders/FOO/invoice", 405),
("can_change_orders", "orders/FOO/change", 200),
+ ("can_change_orders", "orders/FOO/comment", 405),
+ ("can_change_orders", "orders/FOO/locale", 200),
("can_change_vouchers", "vouchers/add", 200),
+ ("can_change_orders", "requiredactions/", 200),
("can_change_vouchers", "vouchers/bulk_add", 200),
("can_view_vouchers", "vouchers/", 200),
("can_view_vouchers", "vouchers/tags/", 200),
@@ -164,40 +207,71 @@ event_permission_urls = [
@pytest.mark.django_db
@pytest.mark.parametrize("perm,url,code", event_permission_urls)
def test_wrong_event_permission(perf_patch, client, env, perm, url, code):
- ep = EventPermission(
- event=env[0], user=env[1],
+ t = Team(
+ organizer=env[2], all_events=True
)
- setattr(ep, perm, False)
- ep.save()
+ setattr(t, perm, False)
+ t.save()
+ t.members.add(env[1])
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.get('/control/event/dummy/dummy/' + url)
assert response.status_code == 403
@pytest.mark.django_db
-def test_current_permission(client, env):
- ep = EventPermission(
- event=env[0], user=env[1],
+@pytest.mark.parametrize("perm,url,code", event_permission_urls)
+def test_limited_event_permission_for_other_event(perf_patch, client, env, perm, url, code):
+ event2 = Event.objects.create(
+ organizer=env[2], name='Dummy', slug='dummy2',
+ date_from=now(), plugins='pretix.plugins.banktransfer'
)
- setattr(ep, 'can_change_settings', True)
- ep.save()
+ t = Team.objects.create(organizer=env[2], can_change_event_settings=True)
+ t.members.add(env[1])
+ t.limit_events.add(event2)
+
+ client.login(email='dummy@dummy.dummy', password='dummy')
+ response = client.get('/control/event/dummy/dummy/' + url)
+ assert response.status_code == 404
+
+
+@pytest.mark.django_db
+def test_current_permission(client, env):
+ t = Team(
+ organizer=env[2], all_events=True
+ )
+ setattr(t, 'can_change_event_settings', True)
+ t.save()
+ t.members.add(env[1])
+
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.get('/control/event/dummy/dummy/settings/')
assert response.status_code == 200
- setattr(ep, 'can_change_settings', False)
- ep.save()
+ setattr(t, 'can_change_event_settings', False)
+ t.save()
response = client.get('/control/event/dummy/dummy/settings/')
assert response.status_code == 403
@pytest.mark.django_db
@pytest.mark.parametrize("perm,url,code", event_permission_urls)
-def test_correct_event_permission(perf_patch, client, env, perm, url, code):
- ep = EventPermission(
- event=env[0], user=env[1],
- )
- setattr(ep, perm, True)
- ep.save()
+def test_correct_event_permission_all_events(perf_patch, client, env, perm, url, code):
+ t = Team(organizer=env[2], all_events=True)
+ setattr(t, perm, True)
+ t.save()
+ t.members.add(env[1])
+ client.login(email='dummy@dummy.dummy', password='dummy')
+ response = client.get('/control/event/dummy/dummy/' + url)
+ assert response.status_code == code
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize("perm,url,code", event_permission_urls)
+def test_correct_event_permission_limited(perf_patch, client, env, perm, url, code):
+ t = Team(organizer=env[2])
+ setattr(t, perm, True)
+ t.save()
+ t.members.add(env[1])
+ t.limit_events.add(env[0])
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.get('/control/event/dummy/dummy/' + url)
assert response.status_code == code
@@ -213,21 +287,23 @@ def test_wrong_organizer(perf_patch, client, env, url):
assert response.status_code == 404
-""" Disabled as tehre are currtnly no fitting URLs
organizer_permission_urls = [
- ("can_create_events", "organizer/dummy/edit", 200),
+ ("can_change_teams", "organizer/dummy/teams", 200),
+ ("can_change_teams", "organizer/dummy/team/add", 200),
+ ("can_change_teams", "organizer/dummy/team/1/", 200),
+ ("can_change_teams", "organizer/dummy/team/1/edit", 200),
+ ("can_change_teams", "organizer/dummy/team/1/delete", 200),
+ ("can_change_organizer_settings", "organizer/dummy/edit", 200),
]
@pytest.mark.django_db
@pytest.mark.parametrize("perm,url,code", organizer_permission_urls)
def test_wrong_organizer_permission(perf_patch, client, env, perm, url, code):
- if perm:
- op = OrganizerPermission(
- organizer=env[2], user=env[1],
- )
- setattr(op, perm, False)
- op.save()
+ t = Team(organizer=env[2])
+ setattr(t, perm, False)
+ t.save()
+ t.members.add(env[1])
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.get('/control/' + url)
assert response.status_code == 403
@@ -236,13 +312,10 @@ def test_wrong_organizer_permission(perf_patch, client, env, perm, url, code):
@pytest.mark.django_db
@pytest.mark.parametrize("perm,url,code", organizer_permission_urls)
def test_correct_organizer_permission(perf_patch, client, env, perm, url, code):
- op = OrganizerPermission(
- organizer=env[2], user=env[1],
- )
- if perm:
- setattr(op, perm, True)
- op.save()
+ t = Team(organizer=env[2])
+ setattr(t, perm, True)
+ t.save()
+ t.members.add(env[1])
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.get('/control/' + url)
assert response.status_code == code
-"""
diff --git a/src/tests/control/test_settings.py b/src/tests/control/test_settings.py
index 4b48e62b3..6dc2a43e2 100644
--- a/src/tests/control/test_settings.py
+++ b/src/tests/control/test_settings.py
@@ -4,9 +4,7 @@ import re
from tests.base import SoupTest
-from pretix.base.models import (
- Event, EventPermission, Organizer, OrganizerPermission, User,
-)
+from pretix.base.models import Event, Organizer, Team, User
class MailSettingPreviewTest(SoupTest):
@@ -25,11 +23,10 @@ class MailSettingPreviewTest(SoupTest):
)
self.locale_event.settings.locales = ['en', 'de-informal']
self.locale_event.save()
- OrganizerPermission.objects.create(organizer=self.orga1, user=self.user)
- EventPermission.objects.create(event=self.event1, user=self.user, can_change_items=True,
- can_change_settings=True)
- EventPermission.objects.create(event=self.locale_event, user=self.user, can_change_items=True,
- can_change_settings=True)
+ t = Team.objects.create(organizer=self.orga1, can_change_items=True, can_change_event_settings=True)
+ t.members.add(self.user)
+ t.limit_events.add(self.locale_event)
+ t.limit_events.add(self.event1)
self.client.login(email='dummy@dummy.dummy', password='dummy')
self.target = '/control/event/{}/{}/settings/email/preview'
diff --git a/src/tests/control/test_teams.py b/src/tests/control/test_teams.py
new file mode 100644
index 000000000..520dddc4e
--- /dev/null
+++ b/src/tests/control/test_teams.py
@@ -0,0 +1,245 @@
+import pytest
+from django.core import mail as djmail
+from django.utils.timezone import now
+
+from pretix.base.models import Event, Organizer, Team, User
+
+
+@pytest.fixture
+def organizer():
+ return Organizer.objects.create(name='Dummy', slug='dummy')
+
+
+@pytest.fixture
+def event(organizer):
+ event = Event.objects.create(
+ organizer=organizer, name='Dummy', slug='dummy',
+ date_from=now()
+ )
+ return event
+
+
+@pytest.fixture
+def admin_team(organizer):
+ return Team.objects.create(organizer=organizer, can_change_teams=True, name='Admin team')
+
+
+@pytest.fixture
+def admin_user(admin_team):
+ u = User.objects.create_user('dummy@dummy.dummy', 'dummy')
+ admin_team.members.add(u)
+ return u
+
+
+@pytest.mark.django_db
+def test_list_of_teams(event, admin_user, client):
+ client.login(email='dummy@dummy.dummy', password='dummy')
+ resp = client.get('/control/organizer/dummy/teams')
+ assert 'Admin team' in resp.rendered_content
+
+
+@pytest.mark.django_db
+def test_team_detail_view(event, admin_user, admin_team, client):
+ client.login(email='dummy@dummy.dummy', password='dummy')
+ resp = client.get('/control/organizer/dummy/team/{}/'.format(admin_team.pk))
+ assert 'Admin team' in resp.rendered_content
+ assert admin_user.email in resp.rendered_content
+
+
+@pytest.mark.django_db
+def test_team_add_user(event, admin_user, admin_team, client):
+ client.login(email='dummy@dummy.dummy', password='dummy')
+
+ u = User.objects.create_user('dummy2@dummy.dummy', 'dummy')
+
+ resp = client.post('/control/organizer/dummy/team/{}/'.format(admin_team.pk), {
+ 'user': u.email
+ }, follow=True)
+ assert 'Admin team' in resp.rendered_content
+ assert admin_user.email in resp.rendered_content
+ assert u.email in resp.rendered_content
+ assert u in admin_team.members.all()
+
+
+@pytest.mark.django_db
+def test_team_create_invite(event, admin_user, admin_team, client):
+ client.login(email='dummy@dummy.dummy', password='dummy')
+ djmail.outbox = []
+
+ resp = client.post('/control/organizer/dummy/team/{}/'.format(admin_team.pk), {
+ 'user': 'foo@example.org'
+ }, follow=True)
+ assert 'Admin team' in resp.rendered_content
+ assert admin_user.email in resp.rendered_content
+ assert 'foo@example.org' in resp.rendered_content
+ assert admin_team.invites.first().email == 'foo@example.org'
+ assert len(djmail.outbox) == 1
+
+
+@pytest.mark.django_db
+def test_team_revoke_invite(event, admin_user, admin_team, client):
+ client.login(email='dummy@dummy.dummy', password='dummy')
+
+ inv = admin_team.invites.create(email='foo@example.org')
+ resp = client.post('/control/organizer/dummy/team/{}/'.format(admin_team.pk), {
+ 'remove-invite': str(inv.pk)
+ }, follow=True)
+ assert 'Admin team' in resp.rendered_content
+ assert admin_user.email in resp.rendered_content
+ assert not admin_team.invites.exists()
+
+
+@pytest.mark.django_db
+def test_team_remove_user(event, admin_user, admin_team, client):
+ client.login(email='dummy@dummy.dummy', password='dummy')
+
+ u = User.objects.create_user('dummy2@dummy.dummy', 'dummy')
+ admin_team.members.add(u)
+
+ resp = client.post('/control/organizer/dummy/team/{}/'.format(admin_team.pk), {
+ 'remove-member': u.pk
+ }, follow=True)
+ assert 'Admin team' in resp.rendered_content
+ assert admin_user.email in resp.rendered_content
+ assert u not in admin_team.members.all()
+
+
+@pytest.mark.django_db
+def test_team_remove_last_admin(event, admin_user, admin_team, client):
+ client.login(email='dummy@dummy.dummy', password='dummy')
+
+ resp = client.post('/control/organizer/dummy/team/{}/'.format(admin_team.pk), {
+ 'remove-member': admin_user.pk
+ }, follow=True)
+ assert 'alert-danger' in resp.rendered_content
+ assert admin_user in admin_team.members.all()
+
+ t2 = Team.objects.create(organizer=event.organizer, name='Admin team 2')
+ resp = client.post('/control/organizer/dummy/team/{}/'.format(admin_team.pk), {
+ 'remove-member': admin_user.pk
+ }, follow=True)
+ assert 'alert-danger' in resp.rendered_content
+ assert admin_user in admin_team.members.all()
+
+ t2.members.add(admin_user)
+ resp = client.post('/control/organizer/dummy/team/{}/'.format(admin_team.pk), {
+ 'remove-member': admin_user.pk
+ }, follow=True)
+ assert 'alert-danger' in resp.rendered_content
+ assert admin_user in admin_team.members.all()
+
+ t2.can_change_teams = True
+ t2.save()
+ resp = client.post('/control/organizer/dummy/team/{}/'.format(admin_team.pk), {
+ 'remove-member': admin_user.pk
+ }, follow=True)
+ assert 'alert-danger' not in resp.rendered_content
+ assert admin_user not in admin_team.members.all()
+
+
+@pytest.mark.django_db
+def test_create_team(event, admin_user, admin_team, client):
+ client.login(email='dummy@dummy.dummy', password='dummy')
+ client.post('/control/organizer/dummy/team/add', {
+ 'name': 'Foo',
+ 'can_create_events': 'on',
+ 'limit_events': str(event.pk),
+ 'can_change_event_settings': 'on'
+ }, follow=True)
+ t = Team.objects.last()
+ assert t.can_change_event_settings
+ assert t.can_create_events
+ assert not t.can_change_organizer_settings
+ assert list(t.limit_events.all()) == [event]
+ assert list(t.members.all()) == [admin_user]
+
+
+@pytest.mark.django_db
+def test_update_team(event, admin_user, admin_team, client):
+ client.login(email='dummy@dummy.dummy', password='dummy')
+ client.post('/control/organizer/dummy/team/{}/edit'.format(admin_team.pk), {
+ 'name': 'Admin',
+ 'can_change_teams': 'on',
+ 'limit_events': str(event.pk),
+ 'can_change_event_settings': 'on'
+ }, follow=True)
+ admin_team.refresh_from_db()
+ assert admin_team.can_change_event_settings
+ assert not admin_team.can_change_organizer_settings
+ assert list(admin_team.limit_events.all()) == [event]
+
+
+@pytest.mark.django_db
+def test_update_last_team_to_be_no_admin(event, admin_user, admin_team, client):
+ client.login(email='dummy@dummy.dummy', password='dummy')
+ resp = client.post('/control/organizer/dummy/team/{}/edit'.format(admin_team.pk), {
+ 'name': 'Admin',
+ 'can_change_event_settings': 'on'
+ }, follow=True)
+ assert 'alert-danger' in resp.rendered_content
+
+
+@pytest.mark.django_db
+def test_remove_team(event, admin_user, admin_team, client):
+ client.login(email='dummy@dummy.dummy', password='dummy')
+
+ t2 = Team.objects.create(organizer=event.organizer, name='Admin team 2')
+ resp = client.post('/control/organizer/dummy/team/{}/delete'.format(t2.pk), {}, follow=True)
+ assert Team.objects.count() == 1
+ assert 'alert-success' in resp.rendered_content
+
+
+@pytest.mark.django_db
+def test_remove_last_admin_team(event, admin_user, admin_team, client):
+ client.login(email='dummy@dummy.dummy', password='dummy')
+
+ resp = client.post('/control/organizer/dummy/team/{}/delete'.format(admin_team.pk), {}, follow=True)
+ assert Team.objects.count() == 1
+ assert 'alert-danger' in resp.rendered_content
+
+
+@pytest.mark.django_db
+def test_invite_invalid_token(event, admin_team, client):
+ i = admin_team.invites.create(email='foo@bar.com')
+ resp = client.get('/control/invite/foo{}bar'.format(i.token), follow=True)
+ assert b'alert-danger' in resp.content
+ assert b'invalid link' in resp.content
+
+
+@pytest.mark.django_db
+def test_invite_existing_team_member(event, admin_team, client):
+ u = User.objects.create_user('dummy2@dummy.dummy', 'dummy')
+ admin_team.members.add(u)
+ client.login(email='dummy2@dummy.dummy', password='dummy')
+ i = admin_team.invites.create(email='foo@bar.com')
+ resp = client.get('/control/invite/{}'.format(i.token), follow=True)
+ print(resp.content)
+ assert b'alert-danger' in resp.content
+ assert b'already are part of' in resp.content
+
+
+@pytest.mark.django_db
+def test_invite_authenticated(event, admin_team, client):
+ u = User.objects.create_user('dummy2@dummy.dummy', 'dummy')
+ client.login(email='dummy2@dummy.dummy', password='dummy')
+ i = admin_team.invites.create(email='foo@bar.com')
+ resp = client.get('/control/invite/{}'.format(i.token), follow=True)
+ assert b'alert-success' in resp.content
+ assert u in admin_team.members.all()
+ assert not admin_team.invites.exists()
+
+
+@pytest.mark.django_db
+def test_invite_new_user(event, admin_team, client):
+ i = admin_team.invites.create(email='foo@bar.com')
+ resp = client.get('/control/invite/{}'.format(i.token), follow=True)
+ assert b'