Add gates (groups of check-in devices) (#1825)

This commit is contained in:
Raphael Michel
2020-10-24 12:22:02 +02:00
committed by GitHub
parent 38e067da9c
commit a0dd8f74e4
17 changed files with 352 additions and 21 deletions
+13 -2
View File
@@ -8,7 +8,7 @@ from rest_framework.views import APIView
from pretix.api.auth.device import DeviceTokenAuthentication from pretix.api.auth.device import DeviceTokenAuthentication
from pretix.base.models import Device from pretix.base.models import Device
from pretix.base.models.devices import generate_api_token from pretix.base.models.devices import Gate, generate_api_token
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -28,14 +28,25 @@ class UpdateRequestSerializer(serializers.Serializer):
software_version = serializers.CharField(max_length=190) software_version = serializers.CharField(max_length=190)
class GateSerializer(serializers.ModelSerializer):
class Meta:
model = Gate
fields = [
'id',
'name',
'identifier',
]
class DeviceSerializer(serializers.ModelSerializer): class DeviceSerializer(serializers.ModelSerializer):
organizer = serializers.SlugRelatedField(slug_field='slug', read_only=True) organizer = serializers.SlugRelatedField(slug_field='slug', read_only=True)
gate = GateSerializer(read_only=True)
class Meta: class Meta:
model = Device model = Device
fields = [ fields = [
'organizer', 'device_id', 'unique_serial', 'api_token', 'organizer', 'device_id', 'unique_serial', 'api_token',
'name', 'security_profile' 'name', 'security_profile', 'gate'
] ]
@@ -0,0 +1,33 @@
# Generated by Django 3.0.9 on 2020-10-23 14:47
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0167_checkinlist_exit_all_at'),
]
operations = [
migrations.CreateModel(
name='Gate',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
('name', models.CharField(max_length=190)),
('identifier', models.CharField(max_length=190)),
('organizer', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='gates', to='pretixbase.Organizer')),
],
),
migrations.AddField(
model_name='checkin',
name='gate',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='checkins', to='pretixbase.Gate'),
),
migrations.AddField(
model_name='device',
name='gate',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='devices', to='pretixbase.Gate'),
),
]
+3 -3
View File
@@ -2,7 +2,7 @@ from ..settings import GlobalSettingsObject_SettingsStore
from .auth import U2FDevice, User, WebAuthnDevice from .auth import U2FDevice, User, WebAuthnDevice
from .base import CachedFile, LoggedModel, cachedfile_name from .base import CachedFile, LoggedModel, cachedfile_name
from .checkin import Checkin, CheckinList from .checkin import Checkin, CheckinList
from .devices import Device from .devices import Device, Gate
from .event import ( from .event import (
Event, Event_SettingsStore, EventLock, EventMetaProperty, EventMetaValue, Event, Event_SettingsStore, EventLock, EventMetaProperty, EventMetaValue,
RequiredAction, SubEvent, SubEventMetaValue, generate_invite_token, RequiredAction, SubEvent, SubEventMetaValue, generate_invite_token,
@@ -19,8 +19,8 @@ from .notifications import NotificationSetting
from .orders import ( from .orders import (
AbstractPosition, CachedCombinedTicket, CachedTicket, CartPosition, AbstractPosition, CachedCombinedTicket, CachedTicket, CartPosition,
InvoiceAddress, Order, OrderFee, OrderPayment, OrderPosition, OrderRefund, InvoiceAddress, Order, OrderFee, OrderPayment, OrderPosition, OrderRefund,
QuestionAnswer, cachedcombinedticket_name, cachedticket_name, QuestionAnswer, RevokedTicketSecret, cachedcombinedticket_name,
generate_position_secret, generate_secret, cachedticket_name, generate_position_secret, generate_secret,
) )
from .organizer import ( from .organizer import (
Organizer, Organizer_SettingsStore, Team, TeamAPIToken, TeamInvite, Organizer, Organizer_SettingsStore, Team, TeamAPIToken, TeamInvite,
+3
View File
@@ -159,6 +159,9 @@ class Checkin(models.Model):
device = models.ForeignKey( device = models.ForeignKey(
'pretixbase.Device', related_name='checkins', on_delete=models.PROTECT, null=True, blank=True 'pretixbase.Device', related_name='checkins', on_delete=models.PROTECT, null=True, blank=True
) )
gate = models.ForeignKey(
'pretixbase.Gate', related_name='checkins', on_delete=models.SET_NULL, null=True, blank=True
)
auto_checked_in = models.BooleanField(default=False) auto_checked_in = models.BooleanField(default=False)
objects = ScopedManager(organizer='position__order__event__organizer') objects = ScopedManager(organizer='position__order__event__organizer')
+53
View File
@@ -1,5 +1,6 @@
import string import string
from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.db.models import Max from django.db.models import Max
from django.utils.crypto import get_random_string from django.utils.crypto import get_random_string
@@ -34,12 +35,64 @@ def generate_api_token():
return token return token
class Gate(LoggedModel):
organizer = models.ForeignKey(
'pretixbase.Organizer',
on_delete=models.PROTECT,
related_name='gates'
)
name = models.CharField(
verbose_name=_("Name"),
max_length=190,
)
identifier = models.CharField(
max_length=190, blank=True,
verbose_name=_("Internal identifier"),
help_text=_('You can enter any value here to make it easier to match the data with other sources. If you do '
'not input one, we will generate one automatically.')
)
class Meta:
ordering = ('name',)
def __str__(self):
return self.name
def clean_identifier(self, code):
Gate._clean_identifier(self.organizer, code, self)
@staticmethod
def _clean_identifier(organizer, code, instance=None):
qs = Gate.objects.filter(organizer=organizer, identifier__iexact=code)
if instance:
qs = qs.exclude(pk=instance.pk)
if qs.exists():
raise ValidationError(_('This identifier is already used for a different question.'))
def save(self, *args, **kwargs):
if not self.identifier:
charset = list('ABCDEFGHJKLMNPQRSTUVWXYZ3789')
while True:
code = get_random_string(length=8, allowed_chars=charset)
if not Gate.objects.filter(organizer=self.organizer, identifier=code).exists():
self.identifier = code
break
return super().save(*args, **kwargs)
class Device(LoggedModel): class Device(LoggedModel):
organizer = models.ForeignKey( organizer = models.ForeignKey(
'pretixbase.Organizer', 'pretixbase.Organizer',
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='devices' related_name='devices'
) )
gate = models.ForeignKey(
'pretixbase.Gate',
verbose_name=_('Gate'),
on_delete=models.SET_NULL,
null=True, blank=True,
related_name='devices'
)
device_id = models.PositiveIntegerField() device_id = models.PositiveIntegerField()
unique_serial = models.CharField(max_length=190, default=generate_serial, unique=True) unique_serial = models.CharField(max_length=190, default=generate_serial, unique=True)
initialization_token = models.CharField(max_length=190, default=generate_initialization_token, unique=True) initialization_token = models.CharField(max_length=190, default=generate_initialization_token, unique=True)
+1
View File
@@ -230,6 +230,7 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict,
list=clist, list=clist,
datetime=dt, datetime=dt,
device=device, device=device,
gate=device.gate if device else None,
nonce=nonce, nonce=nonce,
forced=force and not entry_allowed, forced=force and not entry_allowed,
) )
+14 -2
View File
@@ -15,7 +15,7 @@ from pretix.api.models import WebHook
from pretix.api.webhooks import get_all_webhook_events from pretix.api.webhooks import get_all_webhook_events
from pretix.base.forms import I18nModelForm, SettingsForm from pretix.base.forms import I18nModelForm, SettingsForm
from pretix.base.forms.widgets import SplitDateTimePickerWidget from pretix.base.forms.widgets import SplitDateTimePickerWidget
from pretix.base.models import Device, GiftCard, Organizer, Team from pretix.base.models import Device, Gate, GiftCard, Organizer, Team
from pretix.control.forms import ( from pretix.control.forms import (
ExtFileField, FontSelect, MultipleLanguagesWidget, SplitDateTimeField, ExtFileField, FontSelect, MultipleLanguagesWidget, SplitDateTimeField,
) )
@@ -175,6 +175,17 @@ class TeamForm(forms.ModelForm):
return data return data
class GateForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
kwargs.pop('organizer')
super().__init__(*args, **kwargs)
class Meta:
model = Gate
fields = ['name', 'identifier']
class DeviceForm(forms.ModelForm): class DeviceForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@@ -183,6 +194,7 @@ class DeviceForm(forms.ModelForm):
self.fields['limit_events'].queryset = organizer.events.all().order_by( self.fields['limit_events'].queryset = organizer.events.all().order_by(
'-has_subevents', '-date_from' '-has_subevents', '-date_from'
) )
self.fields['gate'].queryset = organizer.gates.all()
def clean(self): def clean(self):
d = super().clean() d = super().clean()
@@ -193,7 +205,7 @@ class DeviceForm(forms.ModelForm):
class Meta: class Meta:
model = Device model = Device
fields = ['name', 'all_events', 'limit_events', 'security_profile'] fields = ['name', 'all_events', 'limit_events', 'security_profile', 'gate']
widgets = { widgets = {
'limit_events': forms.CheckboxSelectMultiple(attrs={ 'limit_events': forms.CheckboxSelectMultiple(attrs={
'data-inverse-dependency': '#id_all_events', 'data-inverse-dependency': '#id_all_events',
+3
View File
@@ -412,6 +412,9 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
'pretix.team.created': _('The team has been created.'), 'pretix.team.created': _('The team has been created.'),
'pretix.team.changed': _('The team settings have been changed.'), 'pretix.team.changed': _('The team settings have been changed.'),
'pretix.team.deleted': _('The team has been deleted.'), 'pretix.team.deleted': _('The team has been deleted.'),
'pretix.gate.created': _('The gate has been created.'),
'pretix.gate.changed': _('The gate has been changed.'),
'pretix.gate.deleted': _('The gate has been deleted.'),
'pretix.subevent.deleted': pgettext_lazy('subevent', 'The event date has been deleted.'), 'pretix.subevent.deleted': pgettext_lazy('subevent', 'The event date has been deleted.'),
'pretix.subevent.canceled': pgettext_lazy('subevent', 'The event date has been canceled.'), 'pretix.subevent.canceled': pgettext_lazy('subevent', 'The event date has been canceled.'),
'pretix.subevent.changed': pgettext_lazy('subevent', 'The event date has been changed.'), 'pretix.subevent.changed': pgettext_lazy('subevent', 'The event date has been changed.'),
+16 -1
View File
@@ -454,8 +454,23 @@ def get_organizer_navigation(request):
'url': reverse('control:organizer.devices', kwargs={ 'url': reverse('control:organizer.devices', kwargs={
'organizer': request.organizer.slug 'organizer': request.organizer.slug
}), }),
'active': 'organizer.device' in url.url_name,
'icon': 'tablet', 'icon': 'tablet',
'children': [
{
'label': _('Devices'),
'url': reverse('control:organizer.devices', kwargs={
'organizer': request.organizer.slug
}),
'active': 'organizer.device' in url.url_name,
},
{
'label': _('Gates'),
'url': reverse('control:organizer.gates', kwargs={
'organizer': request.organizer.slug
}),
'active': 'organizer.gate' in url.url_name,
}
]
}) })
if 'can_manage_gift_cards' in request.orgapermset: if 'can_manage_gift_cards' in request.orgapermset:
nav.append({ nav.append({
@@ -302,14 +302,14 @@
{% if c.auto_checked_in %} {% if c.auto_checked_in %}
<span class="fa fa-fw fa-hourglass-end" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Automatically marked not present: {{ date }}{% endblocktrans %}"></span> <span class="fa fa-fw fa-hourglass-end" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Automatically marked not present: {{ date }}{% endblocktrans %}"></span>
{% else %} {% else %}
<span class="fa fa-fw fa-sign-out" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Exit scan: {{ date }}{% endblocktrans %}"></span> <span class="fa fa-fw fa-sign-out" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Exit scan: {{ date }}{% endblocktrans %}{% if c.gate %}<br>{{ c.gate }}{% endif %}"></span>
{% endif %} {% endif %}
{% elif c.forced %} {% elif c.forced %}
<span class="fa fa-fw fa-warning" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Additional entry scan: {{ date }}{% endblocktrans %}"></span> <span class="fa fa-fw fa-warning" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Additional entry scan: {{ date }}{% endblocktrans %}{% if c.gate %}<br>{{ c.gate }}{% endif %}"></span>
{% elif c.auto_checked_in %} {% elif c.auto_checked_in %}
<span class="fa fa-fw fa-magic" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Automatically checked in: {{ date }}{% endblocktrans %}"></span> <span class="fa fa-fw fa-magic" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Automatically checked in: {{ date }}{% endblocktrans %}{% if c.gate %}<br>{{ c.gate }}{% endif %}"></span>
{% else %} {% else %}
<span class="fa fa-fw fa-check" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Entry scan: {{ date }}{% endblocktrans %}"></span> <span class="fa fa-fw fa-check" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Entry scan: {{ date }}{% endblocktrans %}{% if c.gate %}<br>{{ c.gate }}{% endif %}"></span>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% endif %} {% endif %}
@@ -14,10 +14,17 @@
{% endif %} {% endif %}
{% csrf_token %} {% csrf_token %}
{% bootstrap_form_errors form %} {% bootstrap_form_errors form %}
{% bootstrap_field form.name layout="control" %} <fieldset>
{% bootstrap_field form.all_events layout="control" %} <legend>{% trans "General" %}</legend>
{% bootstrap_field form.limit_events layout="control" %} {% bootstrap_field form.name layout="control" %}
{% bootstrap_field form.security_profile layout="control" %} {% bootstrap_field form.all_events layout="control" %}
{% bootstrap_field form.limit_events layout="control" %}
</fieldset>
<fieldset>
<legend>{% trans "Advanced settings" %}</legend>
{% bootstrap_field form.security_profile layout="control" %}
{% bootstrap_field form.gate layout="control" %}
</fieldset>
<div class="form-group submit-group"> <div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save"> <button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %} {% trans "Save" %}
@@ -0,0 +1,19 @@
{% extends "pretixcontrol/organizers/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block inner %}
<h1>{% trans "Delete gate:" %} {{ gate.name }}</h1>
<form action="" method="post" class="form-horizontal">
{% csrf_token %}
<p>{% blocktrans %}Are you sure you want to delete the gate?{% endblocktrans %}
</p>
<div class="form-group submit-group">
<a href="{% url "control:organizer.teams" organizer=request.organizer.slug %}" class="btn btn-default btn-cancel">
{% trans "Cancel" %}
</a>
<button type="submit" class="btn btn-danger btn-save">
{% trans "Delete" %}
</button>
</div>
</form>
{% endblock %}
@@ -0,0 +1,20 @@
{% extends "pretixcontrol/organizers/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block inner %}
{% if gate %}
<h1>{% trans "Gate:" %} {{ gate.name }}</h1>
{% else %}
<h1>{% trans "Create a new gate" %}</h1>
{% endif %}
<form class="form-horizontal" action="" method="post">
{% csrf_token %}
{% bootstrap_form form layout="control" %}
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %}
</button>
</div>
</form>
{% endblock %}
@@ -0,0 +1,38 @@
{% extends "pretixcontrol/organizers/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block inner %}
<h1>{% trans "Gates" %}</h1>
<p>
{% trans "The list below shows gates that youc an use to group check-in devices." %}
</p>
<a href="{% url "control:organizer.gate.add" organizer=request.organizer.slug %}" class="btn btn-default">
<span class="fa fa-plus"></span>
{% trans "Create a new gate" %}
</a>
<table class="table table-condensed table-hover">
<thead>
<tr>
<th>{% trans "Gate" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for g in gates %}
<tr>
<td><strong>
<a href="{% url "control:organizer.gate.edit" organizer=request.organizer.slug gate=g.id %}">
{{ g.name }}
</a>
</strong></td>
<td class="text-right flip">
<a href="{% url "control:organizer.gate.edit" organizer=request.organizer.slug gate=g.id %}"
class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
<a href="{% url "control:organizer.gate.delete" organizer=request.organizer.slug gate=g.id %}"
class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
+6
View File
@@ -99,6 +99,12 @@ urlpatterns = [
name='organizer.device.revoke'), name='organizer.device.revoke'),
url(r'^organizer/(?P<organizer>[^/]+)/device/(?P<device>[^/]+)/logs$', organizer.DeviceLogView.as_view(), url(r'^organizer/(?P<organizer>[^/]+)/device/(?P<device>[^/]+)/logs$', organizer.DeviceLogView.as_view(),
name='organizer.device.logs'), name='organizer.device.logs'),
url(r'^organizer/(?P<organizer>[^/]+)/gates$', organizer.GateListView.as_view(), name='organizer.gates'),
url(r'^organizer/(?P<organizer>[^/]+)/gate/add$', organizer.GateCreateView.as_view(), name='organizer.gate.add'),
url(r'^organizer/(?P<organizer>[^/]+)/gate/(?P<gate>[^/]+)/edit$', organizer.GateUpdateView.as_view(),
name='organizer.gate.edit'),
url(r'^organizer/(?P<organizer>[^/]+)/gate/(?P<gate>[^/]+)/delete$', organizer.GateDeleteView.as_view(),
name='organizer.gate.delete'),
url(r'^organizer/(?P<organizer>[^/]+)/teams$', organizer.TeamListView.as_view(), name='organizer.teams'), url(r'^organizer/(?P<organizer>[^/]+)/teams$', organizer.TeamListView.as_view(), name='organizer.teams'),
url(r'^organizer/(?P<organizer>[^/]+)/team/add$', organizer.TeamCreateView.as_view(), name='organizer.team.add'), url(r'^organizer/(?P<organizer>[^/]+)/team/add$', organizer.TeamCreateView.as_view(), name='organizer.team.add'),
url(r'^organizer/(?P<organizer>[^/]+)/team/(?P<team>[^/]+)/$', organizer.TeamMemberView.as_view(), url(r'^organizer/(?P<organizer>[^/]+)/team/(?P<team>[^/]+)/$', organizer.TeamMemberView.as_view(),
+107 -5
View File
@@ -28,8 +28,8 @@ from django.views.generic import (
from pretix.api.models import WebHook from pretix.api.models import WebHook
from pretix.base.auth import get_auth_backends from pretix.base.auth import get_auth_backends
from pretix.base.models import ( from pretix.base.models import (
CachedFile, Device, GiftCard, LogEntry, OrderPayment, Organizer, Team, CachedFile, Device, Gate, GiftCard, LogEntry, OrderPayment, Organizer,
TeamInvite, User, Team, TeamInvite, User,
) )
from pretix.base.models.event import Event, EventMetaProperty, EventMetaValue from pretix.base.models.event import Event, EventMetaProperty, EventMetaValue
from pretix.base.models.giftcards import ( from pretix.base.models.giftcards import (
@@ -46,9 +46,9 @@ from pretix.control.forms.filter import (
) )
from pretix.control.forms.orders import ExporterForm from pretix.control.forms.orders import ExporterForm
from pretix.control.forms.organizer import ( from pretix.control.forms.organizer import (
DeviceForm, EventMetaPropertyForm, GiftCardCreateForm, GiftCardUpdateForm, DeviceForm, EventMetaPropertyForm, GateForm, GiftCardCreateForm,
OrganizerDeleteForm, OrganizerForm, OrganizerSettingsForm, GiftCardUpdateForm, OrganizerDeleteForm, OrganizerForm,
OrganizerUpdateForm, TeamForm, WebHookForm, OrganizerSettingsForm, OrganizerUpdateForm, TeamForm, WebHookForm,
) )
from pretix.control.permissions import ( from pretix.control.permissions import (
AdministratorPermissionRequiredMixin, OrganizerPermissionRequiredMixin, AdministratorPermissionRequiredMixin, OrganizerPermissionRequiredMixin,
@@ -1265,3 +1265,105 @@ class ExportView(OrganizerPermissionRequiredMixin, ExportMixin, TemplateView):
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
ctx['exporters'] = self.exporters ctx['exporters'] = self.exporters
return ctx return ctx
class GateListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
model = Gate
template_name = 'pretixcontrol/organizers/gates.html'
permission = 'can_change_organizer_settings'
context_object_name = 'gates'
def get_queryset(self):
return self.request.organizer.gates.all()
class GateCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CreateView):
model = Gate
template_name = 'pretixcontrol/organizers/gate_edit.html'
permission = 'can_change_organizer_settings'
form_class = GateForm
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(Gate, organizer=self.request.organizer, pk=self.kwargs.get('gate'))
def get_success_url(self):
return reverse('control:organizer.gates', kwargs={
'organizer': self.request.organizer.slug,
})
def form_valid(self, form):
messages.success(self.request, _('The gate has been created.'))
form.instance.organizer = self.request.organizer
ret = super().form_valid(form)
form.instance.log_action('pretix.gate.created', user=self.request.user, data={
k: getattr(self.object, k) 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 GateUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, UpdateView):
model = Gate
template_name = 'pretixcontrol/organizers/gate_edit.html'
permission = 'can_change_organizer_settings'
context_object_name = 'gate'
form_class = GateForm
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(Gate, organizer=self.request.organizer, pk=self.kwargs.get('gate'))
def get_success_url(self):
return reverse('control:organizer.gate', kwargs={
'organizer': self.request.organizer.slug,
'gate': self.object.pk
})
def form_valid(self, form):
if form.has_changed():
self.object.log_action('pretix.gate.changed', user=self.request.user, data={
k: getattr(self.object, k)
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 GateDeleteView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, DeleteView):
model = Gate
template_name = 'pretixcontrol/organizers/gate_delete.html'
permission = 'can_change_organizer_settings'
context_object_name = 'gate'
def get_object(self, queryset=None):
return get_object_or_404(Gate, organizer=self.request.organizer, pk=self.kwargs.get('gate'))
def get_success_url(self):
return reverse('control:organizer.gates', kwargs={
'organizer': self.request.organizer.slug,
})
@transaction.atomic
def delete(self, request, *args, **kwargs):
success_url = self.get_success_url()
self.object = self.get_object()
self.object.log_action('pretix.gate.deleted', user=self.request.user)
self.object.delete()
messages.success(request, _('The selected gate has been deleted.'))
return redirect(success_url)
+8
View File
@@ -142,6 +142,10 @@ organizer_urls = [
'organizer/abc/device/1/edit', 'organizer/abc/device/1/edit',
'organizer/abc/device/1/connect', 'organizer/abc/device/1/connect',
'organizer/abc/device/1/revoke', 'organizer/abc/device/1/revoke',
'organizer/abc/gates',
'organizer/abc/gate/add',
'organizer/abc/gate/1/edit',
'organizer/abc/gate/1/delete',
'organizer/abc/webhooks', 'organizer/abc/webhooks',
'organizer/abc/webhook/add', 'organizer/abc/webhook/add',
'organizer/abc/webhook/1/edit', 'organizer/abc/webhook/1/edit',
@@ -416,6 +420,10 @@ organizer_permission_urls = [
("can_change_organizer_settings", "organizer/dummy/device/1/edit", 404), ("can_change_organizer_settings", "organizer/dummy/device/1/edit", 404),
("can_change_organizer_settings", "organizer/dummy/device/1/connect", 404), ("can_change_organizer_settings", "organizer/dummy/device/1/connect", 404),
("can_change_organizer_settings", "organizer/dummy/device/1/revoke", 404), ("can_change_organizer_settings", "organizer/dummy/device/1/revoke", 404),
("can_change_organizer_settings", "organizer/dummy/gates", 200),
("can_change_organizer_settings", "organizer/dummy/gate/add", 200),
("can_change_organizer_settings", "organizer/dummy/gate/1/edit", 404),
("can_change_organizer_settings", "organizer/dummy/gate/1/delete", 404),
("can_manage_gift_cards", "organizer/dummy/giftcards", 200), ("can_manage_gift_cards", "organizer/dummy/giftcards", 200),
("can_manage_gift_cards", "organizer/dummy/giftcard/add", 200), ("can_manage_gift_cards", "organizer/dummy/giftcard/add", 200),
("can_manage_gift_cards", "organizer/dummy/giftcard/1/", 404), ("can_manage_gift_cards", "organizer/dummy/giftcard/1/", 404),