Data model draft

This commit is contained in:
Raphael Michel
2025-12-16 17:16:29 +01:00
committed by Raphael Michel
parent 3ce6dbf798
commit f194c7df65
8 changed files with 451 additions and 114 deletions

View File

@@ -0,0 +1,129 @@
from django.db import migrations, models
from pretix.helpers.permission_migration import (
OLD_TO_NEW_EVENT_MIGRATION, OLD_TO_NEW_ORGANIZER_MIGRATION,
)
def migrate_teams_forward(apps, schema_editor):
Team = apps.get_model("pretixbase", "Team")
for team in Team.objects.iterator():
if all(getattr(team, k) for k in OLD_TO_NEW_EVENT_MIGRATION.keys() if k != "can_checkin_orders"):
team.all_event_permissions = True
team.limit_event_permissions = {}
else:
team.all_event_permissions = False
for k, v in OLD_TO_NEW_EVENT_MIGRATION.items():
if getattr(team, k):
team.limit_event_permissions.update({kk: True for kk in v})
if all(getattr(team, k) for k in OLD_TO_NEW_ORGANIZER_MIGRATION.keys()):
team.all_organizer_permissions = True
team.limit_organizer_permissions = {}
else:
team.all_organizer_permissions = False
for k, v in OLD_TO_NEW_ORGANIZER_MIGRATION.items():
if getattr(team, k):
team.limit_organizer_permissions.update({kk: True for kk in v})
team.save(update_fields=[
"all_event_permissions", "limit_event_permissions", "all_organizer_permissions", "limit_organizer_permissions"
])
def migrate_teams_backward(apps, schema_editor):
Team = apps.get_model("pretixbase", "Team")
for team in Team.objects.iterator():
for k, v in OLD_TO_NEW_EVENT_MIGRATION.items():
setattr(team, k, team.all_event_permissions or all(team.limit_event_permissions.get(kk) for kk in v))
for k, v in OLD_TO_NEW_ORGANIZER_MIGRATION.items():
setattr(team, k, team.all_organizer_permissions or all(team.limit_organizer_permissions.get(kk) for kk in v))
team.save()
class Migration(migrations.Migration):
dependencies = [
("pretixbase", "0296_invoice_invoice_from_state"),
]
operations = [
migrations.AddField(
model_name="team",
name="all_event_permissions",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="team",
name="all_organizer_permissions",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="team",
name="limit_event_permissions",
field=models.JSONField(default=dict),
),
migrations.AddField(
model_name="team",
name="limit_organizer_permissions",
field=models.JSONField(default=dict),
),
migrations.RunPython(
migrate_teams_forward,
migrate_teams_backward,
),
migrations.RemoveField(
model_name="team",
name="can_change_event_settings",
),
migrations.RemoveField(
model_name="team",
name="can_change_items",
),
migrations.RemoveField(
model_name="team",
name="can_change_orders",
),
migrations.RemoveField(
model_name="team",
name="can_change_organizer_settings",
),
migrations.RemoveField(
model_name="team",
name="can_change_teams",
),
migrations.RemoveField(
model_name="team",
name="can_change_vouchers",
),
migrations.RemoveField(
model_name="team",
name="can_checkin_orders",
),
migrations.RemoveField(
model_name="team",
name="can_create_events",
),
migrations.RemoveField(
model_name="team",
name="can_manage_customers",
),
migrations.RemoveField(
model_name="team",
name="can_manage_gift_cards",
),
migrations.RemoveField(
model_name="team",
name="can_manage_reusable_media",
),
migrations.RemoveField(
model_name="team",
name="can_view_orders",
),
migrations.RemoveField(
model_name="team",
name="can_view_vouchers",
),
]

View File

