forked from CGM_Public/pretix_original
Add gates (groups of check-in devices) (#1825)
This commit is contained in:
@@ -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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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.'),
|
||||||
|
|||||||
@@ -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 %}
|
||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
Reference in New Issue
Block a user