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)