Refs #39 -- New concept of "teams" (#478)

* New models

* CRUD UI

* UI for adding/removing team members

* Log display for teams

* Fix invitations, move frontend

* Drop old models (incomplete)

* Drop more old stuff

* Drop even more old stuff

* Fix tests

* Fix permission test

* flake8 fix

* Add tests fore the new code

* Rebase migrations
This commit is contained in:
Raphael Michel
2017-05-03 16:55:37 +02:00
committed by GitHub
parent 8294391ebc
commit d08a0bdb00
62 changed files with 1960 additions and 867 deletions

View File

@@ -20,13 +20,10 @@ Organizers and events
.. autoclass:: pretix.base.models.Organizer
:members:
.. autoclass:: pretix.base.models.OrganizerPermission
:members:
.. autoclass:: pretix.base.models.Event
:members:
.. autoclass:: pretix.base.models.EventPermission
.. autoclass:: pretix.base.models.Team
:members:
.. autoclass:: pretix.base.models.RequiredAction

View File

@@ -23,9 +23,6 @@ user.save()
organizer = Organizer.objects.create(
name='BigEvents LLC', slug='bigevents'
)
OrganizerPermission.objects.get_or_create(
organizer=organizer, user=user
)
year = now().year + 1
event = Event.objects.create(
organizer=organizer, name='Demo Conference {}'.format(year),
@@ -33,9 +30,13 @@ event = Event.objects.create(
date_from=datetime(year, 9, 4, 17, 0, 0),
date_to=datetime(year, 9, 6, 17, 0, 0),
)
EventPermission.objects.get_or_create(
event=event, user=user
t = Team.objects.get_or_create(
organizer=organizer, name='Admin Team',
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(user)
cat_tickets = ItemCategory.objects.create(
event=event, name='Tickets'
)

View File

@@ -0,0 +1,114 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-04-27 09:11
from __future__ import unicode_literals
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
import pretix.base.models.organizer
def create_teams(apps, schema_editor):
Event = apps.get_model('pretixbase', 'Event')
Organizer = apps.get_model('pretixbase', 'Organizer')
Team = apps.get_model('pretixbase', 'Team')
TeamInvite = apps.get_model('pretixbase', 'TeamInvite')
EventPermission = apps.get_model('pretixbase', 'EventPermission')
OrganizerPermission = apps.get_model('pretixbase', 'OrganizerPermission')
for o in Organizer.objects.prefetch_related('events'):
for e in o.events.all():
teams = {}
for p in e.user_perms.all():
pkey = (p.can_change_settings, p.can_change_items, p.can_view_orders,
p.can_change_permissions, p.can_change_orders, p.can_view_vouchers,
p.can_change_vouchers)
if pkey not in teams:
team = Team()
team.can_change_event_settings = p.can_change_settings
team.can_change_items = p.can_change_items
team.can_view_orders = p.can_view_orders
team.can_change_orders = p.can_change_orders
team.can_view_vouchers = p.can_view_vouchers
team.can_change_vouchers = p.can_change_vouchers
team.organizer = o
team.name = '{} Team {}'.format(
str(e.name), len(teams) + 1
)
team.save()
team.limit_events.add(e)
teams[pkey] = team
if p.user:
teams[pkey].members.add(p.user)
else:
teams[pkey].invites.create(email=p.invite_email, token=p.invite_token)
teams = {}
for p in o.user_perms.all():
pkey = (p.can_create_events, p.can_change_permissions)
if pkey not in teams:
team = Team()
team.can_change_organizer_settings = True
team.can_create_events = p.can_create_events
team.can_change_teams = p.can_change_permissions
team.organizer = o
team.name = '{} Team {}'.format(
str(o.name), len(teams) + 1
)
team.save()
teams[pkey] = team
if p.user:
teams[pkey].members.add(p.user)
else:
teams[pkey].invites.create(email=p.invite_email, token=p.invite_token)
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0051_auto_20170206_2027_squashed_0057_auto_20170501_2116'),
]
operations = [
migrations.CreateModel(
name='Team',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=190, verbose_name='Team name')),
('all_events', models.BooleanField(default=False, verbose_name='All events (including newly created ones)')),
('can_create_events', models.BooleanField(default=False, verbose_name='Can create events')),
('can_change_teams', models.BooleanField(default=False, verbose_name='Can change permissions')),
('can_change_organizer_settings', models.BooleanField(default=False, verbose_name='Can change organizer settings')),
('can_change_event_settings', models.BooleanField(default=False, verbose_name='Can change event settings')),
('can_change_items', models.BooleanField(default=False, verbose_name='Can change product settings')),
('can_view_orders', models.BooleanField(default=False, verbose_name='Can view orders')),
('can_change_orders', models.BooleanField(default=False, verbose_name='Can change orders')),
('can_view_vouchers', models.BooleanField(default=False, verbose_name='Can view vouchers')),
('can_change_vouchers', models.BooleanField(default=False, verbose_name='Can change vouchers')),
('limit_events', models.ManyToManyField(to='pretixbase.Event', verbose_name='Limit to events')),
('members', models.ManyToManyField(related_name='teams', to=settings.AUTH_USER_MODEL, verbose_name='Team members')),
('organizer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='teams', to='pretixbase.Organizer')),
],
options={
'verbose_name_plural': 'Teams',
'verbose_name': 'Team',
},
),
migrations.CreateModel(
name='TeamInvite',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('email', models.EmailField(blank=True, max_length=254, null=True)),
('token', models.CharField(blank=True, default=pretix.base.models.organizer.generate_invite_token, max_length=64, null=True)),
('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invites', to='pretixbase.Team')),
],
),
migrations.RunPython(
create_teams, migrations.RunPython.noop
)
]

View File

@@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-04-29 10:20
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0052_team_teaminvite'),
]
operations = [
migrations.RemoveField(
model_name='eventpermission',
name='event',
),
migrations.RemoveField(
model_name='eventpermission',
name='user',
),
migrations.RemoveField(
model_name='organizerpermission',
name='organizer',
),
migrations.RemoveField(
model_name='organizerpermission',
name='user',
),
migrations.RemoveField(
model_name='event',
name='permitted',
),
migrations.RemoveField(
model_name='organizer',
name='permitted',
),
migrations.AlterField(
model_name='team',
name='can_change_teams',
field=models.BooleanField(default=False, verbose_name='Can change teams and permissions'),
),
migrations.AlterField(
model_name='team',
name='limit_events',
field=models.ManyToManyField(blank=True, to='pretixbase.Event', verbose_name='Limit to events'),
),
migrations.DeleteModel(
name='EventPermission',
),
migrations.DeleteModel(
name='OrganizerPermission',
),
]

View File

@@ -3,7 +3,7 @@ from .auth import U2FDevice, User
from .base import CachedFile, LoggedModel, cachedfile_name
from .checkin import Checkin
from .event import (
Event, Event_SettingsStore, EventLock, EventPermission, RequiredAction,
Event, Event_SettingsStore, EventLock, RequiredAction,
generate_invite_token,
)
from .invoices import Invoice, InvoiceLine, invoice_filename
@@ -18,6 +18,6 @@ from .orders import (
cachedcombinedticket_name, cachedticket_name, generate_position_secret,
generate_secret,
)
from .organizer import Organizer, Organizer_SettingsStore, OrganizerPermission
from .organizer import Organizer, Organizer_SettingsStore, Team, TeamInvite
from .vouchers import Voucher
from .waitinglist import WaitingListEntry

View File

