text: import json
  from decimal import Decimal
  
  from django import forms
  from django.conf import settings
  from django.contrib import messages
  from django.core.exceptions import PermissionDenied, ValidationError
  from django.core.files import File
  from django.db import transaction
  from django.db.models import (
      Count, DecimalField, Max, Min, Prefetch, ProtectedError, Sum,
  )
  from django.db.models.functions import Coalesce, Greatest
  from django.forms import inlineformset_factory
  from django.http import JsonResponse
  from django.shortcuts import get_object_or_404, redirect
  from django.urls import reverse
  from django.utils.functional import cached_property
  from django.utils.translation import ugettext_lazy as _
  from django.views import View
  from django.views.generic import (
      CreateView, DeleteView, DetailView, FormView, ListView, UpdateView,
  )
  
  from pretix.api.models import WebHook
  from pretix.base.auth import get_auth_backends
  from pretix.base.models import (
      Device, GiftCard, Organizer, Team, TeamInvite, User,
  )
  from pretix.base.models.event import Event, EventMetaProperty, EventMetaValue
  from pretix.base.models.giftcards import gen_giftcard_secret
  from pretix.base.models.organizer import TeamAPIToken
  from pretix.base.services.mail import SendMailException, mail
  from pretix.control.forms.filter import (
      EventFilterForm, GiftCardFilterForm, OrganizerFilterForm,
  )
  from pretix.control.forms.organizer import (
      DeviceForm, EventMetaPropertyForm, GiftCardCreateForm, OrganizerDeleteForm,
      OrganizerForm, OrganizerSettingsForm, OrganizerUpdateForm, TeamForm,
      WebHookForm,
  )
  from pretix.control.permissions import (
      AdministratorPermissionRequiredMixin, OrganizerPermissionRequiredMixin,
  )
  from pretix.control.signals import nav_organizer
  from pretix.control.views import PaginationMixin
  from pretix.helpers.dicts import merge_dicts
  from pretix.helpers.urls import build_absolute_uri
  from pretix.presale.style import regenerate_organizer_css
  
  
  class OrganizerList(PaginationMixin, ListView):
      model = Organizer
      context_object_name = 'organizers'
      template_name = 'pretixcontrol/organizers/index.html'
  
      def get_queryset(self):
          qs = Organizer.objects.all()
          if self.filter_form.is_valid():
              qs = self.filter_form.filter_qs(qs)
          if self.request.user.has_active_staff_session(self.request.session.session_key):
              return qs
          else:
              return qs.filter(pk__in=self.request.user.teams.values_list('organizer', flat=True))
  
      def get_context_data(self, **kwargs):
          ctx = super().get_context_data(**kwargs)
          ctx['filter_form'] = self.filter_form
          return ctx
  
      @cached_property
      def filter_form(self):
          return OrganizerFilterForm(data=self.request.GET, request=self.request)
  
  
  class InviteForm(forms.Form):
      user = forms.EmailField(required=False, label=_('User'))
  
  
  class TokenForm(forms.Form):
      name = forms.CharField(required=False, label=_('Token name'))
  
  
  class OrganizerDetailViewMixin:
      def get_context_data(self, **kwargs):
          ctx = super().get_context_data(**kwargs)
          ctx['nav_organizer'] = []
          ctx['organizer'] = self.request.organizer
  
          for recv, retv in nav_organizer.send(sender=self.request.organizer, request=self.request,
                                               organizer=self.request.organizer):
              ctx['nav_organizer'] += retv
          ctx['nav_organizer'].sort(key=lambda n: n['label'])
          return ctx
  
      def get_object(self, queryset=None) -> Organizer:
          return self.request.organizer
  
  
  class OrganizerDetail(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
      model = Event
      template_name = 'pretixcontrol/organizers/detail.html'
      permission = None
      context_object_name = 'events'
      paginate_by = 50
  
      @property
      def organizer(self):
          return self.request.organizer
  
      def get_queryset(self):
          qs = self.request.user.get_events_with_any_permission(self.request).select_related('organizer').prefetch_related(
              'organizer', '_settings_objects', 'organizer___settings_objects',
              'organizer__meta_properties',
              Prefetch(
                  'meta_values',
                  EventMetaValue.objects.select_related('property'),
                  to_attr='meta_values_cached'
              )
          ).filter(organizer=self.request.organizer).order_by('-date_from')
          qs = qs.annotate(
              min_from=Min('subevents__date_from'),
              max_from=Max('subevents__date_from'),
              max_to=Max('subevents__date_to'),
              max_fromto=Greatest(Max('subevents__date_to'), Max('subevents__date_from'))
          ).annotate(
              order_from=Coalesce('min_from', 'date_from'),
              order_to=Coalesce('max_fromto', 'max_to', 'max_from', 'date_to', 'date_from'),
          )
          if self.filter_form.is_valid():
              qs = self.filter_form.filter_qs(qs)
          return qs
  
      @cached_property
      def filter_form(self):
          return EventFilterForm(data=self.request.GET, request=self.request, organizer=self.organizer)
  
      def get_context_data(self, **kwargs):
          ctx = super().get_context_data(**kwargs)
          ctx['filter_form'] = self.filter_form
          ctx['meta_fields'] = [
              self.filter_form['meta_{}'.format(p.name)] for p in self.organizer.meta_properties.all()
          ]
          return ctx
  
  
  class OrganizerTeamView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, DetailView):
      model = Organizer
      template_name = 'pretixcontrol/organizers/teams.html'
      permission = 'can_change_permissions'
      context_object_name = 'organizer'
  
  
  class OrganizerSettingsFormView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, FormView):
      model = Organizer
      permission = 'can_change_organizer_settings'
  
      def get_form_kwargs(self):
          kwargs = super().get_form_kwargs()
          kwargs['obj'] = self.request.organizer
          return kwargs
  
      @transaction.atomic
      def post(self, request, *args, **kwargs):
          form = self.get_form()
          if form.is_valid():
              form.save()
              if form.has_changed():
                  self.request.organizer.log_action(
                      'pretix.organizer.settings', user=self.request.user, data={
                          k: (form.cleaned_data.get(k).name
                              if isinstance(form.cleaned_data.get(k), File)
                              else form.cleaned_data.get(k))
                          for k in form.changed_data
                      }
                  )
              messages.success(self.request, _('Your changes have been saved.'))
              return redirect(self.get_success_url())
          else:
              messages.error(self.request, _('We could not save your changes. See below for details.'))
              return self.get(request)
  
  
  class OrganizerDisplaySettings(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, View):
      permission = None
  
      def get(self, request, *wargs, **kwargs):
          return redirect(reverse('control:organizer.edit', kwargs={
              'organizer': self.request.organizer.slug,
          }) + '#tab-0-3-open')
  
  
  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
      template_name = 'pretixcontrol/organizers/edit.html'
      permission = 'can_change_organizer_settings'
      context_object_name = 'organizer'
  
      @cached_property
      def object(self) -> Organizer:
          return self.request.organizer
  
      def get_object(self, queryset=None) -> Organizer:
          return self.object
  
      @cached_property
      def sform(self):
          return OrganizerSettingsForm(
              obj=self.object,
              prefix='settings',
              data=self.request.POST if self.request.method == 'POST' else None,
              files=self.request.FILES if self.request.method == 'POST' else None
          )
  
      def get_context_data(self, *args, **kwargs) -> dict:
          context = super().get_context_data(*args, **kwargs)
          context['sform'] = self.sform
          context['formset'] = self.formset
          return context
  
      @transaction.atomic
      def form_valid(self, form):
          self.save_formset(self.object)
          self.sform.save()
          change_css = False
          if self.sform.has_changed():
              self.request.organizer.log_action(
                  'pretix.organizer.settings',
                  user=self.request.user,
                  data={
                      k: (self.sform.cleaned_data.get(k).name
                          if isinstance(self.sform.cleaned_data.get(k), File)
                          else self.sform.cleaned_data.get(k))
                      for k in self.sform.changed_data
                  }
              )
              display_properties = (
                  'primary_color', 'theme_color_success', 'theme_color_danger', 'primary_font',
                  'theme_color_background', 'theme_round_borders'
              )
              if any(p in self.sform.changed_data for p in display_properties):
                  change_css = True
          if form.has_changed():
              self.request.organizer.log_action(
                  'pretix.organizer.changed',
                  user=self.request.user,
                  data={k: form.cleaned_data.get(k) for k in form.changed_data}
              )
  
          if change_css:
              regenerate_organizer_css.apply_async(args=(self.request.organizer.pk,))
              messages.success(self.request, _('Your changes have been saved. Please note that it can '
                                               'take a short period of time until your changes become '
                                               'active.'))
          else:
              messages.success(self.request, _('Your changes have been saved.'))
          return super().form_valid(form)
  
      def get_form_kwargs(self):
          kwargs = super().get_form_kwargs()
          if self.request.user.has_active_staff_session(self.request.session.session_key):
              kwargs['domain'] = True
              kwargs['change_slug'] = True
          return kwargs
  
      def get_success_url(self) -> str:
          return reverse('control:organizer.edit', kwargs={
              'organizer': self.request.organizer.slug,
          })
  
      def post(self, request, *args, **kwargs):
          self.object = self.get_object()
          form = self.get_form()
          if form.is_valid() and self.sform.is_valid() and self.formset.is_valid():
              return self.form_valid(form)
          else:
              return self.form_invalid(form)
  
      @cached_property
      def formset(self):
          formsetclass = inlineformset_factory(
              Organizer, EventMetaProperty,
              form=EventMetaPropertyForm, can_order=False, can_delete=True, extra=0
          )
          return formsetclass(self.request.POST if self.request.method == "POST" else None,
                              instance=self.object, queryset=self.object.meta_properties.all())
  
      def save_formset(self, obj):
          for form in self.formset.initial_forms:
              if form in self.formset.deleted_forms:
                  if not form.instance.pk:
                      continue
                  form.instance.delete()
                  form.instance.pk = None
              elif form.has_changed():
                  form.save()
  
          for form in self.formset.extra_forms:
              if not form.has_changed():
                  continue
              if self.formset._should_delete_form(form):
                  continue
              form.instance.organizer = obj
              form.save()
  
  
  class OrganizerCreate(CreateView):
      model = Organizer
      form_class = OrganizerForm
      template_name = 'pretixcontrol/organizers/create.html'
      context_object_name = 'organizer'
  
      def dispatch(self, request, *args, **kwargs):
          if not request.user.has_active_staff_session(self.request.session.session_key):
              raise PermissionDenied()  # TODO
          return super().dispatch(request, *args, **kwargs)
  
      @transaction.atomic
      def form_valid(self, form):
          messages.success(self.request, _('The new organizer has been created.'))
          ret = super().form_valid(form)
          t = Team.objects.create(
              organizer=form.instance, name=_('Administrators'),
              all_events=True, can_create_events=True, can_change_teams=True, can_manage_gift_cards=True,
              can_change_organizer_settings=True, can_change_event_settings=True, can_change_items=True,
              can_view_orders=True, can_change_orders=True, can_view_vouchers=True, can_change_vouchers=True
          )
          t.members.add(self.request.user)
          return ret
  
      def get_success_url(self) -> str:
          return reverse('control:organizer', kwargs={
              'organizer': self.object.slug,
          })
  
  
  class TeamListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
      model = Team
      template_name = 'pretixcontrol/organizers/teams.html'
      permission = 'can_change_teams'
      context_object_name = 'teams'
  
      def get_queryset(self):
          return self.request.organizer.teams.annotate(
              memcount=Count('members', distinct=True),
              eventcount=Count('limit_events', distinct=True),
              invcount=Count('invites', distinct=True)
          ).all()
  
  
  class TeamCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CreateView):
      model = Team
      template_name = 'pretixcontrol/organizers/team_edit.html'
      permission = 'can_change_teams'
      form_class = TeamForm
  
      def get_form_kwargs(self):
          kwargs = super().get_form_kwargs()
          kwargs['organizer'] = self.request.organizer
          return kwargs
  
      def get_object(self, queryset=None):
          return get_object_or_404(Team, organizer=self.request.organizer, pk=self.kwargs.get('team'))
  
      def get_success_url(self):
          return reverse('control:organizer.team', kwargs={
              'organizer': self.request.organizer.slug,
              'team': self.object.pk
          })
  
      def form_valid(self, form):
          messages.success(self.request, _('The team has been created. You can now add members to the team.'))
          form.instance.organizer = self.request.organizer
          ret = super().form_valid(form)
          form.instance.members.add(self.request.user)
          form.instance.log_action('pretix.team.created', user=self.request.user, data={
              k: getattr(self.object, k) if k != 'limit_events' else [e.id for e in getattr(self.object, k).all()]
              for k in form.changed_data
          })
          return ret
  
      def form_invalid(self, form):
          messages.error(self.request, _('Your changes could not be saved.'))
          return super().form_invalid(form)
  
  
  class TeamUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, UpdateView):
      model = Team
      template_name = 'pretixcontrol/organizers/team_edit.html'
      permission = 'can_change_teams'
      context_object_name = 'team'
      form_class = TeamForm
  
      def get_form_kwargs(self):
          kwargs = super().get_form_kwargs()
          kwargs['organizer'] = self.request.organizer
          return kwargs
  
      def get_object(self, queryset=None):
          return get_object_or_404(Team, organizer=self.request.organizer, pk=self.kwargs.get('team'))
  
      def get_success_url(self):
          return reverse('control:organizer.team', kwargs={
              'organizer': self.request.organizer.slug,
              'team': self.object.pk
          })
  
      def form_valid(self, form):
          if form.has_changed():
              self.object.log_action('pretix.team.changed', user=self.request.user, data={
                  k: getattr(self.object, k) if k != 'limit_events' else [e.id for e in getattr(self.object, k).all()]
                  for k in form.changed_data
              })
          messages.success(self.request, _('Your changes have been saved.'))
          return super().form_valid(form)
  
      def form_invalid(self, form):
          messages.error(self.request, _('Your changes could not be saved.'))
          return super().form_invalid(form)
  
  
  class TeamDeleteView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, DeleteView):
      model = Team
      template_name = 'pretixcontrol/organizers/team_delete.html'
      permission = 'can_change_teams'
      context_object_name = 'team'
  
      def get_object(self, queryset=None):
          return get_object_or_404(Team, organizer=self.request.organizer, pk=self.kwargs.get('team'))
  
      def get_success_url(self):
          return reverse('control:organizer.teams', kwargs={
              'organizer': self.request.organizer.slug,
          })
  
      def get_context_data(self, *args, **kwargs) -> dict:
          context = super().get_context_data(*args, **kwargs)
          context['possible'] = self.is_allowed()
          return context
  
      def is_allowed(self) -> bool:
          return self.request.organizer.teams.exclude(pk=self.kwargs.get('team')).filter(
              can_change_teams=True, members__isnull=False
          ).exists()
  
      @transaction.atomic
      def delete(self, request, *args, **kwargs):
          success_url = self.get_success_url()
          self.object = self.get_object()
          if self.is_allowed():
              self.object.log_action('pretix.team.deleted', user=self.request.user)
              self.object.delete()
              messages.success(request, _('The selected team has been deleted.'))
              return redirect(success_url)
          else:
              messages.error(request, _('The selected team cannot be deleted.'))
              return redirect(success_url)
  
  
  class TeamMemberView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, DetailView):
      template_name = 'pretixcontrol/organizers/team_members.html'
      context_object_name = 'team'
      permission = 'can_change_teams'
      model = Team
  
      def get_object(self, queryset=None):
          return get_object_or_404(Team, organizer=self.request.organizer, pk=self.kwargs.get('team'))
  
      @cached_property
      def add_form(self):
          return InviteForm(data=(self.request.POST
                                  if self.request.method == "POST" and "user" in self.request.POST else None))
  
      @cached_property
      def add_token_form(self):
          return TokenForm(data=(self.request.POST
                                 if self.request.method == "POST" and "name" in self.request.POST else None))
  
      def get_context_data(self, **kwargs):
          ctx = super().get_context_data(**kwargs)
          ctx['add_form'] = self.add_form
          ctx['add_token_form'] = self.add_token_form
          return ctx
  
      def _send_invite(self, instance):
          try:
              mail(
                  instance.email,
                  _('pretix account invitation'),
                  'pretixcontrol/email/invitation.txt',
                  {
                      'user': self,
                      'organizer': self.request.organizer.name,
                      'team': instance.team.name,
                      'url': build_absolute_uri('control:auth.invite', kwargs={
                          'token': instance.token
                      })
                  },
                  event=None,
                  locale=self.request.LANGUAGE_CODE
              )
          except SendMailException:
              pass  # Already logged
  
      @transaction.atomic
      def post(self, request, *args, **kwargs):
          self.object = self.get_object()
  
          if 'remove-member' in request.POST:
              try:
                  user = User.objects.get(pk=request.POST.get('remove-member'))
              except (User.DoesNotExist, ValueError):
                  pass
              else:
                  other_admin_teams = self.request.organizer.teams.exclude(pk=self.object.pk).filter(
                      can_change_teams=True, members__isnull=False
                  ).exists()
                  if not other_admin_teams and self.object.can_change_teams and self.object.members.count() == 1:
                      messages.error(self.request, _('You cannot remove the last member from this team as no one would '
                                                     'be left with the permission to change teams.'))
                      return redirect(self.get_success_url())
                  else:
                      self.object.members.remove(user)
                      self.object.log_action(
                          'pretix.team.member.removed', user=self.request.user, data={
                              'email': user.email,
                              'user': user.pk
                          }
                      )
                      messages.success(self.request, _('The member has been removed from the team.'))
                      return redirect(self.get_success_url())
  
          elif 'remove-invite' in request.POST:
              try:
                  invite = self.object.invites.get(pk=request.POST.get('remove-invite'))
              except (TeamInvite.DoesNotExist, ValueError):
                  messages.error(self.request, _('Invalid invite selected.'))
                  return redirect(self.get_success_url())
              else:
                  invite.delete()
                  self.object.log_action(
                      'pretix.team.invite.deleted', user=self.request.user, data={
                          'email': invite.email
                      }
                  )
                  messages.success(self.request, _('The invite has been revoked.'))
                  return redirect(self.get_success_url())
  
          elif 'resend-invite' in request.POST:
              try:
                  invite = self.object.invites.get(pk=request.POST.get('resend-invite'))
              except (TeamInvite.DoesNotExist, ValueError):
                  messages.error(self.request, _('Invalid invite selected.'))
                  return redirect(self.get_success_url())
              else:
                  self._send_invite(invite)
                  self.object.log_action(
                      'pretix.team.invite.resent', user=self.request.user, data={
                          'email': invite.email
                      }
                  )
                  messages.success(self.request, _('The invite has been resent.'))
                  return redirect(self.get_success_url())
  
          elif 'remove-token' in request.POST:
              try:
                  token = self.object.tokens.get(pk=request.POST.get('remove-token'))
              except (TeamAPIToken.DoesNotExist, ValueError):
                  messages.error(self.request, _('Invalid token selected.'))
                  return redirect(self.get_success_url())
              else:
                  token.active = False
                  token.save()
                  self.object.log_action(
                      'pretix.team.token.deleted', user=self.request.user, data={
                          'name': token.name
                      }
                  )
                  messages.success(self.request, _('The token has been revoked.'))
                  return redirect(self.get_success_url())
  
          elif "user" in self.request.POST and self.add_form.is_valid() and self.add_form.has_changed():
  
              try:
                  user = User.objects.get(email__iexact=self.add_form.cleaned_data['user'])
              except User.DoesNotExist:
                  if self.object.invites.filter(email__iexact=self.add_form.cleaned_data['user']).exists():
                      messages.error(self.request, _('This user already has been invited for this team.'))
                      return self.get(request, *args, **kwargs)
                  if 'native' not in get_auth_backends():
                      messages.error(self.request, _('Users need to have a pretix account before they can be invited.'))
                      return self.get(request, *args, **kwargs)
  
                  invite = self.object.invites.create(email=self.add_form.cleaned_data['user'])
                  self._send_invite(invite)
                  self.object.log_action(
                      'pretix.team.invite.created', user=self.request.user, data={
                          'email': self.add_form.cleaned_data['user']
                      }
                  )
                  messages.success(self.request, _('The new member has been invited to the team.'))
                  return redirect(self.get_success_url())
              else:
                  if self.object.members.filter(pk=user.pk).exists():
                      messages.error(self.request, _('This user already has permissions for this team.'))
                      return self.get(request, *args, **kwargs)
  
                  self.object.members.add(user)
                  self.object.log_action(
                      'pretix.team.member.added', user=self.request.user,
                      data={
                          'email': user.email,
                          'user': user.pk,
                      }
                  )
                  messages.success(self.request, _('The new member has been added to the team.'))
                  return redirect(self.get_success_url())
  
          elif "name" in self.request.POST and self.add_token_form.is_valid() and self.add_token_form.has_changed():
              token = self.object.tokens.create(name=self.add_token_form.cleaned_data['name'])
              self.object.log_action(
                  'pretix.team.token.created', user=self.request.user, data={
                      'name': self.add_token_form.cleaned_data['name'],
                      'id': token.pk
                  }
              )
              messages.success(self.request, _('A new API token has been created with the following secret: {}\n'
                                               'Please copy this secret to a safe place. You will not be able to '
                                               'view it again here.').format(token.token))
              return redirect(self.get_success_url())
          else:
              messages.error(self.request, _('Your changes could not be saved.'))
              return self.get(request, *args, **kwargs)
  
      def get_success_url(self) -> str:
          return reverse('control:organizer.team', kwargs={
              'organizer': self.request.organizer.slug,
              'team': self.object.pk
          })
  
  
  class DeviceListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
      model = Device
      template_name = 'pretixcontrol/organizers/devices.html'
      permission = 'can_change_organizer_settings'
      context_object_name = 'devices'
  
      def get_queryset(self):
          return self.request.organizer.devices.prefetch_related(
              'limit_events'
          ).order_by('revoked', '-device_id')
  
  
  class DeviceCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CreateView):
      model = Device
      template_name = 'pretixcontrol/organizers/device_edit.html'
      permission = 'can_change_organizer_settings'
      form_class = DeviceForm
  
      def get_form_kwargs(self):
          kwargs = super().get_form_kwargs()
          kwargs['organizer'] = self.request.organizer
          return kwargs
  
      def get_success_url(self):
          return reverse('control:organizer.device.connect', kwargs={
              'organizer': self.request.organizer.slug,
              'device': self.object.pk
          })
  
      def form_valid(self, form):
          form.instance.organizer = self.request.organizer
          ret = super().form_valid(form)
          form.instance.log_action('pretix.device.created', user=self.request.user, data={
              k: getattr(self.object, k) if k != 'limit_events' else [e.id for e in getattr(self.object, k).all()]
              for k in form.changed_data
          })
          return ret
  
      def form_invalid(self, form):
          messages.error(self.request, _('Your changes could not be saved.'))
          return super().form_invalid(form)
  
  
  class DeviceUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, UpdateView):
      model = Device
      template_name = 'pretixcontrol/organizers/device_edit.html'
      permission = 'can_change_organizer_settings'
      context_object_name = 'device'
      form_class = DeviceForm
  
      def get_form_kwargs(self):
          kwargs = super().get_form_kwargs()
          kwargs['organizer'] = self.request.organizer
          return kwargs
  
      def get_object(self, queryset=None):
          return get_object_or_404(Device, organizer=self.request.organizer, pk=self.kwargs.get('device'))
  
      def get_success_url(self):
          return reverse('control:organizer.devices', kwargs={
              'organizer': self.request.organizer.slug,
          })
  
      def form_valid(self, form):
          if form.has_changed():
              self.object.log_action('pretix.device.changed', user=self.request.user, data={
                  k: getattr(self.object, k) if k != 'limit_events' else [e.id for e in getattr(self.object, k).all()]
                  for k in form.changed_data
              })
          messages.success(self.request, _('Your changes have been saved.'))
          return super().form_valid(form)
  
      def form_invalid(self, form):
          messages.error(self.request, _('Your changes could not be saved.'))
          return super().form_invalid(form)
  
  
  class DeviceConnectView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, DetailView):
      model = Device
      template_name = 'pretixcontrol/organizers/device_connect.html'
      permission = 'can_change_organizer_settings'
      context_object_name = 'device'
  
      def get_object(self, queryset=None):
          return get_object_or_404(Device, organizer=self.request.organizer, pk=self.kwargs.get('device'))
  
      def get(self, request, *args, **kwargs):
          self.object = self.get_object()
          if 'ajax' in request.GET:
              return JsonResponse({
                  'initialized': bool(self.object.initialized)
              })
          if self.object.initialized:
              messages.success(request, _('This device has been set up successfully.'))
              return redirect(reverse('control:organizer.devices', kwargs={
                  'organizer': self.request.organizer.slug,
              }))
          return super().get(request, *args, **kwargs)
  
      def get_context_data(self, **kwargs):
          ctx = super().get_context_data(**kwargs)
          ctx['qrdata'] = json.dumps({
              'handshake_version': 1,
              'url': settings.SITE_URL,
              'token': self.object.initialization_token,
          })
          return ctx
  
  
  class DeviceRevokeView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, DetailView):
      model = Device
      template_name = 'pretixcontrol/organizers/device_revoke.html'
      permission = 'can_change_organizer_settings'
      context_object_name = 'device'
  
      def get_object(self, queryset=None):
          return get_object_or_404(Device, organizer=self.request.organizer, pk=self.kwargs.get('device'))
  
      def get(self, request, *args, **kwargs):
          self.object = self.get_object()
          if not self.object.api_token:
              messages.success(request, _('This device currently does not have access.'))
              return redirect(reverse('control:organizer.devices', kwargs={
                  'organizer': self.request.organizer.slug,
              }))
          return super().get(request, *args, **kwargs)
  
      def post(self, request, *args, **kwargs):
          self.object = self.get_object()
          self.object.revoked = True
          self.object.save()
          self.object.log_action('pretix.device.revoked', user=self.request.user)
          messages.success(request, _('Access for this device has been revoked.'))
          return redirect(reverse('control:organizer.devices', kwargs={
              'organizer': self.request.organizer.slug,
          }))
  
  
  class WebHookListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
      model = WebHook
      template_name = 'pretixcontrol/organizers/webhooks.html'
      permission = 'can_change_organizer_settings'
      context_object_name = 'webhooks'
  
      def get_queryset(self):
          return self.request.organizer.webhooks.prefetch_related('limit_events')
  
  
  class WebHookCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CreateView):
      model = WebHook
      template_name = 'pretixcontrol/organizers/webhook_edit.html'
      permission = 'can_change_organizer_settings'
      form_class = WebHookForm
  
      def get_form_kwargs(self):
          kwargs = super().get_form_kwargs()
          kwargs['organizer'] = self.request.organizer
          return kwargs
  
      def get_success_url(self):
          return reverse('control:organizer.webhooks', kwargs={
              'organizer': self.request.organizer.slug,
          })
  
      def form_valid(self, form):
          form.instance.organizer = self.request.organizer
          ret = super().form_valid(form)
          self.request.organizer.log_action('pretix.webhook.created', user=self.request.user, data=merge_dicts({
              k: form.cleaned_data[k] if k != 'limit_events' else [e.id for e in getattr(self.object, k).all()]
              for k in form.changed_data
          }, {'id': form.instance.pk}))
          new_listeners = set(form.cleaned_data['events'])
          for l in new_listeners:
              self.object.listeners.create(action_type=l)
          return ret
  
      def form_invalid(self, form):
          messages.error(self.request, _('Your changes could not be saved.'))
          return super().form_invalid(form)
  
  
  class WebHookUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, UpdateView):
      model = WebHook
      template_name = 'pretixcontrol/organizers/webhook_edit.html'
      permission = 'can_change_organizer_settings'
      context_object_name = 'webhook'
      form_class = WebHookForm
  
      def get_form_kwargs(self):
          kwargs = super().get_form_kwargs()
          kwargs['organizer'] = self.request.organizer
          return kwargs
  
      def get_object(self, queryset=None):
          return get_object_or_404(WebHook, organizer=self.request.organizer, pk=self.kwargs.get('webhook'))
  
      def get_success_url(self):
          return reverse('control:organizer.webhooks', kwargs={
              'organizer': self.request.organizer.slug,
          })
  
      def form_valid(self, form):
          if form.has_changed():
              self.request.organizer.log_action('pretix.webhook.changed', user=self.request.user, data=merge_dicts({
                  k: form.cleaned_data[k] if k != 'limit_events' else [e.id for e in getattr(self.object, k).all()]
                  for k in form.changed_data
              }, {'id': form.instance.pk}))
  
          current_listeners = set(self.object.listeners.values_list('action_type', flat=True))
          new_listeners = set(form.cleaned_data['events'])
          for l in current_listeners - new_listeners:
              self.object.listeners.filter(action_type=l).delete()
          for l in new_listeners - current_listeners:
              self.object.listeners.create(action_type=l)
  
          messages.success(self.request, _('Your changes have been saved.'))
          return super().form_valid(form)
  
      def form_invalid(self, form):
          messages.error(self.request, _('Your changes could not be saved.'))
          return super().form_invalid(form)
  
  
  class WebHookLogsView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
      model = WebHook
      template_name = 'pretixcontrol/organizers/webhook_logs.html'
      permission = 'can_change_organizer_settings'
      context_object_name = 'calls'
      paginate_by = 50
  
      def get_context_data(self, **kwargs):
          ctx = super().get_context_data(**kwargs)
          ctx['webhook'] = self.webhook
          return ctx
  
      @cached_property
      def webhook(self):
          return get_object_or_404(
              WebHook, organizer=self.request.organizer, pk=self.kwargs.get('webhook')
          )
  
      def get_queryset(self):
          return self.webhook.calls.order_by('-datetime')
  
  
  class GiftCardListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
      model = GiftCard
      template_name = 'pretixcontrol/organizers/giftcards.html'
      permission = 'can_manage_gift_cards'
      context_object_name = 'giftcards'
  
      def get_queryset(self):
          qs = self.request.organizer.issued_gift_cards.annotate(
              cached_value=Coalesce(Sum('transactions__value'), Decimal('0.00'))
          )
          if self.filter_form.is_valid():
              qs = self.filter_form.filter_qs(qs)
          return qs
  
      def post(self, request, *args, **kwargs):
          if "add" in request.POST:
              o = self.request.user.get_organizers_with_permission(
                  'can_manage_gift_cards', self.request
              ).exclude(pk=self.request.organizer.pk).filter(
                  slug=request.POST.get("add")
              ).first()
              if o:
                  self.request.organizer.gift_card_issuer_acceptance.get_or_create(
                      issuer=o
                  )
                  self.request.organizer.log_action(
                      'pretix.giftcards.acceptance.added',
                      data={'issuer': o.slug},
                      user=request.user
                  )
                  messages.success(self.request, _('The selected gift card issuer has been added.'))
          if "del" in request.POST:
              o = Organizer.objects.filter(
                  slug=request.POST.get("del")
              ).first()
              if o:
                  self.request.organizer.gift_card_issuer_acceptance.filter(
                      issuer=o
                  ).delete()
                  self.request.organizer.log_action(
                      'pretix.giftcards.acceptance.removed',
                      data={'issuer': o.slug},
                      user=request.user
                  )
                  messages.success(self.request, _('The selected gift card issuer has been removed.'))
          return redirect(reverse('control:organizer.giftcards', kwargs={'organizer': self.request.organizer.slug}))
  
      def get_context_data(self, **kwargs):
          ctx = super().get_context_data(**kwargs)
          ctx['filter_form'] = self.filter_form
          ctx['other_organizers'] = self.request.user.get_organizers_with_permission(
              'can_manage_gift_cards', self.request
          ).exclude(pk=self.request.organizer.pk)
          return ctx
  
      @cached_property
      def filter_form(self):
          return GiftCardFilterForm(data=self.request.GET, request=self.request)
  
  
  class GiftCardDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, DetailView):
      template_name = 'pretixcontrol/organizers/giftcard.html'
      permission = 'can_manage_gift_cards'
      context_object_name = 'card'
  
      def get_object(self, queryset=None) -> Organizer:
          return get_object_or_404(
              self.request.organizer.issued_gift_cards,
              pk=self.kwargs.get('giftcard')
          )
  
      @transaction.atomic()
      def post(self, request, *args, **kwargs):
          self.object = GiftCard.objects.select_for_update().get(pk=self.get_object().pk)
          if 'value' in request.POST:
              try:
                  value = DecimalField().to_python(request.POST.get('value'))
              except ValidationError:
                  messages.error(request, _('Your input was invalid, please try again.'))
              else:
                  if self.object.value + value < Decimal('0.00'):
                      messages.error(request, _('Gift cards are not allowed to have negative values.'))
                  else:
                      self.object.transactions.create(
                          value=value
                      )
                      self.object.log_action(
                          'pretix.giftcards.transaction.manual',
                          data={
                              'value': value
                          },
                          user=self.request.user,
                      )
                      messages.success(request, _('The manual transaction has been saved.'))
                      return redirect(reverse(
                          'control:organizer.giftcard',
                          kwargs={
                              'organizer': request.organizer.slug,
                              'giftcard': self.object.pk
                          }
                      ))
          return self.get(request, *args, **kwargs)
  
  
  class GiftCardCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CreateView):
      template_name = 'pretixcontrol/organizers/giftcard_create.html'
      permission = 'can_manage_gift_cards'
      form_class = GiftCardCreateForm
      success_url = 'invalid'
  
      def get_form_kwargs(self):
          kwargs = super().get_form_kwargs()
          any_event = self.request.organizer.events.first()
          kwargs['initial'] = {
              'currency': any_event.currency if any_event else settings.DEFAULT_CURRENCY,
              'secret': gen_giftcard_secret(self.request.organizer.settings.giftcard_length)
          }
          kwargs['organizer'] = self.request.organizer
          return kwargs
  
      @transaction.atomic()
      def post(self, request, *args, **kwargs):
          return super().post(request, *args, **kwargs)
  
      def form_valid(self, form):
          messages.success(self.request, _('The gift card has been created and can now be used.'))
          form.instance.issuer = self.request.organizer
          super().form_valid(form)
          form.instance.transactions.create(
              value=form.cleaned_data['value']
          )
          form.instance.log_action('pretix.giftcards.created', user=self.request.user, data={})
          if form.cleaned_data['value']:
              form.instance.log_action('pretix.giftcards.transaction.manual', user=self.request.user, data={
                  'value': form.cleaned_data['value']
              })
          return redirect(reverse(
              'control:organizer.giftcard',
              kwargs={
                  'organizer': self.request.organizer.slug,
                  'giftcard': self.object.pk
              }
          ))
  