diff --git a/doc/user/faq.rst b/doc/user/faq.rst index 2cf2ded7f..89e0a1034 100644 --- a/doc/user/faq.rst +++ b/doc/user/faq.rst @@ -24,9 +24,11 @@ received any real orders (i.e. taken the shop public). We won't charge any fees How do I delete an event? ------------------------- -It is currently not possible to delete events, you can just disable the shop by clicking the first square on your event -dashboard. Events can't be deleted as they most likely contain information on financial transactions which legally -needs to be kept on record for multiple years in most countries. +You can find the event deletion button at the bottom of the event settings page. Note however, that it is not possible +to delete an event once any order or invoice has been created, as those likely contain information on financial +transactions which legally may not be tampered with and needs to be kept on record for multiple years in most +countries. In this case, you can just disable the shop by clicking the first square on your event +dashboard. If you are using the hosted service at pretix.eu and want to get rid of an event that you only used for testing, contact us at support@pretix.eu and we can remove it for you. diff --git a/src/pretix/base/models/event.py b/src/pretix/base/models/event.py index 3063037db..480713dc0 100644 --- a/src/pretix/base/models/event.py +++ b/src/pretix/base/models/event.py @@ -544,6 +544,9 @@ class Event(EventMixin, LoggedModel): Q(is_superuser=True) | Q(twp=True) ) + def allow_delete(self): + return not self.orders.exists() and not self.invoices.exists() + class SubEvent(EventMixin, LoggedModel): """ diff --git a/src/pretix/base/models/log.py b/src/pretix/base/models/log.py index 023cbd8b8..ec5bade41 100644 --- a/src/pretix/base/models/log.py +++ b/src/pretix/base/models/log.py @@ -41,7 +41,7 @@ class LogEntry(models.Model): datetime = models.DateTimeField(auto_now_add=True, db_index=True) user = models.ForeignKey('User', null=True, blank=True, on_delete=models.PROTECT) api_token = models.ForeignKey('TeamAPIToken', null=True, blank=True, on_delete=models.PROTECT) - event = models.ForeignKey('Event', null=True, blank=True, on_delete=models.CASCADE) + event = models.ForeignKey('Event', null=True, blank=True, on_delete=models.SET_NULL) action_type = models.CharField(max_length=255) data = models.TextField(default='{}') visible = models.BooleanField(default=True) diff --git a/src/pretix/control/forms/event.py b/src/pretix/control/forms/event.py index 8abe13f61..649677e64 100644 --- a/src/pretix/control/forms/event.py +++ b/src/pretix/control/forms/event.py @@ -1,5 +1,6 @@ from django import forms from django.conf import settings +from django.contrib.auth.hashers import check_password from django.core.exceptions import ValidationError from django.core.validators import RegexValidator from django.db.models import Q @@ -951,3 +952,43 @@ class WidgetCodeForm(forms.Form): raise ValidationError(_('The given voucher code does not exist.')) return v + + +class EventDeleteForm(forms.Form): + error_messages = { + 'pw_current_wrong': _("The password you entered was not correct."), + 'slug_wrong': _("The slug you entered was not correct."), + } + user_pw = forms.CharField( + max_length=255, + label=_("New password"), + widget=forms.PasswordInput() + ) + slug = forms.CharField( + max_length=255, + label=_("Event slug"), + ) + + def __init__(self, *args, **kwargs): + self.event = kwargs.pop('event') + self.user = kwargs.pop('user') + super().__init__(*args, **kwargs) + + def clean_user_pw(self): + user_pw = self.cleaned_data.get('user_pw') + if not check_password(user_pw, self.user.password): + raise forms.ValidationError( + self.error_messages['pw_current_wrong'], + code='pw_current_wrong', + ) + + return user_pw + + def clean_slug(self): + slug = self.cleaned_data.get('slug') + if slug != self.event.slug: + raise forms.ValidationError( + self.error_messages['slug_wrong'], + code='slug_wrong', + ) + return slug diff --git a/src/pretix/control/logdisplay.py b/src/pretix/control/logdisplay.py index 3b4bb0cb5..46ab30cc1 100644 --- a/src/pretix/control/logdisplay.py +++ b/src/pretix/control/logdisplay.py @@ -137,6 +137,7 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs): 'pretix.event.order.email.order_paid': _('An email has been sent to notify the user that payment has been received.'), 'pretix.event.order.email.order_placed': _('An email has been sent to notify the user that the order has been received and requires payment.'), 'pretix.event.order.email.resend': _('An email with a link to the order detail page has been resent to the user.'), + 'pretix.control.auth.user.created': _('The user has been created.'), 'pretix.user.settings.2fa.enabled': _('Two-factor authentication has been enabled.'), 'pretix.user.settings.2fa.disabled': _('Two-factor authentication has been disabled.'), 'pretix.user.settings.2fa.regenemergency': _('Your two-factor emergency codes have been regenerated.'), diff --git a/src/pretix/control/templates/pretixcontrol/event/delete.html b/src/pretix/control/templates/pretixcontrol/event/delete.html new file mode 100644 index 000000000..c36e79330 --- /dev/null +++ b/src/pretix/control/templates/pretixcontrol/event/delete.html @@ -0,0 +1,70 @@ +{% extends "pretixcontrol/event/base.html" %} +{% load i18n %} +{% load bootstrap3 %} +{% block content %} +
+ {% blocktrans trimmed %} + This operation will destroy your event including all configuration, products, quotas, questions, + vouchers, lists, etc. + {% endblocktrans %} +
++ {% blocktrans trimmed %} + This operation is irreversible and there is no way to bring your data back. + {% endblocktrans %} +
+ + {% else %} ++ {% trans "Your event can not be deleted as it already contains orders." %} +
++ {% blocktrans trimmed %} + pretix does not allow deleting orders once they have been placed in order to be audit-proof and + trustable by financial authorities. + {% endblocktrans %} +
+ {% if request.event.live %} ++ {% trans "You can instead take your shop offline. This will hide it from everyone except from the organizer teams you configured to have access to the event." %} +
+ + {% else %} ++ {% trans "However, since your shop is offline, it is only visible to the organizing team according to the permissions you configured." %} +
+ {% endif %} + {% endif %} +{% endblock %} diff --git a/src/pretix/control/templates/pretixcontrol/event/settings.html b/src/pretix/control/templates/pretixcontrol/event/settings.html index 9d3bc99c6..de506669d 100644 --- a/src/pretix/control/templates/pretixcontrol/event/settings.html +++ b/src/pretix/control/templates/pretixcontrol/event/settings.html @@ -78,6 +78,10 @@ + + {% trans "Delete event" %} + {% endblock %} diff --git a/src/pretix/control/urls.py b/src/pretix/control/urls.py index d099abe20..77fc34053 100644 --- a/src/pretix/control/urls.py +++ b/src/pretix/control/urls.py @@ -66,6 +66,7 @@ urlpatterns = [ url(r'^$', dashboards.event_index, name='event.index'), url(r'^live/$', event.EventLive.as_view(), name='event.live'), url(r'^logs/$', event.EventLog.as_view(), name='event.log'), + url(r'^delete/$', event.EventDelete.as_view(), name='event.delete'), url(r'^requiredactions/$', event.EventActions.as_view(), name='event.requiredactions'), url(r'^requiredactions/(?P