@@ -513,8 +513,8 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
if teams: if teams:
self._teamcache['e{}'.format(event.pk)] = teams self._teamcache['e{}'.format(event.pk)] = teams
if isinstance(perm_name, (tuple, list)): if isinstance(perm_name, (tuple, list)):
return any([any(team.has_permission(p) for team in teams) for p in perm_name]) return any([any(team.has_event_permission(p) for team in teams) for p in perm_name])
if not perm_name or any([team.has_permission(perm_name) for team in teams]): if not perm_name or any([team.has_event_permission(perm_name) for team in teams]):
return True return True
return False return False
@@ -533,8 +533,8 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
teams = self._get_teams_for_organizer(organizer) teams = self._get_teams_for_organizer(organizer)
if teams: if teams:
if isinstance(perm_name, (tuple, list)): if isinstance(perm_name, (tuple, list)):
return any([any(team.has_permission(p) for team in teams) for p in perm_name]) return any([any(team.has_organizer_permission(p) for team in teams) for p in perm_name])
if not perm_name or any([team.has_permission(perm_name) for team in teams]): if not perm_name or any([team.has_organizer_permission(perm_name) for team in teams]):
return True return True
return False return False
@@ -565,14 +565,15 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
:return: Iterable of Events :return: Iterable of Events
""" """
from .event import Event from .event import Event
from .organizer import TeamQuerySet
if request and self.has_active_staff_session(request.session.session_key): if request and self.has_active_staff_session(request.session.session_key):
return Event.objects.all() return Event.objects.all()
if isinstance(permission, (tuple, list)): if isinstance(permission, (tuple, list)):
q = reduce(operator.or_, [Q(**{p: True}) for p in permission]) q = reduce(operator.or_, [TeamQuerySet.event_permission_q(p) for p in permission])
else: else:
q = Q(**{permission: True}) q = TeamQuerySet.event_permission_q(permission)
return Event.objects.filter( return Event.objects.filter(
Q(organizer_id__in=self.teams.filter(q, all_events=True).values_list('organizer', flat=True)) Q(organizer_id__in=self.teams.filter(q, all_events=True).values_list('organizer', flat=True))
@@ -605,14 +606,13 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
:return: Iterable of Organizers :return: Iterable of Organizers
""" """
from .event import Organizer from .event import Organizer
from .organizer import TeamQuerySet
if request and self.has_active_staff_session(request.session.session_key): if request and self.has_active_staff_session(request.session.session_key):
return Organizer.objects.all() return Organizer.objects.all()
kwargs = {permission: True}
return Organizer.objects.filter( return Organizer.objects.filter(
id__in=self.teams.filter(**kwargs).values_list('organizer', flat=True) id__in=self.teams.filter(TeamQuerySet.organizer_permission_q(permission)).values_list('organizer', flat=True)
) )
def has_active_staff_session(self, session_key=None): def has_active_staff_session(self, session_key=None):

View File

@@ -31,9 +31,10 @@
# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is # Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License. # License for the specific language governing permissions and limitations under the License.
import operator
import string import string
from datetime import date, datetime, time from datetime import date, datetime, time
from functools import reduce
import pytz_deprecation_shim import pytz_deprecation_shim
from django.conf import settings from django.conf import settings
@@ -53,6 +54,10 @@ from i18nfield.strings import LazyI18nString
from pretix.base.models.base import LoggedModel from pretix.base.models.base import LoggedModel
from pretix.base.validators import OrganizerSlugBanlistValidator from pretix.base.validators import OrganizerSlugBanlistValidator
from ...helpers.permission_migration import (
OLD_TO_NEW_EVENT_COMPAT, OLD_TO_NEW_ORGANIZER_COMPAT,
LegacyPermissionProperty,
)
from ..settings import settings_hierarkey from ..settings import settings_hierarkey
from .auth import User from .auth import User
@@ -309,6 +314,32 @@ def generate_api_token():
return get_random_string(length=64, allowed_chars=string.ascii_lowercase + string.digits) return get_random_string(length=64, allowed_chars=string.ascii_lowercase + string.digits)
class TeamQuerySet(models.QuerySet):
@classmethod
def event_permission_q(cls, perm_name):
if perm_name.startswith('can_') and perm_name in OLD_TO_NEW_EVENT_COMPAT: # legacy
return reduce(operator.and_, [cls.event_permission_q(p) for p in OLD_TO_NEW_EVENT_COMPAT[perm_name]])
return (
Q(all_event_permissions=True) |
Q(**{f'limit_event_permissions__{perm_name}': True})
)
@classmethod
def organizer_permission_q(cls, perm_name):
if perm_name.startswith('can_') and perm_name in OLD_TO_NEW_ORGANIZER_COMPAT: # legacy
return reduce(operator.and_, [cls.organizer_permission_q(p) for p in OLD_TO_NEW_ORGANIZER_COMPAT[perm_name]])
return (
Q(all_organizer_permissions=True) |
Q(**{f'limit_organizer_permissions__{perm_name}': True})
)
def with_event_permission(self, perm_name):
return self.filter(self.event_permission_q(perm_name))
def with_organizer_permission(self, perm_name):
return self.filter(self.organizer_permission_q(perm_name))
class Team(LoggedModel): class Team(LoggedModel):
""" """
A team is a collection of people given certain access rights to one or more events of an organizer. A team is a collection of people given certain access rights to one or more events of an organizer.
@@ -321,36 +352,10 @@ class Team(LoggedModel):
:param all_events: Whether this team has access to all events of this organizer :param all_events: Whether this team has access to all events of this organizer
:type all_events: bool :type all_events: bool
:param limit_events: A set of events this team has access to. Irrelevant if ``all_events`` is ``True``. :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_manage_customers: If ``True``, the members can view and change organizer-level customer accounts.
:type can_manage_customers: bool
:param can_manage_reusable_media: If ``True``, the members can view and change organizer-level reusable media.
:type can_manage_reusable_media: 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_checkin_orders: If ``True``, the members can perform check-in related actions.
:type can_checkin_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) organizer = models.ForeignKey(Organizer, related_name="teams", on_delete=models.CASCADE)
name = models.CharField(max_length=190, verbose_name=_("Team name")) name = models.CharField(max_length=190, verbose_name=_("Team name"))
members = models.ManyToManyField(User, related_name="teams", verbose_name=_("Team members")) 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)
require_2fa = models.BooleanField( require_2fa = models.BooleanField(
default=False, verbose_name=_("Require all members of this team to use two-factor authentication"), default=False, verbose_name=_("Require all members of this team to use two-factor authentication"),
help_text=_("If you turn this on, all members of the team will be required to either set up two-factor " help_text=_("If you turn this on, all members of the team will be required to either set up two-factor "
@@ -358,62 +363,33 @@ class Team(LoggedModel):
"all users.") "all users.")
) )
can_create_events = models.BooleanField( # Scope
default=False, all_events = models.BooleanField(default=False, verbose_name=_("All events (including newly created ones)"))
verbose_name=_("Can create events"), limit_events = models.ManyToManyField('Event', verbose_name=_("Limit to events"), blank=True)
)
can_change_teams = models.BooleanField( # Permissions
default=False, # We store them as {key: True} instead of [key] because otherwise not all lookups we need are supported on SQLite
verbose_name=_("Can change teams and permissions"), all_event_permissions = models.BooleanField(default=False, verbose_name=_("All event permissions"))
) limit_event_permissions = models.JSONField(default=dict, verbose_name=_("Event permissions"))
can_change_organizer_settings = models.BooleanField( all_organizer_permissions = models.BooleanField(default=False, verbose_name=_("All organizer permissions"))
default=False, limit_organizer_permissions = models.JSONField(default=dict, verbose_name=_("Organizer permissions"))
verbose_name=_("Can change organizer settings"),
help_text=_('Someone with this setting can get access to most data of all of your events, i.e. via privacy ' # Legacy lookups for plugin compatibility
'reports, so be careful who you add to this team!') can_change_event_settings = LegacyPermissionProperty()
) can_change_items = LegacyPermissionProperty()
can_manage_customers = models.BooleanField( can_view_orders = LegacyPermissionProperty()
default=False, can_change_orders = LegacyPermissionProperty()
verbose_name=_("Can manage customer accounts") can_checkin_orders = LegacyPermissionProperty()
) can_view_vouchers = LegacyPermissionProperty()
can_manage_reusable_media = models.BooleanField( can_change_vuchers = LegacyPermissionProperty()
default=False, can_create_events = LegacyPermissionProperty()
verbose_name=_("Can manage reusable media") can_change_organizer_settings = LegacyPermissionProperty()
) can_change_teams = LegacyPermissionProperty()
can_manage_gift_cards = models.BooleanField( can_manage_gift_cards = LegacyPermissionProperty()
default=False, can_manage_customers = LegacyPermissionProperty()
verbose_name=_("Can manage gift cards") can_manage_reusable_media = LegacyPermissionProperty()
)
can_change_event_settings = models.BooleanField( objects = TeamQuerySet.as_manager()
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_checkin_orders = models.BooleanField(
default=False,
verbose_name=_("Can perform check-ins"),
help_text=_('This includes searching for attendees, which can be used to obtain personal information about '
'attendees. Users with "can change orders" can also perform check-ins.')
)
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: def __str__(self) -> str:
return _("%(name)s on %(object)s") % { return _("%(name)s on %(object)s") % {
@@ -421,21 +397,45 @@ class Team(LoggedModel):
'object': str(self.organizer), 'object': str(self.organizer),
} }
def permission_set(self) -> set: def permission_set(self, include_legacy=True) -> set:
attribs = dir(self) from ..permissions import (
return { get_all_event_permissions, get_all_organizer_permissions,
a for a in attribs if a.startswith('can_') and self.has_permission(a) )
}
result = set()
for permission in get_all_event_permissions():
if self.all_event_permissions or self.limit_event_permissions.get(permission.name):
result.add(permission.name)
for permission in get_all_organizer_permissions():
if self.all_organizer_permissions or self.limit_organizer_permissions.get(permission.name):
result.add(permission.name)
if include_legacy:
# Add legacy permissions as well for plugin compatibility
for k, v in OLD_TO_NEW_EVENT_COMPAT.items():
if self.all_event_permissions or all(self.limit_event_permissions.get(kk) for kk in v):
result.add(k)
for k, v in OLD_TO_NEW_ORGANIZER_COMPAT.items():
if self.all_organizer_permissions or all(self.limit_organizer_permissions.get(kk) for kk in v):
result.add(k)
return result
@property @property
def can_change_settings(self): # Legacy compatiblilty def can_change_settings(self): # Legacy compatibility
return self.can_change_event_settings return self.can_change_event_settings
def has_permission(self, perm_name): def has_event_permission(self, perm_name):
try: if perm_name.startswith('can_') and hasattr(self, perm_name): # legacy
return getattr(self, perm_name) return getattr(self, perm_name)
except AttributeError: return self.all_event_permissions or self.limit_event_permissions.get(perm_name, False)
raise ValueError('Invalid required permission: %s' % perm_name)
def has_organizer_permission(self, perm_name):
if perm_name.startswith('can_') and hasattr(self, perm_name): # legacy
return getattr(self, perm_name)
return self.all_organizer_permissions or self.limit_organizer_permissions.get(perm_name, False)
def permission_for_event(self, event): def permission_for_event(self, event):
if self.all_events: if self.all_events:
@@ -529,8 +529,8 @@ class TeamAPIToken(models.Model):
event in self.team.limit_events.all() event in self.team.limit_events.all()
) )
if isinstance(perm_name, (tuple, list)): if isinstance(perm_name, (tuple, list)):
return has_event_access and any(self.team.has_permission(p) for p in perm_name) return has_event_access and any(self.team.has_event_permission(p) for p in perm_name)
return has_event_access and (not perm_name or self.team.has_permission(perm_name)) return has_event_access and (not perm_name or self.team.has_event_permission(perm_name))
def has_organizer_permission(self, organizer, perm_name=None, request=None): def has_organizer_permission(self, organizer, perm_name=None, request=None):
""" """
@@ -543,8 +543,8 @@ class TeamAPIToken(models.Model):
:return: bool :return: bool
""" """
if isinstance(perm_name, (tuple, list)): if isinstance(perm_name, (tuple, list)):
return organizer == self.team.organizer and any(self.team.has_permission(p) for p in perm_name) return organizer == self.team.organizer and any(self.team.has_organizer_permission(p) for p in perm_name)
return organizer == self.team.organizer and (not perm_name or self.team.has_permission(perm_name)) return organizer == self.team.organizer and (not perm_name or self.team.has_organizer_permission(perm_name))
def get_events_with_any_permission(self): def get_events_with_any_permission(self):
""" """