@@ -1,9 +1,12 @@
from typing import Union
from django.conf import settings
from django.contrib.auth.models import (
AbstractBaseUser, BaseUserManager, PermissionsMixin,
)
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from django_otp.models import Device
@@ -81,6 +84,10 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
objects = UserManager()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._teamcache = {}
class Meta:
verbose_name = _("User")
verbose_name_plural = _("Users")
@@ -147,6 +154,103 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
return LogEntry.objects.filter(content_type=ContentType.objects.get_for_model(User),
object_id=self.pk)
def _get_teams_for_organizer(self, organizer):
if 'o{}'.format(organizer.pk) not in self._teamcache:
self._teamcache['o{}'.format(organizer.pk)] = list(self.teams.filter(organizer=organizer))
return self._teamcache['o{}'.format(organizer.pk)]
def _get_teams_for_event(self, organizer, event):
if 'e{}'.format(event.pk) not in self._teamcache:
self._teamcache['e{}'.format(event.pk)] = list(self.teams.filter(organizer=organizer).filter(
Q(all_events=True) | Q(limit_events=event)
))
return self._teamcache['e{}'.format(event.pk)]
class SuperuserPermissionSet:
def __contains__(self, item):
return True
def get_event_permission_set(self, organizer, event) -> Union[set, SuperuserPermissionSet]:
"""
Gets a set of permissions (as strings) that a user holds for a particular event
:param organizer: The organizer of the event
:param event: The event to check
:return: set in case of a normal user and a SuperuserPermissionSet in case of a superuser (fake object where
a in b always returns true).
"""
if self.is_superuser:
return self.SuperuserPermissionSet()
teams = self._get_teams_for_event(organizer, event)
return set.union(*[t.permission_set() for t in teams])
def get_organizer_permission_set(self, organizer) -> Union[set, SuperuserPermissionSet]:
"""
Gets a set of permissions (as strings) that a user holds for a particular organizer
:param organizer: The organizer of the event
:return: set in case of a normal user and a SuperuserPermissionSet in case of a superuser (fake object where
a in b always returns true).
"""
if self.is_superuser:
return self.SuperuserPermissionSet()
teams = self._get_teams_for_organizer(organizer)
return set.union(*[t.permission_set() for t in teams])
def has_event_permisson(self, organizer, event, perm_name=None) -> bool:
"""
Checks if this user is part of any team that grants access of type ``perm_name``
to the event ``event``.
:param organizer: The organizer of the event
:param event: The event to check
:param perm_name: The permission, e.g. ``can_change_teams``
:return: bool
"""
if self.is_superuser:
return True
teams = self._get_teams_for_event(organizer, event)
if teams:
self._teamcache['e{}'.format(event.pk)] = teams
if not perm_name or any([team.has_permission(perm_name) for team in teams]):
return True
return False
def has_organizer_permisson(self, organizer, perm_name=None):
"""
Checks if this user is part of any team that grants access of type ``perm_name``
to the organizer ``organizer``.
:param organizer: The organizer to check
:param perm_name: The permission, e.g. ``can_change_teams``
:return: bool
"""
if self.is_superuser:
return True
teams = self._get_teams_for_organizer(organizer)
if teams:
if not perm_name or any([team.has_permission(perm_name) for team in teams]):
return True
return False
def get_events_with_any_permission(self):
"""
Returns a queryset of events the user has any permissions to.
:return: Iterable of Events
"""
from .event import Event
if self.is_superuser:
return Event.objects.all()
return Event.objects.filter(
Q(organizer_id__in=self.teams.filter(all_events=True).values_list('organizer', flat=True))
| Q(id__in=self.teams.values_list('limit_events__id', flat=True))
)
class U2FDevice(Device):
json_data = models.TextField()

View File

