From e6f731ad77fd25986eae893ab5fe11081bf8e2f8 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Sun, 1 Jan 2017 18:44:45 +0100 Subject: [PATCH] New event creation wizard --- src/pretix/base/forms/__init__.py | 15 ++- src/pretix/base/models/event.py | 4 +- src/pretix/control/forms/event.py | 79 +++++++---- .../pretixcontrol/events/create.html | 40 ------ .../pretixcontrol/events/create_base.html | 27 ++++ .../pretixcontrol/events/create_basics.html | 23 ++++ .../events/create_foundation.html | 7 + .../templates/pretixcontrol/events/start.html | 30 ----- src/pretix/control/urls.py | 3 +- src/pretix/control/views/main.py | 127 ++++++++---------- src/requirements/production.txt | 1 + src/tests/control/test_permissions.py | 9 +- 12 files changed, 182 insertions(+), 183 deletions(-) delete mode 100644 src/pretix/control/templates/pretixcontrol/events/create.html create mode 100644 src/pretix/control/templates/pretixcontrol/events/create_base.html create mode 100644 src/pretix/control/templates/pretixcontrol/events/create_basics.html create mode 100644 src/pretix/control/templates/pretixcontrol/events/create_foundation.html delete mode 100644 src/pretix/control/templates/pretixcontrol/events/start.html diff --git a/src/pretix/base/forms/__init__.py b/src/pretix/base/forms/__init__.py index 038d81cdd..6b38ddb10 100644 --- a/src/pretix/base/forms/__init__.py +++ b/src/pretix/base/forms/__init__.py @@ -23,11 +23,12 @@ class BaseI18nModelForm(BaseModelForm): """ def __init__(self, *args, **kwargs): event = kwargs.pop('event', None) + locales = kwargs.pop('locales', None) super().__init__(*args, **kwargs) - if event: + if event or locales: for k, field in self.fields.items(): if isinstance(field, I18nFormField): - field.widget.enabled_langcodes = event.settings.get('locales') + field.widget.enabled_langcodes = event.settings.get('locales') if event else locales class I18nModelForm(six.with_metaclass(ModelFormMetaclass, BaseI18nModelForm)): @@ -97,12 +98,14 @@ class SettingsForm(forms.Form): ) def __init__(self, *args, **kwargs): - self.obj = kwargs.pop('obj') + self.obj = kwargs.pop('obj', None) + self.locales = kwargs.pop('locales', None) kwargs['initial'] = self.obj.settings.freeze() super().__init__(*args, **kwargs) - for k, field in self.fields.items(): - if isinstance(field, I18nFormField): - field.widget.enabled_langcodes = self.obj.settings.get('locales') + if self.obj or self.locales: + for k, field in self.fields.items(): + if isinstance(field, I18nFormField): + field.widget.enabled_langcodes = self.obj.settings.get('locales') if self.obj else self.locales def save(self): """ diff --git a/src/pretix/base/models/event.py b/src/pretix/base/models/event.py index 8ac9c2bb3..6904a8563 100644 --- a/src/pretix/base/models/event.py +++ b/src/pretix/base/models/event.py @@ -63,7 +63,7 @@ class Event(LoggedModel): max_length=50, db_index=True, help_text=_( "Should be short, only contain lowercase letters and numbers, and must be unique among your events. " - "This is being used in addresses and bank transfer references."), + "This will be used in order codes, invoice numbers, links and bank transfer references."), validators=[ RegexValidator( regex="^[a-zA-Z0-9.-]+$", @@ -71,7 +71,7 @@ class Event(LoggedModel): ), EventSlugBlacklistValidator() ], - verbose_name=_("Slug"), + verbose_name=_("Short form"), ) live = models.BooleanField(default=False, verbose_name=_("Shop is live")) permitted = models.ManyToManyField(User, through='EventPermission', diff --git a/src/pretix/control/forms/event.py b/src/pretix/control/forms/event.py index e8a0c56bb..e24db36a5 100644 --- a/src/pretix/control/forms/event.py +++ b/src/pretix/control/forms/event.py @@ -2,19 +2,59 @@ from django import forms from django.conf import settings from django.core.exceptions import ValidationError from django.core.validators import RegexValidator +from django.utils.timezone import get_current_timezone_name from django.utils.translation import ugettext_lazy as _ from pytz import common_timezones from pretix.base.forms import I18nModelForm, SettingsForm from pretix.base.i18n import I18nFormField, I18nTextarea -from pretix.base.models import Event +from pretix.base.models import Event, Organizer from pretix.control.forms import ExtFileField -class EventCreateForm(I18nModelForm): +class EventWizardFoundationForm(forms.Form): + locales = forms.MultipleChoiceField( + choices=settings.LANGUAGES, + label=_("Use languages"), + widget=forms.CheckboxSelectMultiple, + help_text=_('Choose all languages that your event should be available in.') + ) + + def __init__(self, *args, **kwargs): + self.user = kwargs.pop('user') + super().__init__(*args, **kwargs) + self.fields['organizer'] = forms.ModelChoiceField( + label=_("Organizer"), + queryset=Organizer.objects.filter( + id__in=self.user.organizer_perms.filter(can_create_events=True).values_list('id', flat=True) + ), + widget=forms.RadioSelect, + empty_label=None, + required=True + ) + + def clean_slug(self): + slug = self.cleaned_data['slug'] + if Event.objects.filter(slug=slug, organizer=self.organizer).exists(): + raise forms.ValidationError( + self.error_messages['duplicate_slug'], + code='duplicate_slug' + ) + return slug + + +class EventWizardBasicsForm(I18nModelForm): error_messages = { 'duplicate_slug': _("You already used this slug for a different event. Please choose a new one."), } + timezone = forms.ChoiceField( + choices=((a, a) for a in common_timezones), + label=_("Default timezone"), + ) + locale = forms.ChoiceField( + choices=settings.LANGUAGES, + label=_("Default language"), + ) class Meta: model = Event @@ -36,7 +76,19 @@ class EventCreateForm(I18nModelForm): def __init__(self, *args, **kwargs): self.organizer = kwargs.pop('organizer') + self.locales = kwargs.get('locales') + kwargs.pop('user') super().__init__(*args, **kwargs) + self.initial['timezone'] = get_current_timezone_name() + self.fields['locale'].choices = [(a, b) for a, b in settings.LANGUAGES if a in self.locales] + + def clean(self): + data = super().clean() + if data['locale'] not in self.locales: + raise ValidationError({ + 'locale': _('Your default locale must also be enabled for your event (see box above).') + }) + return data def clean_slug(self): slug = self.cleaned_data['slug'] @@ -48,29 +100,6 @@ class EventCreateForm(I18nModelForm): return slug -class EventCreateSettingsForm(SettingsForm): - timezone = forms.ChoiceField( - choices=((a, a) for a in common_timezones), - label=_("Default timezone"), - ) - locales = forms.MultipleChoiceField( - choices=settings.LANGUAGES, - label=_("Available langauges"), - ) - locale = forms.ChoiceField( - choices=settings.LANGUAGES, - label=_("Default language"), - ) - - def clean(self): - data = super().clean() - if data['locale'] not in data['locales']: - raise ValidationError({ - 'locale': _('Your default locale must also be enabled for your event (see box above).') - }) - return data - - class EventUpdateForm(I18nModelForm): def clean_slug(self): return self.instance.slug diff --git a/src/pretix/control/templates/pretixcontrol/events/create.html b/src/pretix/control/templates/pretixcontrol/events/create.html deleted file mode 100644 index 64ef36a80..000000000 --- a/src/pretix/control/templates/pretixcontrol/events/create.html +++ /dev/null @@ -1,40 +0,0 @@ -{% extends "pretixcontrol/base.html" %} -{% load i18n %} -{% load bootstrap3 %} -{% block title %}{% trans "Create a new event" %}{% endblock %} -{% block content %} -

{% trans "Create a new event" %}

-
- {% csrf_token %} - {% bootstrap_form_errors form %} -
- {% trans "General information" %} - {% bootstrap_field form.name layout="horizontal" %} - {% bootstrap_field form.slug layout="horizontal" %} - {% bootstrap_field form.date_from layout="horizontal" %} - {% bootstrap_field form.date_to layout="horizontal" %} - {% bootstrap_field form.currency layout="horizontal" %} -
-
- {% trans "Display settings" %} - {% bootstrap_field sform.locales layout="horizontal" %} - {% bootstrap_field sform.locale layout="horizontal" %} - {% bootstrap_field sform.timezone layout="horizontal" %} -
-
- {% trans "Timeline" %} - {% bootstrap_field form.presale_start layout="horizontal" %} - {% bootstrap_field form.presale_end layout="horizontal" %} -
-

- {% blocktrans trimmed %} - You will be able to adjust further settings in the next step. - {% endblocktrans %} -

-
- -
-
-{% endblock %} diff --git a/src/pretix/control/templates/pretixcontrol/events/create_base.html b/src/pretix/control/templates/pretixcontrol/events/create_base.html new file mode 100644 index 000000000..cde3f3adf --- /dev/null +++ b/src/pretix/control/templates/pretixcontrol/events/create_base.html @@ -0,0 +1,27 @@ +{% extends "pretixcontrol/base.html" %} +{% load i18n %} +{% load bootstrap3 %} +{% block title %}{% trans "Create a new event" %}{% endblock %} +{% block content %} +

{% trans "Create a new event" %} {% blocktrans trimmed with step=wizard.steps.step1 %} + Step {{ step }} + {% endblocktrans %}

+
+ {% csrf_token %} + {{ wizard.management_form }} + {% bootstrap_form_errors form %} + {% block form %} + {% endblock %} +
+ {% if wizard.steps.prev %} + + {% endif %} + +
+
+{% endblock %} diff --git a/src/pretix/control/templates/pretixcontrol/events/create_basics.html b/src/pretix/control/templates/pretixcontrol/events/create_basics.html new file mode 100644 index 000000000..875d57532 --- /dev/null +++ b/src/pretix/control/templates/pretixcontrol/events/create_basics.html @@ -0,0 +1,23 @@ +{% extends "pretixcontrol/events/create_base.html" %} +{% load i18n %} +{% load bootstrap3 %} +{% block form %} +
+ {% trans "General information" %} + {% bootstrap_field form.name layout="horizontal" %} + {% bootstrap_field form.slug layout="horizontal" %} + {% bootstrap_field form.date_from layout="horizontal" %} + {% bootstrap_field form.date_to layout="horizontal" %} + {% bootstrap_field form.currency layout="horizontal" %} +
+
+ {% trans "Display settings" %} + {% bootstrap_field form.locale layout="horizontal" %} + {% bootstrap_field form.timezone layout="horizontal" %} +
+
+ {% trans "Timeline" %} + {% bootstrap_field form.presale_start layout="horizontal" %} + {% bootstrap_field form.presale_end layout="horizontal" %} +
+{% endblock %} diff --git a/src/pretix/control/templates/pretixcontrol/events/create_foundation.html b/src/pretix/control/templates/pretixcontrol/events/create_foundation.html new file mode 100644 index 000000000..2c5eafe5c --- /dev/null +++ b/src/pretix/control/templates/pretixcontrol/events/create_foundation.html @@ -0,0 +1,7 @@ +{% extends "pretixcontrol/events/create_base.html" %} +{% load i18n %} +{% load bootstrap3 %} +{% block form %} + {% bootstrap_field form.organizer layout="horizontal" %} + {% bootstrap_field form.locales layout="horizontal" %} +{% endblock %} diff --git a/src/pretix/control/templates/pretixcontrol/events/start.html b/src/pretix/control/templates/pretixcontrol/events/start.html deleted file mode 100644 index d29a95766..000000000 --- a/src/pretix/control/templates/pretixcontrol/events/start.html +++ /dev/null @@ -1,30 +0,0 @@ -{% extends "pretixcontrol/base.html" %} -{% load i18n %} -{% block title %}{% trans "Create a new event" %}{% endblock %} -{% block content %} -

{% trans "Create a new event" %}

- {% if organizers|length == 0 %} -
- {% trans "You are not permitted to create new events in the name of any organizer." %} -
- {% else %} -

{% trans "Please choose the organizer of this event from the list below." %}

- - - - - - - - {% for o in organizers %} - - - - {% endfor %} - -
{% trans "Organizer name" %}
- {{ o.name }} -
- {% include "pretixcontrol/pagination.html" %} - {% endif %} -{% endblock %} diff --git a/src/pretix/control/urls.py b/src/pretix/control/urls.py index fff0f5053..4603c4a15 100644 --- a/src/pretix/control/urls.py +++ b/src/pretix/control/urls.py @@ -33,8 +33,7 @@ urlpatterns = [ url(r'^organizers/add$', organizer.OrganizerCreate.as_view(), name='organizers.add'), url(r'^organizer/(?P[^/]+)/edit$', organizer.OrganizerUpdate.as_view(), name='organizer.edit'), url(r'^events/$', main.EventList.as_view(), name='events'), - url(r'^events/add$', main.EventCreateStart.as_view(), name='events.add'), - url(r'^event/(?P[^/]+)/add', main.EventCreate.as_view(), name='events.create'), + url(r'^events/add$', main.EventWizard.as_view(), name='events.add'), url(r'^event/(?P[^/]+)/(?P[^/]+)/', include([ url(r'^$', dashboards.event_index, name='event.index'), url(r'^live/$', event.EventLive.as_view(), name='event.live'), diff --git a/src/pretix/control/views/main.py b/src/pretix/control/views/main.py index 0c8795f3d..580f794ed 100644 --- a/src/pretix/control/views/main.py +++ b/src/pretix/control/views/main.py @@ -2,13 +2,15 @@ from django.conf import settings from django.contrib import messages from django.core.urlresolvers import reverse from django.db import transaction -from django.utils.functional import cached_property +from django.shortcuts import redirect from django.utils.translation import ugettext_lazy as _ -from django.views.generic import CreateView, ListView, TemplateView +from django.views.generic import ListView +from formtools.wizard.views import SessionWizardView -from pretix.base.models import Event, EventPermission, OrganizerPermission -from pretix.control.forms.event import EventCreateForm, EventCreateSettingsForm -from pretix.control.permissions import OrganizerPermissionRequiredMixin +from pretix.base.models import Event, EventPermission +from pretix.control.forms.event import ( + EventWizardBasicsForm, EventWizardFoundationForm, +) class EventList(ListView): @@ -25,76 +27,55 @@ class EventList(ListView): ) -class EventCreateStart(TemplateView): - template_name = 'pretixcontrol/events/start.html' +class EventWizard(SessionWizardView): + form_list = [ + ('foundation', EventWizardFoundationForm), + ('basics', EventWizardBasicsForm), + ] + templates = { + 'foundation': 'pretixcontrol/events/create_foundation.html', + 'basics': 'pretixcontrol/events/create_basics.html' + } + condition_dict = { - def get_context_data(self, **kwargs): - ctx = super().get_context_data(**kwargs) - ctx['organizers'] = [ - p.organizer for p in OrganizerPermission.objects.filter( - user=self.request.user, can_create_events=True - ).select_related("organizer") - ] - return ctx + } - -class EventCreate(OrganizerPermissionRequiredMixin, CreateView): - model = Event - form_class = EventCreateForm - template_name = 'pretixcontrol/events/create.html' - context_object_name = 'event' - permission = 'can_create_events' - - @cached_property - def sform(self): - return EventCreateSettingsForm( - obj=Event(), - prefix='settings', - data=self.request.POST if self.request.method == 'POST' else None - ) - - def post(self, request, *args, **kwargs): - form = self.get_form() - if form.is_valid() and self.sform.is_valid(): - return self.form_valid(form) - else: - return self.form_invalid(form) - - def get_context_data(self, *args, **kwargs) -> dict: - context = super().get_context_data(*args, **kwargs) - context['sform'] = self.sform - return context - - def dispatch(self, request, *args, **kwargs): - self.object = Event() - return super().dispatch(request, *args, **kwargs) - - def get_form_kwargs(self): - kwargs = super().get_form_kwargs() - kwargs['organizer'] = self.request.organizer + def get_form_kwargs(self, step=None): + kwargs = { + 'user': self.request.user + } + if step != 'foundation': + fdata = self.get_cleaned_data_for_step('foundation') + kwargs.update(fdata) return kwargs - @transaction.atomic - def form_valid(self, form): + def get_template_names(self): + return [self.templates[self.steps.current]] + + def done(self, form_list, form_dict, **kwargs): + foundation_data = self.get_cleaned_data_for_step('foundation') + basics_data = self.get_cleaned_data_for_step('basics') + + with transaction.atomic(): + event = form_dict['basics'].instance + event.organizer = foundation_data['organizer'] + event.plugins = settings.PRETIX_PLUGINS_DEFAULT + form_dict['basics'].save() + EventPermission.objects.create(event=event, user=self.request.user) + + event.settings.set('timezone', basics_data['timezone']) + event.settings.set('locale', basics_data['locale']) + event.settings.set('locales', foundation_data['locales']) + + logdata = {} + for f in form_list: + logdata.update({ + k: v for k, v in f.cleaned_data.items() + }) + event.log_action('pretix.event.settings', user=self.request.user, data=logdata) + messages.success(self.request, _('The new event has been created.')) - form.instance.organizer = self.request.organizer - ret = super().form_valid(form) - EventPermission.objects.create( - event=form.instance, user=self.request.user, - ) - self.object = form.instance - self.object.plugins = settings.PRETIX_PLUGINS_DEFAULT - self.object.save() - - self.sform.obj = form.instance - self.sform.save() - form.instance.log_action('pretix.event.settings', user=self.request.user, data={ - k: form.instance.settings.get(k) for k in self.sform.changed_data - }) - return ret - - def get_success_url(self) -> str: - return reverse('control:event.settings', kwargs={ - 'organizer': self.request.organizer.slug, - 'event': self.object.slug, - }) + return redirect(reverse('control:event.settings', kwargs={ + 'organizer': event.organizer.slug, + 'event': event.slug, + })) diff --git a/src/requirements/production.txt b/src/requirements/production.txt index 978de2829..0e43d15b3 100644 --- a/src/requirements/production.txt +++ b/src/requirements/production.txt @@ -12,6 +12,7 @@ django-libsass libsass django-otp==0.3.* python-u2flib-server==4.* +django-formtools==1.0 # celery>=3.1,<3.2 # until the following issue is fixed, we need our own celery version # https://github.com/celery/celery/pull/3199 diff --git a/src/tests/control/test_permissions.py b/src/tests/control/test_permissions.py index ff5ee8814..02bb90e0d 100644 --- a/src/tests/control/test_permissions.py +++ b/src/tests/control/test_permissions.py @@ -3,9 +3,7 @@ from datetime import timedelta import pytest from django.utils.timezone import now -from pretix.base.models import ( - Event, EventPermission, Order, Organizer, OrganizerPermission, User, -) +from pretix.base.models import Event, EventPermission, Order, Organizer, User @pytest.fixture @@ -74,7 +72,6 @@ event_urls = [ organizer_urls = [ 'organizer/abc/edit', - 'event/abc/add' ] @@ -211,8 +208,9 @@ 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", "event/dummy/add", 200), + ("can_create_events", "organizer/dummy/edit", 200), ] @@ -242,3 +240,4 @@ def test_correct_organizer_permission(perf_patch, client, env, perm, url, code): client.login(email='dummy@dummy.dummy', password='dummy') response = client.get('/control/' + url) assert response.status_code == code +"""