diff --git a/src/pretix/control/templates/pretixcontrol/event/settings.html b/src/pretix/control/templates/pretixcontrol/event/settings.html
index 57b4596cd3..8c9b6caf22 100644
--- a/src/pretix/control/templates/pretixcontrol/event/settings.html
+++ b/src/pretix/control/templates/pretixcontrol/event/settings.html
@@ -90,6 +90,11 @@
{% trans "Delete personal data" %}
+
+
+ {% trans "Clone event" %}
+
diff --git a/src/pretix/control/templates/pretixcontrol/events/index.html b/src/pretix/control/templates/pretixcontrol/events/index.html
index 1ce4a8c91f..915d9b6749 100644
--- a/src/pretix/control/templates/pretixcontrol/events/index.html
+++ b/src/pretix/control/templates/pretixcontrol/events/index.html
@@ -72,11 +72,13 @@
-
+ |
{% trans "Status" %}
|
+
+ |
@@ -117,7 +119,7 @@
{% endif %}
-
+ |
{% if not e.live %}
{% trans "Shop disabled" %}
{% elif e.presale_has_ended %}
@@ -128,6 +130,12 @@
{% trans "On sale" %}
{% endif %}
|
+
+
+
+
+ |
{% endfor %}
diff --git a/src/pretix/control/views/main.py b/src/pretix/control/views/main.py
index 347b283287..fd282db487 100644
--- a/src/pretix/control/views/main.py
+++ b/src/pretix/control/views/main.py
@@ -1,4 +1,5 @@
from django.conf import settings
+from django.contrib import messages
from django.db import transaction
from django.db.models import (
F, IntegerField, Max, Min, OuterRef, Prefetch, Subquery, Sum,
@@ -94,7 +95,10 @@ class EventList(PaginationMixin, ListView):
def condition_copy(wizard):
- return EventWizardCopyForm.copy_from_queryset(wizard.request.user).exists()
+ return (
+ not wizard.clone_from and
+ EventWizardCopyForm.copy_from_queryset(wizard.request.user).exists()
+ )
class EventWizard(SafeSessionWizardView):
@@ -112,6 +116,49 @@ class EventWizard(SafeSessionWizardView):
'copy': condition_copy
}
+ def get_form_initial(self, step):
+ initial = super().get_form_initial(step)
+ if self.clone_from:
+ if step == 'foundation':
+ initial['organizer'] = self.clone_from.organizer
+ initial['locales'] = self.clone_from.settings.locales
+ initial['has_subevents'] = self.clone_from.has_subevents
+ elif step == 'basics':
+ initial['name'] = self.clone_from.name
+ initial['slug'] = self.clone_from.slug + '-2'
+ initial['currency'] = self.clone_from.currency
+ initial['date_from'] = self.clone_from.date_from
+ initial['date_to'] = self.clone_from.date_to
+ initial['presale_start'] = self.clone_from.presale_start
+ initial['presale_end'] = self.clone_from.presale_end
+ initial['location'] = self.clone_from.location
+ initial['timezone'] = self.clone_from.settings.timezone
+ initial['locale'] = self.clone_from.settings.locale
+ if self.clone_from.settings.tax_rate_default:
+ initial['tax_rate'] = self.clone_from.settings.tax_rate_default.rate
+
+ return initial
+
+ def dispatch(self, request, *args, **kwargs):
+ self.clone_from = None
+ if 'clone' in self.request.GET:
+ try:
+ clone_from = Event.objects.select_related('organizer').get(pk=self.request.GET.get("clone"))
+ except Event.DoesNotExist:
+ allow = False
+ else:
+ allow = (
+ request.user.has_event_permission(clone_from.organizer, clone_from,
+ 'can_change_event_settings', request)
+ and request.user.has_event_permission(clone_from.organizer, clone_from,
+ 'can_change_items', request)
+ )
+ if not allow:
+ messages.error(self.request, _('You do not have permission to clone this event.'))
+ else:
+ self.clone_from = clone_from
+ return super().dispatch(request, *args, **kwargs)
+
def get_context_data(self, form, **kwargs):
ctx = super().get_context_data(form, **kwargs)
ctx['has_organizer'] = self.request.user.teams.filter(can_create_events=True).exists()
@@ -193,6 +240,8 @@ class EventWizard(SafeSessionWizardView):
if copy_data and copy_data['copy_from_event']:
from_event = copy_data['copy_from_event']
event.copy_data_from(from_event)
+ elif self.clone_from:
+ event.copy_data_from(self.clone_from)
elif event.has_subevents:
event.checkin_lists.create(
name=str(se),
@@ -216,7 +265,7 @@ class EventWizard(SafeSessionWizardView):
event.settings.set('locale', basics_data['locale'])
event.settings.set('locales', foundation_data['locales'])
- if (copy_data and copy_data['copy_from_event']) or event.has_subevents:
+ if (copy_data and copy_data['copy_from_event']) or self.clone_from or event.has_subevents:
return redirect(reverse('control:event.settings', kwargs={
'organizer': event.organizer.slug,
'event': event.slug,
diff --git a/src/tests/control/test_events.py b/src/tests/control/test_events.py
index 153abd55b2..29b8bc9994 100644
--- a/src/tests/control/test_events.py
+++ b/src/tests/control/test_events.py
@@ -706,6 +706,73 @@ class EventsTest(SoupTest):
assert ev.organizer == self.orga1
assert ev.location == LazyI18nString({'de': 'Hamburg', 'en': 'Hamburg'})
assert Team.objects.filter(limit_events=ev, members=self.user).exists()
+ assert ev.items.count() == 1
+
+ berlin_tz = timezone('Europe/Berlin')
+ assert ev.date_from == berlin_tz.localize(datetime.datetime(2016, 12, 27, 10, 0, 0)).astimezone(pytz.utc)
+ assert ev.date_to == berlin_tz.localize(datetime.datetime(2016, 12, 30, 19, 0, 0)).astimezone(pytz.utc)
+ assert ev.presale_start == berlin_tz.localize(datetime.datetime(2016, 11, 1, 10, 0, 0)).astimezone(pytz.utc)
+ assert ev.presale_end == berlin_tz.localize(datetime.datetime(2016, 11, 30, 18, 0, 0)).astimezone(pytz.utc)
+
+ assert ev.tax_rules.filter(rate=Decimal('19.00')).count() == 1
+
+ def test_create_event_clone_success(self):
+ tr = self.event1.tax_rules.create(
+ rate=19, name="VAT"
+ )
+ self.event1.items.create(
+ name='Early-bird ticket',
+ category=None, default_price=23, tax_rule=tr,
+ admission=True
+ )
+ self.event1.settings.tax_rate_default = tr
+ doc = self.get_doc('/control/events/add?clone=' + str(self.event1.pk))
+ tabletext = doc.select("form")[0].text
+ self.assertIn("CCC", tabletext)
+ self.assertNotIn("MRM", tabletext)
+
+ doc = self.post_doc('/control/events/add?clone=' + str(self.event1.pk), {
+ 'event_wizard-current_step': 'foundation',
+ 'event_wizard-prefix': 'event_wizard',
+ 'foundation-organizer': self.orga1.pk,
+ 'foundation-locales': ('en', 'de')
+ })
+ assert doc.select("#id_basics-date_from_0")[0]['value'] == '2013-12-26'
+
+ doc = self.post_doc('/control/events/add?clone=' + str(self.event1.pk), {
+ 'event_wizard-current_step': 'basics',
+ 'event_wizard-prefix': 'event_wizard',
+ 'basics-name_0': '33C3',
+ 'basics-name_1': '33C3',
+ 'basics-slug': '33c3',
+ 'basics-date_from_0': '2016-12-27',
+ 'basics-date_from_1': '10:00:00',
+ 'basics-date_to_0': '2016-12-30',
+ 'basics-date_to_1': '19:00:00',
+ 'basics-location_0': 'Hamburg',
+ 'basics-location_1': 'Hamburg',
+ 'basics-currency': 'EUR',
+ 'basics-tax_rate': '19.00',
+ 'basics-locale': 'en',
+ 'basics-timezone': 'Europe/Berlin',
+ 'basics-presale_start_0': '2016-11-01',
+ 'basics-presale_start_1': '10:00:00',
+ 'basics-presale_end_0': '2016-11-30',
+ 'basics-presale_end_1': '18:00:00',
+ })
+
+ assert not doc.select("#id_copy-copy_from_event_1")
+
+ ev = Event.objects.get(slug='33c3')
+ assert ev.name == LazyI18nString({'de': '33C3', 'en': '33C3'})
+ assert ev.settings.locales == ['en', 'de']
+ assert ev.settings.locale == 'en'
+ assert ev.currency == 'EUR'
+ assert ev.settings.timezone == 'Europe/Berlin'
+ assert ev.organizer == self.orga1
+ assert ev.location == LazyI18nString({'de': 'Hamburg', 'en': 'Hamburg'})
+ assert Team.objects.filter(limit_events=ev, members=self.user).exists()
+ assert ev.items.count() == 1
berlin_tz = timezone('Europe/Berlin')
assert ev.date_from == berlin_tz.localize(datetime.datetime(2016, 12, 27, 10, 0, 0)).astimezone(pytz.utc)