@@ -21,7 +21,6 @@ from pretix.base.validators import EventSlugBlacklistValidator
from pretix.helpers.daterange import daterange
from ..settings import settings_hierarkey
from .auth import User
from .organizer import Organizer
@@ -79,8 +78,6 @@ class Event(LoggedModel):
verbose_name=_("Short form"),
)
live = models.BooleanField(default=False, verbose_name=_("Shop is live"))
permitted = models.ManyToManyField(User, through='EventPermission',
related_name="events", )
currency = models.CharField(max_length=10,
verbose_name=_("Default currency"),
choices=CURRENCY_CHOICES,
@@ -307,69 +304,6 @@ def generate_invite_token():
return get_random_string(length=32, allowed_chars=string.ascii_lowercase + string.digits)
class EventPermission(models.Model):
"""
The relation between an Event and a User who has permissions to
access an event.
:param event: The event this permission refers to
:type event: Event
:param user: The user this permission set applies to
:type user: User
:param can_change_settings: If ``True``, the user can change all basic settings for this event.
:type can_change_settings: bool
:param can_change_items: If ``True``, the user can change and add items and related objects for this event.
:type can_change_items: bool
:param can_view_orders: If ``True``, the user can inspect details of all orders.
:type can_view_orders: bool
:param can_change_orders: If ``True``, the user can change details of orders
:type can_change_orders: bool
"""
event = models.ForeignKey(Event, related_name="user_perms", on_delete=models.CASCADE)
user = models.ForeignKey(User, related_name="event_perms", on_delete=models.CASCADE, null=True, blank=True)
invite_email = models.EmailField(null=True, blank=True)
invite_token = models.CharField(default=generate_invite_token, max_length=64, null=True, blank=True)
can_change_settings = models.BooleanField(
default=True,
verbose_name=_("Can change event settings")
)
can_change_items = models.BooleanField(
default=True,
verbose_name=_("Can change product settings")
)
can_view_orders = models.BooleanField(
default=True,
verbose_name=_("Can view orders")
)
can_change_permissions = models.BooleanField(
default=True,
verbose_name=_("Can change permissions")
)
can_change_orders = models.BooleanField(
default=True,
verbose_name=_("Can change orders")
)
can_view_vouchers = models.BooleanField(
default=True,
verbose_name=_("Can view vouchers")
)
can_change_vouchers = models.BooleanField(
default=True,
verbose_name=_("Can change vouchers")
)
class Meta:
verbose_name = _("Event permission")
verbose_name_plural = _("Event permissions")
def __str__(self):
return _("%(name)s on %(object)s") % {
'name': str(self.user),
'object': str(self.event),
}
class EventLock(models.Model):
event = models.CharField(max_length=36, primary_key=True)
date = models.DateTimeField(auto_now=True)

View File

@@ -42,8 +42,6 @@ class Organizer(LoggedModel):
],
verbose_name=_("Short form"),
)
permitted = models.ManyToManyField(User, through='OrganizerPermission',
related_name="organizers")
class Meta:
verbose_name = _("Organizer")
@@ -74,39 +72,131 @@ def generate_invite_token():
return get_random_string(length=32, allowed_chars=string.ascii_lowercase + string.digits)
class OrganizerPermission(models.Model):
class Team(LoggedModel):
"""
The relation between an Organizer and a User who has permissions to
access an organizer profile.
A team is a collection of people given certain access rights to one or more events of an organizer.
:param organizer: The organizer this relation refers to
:param name: The name of this team
:type name: str
:param organizer: The organizer this team belongs to
:type organizer: Organizer
:param user: The user this set of permissions is valid for
:type user: User
:param can_create_events: Whether or not this user can create new events with this
organizer account.
:param members: A set of users who belong to this team
:param all_events: Whether this team has access to all events of this organizer
:type all_events: bool
:param limit_events: A set of events this team has access to. Irrelevant if ``all_events`` is ``True``.
:param can_create_events: Whether or not the members can create new events with this organizer account.
:type can_create_events: bool
:param can_change_teams: If ``True``, the members can change the teams of this organizer account.
:type can_change_teams: bool
:param can_change_organizer_settings: If ``True``, the members can change the settings of this organizer account.
:type can_change_organizer_settings: bool
:param can_change_event_settings: If ``True``, the members can change the settings of the associated events.
:type can_change_event_settings: bool
:param can_change_items: If ``True``, the members can change and add items and related objects for the associated events.
:type can_change_items: bool
:param can_view_orders: If ``True``, the members can inspect details of all orders of the associated events.
:type can_view_orders: bool
:param can_change_orders: If ``True``, the members can change details of orders of the associated events.
:type can_change_orders: bool
:param can_view_vouchers: If ``True``, the members can inspect details of all vouchers of the associated events.
:type can_view_vouchers: bool
:param can_change_vouchers: If ``True``, the members can change and create vouchers for the associated events.
:type can_change_vouchers: bool
"""
organizer = models.ForeignKey(Organizer, related_name="teams", on_delete=models.CASCADE)
name = models.CharField(max_length=190, verbose_name=_("Team name"))
members = models.ManyToManyField(User, related_name="teams", verbose_name=_("Team members"))
all_events = models.BooleanField(default=False, verbose_name=_("All events (including newly created ones)"))
limit_events = models.ManyToManyField('Event', verbose_name=_("Limit to events"), blank=True)
organizer = models.ForeignKey(Organizer, related_name="user_perms", on_delete=models.CASCADE)
user = models.ForeignKey(User, related_name="organizer_perms", on_delete=models.CASCADE, null=True, blank=True)
invite_email = models.EmailField(null=True, blank=True)
invite_token = models.CharField(default=generate_invite_token, max_length=64, null=True, blank=True)
can_create_events = models.BooleanField(
default=True,
default=False,
verbose_name=_("Can create events"),
)
can_change_permissions = models.BooleanField(
default=True,
verbose_name=_("Can change permissions"),
can_change_teams = models.BooleanField(
default=False,
verbose_name=_("Can change teams and permissions"),
)
can_change_organizer_settings = models.BooleanField(
default=False,
verbose_name=_("Can change organizer settings")
)
class Meta:
verbose_name = _("Organizer permission")
verbose_name_plural = _("Organizer permissions")
can_change_event_settings = models.BooleanField(
default=False,
verbose_name=_("Can change event settings")
)
can_change_items = models.BooleanField(
default=False,
verbose_name=_("Can change product settings")
)
can_view_orders = models.BooleanField(
default=False,
verbose_name=_("Can view orders")
)
can_change_orders = models.BooleanField(
default=False,
verbose_name=_("Can change orders")
)
can_view_vouchers = models.BooleanField(
default=False,
verbose_name=_("Can view vouchers")
)
can_change_vouchers = models.BooleanField(
default=False,
verbose_name=_("Can change vouchers")
)
def __str__(self) -> str:
return _("%(name)s on %(object)s") % {
'name': str(self.user),
'name': str(self.name),
'object': str(self.organizer),
}
def permission_set(self) -> set:
attribs = dir(self)
return {
a for a in attribs if a.startswith('can_') and self.has_permission(a)
}
@property
def can_change_settings(self): # Legacy compatiblilty
return self.can_change_event_settings
def has_permission(self, perm_name):
try:
return getattr(self, perm_name)
except AttributeError:
raise ValueError('Invalid required permission: %s' % perm_name)
def permission_for_event(self, event):
if self.all_events:
return event.organizer_id == self.organizer_id
else:
return self.limit_events.filter(pk=event.pk).exists()
class Meta:
verbose_name = _("Team")
verbose_name_plural = _("Teams")
class TeamInvite(models.Model):
"""
A TeamInvite represents someone who has been invited to a team but hasn't accept the invitation
yet.
:param team: The team the person is invited to
:type team: Team
:param email: The email the invite has been sent to
:type email: str
:param token: The secret required to redeem the invite
:type token: str
"""
team = models.ForeignKey(Team, related_name="invites", on_delete=models.CASCADE)
email = models.EmailField(null=True, blank=True)
token = models.CharField(default=generate_invite_token, max_length=64, null=True, blank=True)
def __str__(self) -> str:
return _("Invite to team '{team}' for '{email}'").format(
team=str(self.team), email=self.email
)

View File

@@ -2,6 +2,7 @@ from django import forms
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
from django.db.models import Q
from django.utils.timezone import get_current_timezone_name
from django.utils.translation import ugettext_lazy as _
from i18nfield.forms import I18nFormField, I18nTextarea
@@ -26,7 +27,7 @@ class EventWizardFoundationForm(forms.Form):
self.fields['organizer'] = forms.ModelChoiceField(
label=_("Organizer"),
queryset=Organizer.objects.filter(
id__in=self.user.organizer_perms.filter(can_create_events=True).values_list('organizer', flat=True)
id__in=self.user.teams.filter(can_create_events=True).values_list('organizer', flat=True)
),
widget=forms.RadioSelect,
empty_label=None,
@@ -111,6 +112,16 @@ class EventWizardBasicsForm(I18nModelForm):
class EventWizardCopyForm(forms.Form):
@staticmethod
def copy_from_queryset(user):
return Event.objects.filter(
Q(organizer_id__in=user.teams.filter(
all_events=True, can_change_event_settings=True, can_change_items=True
).values_list('organizer', flat=True)) | Q(id__in=user.teams.filter(
can_change_event_settings=True, can_change_items=True
).values_list('limit_events__id', flat=True))
)
def __init__(self, *args, **kwargs):
kwargs.pop('organizer')
kwargs.pop('locales')
@@ -118,11 +129,7 @@ class EventWizardCopyForm(forms.Form):
super().__init__(*args, **kwargs)
self.fields['copy_from_event'] = forms.ModelChoiceField(
label=_("Copy configuration from"),
queryset=Event.objects.filter(
id__in=self.user.event_perms.filter(
can_change_items=True, can_change_settings=True
).values_list('event', flat=True)
),
queryset=EventWizardCopyForm.copy_from_queryset(self.user),
widget=forms.RadioSelect,
empty_label=_('Do not copy'),
required=False

View File

@@ -1,8 +1,9 @@
from django import forms
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
from pretix.base.forms import I18nModelForm
from pretix.base.models import Organizer
from pretix.base.models import Organizer, Team
from pretix.multidomain.models import KnownDomain
@@ -65,3 +66,35 @@ class OrganizerUpdateForm(OrganizerForm):
instance.get_cache().clear()
return instance
class TeamForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
organizer = kwargs.pop('organizer')
super().__init__(*args, **kwargs)
self.fields['limit_events'].queryset = organizer.events.all()
class Meta:
model = Team
fields = ['name', 'all_events', 'limit_events', 'can_create_events',
'can_change_teams', 'can_change_organizer_settings',
'can_change_event_settings', 'can_change_items',
'can_view_orders', 'can_change_orders',
'can_view_vouchers', 'can_change_vouchers']
widgets = {
'limit_events': forms.CheckboxSelectMultiple(attrs={
'data-inverse-dependency': '#id_all_events'
}),
}
def clean(self):
data = super().clean()
if self.instance.pk and not data['can_change_teams']:
if not self.instance.organizer.teams.exclude(pk=self.instance.pk).filter(
can_change_teams=True, members__isnull=False
).exists():
raise ValidationError(_('The changes could not be saved because there would be no remaining team with '
'the permission to change teams and permissions.'))
return data

View File

@@ -121,7 +121,10 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
'pretix.event.permissions.invited': _('A user has been invited to the event team.'),
'pretix.event.permissions.changed': _('A user\'s permissions have been changed.'),
'pretix.event.permissions.deleted': _('A user has been removed from the event team.'),
'pretix.waitinglist.voucher': _('A voucher has been sent to a person on the waiting list.')
'pretix.waitinglist.voucher': _('A voucher has been sent to a person on the waiting list.'),
'pretix.team.created': _('The team has been created.'),
'pretix.team.changed': _('The team settings have been modified.'),
'pretix.team.deleted': _('The team settings has been deleted.'),
}
data = json.loads(logentry.data)
@@ -149,6 +152,23 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
if logentry.action_type.startswith('pretix.event.tickets.provider.'):
return _('The settings of a ticket output provider have been changed.')
if logentry.action_type == 'pretix.team.member.added':
return _('{user} has been added to the team.').format(user=data.get('email'))
if logentry.action_type == 'pretix.team.member.removed':
return _('{user} has been removed from the team.').format(user=data.get('email'))
if logentry.action_type == 'pretix.team.member.joined':
return _('{user} has joined the team using the invite sent to {email}.').format(
user=data.get('email'), email=data.get('invite_email')
)
if logentry.action_type == 'pretix.team.invite.created':
return _('{user} has been invited to the team.').format(user=data.get('email'))
if logentry.action_type == 'pretix.team.invite.deleted':
return _('The invite for {user} has been revoked.').format(user=data.get('email'))
if logentry.action_type == 'pretix.user.settings.changed':
text = str(_('Your account settings have been changed.'))
if 'email' in data:

View File

@@ -9,9 +9,7 @@ from django.utils.deprecation import MiddlewareMixin
from django.utils.encoding import force_str
from django.utils.translation import ugettext as _
from pretix.base.models import (
Event, EventPermission, Organizer, OrganizerPermission,
)
from pretix.base.models import Event, Organizer
class PermissionMiddleware(MiddlewareMixin):
@@ -61,53 +59,23 @@ class PermissionMiddleware(MiddlewareMixin):
return redirect_to_login(
path, resolved_login_url, REDIRECT_FIELD_NAME)
events = Event.objects.all() if request.user.is_superuser else request.user.events
request.user.events_cache = events.order_by(
"organizer", "date_from").prefetch_related("organizer")
events = request.user.get_events_with_any_permission()
request.user.events_cache = events.order_by("organizer", "date_from").prefetch_related("organizer")
if 'event' in url.kwargs and 'organizer' in url.kwargs:
try:
if request.user.is_superuser:
request.event = Event.objects.filter(
slug=url.kwargs['event'],
organizer__slug=url.kwargs['organizer'],
).select_related('organizer')[0]
request.eventperm = EventPermission(
event=request.event,
user=request.user
)
else:
request.event = Event.objects.filter(
slug=url.kwargs['event'],
permitted__id__exact=request.user.id,
organizer__slug=url.kwargs['organizer'],
).select_related('organizer')[0]
request.eventperm = EventPermission.objects.get(
event=request.event,
user=request.user
)
request.organizer = request.event.organizer
except IndexError:
).select_related('organizer').first()
if not request.event or not request.user.has_event_permisson(request.event.organizer, request.event):
raise Http404(_("The selected event was not found or you "
"have no permission to administrate it."))
request.organizer = request.event.organizer
request.eventpermset = request.user.get_event_permission_set(request.organizer, request.event)
elif 'organizer' in url.kwargs:
try:
if request.user.is_superuser:
request.organizer = Organizer.objects.filter(
slug=url.kwargs['organizer'],
)[0]
request.orgaperm = OrganizerPermission(
organizer=request.organizer,
user=request.user
)
else:
request.organizer = Organizer.objects.filter(
slug=url.kwargs['organizer'],
permitted__id__exact=request.user.id,
)[0]
request.orgaperm = OrganizerPermission.objects.get(
organizer=request.organizer,
user=request.user
)
except IndexError:
).first()
if not request.organizer or not request.user.has_organizer_permisson(request.organizer):
raise Http404(_("The selected organizer was not found or you "
"have no permission to administrate it."))
request.orgapermset = request.user.get_organizer_permission_set(request.organizer)

