diff --git a/src/pretix/control/forms/organizer.py b/src/pretix/control/forms/organizer.py index 6834ec1414..a1be50e814 100644 --- a/src/pretix/control/forms/organizer.py +++ b/src/pretix/control/forms/organizer.py @@ -6,7 +6,7 @@ from django.utils.translation import ugettext_lazy as _ from i18nfield.forms import I18nFormField, I18nTextarea from pretix.base.forms import I18nModelForm, SettingsForm -from pretix.base.models import Organizer, Team +from pretix.base.models import Device, Organizer, Team from pretix.control.forms import ExtFileField, MultipleLanguagesWidget from pretix.multidomain.models import KnownDomain from pretix.presale.style import get_fonts @@ -107,7 +107,7 @@ class TeamForm(forms.ModelForm): data = super().clean() if self.instance.pk and not data['can_change_teams']: if not self.instance.organizer.teams.exclude(pk=self.instance.pk).filter( - can_change_teams=True, members__isnull=False + can_change_teams=True, members__isnull=False ).exists(): raise ValidationError(_('The changes could not be saved because there would be no remaining team with ' 'the permission to change teams and permissions.')) @@ -115,6 +115,23 @@ class TeamForm(forms.ModelForm): return data +class DeviceForm(forms.ModelForm): + + def __init__(self, *args, **kwargs): + organizer = kwargs.pop('organizer') + super().__init__(*args, **kwargs) + self.fields['limit_events'].queryset = organizer.events.all() + + class Meta: + model = Device + fields = ['name', 'all_events', 'limit_events'] + widgets = { + 'limit_events': forms.CheckboxSelectMultiple(attrs={ + 'data-inverse-dependency': '#id_all_events' + }), + } + + class OrganizerSettingsForm(SettingsForm): organizer_info_text = I18nFormField( diff --git a/src/pretix/control/templates/pretixcontrol/organizers/base.html b/src/pretix/control/templates/pretixcontrol/organizers/base.html index ab88911115..7a67d8f70b 100644 --- a/src/pretix/control/templates/pretixcontrol/organizers/base.html +++ b/src/pretix/control/templates/pretixcontrol/organizers/base.html @@ -33,6 +33,13 @@ {% endif %} + {% if 'can_change_organizer_settings' in request.orgapermset %} +
  • + + {% trans "Devices" %} + +
  • + {% endif %} {% for nav in nav_organizer %}
  • diff --git a/src/pretix/control/templates/pretixcontrol/organizers/device_connect.html b/src/pretix/control/templates/pretixcontrol/organizers/device_connect.html new file mode 100644 index 0000000000..e7c9ef7744 --- /dev/null +++ b/src/pretix/control/templates/pretixcontrol/organizers/device_connect.html @@ -0,0 +1,7 @@ +{% extends "pretixcontrol/organizers/base.html" %} +{% load i18n %} +{% load bootstrap3 %} +{% block inner %} + {% trans "Connect to device:" %} {{ device.name }} + +{% endblock %} diff --git a/src/pretix/control/templates/pretixcontrol/organizers/device_edit.html b/src/pretix/control/templates/pretixcontrol/organizers/device_edit.html new file mode 100644 index 0000000000..ab58994560 --- /dev/null +++ b/src/pretix/control/templates/pretixcontrol/organizers/device_edit.html @@ -0,0 +1,23 @@ +{% extends "pretixcontrol/organizers/base.html" %} +{% load i18n %} +{% load bootstrap3 %} +{% block inner %} + {% if device %} + {% trans "Device:" %} {{ device.name }} + {% else %} + {% trans "Connect a new device" %} + {% 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" %} +
    + +
    + +
    +{% endblock %} diff --git a/src/pretix/control/templates/pretixcontrol/organizers/devices.html b/src/pretix/control/templates/pretixcontrol/organizers/devices.html new file mode 100644 index 0000000000..234ac3d7cd --- /dev/null +++ b/src/pretix/control/templates/pretixcontrol/organizers/devices.html @@ -0,0 +1,90 @@ +{% extends "pretixcontrol/organizers/base.html" %} +{% load i18n %} +{% load bootstrap3 %} +{% block inner %} + + {% trans "Connected devices" %} + + {% if devices|length == 0 %} +
    +

    + {% blocktrans trimmed %} + You haven't connected any hardware devices yet. + {% endblocktrans %} +

    + +
    {% trans "Connect a device" %} +
    + {% else %} +

    + {% trans "Connect a device" %} +

    +
    + + + + + + + + + + + + + + {% for d in devices %} + + + + + + + + + + {% endfor %} + +
    {% trans "Device ID" %}{% trans "Name" %}{% trans "Hardware model" %}{% trans "Software" %}{% trans "Setup date" %}{% trans "Events" %}
    + {{ d.device_id }} + + {{ d.name }} + + {{ d.hardware_brand|default_if_none:"" }} {{ d.hardware_model|default_if_none:"" }} + + {{ d.software_brand|default_if_none:"" }} {{ d.software_version|default_if_none:"" }} + + {% if d.initialized %} + {{ d.initialized|date:"SHORT_DATETIME_FORMAT" }} + {% else %} + {% trans "Not yet initialized" %} + {% endif %} + + {% if d.all_events %} + {% trans "All" %} + {% else %} +
      + {% for e in d.limit_events.all %} +
    • + + {{ e }} + +
    • + {% endfor %} +
    + {% endif %} +
    + {% if not d.initialized %} + + {% trans "Connect" %} + {% endif %} + +
    +
    + {% include "pretixcontrol/pagination.html" %} + {% endif %} +{% endblock %} diff --git a/src/pretix/control/urls.py b/src/pretix/control/urls.py index b6e99ee123..6ec60df4cd 100644 --- a/src/pretix/control/urls.py +++ b/src/pretix/control/urls.py @@ -68,6 +68,13 @@ urlpatterns = [ url(r'^organizer/(?P[^/]+)/edit$', organizer.OrganizerUpdate.as_view(), name='organizer.edit'), url(r'^organizer/(?P[^/]+)/settings/display$', organizer.OrganizerDisplaySettings.as_view(), name='organizer.display'), + url(r'^organizer/(?P[^/]+)/devices$', organizer.DeviceListView.as_view(), name='organizer.devices'), + url(r'^organizer/(?P[^/]+)/device/add$', organizer.DeviceCreateView.as_view(), + name='organizer.device.add'), + url(r'^organizer/(?P[^/]+)/device/(?P[^/]+)/edit', organizer.DeviceUpdateView.as_view(), + name='organizer.device.edit'), + url(r'^organizer/(?P[^/]+)/device/(?P[^/]+)/connect', organizer.DeviceConnectView.as_view(), + name='organizer.device.connect'), url(r'^organizer/(?P[^/]+)/teams$', organizer.TeamListView.as_view(), name='organizer.teams'), url(r'^organizer/(?P[^/]+)/team/add$', organizer.TeamCreateView.as_view(), name='organizer.team.add'), url(r'^organizer/(?P[^/]+)/team/(?P[^/]+)/$', organizer.TeamMemberView.as_view(), diff --git a/src/pretix/control/views/organizer.py b/src/pretix/control/views/organizer.py index 8cc01ef635..d8f8be9063 100644 --- a/src/pretix/control/views/organizer.py +++ b/src/pretix/control/views/organizer.py @@ -13,14 +13,14 @@ from django.views.generic import ( CreateView, DeleteView, DetailView, FormView, ListView, UpdateView, ) -from pretix.base.models import Organizer, Team, TeamInvite, User +from pretix.base.models import Device, Organizer, Team, TeamInvite, User from pretix.base.models.event import EventMetaProperty from pretix.base.models.organizer import TeamAPIToken from pretix.base.services.mail import SendMailException, mail from pretix.control.forms.filter import OrganizerFilterForm from pretix.control.forms.organizer import ( - EventMetaPropertyForm, OrganizerDisplaySettingsForm, OrganizerForm, - OrganizerSettingsForm, OrganizerUpdateForm, TeamForm, + DeviceForm, EventMetaPropertyForm, OrganizerDisplaySettingsForm, + OrganizerForm, OrganizerSettingsForm, OrganizerUpdateForm, TeamForm, ) from pretix.control.permissions import OrganizerPermissionRequiredMixin from pretix.control.signals import nav_organizer @@ -576,3 +576,99 @@ class TeamMemberView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, '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') + + +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.members.add(self.request.user) + 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' + form_class = DeviceForm + + 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 self.object.initialized: + messages.error(request, _('This device already has been connected.')) + return redirect(reverse('control:organizer.devices', kwargs={ + 'organizer': self.request.organizer.slug, + })) + return super().get(request, *args, **kwargs)