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

View File

@@ -15,7 +15,7 @@ from pretix.api.models import WebHook
from pretix.api.webhooks import get_all_webhook_events
from pretix.base.forms import I18nModelForm, SettingsForm
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 (
ExtFileField, FontSelect, MultipleLanguagesWidget, SplitDateTimeField,
)
@@ -175,6 +175,17 @@ class TeamForm(forms.ModelForm):
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):
def __init__(self, *args, **kwargs):
@@ -183,6 +194,7 @@ class DeviceForm(forms.ModelForm):
self.fields['limit_events'].queryset = organizer.events.all().order_by(
'-has_subevents', '-date_from'
)
self.fields['gate'].queryset = organizer.gates.all()
def clean(self):
d = super().clean()
@@ -193,7 +205,7 @@ class DeviceForm(forms.ModelForm):
class Meta:
model = Device
fields = ['name', 'all_events', 'limit_events', 'security_profile']
fields = ['name', 'all_events', 'limit_events', 'security_profile', 'gate']
widgets = {
'limit_events': forms.CheckboxSelectMultiple(attrs={
'data-inverse-dependency': '#id_all_events',

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.changed': _('The team settings have been changed.'),
'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.canceled': pgettext_lazy('subevent', 'The event date has been canceled.'),
'pretix.subevent.changed': pgettext_lazy('subevent', 'The event date has been changed.'),

View File

@@ -454,8 +454,23 @@ def get_organizer_navigation(request):
'url': reverse('control:organizer.devices', kwargs={
'organizer': request.organizer.slug
}),
'active': 'organizer.device' in url.url_name,
'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:
nav.append({

View File

@@ -302,14 +302,14 @@
{% 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>
{% 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 %}
{% 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 %}
<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 %}
<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 %}
{% endfor %}
{% endif %}

View File

@@ -14,10 +14,17 @@
{% endif %}
{% csrf_token %}
{% bootstrap_form_errors form %}
{% bootstrap_field form.name layout="control" %}
{% bootstrap_field form.all_events layout="control" %}
{% bootstrap_field form.limit_events layout="control" %}
{% bootstrap_field form.security_profile layout="control" %}
<fieldset>
<legend>{% trans "General" %}</legend>
{% bootstrap_field form.name 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">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -99,6 +99,12 @@ urlpatterns = [
name='organizer.device.revoke'),
url(r'^organizer/(?P<organizer>[^/]+)/device/(?P<device>[^/]+)/logs$', organizer.DeviceLogView.as_view(),
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>[^/]+)/team/add$', organizer.TeamCreateView.as_view(), name='organizer.team.add'),
url(r'^organizer/(?P<organizer>[^/]+)/team/(?P<team>[^/]+)/$', organizer.TeamMemberView.as_view(),

View File

@@ -28,8 +28,8 @@ from django.views.generic import (
from pretix.api.models import WebHook
from pretix.base.auth import get_auth_backends
from pretix.base.models import (
CachedFile, Device, GiftCard, LogEntry, OrderPayment, Organizer, Team,
TeamInvite, User,
CachedFile, Device, Gate, GiftCard, LogEntry, OrderPayment, Organizer,
Team, TeamInvite, User,
)
from pretix.base.models.event import Event, EventMetaProperty, EventMetaValue
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.organizer import (
DeviceForm, EventMetaPropertyForm, GiftCardCreateForm, GiftCardUpdateForm,
OrganizerDeleteForm, OrganizerForm, OrganizerSettingsForm,
OrganizerUpdateForm, TeamForm, WebHookForm,
DeviceForm, EventMetaPropertyForm, GateForm, GiftCardCreateForm,
GiftCardUpdateForm, OrganizerDeleteForm, OrganizerForm,
OrganizerSettingsForm, OrganizerUpdateForm, TeamForm, WebHookForm,
)
from pretix.control.permissions import (
AdministratorPermissionRequiredMixin, OrganizerPermissionRequiredMixin,
@@ -1265,3 +1265,105 @@ class ExportView(OrganizerPermissionRequiredMixin, ExportMixin, TemplateView):
ctx = super().get_context_data(**kwargs)
ctx['exporters'] = self.exporters
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)