View File

@@ -0,0 +1,111 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
import logging
from collections import OrderedDict, namedtuple
from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from pretix.base.signals import (
register_event_permissions, register_organizer_permissions,
)
logger = logging.getLogger(__name__)
_ALL_EVENT_PERMISSIONS = None
_ALL_ORGANIZER_PERMISSIONS = None
Permission = namedtuple('Permission', ('name', 'label', 'plugin_name', 'help_text'))
def get_all_event_permissions():
global _ALL_EVENT_PERMISSIONS
if _ALL_EVENT_PERMISSIONS:
return _ALL_EVENT_PERMISSIONS
types = OrderedDict()
for recv, ret in register_event_permissions.send(None):
if isinstance(ret, (list, tuple)):
for r in ret:
types[r.name] = r
else:
types[ret.name] = ret
_ALL_EVENT_PERMISSIONS = types
return types
def get_all_organizer_permissions():
global _ALL_ORGANIZER_PERMISSIONS
if _ALL_ORGANIZER_PERMISSIONS:
return _ALL_ORGANIZER_PERMISSIONS
types = OrderedDict()
for recv, ret in register_organizer_permissions.send(None):
if isinstance(ret, (list, tuple)):
for r in ret:
types[r.name] = r
else:
types[ret.name] = ret
_ALL_ORGANIZER_PERMISSIONS = types
return types
@receiver(register_event_permissions, dispatch_uid="base_register_default_event_permissions")
def register_default_event_permissions(sender, **kwargs):
return [
Permission("event.settings.general:write", _("Change general settings"), None, None),
Permission("event.settings.payment:write", _("Change payment settings"), None, None),
Permission("event.settings.plugins:write", _("Change plugin settings"), None, None),
Permission("event.settings.email.sender:write", _("Change email sending settings"), None, None),
Permission("event.settings.tax:write", _("Change tax rules"), None, None),
Permission("event.settings.invoicing:write", _("Change invoicing settings"), None, None),
Permission("event.subevents:write", pgettext_lazy("subevent", "Change event series dates"), None, None),
Permission("event.items:write", _("Change products and quotas"), None, None), # and questions but that might change?
Permission("event.orders:read", _("View orders"), None, None),
Permission("event.orders:write", _("Change orders"), None, _("This includes the ability to cancel and refund individual orders.")),
Permission("event.orders:checkin", _("Check-in orders"), None, None),
Permission("event:cancel", pgettext_lazy("subevent", "Cancel the entire event or date"), None, None),
Permission("event.vouchers:read", _("View vouchers"), None, None),
Permission("event.vouchers:write", _("Change vouchers"), None, None),
]
@receiver(register_organizer_permissions, dispatch_uid="base_register_default_organizer_permissions")
def register_default_organizer_permissions(sender, **kwargs):
return [
Permission("organizer.events:create", _("Create events"), None, None),
Permission("organizer.settings.general:write", _("Change settings"), None, None),
Permission("organizer.teams:write", _("Change teams"), None,
_("This includes the ability to give someone (including oneself) additional permissions.")),
Permission("organizer.giftcards:read", _("View gift cards"), None, None),
Permission("organizer.giftcards:write", _("Change gift cards"), None, None),
Permission("organizer.customers:read", _("View customer accounts"), None, None),
Permission("organizer.customers:write", _("Change customer accounts"), None, None),
Permission("organizer.reusablemedia:read", _("View reusable media"), None, None),
Permission("organizer.reusablemedia:write", _("Change reusable media"), None, None),
Permission("organizer.devices:read", _("View devices"), None, None),
Permission("organizer.devices:write", _("Change devices"), None,
_("This inclues the ability to give access to events and date oneself does not have access to.")),
]