View File

@@ -1,37 +1,29 @@
from django.core.exceptions import PermissionDenied
from django.utils.translation import ugettext as _
from pretix.base.models import EventPermission, OrganizerPermission
def event_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.
"""
if permission == 'can_change_settings':
# Legacy support
permission = 'can_change_event_settings'
def decorator(function):
def wrapper(request, *args, **kw):
if not request.user.is_authenticated: # NOQA
# just a double check, should not ever happen
raise PermissionDenied()
if request.user.is_superuser:
return function(request, *args, **kw)
try:
perm = EventPermission.objects.get(
event=request.event,
user=request.user
allowed = (
request.user.is_superuser
or request.user.has_event_permisson(request.organizer, request.event, permission)
)
except EventPermission.DoesNotExist:
pass
else:
allowed = not permission
try:
if permission:
allowed = getattr(perm, permission)
except AttributeError:
pass
if allowed or request.user.is_superuser:
if allowed:
return function(request, *args, **kw)
raise PermissionDenied(_('You do not have permission to view this content.'))
return wrapper
return decorator
@@ -55,29 +47,23 @@ 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.
"""
if permission == 'can_change_settings':
# Legacy support
permission = 'can_change_organizer_settings'
def decorator(function):
def wrapper(request, *args, **kw):
if not request.user.is_authenticated: # NOQA
# just a double check, should not ever happen
raise PermissionDenied()
if request.user.is_superuser:
return function(request, *args, **kw)
try:
perm = OrganizerPermission.objects.get(
organizer=request.organizer,
user=request.user
allowed = (
request.user.is_superuser
or request.user.has_organizer_permisson(request.organizer, permission)
)
except OrganizerPermission.DoesNotExist:
pass
else:
allowed = not permission
try:
if permission:
allowed = getattr(perm, permission)
except AttributeError:
pass
if allowed or request.user.is_superuser:
if allowed:
return function(request, *args, **kw)
raise PermissionDenied(_('You do not have permission to view this content.'))
return wrapper
return decorator

View File

@@ -1,9 +1,10 @@
{% load i18n %}{% blocktrans with url=url|safe %}Hello,
you have been invited to the team of an event that uses pretix for their
you have been invited to a team on pretix, a platform to perform event
ticket sales.
Event: {{ event }}
Organizer: {{ organizer }}
Team: {{ team }}
If you want to join that team, just click on the following link:
{{ url }}

View File

@@ -1,16 +0,0 @@
{% load i18n %}{% blocktrans with url=url|safe %}Hello,
you have been invited to the team of an event organizer that uses pretix
for their ticket sales.
Organizer: {{ organizer }}
If you want to join that team, just click on the following link:
{{ url }}
If you do not want to join, you can safely ignore or delete this email.
Best regards,
Your pretix team
{% endblocktrans %}

View File

@@ -10,7 +10,7 @@
{% trans "Dashboard" %}
</a>
</li>
{% if request.eventperm.can_change_settings or request.eventperm.can_change_permissions %}
{% if 'can_change_event_settings' in request.eventpermset or 'can_change_permissions' in request.eventpermset %}
<li>
<a href="{% url 'control:event.settings' organizer=request.event.organizer.slug event=request.event.slug %}">
<i class="fa fa-wrench fa-fw"></i>
@@ -18,7 +18,7 @@
</a>
</li>
{% endif %}
{% if request.eventperm.can_change_items %}
{% if 'can_change_items' in request.eventpermset %}
<li>
<a href="{% url 'control:event.items' organizer=request.event.organizer.slug event=request.event.slug %}"
class="has-children">
@@ -55,7 +55,7 @@
</ul>
</li>
{% endif %}
{% if request.eventperm.can_view_orders %}
{% if 'can_view_orders' in request.eventpermset %}
<li>
<a href="{% url 'control:event.orders' organizer=request.event.organizer.slug event=request.event.slug %}"
class="has-children">
@@ -93,7 +93,7 @@
</ul>
</li>
{% endif %}
{% if request.eventperm.can_view_vouchers %}
{% if 'can_view_vouchers' in request.eventpermset %}
<li>
<a href="{% url 'control:event.vouchers' organizer=request.event.organizer.slug event=request.event.slug %}"
{% if url_name == "event.vouchers" %}class="active"{% endif %}>

View File

@@ -15,9 +15,11 @@
{% trans "Customer actions" %}
</option>
{% for up in userlist %}
<option value="{{ up.user_id }}" {% if request.GET.user == up.user_id %}selected="selected"{% endif %}>
{{ up.user }}
{% if up.user__id %}
<option value="{{ up.user__id }}" {% if request.GET.user == up.user__id %}selected="selected"{% endif %}>
{{ up.user__email }}
</option>
{% endif %}
{% endfor %}
</select>
<button class="btn btn-primary" type="submit">{% trans "Filter" %}</button>

View File

@@ -1,88 +1,19 @@
{% extends "pretixcontrol/event/settings_base.html" %}
{% load i18n %}
{% load staticfiles %}
{% load bootstrap3 %}
{% block inside %}
<form action="" method="post" class="form-horizontal form-permissions">
{% csrf_token %}
<fieldset>
<legend>{% trans "Permissions" %}</legend>
{% bootstrap_formset_errors formset %}
{{ formset.management_form }}
<div class="table-responsive">
<table class="table table-striped table-condensed">
<thead>
<tr>
<th>{% trans "User" %}</th>
<th>{% trans "Change settings" %}</th>
<th>{% trans "Change products" %}</th>
<th>{% trans "View orders" %}</th>
<th>{% trans "Change orders" %}</th>
<th>{% trans "Change permissions" %}</th>
<th>{% trans "View vouchers" %}</th>
<th>{% trans "Change vouchers" %}</th>
<th>{% trans "Delete" %}</th>
</tr>
</thead>
<tbody>
{% for form in formset %}
<tr>
<td>
{{ form.id }}
{% if form.instance.user %}
{{ form.instance.user }}
{% else %}
{{ form.instance.invite_email }}
<span class="fa fa-envelope-o" data-toggle="tooltip"
title="{% trans "invited, pending response" %}"></span>
{% endif %}
</td>
<td>{{ form.can_change_settings }}</td>
<td>{{ form.can_change_items }}</td>
<td>{{ form.can_view_orders }}</td>
<td>{{ form.can_change_orders }}</td>
<td>{{ form.can_change_permissions }}</td>
<td>{{ form.can_view_vouchers }}</td>
<td>{{ form.can_change_vouchers }}</td>
<td>{{ form.DELETE }}</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td colspan="9">
<strong>{% trans "Adding a new user" %}</strong><br>
{% blocktrans trimmed %}
To add a new user, you can enter their email address here. If they already have a
pretix account, they will immediately be added to the event. Otherwise, they will
be sent an email with an invitation.
{% endblocktrans %}
</td>
</tr>
<tr>
<td>
<div class="row-fluid">
<div class="col-sm-12">
{% bootstrap_field add_form.user layout='inline' %}
<div class="section-moved">
<img src="{% static "pretixcontrol/img/moved.svg" %}" class="img-moved">
<p>
{% blocktrans trimmed %}
Permission settings have moved and are now configured as part of an organizer account instead
of every event on its own.
{% endblocktrans %}
</p>
<a href="{% url "control:organizer.teams" organizer=request.event.organizer.slug %}"
class="btn btn-link btn-lg">{% trans "Go to the organizer team settings" %}</a>
</div>
</div>
</td>
<td>{{ add_form.can_change_settings }}</td>
<td>{{ add_form.can_change_items }}</td>
<td>{{ add_form.can_view_orders }}</td>
<td>{{ add_form.can_change_orders }}</td>
<td>{{ add_form.can_change_permissions }}</td>
<td>{{ add_form.can_view_vouchers }}</td>
<td>{{ add_form.can_change_vouchers }}</td>
</tr>
</tfoot>
</table>
</div>
</fieldset>
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %}
</button>
</div>
</form>
{% endblock %}

