From dddf91d3bf7d454d69893fa6ea7ab229bf6e2a50 Mon Sep 17 00:00:00 2001 From: Maico Timmerman Date: Thu, 5 Dec 2019 12:20:40 +0100 Subject: [PATCH] Feature/optional team provisioning (#1487) * Event creation: Add option to select existing team instead of provisioning. * Event creation: disallow event team provisioning on organizer level. * Event team provisioning: update default setting location and display strings. * Update src/pretix/control/views/main.py Co-Authored-By: Raphael Michel --- src/pretix/base/settings.py | 4 ++ src/pretix/control/forms/event.py | 30 ++++++++- src/pretix/control/forms/organizer.py | 9 +++ .../pretixcontrol/events/create_basics.html | 7 ++ .../pretixcontrol/organizers/edit.html | 1 + src/pretix/control/views/main.py | 26 ++++---- src/tests/control/test_events.py | 65 +++++++++++++++++-- src/tests/plugins/badges/test_control.py | 3 +- .../plugins/ticketoutputpdf/test_control.py | 3 +- 9 files changed, 127 insertions(+), 21 deletions(-) diff --git a/src/pretix/base/settings.py b/src/pretix/base/settings.py index ab1417abe..2786c2b93 100644 --- a/src/pretix/base/settings.py +++ b/src/pretix/base/settings.py @@ -683,6 +683,10 @@ Your {event} team""")) 'default': '', 'type': LazyI18nString }, + 'event_team_provisioning': { + 'default': 'True', + 'type': bool + }, 'update_check_ack': { 'default': 'False', 'type': bool diff --git a/src/pretix/control/forms/event.py b/src/pretix/control/forms/event.py index 1d94979e3..b2c6a4e4f 100644 --- a/src/pretix/control/forms/event.py +++ b/src/pretix/control/forms/event.py @@ -22,7 +22,7 @@ from pytz import common_timezones, timezone from pretix.base.channels import get_all_sales_channels from pretix.base.email import get_available_placeholders from pretix.base.forms import I18nModelForm, PlaceholderValidator, SettingsForm -from pretix.base.models import Event, Organizer, TaxRule +from pretix.base.models import Event, Organizer, TaxRule, Team from pretix.base.models.event import EventMetaValue, SubEvent from pretix.base.reldate import RelativeDateField, RelativeDateTimeField from pretix.base.settings import PERSON_NAME_SCHEMES, PERSON_NAME_TITLE_GROUPS @@ -101,6 +101,16 @@ class EventWizardBasicsForm(I18nModelForm): required=False ) + team = forms.ModelChoiceField( + label=_("Grant access to team"), + help_text=_("You are allowed to create events under this organizer, however you do not have permission " + "to edit all events under this organizer. Please select one of your existing teams that will" + " be granted access to this event."), + queryset=Team.objects.none(), + required=False, + empty_label=_('Create a new team for this event with me as the only member') + ) + class Meta: model = Event fields = [ @@ -133,7 +143,7 @@ class EventWizardBasicsForm(I18nModelForm): self.organizer = kwargs.pop('organizer') self.locales = kwargs.get('locales') self.has_subevents = kwargs.pop('has_subevents') - kwargs.pop('user') + self.user = kwargs.pop('user') kwargs.pop('session') super().__init__(*args, **kwargs) self.initial['timezone'] = get_current_timezone_name() @@ -147,6 +157,15 @@ class EventWizardBasicsForm(I18nModelForm): del self.fields['presale_start'] del self.fields['presale_end'] + if self.has_control_rights(self.user, self.organizer): + del self.fields['team'] + else: + self.fields['team'].queryset = self.user.teams.filter(organizer=self.organizer) + if not self.organizer.settings.get("event_team_provisioning", True, as_type=bool): + self.fields['team'].required = True + self.fields['team'].empty_label = None + self.fields['team'].initial = 0 + def clean(self): data = super().clean() if data.get('locale') not in self.locales: @@ -179,6 +198,13 @@ class EventWizardBasicsForm(I18nModelForm): ) return slug + @staticmethod + def has_control_rights(user, organizer): + return user.teams.filter( + organizer=organizer, all_events=True, can_change_event_settings=True, can_change_items=True, + can_change_orders=True, can_change_vouchers=True + ).exists() + class EventChoiceMixin: def label_from_instance(self, obj): diff --git a/src/pretix/control/forms/organizer.py b/src/pretix/control/forms/organizer.py index 88015c61f..faff0080f 100644 --- a/src/pretix/control/forms/organizer.py +++ b/src/pretix/control/forms/organizer.py @@ -211,6 +211,15 @@ class OrganizerSettingsForm(SettingsForm): widget=I18nTextarea, help_text=_('Not displayed anywhere by default, but if you want to, you can use this e.g. in ticket templates.') ) + + event_team_provisioning = forms.BooleanField( + label=_('Allow creating a new team during event creation'), + help_text=_('Users that do not have access to all events under this organizer, must select one of their teams ' + 'to have access to the created event. This setting allows users to create an event-specified team' + ' on-the-fly, even when they do not have \"Can change teams and permissions\" permission.'), + required=False, + ) + primary_color = forms.CharField( label=_("Primary color"), required=False, diff --git a/src/pretix/control/templates/pretixcontrol/events/create_basics.html b/src/pretix/control/templates/pretixcontrol/events/create_basics.html index 501c6664f..f2575d5f1 100644 --- a/src/pretix/control/templates/pretixcontrol/events/create_basics.html +++ b/src/pretix/control/templates/pretixcontrol/events/create_basics.html @@ -76,4 +76,11 @@ {% bootstrap_field form.presale_end layout="control" %} {% endif %} + + {% if form.team %} +
+ {% trans "Team" %} + {% bootstrap_field form.team layout="control" %} +
+ {% endif %} {% endblock %} diff --git a/src/pretix/control/templates/pretixcontrol/organizers/edit.html b/src/pretix/control/templates/pretixcontrol/organizers/edit.html index 08f489a73..dad07d10f 100644 --- a/src/pretix/control/templates/pretixcontrol/organizers/edit.html +++ b/src/pretix/control/templates/pretixcontrol/organizers/edit.html @@ -31,6 +31,7 @@ {% bootstrap_field form.domain layout="control" %} {% endif %} {% bootstrap_field sform.organizer_info_text layout="control" %} + {% bootstrap_field sform.event_team_provisioning layout="control" %}
{% trans "Organizer page" %} diff --git a/src/pretix/control/views/main.py b/src/pretix/control/views/main.py index b1fb43cef..4fec1538c 100644 --- a/src/pretix/control/views/main.py +++ b/src/pretix/control/views/main.py @@ -228,19 +228,19 @@ class EventWizard(SafeSessionWizardView): event.testmode = True form_dict['basics'].save() - 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) + if not EventWizardBasicsForm.has_control_rights(self.request.user, event.organizer): + if basics_data["team"] is not None: + t = basics_data["team"] + t.limit_events.add(event) + elif event.organizer.settings.event_team_provisioning: + 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) if event.has_subevents: se = event.subevents.create( diff --git a/src/tests/control/test_events.py b/src/tests/control/test_events.py index b589a30c5..65aa32055 100644 --- a/src/tests/control/test_events.py +++ b/src/tests/control/test_events.py @@ -36,10 +36,14 @@ class EventsTest(SoupTest): date_from=datetime.datetime(2014, 9, 5, tzinfo=datetime.timezone.utc), ) - 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.team1 = Team.objects.create(organizer=self.orga1, can_create_events=True, can_change_event_settings=True, + can_change_items=True) + self.team1.members.add(self.user) + self.team1.limit_events.add(self.event1) + + self.team2 = Team.objects.create(organizer=self.orga1, can_change_event_settings=True, can_change_items=True, + can_change_orders=True, can_change_vouchers=True) + self.team2.members.add(self.user) self.client.login(email='dummy@dummy.dummy', password='dummy') @@ -579,6 +583,7 @@ class EventsTest(SoupTest): 'basics-presale_start_1': '10:00:00', 'basics-presale_end_0': '2016-11-30', 'basics-presale_end_1': '18:00:00', + 'basics-team': '', }) self.post_doc('/control/events/add', { @@ -639,6 +644,7 @@ class EventsTest(SoupTest): 'basics-presale_start_1': '10:00:00', 'basics-presale_end_0': '2016-11-30', 'basics-presale_end_1': '18:00:00', + 'basics-team': '', }) self.post_doc('/control/events/add', { 'event_wizard-current_step': 'copy', @@ -772,6 +778,7 @@ class EventsTest(SoupTest): 'basics-presale_start_1': '10:00:00', 'basics-presale_end_0': '2016-11-30', 'basics-presale_end_1': '18:00:00', + 'basics-team': '', }) assert not doc.select("#id_copy-copy_from_event_1") @@ -822,6 +829,7 @@ class EventsTest(SoupTest): 'basics-presale_start_1': '', 'basics-presale_end_0': '', 'basics-presale_end_1': '', + 'basics-team': '', }) self.post_doc('/control/events/add', { 'event_wizard-current_step': 'copy', @@ -844,6 +852,55 @@ class EventsTest(SoupTest): assert ev.presale_start is None assert ev.presale_end is None + def test_create_event_existing_team(self): + self.post_doc('/control/events/add', { + 'event_wizard-current_step': 'foundation', + 'event_wizard-prefix': 'event_wizard', + 'foundation-organizer': self.orga1.pk, + 'foundation-locales': 'en' + }) + self.post_doc('/control/events/add', { + 'event_wizard-current_step': 'basics', + 'event_wizard-prefix': 'event_wizard', + 'basics-name_0': '33C3', + 'basics-slug': '33c3', + 'basics-date_from_0': '2016-12-27', + 'basics-date_from_1': '10:00:00', + 'basics-date_to_0': '', + 'basics-date_to_1': '', + 'basics-location_0': 'Hamburg', + 'basics-currency': 'EUR', + 'basics-tax_rate': '', + 'basics-locale': 'en', + 'basics-timezone': 'UTC', + 'basics-presale_start_0': '', + 'basics-presale_start_1': '', + 'basics-presale_end_0': '', + 'basics-presale_end_1': '', + 'basics-team': str(self.team2.pk), + }) + self.post_doc('/control/events/add', { + 'event_wizard-current_step': 'copy', + 'event_wizard-prefix': 'event_wizard', + 'copy-copy_from_event': '' + }) + + with scopes_disabled(): + ev = Event.objects.get(slug='33c3') + assert ev.name == LazyI18nString({'en': '33C3'}) + assert ev.settings.locales == ['en'] + assert ev.settings.locale == 'en' + assert ev.currency == 'EUR' + assert ev.settings.timezone == 'UTC' + assert ev.organizer == self.orga1 + assert ev.location == LazyI18nString({'en': 'Hamburg'}) + team = Team.objects.filter(limit_events=ev, members=self.user).first() + assert team == self.team2 + 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 + assert ev.presale_end is None + def test_create_event_missing_date_from(self): # date_from is mandatory self.post_doc('/control/events/add', { diff --git a/src/tests/plugins/badges/test_control.py b/src/tests/plugins/badges/test_control.py index edc3af5b6..f31faa1cc 100644 --- a/src/tests/plugins/badges/test_control.py +++ b/src/tests/plugins/badges/test_control.py @@ -20,7 +20,8 @@ class BadgeLayoutFormTest(SoupTest): ) self.item1 = Item.objects.create(event=self.event1, name="Standard", default_price=0, position=1) t = Team.objects.create(organizer=self.orga1, can_change_event_settings=True, can_view_orders=True, - can_change_items=True, all_events=True, can_create_events=True) + can_change_items=True, all_events=True, can_create_events=True, + can_change_orders=True, can_change_vouchers=True) t.members.add(self.user) t.limit_events.add(self.event1) self.client.login(email='dummy@dummy.dummy', password='dummy') diff --git a/src/tests/plugins/ticketoutputpdf/test_control.py b/src/tests/plugins/ticketoutputpdf/test_control.py index bd87aff67..6b9c8bf85 100644 --- a/src/tests/plugins/ticketoutputpdf/test_control.py +++ b/src/tests/plugins/ticketoutputpdf/test_control.py @@ -21,7 +21,8 @@ class TicketLayoutFormTest(SoupTest): ) self.item1 = Item.objects.create(event=self.event1, name="Standard", default_price=0, position=1) t = Team.objects.create(organizer=self.orga1, can_change_event_settings=True, can_view_orders=True, - can_change_items=True, all_events=True, can_create_events=True) + can_change_items=True, all_events=True, can_create_events=True, + can_change_vouchers=True, can_change_orders=True) t.members.add(self.user) t.limit_events.add(self.event1) self.client.login(email='dummy@dummy.dummy', password='dummy')