View File

@@ -561,6 +561,18 @@ however for this signal, the ``sender`` **may also be None** to allow creating t
notification settings! notification settings!
""" """
register_event_permissions = GlobalSignal()
"""
This signal is sent out to get all known permissions. Receivers should return an
instance of pretix.base.permissions.Permission or a list of such instances.
"""
register_organizer_permissions = GlobalSignal()
"""
This signal is sent out to get all known permissions. Receivers should return an
instance of pretix.base.permissions.Permission or a list of such instances.
"""
notification = EventPluginSignal() notification = EventPluginSignal()
""" """
Arguments: ``logentry_id``, ``notification_type`` Arguments: ``logentry_id``, ``notification_type``

View File

@@ -308,13 +308,9 @@ class TeamForm(forms.ModelForm):
class Meta: class Meta:
model = Team model = Team
fields = ['name', 'require_2fa', 'all_events', 'limit_events', 'can_create_events', fields = ['name', 'require_2fa', 'all_events', 'limit_events',
'can_change_teams', 'can_change_organizer_settings', 'all_event_permissions', 'limit_event_permissions',
'can_manage_gift_cards', 'can_manage_customers', 'all_organizer_permissions', 'limit_organizer_permissions']
'can_manage_reusable_media',
'can_change_event_settings', 'can_change_items',
'can_view_orders', 'can_change_orders', 'can_checkin_orders',
'can_view_vouchers', 'can_change_vouchers']
widgets = { widgets = {
'limit_events': forms.CheckboxSelectMultiple(attrs={ 'limit_events': forms.CheckboxSelectMultiple(attrs={
'data-inverse-dependency': '#id_all_events', 'data-inverse-dependency': '#id_all_events',

View File

@@ -635,7 +635,7 @@ def user_index(request):
ctx = { ctx = {
'widgets': rearrange(widgets), 'widgets': rearrange(widgets),
'can_create_event': request.user.teams.filter(can_create_events=True).exists(), 'can_create_event': request.user.teams.with_organizer_permission("organizer:events.create").exists(),
'upcoming': widgets_for_event_qs( 'upcoming': widgets_for_event_qs(
request, request,
annotated_event_query(request, lazy=True).filter( annotated_event_query(request, lazy=True).filter(

View File

@@ -0,0 +1,89 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
import warnings
OLD_TO_NEW_EVENT_MIGRATION = {
"can_change_event_settings": [
"event.settings.general:write",
"event.settings.payment:write",
"event.settings.plugins:write",
"event.settings.email.sender:write",
"event.settings.tax:write"
"event.settings.invoicing:write",
"event.subevents:write",
],
"can_change_items": ["event.items:write"],
"can_view_orders": ["event.orders:read"],
"can_change_orders": ["event.orders:write", "event:cancel"],
"can_checkin_orders": ["event.orders:checkin"],
"can_view_vouchers": ["event.vouchers:read"],
"can_change_vouchers": ["event.vouchers:write"],
}
OLD_TO_NEW_ORGANIZER_MIGRATION = {
"can_create_events": ["organizer.events:create"],
"can_change_organizer_settings": ["organizer.settings.general:write", "organizer.devices:read",
"organizer.devices:write"],
"can_change_teams": ["organizer.teams:write"],
"can_manage_gift_cards": ["organizer.giftcards:read", "organizer.giftcards:write"],
"can_manage_customers": ["organizer.customers:read", "organizer.customers:write"],
"can_manage_reusable_media": ["organizer.reusablemedia:read", "organizer.reusablemedia:write"],
}
OLD_TO_NEW_EVENT_COMPAT = {
"can_change_event_settings": ["event.settings.general:write",],
"can_change_items": ["event.items:write"],
"can_view_orders": ["event.orders:read"],
"can_change_orders": ["event.orders:write"],
"can_checkin_orders": ["event.orders:checkin"],
"can_view_vouchers": ["event.vouchers:read"],
"can_change_vouchers": ["event.vouchers:write"],
}
OLD_TO_NEW_ORGANIZER_COMPAT = {
"can_create_events": ["organizer.events:create"],
"can_change_organizer_settings": ["organizer.settings.general:write"],
"can_change_teams": ["organizer.teams:write"],
"can_manage_gift_cards": ["organizer.giftcards:read", "organizer.giftcards:write"],
"can_manage_customers": ["organizer.customers:read", "organizer.customers:write"],
"can_manage_reusable_media": ["organizer.reusablemedia:read", "organizer.reusablemedia:write"],
}
class LegacyPermissionProperty:
name = None
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner=None):
if instance is None:
return self
warnings.warn("Legacy permission attribute used", DeprecationWarning, stacklevel=2)
if self.name in OLD_TO_NEW_EVENT_COMPAT:
return instance.all_event_permissions or all(
kk in instance.limit_event_permissions for kk in OLD_TO_NEW_EVENT_COMPAT[self.name]
)
if self.name in OLD_TO_NEW_ORGANIZER_COMPAT:
return instance.all_organizer_permissions or all(
kk in instance.limit_organizer_permissions for kk in OLD_TO_NEW_ORGANIZER_COMPAT[self.name]
)
raise AttributeError("Unknown legacy attribute")