Compare commits

..

17 Commits

Author SHA1 Message Date
Mira Weller
5c599ab056 add back sort button disabled state management 2024-06-25 12:29:09 +02:00
Mira Weller
b2a0893e63 Change description text 2024-06-24 18:37:01 +02:00
Mira Weller
4945003f32 address review comments (improve reorder arrow logic, dimming) 2024-06-24 17:34:42 +02:00
Mira
b7cd10308f Apply suggestions from code review
Co-authored-by: Richard Schreiber <schreiber@rami.io>
2024-06-24 17:34:42 +02:00
Mira
58a9a25515 Fix url in test 2024-06-24 17:34:42 +02:00
Mira Weller
9e23db1660 tests 2024-06-24 17:34:41 +02:00
Mira Weller
2d044b0b02 additional ui changes 2024-06-24 17:34:41 +02:00
Mira Weller
93a9528d66 show sort buttons if user fails at drag and drop, add labels for accessibility 2024-06-24 17:34:41 +02:00
Mira Weller
578666f8e8 sort buttons .sr-only instead of hide 2024-06-24 17:34:41 +02:00
Mira Weller
46ec507442 improve product list sorting ui (allow move between categories, hide up/down arrows if drag-drop is available) 2024-06-24 17:34:41 +02:00
Raphael Michel
4f9297e7d8 Show minimal check-in status in order export (Z#23154920) (#4223)
* Show minimal check-in status in order export (Z#23154920)

* Update src/pretix/helpers/database.py

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

* Review note

---------

Co-authored-by: Mira <weller@rami.io>
2024-06-24 17:34:10 +02:00
Erik Löfman
70b48fdd4b Translations: Update Swedish
Currently translated at 35.2% (1994 of 5664 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/sv/

powered by weblate
2024-06-24 17:20:24 +02:00
Anarion Dunedain
e7b5317431 Translations: Update Polish
Currently translated at 75.1% (4259 of 5664 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/pl/

powered by weblate
2024-06-24 17:20:24 +02:00
Mira
63ef2e70e2 Translations: Update Spanish
Currently translated at 75.3% (174 of 231 strings)

Translation: pretix/pretix (JavaScript parts)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/es/

powered by weblate
2024-06-24 17:20:24 +02:00
Mira
c4db2a48b6 Translations: Update Spanish
Currently translated at 87.6% (4964 of 5664 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/es/

powered by weblate
2024-06-24 17:20:24 +02:00
Reece Needham
de255b021e Translations: Update Spanish
Currently translated at 87.6% (4964 of 5664 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/es/

powered by weblate
2024-06-24 17:20:24 +02:00
Raphael Michel
d3fce71b7f Bump version to 2024.7.0.dev0 2024-06-24 11:05:53 +02:00
18 changed files with 1679 additions and 1189 deletions

View File

@@ -19,4 +19,4 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
__version__ = "2024.6.0"
__version__ = "2024.7.0.dev0"

View File

@@ -37,6 +37,7 @@ from decimal import Decimal
from zoneinfo import ZoneInfo
from django import forms
from django.conf import settings
from django.db.models import (
Case, CharField, Count, DateTimeField, F, IntegerField, Max, Min, OuterRef,
Q, Subquery, Sum, When,
@@ -54,7 +55,7 @@ from openpyxl.comments import Comment
from openpyxl.styles import Font, PatternFill
from pretix.base.models import (
GiftCard, GiftCardTransaction, Invoice, InvoiceAddress, Order,
Checkin, GiftCard, GiftCardTransaction, Invoice, InvoiceAddress, Order,
OrderPosition, Question,
)
from pretix.base.models.orders import (
@@ -541,6 +542,22 @@ class OrderListExporter(MultiSheetListExporter):
).order_by()
qs = base_qs.annotate(
payment_providers=Subquery(p_providers, output_field=CharField()),
checked_in_lists=Subquery(
Checkin.objects.filter(
successful=True,
type=Checkin.TYPE_ENTRY,
position=OuterRef("pk"),
).order_by().values("position").annotate(
c=GroupConcat(
"list__name",
# These appear not to work properly on SQLite. Well, we don't support SQLite outside testing
# anyways.
ordered='sqlite' not in settings.DATABASES['default']['ENGINE'],
distinct='sqlite' not in settings.DATABASES['default']['ENGINE'],
delimiter=", "
)
).values("c")
),
).select_related(
'order', 'order__invoice_address', 'order__customer', 'item', 'variation',
'voucher', 'tax_rule'
@@ -638,6 +655,7 @@ class OrderListExporter(MultiSheetListExporter):
_('Sales channel'), _('Order locale'),
_('E-mail address verified'),
_('External customer ID'),
_('Check-in lists'),
_('Payment providers'),
]
@@ -776,6 +794,7 @@ class OrderListExporter(MultiSheetListExporter):
_('Yes') if order.email_known_to_work else _('No'),
str(order.customer.external_identifier) if order.customer and order.customer.external_identifier else '',
]
row.append(op.checked_in_lists or "")
row.append(', '.join([
str(self.providers.get(p, p)) for p in sorted(set((op.payment_providers or '').split(',')))
if p and p != 'free'

View File

@@ -122,6 +122,16 @@ class ItemCategory(LoggedModel):
return _('{category} (Add-On products)').format(category=str(name))
return str(name)
def get_category_type_display(self):
if self.is_addon:
return _('Add-On products')
else:
return None
@property
def category_type(self):
return 'addon' if self.is_addon else 'normal'
def delete(self, *args, **kwargs):
super().delete(*args, **kwargs)
if self.event:

View File

@@ -32,7 +32,6 @@
<tr>
<th>{% trans "Product categories" %}</th>
<th class="action-col-2"></th>
<th class="action-col-2"></th>
</tr>
</thead>
<tbody data-dnd-url="{% url "control:event.items.categories.reorder" organizer=request.event.organizer.slug event=request.event.slug %}">
@@ -41,18 +40,16 @@
<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>
</td>
<td>
<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>
<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 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>
<button title="{% trans "Move up" %}" 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>
<button title="{% trans "Move down" %}" 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" title="{% trans "Click and drag this button to reorder. Double click to show buttons for reordering." %}"></span>
<a title="{% trans "Edit" %}" 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.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ c.id }}"
class="btn btn-sm btn-default" title="{% trans "Clone" %}" data-toggle="tooltip">
<span class="fa fa-copy"></span>
</a>
<a href="{% url "control:event.items.categories.delete" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
<a title="{% trans "Delete" %}" href="{% url "control:event.items.categories.delete" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
</td>
</tr>
{% endfor %}

View File

@@ -56,8 +56,7 @@
<th>{% trans "Internal name" %}</th>
<th></th>
<th></th>
<th>{% trans "Products" %}</th>
<th class="action-col-2"></th>
<th colspan="2">{% trans "Products" %}</th>
<th class="action-col-2"></th>
</tr>
</thead>
@@ -102,9 +101,10 @@
{% endif %}
{% endif %}
</td>
<td>
<td {% if d.benefit_same_products %}colspan="2"{% endif %}>
{% if not d.benefit_same_products %}{% trans "Condition:" %}{% endif %}
{% if d.condition_all_products %}
<em>{% trans "All" %}</em>
<ul><li><em>{% trans "All" %}</em></li></ul>
{% else %}
<ul>
{% for item in d.condition_limit_products.all %}
@@ -115,18 +115,28 @@
</ul>
{% endif %}
</td>
<td>
{% if not d.benefit_same_products %}
<td>
{% trans "Applies to:" %}
<ul>
{% for item in d.benefit_limit_products.all %}
<li>
<a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=item.id %}">{{ item }}</a>
</li>
{% endfor %}
</ul>
</td>
{% endif %}
<td class="text-right flip">
<button formaction="{% url "control:event.items.discounts.up" organizer=request.event.organizer.slug event=request.event.slug discount=d.id %}"
class="btn btn-default btn-sm sortable-up"
class="btn btn-default btn-sm sortable-up" title="{% trans "Move up" %}"
{% if forloop.counter0 == 0 and not page_obj.has_previous %}
disabled{% endif %}><i class="fa fa-arrow-up"></i></button>
<button formaction="{% url "control:event.items.discounts.down" organizer=request.event.organizer.slug event=request.event.slug discount=d.id %}"
class="btn btn-default btn-sm sortable-down"
class="btn btn-default btn-sm sortable-down" title="{% trans "Move 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 class="text-right flip">
<span class="dnd-container" title="{% trans "Click and drag this button to reorder. Double click to show buttons for reordering." %}"></span>
<a href="{% url "control:event.items.discounts.edit" organizer=request.event.organizer.slug event=request.event.slug discount=d.id %}"
class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
<a href="{% url "control:event.items.discounts.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ d.id }}"

View File

@@ -1,13 +1,15 @@
{% extends "pretixcontrol/items/base.html" %}
{% load i18n %}
{% load money %}
{% block title %}{% trans "Products" %}{% endblock %}
{% block inside %}
{% blocktrans asvar s_taxes %}taxes{% endblocktrans %}
<h1>{% trans "Products" %}</h1>
<p>
{% blocktrans trimmed %}
Below, you find a list of all available products. You can click on a product name to inspect and change
product details. You can also use the buttons on the right to change the order of products within a
give category.
product details. You can also use the buttons on the right to change the order of products or move
products to a different category.
{% endblocktrans %}
</p>
{% if items|length == 0 %}
@@ -29,7 +31,7 @@
<form method="post">
{% csrf_token %}
<div class="table-responsive">
<table class="table table-condensed table-hover">
<table class="table table-condensed table-hover table-items">
<thead>
<tr>
<th>{% trans "Product name" %}</th>
@@ -37,16 +39,24 @@
<th class="iconcol"></th>
<th class="iconcol"></th>
<th class="iconcol"></th>
<th>{% trans "Category" %}</th>
<th class="action-col-2"><span class="sr-only">Move</span></th>
<th class="text-right flip">{% trans "Default price" %}</th>
<th class="action-col-2"><span class="sr-only">Edit</span></th>
</tr>
</thead>
{% regroup items by category as 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 %}
{% if forloop.counter0 == 0 and i.category %}<tr class="sortable-disabled"><th colspan="8" scope="colgroup" class="text-muted">{{ i.category }}</th></tr>{% endif %}
{% for c, items in cat_list %}
{% if c %}
<tbody>
<tr class="sortable-disabled"><th colspan="9" scope="colgroup" class="text-muted">
{{ c.name }}{% if c.category_type != "normal" %} <span class="font-normal">({{ c.get_category_type_display }})</span>{% endif %}
<a href="{% url "control:event.items.categories.edit" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" title="{% trans "Edit" %}"><span class="fa fa-edit fa-fw"></span></a>
</th></tr>
</tbody>
{% endif %}
<tbody data-dnd-url="{% url "control:event.items.reorder" organizer=request.event.organizer.slug event=request.event.slug category=c.id|default:0 %}"
data-dnd-group="items">
{% for i in items %}
{% if forloop.counter0 == 0 and i.category %}{% endif %}
<tr data-dnd-id="{{ i.id }}" {% if not i.active %}class="row-muted"{% endif %}>
<td><strong>
{% if not i.active %}<strike>{% endif %}
@@ -92,15 +102,15 @@
</td>
<td>
{% if i.var_count %}
<span class="fa fa-th-large fa-fw text-muted" data-toggle="tooltip" title="{% trans "Product with variations" %}"></span>
<span class="fa fa-bars fa-fw text-muted" data-toggle="tooltip" title="{% trans "Product with variations" %}"></span>
{% endif %}
</td>
<td>
{% if i.category.is_addon %}
<span class="fa fa-puzzle-piece fa-fw text-muted" data-toggle="tooltip"
<span class="fa fa-plus-square fa-fw text-muted" data-toggle="tooltip"
title="{% trans "Only available as an add-on product" %}"></span>
{% elif i.require_bundling %}
<span class="fa fa-puzzle-piece fa-fw text-muted" data-toggle="tooltip"
<span class="fa fa-plus-square fa-fw text-muted" data-toggle="tooltip"
title="{% trans "Only available as part of a bundle" %}"></span>
{% elif i.hide_without_voucher %}
<span class="fa fa-tags fa-fw text-muted" data-toggle="tooltip"
@@ -110,16 +120,29 @@
title="{% trans "Can only be bought using a voucher" %}"></span>
{% endif %}
</td>
<td>{% if i.category %}{{ i.category.name }}{% endif %}</td>
<td>
<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>
<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 class="text-right flip">
{% if i.free_price %}
<span class="fa fa-edit fa-fw text-muted" data-toggle="tooltip" title="{% trans "Free price input" %}">
</span>
{% endif %}
{{ i.default_price|money:request.event.currency }}
{% if i.original_price %}<strike class="text-muted">{{ i.original_price|money:request.event.currency }}</strike>{% endif %}
{% if i.tax_rule and i.default_price %}
<br/>
<small class="text-muted">
{% blocktrans trimmed with rate=i.tax_rule.rate|floatformat:-2 taxname=i.tax_rule.name|default:s_taxes %}
incl. {{ rate }}% {{ taxname }}
{% endblocktrans %}
</small>
{% endif %}
</td>
<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>
<button title="{% trans "Move up" %}" 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>
<button title="{% trans "Move down" %}" 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" title="{% trans "Click and drag this button to reorder. Double click to show buttons for reordering." %}"></span>
<a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-default btn-sm" title="{% trans "Edit" %}"><i class="fa fa-edit"></i></a>
<a href="{% url "control:event.items.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ i.id }}" class="btn btn-default btn-sm" title="{% trans "Clone" %}" data-toggle="tooltip"><i class="fa fa-copy"></i></a>
<a href="{% url "control:event.items.delete" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
<a href="{% url "control:event.items.delete" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-danger btn-sm" title="{% trans "Delete" %}"><i class="fa fa-trash"></i></a>
</td>
</tr>
{% endfor %}

View File

@@ -51,9 +51,9 @@
{% endif %}
</td>
<td>
<button formaction="{% url "control:organizer.property.up" organizer=request.organizer.slug property=p.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>
<button formaction="{% url "control:organizer.property.down" organizer=request.organizer.slug property=p.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>
<button title="{% trans "Move up" %}" formaction="{% url "control:organizer.property.up" organizer=request.organizer.slug property=p.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>
<button title="{% trans "Move down" %}" formaction="{% url "control:organizer.property.down" organizer=request.organizer.slug property=p.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" title="{% trans "Click and drag this button to reorder. Double click to show buttons for reordering." %}"></span>
</td>
<td class="text-right flip">
<a href="{% url "control:organizer.property.edit" organizer=request.organizer.slug property=p.id %}"

View File

@@ -293,7 +293,7 @@ urlpatterns = [
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+)/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/reorder/(?P<category>\d+)/$', 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/typeahead/meta/$', typeahead.item_meta_values, name='event.items.meta.typeahead'),
re_path(r'^items/select2$', typeahead.items_select2, name='event.items.select2'),

View File

@@ -35,6 +35,7 @@
import json
from collections import OrderedDict, namedtuple
from itertools import groupby
from json.decoder import JSONDecodeError
from django.contrib import messages
@@ -113,6 +114,8 @@ class ItemList(ListView):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['sales_channels'] = get_all_sales_channels()
items_by_category = {cat: list(items) for cat, items in groupby(ctx['items'], lambda item: item.category)}
ctx['cat_list'] = [(cat, items_by_category.get(cat, [])) for cat in [None, *self.request.event.categories.all()]]
return ctx
@@ -169,7 +172,7 @@ def item_move_down(request, organizer, event, item):
@transaction.atomic
@event_permission_required("can_change_items")
@require_http_methods(["POST"])
def reorder_items(request, organizer, event):
def reorder_items(request, organizer, event, category):
try:
ids = json.loads(request.body.decode('utf-8'))['ids']
except (JSONDecodeError, KeyError, ValueError):
@@ -180,23 +183,21 @@ def reorder_items(request, organizer, event):
if len(input_items) != len(ids):
raise Http404(_("Some of the provided object 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 objects have been selected."))
if int(category):
target_category = request.event.categories.get(id=category)
else:
target_category = None
for i in input_items:
pos = ids.index(str(i.pk))
if pos != i.position: # Save unneccessary UPDATE queries
if pos != i.position or target_category != i.category: # Save unneccessary UPDATE queries
i.position = pos
i.save(update_fields=['position'])
i.category = target_category
i.save(update_fields=['position', 'category_id'])
i.log_action(
'pretix.event.item.reordered', user=request.user, data={
'position': i,
'category': target_category and target_category.pk,
}
)

View File

@@ -64,7 +64,8 @@ def casual_reads():
class GroupConcat(Aggregate):
function = 'group_concat'
template = '%(function)s(%(field)s, "%(separator)s")'
template = '%(function)s(%(distinct)s%(field)s, "%(separator)s")'
allow_distinct = True
def __init__(self, *expressions, ordered=False, **extra):
self.ordered = ordered
@@ -73,19 +74,17 @@ class GroupConcat(Aggregate):
extra.update({'separator': ','})
super().__init__(*expressions, **extra)
def as_postgresql(self, compiler, connection):
def as_postgresql(self, compiler, connection, **extra_context):
if self.ordered:
return super().as_sql(
compiler, connection,
function='string_agg',
template="%(function)s(%(field)s::text, '%(separator)s' ORDER BY %(field)s ASC)",
)
template = "%(function)s(%(distinct)s%(field)s::text, '%(separator)s' ORDER BY %(field)s::text ASC)"
else:
return super().as_sql(
compiler, connection,
function='string_agg',
template="%(function)s(%(field)s::text, '%(separator)s')",
)
template = "%(function)s(%(distinct)s%(field)s::text, '%(separator)s')"
return super().as_sql(
compiler, connection,
function='string_agg',
template=template,
**extra_context,
)
class ReplicaRouter:

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-06-24 08:20+0000\n"
"PO-Revision-Date: 2024-01-31 04:00+0000\n"
"Last-Translator: Eduardo Fernandez <sistemas@oyasama.es>\n"
"PO-Revision-Date: 2024-06-24 15:12+0000\n"
"Last-Translator: Mira <weller@rami.io>\n"
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix/"
"es/>\n"
"Language: es\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.3.1\n"
"X-Generator: Weblate 5.5.5\n"
#: pretix/_base_settings.py:78
msgid "English"
@@ -37,7 +37,7 @@ msgstr "Árabe"
#: pretix/_base_settings.py:82
msgid "Catalan"
msgstr ""
msgstr "catalán"
#: pretix/_base_settings.py:83
msgid "Chinese (simplified)"
@@ -117,7 +117,7 @@ msgstr "Ruso"
#: pretix/_base_settings.py:102
msgid "Slovak"
msgstr ""
msgstr "eslovaco"
#: pretix/_base_settings.py:103
msgid "Spanish"
@@ -2876,7 +2876,7 @@ msgstr "Código del cupón"
#: pretix/base/forms/__init__.py:118
#, python-brace-format
msgid "You can use {markup_name} in this field."
msgstr ""
msgstr "Se puede usar {markup_name} en este campo."
#: pretix/base/forms/__init__.py:178
#, python-format
@@ -3095,6 +3095,10 @@ msgid ""
"up. Please note: to use literal \"{\" or \"}\", you need to double them as "
"\"{{\" and \"}}\"."
msgstr ""
"Hay un error con el sintaxis de marcador de posición. Por favor, asegúrase "
"de que las llaves de apertura «{» y las llaves de cierre «}» de sus "
"marcadores de posición coinciden. Tenga en cuenta: para usar «{» o «}» "
"literalmente, se debe teclearlas dos veces como «{{» o «}}» respectivamente."
#: pretix/base/forms/validators.py:72 pretix/control/views/event.py:771
#, fuzzy, python-format
@@ -4601,7 +4605,7 @@ msgstr "Esperando, producto no disponible"
#: pretix/base/models/items.py:382
#: pretix/control/templates/pretixcontrol/subevents/fragment_unavail_mode_indicator.html:5
msgid "Show info text if unavailable"
msgstr ""
msgstr "Muestra el texto informativo, si no es disponible"
#: pretix/base/models/items.py:389 pretix/base/models/items.py:704
msgid "Don't use re-usable media, use regular one-off tickets"
@@ -5964,6 +5968,9 @@ msgid ""
"up two-factor authentication or leave the team. The setting may take a few "
"minutes to become effective for all users."
msgstr ""
"Si activado, todos los miembros del equipo requerirán configurar la "
"autenticación de dos factores o dejar el equipo. El ajuste podría tardar "
"pocos minutos para activar para todos los usuarios."
#: pretix/base/models/organizer.py:275
msgid "Can create events"
@@ -6240,6 +6247,9 @@ msgid ""
"of a specific product, you can also select a quota. In this case, all "
"products assigned to this quota can be selected."
msgstr ""
"Este producto añadirá al carrito del usuario si está usado el vale. En vez "
"de un producto específico, se puede seleccionar una cuota. En este caso, "
"todos los productos que están asignados al esta cuota se pueden seleccionar."
#: pretix/base/models/vouchers.py:265
msgid "This variation of the product select above is being used."
@@ -9651,6 +9661,11 @@ msgid ""
"still people on the waiting list. Vouchers that have already been sent "
"remain active."
msgstr ""
"La lista de espera estará deshabilitada después de esta fecha. Esto "
"significa que nadie puede añadirse a la lista; sin embargo, las entradas "
"estarán disponibles para la venta de nuevo si permite la cuota, incluso si "
"hay alguien en la lista de espera. Los vales que ya se han enviado "
"permanecerán activos."
#: pretix/base/settings.py:1420
msgid "Ask for a name"
@@ -9894,7 +9909,7 @@ msgstr "Ocultar todas las fechas pasadas del calendario"
#: pretix/base/settings.py:1664 pretix/base/settings.py:1673
msgid "No modifications after order was submitted"
msgstr ""
msgstr "Ningunas modificaciones se entregaron después del pedido"
#: pretix/base/settings.py:1665 pretix/base/settings.py:1674
#, fuzzy
@@ -9904,7 +9919,7 @@ msgstr "Sólo las órdenes pendientes o pagadas pueden ser cambiadas."
#: pretix/base/settings.py:1666 pretix/base/settings.py:1675
msgid "Both the attendee and the person who ordered can make changes"
msgstr ""
msgstr "Ambos el/la asistente y el/la comprador(a) pueden hacer cambios"
#: pretix/base/settings.py:1670
#, fuzzy
@@ -9923,6 +9938,8 @@ msgid ""
"By default, no more modifications are possible for an order as soon as one "
"of the tickets in the order has been checked in."
msgstr ""
"Por defecto, las modificaciones del pedido no se estarán permitidas una vez "
"que una de las entradas del pedido se hayan canjeado."
#: pretix/base/settings.py:1696
msgid "Last date of modifications"
@@ -16020,6 +16037,8 @@ msgid ""
"A first login using {agent_type} on {os_type} from {country} has been "
"detected."
msgstr ""
"Se ha detectado un inicio de sesión nuevo por {agent_type} usando {os_type} "
"de {country}."
#: pretix/control/logdisplay.py:460 pretix/control/views/user.py:489
#: pretix/control/views/user.py:549 pretix/control/views/user.py:608
@@ -18110,6 +18129,21 @@ msgid ""
"Best regards, \n"
"Your %(instance)s team\n"
msgstr ""
"Hola,\n"
"\n"
"Se ha detectado un inicio de sesión de tu cuenta %(instance)s por una "
"ubicación nueva o inusual. Se inició el sesión por %(agent)s de %(os)s de "
"%(country)s.\n"
"\n"
"Si tú hiciste esto, puedes ignorar este correo.\n"
"\n"
"Si no fuiste tú, recomendamos que cambies la contraseña en los ajustes de "
"cuenta:\n"
"\n"
"%(url)s\n"
"\n"
"Saludos cordiales,\n"
"Su equipo de %(instance)s\n"
#: pretix/control/templates/pretixcontrol/email/security_notice.txt:1
#, python-format
@@ -19970,52 +20004,51 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/global_sysreport.html:16
msgid "January"
msgstr "Enero"
msgstr "enero"
#: pretix/control/templates/pretixcontrol/global_sysreport.html:17
msgid "February"
msgstr "Febrero"
msgstr "febrero"
#: pretix/control/templates/pretixcontrol/global_sysreport.html:18
#, fuzzy
msgid "March"
msgstr "Marzo"
msgstr "marzo"
#: pretix/control/templates/pretixcontrol/global_sysreport.html:19
msgid "April"
msgstr "Abril"
msgstr "abril"
#: pretix/control/templates/pretixcontrol/global_sysreport.html:20
msgid "May"
msgstr "Mayo"
msgstr "mayo"
#: pretix/control/templates/pretixcontrol/global_sysreport.html:21
msgid "June"
msgstr "Junio"
msgstr "junio"
#: pretix/control/templates/pretixcontrol/global_sysreport.html:22
msgid "July"
msgstr "Julio"
msgstr "julio"
#: pretix/control/templates/pretixcontrol/global_sysreport.html:23
msgid "August"
msgstr "Agosto"
msgstr "agosto"
#: pretix/control/templates/pretixcontrol/global_sysreport.html:24
msgid "September"
msgstr "Septiembre"
msgstr "septiembre"
#: pretix/control/templates/pretixcontrol/global_sysreport.html:25
msgid "October"
msgstr "Octubre"
msgstr "octubre"
#: pretix/control/templates/pretixcontrol/global_sysreport.html:26
msgid "November"
msgstr "Noviembre"
msgstr "noviembre"
#: pretix/control/templates/pretixcontrol/global_sysreport.html:27
msgid "December"
msgstr "Diciembre"
msgstr "diciembre"
#: pretix/control/templates/pretixcontrol/global_sysreport.html:32
#, fuzzy

View File

@@ -8,16 +8,16 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-06-24 08:20+0000\n"
"PO-Revision-Date: 2023-11-07 14:00+0000\n"
"Last-Translator: Zona Vip <contacto@zonavip.mx>\n"
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix-"
"js/es/>\n"
"PO-Revision-Date: 2024-06-24 15:12+0000\n"
"Last-Translator: Mira <weller@rami.io>\n"
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/"
"pretix-js/es/>\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.1.1\n"
"X-Generator: Weblate 5.5.5\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -1069,51 +1069,51 @@ msgstr "Dom"
#: pretix/static/pretixpresale/js/widget/widget.js:78
msgid "January"
msgstr "Enero"
msgstr "enero"
#: pretix/static/pretixpresale/js/widget/widget.js:79
msgid "February"
msgstr "Febrero"
msgstr "febrero"
#: pretix/static/pretixpresale/js/widget/widget.js:80
msgid "March"
msgstr "Marzo"
msgstr "marzo"
#: pretix/static/pretixpresale/js/widget/widget.js:81
msgid "April"
msgstr "Abril"
msgstr "abril"
#: pretix/static/pretixpresale/js/widget/widget.js:82
msgid "May"
msgstr "Mayo"
msgstr "mayo"
#: pretix/static/pretixpresale/js/widget/widget.js:83
msgid "June"
msgstr "Junio"
msgstr "junio"
#: pretix/static/pretixpresale/js/widget/widget.js:84
msgid "July"
msgstr "Julio"
msgstr "julio"
#: pretix/static/pretixpresale/js/widget/widget.js:85
msgid "August"
msgstr "Agosto"
msgstr "agosto"
#: pretix/static/pretixpresale/js/widget/widget.js:86
msgid "September"
msgstr "Septiembre"
msgstr "septiembre"
#: pretix/static/pretixpresale/js/widget/widget.js:87
msgid "October"
msgstr "Octubre"
msgstr "octubre"
#: pretix/static/pretixpresale/js/widget/widget.js:88
msgid "November"
msgstr "Noviembre"
msgstr "noviembre"
#: pretix/static/pretixpresale/js/widget/widget.js:89
msgid "December"
msgstr "Diciembre"
msgstr "diciembre"
#~ msgctxt "widget"
#~ msgid "See variations"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,48 +1,78 @@
/*global $, Sortable*/
$(function () {
$("[data-dnd-url]").each(function(){
var container = $(this),
const allContainers = $("[data-dnd-url]");
function updateAllSortButtonStates() {
allContainers.each(function() { updateSortButtonState($(this)); });
}
function updateSortButtonState(container) {
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);
}
}
let didSort = false, lastClick = 0;
allContainers.each(function(){
const container = $(this),
url = container.data("dnd-url"),
handle = $('<span class="btn btn-default btn-sm dnd-sort-handle"><i class="fa fa-arrows"></i></span>');
container.find(".dnd-container").append(handle);
if (container.find("[data-dnd-id]").length < 2) {
if (!sessionStorage.dndShowMoveButtons) {
container.find(".sortable-up, .sortable-down").addClass("sr-only").on("click", function () {
sessionStorage.dndShowMoveButtons = 'true';
});
}
if (container.find("[data-dnd-id]").length < 2 && !container.data("dnd-group")) {
handle.addClass("disabled");
return;
}
function maybeShowSortButtons() {
if (Date.now() - lastClick < 3000) {
$("[data-dnd-url] .sortable-up, [data-dnd-url] .sortable-down").removeClass("sr-only");
updateAllSortButtonStates();
}
lastClick = Date.now();
}
container.find(".dnd-sort-handle").on("mouseup", maybeShowSortButtons);
const group = container.data("dnd-group");
const containers = group ? container.parent().find('[data-dnd-group="' + group + '"]') : container;
Sortable.create(container.get(0), {
filter: ".sortable-disabled",
handle: ".dnd-sort-handle",
group: group,
onMove: function (evt) {
return evt.related.className.indexOf('sortable-disabled') === -1;
},
onStart: function (evt) {
container.addClass("sortable-dragarea");
containers.addClass("sortable-dragarea");
container.parent().addClass("sortable-sorting");
didSort = false;
},
onEnd: function (evt) {
container.removeClass("sortable-dragarea");
containers.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);
if (!didSort) {
maybeShowSortButtons();
} else {
$("[data-dnd-url] .sortable-up, [data-dnd-url] .sortable-down").addClass("sr-only");
delete sessionStorage.dndShowMoveButtons;
}
},
onSort: function (evt){
var container = $(evt.to),
ids = container.find("[data-dnd-id]").toArray().map(function (e) { return e.dataset.dndId; });
if (evt.target !== evt.to) return;
didSort = true;
const ids = container.find("[data-dnd-id]").toArray().map(function (e) { return e.dataset.dndId; });
$.ajax(
{
'type': 'POST',

View File

@@ -841,9 +841,21 @@ h1 .label {
tbody[data-dnd-url] {
transition: opacity 1s;
}
.table-items tbody + tbody {
border-top-width: 1px;
}
.table-items tbody[data-dnd-url]:after {
content:'';
display: table-row;
height: 0.5em;
}
.sortable-sorting tbody:not(.sortable-dragarea) {
opacity: .4;
}
.font-normal {
font-style: normal;
font-weight: normal;
}
tbody th {
background: $table-bg-hover;
}

View File

@@ -402,6 +402,24 @@ class ItemsTest(ItemFormTest):
self.item2.refresh_from_db()
assert self.item1.position < self.item2.position
def test_reorder(self):
self.client.post('/control/event/%s/%s/items/reorder/0/' % (self.orga1.slug, self.event1.slug), {
'ids': [str(self.item2.id), str(self.item1.id)],
}, content_type='application/json')
self.item1.refresh_from_db()
self.item2.refresh_from_db()
assert self.item1.position > self.item2.position
assert self.item1.category is None
assert self.item2.category is None
self.client.post('/control/event/%s/%s/items/reorder/%s/' % (self.orga1.slug, self.event1.slug, self.addoncat.id), {
'ids': [str(self.item1.id), str(self.item2.id)],
}, content_type='application/json')
self.item1.refresh_from_db()
self.item2.refresh_from_db()
assert self.item1.position < self.item2.position
assert self.item1.category.id == self.addoncat.id
assert self.item2.category.id == self.addoncat.id
def test_create(self):
self.client.post('/control/event/%s/%s/items/add' % (self.orga1.slug, self.event1.slug), {
'name_0': 'T-Shirt',

View File

@@ -323,7 +323,7 @@ event_permission_urls = [
("can_change_items", "items/add", 200, HTTP_GET),
("can_change_items", "items/1/up", 404, HTTP_POST),
("can_change_items", "items/1/down", 404, HTTP_POST),
("can_change_items", "items/reorder", 400, HTTP_POST),
("can_change_items", "items/reorder/2/", 400, HTTP_POST),
("can_change_items", "items/1/delete", 404, HTTP_GET),
# ("can_change_items", "categories/", 200),
# We don't have to create categories and similar objects