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 <mail@raphaelmichel.de>
This commit is contained in:
Maico Timmerman
2019-12-05 12:20:40 +01:00
committed by Raphael Michel
co-authored by Raphael Michel
parent f88a09a78c
commit dddf91d3bf
9 changed files with 127 additions and 21 deletions
+4
View File
@@ -683,6 +683,10 @@ Your {event} team"""))
'default': '', 'default': '',
'type': LazyI18nString 'type': LazyI18nString
}, },
'event_team_provisioning': {
'default': 'True',
'type': bool
},
'update_check_ack': { 'update_check_ack': {
'default': 'False', 'default': 'False',
'type': bool 'type': bool
+28 -2
View File
@@ -22,7 +22,7 @@ from pytz import common_timezones, timezone
from pretix.base.channels import get_all_sales_channels from pretix.base.channels import get_all_sales_channels
from pretix.base.email import get_available_placeholders from pretix.base.email import get_available_placeholders
from pretix.base.forms import I18nModelForm, PlaceholderValidator, SettingsForm 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.models.event import EventMetaValue, SubEvent
from pretix.base.reldate import RelativeDateField, RelativeDateTimeField from pretix.base.reldate import RelativeDateField, RelativeDateTimeField
from pretix.base.settings import PERSON_NAME_SCHEMES, PERSON_NAME_TITLE_GROUPS from pretix.base.settings import PERSON_NAME_SCHEMES, PERSON_NAME_TITLE_GROUPS
@@ -101,6 +101,16 @@ class EventWizardBasicsForm(I18nModelForm):
required=False 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: class Meta:
model = Event model = Event
fields = [ fields = [
@@ -133,7 +143,7 @@ class EventWizardBasicsForm(I18nModelForm):
self.organizer = kwargs.pop('organizer') self.organizer = kwargs.pop('organizer')
self.locales = kwargs.get('locales') self.locales = kwargs.get('locales')
self.has_subevents = kwargs.pop('has_subevents') self.has_subevents = kwargs.pop('has_subevents')
kwargs.pop('user') self.user = kwargs.pop('user')
kwargs.pop('session') kwargs.pop('session')
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.initial['timezone'] = get_current_timezone_name() self.initial['timezone'] = get_current_timezone_name()
@@ -147,6 +157,15 @@ class EventWizardBasicsForm(I18nModelForm):
del self.fields['presale_start'] del self.fields['presale_start']
del self.fields['presale_end'] 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): def clean(self):
data = super().clean() data = super().clean()
if data.get('locale') not in self.locales: if data.get('locale') not in self.locales:
@@ -179,6 +198,13 @@ class EventWizardBasicsForm(I18nModelForm):
) )
return slug 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: class EventChoiceMixin:
def label_from_instance(self, obj): def label_from_instance(self, obj):
+9
View File
@@ -211,6 +211,15 @@ class OrganizerSettingsForm(SettingsForm):
widget=I18nTextarea, widget=I18nTextarea,
help_text=_('Not displayed anywhere by default, but if you want to, you can use this e.g. in ticket templates.') 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( primary_color = forms.CharField(
label=_("Primary color"), label=_("Primary color"),
required=False, required=False,
@@ -76,4 +76,11 @@
{% bootstrap_field form.presale_end layout="control" %} {% bootstrap_field form.presale_end layout="control" %}
</fieldset> </fieldset>
{% endif %} {% endif %}
{% if form.team %}
<fieldset>
<legend>{% trans "Team" %}</legend>
{% bootstrap_field form.team layout="control" %}
</fieldset>
{% endif %}
{% endblock %} {% endblock %}
@@ -31,6 +31,7 @@
{% bootstrap_field form.domain layout="control" %} {% bootstrap_field form.domain layout="control" %}
{% endif %} {% endif %}
{% bootstrap_field sform.organizer_info_text layout="control" %} {% bootstrap_field sform.organizer_info_text layout="control" %}
{% bootstrap_field sform.event_team_provisioning layout="control" %}
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend>{% trans "Organizer page" %}</legend> <legend>{% trans "Organizer page" %}</legend>
+13 -13
View File
@@ -228,19 +228,19 @@ class EventWizard(SafeSessionWizardView):
event.testmode = True event.testmode = True
form_dict['basics'].save() form_dict['basics'].save()
has_control_rights = self.request.user.teams.filter( if not EventWizardBasicsForm.has_control_rights(self.request.user, event.organizer):
organizer=event.organizer, all_events=True, can_change_event_settings=True, can_change_items=True, if basics_data["team"] is not None:
can_change_orders=True, can_change_vouchers=True t = basics_data["team"]
).exists() t.limit_events.add(event)
if not has_control_rights: elif event.organizer.settings.event_team_provisioning:
t = Team.objects.create( t = Team.objects.create(
organizer=event.organizer, name=_('Team {event}').format(event=event.name), organizer=event.organizer, name=_('Team {event}').format(event=event.name),
can_change_event_settings=True, can_change_items=True, can_change_event_settings=True, can_change_items=True,
can_view_orders=True, can_change_orders=True, can_view_vouchers=True, can_view_orders=True, can_change_orders=True, can_view_vouchers=True,
can_change_vouchers=True can_change_vouchers=True
) )
t.members.add(self.request.user) t.members.add(self.request.user)
t.limit_events.add(event) t.limit_events.add(event)
if event.has_subevents: if event.has_subevents:
se = event.subevents.create( se = event.subevents.create(
+61 -4
View File
@@ -36,10 +36,14 @@ class EventsTest(SoupTest):
date_from=datetime.datetime(2014, 9, 5, tzinfo=datetime.timezone.utc), 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, self.team1 = Team.objects.create(organizer=self.orga1, can_create_events=True, can_change_event_settings=True,
can_change_items=True) can_change_items=True)
t.members.add(self.user) self.team1.members.add(self.user)
t.limit_events.add(self.event1) 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') 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_start_1': '10:00:00',
'basics-presale_end_0': '2016-11-30', 'basics-presale_end_0': '2016-11-30',
'basics-presale_end_1': '18:00:00', 'basics-presale_end_1': '18:00:00',
'basics-team': '',
}) })
self.post_doc('/control/events/add', { self.post_doc('/control/events/add', {
@@ -639,6 +644,7 @@ class EventsTest(SoupTest):
'basics-presale_start_1': '10:00:00', 'basics-presale_start_1': '10:00:00',
'basics-presale_end_0': '2016-11-30', 'basics-presale_end_0': '2016-11-30',
'basics-presale_end_1': '18:00:00', 'basics-presale_end_1': '18:00:00',
'basics-team': '',
}) })
self.post_doc('/control/events/add', { self.post_doc('/control/events/add', {
'event_wizard-current_step': 'copy', 'event_wizard-current_step': 'copy',
@@ -772,6 +778,7 @@ class EventsTest(SoupTest):
'basics-presale_start_1': '10:00:00', 'basics-presale_start_1': '10:00:00',
'basics-presale_end_0': '2016-11-30', 'basics-presale_end_0': '2016-11-30',
'basics-presale_end_1': '18:00:00', 'basics-presale_end_1': '18:00:00',
'basics-team': '',
}) })
assert not doc.select("#id_copy-copy_from_event_1") assert not doc.select("#id_copy-copy_from_event_1")
@@ -822,6 +829,7 @@ class EventsTest(SoupTest):
'basics-presale_start_1': '', 'basics-presale_start_1': '',
'basics-presale_end_0': '', 'basics-presale_end_0': '',
'basics-presale_end_1': '', 'basics-presale_end_1': '',
'basics-team': '',
}) })
self.post_doc('/control/events/add', { self.post_doc('/control/events/add', {
'event_wizard-current_step': 'copy', 'event_wizard-current_step': 'copy',
@@ -844,6 +852,55 @@ class EventsTest(SoupTest):
assert ev.presale_start is None assert ev.presale_start is None
assert ev.presale_end 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): def test_create_event_missing_date_from(self):
# date_from is mandatory # date_from is mandatory
self.post_doc('/control/events/add', { self.post_doc('/control/events/add', {
+2 -1
View File
@@ -20,7 +20,8 @@ class BadgeLayoutFormTest(SoupTest):
) )
self.item1 = Item.objects.create(event=self.event1, name="Standard", default_price=0, position=1) 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, 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.members.add(self.user)
t.limit_events.add(self.event1) t.limit_events.add(self.event1)
self.client.login(email='dummy@dummy.dummy', password='dummy') self.client.login(email='dummy@dummy.dummy', password='dummy')
@@ -21,7 +21,8 @@ class TicketLayoutFormTest(SoupTest):
) )
self.item1 = Item.objects.create(event=self.event1, name="Standard", default_price=0, position=1) 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, 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.members.add(self.user)
t.limit_events.add(self.event1) t.limit_events.add(self.event1)
self.client.login(email='dummy@dummy.dummy', password='dummy') self.client.login(email='dummy@dummy.dummy', password='dummy')