mirror of
https://github.com/pretix/pretix.git
synced 2026-08-22 12:42:00 +00:00
Control: Add drag and drop to sort categories and products (#2242)
* add drag and drop to categories * add drag and drop to products * add light grey background to dragged element * add missing th, add sr-only desc of columns * group up/down/move elements * improve visualizing drag-area by dimming others * change up/down-links to buttons in form-post * limit sorting to POST requests Co-authored-by: Raphael Michel <michel@rami.io>
This commit is contained in:
co-authored by
Raphael Michel
parent
dde4e12ce1
commit
8121167d5e
@@ -24,6 +24,8 @@
|
|||||||
<a href="{% url "control:event.items.categories.add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new category" %}
|
<a href="{% url "control:event.items.categories.add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new category" %}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -33,15 +35,16 @@
|
|||||||
<th class="action-col-2"></th>
|
<th class="action-col-2"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody data-dnd-url="{% url "control:event.items.categories.reorder" organizer=request.event.organizer.slug event=request.event.slug %}">
|
||||||
{% for c in categories %}
|
{% for c in categories %}
|
||||||
<tr>
|
<tr data-dnd-id="{{ c.id }}">
|
||||||
<td>
|
<td>
|
||||||
<strong><a href="{% url "control:event.items.categories.edit" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}">{{ c.internal_name|default:c.name }}</a></strong>
|
<strong><a href="{% url "control:event.items.categories.edit" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}">{{ c.internal_name|default:c.name }}</a></strong>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url "control:event.items.categories.up" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-default btn-sm {% if forloop.counter0 == 0 and is_paginated and not page_obj.has_previous %}disabled{% endif %}"><i class="fa fa-arrow-up"></i></a>
|
<button formaction="{% url "control:event.items.categories.up" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-default btn-sm sortable-up"{% if forloop.counter0 == 0 and not page_obj.has_previous %} disabled{% endif %}><i class="fa fa-arrow-up"></i></button>
|
||||||
<a href="{% url "control:event.items.categories.down" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-default btn-sm {% if forloop.revcounter0 == 0 and is_paginated and not page_obj.has_next %}disabled{% endif %}"><i class="fa fa-arrow-down"></i></a>
|
<button formaction="{% url "control:event.items.categories.down" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-default btn-sm sortable-down"{% if forloop.revcounter0 == 0 and not page_obj.has_next %} disabled{% endif %}><i class="fa fa-arrow-down"></i></button>
|
||||||
|
<span class="dnd-container"></span>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right flip">
|
<td class="text-right flip">
|
||||||
<a href="{% url "control:event.items.categories.edit" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
<a href="{% url "control:event.items.categories.edit" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
||||||
@@ -56,6 +59,7 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
{% include "pretixcontrol/pagination.html" %}
|
{% include "pretixcontrol/pagination.html" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@
|
|||||||
<a href="{% url "control:event.items.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
<a href="{% url "control:event.items.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||||
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new product" %}</a>
|
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new product" %}</a>
|
||||||
</p>
|
</p>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-condensed table-hover">
|
<table class="table table-condensed table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -36,15 +38,16 @@
|
|||||||
<th class="iconcol"></th>
|
<th class="iconcol"></th>
|
||||||
<th class="iconcol"></th>
|
<th class="iconcol"></th>
|
||||||
<th>{% trans "Category" %}</th>
|
<th>{% trans "Category" %}</th>
|
||||||
<th class="action-col-2"></th>
|
<th class="action-col-2"><span class="sr-only">Move</span></th>
|
||||||
<th class="action-col-2"></th>
|
<th class="action-col-2"><span class="sr-only">Edit</span></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
|
||||||
{% regroup items by category as cat_list %}
|
{% regroup items by category as cat_list %}
|
||||||
{% for c in cat_list %}
|
{% for c in cat_list %}
|
||||||
|
<tbody data-dnd-url="{% url "control:event.items.reorder" organizer=request.event.organizer.slug event=request.event.slug %}">
|
||||||
{% for i in c.list %}
|
{% for i in c.list %}
|
||||||
<tr {% if not i.active %}class="row-muted"{% endif %}>
|
{% if forloop.counter0 == 0 and i.category %}<tr class="sortable-disabled"><th colspan="8" scope="colgroup" class="text-muted">{{ i.category.name }}</th></tr>{% endif %}
|
||||||
|
<tr data-dnd-id="{{ i.id }}" {% if not i.active %}class="row-muted"{% endif %}>
|
||||||
<td><strong>
|
<td><strong>
|
||||||
{% if not i.active %}<strike>{% endif %}
|
{% if not i.active %}<strike>{% endif %}
|
||||||
<a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}">{{ i }}</a>
|
<a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}">{{ i }}</a>
|
||||||
@@ -105,8 +108,9 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>{% if i.category %}{{ i.category.name }}{% endif %}</td>
|
<td>{% if i.category %}{{ i.category.name }}{% endif %}</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url "control:event.items.up" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-default btn-sm {% if forloop.counter0 == 0 and is_paginated and not page_obj.has_previous %}disabled{% endif %}"><i class="fa fa-arrow-up"></i></a>
|
<button formaction="{% url "control:event.items.up" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-default btn-sm sortable-up"{% if forloop.counter0 == 0 %} disabled{% endif %}><i class="fa fa-arrow-up"></i></button>
|
||||||
<a href="{% url "control:event.items.down" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-default btn-sm {% if forloop.revcounter0 == 0 and is_paginated and not page_obj.has_next %}disabled{% endif %}"><i class="fa fa-arrow-down"></i></a>
|
<button formaction="{% url "control:event.items.down" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-default btn-sm sortable-down"{% if forloop.revcounter0 == 0 %} disabled{% endif %}><i class="fa fa-arrow-down"></i></button>
|
||||||
|
<span class="dnd-container"></span>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right flip col-actions">
|
<td class="text-right flip col-actions">
|
||||||
<a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
<a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
||||||
@@ -115,10 +119,11 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
{% include "pretixcontrol/pagination.html" %}
|
{% include "pretixcontrol/pagination.html" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -239,6 +239,7 @@ urlpatterns = [
|
|||||||
re_path(r'^items/(?P<item>\d+)/$', item.ItemUpdateGeneral.as_view(), name='event.item'),
|
re_path(r'^items/(?P<item>\d+)/$', item.ItemUpdateGeneral.as_view(), name='event.item'),
|
||||||
re_path(r'^items/(?P<item>\d+)/up$', item.item_move_up, name='event.items.up'),
|
re_path(r'^items/(?P<item>\d+)/up$', item.item_move_up, name='event.items.up'),
|
||||||
re_path(r'^items/(?P<item>\d+)/down$', item.item_move_down, name='event.items.down'),
|
re_path(r'^items/(?P<item>\d+)/down$', item.item_move_down, name='event.items.down'),
|
||||||
|
re_path(r'^items/reorder$', item.reorder_items, name='event.items.reorder'),
|
||||||
re_path(r'^items/(?P<item>\d+)/delete$', item.ItemDelete.as_view(), name='event.items.delete'),
|
re_path(r'^items/(?P<item>\d+)/delete$', item.ItemDelete.as_view(), name='event.items.delete'),
|
||||||
re_path(r'^items/typeahead/meta/$', typeahead.item_meta_values, name='event.items.meta.typeahead'),
|
re_path(r'^items/typeahead/meta/$', typeahead.item_meta_values, name='event.items.meta.typeahead'),
|
||||||
re_path(r'^items/select2$', typeahead.items_select2, name='event.items.select2'),
|
re_path(r'^items/select2$', typeahead.items_select2, name='event.items.select2'),
|
||||||
@@ -250,6 +251,7 @@ urlpatterns = [
|
|||||||
re_path(r'^categories/(?P<category>\d+)/up$', item.category_move_up, name='event.items.categories.up'),
|
re_path(r'^categories/(?P<category>\d+)/up$', item.category_move_up, name='event.items.categories.up'),
|
||||||
re_path(r'^categories/(?P<category>\d+)/down$', item.category_move_down,
|
re_path(r'^categories/(?P<category>\d+)/down$', item.category_move_down,
|
||||||
name='event.items.categories.down'),
|
name='event.items.categories.down'),
|
||||||
|
re_path(r'^categories/reorder$', item.reorder_categories, name='event.items.categories.reorder'),
|
||||||
re_path(r'^categories/(?P<category>\d+)/$', item.CategoryUpdate.as_view(),
|
re_path(r'^categories/(?P<category>\d+)/$', item.CategoryUpdate.as_view(),
|
||||||
name='event.items.categories.edit'),
|
name='event.items.categories.edit'),
|
||||||
re_path(r'^categories/add$', item.CategoryCreate.as_view(), name='event.items.categories.add'),
|
re_path(r'^categories/add$', item.CategoryCreate.as_view(), name='event.items.categories.add'),
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ from django.urls import resolve, reverse
|
|||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.utils.translation import gettext, gettext_lazy as _
|
from django.utils.translation import gettext, gettext_lazy as _
|
||||||
|
from django.views.decorators.http import require_http_methods
|
||||||
from django.views.generic import ListView
|
from django.views.generic import ListView
|
||||||
from django.views.generic.detail import DetailView, SingleObjectMixin
|
from django.views.generic.detail import DetailView, SingleObjectMixin
|
||||||
from django.views.generic.edit import DeleteView
|
from django.views.generic.edit import DeleteView
|
||||||
@@ -138,6 +139,7 @@ def item_move(request, item, up=True):
|
|||||||
|
|
||||||
|
|
||||||
@event_permission_required("can_change_items")
|
@event_permission_required("can_change_items")
|
||||||
|
@require_http_methods(["POST"])
|
||||||
def item_move_up(request, organizer, event, item):
|
def item_move_up(request, organizer, event, item):
|
||||||
item_move(request, item, up=True)
|
item_move(request, item, up=True)
|
||||||
return redirect('control:event.items',
|
return redirect('control:event.items',
|
||||||
@@ -146,6 +148,7 @@ def item_move_up(request, organizer, event, item):
|
|||||||
|
|
||||||
|
|
||||||
@event_permission_required("can_change_items")
|
@event_permission_required("can_change_items")
|
||||||
|
@require_http_methods(["POST"])
|
||||||
def item_move_down(request, organizer, event, item):
|
def item_move_down(request, organizer, event, item):
|
||||||
item_move(request, item, up=False)
|
item_move(request, item, up=False)
|
||||||
return redirect('control:event.items',
|
return redirect('control:event.items',
|
||||||
@@ -153,6 +156,38 @@ def item_move_down(request, organizer, event, item):
|
|||||||
event=request.event.slug)
|
event=request.event.slug)
|
||||||
|
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
@event_permission_required("can_change_items")
|
||||||
|
@require_http_methods(["POST"])
|
||||||
|
def reorder_items(request, organizer, event):
|
||||||
|
try:
|
||||||
|
ids = json.loads(request.body.decode('utf-8'))['ids']
|
||||||
|
except (JSONDecodeError, KeyError, ValueError):
|
||||||
|
return HttpResponseBadRequest("expected JSON: {ids:[]}")
|
||||||
|
|
||||||
|
input_items = list(request.event.items.filter(id__in=[i for i in ids if i.isdigit()]))
|
||||||
|
|
||||||
|
if len(input_items) != len(ids):
|
||||||
|
raise Http404(_("Some of the provided item ids are invalid."))
|
||||||
|
|
||||||
|
item_categories = {i.category_id for i in input_items}
|
||||||
|
if len(item_categories) > 1:
|
||||||
|
raise Http404(_("You cannot reorder items spanning different categories."))
|
||||||
|
|
||||||
|
# get first and only category
|
||||||
|
item_category = next(iter(item_categories))
|
||||||
|
if len(input_items) != request.event.items.filter(category=item_category).count():
|
||||||
|
raise Http404(_("Not all items have been selected."))
|
||||||
|
|
||||||
|
for i in input_items:
|
||||||
|
pos = ids.index(str(i.pk))
|
||||||
|
if pos != i.position: # Save unneccessary UPDATE queries
|
||||||
|
i.position = pos
|
||||||
|
i.save(update_fields=['position'])
|
||||||
|
|
||||||
|
return HttpResponse()
|
||||||
|
|
||||||
|
|
||||||
class CategoryDelete(EventPermissionRequiredMixin, DeleteView):
|
class CategoryDelete(EventPermissionRequiredMixin, DeleteView):
|
||||||
model = ItemCategory
|
model = ItemCategory
|
||||||
form_class = CategoryForm
|
form_class = CategoryForm
|
||||||
@@ -307,6 +342,7 @@ def category_move(request, category, up=True):
|
|||||||
|
|
||||||
|
|
||||||
@event_permission_required("can_change_items")
|
@event_permission_required("can_change_items")
|
||||||
|
@require_http_methods(["POST"])
|
||||||
def category_move_up(request, organizer, event, category):
|
def category_move_up(request, organizer, event, category):
|
||||||
category_move(request, category, up=True)
|
category_move(request, category, up=True)
|
||||||
return redirect('control:event.items.categories',
|
return redirect('control:event.items.categories',
|
||||||
@@ -315,6 +351,7 @@ def category_move_up(request, organizer, event, category):
|
|||||||
|
|
||||||
|
|
||||||
@event_permission_required("can_change_items")
|
@event_permission_required("can_change_items")
|
||||||
|
@require_http_methods(["POST"])
|
||||||
def category_move_down(request, organizer, event, category):
|
def category_move_down(request, organizer, event, category):
|
||||||
category_move(request, category, up=False)
|
category_move(request, category, up=False)
|
||||||
return redirect('control:event.items.categories',
|
return redirect('control:event.items.categories',
|
||||||
@@ -322,6 +359,32 @@ def category_move_down(request, organizer, event, category):
|
|||||||
event=request.event.slug)
|
event=request.event.slug)
|
||||||
|
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
@event_permission_required("can_change_items")
|
||||||
|
@require_http_methods(["POST"])
|
||||||
|
def reorder_categories(request, organizer, event):
|
||||||
|
try:
|
||||||
|
ids = json.loads(request.body.decode('utf-8'))['ids']
|
||||||
|
except (JSONDecodeError, KeyError, ValueError):
|
||||||
|
return HttpResponseBadRequest("expected JSON: {ids:[]}")
|
||||||
|
|
||||||
|
input_categories = list(request.event.categories.filter(id__in=[i for i in ids if i.isdigit()]))
|
||||||
|
|
||||||
|
if len(input_categories) != len(ids):
|
||||||
|
raise Http404(_("Some of the provided category ids are invalid."))
|
||||||
|
|
||||||
|
if len(input_categories) != request.event.categories.count():
|
||||||
|
raise Http404(_("Not all categories have been selected."))
|
||||||
|
|
||||||
|
for c in input_categories:
|
||||||
|
pos = ids.index(str(c.pk))
|
||||||
|
if pos != c.position: # Save unneccessary UPDATE queries
|
||||||
|
c.position = pos
|
||||||
|
c.save(update_fields=['position'])
|
||||||
|
|
||||||
|
return HttpResponse()
|
||||||
|
|
||||||
|
|
||||||
FakeQuestion = namedtuple(
|
FakeQuestion = namedtuple(
|
||||||
'FakeQuestion', 'id question position required'
|
'FakeQuestion', 'id question position required'
|
||||||
)
|
)
|
||||||
@@ -423,18 +486,21 @@ class QuestionList(ListView):
|
|||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
@event_permission_required("can_change_items")
|
@event_permission_required("can_change_items")
|
||||||
|
@require_http_methods(["POST"])
|
||||||
def reorder_questions(request, organizer, event):
|
def reorder_questions(request, organizer, event):
|
||||||
try:
|
try:
|
||||||
ids = json.loads(request.body.decode('utf-8'))['ids']
|
ids = json.loads(request.body.decode('utf-8'))['ids']
|
||||||
except (JSONDecodeError, KeyError, ValueError):
|
except (JSONDecodeError, KeyError, ValueError):
|
||||||
return HttpResponseBadRequest("expected JSON: {ids:[]}")
|
return HttpResponseBadRequest("expected JSON: {ids:[]}")
|
||||||
|
|
||||||
input_questions = request.event.questions.filter(id__in=[i for i in ids if i.isdigit()])
|
# filter system_questions - normal questions are int/digit, system_questions strings
|
||||||
|
custom_question_ids = [i for i in ids if i.isdigit()]
|
||||||
|
input_questions = list(request.event.questions.filter(id__in=custom_question_ids))
|
||||||
|
|
||||||
if input_questions.count() != len([i for i in ids if i.isdigit()]):
|
if len(input_questions) != len(custom_question_ids):
|
||||||
raise Http404(_("Some of the provided question ids are invalid."))
|
raise Http404(_("Some of the provided question ids are invalid."))
|
||||||
|
|
||||||
if input_questions.count() != request.event.questions.count():
|
if len(input_questions) != request.event.questions.count():
|
||||||
raise Http404(_("Not all questions have been selected."))
|
raise Http404(_("Not all questions have been selected."))
|
||||||
|
|
||||||
for q in input_questions:
|
for q in input_questions:
|
||||||
|
|||||||
@@ -5,11 +5,40 @@ $(function () {
|
|||||||
url = container.data("dnd-url"),
|
url = container.data("dnd-url"),
|
||||||
handle = $('<span class="btn btn-default btn-sm dnd-sort-handle"><i class="fa fa-arrows"></i></span>');
|
handle = $('<span class="btn btn-default btn-sm dnd-sort-handle"><i class="fa fa-arrows"></i></span>');
|
||||||
|
|
||||||
console.log(container, container.find(".dnd-container"));
|
|
||||||
container.find(".dnd-container").append(handle);
|
container.find(".dnd-container").append(handle);
|
||||||
|
if (container.find("[data-dnd-id]").length < 2) {
|
||||||
|
handle.addClass("disabled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Sortable.create(container.get(0), {
|
Sortable.create(container.get(0), {
|
||||||
|
filter: ".sortable-disabled",
|
||||||
handle: ".dnd-sort-handle",
|
handle: ".dnd-sort-handle",
|
||||||
|
onMove: function (evt) {
|
||||||
|
return evt.related.className.indexOf('sortable-disabled') === -1;
|
||||||
|
},
|
||||||
|
onStart: function (evt) {
|
||||||
|
container.addClass("sortable-dragarea");
|
||||||
|
container.parent().addClass("sortable-sorting");
|
||||||
|
},
|
||||||
|
onEnd: function (evt) {
|
||||||
|
container.removeClass("sortable-dragarea");
|
||||||
|
container.parent().removeClass("sortable-sorting");
|
||||||
|
|
||||||
|
var disabledUp = container.find(".sortable-up:disabled"),
|
||||||
|
firstUp = container.find(">tr[data-dnd-id] .sortable-up").first();
|
||||||
|
if (disabledUp.length && disabledUp.get(0) !== firstUp.get(0)) {
|
||||||
|
disabledUp.prop("disabled", false);
|
||||||
|
firstUp.prop("disabled", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
var disabledDown = container.find(".sortable-down:disabled"),
|
||||||
|
lastDown = container.find(">tr[data-dnd-id] .sortable-down").last();
|
||||||
|
if (disabledDown.length && disabledDown.get(0) !== lastDown.get(0)) {
|
||||||
|
disabledDown.prop("disabled", false);
|
||||||
|
lastDown.prop("disabled", true);
|
||||||
|
}
|
||||||
|
},
|
||||||
onSort: function (evt){
|
onSort: function (evt){
|
||||||
var container = $(evt.to),
|
var container = $(evt.to),
|
||||||
ids = container.find("[data-dnd-id]").toArray().map(function (e) { return e.dataset.dndId; });
|
ids = container.find("[data-dnd-id]").toArray().map(function (e) { return e.dataset.dndId; });
|
||||||
|
|||||||
@@ -706,6 +706,20 @@ h1 .label {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sortable-chosen {
|
||||||
|
background-color: $table-bg-hover;
|
||||||
|
}
|
||||||
|
tbody[data-dnd-url] {
|
||||||
|
transition: opacity 1s;
|
||||||
|
}
|
||||||
|
.sortable-sorting tbody:not(.sortable-dragarea) {
|
||||||
|
opacity: .4;
|
||||||
|
}
|
||||||
|
tbody th {
|
||||||
|
background: $table-bg-hover;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.withoutjs {
|
.withoutjs {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,12 +94,12 @@ class CategoriesTest(ItemFormTest):
|
|||||||
self.assertIn("Entry tickets", doc.select("table > tbody > tr")[0].text)
|
self.assertIn("Entry tickets", doc.select("table > tbody > tr")[0].text)
|
||||||
self.assertIn("T-Shirts", doc.select("table > tbody > tr")[1].text)
|
self.assertIn("T-Shirts", doc.select("table > tbody > tr")[1].text)
|
||||||
|
|
||||||
self.client.get('/control/event/%s/%s/categories/%s/down' % (self.orga1.slug, self.event1.slug, c1.id))
|
self.client.post('/control/event/%s/%s/categories/%s/down' % (self.orga1.slug, self.event1.slug, c1.id))
|
||||||
doc = self.get_doc('/control/event/%s/%s/categories/' % (self.orga1.slug, self.event1.slug))
|
doc = self.get_doc('/control/event/%s/%s/categories/' % (self.orga1.slug, self.event1.slug))
|
||||||
self.assertIn("Entry tickets", doc.select("table > tbody > tr")[1].text)
|
self.assertIn("Entry tickets", doc.select("table > tbody > tr")[1].text)
|
||||||
self.assertIn("T-Shirts", doc.select("table > tbody > tr")[0].text)
|
self.assertIn("T-Shirts", doc.select("table > tbody > tr")[0].text)
|
||||||
|
|
||||||
self.client.get('/control/event/%s/%s/categories/%s/up' % (self.orga1.slug, self.event1.slug, c1.id))
|
self.client.post('/control/event/%s/%s/categories/%s/up' % (self.orga1.slug, self.event1.slug, c1.id))
|
||||||
doc = self.get_doc('/control/event/%s/%s/categories/' % (self.orga1.slug, self.event1.slug))
|
doc = self.get_doc('/control/event/%s/%s/categories/' % (self.orga1.slug, self.event1.slug))
|
||||||
self.assertIn("Entry tickets", doc.select("table > tbody > tr")[0].text)
|
self.assertIn("Entry tickets", doc.select("table > tbody > tr")[0].text)
|
||||||
self.assertIn("T-Shirts", doc.select("table > tbody > tr")[1].text)
|
self.assertIn("T-Shirts", doc.select("table > tbody > tr")[1].text)
|
||||||
|
|||||||
@@ -272,104 +272,109 @@ def test_wrong_event(perf_patch, client, env, url):
|
|||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
HTTP_POST = "post"
|
||||||
|
HTTP_GET = "get"
|
||||||
|
|
||||||
event_permission_urls = [
|
event_permission_urls = [
|
||||||
("can_change_event_settings", "live/", 200),
|
("can_change_event_settings", "live/", 200, HTTP_GET),
|
||||||
("can_change_event_settings", "delete/", 200),
|
("can_change_event_settings", "delete/", 200, HTTP_GET),
|
||||||
("can_change_event_settings", "dangerzone/", 200),
|
("can_change_event_settings", "dangerzone/", 200, HTTP_GET),
|
||||||
("can_change_event_settings", "settings/", 200),
|
("can_change_event_settings", "settings/", 200, HTTP_GET),
|
||||||
("can_change_event_settings", "settings/plugins", 200),
|
("can_change_event_settings", "settings/plugins", 200, HTTP_GET),
|
||||||
("can_change_event_settings", "settings/payment", 200),
|
("can_change_event_settings", "settings/payment", 200, HTTP_GET),
|
||||||
("can_change_event_settings", "settings/tickets", 200),
|
("can_change_event_settings", "settings/tickets", 200, HTTP_GET),
|
||||||
("can_change_event_settings", "settings/email", 200),
|
("can_change_event_settings", "settings/email", 200, HTTP_GET),
|
||||||
("can_change_event_settings", "settings/cancel", 200),
|
("can_change_event_settings", "settings/cancel", 200, HTTP_GET),
|
||||||
("can_change_event_settings", "settings/invoice", 200),
|
("can_change_event_settings", "settings/invoice", 200, HTTP_GET),
|
||||||
("can_change_event_settings", "settings/widget", 200),
|
("can_change_event_settings", "settings/widget", 200, HTTP_GET),
|
||||||
("can_change_event_settings", "settings/invoice/preview", 200),
|
("can_change_event_settings", "settings/invoice/preview", 200, HTTP_GET),
|
||||||
("can_change_event_settings", "settings/tax/", 200),
|
("can_change_event_settings", "settings/tax/", 200, HTTP_GET),
|
||||||
("can_change_event_settings", "settings/tax/1/", 404),
|
("can_change_event_settings", "settings/tax/1/", 404, HTTP_GET),
|
||||||
("can_change_event_settings", "settings/tax/add", 200),
|
("can_change_event_settings", "settings/tax/add", 200, HTTP_GET),
|
||||||
("can_change_event_settings", "settings/tax/1/delete", 404),
|
("can_change_event_settings", "settings/tax/1/delete", 404, HTTP_GET),
|
||||||
("can_change_event_settings", "comment/", 405),
|
("can_change_event_settings", "comment/", 405, HTTP_GET),
|
||||||
# Lists are currently not access-controlled
|
# Lists are currently not access-controlled
|
||||||
# ("can_change_items", "items/", 200),
|
# ("can_change_items", "items/", 200),
|
||||||
("can_change_items", "items/add", 200),
|
("can_change_items", "items/add", 200, HTTP_GET),
|
||||||
("can_change_items", "items/1/up", 404),
|
("can_change_items", "items/1/up", 404, HTTP_POST),
|
||||||
("can_change_items", "items/1/down", 404),
|
("can_change_items", "items/1/down", 404, HTTP_POST),
|
||||||
("can_change_items", "items/1/delete", 404),
|
("can_change_items", "items/reorder", 400, HTTP_POST),
|
||||||
|
("can_change_items", "items/1/delete", 404, HTTP_GET),
|
||||||
# ("can_change_items", "categories/", 200),
|
# ("can_change_items", "categories/", 200),
|
||||||
# We don't have to create categories and similar objects
|
# We don't have to create categories and similar objects
|
||||||
# for testing this, it is enough to test that a 404 error
|
# for testing this, it is enough to test that a 404 error
|
||||||
# is returned instead of a 403 one.
|
# is returned instead of a 403 one.
|
||||||
("can_change_items", "categories/2/", 404),
|
("can_change_items", "categories/2/", 404, HTTP_GET),
|
||||||
("can_change_items", "categories/2/delete", 404),
|
("can_change_items", "categories/2/delete", 404, HTTP_GET),
|
||||||
("can_change_items", "categories/2/up", 404),
|
("can_change_items", "categories/2/up", 404, HTTP_POST),
|
||||||
("can_change_items", "categories/2/down", 404),
|
("can_change_items", "categories/2/down", 404, HTTP_POST),
|
||||||
("can_change_items", "categories/add", 200),
|
("can_change_items", "categories/reorder", 400, HTTP_POST),
|
||||||
# ("can_change_items", "questions/", 200),
|
("can_change_items", "categories/add", 200, HTTP_GET),
|
||||||
("can_change_items", "questions/2/", 404),
|
# ("can_change_items", "questions/", 200, HTTP_GET),
|
||||||
("can_change_items", "questions/2/delete", 404),
|
("can_change_items", "questions/2/", 404, HTTP_GET),
|
||||||
("can_change_items", "questions/reorder", 400),
|
("can_change_items", "questions/2/delete", 404, HTTP_GET),
|
||||||
("can_change_items", "questions/add", 200),
|
("can_change_items", "questions/reorder", 400, HTTP_POST),
|
||||||
# ("can_change_items", "quotas/", 200),
|
("can_change_items", "questions/add", 200, HTTP_GET),
|
||||||
("can_change_items", "quotas/2/change", 404),
|
# ("can_change_items", "quotas/", 200, HTTP_GET),
|
||||||
("can_change_items", "quotas/2/delete", 404),
|
("can_change_items", "quotas/2/change", 404, HTTP_GET),
|
||||||
("can_change_items", "quotas/add", 200),
|
("can_change_items", "quotas/2/delete", 404, HTTP_GET),
|
||||||
("can_change_event_settings", "subevents/", 200),
|
("can_change_items", "quotas/add", 200, HTTP_GET),
|
||||||
("can_change_event_settings", "subevents/2/", 404),
|
("can_change_event_settings", "subevents/", 200, HTTP_GET),
|
||||||
("can_change_event_settings", "subevents/2/delete", 404),
|
("can_change_event_settings", "subevents/2/", 404, HTTP_GET),
|
||||||
("can_change_event_settings", "subevents/add", 200),
|
("can_change_event_settings", "subevents/2/delete", 404, HTTP_GET),
|
||||||
("can_view_orders", "orders/overview/", 200),
|
("can_change_event_settings", "subevents/add", 200, HTTP_GET),
|
||||||
("can_view_orders", "orders/export/", 200),
|
("can_view_orders", "orders/overview/", 200, HTTP_GET),
|
||||||
("can_view_orders", "orders/", 200),
|
("can_view_orders", "orders/export/", 200, HTTP_GET),
|
||||||
("can_view_orders", "orders/FOO/", 200),
|
("can_view_orders", "orders/", 200, HTTP_GET),
|
||||||
("can_change_orders", "orders/FOO/extend", 200),
|
("can_view_orders", "orders/FOO/", 200, HTTP_GET),
|
||||||
("can_change_orders", "orders/FOO/reactivate", 302),
|
("can_change_orders", "orders/FOO/extend", 200, HTTP_GET),
|
||||||
("can_change_orders", "orders/FOO/contact", 200),
|
("can_change_orders", "orders/FOO/reactivate", 302, HTTP_GET),
|
||||||
("can_change_orders", "orders/FOO/transition", 405),
|
("can_change_orders", "orders/FOO/contact", 200, HTTP_GET),
|
||||||
("can_change_orders", "orders/FOO/checkvatid", 405),
|
("can_change_orders", "orders/FOO/transition", 405, HTTP_GET),
|
||||||
("can_change_orders", "orders/FOO/resend", 405),
|
("can_change_orders", "orders/FOO/checkvatid", 405, HTTP_GET),
|
||||||
("can_change_orders", "orders/FOO/invoice", 405),
|
("can_change_orders", "orders/FOO/resend", 405, HTTP_GET),
|
||||||
("can_change_orders", "orders/FOO/change", 200),
|
("can_change_orders", "orders/FOO/invoice", 405, HTTP_GET),
|
||||||
("can_change_orders", "orders/FOO/approve", 200),
|
("can_change_orders", "orders/FOO/change", 200, HTTP_GET),
|
||||||
("can_change_orders", "orders/FOO/deny", 200),
|
("can_change_orders", "orders/FOO/approve", 200, HTTP_GET),
|
||||||
("can_change_orders", "orders/FOO/delete", 302),
|
("can_change_orders", "orders/FOO/deny", 200, HTTP_GET),
|
||||||
("can_change_orders", "orders/FOO/comment", 405),
|
("can_change_orders", "orders/FOO/delete", 302, HTTP_GET),
|
||||||
("can_change_orders", "orders/FOO/locale", 200),
|
("can_change_orders", "orders/FOO/comment", 405, HTTP_GET),
|
||||||
("can_change_orders", "orders/FOO/sendmail", 200),
|
("can_change_orders", "orders/FOO/locale", 200, HTTP_GET),
|
||||||
("can_change_orders", "orders/FOO/1/sendmail", 404),
|
("can_change_orders", "orders/FOO/sendmail", 200, HTTP_GET),
|
||||||
("can_change_orders", "orders/import/", 200),
|
("can_change_orders", "orders/FOO/1/sendmail", 404, HTTP_GET),
|
||||||
("can_change_orders", "orders/import/0ab7b081-92d3-4480-82de-2f8b056fd32f/", 404),
|
("can_change_orders", "orders/import/", 200, HTTP_GET),
|
||||||
("can_view_orders", "orders/FOO/answer/5/", 404),
|
("can_change_orders", "orders/import/0ab7b081-92d3-4480-82de-2f8b056fd32f/", 404, HTTP_GET),
|
||||||
("can_change_orders", "cancel/", 200),
|
("can_view_orders", "orders/FOO/answer/5/", 404, HTTP_GET),
|
||||||
("can_change_vouchers", "vouchers/add", 200),
|
("can_change_orders", "cancel/", 200, HTTP_GET),
|
||||||
("can_change_vouchers", "vouchers/bulk_add", 200),
|
("can_change_vouchers", "vouchers/add", 200, HTTP_GET),
|
||||||
("can_view_vouchers", "vouchers/", 200),
|
("can_change_vouchers", "vouchers/bulk_add", 200, HTTP_GET),
|
||||||
("can_view_vouchers", "vouchers/tags/", 200),
|
("can_view_vouchers", "vouchers/", 200, HTTP_GET),
|
||||||
("can_change_vouchers", "vouchers/1234/", 404),
|
("can_view_vouchers", "vouchers/tags/", 200, HTTP_GET),
|
||||||
("can_change_vouchers", "vouchers/1234/delete", 404),
|
("can_change_vouchers", "vouchers/1234/", 404, HTTP_GET),
|
||||||
("can_view_orders", "waitinglist/", 200),
|
("can_change_vouchers", "vouchers/1234/delete", 404, HTTP_GET),
|
||||||
("can_change_orders", "waitinglist/auto_assign", 405),
|
("can_view_orders", "waitinglist/", 200, HTTP_GET),
|
||||||
("can_change_orders", "waitinglist/action", 405),
|
("can_change_orders", "waitinglist/auto_assign", 405, HTTP_GET),
|
||||||
("can_view_orders", "checkins/", 200),
|
("can_change_orders", "waitinglist/action", 405, HTTP_GET),
|
||||||
("can_view_orders", "checkinlists/", 200),
|
("can_view_orders", "checkins/", 200, HTTP_GET),
|
||||||
("can_view_orders", "checkinlists/1/", 404),
|
("can_view_orders", "checkinlists/", 200, HTTP_GET),
|
||||||
("can_change_event_settings", "checkinlists/add", 200),
|
("can_view_orders", "checkinlists/1/", 404, HTTP_GET),
|
||||||
("can_change_event_settings", "checkinlists/1/change", 404),
|
("can_change_event_settings", "checkinlists/add", 200, HTTP_GET),
|
||||||
("can_change_event_settings", "checkinlists/1/delete", 404),
|
("can_change_event_settings", "checkinlists/1/change", 404, HTTP_GET),
|
||||||
|
("can_change_event_settings", "checkinlists/1/delete", 404, HTTP_GET),
|
||||||
|
|
||||||
# bank transfer
|
# bank transfer
|
||||||
("can_change_orders", "banktransfer/import/", 200),
|
("can_change_orders", "banktransfer/import/", 200, HTTP_GET),
|
||||||
("can_change_orders", "banktransfer/job/1/", 404),
|
("can_change_orders", "banktransfer/job/1/", 404, HTTP_GET),
|
||||||
("can_change_orders", "banktransfer/action/", 200),
|
("can_change_orders", "banktransfer/action/", 200, HTTP_GET),
|
||||||
("can_change_orders", "banktransfer/refunds/", 200),
|
("can_change_orders", "banktransfer/refunds/", 200, HTTP_GET),
|
||||||
("can_change_orders", "banktransfer/export/1/", 404),
|
("can_change_orders", "banktransfer/export/1/", 404, HTTP_GET),
|
||||||
("can_change_orders", "banktransfer/sepa-export/1/", 404),
|
("can_change_orders", "banktransfer/sepa-export/1/", 404, HTTP_GET),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.parametrize("perm,url,code", event_permission_urls)
|
@pytest.mark.parametrize("perm,url,code,http_method", event_permission_urls)
|
||||||
def test_wrong_event_permission(perf_patch, client, env, perm, url, code):
|
def test_wrong_event_permission(perf_patch, client, env, perm, url, code, http_method):
|
||||||
t = Team(
|
t = Team(
|
||||||
organizer=env[2], all_events=True
|
organizer=env[2], all_events=True
|
||||||
)
|
)
|
||||||
@@ -377,13 +382,16 @@ def test_wrong_event_permission(perf_patch, client, env, perm, url, code):
|
|||||||
t.save()
|
t.save()
|
||||||
t.members.add(env[1])
|
t.members.add(env[1])
|
||||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||||
response = client.get('/control/event/dummy/dummy/' + url)
|
if http_method and http_method == HTTP_POST:
|
||||||
|
response = client.post('/control/event/dummy/dummy/' + url)
|
||||||
|
else:
|
||||||
|
response = client.get('/control/event/dummy/dummy/' + url)
|
||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.parametrize("perm,url,code", event_permission_urls)
|
@pytest.mark.parametrize("perm,url,code,http_method", event_permission_urls)
|
||||||
def test_limited_event_permission_for_other_event(perf_patch, client, env, perm, url, code):
|
def test_limited_event_permission_for_other_event(perf_patch, client, env, perm, url, code, http_method):
|
||||||
event2 = Event.objects.create(
|
event2 = Event.objects.create(
|
||||||
organizer=env[2], name='Dummy', slug='dummy2',
|
organizer=env[2], name='Dummy', slug='dummy2',
|
||||||
date_from=now(), plugins='pretix.plugins.banktransfer'
|
date_from=now(), plugins='pretix.plugins.banktransfer'
|
||||||
@@ -393,7 +401,10 @@ def test_limited_event_permission_for_other_event(perf_patch, client, env, perm,
|
|||||||
t.limit_events.add(event2)
|
t.limit_events.add(event2)
|
||||||
|
|
||||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||||
response = client.get('/control/event/dummy/dummy/' + url)
|
if http_method and http_method == HTTP_POST:
|
||||||
|
response = client.post('/control/event/dummy/dummy/' + url)
|
||||||
|
else:
|
||||||
|
response = client.get('/control/event/dummy/dummy/' + url)
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
@@ -416,8 +427,8 @@ def test_current_permission(client, env):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.parametrize("perm,url,code", event_permission_urls)
|
@pytest.mark.parametrize("perm,url,code,http_method", event_permission_urls)
|
||||||
def test_correct_event_permission_all_events(perf_patch, client, env, perm, url, code):
|
def test_correct_event_permission_all_events(perf_patch, client, env, perm, url, code, http_method):
|
||||||
t = Team(organizer=env[2], all_events=True)
|
t = Team(organizer=env[2], all_events=True)
|
||||||
setattr(t, perm, True)
|
setattr(t, perm, True)
|
||||||
t.save()
|
t.save()
|
||||||
@@ -426,13 +437,16 @@ def test_correct_event_permission_all_events(perf_patch, client, env, perm, url,
|
|||||||
session = client.session
|
session = client.session
|
||||||
session['pretix_auth_login_time'] = int(time.time())
|
session['pretix_auth_login_time'] = int(time.time())
|
||||||
session.save()
|
session.save()
|
||||||
response = client.get('/control/event/dummy/dummy/' + url)
|
if http_method and http_method == HTTP_POST:
|
||||||
|
response = client.post('/control/event/dummy/dummy/' + url)
|
||||||
|
else:
|
||||||
|
response = client.get('/control/event/dummy/dummy/' + url)
|
||||||
assert response.status_code == code
|
assert response.status_code == code
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.parametrize("perm,url,code", event_permission_urls)
|
@pytest.mark.parametrize("perm,url,code,http_method", event_permission_urls)
|
||||||
def test_correct_event_permission_limited(perf_patch, client, env, perm, url, code):
|
def test_correct_event_permission_limited(perf_patch, client, env, perm, url, code, http_method):
|
||||||
t = Team(organizer=env[2])
|
t = Team(organizer=env[2])
|
||||||
setattr(t, perm, True)
|
setattr(t, perm, True)
|
||||||
t.save()
|
t.save()
|
||||||
@@ -442,7 +456,10 @@ def test_correct_event_permission_limited(perf_patch, client, env, perm, url, co
|
|||||||
session = client.session
|
session = client.session
|
||||||
session['pretix_auth_login_time'] = int(time.time())
|
session['pretix_auth_login_time'] = int(time.time())
|
||||||
session.save()
|
session.save()
|
||||||
response = client.get('/control/event/dummy/dummy/' + url)
|
if http_method and http_method == HTTP_POST:
|
||||||
|
response = client.post('/control/event/dummy/dummy/' + url)
|
||||||
|
else:
|
||||||
|
response = client.get('/control/event/dummy/dummy/' + url)
|
||||||
assert response.status_code == code
|
assert response.status_code == code
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user