View File

@@ -5,7 +5,7 @@
{% block content %}
<h1>{% trans "Settings" %}</h1>
<ul class="nav nav-pills">
{% if request.eventperm.can_change_settings %}
{% if 'can_change_event_settings' in request.eventpermset %}
<li {% if "event.settings" == url_name %}class="active"{% endif %}>
<a href="{% url 'control:event.settings' organizer=request.event.organizer.slug event=request.event.slug %}">
{% trans "General" %}
@@ -41,8 +41,6 @@
{% trans "Invoicing" %}
</a>
</li>
{% endif %}
{% if request.eventperm.can_change_permissions %}
<li {% if "event.settings.permissions" == url_name %}class="active"{% endif %}>
<a href="{% url 'control:event.settings.permissions' organizer=request.event.organizer.slug event=request.event.slug %}">
{% trans "Permissions" %}

View File

@@ -6,7 +6,7 @@
{% block inside %}
<h1>
{% blocktrans with name=quota.name %}Quota: {{ name }}{% endblocktrans %}
{% if request.eventperm.can_change_items %}
{% if 'can_change_items' in request.eventpermset %}
<a href="{% url "control:event.items.quotas.edit" event=request.event.slug organizer=request.event.organizer.slug quota=quota.pk %}"
class="btn btn-default">
<span class="fa fa-edit"></span>

View File

@@ -14,7 +14,7 @@
{% endblocktrans %}
{% include "pretixcontrol/orders/fragment_order_status.html" with order=order class="pull-right" %}
</h1>
{% if request.eventperm.can_change_orders %}
{% if 'can_change_orders' in request.eventpermset %}
{% if order.status == 'n' or order.status == 'p' or order.status == 'e' %}
<form action="{% url "control:event.order.transition" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}"
method="post">
@@ -151,7 +151,7 @@
<div class="panel panel-default items">
<div class="panel-heading">
<div class="pull-right">
{% if order.changable and request.eventperm.can_change_orders %}
{% if order.changable and 'can_change_orders' in request.eventpermset %}
<a href="{% url "control:event.order.change" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}">
<span class="fa fa-edit"></span>
{% trans "Change products" %}

View File

@@ -5,11 +5,13 @@
{% block content %}
<h1>
{% blocktrans with name=organizer.name %}Organizer: {{ name }}{% endblocktrans %}
{% if 'can_change_organizer_settings' in request.orgapermset %}
<a href="{% url "control:organizer.edit" organizer=organizer.slug %}"
class="btn btn-default">
<span class="fa fa-edit"></span>
{% trans "Edit" %}
</a>
{% endif %}
</h1>
<ul class="nav nav-pills">
<li {% if "organizer" == url_name %}class="active"{% endif %}>
@@ -17,10 +19,10 @@
{% trans "Events" %}
</a>
</li>
{% if request.orgaperm.can_change_permissions %}
<li {% if "organizer.teams" == url_name %}class="active"{% endif %}>
{% if 'can_change_teams' in request.orgapermset %}
<li {% if "organizer.team" in url_name %}class="active"{% endif %}>
<a href="{% url "control:organizer.teams" organizer=organizer.slug %}">
{% trans "Permissions" %}
{% trans "Teams" %}
</a>
</li>
{% endif %}

View File

