diff --git a/src/pretix/base/models/event.py b/src/pretix/base/models/event.py index 6904a8563e..6cf56ce223 100644 --- a/src/pretix/base/models/event.py +++ b/src/pretix/base/models/event.py @@ -4,10 +4,12 @@ from datetime import date, datetime, time import pytz from django.conf import settings from django.core.exceptions import ValidationError +from django.core.files.storage import default_storage from django.core.mail import get_connection from django.core.validators import RegexValidator from django.db import models from django.template.defaultfilters import date as _date +from django.utils.crypto import get_random_string from django.utils.functional import cached_property from django.utils.timezone import make_aware, now from django.utils.translation import ugettext_lazy as _ @@ -224,6 +226,73 @@ class Event(LoggedModel): time(hour=23, minute=59, second=59) ), tz) + def copy_data_from(self, other): + from . import ItemCategory, Item, Question, Quota + self.plugins = other.plugins + self.save() + + category_map = {} + for c in ItemCategory.objects.filter(event=other): + category_map[c.pk] = c + c.pk = None + c.event = self + c.save() + + item_map = {} + variation_map = {} + for i in Item.objects.filter(event=other).prefetch_related('variations'): + vars = list(i.variations.all()) + item_map[i.pk] = i + i.pk = None + i.event = self + if i.picture: + i.picture.save(i.picture.name, i.picture) + if i.category_id: + i.category = category_map[i.category_id] + i.save() + for v in vars: + variation_map[v.pk] = v + v.pk = None + v.item = i + v.save() + + for q in Quota.objects.filter(event=other).prefetch_related('items', 'variations'): + items = list(q.items.all()) + vars = list(q.variations.all()) + q.pk = None + q.event = self + q.save() + for i in items: + q.items.add(item_map[i.pk]) + for v in vars: + q.variations.add(variation_map[v.pk]) + + for q in Question.objects.filter(event=other).prefetch_related('items', 'options'): + items = list(q.items.all()) + opts = list(q.options.all()) + q.pk = None + q.event = self + q.save() + for i in items: + q.items.add(item_map[i.pk]) + for o in opts: + o.pk = None + o.question = q + o.save() + + for s in EventSetting.objects.filter(object=other): + s.object = self + s.pk = None + if s.value.startswith('file://'): + fi = default_storage.open(s.value[7:], 'rb') + nonce = get_random_string(length=8) + fname = '%s/%s/%s.%s.%s' % ( + self.organizer.slug, self.slug, s.key, nonce, s.value.split('.')[-1] + ) + newname = default_storage.save(fname, fi) + s.value = 'file://' + newname + s.save() + class EventPermission(models.Model): """ diff --git a/src/pretix/control/forms/event.py b/src/pretix/control/forms/event.py index e24db36a56..777e9dee08 100644 --- a/src/pretix/control/forms/event.py +++ b/src/pretix/control/forms/event.py @@ -26,22 +26,13 @@ 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('id', flat=True) + id__in=self.user.organizer_perms.filter(can_create_events=True).values_list('organizer', 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 = { @@ -100,6 +91,26 @@ class EventWizardBasicsForm(I18nModelForm): return slug +class EventWizardCopyForm(forms.Form): + + def __init__(self, *args, **kwargs): + kwargs.pop('organizer') + kwargs.pop('locales') + self.user = kwargs.pop('user') + 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) + ), + widget=forms.RadioSelect, + empty_label=_('Do not copy'), + required=True + ) + + class EventUpdateForm(I18nModelForm): def clean_slug(self): return self.instance.slug diff --git a/src/pretix/control/templates/pretixcontrol/events/create_copy.html b/src/pretix/control/templates/pretixcontrol/events/create_copy.html new file mode 100644 index 0000000000..027924d1ca --- /dev/null +++ b/src/pretix/control/templates/pretixcontrol/events/create_copy.html @@ -0,0 +1,20 @@ +{% extends "pretixcontrol/events/create_base.html" %} +{% load i18n %} +{% load bootstrap3 %} +{% block form %} +

+ {% blocktrans trimmed %} + Do you want to copy over your configuration from a different event? We will copy all + products, categories, quotas, and questions as well as general event settings. + {% endblocktrans %} +

+
+ + {% blocktrans trimmed %} + Please make sure to review all settings extensively. You will probably still need to change some + settings manually, e.g. date and time settings and texts that contain the event name. + {% endblocktrans %} + +
+ {% bootstrap_field form.copy_from_event layout="horizontal" %} +{% endblock %} diff --git a/src/pretix/control/views/main.py b/src/pretix/control/views/main.py index 580f794ed8..d3cea5e390 100644 --- a/src/pretix/control/views/main.py +++ b/src/pretix/control/views/main.py @@ -9,7 +9,7 @@ from formtools.wizard.views import SessionWizardView from pretix.base.models import Event, EventPermission from pretix.control.forms.event import ( - EventWizardBasicsForm, EventWizardFoundationForm, + EventWizardBasicsForm, EventWizardCopyForm, EventWizardFoundationForm, ) @@ -27,17 +27,25 @@ class EventList(ListView): ) +def condition_copy(wizard): + return EventPermission.objects.filter( + user=wizard.request.user, can_change_settings=True, can_change_items=True + ).exists() + + class EventWizard(SessionWizardView): form_list = [ ('foundation', EventWizardFoundationForm), ('basics', EventWizardBasicsForm), + ('copy', EventWizardCopyForm), ] templates = { 'foundation': 'pretixcontrol/events/create_foundation.html', - 'basics': 'pretixcontrol/events/create_basics.html' + 'basics': 'pretixcontrol/events/create_basics.html', + 'copy': 'pretixcontrol/events/create_copy.html', } condition_dict = { - + 'copy': condition_copy } def get_form_kwargs(self, step=None): @@ -55,6 +63,7 @@ class EventWizard(SessionWizardView): 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') + copy_data = self.get_cleaned_data_for_step('copy') with transaction.atomic(): event = form_dict['basics'].instance @@ -63,10 +72,6 @@ class EventWizard(SessionWizardView): 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({ @@ -74,7 +79,16 @@ class EventWizard(SessionWizardView): }) event.log_action('pretix.event.settings', user=self.request.user, data=logdata) - messages.success(self.request, _('The new event has been created.')) + if copy_data and copy_data['copy_from_event']: + from_event = copy_data['copy_from_event'] + event.copy_data_from(from_event) + + event.settings.set('timezone', basics_data['timezone']) + event.settings.set('locale', basics_data['locale']) + event.settings.set('locales', foundation_data['locales']) + + messages.success(self.request, _('The new event has been created. You can now adjust the event settings in ' + 'detail.')) return redirect(reverse('control:event.settings', kwargs={ 'organizer': event.organizer.slug, 'event': event.slug,