Pluggable permissions (#5728)

* Data model draft

* Refactor query and assignment usages of old permissions

* Backend UI

* API serializer

* Big string replace

* Docs, tests and fixes for teams api

* Update docs for device auth

* Eliminate old names

* Make tests pass

* Use new permissions, remove inconsistencies

* Add test for translations

* Show plugin permissions

* Add permission for seating plans

* Fix plugin activation

* Fix failing test

* Refactor to permission groups

* Update doc/api/resources/devices.rst

Co-authored-by: luelista <weller@rami.io>

* Update doc/api/resources/events.rst

Co-authored-by: luelista <weller@rami.io>

* Update src/pretix/api/serializers/organizer.py

Co-authored-by: luelista <weller@rami.io>

* Fix typo

* Fix python version compat

* Replacement after rebase

* Add proper permission handling for exports

* Docs for exporters

* Runtime linting of permission names

* Fix typos

* Show export page even without orders permission

* More legacy compat

* Do not strongly validate before plugins are loaded

* Rebase migration

* Add permission for outgoing mails

* Review notes

* Update doc/api/resources/teams.rst

Co-authored-by: Richard Schreiber <schreiber@pretix.eu>

* Clean up logic around exporters

* Review and failures

* Fix migration leading to forbidden combination

* Handle permissions on event copying

* Remove print-statements

* Make test clearer

* Review feedback

* Add AnyPermissionOf

* migration safety

---------

Co-authored-by: luelista <weller@rami.io>
Co-authored-by: Richard Schreiber <schreiber@pretix.eu>
This commit is contained in:
Raphael Michel
2026-03-17 14:43:56 +01:00
committed by GitHub
parent eddde2b6c0
commit df0b580dd6
203 changed files with 5374 additions and 2331 deletions

View File

@@ -93,16 +93,18 @@
{% endif %}
</dl>
</form>
<div class="text-right">
<a href="{% url "control:organizer.customer.edit" organizer=request.organizer.slug customer=customer.identifier %}"
class="btn btn-default">
<i class="fa fa-edit"></i> {% trans "Edit" %}
</a>
<a href="{% url "control:organizer.customer.anonymize" organizer=request.organizer.slug customer=customer.identifier %}"
class="btn btn-danger">
<i class="fa fa-trash"></i> {% trans "Anonymize" %}
</a>
</div>
{% if "organizer.customers:write" in request.orgapermset %}
<div class="text-right">
<a href="{% url "control:organizer.customer.edit" organizer=request.organizer.slug customer=customer.identifier %}"
class="btn btn-default">
<i class="fa fa-edit"></i> {% trans "Edit" %}
</a>
<a href="{% url "control:organizer.customer.anonymize" organizer=request.organizer.slug customer=customer.identifier %}"
class="btn btn-danger">
<i class="fa fa-trash"></i> {% trans "Anonymize" %}
</a>
</div>
{% endif %}
</div>
</div>
<div class="panel panel-default items">
@@ -162,35 +164,39 @@
</div>
</td>
<td class="text-right flip">
<a href="{% url "control:organizer.customer.membership.edit" organizer=request.organizer.slug customer=customer.identifier id=m.pk %}"
data-toggle="tooltip"
title="{% trans "Edit" %}"
class="btn btn-default">
<i class="fa fa-edit"></i>
</a>
{% if m.testmode %}
<a href="{% url "control:organizer.customer.membership.delete" organizer=request.organizer.slug customer=customer.identifier id=m.pk %}"
{% if "organizer.customers:write" in request.orgapermset %}
<a href="{% url "control:organizer.customer.membership.edit" organizer=request.organizer.slug customer=customer.identifier id=m.pk %}"
data-toggle="tooltip"
title="{% trans "Delete" %}"
class="btn btn-danger">
<i class="fa fa-trash"></i>
title="{% trans "Edit" %}"
class="btn btn-default">
<i class="fa fa-edit"></i>
</a>
{% if m.testmode %}
<a href="{% url "control:organizer.customer.membership.delete" organizer=request.organizer.slug customer=customer.identifier id=m.pk %}"
data-toggle="tooltip"
title="{% trans "Delete" %}"
class="btn btn-danger">
<i class="fa fa-trash"></i>
</a>
{% endif %}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td colspan="7">
<a href="{% url "control:organizer.customer.membership.add" organizer=request.organizer.slug customer=customer.identifier %}"
class="btn btn-default">
<i class="fa fa-plus"></i>
{% trans "Add membership" %}
</a>
</td>
</tr>
</tfoot>
{% if "organizer.customers:write" in request.orgapermset %}
<tfoot>
<tr>
<td colspan="7">
<a href="{% url "control:organizer.customer.membership.add" organizer=request.organizer.slug customer=customer.identifier %}"
class="btn btn-default">
<i class="fa fa-plus"></i>
{% trans "Add membership" %}
</a>
</td>
</tr>
</tfoot>
{% endif %}
</table>
</div>
<div class="panel panel-default items">
@@ -300,14 +306,18 @@
{% for gc in gift_cards %}
<tr>
<td>
<a href="{% url "control:organizer.giftcard" organizer=organizer.slug giftcard=gc.id %}">
<strong>{{ gc.secret }}</strong></a>
{% if gc.testmode %}
<span class="label label-warning">{% trans "TEST MODE" %}</span>
{% endif %}
{% if gc.expired %}
<span class="label label-danger">{% trans "Expired" %}</span>
{% endif %}
{% if "organizer.giftcards:read" in request.orgapermset %}
<a href="{% url "control:organizer.giftcard" organizer=organizer.slug giftcard=gc.id %}">
<strong>{{ gc.secret }}</strong></a>
{% else %}
<strong>{{ gc.secret|slice:":3" }}…</strong>
{% endif %}
{% if gc.testmode %}
<span class="label label-warning">{% trans "TEST MODE" %}</span>
{% endif %}
{% if gc.expired %}
<span class="label label-danger">{% trans "Expired" %}</span>
{% endif %}
</td>
<td>{{ gc.issuance|date:"SHORT_DATETIME_FORMAT" }}</td>
<td>{% if gc.expires %}{{ gc.expires|date:"SHORT_DATETIME_FORMAT" }}{% endif %}</td>
@@ -316,10 +326,12 @@
<p class="text-right">{{ gc.value|money:gc.currency }}</p>
</td>
<td class="text-right">
<a href="{% url "control:organizer.giftcard" organizer=organizer.slug giftcard=gc.id %}"
class="btn btn-default btn-sm" data-toggle="tooltip" title="{% trans "Details" %}">
<i class="fa fa-eye"></i>
</a>
{% if "organizer.giftcards:read" in request.orgapermset %}
<a href="{% url "control:organizer.giftcard" organizer=organizer.slug giftcard=gc.id %}"
class="btn btn-default btn-sm" data-toggle="tooltip" title="{% trans "Details" %}">
<i class="fa fa-eye"></i>
</a>
{% endif %}
</td>
</tr>
{% endfor %}

View File

@@ -15,8 +15,10 @@
No customer accounts have been created yet.
{% endblocktrans %}
</p>
<a href="{% url "control:organizer.customer.create" organizer=request.organizer.slug %}"
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new customer" %}</a>
{% if "organizer.customers:write" in request.orgapermset %}
<a href="{% url "control:organizer.customer.create" organizer=request.organizer.slug %}"
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new customer" %}</a>
{% endif %}
</div>
{% else %}
<div class="panel panel-default">
@@ -43,10 +45,12 @@
</div>
</form>
</div>
<p>
<a href="{% url "control:organizer.customer.create" organizer=request.organizer.slug %}"
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new customer" %}</a>
</p>
{% if "organizer.customers:write" in request.orgapermset %}
<p>
<a href="{% url "control:organizer.customer.create" organizer=request.organizer.slug %}"
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new customer" %}</a>
</p>
{% endif %}
<div class="table-responsive">
<table class="table table-condensed table-hover">
<thead>

View File

@@ -7,7 +7,7 @@
{% blocktrans with name=request.organizer.name %}Organizer: {{ name }}{% endblocktrans %}
</h1>
{% if events|length == 0 and not filter_form.filtered %}
{% if "can_create_events" in request.orgapermset %}
{% if "organizer.events:create" in request.orgapermset %}
<p>
<a href="{% url "control:events.add" %}?organizer={{ request.organizer.slug }}" class="btn btn-primary">
<span class="fa fa-plus"></span>
@@ -51,7 +51,7 @@
</div>
</form>
</div>
{% if "can_create_events" in request.orgapermset %}
{% if "organizer.events:create" in request.orgapermset %}
<p>
<a href="{% url "control:events.add" %}?organizer={{ request.organizer.slug }}" class="btn btn-primary">
<span class="fa fa-plus"></span>
@@ -147,7 +147,7 @@
data-toggle="tooltip">
<span class="fa fa-eye"></span>
</a>
{% if "can_create_events" in request.orgapermset %}
{% if "organizer.events:create" in request.orgapermset %}
<a href="{% url "control:events.add" %}?clone={{ e.pk }}" class="btn btn-sm btn-default"
title="{% trans "Clone event" %}" data-toggle="tooltip">
<span class="fa fa-copy"></span>

View File

@@ -51,10 +51,12 @@
</div>
</form>
</div>
<p>
<a href="{% url "control:organizer.device.add" organizer=request.organizer.slug %}"
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Connect a device" %}</a>
</p>
{% if "organizer.devices:write" in request.orgapermset %}
<p>
<a href="{% url "control:organizer.device.add" organizer=request.organizer.slug %}"
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Connect a device" %}</a>
</p>
{% endif %}
<form action="{% url "control:organizer.device.bulk_edit" organizer=request.organizer.slug %}" method="post">
{% csrf_token %}
{% for field in filter_form %}
@@ -64,10 +66,12 @@
<table class="table table-condensed table-hover table-quotas">
<thead>
<tr>
<th>
<label aria-label="{% trans "select all rows for batch-operation" %}"
class="batch-select-label"><input type="checkbox" data-toggle-table/></label>
</th>
{% if "organizer.devices:write" in request.orgapermset %}
<th>
<label aria-label="{% trans "select all rows for batch-operation" %}"
class="batch-select-label"><input type="checkbox" data-toggle-table/></label>
</th>
{% endif %}
<th>{% trans "Device ID" %}
<a href="?{% url_replace request 'ordering' '-device_id' %}"><i
class="fa fa-caret-down"></i></a>
@@ -105,12 +109,14 @@
<tbody>
{% for d in devices %}
<tr {% if d.revoked %}class="text-muted"{% endif %}>
<td>
<label aria-label="{% trans "select row for batch-operation" %}"
class="batch-select-label"><input type="checkbox" name="device"
class="batch-select-checkbox"
value="{{ d.pk }}"/></label>
</td>
{% if "organizer.devices:write" in request.orgapermset %}
<td>
<label aria-label="{% trans "select row for batch-operation" %}"
class="batch-select-label"><input type="checkbox" name="device"
class="batch-select-checkbox"
value="{{ d.pk }}"/></label>
</td>
{% endif %}
<td>
{{ d.device_id }}
</td>
@@ -158,15 +164,17 @@
{% endif %}
</td>
<td class="text-right flip">
{% if not d.initialized %}
<a href="{% url "control:organizer.device.connect" organizer=request.organizer.slug device=d.id %}"
class="btn btn-primary btn-sm"><i class="fa fa-link"></i>
{% trans "Connect" %}</a>
{% endif %}
{% if not d.initialized or d.api_token %}
<a href="{% url "control:organizer.device.revoke" organizer=request.organizer.slug device=d.id %}"
class="btn btn-default btn-sm">
{% trans "Revoke access" %}</a>
{% if "organizer.devices:write" in request.orgapermset %}
{% if not d.initialized %}
<a href="{% url "control:organizer.device.connect" organizer=request.organizer.slug device=d.id %}"
class="btn btn-primary btn-sm"><i class="fa fa-link"></i>
{% trans "Connect" %}</a>
{% endif %}
{% if not d.initialized or d.api_token %}
<a href="{% url "control:organizer.device.revoke" organizer=request.organizer.slug device=d.id %}"
class="btn btn-default btn-sm">
{% trans "Revoke access" %}</a>
{% endif %}
{% endif %}
{% if d.initialized %}
<a href="{% url "control:organizer.device.logs" organizer=request.organizer.slug device=d.id %}"
@@ -175,19 +183,23 @@
{% trans "Logs" %}
</a>
{% endif %}
<a href="{% url "control:organizer.device.edit" organizer=request.organizer.slug device=d.id %}"
class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
{% if "organizer.devices:write" in request.orgapermset %}
<a href="{% url "control:organizer.device.edit" organizer=request.organizer.slug device=d.id %}"
class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="batch-select-actions">
<button type="submit" class="btn btn-primary btn-save" name="action" value="edit">
<i class="fa fa-edit"></i>{% trans "Edit selected" %}
</button>
</div>
{% if "organizer.devices:write" in request.orgapermset %}
<div class="batch-select-actions">
<button type="submit" class="btn btn-primary btn-save" name="action" value="edit">
<i class="fa fa-edit"></i>{% trans "Edit selected" %}
</button>
</div>
{% endif %}
</form>
{% include "pretixcontrol/pagination.html" %}
{% endif %}

View File

@@ -34,7 +34,7 @@
{% if s.export_verbose_name == "?" %}
<strong class="text-danger">
<span class="fa fa-warning fa-fw"></span>
{% trans "Exporter not found" %}
{% trans "Exporter not found or no permission" %}
</strong>
{% elif s.error_counter >= 5 %}
<strong class="text-danger">
@@ -115,5 +115,9 @@
</a>
{% endfor %}
</div>
{% empty %}
<p class="empty-collection">
{% trans "There are no exporters available for you." %}
</p>
{% endfor %}
{% endblock %}

View File

@@ -40,16 +40,18 @@
</fieldset>
{% if schedule_form %}
{% include "pretixcontrol/orders/fragment_export_schedule_form.html" %}
<div class="form-group submit-group">
<button formaction="{{ request.get_full_path }}" name="schedule" value="save" type="submit"
class="btn btn-primary btn-save" data-no-asynctask>
{% if scheduled_copy_from %}
{% trans "Save copy" %}
{% else %}
{% trans "Save" %}
{% endif %}
</button>
</div>
{% if not no_save %}
<div class="form-group submit-group">
<button formaction="{{ request.get_full_path }}" name="schedule" value="save" type="submit"
class="btn btn-primary btn-save" data-no-asynctask>
{% if scheduled_copy_from %}
{% trans "Save copy" %}
{% else %}
{% trans "Save" %}
{% endif %}
</button>
</div>
{% endif %}
{% else %}
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">

View File

@@ -6,10 +6,12 @@
<p>
{% trans "The list below shows gates that you can 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>
{% if "organizer.devices:write" in request.orgapermset %}
<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>
{% endif %}
<table class="table table-condensed table-hover">
<thead>
<tr>
@@ -21,15 +23,21 @@
{% for g in gates %}
<tr>
<td><strong>
<a href="{% url "control:organizer.gate.edit" organizer=request.organizer.slug gate=g.id %}">
{% if "organizer.devices:write" in request.orgapermset %}
<a href="{% url "control:organizer.gate.edit" organizer=request.organizer.slug gate=g.id %}">
{{ g.name }}
</a>
{% else %}
{{ g.name }}
</a>
{% endif %}
</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>
{% if "organizer.devices:write" in request.orgapermset %}
<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>
{% endif %}
</td>
</tr>
{% endfor %}

View File

@@ -10,10 +10,12 @@
{% if card.testmode %}
<span class="label label-warning">{% trans "TEST MODE" %}</span>
{% endif %}
<a href="{% url "control:organizer.giftcard.edit" organizer=request.organizer.slug giftcard=card.id %}"
class="btn btn-default">
<i class="fa fa-edit"></i> {% trans "Edit" %}
</a>
{% if "organizer.giftcards:write" in request.orgapermset %}
<a href="{% url "control:organizer.giftcard.edit" organizer=request.organizer.slug giftcard=card.id %}"
class="btn btn-default">
<i class="fa fa-edit"></i> {% trans "Edit" %}
</a>
{% endif %}
</h1>
<div class="row">
<div class="col-md-10 col-xs-12">
@@ -112,22 +114,24 @@
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td></td>
<td>
<input type="text" class="form-control helper-display-block" placeholder="{% trans "Text" %}"
name="text">
</td>
<td class="text-right form-inline">
<input type="text" class="form-control input-sm" placeholder="{% trans "Value" %}" name="value">
<button type="submit" class="btn btn-primary">
<span class="fa fa-plus"></span>
</button>
</td>
{% if "organizer.giftcards:write" in request.orgapermset %}
<tfoot>
<tr>
<td></td>
<td>
<input type="text" class="form-control helper-display-block" placeholder="{% trans "Text" %}"
name="text">
</td>
<td class="text-right form-inline">
<input type="text" class="form-control input-sm" placeholder="{% trans "Value" %}" name="value">
<button type="submit" class="btn btn-primary">
<span class="fa fa-plus"></span>
</button>
</td>
</tr>
</tfoot>
</tr>
</tfoot>
{% endif %}
</table>
</form>
</div>

View File

@@ -15,10 +15,11 @@
or you can manually issue gift cards.
{% endblocktrans %}
</p>
<a href="{% url "control:organizer.giftcard.add" organizer=request.organizer.slug %}"
class="btn btn-default btn-lg"><i class="fa fa-plus"></i> {% trans "Manually issue a gift card" %}
</a>
{% if "organizer.giftcards:write" in request.orgapermset %}
<a href="{% url "control:organizer.giftcard.add" organizer=request.organizer.slug %}"
class="btn btn-default btn-lg"><i class="fa fa-plus"></i> {% trans "Manually issue a gift card" %}
</a>
{% endif %}
</div>
{% else %}
<div class="panel panel-default">
@@ -45,10 +46,12 @@
</div>
</form>
</div>
<p>
<a href="{% url "control:organizer.giftcard.add" organizer=request.organizer.slug %}"
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Manually issue a gift card" %}</a>
</p>
{% if "organizer.giftcards:write" in request.orgapermset %}
<p>
<a href="{% url "control:organizer.giftcard.add" organizer=request.organizer.slug %}"
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Manually issue a gift card" %}</a>
</p>
{% endif %}
<div class="table-responsive">
<table class="table table-condensed table-hover">
<thead>

View File

@@ -15,8 +15,10 @@
No media have been created yet.
{% endblocktrans %}
</p>
<a href="{% url "control:organizer.reusable_medium.create" organizer=request.organizer.slug %}"
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new medium" %}</a>
{% if "organizer.reusablemedia:write" in request.orgapermset %}
<a href="{% url "control:organizer.reusable_medium.create" organizer=request.organizer.slug %}"
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new medium" %}</a>
{% endif %}
</div>
{% else %}
<div class="panel panel-default">
@@ -40,10 +42,12 @@
</div>
</form>
</div>
<p>
<a href="{% url "control:organizer.reusable_medium.create" organizer=request.organizer.slug %}"
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new medium" %}</a>
</p>
{% if "organizer.reusablemedia:write" in request.orgapermset %}
<p>
<a href="{% url "control:organizer.reusable_medium.create" organizer=request.organizer.slug %}"
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new medium" %}</a>
</p>
{% endif %}
<div class="table-responsive">
<table class="table table-condensed table-hover">
<thead>
@@ -77,9 +81,13 @@
{% if m.customer %}
<span class="helper-display-block">
<span class="fa fa-user fa-fw"></span>
<a href="{% url "control:organizer.customer" organizer=request.organizer.slug customer=m.customer.identifier %}">
{% if "organizer.customers:read" in request.orgapermset %}
<a href="{% url "control:organizer.customer" organizer=request.organizer.slug customer=m.customer.identifier %}">
{{ m.customer }}
</a>
{% else %}
{{ m.customer }}
</a>
{% endif %}
</span>
{% endif %}
{% if m.linked_orderposition %}
@@ -92,8 +100,12 @@
{% if m.linked_giftcard %}
<span class="helper-display-block">
<span class="fa fa-credit-card fa-fw"></span>
<a href="{% url "control:organizer.giftcard" organizer=request.organizer.slug giftcard=m.linked_giftcard.id %}">
{{ m.linked_giftcard.secret }}</a>
{% if "organizer.giftcards:read" in request.orgapermset %}
<a href="{% url "control:organizer.giftcard" organizer=request.organizer.slug giftcard=m.linked_giftcard.id %}">
{{ m.linked_giftcard.secret }}</a>
{% else %}
{{ m.linked_giftcard.secret|slice:":3" }}…
{% endif %}
</span>
{% endif %}
</td>

View File

@@ -22,60 +22,68 @@
</h3>
</div>
<div class="panel-body">
<form action="" method="post">
{% csrf_token %}
<dl class="dl-horizontal">
<dt>{% trans "Media type" context "reusable_media" %}</dt>
<dd>{{ medium.get_type_display }}</dd>
<dt>{% trans "Identifier" context "reusable_media" %}</dt>
<dd><code>{{ medium.identifier }}</code></dd>
<dt>{% trans "Status" %}</dt>
<dd>
{% if not medium.active %}
{% trans "disabled" %}
{% elif medium.is_expired %}
{% trans "expired" %}
{% else %}
{% trans "active" %}
{% endif %}
</dd>
<dt>{% trans "Connections" context "reusable_media" %}</dt>
<dd>
{% if medium.customer %}
<span class="helper-display-block">
<span class="fa fa-user fa-fw"></span>
<dl class="dl-horizontal">
<dt>{% trans "Media type" context "reusable_media" %}</dt>
<dd>{{ medium.get_type_display }}</dd>
<dt>{% trans "Identifier" context "reusable_media" %}</dt>
<dd><code>{{ medium.identifier }}</code></dd>
<dt>{% trans "Status" %}</dt>
<dd>
{% if not medium.active %}
{% trans "disabled" %}
{% elif medium.is_expired %}
{% trans "expired" %}
{% else %}
{% trans "active" %}
{% endif %}
</dd>
<dt>{% trans "Connections" context "reusable_media" %}</dt>
<dd>
{% if medium.customer %}
<span class="helper-display-block">
<span class="fa fa-user fa-fw"></span>
{% if "organizer.customers:read" in request.orgapermset %}
<a href="{% url "control:organizer.customer" organizer=request.organizer.slug customer=medium.customer.identifier %}">
{{ medium.customer }}
</a>
</span>
{% else %}
{{ medium.customer }}
{% endif %}
{% if medium.linked_orderposition %}
<span class="helper-display-block">
<span class="fa fa-ticket fa-fw"></span>
<a href="{% url "control:event.order" event=medium.linked_orderposition.order.event.slug organizer=request.organizer.slug code=medium.linked_orderposition.order.code %}">
{{ medium.linked_orderposition.order.code }}</a>-{{ medium.linked_orderposition.positionid }}
</span>
{% endif %}
{% if medium.linked_giftcard %}
<span class="helper-display-block">
<span class="fa fa-credit-card fa-fw"></span>
<a href="{% url "control:organizer.giftcard" organizer=request.organizer.slug giftcard=medium.linked_giftcard.id %}">
{{ medium.linked_giftcard.secret }}</a>
</span>
{% endif %}
</dd>
{% if medium.notes %}
<dt>{% trans "Notes" %}</dt>
<dd>{{ medium.notes }}</dd>
</span>
{% endif %}
</dl>
</form>
<div class="text-right">
<a href="{% url "control:organizer.reusable_medium.edit" organizer=request.organizer.slug pk=medium.pk %}"
class="btn btn-default">
<i class="fa fa-edit"></i> {% trans "Edit" %}
</a>
</div>
{% if medium.linked_orderposition %}
<span class="helper-display-block">
<span class="fa fa-ticket fa-fw"></span>
<a href="{% url "control:event.order" event=medium.linked_orderposition.order.event.slug organizer=request.organizer.slug code=medium.linked_orderposition.order.code %}">
{{ medium.linked_orderposition.order.code }}</a>-{{ medium.linked_orderposition.positionid }}
</span>
{% endif %}
{% if medium.linked_giftcard %}
<span class="helper-display-block">
<span class="fa fa-credit-card fa-fw"></span>
{% if "organizer.giftcards:read" in request.orgapermset %}
<a href="{% url "control:organizer.giftcard" organizer=request.organizer.slug giftcard=medium.linked_giftcard.id %}">
{{ medium.linked_giftcard.secret }}
</a>
{% else %}
{{ medium.linked_giftcard.secret|slice:":3" }}…
{% endif %}
</span>
{% endif %}
</dd>
{% if medium.notes %}
<dt>{% trans "Notes" %}</dt>
<dd>{{ medium.notes }}</dd>
{% endif %}
</dl>
{% if "organizer.reusablemedia:write" in request.orgapermset %}
<div class="text-right">
<a href="{% url "control:organizer.reusable_medium.edit" organizer=request.organizer.slug pk=medium.pk %}"
class="btn btn-default">
<i class="fa fa-edit"></i> {% trans "Edit" %}
</a>
</div>
{% endif %}
</div>
</div>
</div>

View File

@@ -1,6 +1,7 @@
{% extends "pretixcontrol/organizers/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% load getitem %}
{% block inner %}
{% if team %}
<h1>{% trans "Team:" %} {{ team.name }}</h1>
@@ -22,25 +23,24 @@
</fieldset>
<fieldset>
<legend>{% trans "Organizer permissions" %}</legend>
{% bootstrap_field form.can_create_events layout="control" %}
{% bootstrap_field form.can_manage_gift_cards layout="control" %}
{% bootstrap_field form.can_manage_customers layout="control" %}
{% bootstrap_field form.can_manage_reusable_media layout="control" %}
{% bootstrap_field form.can_change_teams layout="control" %}
{% bootstrap_field form.can_change_organizer_settings layout="control" %}
{% bootstrap_field form.all_organizer_permissions layout="control" %}
<div class="team-permission-groups col-md-9 col-md-offset-3" data-display-dependency="#id_all_organizer_permissions" data-inverse>
{% for f in form.organizer_field_names %}
{% bootstrap_field form|getitem:f layout="control" %}
{% endfor %}
</div>
</fieldset>
<fieldset>
<legend>{% trans "Event permissions" %}</legend>
{% bootstrap_field form.all_events layout="control" %}
{% bootstrap_field form.limit_events layout="control" %}
{% bootstrap_field form.can_change_event_settings layout="control" %}
{% bootstrap_field form.can_change_items layout="control" %}
{% bootstrap_field form.can_view_orders layout="control" %}
{% bootstrap_field form.can_change_orders layout="control" %}
{% bootstrap_field form.can_checkin_orders layout="control" %}
{% bootstrap_field form.can_view_vouchers layout="control" %}
{% bootstrap_field form.can_change_vouchers layout="control" %}
{% bootstrap_field form.all_event_permissions layout="control" %}
<div class="team-permission-groups col-md-9 col-md-offset-3" data-display-dependency="#id_all_event_permissions" data-inverse>
{% for f in form.event_field_names %}
{% bootstrap_field form|getitem:f layout="control" %}
{% endfor %}
</div>
</fieldset>
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">