@@ -0,0 +1,29 @@
{% extends "pretixcontrol/organizers/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block inner %}
<h2>{% trans "Delete team:" %} {{ team.name }}</h2>
{% if not possible %}
<p>{% blocktrans %}You cannot delete the team because there would be noone left who could change team permissions afterwards.{% endblocktrans %}</p>
<div class="form-group submit-group">
<a href="{% url "control:organizer.teams" organizer=request.organizer.slug %}" class="btn btn-default btn-cancel">
{% trans "Cancel" %}
</a>
<div class="clearfix"></div>
</div>
{% else %}
<form action="" method="post" class="form-horizontal">
{% csrf_token %}
<p>{% blocktrans %}Are you sure you want to delete the team?{% endblocktrans %}
</p>
<div class="form-group submit-group">
<a href="{% url "control:organizer.teams" organizer=request.organizer.slug%}" class="btn btn-default btn-cancel">
{% trans "Cancel" %}
</a>
<button type="submit" class="btn btn-danger btn-save">
{% trans "Delete" %}
</button>
</div>
</form>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,47 @@
{% extends "pretixcontrol/organizers/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block inner %}
{% if team %}
<h2>{% trans "Team:" %} {{ team.name }}</h2>
{% else %}
<h2>{% trans "Create a new team" %}</h2>
<p>
{% blocktrans trimmed %}
You will be able to add team members in the next step.
{% endblocktrans %}
</p>
{% endif %}
<form class="form-horizontal" action="" method="post">
{% csrf_token %}
{% bootstrap_form_errors form %}
<fieldset>
<legend>{% trans "General information" %}</legend>
{% bootstrap_field form.name layout="horizontal" %}
</fieldset>
<fieldset>
<legend>{% trans "Organizer permissions" %}</legend>
{% bootstrap_field form.can_create_events layout="horizontal" %}
{% bootstrap_field form.can_change_teams layout="horizontal" %}
{% bootstrap_field form.can_change_organizer_settings layout="horizontal" %}
</fieldset>
<fieldset>
<legend>{% trans "Event permissions" %}</legend>
{% bootstrap_field form.all_events layout="horizontal" %}
{% bootstrap_field form.limit_events layout="horizontal" %}
{% bootstrap_field form.can_change_event_settings layout="horizontal" %}
{% bootstrap_field form.can_change_items layout="horizontal" %}
{% bootstrap_field form.can_view_orders layout="horizontal" %}
{% bootstrap_field form.can_change_orders layout="horizontal" %}
{% bootstrap_field form.can_view_vouchers layout="horizontal" %}
{% bootstrap_field form.can_change_vouchers layout="horizontal" %}
</fieldset>
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %}
</button>
</div>
</form>
{% endblock %}

View File

@@ -0,0 +1,82 @@
{% extends "pretixcontrol/organizers/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block inner %}
<h2>
{% trans "Team:" %} {{ team.name }}
<a href="{% url "control:organizer.team.edit" organizer=organizer.slug team=team.pk %}"
class="btn btn-default">
<span class="fa fa-edit"></span>
{% trans "Edit" %}
</a>
</h2>
<form action="" method="post">
{% csrf_token %}
<!-- Trick browsers into taking this as a default -->
<button type="submit" class="btn btn-primary btn-sm btn-block nearly-gone"></button>
<table class="table table-condensed table-hover">
<thead>
<tr>
<th>{% trans "Member" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for u in team.members.all %}
<tr>
<td>
{{ u.email }}
</td>
<td class="text-right">
<button type="submit" name="remove-member" value="{{ u.id }}"
class="btn btn-danger btn-sm btn-block">
<i class="fa fa-times"></i> {% trans "Remove" %}
</button>
</td>
</tr>
{% endfor %}
{% for i in team.invites.all %}
<tr>
<td>
{{ i.email }}
<span class="fa fa-envelope-o" data-toggle="tooltip"
title="{% trans "invited, pending response" %}"></span>
</td>
<td class="text-right">
<button type="submit" name="remove-invite" value="{{ i.id }}"
class="btn btn-danger btn-sm btn-block">
<i class="fa fa-times"></i> {% trans "Remove" %}
</button>
</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td>
{% bootstrap_field add_form.user layout='inline' %}<br>
{% blocktrans trimmed %}
To add a new user, you can enter their email address here. If they already have a
pretix account, they will immediately be added to the event. Otherwise, they will
be sent an email with an invitation.
{% endblocktrans %}
</td>
<td class="text-right">
<button type="submit" class="btn btn-primary btn-sm btn-block">
<i class="fa fa-plus"></i> {% trans "Add" %}
</button>
</td>
</tr>
</tfoot>
</table>
</form>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
{% trans "Team history" %}
</h3>
</div>
{% include "pretixcontrol/includes/logs.html" with obj=team %}
</div>
{% endblock %}

View File

@@ -2,83 +2,57 @@
{% load i18n %}
{% load bootstrap3 %}
{% block inner %}
<form action="" method="post" class="form-horizontal form-permissions">
{% csrf_token %}
<p>
{% blocktrans trimmed %}
You can use the following list to control who can create new events in the name of
this organizer and who can add more people to this list. This does <strong>not</strong>
control who has access to a particular event. You can control the access to an
event in the "Permissions" section of the event's settings. A user does not need to
be on the list here to get access to an event.
{% endblocktrans %}
{% trans "The list below shows all teams that exist within this organizer." %}
</p>
<p>
{% trans "Everyone on this list can control the organizer settings on this page." %}
</p>
{% bootstrap_formset_errors formset %}
{{ formset.management_form }}
<div class="table-responsive">
<table class="table table-striped table-condensed">
{% if request.user.is_superuser %}
<a href="{% url "control:organizer.team.add" organizer=request.organizer.slug %}" class="btn btn-default">
<span class="fa fa-plus"></span>
{% trans "Create a new team" %}
</a>
{% endif %}
<table class="table table-condensed table-hover">
<thead>
<tr>
<th>{% trans "User" %}</th>
<th>{% trans "Create events" %}</th>
<th>{% trans "Change permissions" %}</th>
<th>{% trans "Delete" %}</th>
<th>{% trans "Team name" %}</th>
<th>{% trans "Members" %}</th>
<th>{% trans "Events" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for form in formset %}
{% for t in teams %}
<tr>
<td><strong>
<a href="{% url "control:organizer.team" organizer=request.organizer.slug team=t.id %}">
{{ t.name }}
</a>
</strong></td>
<td>
{{ form.id }}
{% if form.instance.user %}
{{ form.instance.user }}
{% else %}
{{ form.instance.invite_email }}
<span class="fa fa-envelope-o" data-toggle="tooltip"
title="{% trans "invited, pending response" %}"></span>
{{ t.memcount }}
{% if t.invcount %}
{% blocktrans trimmed with count=t.invcount %}
+ {{ count }} invited
{% endblocktrans %}
{% endif %}
</td>
<td>{{ form.can_create_events }}</td>
<td>{{ form.can_change_permissions }}</td>
<td>{{ form.DELETE }}</td>
<td>
{% if t.all_events %}
{% trans "All" %}
{% else %}
{{ t.eventcount }}
{% endif %}
</td>
<td class="text-right">
<a href="{% url "control:organizer.team" organizer=request.organizer.slug team=t.id %}"
class="btn btn-default btn-sm"><i class="fa fa-list"></i></a>
<a href="{% url "control:organizer.team.edit" organizer=request.organizer.slug team=t.id %}"
class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
<a href="{% url "control:organizer.team.delete" organizer=request.organizer.slug team=t.id %}"
class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td colspan="9">
<strong>{% trans "Adding a new user" %}</strong><br>
{% blocktrans trimmed %}
To add a new user, you can enter their email address here. If they
already have a pretix account, they will immediately be added to the team.
Otherwise, they will be sent an email with an invitation.
{% endblocktrans %}
</td>
</tr>
<tr>
<td>
<div class="row-fluid">
<div class="col-sm-12">
{% bootstrap_field add_form.user layout='inline' %}
</div>
</div>
</td>
<td>{{ add_form.can_create_events }}</td>
<td>{{ add_form.can_change_permissions }}</td>
</tr>
</tfoot>
</table>
</div>
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %}
</button>
</div>
</form>
{% endblock %}

View File

@@ -11,7 +11,7 @@
</div>
{% endif %}
<div class="row">
{% if request.eventperm.can_change_orders %}
{% if 'can_change_orders' in request.eventpermset %}
<form method="post" class="col-md-6"
action="{% url "control:event.orders.waitinglist.auto" event=request.event.slug organizer=request.organizer.slug %}"
data-asynctask>
@@ -48,7 +48,7 @@
</div>
</form>
{% endif %}
<div class="{% if request.eventperm.can_change_orders %}col-md-6{% else %}col-md-12{% endif %}">
<div class="{% if 'can_change_orders' in request.eventpermset %}col-md-6{% else %}col-md-12{% endif %}">
<div class="panel panel-default">
<div class="panel-heading">
{% trans "Sales estimate" %}

View File

@@ -35,7 +35,14 @@ urlpatterns = [
url(r'^organizers/add$', organizer.OrganizerCreate.as_view(), name='organizers.add'),
url(r'^organizer/(?P<organizer>[^/]+)/$', organizer.OrganizerDetail.as_view(), name='organizer'),
url(r'^organizer/(?P<organizer>[^/]+)/edit$', organizer.OrganizerUpdate.as_view(), name='organizer.edit'),
url(r'^organizer/(?P<organizer>[^/]+)/teams$', organizer.OrganizerTeamView.as_view(), name='organizer.teams'),
url(r'^organizer/(?P<organizer>[^/]+)/teams$', organizer.TeamListView.as_view(), name='organizer.teams'),
url(r'^organizer/(?P<organizer>[^/]+)/team/add$', organizer.TeamCreateView.as_view(), name='organizer.team.add'),
url(r'^organizer/(?P<organizer>[^/]+)/team/(?P<team>[^/]+)/$', organizer.TeamMemberView.as_view(),
name='organizer.team'),
url(r'^organizer/(?P<organizer>[^/]+)/team/(?P<team>[^/]+)/edit$', organizer.TeamUpdateView.as_view(),
name='organizer.team.edit'),
url(r'^organizer/(?P<organizer>[^/]+)/team/(?P<team>[^/]+)/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<organizer>[^/]+)/(?P<event>[^/]+)/', include([

View File

@@ -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,34 +107,29 @@ 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:
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')
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))
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')
if request.method == '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)

View File

@@ -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': '<div class="event">{event}<span class="from">{df}</span><span class="to">{dt}</span></div>'.format(

View File

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

View File

@@ -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:

View File

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

View File

@@ -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,

View File

@@ -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 [
{

View File

@@ -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 [
{

View File

@@ -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 [
{

View File

@@ -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 [
{

View File

@@ -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)
)
)

View File

@@ -0,0 +1,128 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="82.443535mm"
height="75.68663mm"
viewBox="0 0 82.443535 75.68663"
version="1.1"
id="svg8"
inkscape:version="0.92.1 r"
sodipodi:docname="moved.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.7"
inkscape:cx="81.791119"
inkscape:cy="179.25689"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1364"
inkscape:window-height="747"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
fit-margin-top="2"
fit-margin-left="2"
fit-margin-right="2"
fit-margin-bottom="2" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-55.908059,-103.84336)">
<path
transform="rotate(-15)"
style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#cccccc;stroke-width:2.51185489;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:100;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="m 19.795938,154.71252 h 69.745453 v 33.08414 H 19.795938 Z"
id="rect4567" />
<g
aria-label="WE HAVE"
transform="rotate(-15)"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.1819725px;line-height:15.22746563px;font-family:DIN;-inkscape-font-specification:DIN;letter-spacing:0px;word-spacing:0px;fill:#cccccc;fill-opacity:1;stroke:none;stroke-width:0.60909861px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="text4571">
<path
d="m 37.290917,169.57716 h -2.777823 l -0.832752,-3.78902 q -0.05948,-0.23793 -0.190344,-0.94577 -0.124912,-0.70784 -0.184395,-1.18369 -0.04759,0.38663 -0.154654,0.96361 -0.107068,0.57103 -0.214136,1.05284 -0.10112,0.4818 -0.862493,3.90203 h -2.777823 l -2.153259,-8.69631 h 2.266275 l 0.945769,4.36005 q 0.321204,1.44542 0.440169,2.30197 0.07733,-0.60672 0.273618,-1.64766 0.20224,-1.04094 0.374739,-1.72499 l 0.767321,-3.28937 h 2.177052 l 0.743529,3.28937 q 0.190343,0.79112 0.386634,1.82611 0.196292,1.03499 0.261722,1.54654 0.07733,-0.66025 0.422325,-2.29007 l 0.963613,-4.37195 h 2.266275 z"
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:12.1819725px;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#cccccc;fill-opacity:1;stroke-width:0.60909861px"
id="path4605" />
<path
d="m 45.719557,169.57716 h -5.163062 v -8.69631 h 5.163062 v 1.88559 h -2.813512 v 1.36809 h 2.605324 v 1.88559 h -2.605324 v 1.64171 h 2.813512 z"
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:12.1819725px;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#cccccc;fill-opacity:1;stroke-width:0.60909861px"
id="path4607" />
<path
d="m 57.859893,169.57716 h -2.361447 v -3.54514 h -2.730237 v 3.54514 h -2.361447 v -8.69631 h 2.361447 v 3.22394 h 2.730237 v -3.22394 h 2.361447 z"
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:12.1819725px;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#cccccc;fill-opacity:1;stroke-width:0.60909861px"
id="path4609" />
<path
d="m 65.069145,169.57716 -0.428272,-1.62981 h -2.825409 l -0.440169,1.62981 h -2.581531 l 2.837305,-8.732 h 3.134717 l 2.872994,8.732 z m -0.916027,-3.55704 -0.374738,-1.42757 q -0.130861,-0.47586 -0.321205,-1.23129 -0.184395,-0.75542 -0.243877,-1.08257 -0.05353,0.30336 -0.214136,0.9993 -0.154654,0.69594 -0.695943,2.74213 z"
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:12.1819725px;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#cccccc;fill-opacity:1;stroke-width:0.60909861px"
id="path4611" />
<path
d="m 73.468046,160.88085 h 2.629117 l -2.849202,8.69631 h -2.765926 l -2.837305,-8.69631 h 2.641013 l 1.183698,4.40764 q 0.36879,1.46921 0.404479,2.04619 0.04164,-0.41637 0.166551,-1.04094 0.124912,-0.62456 0.220084,-0.98146 z"
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:12.1819725px;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#cccccc;fill-opacity:1;stroke-width:0.60909861px"
id="path4613" />
<path
d="m 82.194096,169.57716 h -5.163063 v -8.69631 h 5.163063 v 1.88559 h -2.813512 v 1.36809 h 2.605324 v 1.88559 h -2.605324 v 1.64171 h 2.813512 z"
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:12.1819725px;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#cccccc;fill-opacity:1;stroke-width:0.60909861px"
id="path4615" />
</g>
<g
aria-label="MOVED"
transform="rotate(-15)"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.88248253px;line-height:14.85310268px;font-family:DIN;-inkscape-font-specification:DIN;letter-spacing:0px;word-spacing:0px;fill:#cccccc;fill-opacity:1;stroke:none;stroke-width:0.59412414px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="text4575">
<path
d="m 36.905863,181.548 -1.734797,-6.10949 h -0.05222 q 0.121841,1.56073 0.121841,2.42523 v 3.68426 h -2.030697 v -8.48251 h 3.051848 l 1.769608,6.02247 h 0.04642 l 1.734796,-6.02247 h 3.057651 v 8.48251 h -2.106124 v -3.71907 q 0,-0.2901 0.0058,-0.64402 0.0116,-0.35393 0.08123,-1.7348 H 40.799 l -1.711588,6.09789 z"
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:11.88248253px;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#cccccc;fill-opacity:1;stroke-width:0.59412414px"
id="path4594" />
<path
d="m 52.635066,177.29514 q 0,2.14094 -1.050161,3.25492 -1.05016,1.11398 -3.075056,1.11398 -1.995886,0 -3.063453,-1.11978 -1.061764,-1.11979 -1.061764,-3.26072 0,-2.11773 1.055962,-3.23171 1.061765,-1.11979 3.080859,-1.11979 2.024896,0 3.069254,1.10818 1.044359,1.10819 1.044359,3.25492 z m -5.836805,0 q 0,2.46005 1.711588,2.46005 0.870299,0 1.288042,-0.59761 0.423546,-0.5976 0.423546,-1.86244 0,-1.27063 -0.429348,-1.87404 -0.423545,-0.60921 -1.270636,-0.60921 -1.723192,0 -1.723192,2.48325 z"
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:11.88248253px;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#cccccc;fill-opacity:1;stroke-width:0.59412414px"
id="path4596" />
<path
d="m 58.930228,173.06549 h 2.564481 l -2.779155,8.48251 h -2.697927 l -2.767551,-8.48251 h 2.576085 l 1.154597,4.29928 q 0.359724,1.43309 0.394536,1.99588 0.04061,-0.40614 0.162455,-1.01535 0.121842,-0.60921 0.214674,-0.95733 z"
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:11.88248253px;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#cccccc;fill-opacity:1;stroke-width:0.59412414px"
id="path4598" />
<path
d="m 67.441752,181.548 h -5.03613 v -8.48251 h 5.03613 v 1.83923 h -2.744343 v 1.33446 h 2.541273 v 1.83923 h -2.541273 v 1.60135 h 2.744343 z"
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:11.88248253px;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#cccccc;fill-opacity:1;stroke-width:0.59412414px"
id="path4600" />
<path
d="m 76.127338,177.12689 q 0,2.12352 -1.172003,3.27232 -1.1662,1.14879 -3.283928,1.14879 h -2.744343 v -8.48251 h 2.935809 q 2.042302,0 3.150482,1.04436 1.113983,1.04436 1.113983,3.01704 z m -2.378817,0.0812 q 0,-1.1662 -0.46416,-1.72899 -0.458357,-0.56279 -1.39828,-0.56279 h -0.667229 v 4.75183 h 0.510575 q 1.044359,0 1.531726,-0.60341 0.487368,-0.60921 0.487368,-1.85664 z"
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:11.88248253px;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#cccccc;fill-opacity:1;stroke-width:0.59412414px"
id="path4602" />
</g>
<path
sodipodi:nodetypes="ccc"
inkscape:connector-curvature="0"
id="path4579"
d="m 64.275315,142.94767 29.986329,-36.09493 27.159886,20.78265"
style="fill:none;fill-rule:evenodd;stroke:#cccccc;stroke-width:1.46500003;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -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")),

View File

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

View File

@@ -13,4 +13,6 @@ isort
pytest-mock==1.4.*
pytest-rerunfailures
pytest-warnings
pytest-cache
pytest-sugar
responses

View File

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

View File

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

View File

@@ -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')

View File

@@ -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):

View File

@@ -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
"""

View File

@@ -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'

View File

@@ -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'<form' in resp.content
resp = client.post('/control/invite/{}'.format(i.token), {
'email': 'dummy@example.org',
'password': 'asdsdgfgjh',
'password_repeat': 'asdsdgfgjh'
}, follow=True)
assert b'alert-success' in resp.content
assert admin_team.members.filter(email='dummy@example.org').exists()
assert not admin_team.invites.exists()

View File

@@ -4,8 +4,8 @@ import pytest
from django.utils.timezone import now
from pretix.base.models import (
Event, EventPermission, Item, ItemCategory, Order, OrderPosition,
Organizer, OrganizerPermission, Question, Quota, User, Voucher,
Event, Item, ItemCategory, Order, OrderPosition, Organizer, Question,
Quota, Team, User, Voucher,
)
@@ -57,9 +57,13 @@ def voucher(quota):
@pytest.fixture
def logged_in_client(client, event):
user = User.objects.create_superuser('dummy@dummy.dummy', 'dummy')
OrganizerPermission.objects.create(organizer=event.organizer, user=user, can_create_events=True)
EventPermission.objects.create(event=event, user=user, can_change_items=True,
can_change_settings=True, can_change_orders=True, can_view_orders=True)
t = Team.objects.create(
organizer=event.organizer,
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(user)
client.force_login(user)
return client
@@ -69,6 +73,9 @@ def logged_in_client(client, event):
('/control/settings/2fa/', 302),
('/control/settings/history/', 200),
('/control/global/settings/', 200),
('/control/global/update/', 200),
('/control/organizers/', 200),
('/control/organizers/add', 200),
('/control/organizer/{orga}/edit', 200),
@@ -118,6 +125,7 @@ def logged_in_client(client, event):
('/control/event/{orga}/{event}/orders/{order_code}/contact', 200),
('/control/event/{orga}/{event}/orders/{order_code}/comment', 405),
('/control/event/{orga}/{event}/orders/{order_code}/change', 200),
('/control/event/{orga}/{event}/orders/{order_code}/locale', 200),
('/control/event/{orga}/{event}/orders/{order_code}/', 200),
('/control/event/{orga}/{event}/orders/overview/', 200),
('/control/event/{orga}/{event}/orders/export/', 200),

View File

@@ -5,8 +5,7 @@ from django.utils.timezone import now
from tests.base import SoupTest, extract_form_fields
from pretix.base.models import (
Event, EventPermission, Item, ItemVariation, Organizer,
OrganizerPermission, Quota, User, Voucher,
Event, Item, ItemVariation, Organizer, Quota, Team, User, Voucher,
)
@@ -19,9 +18,9 @@ class VoucherFormTest(SoupTest):
organizer=self.orga, name='30C3', slug='30c3',
date_from=datetime.datetime(2013, 12, 26, tzinfo=datetime.timezone.utc),
)
OrganizerPermission.objects.create(organizer=self.orga, user=self.user)
EventPermission.objects.create(event=self.event, user=self.user, can_change_vouchers=True,
can_change_settings=True)
t = Team.objects.create(organizer=self.orga, can_view_vouchers=True, can_change_vouchers=True)
t.members.add(self.user)
t.limit_events.add(self.event)
self.client.login(email='dummy@dummy.dummy', password='dummy')
self.quota_shirts = Quota.objects.create(event=self.event, name='Shirts', size=2)

View File

@@ -2,8 +2,7 @@ import pytest
from django.utils.timezone import now
from pretix.base.models import (
Event, EventPermission, Item, Organizer, Quota, User, Voucher,
WaitingListEntry,
Event, Item, Organizer, Quota, Team, User, Voucher, WaitingListEntry,
)
from pretix.control.views.dashboards import waitinglist_widgets
@@ -34,12 +33,9 @@ def env():
event=event, item=item2, email='item2@example.org'
)
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)
return event, user, o, item1

View File

@@ -5,7 +5,7 @@ import pytest
from django.utils.timezone import now
from pretix.base.models import (
Event, EventPermission, Item, Order, OrderPosition, Organizer, Quota, User,
Event, Item, Order, OrderPosition, Organizer, Quota, Team, User,
)
from pretix.plugins.banktransfer.models import BankImportJob, BankTransaction
@@ -18,7 +18,9 @@ def env():
date_from=now(), plugins='pretix.plugins.banktransfer'
)
user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
EventPermission.objects.create(user=user, event=event)
t = Team.objects.create(organizer=event.organizer, can_view_orders=True, can_change_orders=True)
t.members.add(user)
t.limit_events.add(event)
o1 = Order.objects.create(
code='1Z3AS', event=event,
status=Order.STATUS_PENDING,

View File

@@ -7,7 +7,7 @@ from django.core.files.uploadedfile import SimpleUploadedFile
from django.utils.timezone import now
from pretix.base.models import (
Event, EventPermission, Item, Order, OrderPosition, Organizer, Quota, User,
Event, Item, Order, OrderPosition, Organizer, Quota, Team, User,
)
from pretix.plugins.banktransfer.models import BankImportJob
from pretix.plugins.banktransfer.tasks import process_banktransfers
@@ -21,7 +21,9 @@ def env():
date_from=now(), plugins='pretix.plugins.banktransfer'
)
user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
EventPermission.objects.create(user=user, event=event)
t = Team.objects.create(organizer=event.organizer, can_view_orders=True, can_change_orders=True)
t.members.add(user)
t.limit_events.add(event)
o1 = Order.objects.create(
code='1Z3AS', event=event,
status=Order.STATUS_PENDING,

View File

@@ -2,7 +2,7 @@ import datetime
import pytest
from pretix.base.models import Event, EventPermission, Organizer, User
from pretix.base.models import Event, Organizer, Team, User
@pytest.fixture
@@ -17,7 +17,9 @@ def env(client):
event.settings.set('attendee_names_asked', False)
event.settings.set('payment_paypal__enabled', True)
user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
EventPermission.objects.create(user=user, event=event, can_change_settings=True)
t = Team.objects.create(organizer=event.organizer, can_change_event_settings=True)
t.members.add(user)
t.limit_events.add(event)
client.force_login(user)
return client, event

View File

@@ -6,7 +6,7 @@ import pytest
from django.utils.timezone import now
from pretix.base.models import (
Event, EventPermission, Order, Organizer, RequiredAction, User,
Event, Order, Organizer, RequiredAction, Team, User,
)
@@ -18,7 +18,9 @@ def env():
organizer=o, name='Dummy', slug='dummy',
date_from=now(), live=True
)
EventPermission.objects.create(event=event, user=user)
t = Team.objects.create(organizer=event.organizer, can_view_orders=True, can_change_orders=True)
t.members.add(user)
t.limit_events.add(event)
o1 = Order.objects.create(
code='FOOBAR', event=event, email='dummy@dummy.test',
status=Order.STATUS_PAID,

View File

@@ -2,7 +2,7 @@ import datetime
import pytest
from pretix.base.models import Event, EventPermission, Organizer, User
from pretix.base.models import Event, Organizer, Team, User
@pytest.fixture
@@ -17,7 +17,9 @@ def env(client):
event.settings.set('attendee_names_asked', False)
event.settings.set('payment_stripe__enabled', True)
user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
EventPermission.objects.create(user=user, event=event, can_change_settings=True)
t = Team.objects.create(organizer=event.organizer, can_change_event_settings=True)
t.members.add(user)
t.limit_events.add(event)
client.force_login(user)
return client, event

View File

@@ -6,7 +6,7 @@ import pytest
from django.utils.timezone import now
from pretix.base.models import (
Event, EventPermission, Order, Organizer, RequiredAction, User,
Event, Order, Organizer, RequiredAction, Team, User,
)
@@ -18,7 +18,9 @@ def env():
organizer=o, name='Dummy', slug='dummy',
date_from=now(), live=True
)
EventPermission.objects.create(event=event, user=user)
t = Team.objects.create(organizer=event.organizer, can_view_orders=True, can_change_orders=True)
t.members.add(user)
t.limit_events.add(event)
o1 = Order.objects.create(
code='FOOBAR', event=event, email='dummy@dummy.test',
status=Order.STATUS_PAID,

View File

@@ -5,8 +5,8 @@ import pytest
from django.utils.timezone import now
from pretix.base.models import (
Checkin, Event, EventPermission, Item, ItemVariation, Order, OrderPosition,
Organizer, User,
Checkin, Event, Item, ItemVariation, Order, OrderPosition, Organizer, Team,
User,
)
@@ -18,7 +18,11 @@ def env():
date_from=now(), plugins='pretix.plugins.banktransfer,pretix.plugins.pretixdroid'
)
user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
EventPermission.objects.create(user=user, event=event)
t = Team.objects.create(organizer=o, can_change_event_settings=True, can_change_items=True)
t.members.add(user)
t.limit_events.add(event)
shirt = Item.objects.create(event=event, name='T-Shirt', default_price=12)
shirt_red = ItemVariation.objects.create(item=shirt, default_price=14, value="Red")
ItemVariation.objects.create(item=shirt, value="Blue")

View File

@@ -5,8 +5,7 @@ from django.core import mail as djmail
from django.utils.timezone import now
from pretix.base.models import (
Event, EventPermission, Item, ItemCategory, Order, OrderPosition,
Organizer, OrganizerPermission, User,
Event, Item, ItemCategory, Order, OrderPosition, Organizer, Team, User,
)
@@ -49,9 +48,9 @@ def order(item):
def logged_in_client(client, event):
"""Returns a logged client"""
user = User.objects.create_superuser('dummy@dummy.dummy', 'dummy')
OrganizerPermission.objects.create(organizer=event.organizer, user=user, can_create_events=True)
EventPermission.objects.create(event=event, user=user, can_change_items=True,
can_change_settings=True, can_change_orders=True, can_view_orders=True)
t = Team.objects.create(organizer=event.organizer, can_view_orders=True, can_change_orders=True)
t.members.add(user)
t.limit_events.add(event)
client.force_login(user)
return client

View File

@@ -11,8 +11,8 @@ from pytz import timezone
from tests.base import SoupTest
from pretix.base.models import (
Event, EventPermission, Item, ItemCategory, ItemVariation, Order,
Organizer, Quota, User, WaitingListEntry,
Event, Item, ItemCategory, ItemVariation, Order, Organizer, Quota, Team,
User, WaitingListEntry,
)
@@ -26,7 +26,9 @@ class EventTestMixin:
live=True
)
self.user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
EventPermission.objects.create(user=self.user, event=self.event)
t = Team.objects.create(organizer=self.orga, can_change_event_settings=True)
t.members.add(self.user)
t.limit_events.add(self.event)
class EventMiddlewareTest(EventTestMixin, SoupTest):