Allow to delete organizers

This commit is contained in:
Raphael Michel
2018-11-05 10:55:06 +01:00
parent 87f3318431
commit 0a5347c08b
8 changed files with 143 additions and 9 deletions

View File

@@ -282,10 +282,10 @@ class Event(EventMixin, LoggedModel):
if not really:
raise TypeError("Pass really=True as a parameter.")
OrderPosition.objects.all().delete(order__event=self)
OrderFee.objects.all().delete(order__event=self)
OrderPayment.objects.all().delete(order__event=self)
OrderRefund.objects.all().delete(order__event=self)
OrderPosition.objects.filter(order__event=self).delete()
OrderFee.objects.filter(order__event=self).delete()
OrderPayment.objects.filter(order__event=self).delete()
OrderRefund.objects.filter(order__event=self).delete()
self.orders.all().delete()
def save(self, *args, **kwargs):
@@ -301,7 +301,7 @@ class Event(EventMixin, LoggedModel):
return []
return self.plugins.split(",")
def get_cache(self) -> "pretix.base.cache.ObjectRelatedCache":
def get_cache(self):
"""
Returns an :py:class:`ObjectRelatedCache` object. This behaves equivalent to
Django's built-in cache backends, but puts you into an isolated environment for

View File

@@ -82,6 +82,20 @@ class Organizer(LoggedModel):
return ObjectRelatedCache(self)
def allow_delete(self):
from . import Order, Invoice
return (
not Order.objects.filter(event__organizer=self).exists() and
not Invoice.objects.filter(event__organizer=self).exists() and
not self.devices.exists()
)
def delete_sub_objects(self):
for e in self.events.all():
e.delete_sub_objects()
e.delete()
self.teams.all().delete()
def generate_invite_token():
return get_random_string(length=32, allowed_chars=string.ascii_lowercase + string.digits)

View File

@@ -31,6 +31,29 @@ class OrganizerForm(I18nModelForm):
return slug
class OrganizerDeleteForm(forms.Form):
error_messages = {
'slug_wrong': _("The slug you entered was not correct."),
}
slug = forms.CharField(
max_length=255,
label=_("Event slug"),
)
def __init__(self, *args, **kwargs):
self.organizer = kwargs.pop('organizer')
super().__init__(*args, **kwargs)
def clean_slug(self):
slug = self.cleaned_data.get('slug')
if slug != self.organizer.slug:
raise forms.ValidationError(
self.error_messages['slug_wrong'],
code='slug_wrong',
)
return slug
class OrganizerUpdateForm(OrganizerForm):
def __init__(self, *args, **kwargs):

View File

@@ -215,6 +215,7 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
'pretix.user.settings.notifications.enabled': _('Notifications have been enabled.'),
'pretix.user.settings.notifications.disabled': _('Notifications have been disabled.'),
'pretix.user.settings.notifications.changed': _('Your notification settings have been changed.'),
'pretix.organizer.deleted': _('The organizer "{name}" has been deleted.'),
'pretix.user.oauth.authorized': _('The application "{application_name}" has been authorized to access your '
'account.'),
'pretix.control.auth.user.forgot_password.mail_sent': _('Password reset mail sent.'),

View File

@@ -12,6 +12,12 @@
{% trans "Edit" %}
</a>
{% endif %}
{% if request.user.is_staff and staff_session %}
<a href="{% url "control:organizer.delete" organizer=organizer.slug %}"
class="btn btn-danger hidden-print">
<span class="fa fa-trash"></span>
</a>
{% endif %}
</h1>
<ul class="nav nav-pills hidden-print">
<li {% if "organizer" == url_name %}class="active"{% endif %}>

View File

@@ -0,0 +1,45 @@
{% extends "pretixcontrol/organizers/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block content %}
<h1>{% trans "Delete organizer" %}</h1>
{% if request.organizer.allow_delete %}
{% bootstrap_form_errors form layout="inline" %}
<p>
{% blocktrans trimmed %}
This operation will destroy this organizer including all events, configuration, products, quotas,
questions, vouchers, lists, etc.
{% endblocktrans %}
</p>
<p><strong>
{% blocktrans trimmed %}
This operation is irreversible and there is no way to bring your data back.
{% endblocktrans %}
</strong></p>
<form action="" method="post">
{% csrf_token %}
<p>
{% blocktrans trimmed with slug=request.organizer.slug %}
To confirm you really want this, please type out the organizer's short name ("{{ slug }}") here:
{% endblocktrans %}
</p>
{% bootstrap_field form.slug layout="inline" %}
<div class="form-group submit-group">
<button type="submit" class="btn btn-danger btn-save">
{% trans "Delete" %}
</button>
</div>
</form>
{% else %}
<p>
{% trans "This organizer account can not be deleted as it already contains orders, invoices, or devices." %}
</p>
<p>
{% 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 %}
</p>
{% endif %}
{% endblock %}

View File

@@ -67,6 +67,7 @@ urlpatterns = [
url(r'^organizers/select2$', typeahead.organizer_select2, name='organizers.select2'),
url(r'^organizer/(?P<organizer>[^/]+)/$', organizer.OrganizerDetail.as_view(), name='organizer'),
url(r'^organizer/(?P<organizer>[^/]+)/edit$', organizer.OrganizerUpdate.as_view(), name='organizer.edit'),
url(r'^organizer/(?P<organizer>[^/]+)/delete$', organizer.OrganizerDelete.as_view(), name='organizer.delete'),
url(r'^organizer/(?P<organizer>[^/]+)/settings/display$', organizer.OrganizerDisplaySettings.as_view(),
name='organizer.display'),
url(r'^organizer/(?P<organizer>[^/]+)/devices$', organizer.DeviceListView.as_view(), name='organizer.devices'),

View File

@@ -6,7 +6,7 @@ from django.contrib import messages
from django.core.exceptions import PermissionDenied
from django.core.files import File
from django.db import transaction
from django.db.models import Count
from django.db.models import Count, ProtectedError
from django.forms import inlineformset_factory
from django.http import JsonResponse
from django.shortcuts import get_object_or_404, redirect
@@ -23,10 +23,13 @@ from pretix.base.models.organizer import TeamAPIToken
from pretix.base.services.mail import SendMailException, mail
from pretix.control.forms.filter import OrganizerFilterForm
from pretix.control.forms.organizer import (
DeviceForm, EventMetaPropertyForm, OrganizerDisplaySettingsForm,
OrganizerForm, OrganizerSettingsForm, OrganizerUpdateForm, TeamForm,
DeviceForm, EventMetaPropertyForm, OrganizerDeleteForm,
OrganizerDisplaySettingsForm, OrganizerForm, OrganizerSettingsForm,
OrganizerUpdateForm, TeamForm,
)
from pretix.control.permissions import (
AdministratorPermissionRequiredMixin, OrganizerPermissionRequiredMixin,
)
from pretix.control.permissions import OrganizerPermissionRequiredMixin
from pretix.control.signals import nav_organizer
from pretix.control.views import PaginationMixin
from pretix.helpers.urls import build_absolute_uri
@@ -168,6 +171,47 @@ class OrganizerDisplaySettings(OrganizerSettingsFormView):
return self.get(request)
class OrganizerDelete(AdministratorPermissionRequiredMixin, FormView):
model = Organizer
template_name = 'pretixcontrol/organizers/delete.html'
context_object_name = 'organizer'
form_class = OrganizerDeleteForm
def post(self, request, *args, **kwargs):
if not self.request.organizer.allow_delete():
messages.error(self.request, _('This organizer can not be deleted.'))
return self.get(self.request, *self.args, **self.kwargs)
return super().post(request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['organizer'] = self.request.organizer
return kwargs
def form_valid(self, form):
try:
with transaction.atomic():
self.request.user.log_action(
'pretix.organizer.deleted', user=self.request.user,
data={
'organizer_id': self.request.organizer.pk,
'name': str(self.request.organizer.name),
'logentries': list(self.request.organizer.all_logentries().values_list('pk', flat=True))
}
)
self.request.organizer.delete_sub_objects()
self.request.organizer.delete()
messages.success(self.request, _('The organizer has been deleted.'))
return redirect(self.get_success_url())
except ProtectedError:
messages.error(self.request, _('The organizer could not be deleted as some constraints (e.g. data created by '
'plug-ins) do not allow it.'))
return self.get(self.request, *self.args, **self.kwargs)
def get_success_url(self) -> str:
return reverse('control:index')
class OrganizerUpdate(OrganizerPermissionRequiredMixin, UpdateView):
model = Organizer
form_class = OrganizerUpdateForm