Compare commits

..

32 Commits

Author SHA1 Message Date
Mira Weller
4619901a37 docs: update fee_type options 2025-02-25 14:16:32 +01:00
Mira Weller
ee921a6331 docs: None -> null 2025-02-25 14:04:27 +01:00
Mira Weller
3310e9670b Add create_fees to example 2025-02-25 13:57:33 +01:00
Mira Weller
93570d42c7 Consistent order of examples 2025-02-25 13:57:01 +01:00
Mira Weller
598527073c Fix example in docs 2025-02-25 13:22:59 +01:00
Raphael Michel
d011651565 API: Allow to add a fee to an order (#4806) 2025-02-10 17:24:46 +01:00
Raphael Michel
0079be68d3 Allow plugins to add data to the order API (Z#23179688) (#4822)
* Allow plugins to add data to the order API (Z#23179688)

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

Co-authored-by: Richard Schreiber <schreiber@rami.io>

* Fix failing test

---------

Co-authored-by: Richard Schreiber <schreiber@rami.io>
2025-02-10 14:06:20 +01:00
Kian Cross
5bf6980a7b Improve date format heuristic for bank statement import (#4814)
When dates are ambiguous, use the event's locale to infer whether the
day or month comes first. Since regions follow certain date format
conventions, this helps make more accurate guesses.
2025-02-10 11:33:14 +01:00
Kian Cross
7e8ef47537 Add delete button to voucher details page (#4815) 2025-02-10 11:32:24 +01:00
Raphael Michel
86120d0296 Enable Finish language 2025-02-10 10:59:35 +01:00
Karoliina Grohn
e2086a8eca Translations: Update Finnish
Currently translated at 65.8% (3848 of 5846 strings)

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

powered by weblate
2025-02-10 10:59:05 +01:00
Hannu Kaakinen
ad7c4a957d Translations: Update Finnish
Currently translated at 65.8% (3848 of 5846 strings)

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

powered by weblate
2025-02-10 10:59:05 +01:00
Karoliina Grohn
f1fbb08c2b Translations: Update Finnish
Currently translated at 62.4% (3651 of 5846 strings)

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

powered by weblate
2025-02-10 10:59:05 +01:00
Hannu Kaakinen
73b58cfb89 Translations: Update Finnish
Currently translated at 62.4% (3651 of 5846 strings)

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

powered by weblate
2025-02-10 10:59:05 +01:00
Hannu Kaakinen
4323366ec3 Translations: Update Finnish
Currently translated at 58.1% (3401 of 5846 strings)

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

powered by weblate
2025-02-10 10:59:05 +01:00
Karoliina Grohn
4d6b63e1c2 Translations: Update Finnish
Currently translated at 54.3% (3177 of 5846 strings)

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

powered by weblate
2025-02-10 10:59:05 +01:00
Hannu Kaakinen
6edc12a89f Translations: Update Finnish
Currently translated at 54.3% (3177 of 5846 strings)

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

powered by weblate
2025-02-10 10:59:05 +01:00
Hannu Kaakinen
e473d0bfce Translations: Update Finnish
Currently translated at 51.7% (3028 of 5846 strings)

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

powered by weblate
2025-02-10 10:59:05 +01:00
Hannu Kaakinen
42469402b6 Translations: Update Finnish
Currently translated at 41.7% (2440 of 5846 strings)

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

powered by weblate
2025-02-10 10:59:05 +01:00
Hannu Kaakinen
fd70d567e0 Translations: Update Finnish
Currently translated at 39.5% (2310 of 5846 strings)

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

powered by weblate
2025-02-10 10:59:05 +01:00
Hannu Kaakinen
2b4d70fa30 Translations: Update Finnish
Currently translated at 37.7% (2207 of 5846 strings)

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

powered by weblate
2025-02-10 10:59:05 +01:00
조정화
3c27aa8591 Translations: Update Korean
Currently translated at 2.1% (125 of 5846 strings)

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

powered by weblate
2025-02-10 10:59:05 +01:00
Hannu Kaakinen
a1d6d636a8 Translations: Update Finnish
Currently translated at 36.9% (2159 of 5846 strings)

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

powered by weblate
2025-02-10 10:59:05 +01:00
Johanna Ketola
373f9e666f Translations: Update Finnish
Currently translated at 36.9% (2159 of 5846 strings)

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

powered by weblate
2025-02-10 10:59:05 +01:00
Karoliina Grohn
e524055249 Translations: Update Finnish
Currently translated at 36.9% (2159 of 5846 strings)

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

powered by weblate
2025-02-10 10:59:05 +01:00
Mira
85ae181ce8 Add missing return (#4823) 2025-02-10 10:54:56 +01:00
Raphael Michel
cfae2c62c3 OIDC: Fix incorrect error handling in server implementation 2025-02-07 18:04:46 +01:00
Mira
9c781a174d Fix Checkin logdisplay for deleted events (#4821) 2025-02-07 14:25:57 +01:00
Mira
f7cba6a2bd Fix log display for checkins and order splits (Z#23181229) (#4818)
- Fix link in pretix.event.order.changed.split
- Add link to existing order in pretix.event.order.changed.split_from
- Fix display of checkin entries without datetime in data
- Add additional info for admins (action type, linked content object)
2025-02-07 11:53:17 +01:00
Raphael Michel
c4436ca319 Change wording for "show_dates_on_frontpage" setting (#4798) 2025-02-06 14:55:15 +01:00
Martin Gross
c87401ef5d Widget: Prefill email_repeat with data-email attribute (Z#23181657) (#4813) 2025-02-06 12:07:43 +01:00
Raphael Michel
a53def0947 Fix CSRF token of admin mode button being removed if e.g. support menu is clicked (#4812) 2025-02-06 12:07:10 +01:00
24 changed files with 3097 additions and 1949 deletions

View File

@@ -75,8 +75,9 @@ positions list of objects List of order p
fees list of objects List of fees included in the order total. By default, only
non-canceled fees are included.
├ id integer Internal ID of the fee record
├ fee_type string Type of fee (currently ``payment``, ``passbook``,
``other``)
├ fee_type string Type of fee (currently ``payment``, ``shipping``,
``service``, ``cancellation``, ``insurance``, ``late``,
``other``, ``giftcard``)
├ value money (string) Fee amount
├ description string Human-readable string with more details (can be empty)
├ internal_type string Internal string (i.e. ID of the payment provider),
@@ -109,6 +110,7 @@ cancellation_date datetime Time of order c
Will not be set for partial cancellations and is not
reliable for orders that have been cancelled,
reactivated and cancelled again.
plugin_data object Additional data added by plugins.
===================================== ========================== =======================================================
@@ -164,6 +166,10 @@ cancellation_date datetime Time of order c
The ``tax_code`` attribute has been added.
.. versionchanged:: 2025.2
The ``plugin_data`` attribute has been added.
.. _order-position-resource:
Order position resource
@@ -251,6 +257,7 @@ seat objects The assigned se
pdf_data object Data object required for ticket PDF generation. By default,
this field is missing. It will be added only if you add the
``pdf_data=true`` query parameter to your request.
plugin_data object Additional data added by plugins.
===================================== ========================== =======================================================
.. versionchanged:: 4.16
@@ -265,6 +272,10 @@ pdf_data object Data object req
The ``tax_code`` attribute has been added.
.. versionchanged:: 2025.2
The ``plugin_data`` attribute has been added.
.. _order-payment-resource:
Order payment resource
@@ -461,7 +472,8 @@ List of all orders
"output": "pdf",
"url": "https://pretix.eu/api/v1/organizers/bigevents/events/sampleconf/orderpositions/23442/download/pdf/"
}
]
],
"plugin_data": {}
}
],
"downloads": [
@@ -483,7 +495,8 @@ List of all orders
}
],
"refunds": [],
"cancellation_date": null
"cancellation_date": null,
"plugin_data": {}
}
]
}
@@ -702,7 +715,8 @@ Fetching individual orders
"output": "pdf",
"url": "https://pretix.eu/api/v1/organizers/bigevents/events/sampleconf/orderpositions/23442/download/pdf/"
}
]
],
"plugin_data": {}
}
],
"downloads": [
@@ -724,7 +738,8 @@ Fetching individual orders
}
],
"refunds": [],
"cancellation_date": null
"cancellation_date": null,
"plugin_data": {}
}
:param organizer: The ``slug`` field of the organizer to fetch
@@ -1671,7 +1686,8 @@ List of all order positions
"output": "pdf",
"url": "https://pretix.eu/api/v1/organizers/bigevents/events/sampleconf/orderpositions/23442/download/pdf/"
}
]
],
"plugin_data": {}
}
]
}
@@ -1798,7 +1814,8 @@ Fetching individual positions
"output": "pdf",
"url": "https://pretix.eu/api/v1/organizers/bigevents/events/sampleconf/orderpositions/23442/download/pdf/"
}
]
],
"plugin_data": {}
}
:param organizer: The ``slug`` field of the organizer to fetch
@@ -2228,6 +2245,9 @@ otherwise, such as splitting an order or changing fees.
* ``cancel_fees``: A list of objects with the single key ``fee`` specifying an order fee ID.
* ``create_fees``: A list of objects describing new order fees with the fields ``fee_type``, ``value``, ``description``,
``internal_type``, ``tax_rule``
* ``recalculate_taxes``: If set to ``"keep_net"``, all taxes will be recalculated based on the tax rule and invoice
address, the net price will be kept. If set to ``"keep_gross"``, the gross price will be kept. If set to ``null``
(the default) the taxes are not recalculated.
@@ -2247,17 +2267,12 @@ otherwise, such as splitting an order or changing fees.
Content-Type: application/json
{
"cancel_positions": [
{
"position": 12373
}
],
"patch_positions": [
{
"position": 12374,
"body": {
"item": 12,
"variation": None,
"variation": null,
"subevent": 562,
"seat": "seat-guid-2",
"price": "99.99",
@@ -2265,6 +2280,11 @@ otherwise, such as splitting an order or changing fees.
}
}
],
"cancel_positions": [
{
"position": 12373
}
],
"split_positions": [
{
"position": 12375
@@ -2273,7 +2293,7 @@ otherwise, such as splitting an order or changing fees.
"create_positions": [
{
"item": 12,
"variation": None,
"variation": null,
"subevent": 562,
"seat": "seat-guid-2",
"price": "99.99",
@@ -2281,12 +2301,7 @@ otherwise, such as splitting an order or changing fees.
"attendee_name": "Peter",
}
],
"cancel_fees": [
{
"fee": 49
}
],
"change_fees": [
"patch_fees": [
{
"fee": 51,
"body": {
@@ -2294,6 +2309,20 @@ otherwise, such as splitting an order or changing fees.
}
}
],
"cancel_fees": [
{
"fee": 49
}
],
"create_fees": [
{
"fee_type": "other",
"value": "1.50",
"description": "Example Fee",
"internal_type": "",
"tax_rule": 15
}
],
"reissue_invoice": true,
"send_email": true,
"recalculate_taxes": "keep_gross"

View File

@@ -103,4 +103,4 @@ API
.. automodule:: pretix.api.signals
:no-index:
:members: register_device_security_profile
:members: register_device_security_profile, order_api_details, orderposition_api_details

View File

@@ -123,7 +123,7 @@ LANGUAGES_RTL = {
'ar', 'hw'
}
LANGUAGES_INCUBATING = {
'fi', 'pt-br', 'gl',
'pt-br', 'gl',
}
LANGUAGES = ALL_LANGUAGES
LOCALE_PATHS = [

View File

@@ -46,6 +46,7 @@ from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.api.serializers.item import (
InlineItemVariationSerializer, ItemSerializer, QuestionSerializer,
)
from pretix.api.signals import order_api_details, orderposition_api_details
from pretix.base.decimal import round_decimal
from pretix.base.i18n import language
from pretix.base.models import (
@@ -494,6 +495,18 @@ class OrderPositionListSerializer(serializers.ListSerializer):
return data
class OrderPositionPluginDataField(serializers.Field):
def to_representation(self, value: OrderPosition):
d = {}
if value and value.pk:
for recv, resp in orderposition_api_details.send(
sender=self.context.get("event") or value.order.event,
orderposition=value
):
d.update(resp)
return d
class OrderPositionSerializer(I18nAwareModelSerializer):
checkins = CheckinSerializer(many=True, read_only=True)
print_logs = PrintLogSerializer(many=True, read_only=True)
@@ -504,6 +517,7 @@ class OrderPositionSerializer(I18nAwareModelSerializer):
seat = InlineSeatSerializer(read_only=True)
country = CompatibleCountryField(source='*')
attendee_name = serializers.CharField(required=False)
plugin_data = OrderPositionPluginDataField(source='*', allow_null=True, read_only=True)
class Meta:
list_serializer_class = OrderPositionListSerializer
@@ -513,7 +527,7 @@ class OrderPositionSerializer(I18nAwareModelSerializer):
'attendee_email', 'voucher', 'tax_rate', 'tax_value', 'secret', 'addon_to', 'subevent', 'checkins',
'print_logs', 'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat', 'canceled',
'print_logs', 'downloads', 'answers', 'tax_rule', 'tax_code', 'pseudonymization_id', 'pdf_data', 'seat',
'canceled', 'valid_from', 'valid_until', 'blocked', 'voucher_budget_use')
'canceled', 'valid_from', 'valid_until', 'blocked', 'voucher_budget_use', 'plugin_data')
read_only_fields = (
'id', 'order', 'positionid', 'item', 'variation', 'price', 'voucher', 'tax_rate', 'tax_value', 'secret',
'addon_to', 'subevent', 'checkins', 'downloads', 'answers', 'tax_rule', 'tax_code', 'pseudonymization_id',
@@ -730,6 +744,18 @@ class OrderListSerializer(serializers.ListSerializer):
return data
class OrderPluginDataField(serializers.Field):
def to_representation(self, value: Order):
d = {}
if value and value.pk:
for recv, resp in order_api_details.send(
sender=self.context.get("event") or value.event,
order=value
):
d.update(resp)
return d
class OrderSerializer(I18nAwareModelSerializer):
event = SlugRelatedField(slug_field='slug', read_only=True)
invoice_address = InvoiceAddressSerializer(allow_null=True)
@@ -747,6 +773,7 @@ class OrderSerializer(I18nAwareModelSerializer):
queryset=SalesChannel.objects.none(),
required=False,
)
plugin_data = OrderPluginDataField(source='*', allow_null=True, read_only=True)
class Meta:
model = Order
@@ -755,7 +782,7 @@ class OrderSerializer(I18nAwareModelSerializer):
'code', 'event', 'status', 'testmode', 'secret', 'email', 'phone', 'locale', 'datetime', 'expires', 'payment_date',
'payment_provider', 'fees', 'total', 'comment', 'custom_followup_at', 'invoice_address', 'positions', 'downloads',
'checkin_attention', 'checkin_text', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel',
'url', 'customer', 'valid_if_pending', 'api_meta', 'cancellation_date'
'url', 'customer', 'valid_if_pending', 'api_meta', 'cancellation_date', 'plugin_data',
)
read_only_fields = (
'code', 'status', 'testmode', 'secret', 'datetime', 'expires', 'payment_date',

View File

@@ -30,7 +30,7 @@ from rest_framework.exceptions import ValidationError
from pretix.api.serializers.order import (
AnswerCreateSerializer, AnswerSerializer, CompatibleCountryField,
OrderPositionCreateSerializer,
OrderFeeCreateSerializer, OrderPositionCreateSerializer,
)
from pretix.base.models import ItemVariation, Order, OrderFee, OrderPosition
from pretix.base.services.orders import OrderError
@@ -104,6 +104,54 @@ class OrderPositionCreateForExistingOrderSerializer(OrderPositionCreateSerialize
raise ValidationError(str(e))
class OrderFeeCreateForExistingOrderSerializer(OrderFeeCreateSerializer):
order = serializers.SlugRelatedField(slug_field='code', queryset=Order.objects.none(), required=True, allow_null=False)
value = serializers.DecimalField(required=True, allow_null=False, decimal_places=2,
max_digits=13)
internal_type = serializers.CharField(required=False, default="")
class Meta:
model = OrderFee
fields = ('order', 'fee_type', 'value', 'description', 'internal_type', 'tax_rule')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.context:
return
self.fields['order'].queryset = self.context['event'].orders.all()
self.fields['tax_rule'].queryset = self.context['event'].tax_rules.all()
if 'order' in self.context:
del self.fields['order']
def validate(self, data):
data = super().validate(data)
if 'order' in self.context:
data['order'] = self.context['order']
return data
def create(self, validated_data):
ocm = self.context['ocm']
try:
f = OrderFee(
order=validated_data['order'],
fee_type=validated_data['fee_type'],
value=validated_data.get('value'),
description=validated_data.get('description'),
internal_type=validated_data.get('internal_type'),
tax_rule=validated_data.get('tax_rule'),
)
f._calculate_tax()
ocm.add_fee(f)
if self.context.get('commit', True):
ocm.commit()
return validated_data['order'].fees.order_by('-pk').first()
else:
return OrderFee() # fake to appease DRF
except OrderError as e:
raise ValidationError(str(e))
class OrderPositionInfoPatchSerializer(serializers.ModelSerializer):
answers = AnswerSerializer(many=True)
country = CompatibleCountryField(source='*')
@@ -401,6 +449,9 @@ class OrderChangeOperationSerializer(serializers.Serializer):
self.fields['split_positions'] = SelectPositionSerializer(
many=True, required=False, context=self.context
)
self.fields['create_fees'] = OrderFeeCreateForExistingOrderSerializer(
many=True, required=False, context=self.context
)
self.fields['patch_fees'] = PatchFeeSerializer(
many=True, required=False, context=self.context
)

View File

@@ -26,7 +26,7 @@ from django.utils.timezone import now
from django_scopes import scopes_disabled
from pretix.api.models import ApiCall, WebHookCall
from pretix.base.signals import periodic_task
from pretix.base.signals import EventPluginSignal, periodic_task
from pretix.helpers.periodic import minimum_interval
register_webhook_events = Signal()
@@ -43,6 +43,28 @@ return an instance of a subclass of ``pretix.api.auth.devicesecurity.BaseSecurit
or a list of such instances.
"""
order_api_details = EventPluginSignal()
"""
Arguments: ``order``
This signal is sent out to fill the ``plugin_details`` field of the order API. Receivers
should return a dictionary that is combined with the dictionaries of all other plugins.
Note that doing database or network queries in receivers to this signal is discouraged
and could cause serious performance issues. The main purpose is to provide information
from e.g. ``meta_info`` to the API consumer,
"""
orderposition_api_details = EventPluginSignal()
"""
Arguments: ``orderposition``
This signal is sent out to fill the ``plugin_details`` field of the order API. Receivers
should return a dictionary that is combined with the dictionaries of all other plugins.
Note that doing database or network queries in receivers to this signal is discouraged
and could cause serious performance issues. The main purpose is to provide information
from e.g. ``meta_info`` to the API consumer,
"""
@receiver(periodic_task)
@scopes_disabled()

View File

@@ -63,7 +63,8 @@ from pretix.api.serializers.order import (
)
from pretix.api.serializers.orderchange import (
BlockNameSerializer, OrderChangeOperationSerializer,
OrderFeeChangeSerializer, OrderPositionChangeSerializer,
OrderFeeChangeSerializer, OrderFeeCreateForExistingOrderSerializer,
OrderPositionChangeSerializer,
OrderPositionCreateForExistingOrderSerializer,
OrderPositionInfoPatchSerializer,
)
@@ -988,6 +989,12 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
ocm.cancel_fee(r['fee'])
canceled_fees.add(r['fee'])
for r in serializer.validated_data.get('create_fees', []):
pos_serializer = OrderFeeCreateForExistingOrderSerializer(
context={'ocm': ocm, 'commit': False, 'event': request.event, **self.get_serializer_context()},
)
pos_serializer.create(r)
for r in serializer.validated_data.get('patch_fees', []):
if r['fee'] in canceled_fees:
continue

View File

@@ -1308,10 +1308,13 @@ DEFAULTS = {
'serializer_class': serializers.BooleanField,
'form_class': forms.BooleanField,
'form_kwargs': dict(
label=_("Show event times and dates on the ticket shop"),
help_text=_("If disabled, no date or time will be shown on the ticket shop's front page. This settings "
"also affects a few other locations, however it should not be expected that the date of the "
"event is shown nowhere to users."),
label=_("This shop represents an event"),
help_text=_(
"Uncheck this box if you are only selling something that has no specific date, such as gift cards or a "
"ticket that can be used any time. The system will then stop showing the event date in some places like "
"the event start page. Note that pretix still is a system built around events and the date may still "
"show up in other places."
),
)
},
'show_date_to': {

View File

@@ -72,7 +72,7 @@ class OrderChangeLogEntryType(OrderLogEntryType):
prefix = _('The order has been changed:')
def display(self, logentry, data):
return self.prefix + ' ' + self.display_prefixed(logentry.event, logentry, data)
return format_html('{} {}', self.prefix, self.display_prefixed(logentry.event, logentry, data))
def display_prefixed(self, event: Event, logentry: LogEntry, data):
return super().display(logentry, data)
@@ -282,12 +282,13 @@ class OrderChangedSplit(OrderChangeLogEntryType):
'organizer': event.organizer.slug,
'code': data['new_order']
})
return mark_safe(self.prefix + ' ' + _('Position #{posid} ({old_item}, {old_price}) split into new order: {order}').format(
return format_html(
_('Position #{posid} ({old_item}, {old_price}) split into new order: {order}'),
old_item=escape(old_item),
posid=data.get('positionid', '?'),
order='<a href="{}">{}</a>'.format(url, data['new_order']),
order=format_html(mark_safe('<a href="{}">{}</a>'), url, data['new_order']),
old_price=money_filter(Decimal(data['old_price']), event.currency),
))
)
@log_entry_types.new()
@@ -295,8 +296,14 @@ class OrderChangedSplitFrom(OrderLogEntryType):
action_type = 'pretix.event.order.changed.split_from'
def display(self, logentry: LogEntry, data):
return _('This order has been created by splitting the order {order}').format(
order=data['original_order'],
url = reverse('control:event.order', kwargs={
'event': logentry.event.slug,
'organizer': logentry.event.organizer.slug,
'code': data['original_order']
})
return format_html(
_('This order has been created by splitting the order {order}'),
order=format_html(mark_safe('<a href="{}">{}</a>'), url, data['original_order']),
)
@@ -318,7 +325,7 @@ class OrderChangedSplitFrom(OrderLogEntryType):
})
class CheckinErrorLogEntryType(OrderLogEntryType):
def display(self, logentry: LogEntry, data):
self.display_plain(self.plain, logentry, data)
return self.display_plain(self.plain, logentry, data)
def display_plain(self, plain, logentry: LogEntry, data):
if isinstance(plain, tuple):
@@ -330,7 +337,7 @@ class CheckinErrorLogEntryType(OrderLogEntryType):
event = logentry.event
if 'list' in data:
if 'list' in data and event:
try:
data['list'] = event.checkin_lists.get(pk=data.get('list')).name
except CheckinList.DoesNotExist:
@@ -343,12 +350,12 @@ class CheckinErrorLogEntryType(OrderLogEntryType):
if 'datetime' in data:
dt = dateutil.parser.parse(data.get('datetime'))
if abs((logentry.datetime - dt).total_seconds()) > 5 or 'forced' in data:
tz = event.timezone
data['datetime'] = date_format(dt.astimezone(tz), "SHORT_DATETIME_FORMAT")
if abs((logentry.datetime - dt).total_seconds()) > 5 or data.get('forced'):
if event:
data['datetime'] = date_format(dt.astimezone(event.timezone), "SHORT_DATETIME_FORMAT")
return str(plain_with_dt).format_map(data)
else:
return str(plain_without_dt).format_map(data)
return str(plain_without_dt).format_map(data)
@log_entry_types.new('pretix.event.checkin')
@@ -410,7 +417,7 @@ class OrderPrintLogEntryType(OrderLogEntryType):
datetime=date_format(
dateutil.parser.parse(data["datetime"]).astimezone(logentry.event.timezone),
"SHORT_DATETIME_FORMAT"
),
) if logentry.event else data["datetime"],
type=dict(PrintLog.PRINT_TYPES)[data["type"]],
)

View File

@@ -125,6 +125,15 @@
<button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %}
</button>
{% if voucher.pk %}
<div class="pull-left">
<a href="{% url "control:event.voucher.delete" organizer=request.organizer.slug event=request.event.slug voucher=voucher.pk %}"
class="btn btn-danger btn-lg">
<span class="fa fa-trash"></span>
{% trans "Delete voucher" %}
</a>
</div>
{% endif %}
</div>
{% endif %}
</form>

View File

@@ -110,7 +110,7 @@ class MessageView(TemplateView):
class LogDetailView(AdministratorPermissionRequiredMixin, View):
def get(self, request, *args, **kwargs):
le = get_object_or_404(LogEntry, pk=request.GET.get('pk'))
return JsonResponse({'data': le.parsed_data})
return JsonResponse({'action_type': le.action_type, 'content_type': str(le.content_type), 'object_id': le.object_id, 'data': le.parsed_data})
class PaymentDetailView(AdministratorPermissionRequiredMixin, View):

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-29 13:18+0000\n"
"PO-Revision-Date: 2025-02-05 20:00+0000\n"
"PO-Revision-Date: 2025-02-06 18:00+0000\n"
"Last-Translator: 조정화 <junghwa.jo@om.org>\n"
"Language-Team: Korean <https://translate.pretix.eu/projects/pretix/pretix/ko/"
">\n"
@@ -516,52 +516,53 @@ msgstr "이벤트 생성"
#: pretix/api/webhooks.py:329
msgid "Event details changed"
msgstr ""
msgstr "이벤트 세부 정보가 변경되었습니다"
#: pretix/api/webhooks.py:333
msgid "Event deleted"
msgstr ""
msgstr "이벤트 삭제"
#: pretix/api/webhooks.py:337
msgctxt "subevent"
msgid "Event series date added"
msgstr ""
msgstr "이벤트 시리즈 날짜 추가"
#: pretix/api/webhooks.py:341
msgctxt "subevent"
msgid "Event series date changed"
msgstr ""
msgstr "이벤트 시리즈 날짜 변경"
#: pretix/api/webhooks.py:345
msgctxt "subevent"
msgid "Event series date deleted"
msgstr ""
msgstr "이벤트 시리즈 날짜 삭제"
#: pretix/api/webhooks.py:349
msgid ""
"Product changed (including product added or deleted and including changes to "
"nested objects like variations or bundles)"
msgstr ""
msgstr "제품 변경(제품 추가 또는 삭제, 변형 또는 번들과 같은 중첩된 객체에 대한 변경 "
"포함)"
#: pretix/api/webhooks.py:354
msgid "Shop taken live"
msgstr ""
msgstr "라이브 촬영 상점"
#: pretix/api/webhooks.py:358
msgid "Shop taken offline"
msgstr ""
msgstr "오프라인 쇼핑"
#: pretix/api/webhooks.py:362
msgid "Test-Mode of shop has been activated"
msgstr ""
msgstr "상점의 테스트 모드가 활성화되었습니다"
#: pretix/api/webhooks.py:366
msgid "Test-Mode of shop has been deactivated"
msgstr ""
msgstr "상점의 테스트 모드가 비활성화되었습니다"
#: pretix/api/webhooks.py:370
msgid "Waiting list entry added"
msgstr ""
msgstr "대기자 명단 항목 추가"
#: pretix/api/webhooks.py:374
msgid "Waiting list entry changed"

View File

@@ -292,11 +292,11 @@ def _handle_transaction(trans: BankTransaction, matches: tuple, event: Event = N
trans.save()
def parse_date(date_str):
def parse_date(date_str, region=None):
try:
return dateutil.parser.parse(
date_str,
dayfirst="." in date_str,
dayfirst="." in date_str or region in ["GB"],
).date()
except (ValueError, OverflowError):
pass
@@ -339,7 +339,7 @@ def _get_unknown_transactions(job: BankImportJob, data: list, event: Event = Non
external_id=row.get('external_id'),
currency=event.currency if event else job.currency)
trans.date_parsed = parse_date(trans.date)
trans.date_parsed = parse_date(trans.date, (event and event.settings.region) or (organizer and organizer.settings.region) or None)
trans.checksum = trans.calculate_checksum()
if trans.checksum not in known_checksums and (not trans.external_id or (trans.external_id, trans.date, trans.amount) not in known_by_external_id):

View File

@@ -244,15 +244,16 @@ class AuthorizeView(View):
response_mode, state)
if "id_token_hint" in request_data:
self._redirect_error("invalid_request", "id_token_hint currently not supported by this server",
redirect_uri, response_mode, state)
return self._redirect_error("invalid_request", "id_token_hint currently not supported by this server",
redirect_uri, response_mode, state)
has_valid_session = bool(request.customer)
if has_valid_session and max_age:
try:
has_valid_session = int(time.time() - get_customer_auth_time(request)) < int(max_age)
except ValueError:
self._redirect_error("invalid_request", "invalid max_age value", redirect_uri, response_mode, state)
return self._redirect_error("invalid_request", "invalid max_age value", redirect_uri,
response_mode, state)
if not has_valid_session and prompt and prompt == "none":
return self._redirect_error("interaction_required", "user is not logged in but no prompt is allowed",

View File

@@ -1065,11 +1065,17 @@ function add_log_expand_handlers(el) {
} else if ($a.is("[data-expandpayment]")) {
url += 'payment/'
}
function format_data(data) {
return Object.entries(data).map(([key, value]) =>
$("<div>").append(
$("<b>").text(key + ': '),
$("<span>").text(JSON.stringify(value, null, 2))));
}
$.getJSON(url + '?pk=' + id, function (data) {
if ($a.parent().tagName === "p") {
$("<pre>").text(JSON.stringify(data.data, null, 2)).insertAfter($a.parent());
$("<pre>").append(format_data(data)).insertAfter($a.parent());
} else {
$("<pre>").text(JSON.stringify(data.data, null, 2)).appendTo($a.parent());
$("<pre>").append(format_data(data)).appendTo($a.parent());
}
$a.remove();
});

View File

@@ -1,6 +1,6 @@
/*global $,u2f */
$(function () {
$('.sidebar .dropdown, ul.navbar-nav .dropdown, .navbar-events-collapse').on('shown.bs.collapse shown.bs.dropdown', function () {
$('.context-selector.dropdown').on('shown.bs.collapse shown.bs.dropdown', function () {
$(this).parent().find("input").val("").trigger('forceRunQuery').focus();
});
$('.dropdown-menu .form-box input').click(function (e) {

View File

@@ -162,6 +162,7 @@ def test_giftcard_detail_expand(token_client, organizer, event, giftcard):
"tax_rule": None,
"pseudonymization_id": op.pseudonymization_id,
"pdf_data": {},
"plugin_data": {},
"seat": None,
"canceled": False,
"valid_from": None,

View File

@@ -1797,6 +1797,13 @@ def test_order_change_cancel_and_create(token_client, organizer, event, order, q
'price': '99.99'
},
],
'create_fees': [
{
'value': '5.99',
'fee_type': 'service',
'description': 'Service fee',
},
],
'cancel_fees': [
{
'fee': f.pk,
@@ -1818,6 +1825,11 @@ def test_order_change_cancel_and_create(token_client, organizer, event, order, q
assert p_new.price == Decimal('99.99')
f.refresh_from_db()
assert f.canceled
f_new = order.all_fees.get(fee_type=OrderFee.FEE_TYPE_SERVICE)
assert f_new.value == Decimal('5.99')
assert f_new.description == "Service fee"
assert f_new.internal_type == ""
assert f_new.tax_value == Decimal('0.00')
@pytest.mark.django_db

View File

@@ -477,7 +477,8 @@ def test_order_create_simulate(token_client, organizer, event, item, quota, ques
'zipcode': None,
'state': None,
'country': None,
'canceled': False
'canceled': False,
'plugin_data': {},
}
],
'downloads': [],
@@ -487,7 +488,8 @@ def test_order_create_simulate(token_client, organizer, event, item, quota, ques
'refunds': [],
'require_approval': False,
'sales_channel': 'web',
'cancellation_date': None
'cancellation_date': None,
'plugin_data': {},
}
@@ -533,13 +535,15 @@ def test_order_create_positionids_addons_simulated(token_client, organizer, even
'street': None, 'zipcode': None, 'city': None, 'country': None, 'state': None, 'attendee_email': None,
'voucher': None, 'tax_rate': '19.00', 'tax_code': 'S/standard', 'tax_value': '3.67', 'discount': None, 'voucher_budget_use': None,
'addon_to': None, 'subevent': None, 'checkins': [], 'print_logs': [], 'downloads': [], 'answers': [], 'tax_rule': item.tax_rule_id,
'pseudonymization_id': 'PREVIEW', 'seat': None, 'canceled': False, 'valid_from': None, 'valid_until': None, 'blocked': None},
'pseudonymization_id': 'PREVIEW', 'seat': None, 'canceled': False, 'valid_from': None, 'valid_until': None, 'blocked': None,
'plugin_data': {}},
{'id': 0, 'order': '', 'positionid': 2, 'item': item.pk, 'variation': None, 'price': '23.00',
'attendee_name': 'Peter', 'attendee_name_parts': {'full_name': 'Peter', '_scheme': 'full'}, 'company': None,
'street': None, 'zipcode': None, 'city': None, 'country': None, 'state': None, 'attendee_email': None,
'voucher': None, 'tax_rate': '19.00', 'tax_code': 'S/standard', 'tax_value': '3.67', 'discount': None, 'voucher_budget_use': None,
'addon_to': 1, 'subevent': None, 'checkins': [], 'print_logs': [], 'downloads': [], 'answers': [], 'tax_rule': item.tax_rule_id,
'pseudonymization_id': 'PREVIEW', 'seat': None, 'canceled': False, 'valid_from': None, 'valid_until': None, 'blocked': None}
'pseudonymization_id': 'PREVIEW', 'seat': None, 'canceled': False, 'valid_from': None, 'valid_until': None, 'blocked': None,
'plugin_data': {}}
]

View File

@@ -236,6 +236,7 @@ TEST_ORDERPOSITION_RES = {
],
"subevent": None,
"canceled": False,
"plugin_data": {},
}
TEST_PAYMENTS_RES = [
{
@@ -333,6 +334,7 @@ TEST_ORDER_RES = {
"payments": TEST_PAYMENTS_RES,
"refunds": TEST_REFUNDS_RES,
"cancellation_date": None,
"plugin_data": {},
}

View File

@@ -203,7 +203,8 @@ def test_medium_detail(token_client, organizer, event, medium, giftcard, custome
"canceled": False,
"valid_from": None,
"valid_until": None,
"blocked": None
"blocked": None,
"plugin_data": {},
}
assert resp.data["linked_giftcard"] == {
"id": giftcard.pk,

View File

@@ -790,3 +790,33 @@ def test_ignore_by_external_id(env, job):
}])
with scopes_disabled():
assert BankTransaction.objects.count() == 2
@pytest.mark.django_db
def test_ambigious_date_without_region(env, job):
process_banktransfers(job, [{
'payer': 'Karla Kundin',
'reference': 'Bestellung DUMMY1Z3AS',
'date': '03/05/2016',
'amount': '23.00'
}])
env[2].refresh_from_db()
with scopes_disabled():
assert env[2].payments.last().info_data["date"] == "2016-03-05"
@pytest.mark.django_db
def test_ambigious_date_with_region(env, job):
env[0].settings.region = "GB"
process_banktransfers(job, [{
'payer': 'Karla Kundin',
'reference': 'Bestellung DUMMY1Z3AS',
'date': '03/05/2016',
'amount': '23.00'
}])
env[2].refresh_from_db()
with scopes_disabled():
assert env[2].payments.last().info_data["date"] == "2016-05-03"

View File

@@ -40,3 +40,5 @@ def test_date_formats():
assert dt == parse_date("2020-07-01")
assert dt == parse_date("2020-7-1")
assert dt == parse_date("01/07/2020", "GB")