mirror of
https://github.com/pretix/pretix.git
synced 2025-12-11 01:22:28 +00:00
Compare commits
26 Commits
validate-u
...
rename-mig
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43b5c68f03 | ||
|
|
1cb2d443f9 | ||
|
|
a0dbf6c5db | ||
|
|
fd9d03786b | ||
|
|
7041d40972 | ||
|
|
0b46982e6d | ||
|
|
29906c6288 | ||
|
|
3380bd3e82 | ||
|
|
6ae8e7cbb6 | ||
|
|
23c2d9266e | ||
|
|
ba155faaa3 | ||
|
|
fd177fa89f | ||
|
|
0b051c1400 | ||
|
|
af8d0f0b65 | ||
|
|
1b7bba195c | ||
|
|
f056f77dc0 | ||
|
|
ee4e7f618f | ||
|
|
cd450f1780 | ||
|
|
fc876978b2 | ||
|
|
d8efaa47f7 | ||
|
|
f0c3514588 | ||
|
|
e1ad4d9dba | ||
|
|
3ab587883e | ||
|
|
b02e1a1515 | ||
|
|
41780add40 | ||
|
|
b07a61e4f1 |
222
doc/api/resources/item_program_times.rst
Normal file
222
doc/api/resources/item_program_times.rst
Normal file
@@ -0,0 +1,222 @@
|
||||
Item program times
|
||||
==================
|
||||
|
||||
Resource description
|
||||
--------------------
|
||||
|
||||
Program times for products (items) that can be set in addition to event times, e.g. to display seperate schedules within an event.
|
||||
The program times resource contains the following public fields:
|
||||
|
||||
.. rst-class:: rest-resource-table
|
||||
|
||||
===================================== ========================== =======================================================
|
||||
Field Type Description
|
||||
===================================== ========================== =======================================================
|
||||
id integer Internal ID of the program time
|
||||
start datetime The start date time for this program time slot.
|
||||
end datetime The end date time for this program time slot.
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
.. versionchanged:: TODO
|
||||
|
||||
The resource has been added.
|
||||
|
||||
|
||||
Endpoints
|
||||
---------
|
||||
|
||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/items/(item)/program_times/
|
||||
|
||||
Returns a list of all program times for a given item.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /api/v1/organizers/bigevents/events/sampleconf/items/11/program_times/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"count": 3,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": 2,
|
||||
"start": "2025-08-14T22:00:00Z",
|
||||
"end": "2025-08-15T00:00:00Z"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"start": "2025-08-12T22:00:00Z",
|
||||
"end": "2025-08-13T22:00:00Z"
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"start": "2025-08-15T22:00:00Z",
|
||||
"end": "2025-08-17T22:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to fetch
|
||||
:param event: The ``slug`` field of the event to fetch
|
||||
:param item: The ``id`` field of the item to fetch
|
||||
:statuscode 200: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event/item does not exist **or** you have no permission to view this resource.
|
||||
|
||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/items/(item)/program_times/(id)/
|
||||
|
||||
Returns information on one program time, identified by its ID.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /api/v1/organizers/bigevents/events/sampleconf/items/1/program_times/1/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"start": "2025-08-15T22:00:00Z",
|
||||
"end": "2025-10-27T23:00:00Z"
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to fetch
|
||||
:param event: The ``slug`` field of the event to fetch
|
||||
:param item: The ``id`` field of the item to fetch
|
||||
:param id: The ``id`` field of the program time to fetch
|
||||
:statuscode 200: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
||||
|
||||
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/items/(item)/program_times/
|
||||
|
||||
Creates a new program time
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/v1/organizers/bigevents/events/sampleconf/items/1/program_times/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"start": "2025-08-15T10:00:00Z",
|
||||
"end": "2025-08-15T22:00:00Z"
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 201 Created
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": 17,
|
||||
"start": "2025-08-15T10:00:00Z",
|
||||
"end": "2025-08-15T22:00:00Z"
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer of the event/item to create a program time for
|
||||
:param event: The ``slug`` field of the event to create a program time for
|
||||
:param item: The ``id`` field of the item to create a program time for
|
||||
:statuscode 201: no error
|
||||
:statuscode 400: The program time could not be created due to invalid submitted data.
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to create this resource.
|
||||
|
||||
.. http:patch:: /api/v1/organizers/(organizer)/events/(event)/items/(item)/program_times/(id)/
|
||||
|
||||
Update a program time. You can also use ``PUT`` instead of ``PATCH``. With ``PUT``, you have to provide all fields of
|
||||
the resource, other fields will be reset to default. With ``PATCH``, you only need to provide the fields that you
|
||||
want to change.
|
||||
|
||||
You can change all fields of the resource except the ``id`` field.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
PATCH /api/v1/organizers/bigevents/events/sampleconf/items/1/program_times/1/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
Content-Type: application/json
|
||||
Content-Length: 94
|
||||
|
||||
{
|
||||
"start": "2025-08-14T10:00:00Z"
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"start": "2025-08-14T10:00:00Z",
|
||||
"end": "2025-08-15T12:00:00Z"
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to modify
|
||||
:param event: The ``slug`` field of the event to modify
|
||||
:param id: The ``id`` field of the item to modify
|
||||
:param id: The ``id`` field of the program time to modify
|
||||
:statuscode 200: no error
|
||||
:statuscode 400: The program time could not be modified due to invalid submitted data
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to change this resource.
|
||||
|
||||
.. http:delete:: /api/v1/organizers/(organizer)/events/(event)/items/(id)/program_times/(id)/
|
||||
|
||||
Delete a program time.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
DELETE /api/v1/organizers/bigevents/events/sampleconf/items/1/program_times/1/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 204 No Content
|
||||
Vary: Accept
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to modify
|
||||
:param event: The ``slug`` field of the event to modify
|
||||
:param id: The ``id`` field of the item to modify
|
||||
:param id: The ``id`` field of the program time to delete
|
||||
:statuscode 204: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to delete this resource.
|
||||
@@ -139,6 +139,9 @@ has_variations boolean Shows whether
|
||||
variations list of objects A list with one object for each variation of this item.
|
||||
Can be empty. Only writable during creation,
|
||||
use separate endpoint to modify this later.
|
||||
program_times list of objects A list with one object for each program time of this item.
|
||||
Can be empty. Only writable during creation,
|
||||
use separate endpoint to modify this later.
|
||||
├ id integer Internal ID of the variation
|
||||
├ value multi-lingual string The "name" of the variation
|
||||
├ default_price money (string) The price set directly for this variation or ``null``
|
||||
@@ -225,6 +228,10 @@ meta_data object Values set fo
|
||||
|
||||
The ``hidden_if_item_available_mode`` attributes has been added.
|
||||
|
||||
.. versionchanged:: 2025.9
|
||||
|
||||
The ``program_times`` attribute has been added.
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
||||
@@ -232,9 +239,9 @@ Please note that an item either always has variations or never has. Once created
|
||||
change to an item without and vice versa. To create an item with variations ensure that you POST an item with at least
|
||||
one variation.
|
||||
|
||||
Also note that ``variations``, ``bundles``, and ``addons`` are only supported on ``POST``. To update/delete variations,
|
||||
bundles, and add-ons please use the dedicated nested endpoints. By design this endpoint does not support ``PATCH`` and ``PUT``
|
||||
with nested ``variations``, ``bundles`` and/or ``addons``.
|
||||
Also note that ``variations``, ``bundles``, ``addons`` and ``program_times`` are only supported on ``POST``. To update/delete variations,
|
||||
bundles, add-ons and program times please use the dedicated nested endpoints. By design this endpoint does not support ``PATCH`` and ``PUT``
|
||||
with nested ``variations``, ``bundles``, ``addons`` and/or ``program_times``.
|
||||
|
||||
Endpoints
|
||||
---------
|
||||
@@ -373,7 +380,8 @@ Endpoints
|
||||
}
|
||||
],
|
||||
"addons": [],
|
||||
"bundles": []
|
||||
"bundles": [],
|
||||
"program_times": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -525,7 +533,8 @@ Endpoints
|
||||
}
|
||||
],
|
||||
"addons": [],
|
||||
"bundles": []
|
||||
"bundles": [],
|
||||
"program_times": []
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to fetch
|
||||
@@ -653,7 +662,13 @@ Endpoints
|
||||
}
|
||||
],
|
||||
"addons": [],
|
||||
"bundles": []
|
||||
"bundles": [],
|
||||
"program_times": [
|
||||
{
|
||||
"start": "2025-08-14T22:00:00Z",
|
||||
"end": "2025-08-15T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
@@ -773,7 +788,13 @@ Endpoints
|
||||
}
|
||||
],
|
||||
"addons": [],
|
||||
"bundles": []
|
||||
"bundles": [],
|
||||
"program_times": [
|
||||
{
|
||||
"start": "2025-08-14T22:00:00Z",
|
||||
"end": "2025-08-15T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer of the event to create an item for
|
||||
@@ -789,8 +810,9 @@ Endpoints
|
||||
the resource, other fields will be reset to default. With ``PATCH``, you only need to provide the fields that you
|
||||
want to change.
|
||||
|
||||
You can change all fields of the resource except the ``has_variations``, ``variations`` and the ``addon`` field. If
|
||||
you need to update/delete variations or add-ons please use the nested dedicated endpoints.
|
||||
You can change all fields of the resource except the ``has_variations``, ``variations``, ``addon`` and the
|
||||
``program_times`` field. If you need to update/delete variations, add-ons or program times, please use the nested
|
||||
dedicated endpoints.
|
||||
|
||||
**Example request**:
|
||||
|
||||
@@ -924,7 +946,8 @@ Endpoints
|
||||
}
|
||||
],
|
||||
"addons": [],
|
||||
"bundles": []
|
||||
"bundles": [],
|
||||
"program_times": []
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to modify
|
||||
|
||||
@@ -33,9 +33,9 @@ dependencies = [
|
||||
"celery==5.5.*",
|
||||
"chardet==5.2.*",
|
||||
"cryptography>=44.0.0",
|
||||
"css-inline==0.17.*",
|
||||
"css-inline==0.18.*",
|
||||
"defusedcsv>=1.1.0",
|
||||
"Django[argon2]==4.2.*,>=4.2.24",
|
||||
"Django[argon2]==4.2.*,>=4.2.26",
|
||||
"django-bootstrap3==25.2",
|
||||
"django-compressor==4.5.1",
|
||||
"django-countries==7.6.*",
|
||||
|
||||
@@ -47,8 +47,9 @@ from pretix.api.serializers.event import MetaDataField
|
||||
from pretix.api.serializers.fields import UploadedFileField
|
||||
from pretix.api.serializers.i18n import I18nAwareModelSerializer
|
||||
from pretix.base.models import (
|
||||
Item, ItemAddOn, ItemBundle, ItemCategory, ItemMetaValue, ItemVariation,
|
||||
ItemVariationMetaValue, Question, QuestionOption, Quota, SalesChannel,
|
||||
Item, ItemAddOn, ItemBundle, ItemCategory, ItemMetaValue, ItemProgramTime,
|
||||
ItemVariation, ItemVariationMetaValue, Question, QuestionOption, Quota,
|
||||
SalesChannel,
|
||||
)
|
||||
|
||||
|
||||
@@ -187,6 +188,12 @@ class InlineItemAddOnSerializer(serializers.ModelSerializer):
|
||||
'position', 'price_included', 'multi_allowed')
|
||||
|
||||
|
||||
class InlineItemProgramTimeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ItemProgramTime
|
||||
fields = ('start', 'end')
|
||||
|
||||
|
||||
class ItemBundleSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ItemBundle
|
||||
@@ -212,6 +219,31 @@ class ItemBundleSerializer(serializers.ModelSerializer):
|
||||
return data
|
||||
|
||||
|
||||
class ItemProgramTimeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ItemProgramTime
|
||||
fields = ('id', 'start', 'end')
|
||||
|
||||
def validate(self, data):
|
||||
data = super().validate(data)
|
||||
|
||||
full_data = self.to_internal_value(self.to_representation(self.instance)) if self.instance else {}
|
||||
full_data.update(data)
|
||||
|
||||
start = full_data.get('start')
|
||||
if not start:
|
||||
raise ValidationError(_("The program start must not be empty."))
|
||||
|
||||
end = full_data.get('end')
|
||||
if not end:
|
||||
raise ValidationError(_("The program end must not be empty."))
|
||||
|
||||
if start > end:
|
||||
raise ValidationError(_("The program end must not be before the program start."))
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class ItemAddOnSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ItemAddOn
|
||||
@@ -250,6 +282,7 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
|
||||
addons = InlineItemAddOnSerializer(many=True, required=False)
|
||||
bundles = InlineItemBundleSerializer(many=True, required=False)
|
||||
variations = InlineItemVariationSerializer(many=True, required=False)
|
||||
program_times = InlineItemProgramTimeSerializer(many=True, required=False)
|
||||
tax_rate = ItemTaxRateField(source='*', read_only=True)
|
||||
meta_data = MetaDataField(required=False, source='*')
|
||||
picture = UploadedFileField(required=False, allow_null=True, allowed_types=(
|
||||
@@ -271,7 +304,7 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
|
||||
'available_from', 'available_from_mode', 'available_until', 'available_until_mode',
|
||||
'require_voucher', 'hide_without_voucher', 'allow_cancel', 'require_bundling',
|
||||
'min_per_order', 'max_per_order', 'checkin_attention', 'checkin_text', 'has_variations', 'variations',
|
||||
'addons', 'bundles', 'original_price', 'require_approval', 'generate_tickets',
|
||||
'addons', 'bundles', 'program_times', 'original_price', 'require_approval', 'generate_tickets',
|
||||
'show_quota_left', 'hidden_if_available', 'hidden_if_item_available', 'hidden_if_item_available_mode', 'allow_waitinglist',
|
||||
'issue_giftcard', 'meta_data',
|
||||
'require_membership', 'require_membership_types', 'require_membership_hidden', 'grant_membership_type',
|
||||
@@ -294,9 +327,9 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
|
||||
|
||||
def validate(self, data):
|
||||
data = super().validate(data)
|
||||
if self.instance and ('addons' in data or 'variations' in data or 'bundles' in data):
|
||||
raise ValidationError(_('Updating add-ons, bundles, or variations via PATCH/PUT is not supported. Please use the '
|
||||
'dedicated nested endpoint.'))
|
||||
if self.instance and ('addons' in data or 'variations' in data or 'bundles' in data or 'program_times' in data):
|
||||
raise ValidationError(_('Updating add-ons, bundles, program times or variations via PATCH/PUT is not '
|
||||
'supported. Please use the dedicated nested endpoint.'))
|
||||
|
||||
Item.clean_per_order(data.get('min_per_order'), data.get('max_per_order'))
|
||||
Item.clean_available(data.get('available_from'), data.get('available_until'))
|
||||
@@ -347,6 +380,13 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
|
||||
ItemAddOn.clean_max_min_count(addon_data.get('max_count', 0), addon_data.get('min_count', 0))
|
||||
return value
|
||||
|
||||
def validate_program_times(self, value):
|
||||
if not self.instance:
|
||||
for program_time_data in value:
|
||||
ItemProgramTime.clean_start_end(self, start=program_time_data.get('start', None),
|
||||
end=program_time_data.get('end', None))
|
||||
return value
|
||||
|
||||
@cached_property
|
||||
def item_meta_properties(self):
|
||||
return {
|
||||
@@ -364,6 +404,7 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
|
||||
variations_data = validated_data.pop('variations') if 'variations' in validated_data else {}
|
||||
addons_data = validated_data.pop('addons') if 'addons' in validated_data else {}
|
||||
bundles_data = validated_data.pop('bundles') if 'bundles' in validated_data else {}
|
||||
program_times_data = validated_data.pop('program_times') if 'program_times' in validated_data else {}
|
||||
meta_data = validated_data.pop('meta_data', None)
|
||||
picture = validated_data.pop('picture', None)
|
||||
require_membership_types = validated_data.pop('require_membership_types', [])
|
||||
@@ -398,6 +439,8 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
|
||||
ItemAddOn.objects.create(base_item=item, **addon_data)
|
||||
for bundle_data in bundles_data:
|
||||
ItemBundle.objects.create(base_item=item, **bundle_data)
|
||||
for program_time_data in program_times_data:
|
||||
ItemProgramTime.objects.create(item=item, **program_time_data)
|
||||
|
||||
# Meta data
|
||||
if meta_data is not None:
|
||||
|
||||
@@ -112,6 +112,7 @@ item_router = routers.DefaultRouter()
|
||||
item_router.register(r'variations', item.ItemVariationViewSet)
|
||||
item_router.register(r'addons', item.ItemAddOnViewSet)
|
||||
item_router.register(r'bundles', item.ItemBundleViewSet)
|
||||
item_router.register(r'program_times', item.ItemProgramTimeViewSet)
|
||||
|
||||
order_router = routers.DefaultRouter()
|
||||
order_router.register(r'payments', order.PaymentViewSet)
|
||||
|
||||
@@ -46,13 +46,13 @@ from rest_framework.response import Response
|
||||
from pretix.api.pagination import TotalOrderingFilter
|
||||
from pretix.api.serializers.item import (
|
||||
ItemAddOnSerializer, ItemBundleSerializer, ItemCategorySerializer,
|
||||
ItemSerializer, ItemVariationSerializer, QuestionOptionSerializer,
|
||||
QuestionSerializer, QuotaSerializer,
|
||||
ItemProgramTimeSerializer, ItemSerializer, ItemVariationSerializer,
|
||||
QuestionOptionSerializer, QuestionSerializer, QuotaSerializer,
|
||||
)
|
||||
from pretix.api.views import ConditionalListView
|
||||
from pretix.base.models import (
|
||||
CartPosition, Item, ItemAddOn, ItemBundle, ItemCategory, ItemVariation,
|
||||
Question, QuestionOption, Quota,
|
||||
CartPosition, Item, ItemAddOn, ItemBundle, ItemCategory, ItemProgramTime,
|
||||
ItemVariation, Question, QuestionOption, Quota,
|
||||
)
|
||||
from pretix.base.services.quotas import QuotaAvailability
|
||||
from pretix.helpers.dicts import merge_dicts
|
||||
@@ -279,6 +279,57 @@ class ItemBundleViewSet(viewsets.ModelViewSet):
|
||||
)
|
||||
|
||||
|
||||
class ItemProgramTimeViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = ItemProgramTimeSerializer
|
||||
queryset = ItemProgramTime.objects.none()
|
||||
filter_backends = (DjangoFilterBackend, TotalOrderingFilter,)
|
||||
ordering_fields = ('id',)
|
||||
ordering = ('id',)
|
||||
permission = None
|
||||
write_permission = 'can_change_items'
|
||||
|
||||
@cached_property
|
||||
def item(self):
|
||||
return get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event)
|
||||
|
||||
def get_queryset(self):
|
||||
return self.item.program_times.all()
|
||||
|
||||
def get_serializer_context(self):
|
||||
ctx = super().get_serializer_context()
|
||||
ctx['event'] = self.request.event
|
||||
ctx['item'] = self.item
|
||||
return ctx
|
||||
|
||||
def perform_create(self, serializer):
|
||||
item = get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event)
|
||||
serializer.save(item=item)
|
||||
item.log_action(
|
||||
'pretix.event.item.program_times.added',
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data=merge_dicts(self.request.data, {'id': serializer.instance.pk})
|
||||
)
|
||||
|
||||
def perform_update(self, serializer):
|
||||
serializer.save(event=self.request.event)
|
||||
serializer.instance.item.log_action(
|
||||
'pretix.event.item.program_times.changed',
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data=merge_dicts(self.request.data, {'id': serializer.instance.pk})
|
||||
)
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
super().perform_destroy(instance)
|
||||
instance.item.log_action(
|
||||
'pretix.event.item.program_times.removed',
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data={'start': instance.start, 'end': instance.end}
|
||||
)
|
||||
|
||||
|
||||
class ItemAddOnViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = ItemAddOnSerializer
|
||||
queryset = ItemAddOn.objects.none()
|
||||
|
||||
@@ -214,21 +214,38 @@ class PasswordRecoverForm(forms.Form):
|
||||
error_messages = {
|
||||
'pw_mismatch': _("Please enter the same password twice"),
|
||||
}
|
||||
email = forms.EmailField(
|
||||
max_length=255,
|
||||
disabled=True,
|
||||
label=_("Your email address"),
|
||||
widget=forms.EmailInput(
|
||||
attrs={'autocomplete': 'username'},
|
||||
),
|
||||
)
|
||||
password = forms.CharField(
|
||||
label=_('Password'),
|
||||
widget=forms.PasswordInput,
|
||||
widget=forms.PasswordInput(attrs={
|
||||
'autocomplete': 'new-password',
|
||||
}),
|
||||
max_length=4096,
|
||||
required=True
|
||||
)
|
||||
password_repeat = forms.CharField(
|
||||
label=_('Repeat password'),
|
||||
widget=forms.PasswordInput,
|
||||
widget=forms.PasswordInput(attrs={
|
||||
'autocomplete': 'new-password',
|
||||
}),
|
||||
max_length=4096,
|
||||
)
|
||||
|
||||
def __init__(self, user_id=None, *args, **kwargs):
|
||||
self.user_id = user_id
|
||||
super().__init__(*args, **kwargs)
|
||||
initial = kwargs.pop('initial', {})
|
||||
try:
|
||||
self.user = User.objects.get(id=user_id)
|
||||
initial['email'] = self.user.email
|
||||
except User.DoesNotExist:
|
||||
self.user = None
|
||||
super().__init__(*args, initial=initial, **kwargs)
|
||||
|
||||
def clean(self):
|
||||
password1 = self.cleaned_data.get('password', '')
|
||||
@@ -243,11 +260,7 @@ class PasswordRecoverForm(forms.Form):
|
||||
|
||||
def clean_password(self):
|
||||
password1 = self.cleaned_data.get('password', '')
|
||||
try:
|
||||
user = User.objects.get(id=self.user_id)
|
||||
except User.DoesNotExist:
|
||||
user = None
|
||||
if validate_password(password1, user=user) is not None:
|
||||
if validate_password(password1, user=self.user) is not None:
|
||||
raise forms.ValidationError(_(password_validators_help_texts()), code='pw_invalid')
|
||||
return password1
|
||||
|
||||
@@ -307,3 +320,10 @@ class ReauthForm(forms.Form):
|
||||
self.error_messages['inactive'],
|
||||
code='inactive',
|
||||
)
|
||||
|
||||
|
||||
class ConfirmationCodeForm(forms.Form):
|
||||
code = forms.IntegerField(
|
||||
label=_('Confirmation code'),
|
||||
widget=forms.NumberInput(attrs={'class': 'confirmation-code-input', 'inputmode': 'numeric', 'type': 'text'}),
|
||||
)
|
||||
|
||||
@@ -39,37 +39,16 @@ from django.contrib.auth.password_validation import (
|
||||
password_validators_help_texts, validate_password,
|
||||
)
|
||||
from django.db.models import Q
|
||||
from django.urls.base import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from pytz import common_timezones
|
||||
|
||||
from pretix.base.models import User
|
||||
from pretix.control.forms import SingleLanguageWidget
|
||||
from pretix.helpers.format import format_map
|
||||
|
||||
|
||||
class UserSettingsForm(forms.ModelForm):
|
||||
error_messages = {
|
||||
'duplicate_identifier': _("There already is an account associated with this email address. "
|
||||
"Please choose a different one."),
|
||||
'pw_current': _("Please enter your current password if you want to change your email address "
|
||||
"or password."),
|
||||
'pw_current_wrong': _("The current password you entered was not correct."),
|
||||
'pw_mismatch': _("Please enter the same password twice"),
|
||||
'rate_limit': _("For security reasons, please wait 5 minutes before you try again."),
|
||||
'pw_equal': _("Please choose a password different to your current one.")
|
||||
}
|
||||
|
||||
old_pw = forms.CharField(max_length=255,
|
||||
required=False,
|
||||
label=_("Your current password"),
|
||||
widget=forms.PasswordInput())
|
||||
new_pw = forms.CharField(max_length=255,
|
||||
required=False,
|
||||
label=_("New password"),
|
||||
widget=forms.PasswordInput())
|
||||
new_pw_repeat = forms.CharField(max_length=255,
|
||||
required=False,
|
||||
label=_("Repeat new password"),
|
||||
widget=forms.PasswordInput())
|
||||
timezone = forms.ChoiceField(
|
||||
choices=((a, a) for a in common_timezones),
|
||||
label=_("Default timezone"),
|
||||
@@ -93,11 +72,60 @@ class UserSettingsForm(forms.ModelForm):
|
||||
self.user = kwargs.pop('user')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['email'].required = True
|
||||
if self.user.auth_backend != 'native':
|
||||
del self.fields['old_pw']
|
||||
del self.fields['new_pw']
|
||||
del self.fields['new_pw_repeat']
|
||||
self.fields['email'].disabled = True
|
||||
self.fields['email'].disabled = True
|
||||
self.fields['email'].help_text = format_map('<a href="{link}"><span class="fa fa-edit"></span> {text}</a>', {
|
||||
'text': _("Change email address"),
|
||||
'link': reverse('control:user.settings.email.change')
|
||||
})
|
||||
|
||||
|
||||
class User2FADeviceAddForm(forms.Form):
|
||||
name = forms.CharField(label=_('Device name'), max_length=64)
|
||||
devicetype = forms.ChoiceField(label=_('Device type'), widget=forms.RadioSelect, choices=(
|
||||
('totp', _('Smartphone with the Authenticator application')),
|
||||
('webauthn', _('WebAuthn-compatible hardware token (e.g. Yubikey)')),
|
||||
))
|
||||
|
||||
|
||||
class UserPasswordChangeForm(forms.Form):
|
||||
error_messages = {
|
||||
'pw_current': _("Please enter your current password if you want to change your email address "
|
||||
"or password."),
|
||||
'pw_current_wrong': _("The current password you entered was not correct."),
|
||||
'pw_mismatch': _("Please enter the same password twice"),
|
||||
'rate_limit': _("For security reasons, please wait 5 minutes before you try again."),
|
||||
'pw_equal': _("Please choose a password different to your current one.")
|
||||
}
|
||||
email = forms.EmailField(max_length=255,
|
||||
disabled=True,
|
||||
label=_("Your email address"),
|
||||
widget=forms.EmailInput(
|
||||
attrs={'autocomplete': 'username'},
|
||||
))
|
||||
old_pw = forms.CharField(max_length=255,
|
||||
required=False,
|
||||
label=_("Your current password"),
|
||||
widget=forms.PasswordInput(
|
||||
attrs={'autocomplete': 'current-password'},
|
||||
))
|
||||
new_pw = forms.CharField(max_length=255,
|
||||
required=False,
|
||||
label=_("New password"),
|
||||
widget=forms.PasswordInput(
|
||||
attrs={'autocomplete': 'new-password'},
|
||||
))
|
||||
new_pw_repeat = forms.CharField(max_length=255,
|
||||
required=False,
|
||||
label=_("Repeat new password"),
|
||||
widget=forms.PasswordInput(
|
||||
attrs={'autocomplete': 'new-password'},
|
||||
))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.user = kwargs.pop('user')
|
||||
initial = kwargs.pop('initial', {})
|
||||
initial['email'] = self.user.email
|
||||
super().__init__(*args, initial=initial, **kwargs)
|
||||
|
||||
def clean_old_pw(self):
|
||||
old_pw = self.cleaned_data.get('old_pw')
|
||||
@@ -121,15 +149,6 @@ class UserSettingsForm(forms.ModelForm):
|
||||
|
||||
return old_pw
|
||||
|
||||
def clean_email(self):
|
||||
email = self.cleaned_data['email']
|
||||
if User.objects.filter(Q(email__iexact=email) & ~Q(pk=self.instance.pk)).exists():
|
||||
raise forms.ValidationError(
|
||||
self.error_messages['duplicate_identifier'],
|
||||
code='duplicate_identifier',
|
||||
)
|
||||
return email
|
||||
|
||||
def clean_new_pw(self):
|
||||
password1 = self.cleaned_data.get('new_pw', '')
|
||||
if password1 and validate_password(password1, user=self.user) is not None:
|
||||
@@ -148,32 +167,24 @@ class UserSettingsForm(forms.ModelForm):
|
||||
code='pw_mismatch'
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
password1 = self.cleaned_data.get('new_pw')
|
||||
email = self.cleaned_data.get('email')
|
||||
old_pw = self.cleaned_data.get('old_pw')
|
||||
|
||||
if (password1 or email != self.user.email) and not old_pw:
|
||||
class UserEmailChangeForm(forms.Form):
|
||||
error_messages = {
|
||||
'duplicate_identifier': _("There already is an account associated with this email address. "
|
||||
"Please choose a different one."),
|
||||
}
|
||||
old_email = forms.EmailField(label=_('Old email address'), disabled=True)
|
||||
new_email = forms.EmailField(label=_('New email address'))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.user = kwargs.pop('user')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def clean_new_email(self):
|
||||
email = self.cleaned_data['new_email']
|
||||
if User.objects.filter(Q(email__iexact=email) & ~Q(pk=self.user.pk)).exists():
|
||||
raise forms.ValidationError(
|
||||
self.error_messages['pw_current'],
|
||||
code='pw_current'
|
||||
self.error_messages['duplicate_identifier'],
|
||||
code='duplicate_identifier',
|
||||
)
|
||||
|
||||
if password1 and password1 == old_pw:
|
||||
raise forms.ValidationError(
|
||||
self.error_messages['pw_equal'],
|
||||
code='pw_equal'
|
||||
)
|
||||
|
||||
if password1:
|
||||
self.instance.set_password(password1)
|
||||
|
||||
return self.cleaned_data
|
||||
|
||||
|
||||
class User2FADeviceAddForm(forms.Form):
|
||||
name = forms.CharField(label=_('Device name'), max_length=64)
|
||||
devicetype = forms.ChoiceField(label=_('Device type'), widget=forms.RadioSelect, choices=(
|
||||
('totp', _('Smartphone with the Authenticator application')),
|
||||
('webauthn', _('WebAuthn-compatible hardware token (e.g. Yubikey)')),
|
||||
))
|
||||
return email
|
||||
|
||||
@@ -23,6 +23,7 @@ import datetime
|
||||
import logging
|
||||
import math
|
||||
import re
|
||||
import textwrap
|
||||
import unicodedata
|
||||
from collections import defaultdict
|
||||
from decimal import Decimal
|
||||
@@ -752,11 +753,59 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
return dt.astimezone(tz).date()
|
||||
|
||||
total = Decimal('0.00')
|
||||
if has_taxes:
|
||||
colwidths = [a * doc.width for a in (.50, .05, .15, .15, .15)]
|
||||
else:
|
||||
colwidths = [a * doc.width for a in (.65, .20, .15)]
|
||||
|
||||
for (description, tax_rate, tax_name, net_value, gross_value, subevent, period_start, period_end), lines in addon_aware_groupby(
|
||||
all_lines,
|
||||
key=_group_key,
|
||||
is_addon=lambda l: l.description.startswith(" +"),
|
||||
):
|
||||
# split description into multiple Paragraphs so each fits in a table cell on a single page
|
||||
# otherwise PDF-build fails
|
||||
|
||||
description_p_list = []
|
||||
# normalize linebreaks to newlines instead of HTML so we can safely substring
|
||||
description = description.replace('<br>', '<br />').replace('<br />\n', '\n').replace('<br />', '\n')
|
||||
|
||||
# start first line with different settings than the rest of the description
|
||||
curr_description = description.split("\n", maxsplit=1)[0]
|
||||
cellpadding = 6 # default cellpadding is only set on right side of column
|
||||
max_width = colwidths[0] - cellpadding
|
||||
max_height = self.stylesheet['Normal'].leading * 5
|
||||
p_style = self.stylesheet['Normal']
|
||||
for __ in range(1000):
|
||||
p = FontFallbackParagraph(
|
||||
self._clean_text(curr_description, tags=['br']),
|
||||
p_style
|
||||
)
|
||||
h = p.wrap(max_width, doc.height)[1]
|
||||
if h <= max_height:
|
||||
description_p_list.append(p)
|
||||
if curr_description == description:
|
||||
break
|
||||
description = description[len(curr_description):].lstrip()
|
||||
curr_description = description.split("\n", maxsplit=1)[0]
|
||||
# use different settings for all except first line
|
||||
max_width = sum(colwidths[0:3 if has_taxes else 2]) - cellpadding
|
||||
max_height = self.stylesheet['Fineprint'].leading * 8
|
||||
p_style = self.stylesheet['Fineprint']
|
||||
continue
|
||||
|
||||
if not description_p_list:
|
||||
# first "manual" line is larger than 5 "real" lines => only allow one line and set rest in Fineprint
|
||||
max_height = self.stylesheet['Normal'].leading
|
||||
|
||||
if h > max_height * 1.1:
|
||||
# quickly bring the text-length down to a managable length to then stepwise reduce
|
||||
wrap_to = math.ceil(len(curr_description) * max_height * 1.1 / h)
|
||||
else:
|
||||
# trim to 95% length, but at most 10 chars to not have strangely short lines in the middle of a paragraph
|
||||
wrap_to = max(len(curr_description) - 10, math.ceil(len(curr_description) * 0.95))
|
||||
curr_description = textwrap.wrap(curr_description, wrap_to, replace_whitespace=False, drop_whitespace=False)[0]
|
||||
|
||||
# Try to be clever and figure out when organizers would want to show the period. This heuristic is
|
||||
# not perfect and the only "fully correct" way would be to include the period on every line always,
|
||||
# however this will cause confusion (a) due to useless repetition of the same date all over the invoice
|
||||
@@ -810,7 +859,10 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
# Group together at the end of the invoice
|
||||
request_show_service_date = period_line
|
||||
elif period_line:
|
||||
description += "\n" + period_line
|
||||
description_p_list.append(FontFallbackParagraph(
|
||||
period_line,
|
||||
self.stylesheet['Fineprint']
|
||||
))
|
||||
|
||||
lines = list(lines)
|
||||
if has_taxes:
|
||||
@@ -819,13 +871,13 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
net_price=money_filter(net_value, self.invoice.event.currency),
|
||||
gross_price=money_filter(gross_value, self.invoice.event.currency),
|
||||
)
|
||||
description = description + "\n" + single_price_line
|
||||
description_p_list.append(FontFallbackParagraph(
|
||||
single_price_line,
|
||||
self.stylesheet['Fineprint']
|
||||
))
|
||||
|
||||
tdata.append((
|
||||
FontFallbackParagraph(
|
||||
self._clean_text(description, tags=['br']),
|
||||
self.stylesheet['Normal']
|
||||
),
|
||||
description_p_list.pop(0),
|
||||
str(len(lines)),
|
||||
localize(tax_rate) + " %",
|
||||
FontFallbackParagraph(
|
||||
@@ -837,23 +889,52 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
self.stylesheet['NormalRight']
|
||||
),
|
||||
))
|
||||
for p in description_p_list:
|
||||
tdata.append((p, "", "", "", ""))
|
||||
tstyledata.append((
|
||||
'SPAN',
|
||||
(0, len(tdata) - 1),
|
||||
(2, len(tdata) - 1),
|
||||
))
|
||||
else:
|
||||
if len(lines) > 1:
|
||||
single_price_line = pgettext('invoice', 'Single price: {price}').format(
|
||||
price=money_filter(gross_value, self.invoice.event.currency),
|
||||
)
|
||||
description = description + "\n" + single_price_line
|
||||
description_p_list.append(FontFallbackParagraph(
|
||||
single_price_line,
|
||||
self.stylesheet['Fineprint']
|
||||
))
|
||||
tdata.append((
|
||||
FontFallbackParagraph(
|
||||
self._clean_text(description, tags=['br']),
|
||||
self.stylesheet['Normal']
|
||||
),
|
||||
description_p_list.pop(0),
|
||||
str(len(lines)),
|
||||
FontFallbackParagraph(
|
||||
money_filter(gross_value * len(lines), self.invoice.event.currency).replace('\xa0', ' '),
|
||||
self.stylesheet['NormalRight']
|
||||
),
|
||||
))
|
||||
for p in description_p_list:
|
||||
tdata.append((p, "", ""))
|
||||
tstyledata.append((
|
||||
'SPAN',
|
||||
(0, len(tdata) - 1),
|
||||
(1, len(tdata) - 1),
|
||||
))
|
||||
|
||||
tstyledata += [
|
||||
(
|
||||
'BOTTOMPADDING',
|
||||
(0, len(tdata) - len(description_p_list)),
|
||||
(-1, len(tdata) - 2),
|
||||
0
|
||||
),
|
||||
(
|
||||
'TOPPADDING',
|
||||
(0, len(tdata) - len(description_p_list)),
|
||||
(-1, len(tdata) - 1),
|
||||
0
|
||||
),
|
||||
]
|
||||
taxvalue_map[tax_rate, tax_name] += (gross_value - net_value) * len(lines)
|
||||
grossvalue_map[tax_rate, tax_name] += gross_value * len(lines)
|
||||
total += gross_value * len(lines)
|
||||
@@ -863,13 +944,11 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Invoice total')), self.stylesheet['Bold']), '', '', '',
|
||||
money_filter(total, self.invoice.event.currency)
|
||||
])
|
||||
colwidths = [a * doc.width for a in (.50, .05, .15, .15, .15)]
|
||||
else:
|
||||
tdata.append([
|
||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Invoice total')), self.stylesheet['Bold']), '',
|
||||
money_filter(total, self.invoice.event.currency)
|
||||
])
|
||||
colwidths = [a * doc.width for a in (.65, .20, .15)]
|
||||
|
||||
if not self.invoice.is_cancellation:
|
||||
if self.invoice.event.settings.invoice_show_payments and self.invoice.order.status == Order.STATUS_PENDING:
|
||||
|
||||
25
src/pretix/base/migrations/0294_item_program_time.py
Normal file
25
src/pretix/base/migrations/0294_item_program_time.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 4.2.19 on 2025-08-11 10:25
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0293_cartposition_price_includes_rounding_correction_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ItemProgramTime',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||
('start', models.DateTimeField()),
|
||||
('end', models.DateTimeField()),
|
||||
('item',
|
||||
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='program_times',
|
||||
to='pretixbase.item')),
|
||||
],
|
||||
),
|
||||
]
|
||||
18
src/pretix/base/migrations/0295_user_is_verified.py
Normal file
18
src/pretix/base/migrations/0295_user_is_verified.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.2.23 on 2025-09-04 16:06
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("pretixbase", "0294_item_program_time"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="is_verified",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
@@ -36,8 +36,9 @@ from .giftcards import GiftCard, GiftCardAcceptance, GiftCardTransaction
|
||||
from .invoices import Invoice, InvoiceLine, invoice_filename
|
||||
from .items import (
|
||||
Item, ItemAddOn, ItemBundle, ItemCategory, ItemMetaProperty, ItemMetaValue,
|
||||
ItemVariation, ItemVariationMetaValue, Question, QuestionOption, Quota,
|
||||
SubEventItem, SubEventItemVariation, itempicture_upload_to,
|
||||
ItemProgramTime, ItemVariation, ItemVariationMetaValue, Question,
|
||||
QuestionOption, Quota, SubEventItem, SubEventItemVariation,
|
||||
itempicture_upload_to,
|
||||
)
|
||||
from .log import LogEntry
|
||||
from .media import ReusableMedium
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
import binascii
|
||||
import json
|
||||
import operator
|
||||
import secrets
|
||||
from datetime import timedelta
|
||||
from functools import reduce
|
||||
|
||||
@@ -44,6 +45,7 @@ from django.contrib.auth.models import (
|
||||
)
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import BadRequest, PermissionDenied
|
||||
from django.db import IntegrityError, models, transaction
|
||||
from django.db.models import Q
|
||||
from django.utils.crypto import get_random_string, salted_hmac
|
||||
@@ -239,9 +241,11 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||
|
||||
USERNAME_FIELD = 'email'
|
||||
REQUIRED_FIELDS = []
|
||||
MAX_CONFIRMATION_CODE_ATTEMPTS = 10
|
||||
|
||||
email = models.EmailField(unique=True, db_index=True, null=True, blank=True,
|
||||
verbose_name=_('Email'), max_length=190)
|
||||
is_verified = models.BooleanField(default=False, verbose_name=_('Verified email address'))
|
||||
fullname = models.CharField(max_length=255, blank=True, null=True,
|
||||
verbose_name=_('Full name'))
|
||||
is_active = models.BooleanField(default=True,
|
||||
@@ -353,6 +357,77 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||
except SendMailException:
|
||||
pass # Already logged
|
||||
|
||||
def send_confirmation_code(self, session, reason, email=None, state=None):
|
||||
"""
|
||||
Sends a confirmation code via email to the user. The code is only valid for the action specified by `reason`.
|
||||
The email is either sent to the email address currently on file for the user, or to the one given in the optional `email` parameter.
|
||||
A `state` value can be provided which is bound to this confirmation code, and returned on successfully checking the code.
|
||||
:param session: the user's request session
|
||||
:param reason: the action which should be confirmed using this confirmation code (currently, only `email_change` is allowed)
|
||||
:param email: optional, the email address to send the confirmation code to
|
||||
:param state: optional
|
||||
"""
|
||||
from pretix.base.services.mail import mail
|
||||
|
||||
with language(self.locale):
|
||||
if reason == 'email_change':
|
||||
msg = str(_('to confirm changing your email address from {old_email}\nto {new_email}, use the following code:').format(
|
||||
old_email=self.email, new_email=email,
|
||||
))
|
||||
elif reason == 'email_verify':
|
||||
msg = str(_('to confirm that your email address {email} belongs to your pretix account, use the following code:').format(
|
||||
email=self.email,
|
||||
))
|
||||
else:
|
||||
raise Exception('Invalid confirmation code reason')
|
||||
|
||||
code = "%07d" % secrets.SystemRandom().randint(0, 9999999)
|
||||
session['user_confirmation_code:' + reason] = {
|
||||
'code': code,
|
||||
'state': state,
|
||||
'attempts': 0,
|
||||
}
|
||||
mail(
|
||||
email or self.email,
|
||||
_('pretix confirmation code'),
|
||||
'pretixcontrol/email/confirmation_code.txt',
|
||||
{
|
||||
'user': self,
|
||||
'reason': msg,
|
||||
'code': code,
|
||||
},
|
||||
event=None,
|
||||
user=self,
|
||||
locale=self.locale
|
||||
)
|
||||
|
||||
def check_confirmation_code(self, session, reason, code):
|
||||
"""
|
||||
Checks a confirmation code entered by the user against the valid code stored in the session.
|
||||
If the code is correct, an optional state bound to the code is returned.
|
||||
If the code is incorrect, PermissionDenied is raised. If the code could not be validated, either because no
|
||||
code for the given reason is stored, or the number of input attempts is exceeded, BadRequest is raised.
|
||||
|
||||
:param session: the user's request session
|
||||
:param reason: the action which should be confirmed using this confirmation code
|
||||
:param code: the code entered by the user
|
||||
:return: optional state bound to this code using the state parameter of send_confirmation_code, None otherwise
|
||||
"""
|
||||
stored = session.get('user_confirmation_code:' + reason)
|
||||
if not stored:
|
||||
raise BadRequest
|
||||
|
||||
if stored['attempts'] > User.MAX_CONFIRMATION_CODE_ATTEMPTS:
|
||||
raise BadRequest
|
||||
|
||||
if int(stored['code']) == int(code):
|
||||
del session['user_confirmation_code:' + reason]
|
||||
return stored['state']
|
||||
else:
|
||||
stored['attempts'] += 1
|
||||
session['user_confirmation_code:' + reason] = stored
|
||||
raise PermissionDenied
|
||||
|
||||
def send_password_reset(self):
|
||||
from pretix.base.services.mail import mail
|
||||
|
||||
|
||||
@@ -847,7 +847,7 @@ class Event(EventMixin, LoggedModel):
|
||||
from ..signals import event_copy_data
|
||||
from . import (
|
||||
Discount, Item, ItemAddOn, ItemBundle, ItemCategory, ItemMetaValue,
|
||||
ItemVariationMetaValue, Question, Quota,
|
||||
ItemProgramTime, ItemVariationMetaValue, Question, Quota,
|
||||
)
|
||||
|
||||
# Note: avoid self.set_active_plugins(), it causes trouble e.g. for the badges plugin.
|
||||
@@ -990,6 +990,11 @@ class Event(EventMixin, LoggedModel):
|
||||
ia.bundled_variation = variation_map[ia.bundled_variation.pk]
|
||||
ia.save(force_insert=True)
|
||||
|
||||
for ipt in ItemProgramTime.objects.filter(item__event=other).prefetch_related('item'):
|
||||
ipt.pk = None
|
||||
ipt.item = item_map[ipt.item.pk]
|
||||
ipt.save(force_insert=True)
|
||||
|
||||
quota_map = {}
|
||||
for q in Quota.objects.filter(event=other, subevent__isnull=True).prefetch_related('items', 'variations'):
|
||||
quota_map[q.pk] = q
|
||||
|
||||
@@ -2294,3 +2294,27 @@ class ItemVariationMetaValue(LoggedModel):
|
||||
|
||||
class Meta:
|
||||
unique_together = ('variation', 'property')
|
||||
|
||||
|
||||
class ItemProgramTime(models.Model):
|
||||
"""
|
||||
This model can be used to add a program time to an item.
|
||||
|
||||
:param item: The item the program time applies to
|
||||
:type item: Item
|
||||
:param start: The date and time this program time starts
|
||||
:type start: datetime
|
||||
:param end: The date and time this program time ends
|
||||
:type end: datetime
|
||||
"""
|
||||
item = models.ForeignKey('Item', related_name='program_times', on_delete=models.CASCADE)
|
||||
start = models.DateTimeField(verbose_name=_("Start"))
|
||||
end = models.DateTimeField(verbose_name=_("End"))
|
||||
|
||||
def clean(self):
|
||||
self.clean_start_end(start=self.start, end=self.end)
|
||||
super().clean()
|
||||
|
||||
def clean_start_end(self, start: datetime = None, end: datetime = None):
|
||||
if start and end and start > end:
|
||||
raise ValidationError(_("The program end must not be before the program start."))
|
||||
|
||||
@@ -280,13 +280,13 @@ class Seat(models.Model):
|
||||
|
||||
def is_available(self, ignore_cart=None, ignore_orderpos=None, ignore_voucher_id=None,
|
||||
sales_channel='web',
|
||||
ignore_distancing=False, distance_ignore_cart_id=None):
|
||||
ignore_distancing=False, distance_ignore_cart_id=None, always_allow_blocked=False):
|
||||
from .orders import Order
|
||||
from .organizer import SalesChannel
|
||||
|
||||
if isinstance(sales_channel, SalesChannel):
|
||||
sales_channel = sales_channel.identifier
|
||||
if self.blocked and sales_channel not in self.event.settings.seating_allow_blocked_seats_for_channel:
|
||||
if not always_allow_blocked and self.blocked and sales_channel not in self.event.settings.seating_allow_blocked_seats_for_channel:
|
||||
return False
|
||||
opqs = self.orderposition_set.filter(
|
||||
order__status__in=[Order.STATUS_PENDING, Order.STATUS_PAID],
|
||||
|
||||
@@ -1377,7 +1377,7 @@ class GiftCardPayment(BasePaymentProvider):
|
||||
execute_payment_needs_user = False
|
||||
verbose_name = _("Gift card")
|
||||
payment_form_class = GiftCardPaymentForm
|
||||
payment_form_template_name = 'pretixcontrol/giftcards/checkout.html'
|
||||
payment_form_template_name = 'pretixpresale/giftcard/checkout.html'
|
||||
|
||||
@cached_property
|
||||
def customer_gift_cards(self):
|
||||
@@ -1504,7 +1504,7 @@ class GiftCardPayment(BasePaymentProvider):
|
||||
return super().order_change_allowed(order) and self.event.organizer.has_gift_cards
|
||||
|
||||
def checkout_confirm_render(self, request, order=None, info_data=None) -> str:
|
||||
return get_template('pretixcontrol/giftcards/checkout_confirm.html').render({
|
||||
return get_template('pretixpresale/giftcard/checkout_confirm.html').render({
|
||||
'info_data': info_data,
|
||||
})
|
||||
|
||||
|
||||
@@ -84,6 +84,7 @@ from pretix.base.settings import PERSON_NAME_SCHEMES
|
||||
from pretix.base.signals import layout_image_variables, layout_text_variables
|
||||
from pretix.base.templatetags.money import money_filter
|
||||
from pretix.base.templatetags.phone_format import phone_format
|
||||
from pretix.helpers.daterange import datetimerange
|
||||
from pretix.helpers.reportlab import ThumbnailingImageReader, reshaper
|
||||
from pretix.presale.style import get_fonts
|
||||
|
||||
@@ -490,6 +491,12 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"TIME_FORMAT"
|
||||
) if op.valid_until else ""
|
||||
}),
|
||||
("program_times", {
|
||||
"label": _("Program times: date and time"),
|
||||
"editor_sample": _(
|
||||
"2017-05-31 10:00 – 12:00\n2017-05-31 14:00 – 16:00\n2017-05-31 14:00 – 2017-06-01 14:00"),
|
||||
"evaluate": lambda op, order, ev: get_program_times(op, ev)
|
||||
}),
|
||||
("medium_identifier", {
|
||||
"label": _("Reusable Medium ID"),
|
||||
"editor_sample": "ABC1234DEF4567",
|
||||
@@ -734,6 +741,16 @@ def get_seat(op: OrderPosition):
|
||||
return None
|
||||
|
||||
|
||||
def get_program_times(op: OrderPosition, ev: Event):
|
||||
return '\n'.join([
|
||||
datetimerange(
|
||||
pt.start.astimezone(ev.timezone),
|
||||
pt.end.astimezone(ev.timezone),
|
||||
as_html=False
|
||||
) for pt in op.item.program_times.all()
|
||||
])
|
||||
|
||||
|
||||
def generate_compressed_addon_list(op, order, event):
|
||||
itemcount = defaultdict(int)
|
||||
addons = [p for p in (
|
||||
|
||||
@@ -258,9 +258,15 @@ def build_invoice(invoice: Invoice) -> Invoice:
|
||||
if resp:
|
||||
desc += "<br/>" + resp
|
||||
|
||||
for answ in p.answers.all():
|
||||
if not answ.question.print_on_invoice:
|
||||
continue
|
||||
answers_qs = p.answers.filter(
|
||||
question__print_on_invoice=True
|
||||
).select_related(
|
||||
'question'
|
||||
).order_by(
|
||||
'question__position',
|
||||
'question__id'
|
||||
)
|
||||
for answ in answers_qs:
|
||||
desc += "<br />{}{} {}".format(
|
||||
answ.question.question,
|
||||
"" if str(answ.question.question).endswith("?") else ":",
|
||||
|
||||
@@ -1670,13 +1670,14 @@ class OrderChangeManager:
|
||||
AddBlockOperation = namedtuple('AddBlockOperation', ('position', 'block_name', 'ignore_from_quota_while_blocked'))
|
||||
RemoveBlockOperation = namedtuple('RemoveBlockOperation', ('position', 'block_name', 'ignore_from_quota_while_blocked'))
|
||||
|
||||
def __init__(self, order: Order, user=None, auth=None, notify=True, reissue_invoice=True):
|
||||
def __init__(self, order: Order, user=None, auth=None, notify=True, reissue_invoice=True, allow_blocked_seats=False):
|
||||
self.order = order
|
||||
self.user = user
|
||||
self.auth = auth
|
||||
self.event = order.event
|
||||
self.split_order = None
|
||||
self.reissue_invoice = reissue_invoice
|
||||
self.allow_blocked_seats = allow_blocked_seats
|
||||
self._committed = False
|
||||
self._totaldiff_guesstimate = 0
|
||||
self._quotadiff = Counter()
|
||||
@@ -2197,7 +2198,7 @@ class OrderChangeManager:
|
||||
for seat, diff in self._seatdiff.items():
|
||||
if diff <= 0:
|
||||
continue
|
||||
if not seat.is_available(sales_channel=self.order.sales_channel, ignore_distancing=True) or diff > 1:
|
||||
if not seat.is_available(sales_channel=self.order.sales_channel, ignore_distancing=True, always_allow_blocked=self.allow_blocked_seats) or diff > 1:
|
||||
raise OrderError(self.error_messages['seat_unavailable'].format(seat=seat.name))
|
||||
|
||||
if self.event.has_subevents:
|
||||
|
||||
@@ -56,7 +56,8 @@ from i18nfield.forms import I18nFormField, I18nTextarea
|
||||
from pretix.base.forms import I18nFormSet, I18nMarkdownTextarea, I18nModelForm
|
||||
from pretix.base.forms.widgets import DatePickerWidget
|
||||
from pretix.base.models import (
|
||||
Item, ItemCategory, ItemVariation, Question, QuestionOption, Quota,
|
||||
Item, ItemCategory, ItemProgramTime, ItemVariation, Question,
|
||||
QuestionOption, Quota,
|
||||
)
|
||||
from pretix.base.models.items import ItemAddOn, ItemBundle, ItemMetaValue
|
||||
from pretix.base.signals import item_copy_data
|
||||
@@ -572,6 +573,8 @@ class ItemCreateForm(I18nModelForm):
|
||||
for b in self.cleaned_data['copy_from'].bundles.all():
|
||||
instance.bundles.create(bundled_item=b.bundled_item, bundled_variation=b.bundled_variation,
|
||||
count=b.count, designated_price=b.designated_price)
|
||||
for pt in self.cleaned_data['copy_from'].program_times.all():
|
||||
instance.program_times.create(start=pt.start, end=pt.end)
|
||||
|
||||
item_copy_data.send(sender=self.event, source=self.cleaned_data['copy_from'], target=instance)
|
||||
|
||||
@@ -1321,3 +1324,49 @@ class ItemMetaValueForm(forms.ModelForm):
|
||||
widgets = {
|
||||
'value': forms.TextInput()
|
||||
}
|
||||
|
||||
|
||||
class ItemProgramTimeFormSet(I18nFormSet):
|
||||
template = "pretixcontrol/item/include_program_times.html"
|
||||
title = _('Program times')
|
||||
|
||||
def _construct_form(self, i, **kwargs):
|
||||
kwargs['event'] = self.event
|
||||
return super()._construct_form(i, **kwargs)
|
||||
|
||||
@property
|
||||
def empty_form(self):
|
||||
self.is_valid()
|
||||
form = self.form(
|
||||
auto_id=self.auto_id,
|
||||
prefix=self.add_prefix('__prefix__'),
|
||||
empty_permitted=True,
|
||||
use_required_attribute=False,
|
||||
locales=self.locales,
|
||||
event=self.event
|
||||
)
|
||||
self.add_fields(form, None)
|
||||
return form
|
||||
|
||||
|
||||
class ItemProgramTimeForm(I18nModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['end'].widget.attrs['data-date-after'] = '#id_{prefix}-start_0'.format(prefix=self.prefix)
|
||||
|
||||
class Meta:
|
||||
model = ItemProgramTime
|
||||
localized_fields = '__all__'
|
||||
fields = [
|
||||
'start',
|
||||
'end',
|
||||
]
|
||||
field_classes = {
|
||||
'start': forms.SplitDateTimeField,
|
||||
'end': forms.SplitDateTimeField,
|
||||
}
|
||||
widgets = {
|
||||
'start': SplitDateTimePickerWidget(),
|
||||
'end': SplitDateTimePickerWidget(),
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ class UserEditForm(forms.ModelForm):
|
||||
'email',
|
||||
'require_2fa',
|
||||
'is_active',
|
||||
'is_verified',
|
||||
'is_staff',
|
||||
'needs_password_change',
|
||||
'last_login'
|
||||
|
||||
@@ -667,6 +667,14 @@ class UserSettingsChangedLogEntryType(LogEntryType):
|
||||
return text
|
||||
|
||||
|
||||
@log_entry_types.new_from_dict({
|
||||
'pretix.user.email.changed': _('Your email address has been changed from {old_email} to {email}.'),
|
||||
'pretix.user.email.confirmed': _('Your email address {email} has been confirmed.'),
|
||||
})
|
||||
class UserEmailChangedLogEntryType(LogEntryType):
|
||||
pass
|
||||
|
||||
|
||||
class UserImpersonatedLogEntryType(LogEntryType):
|
||||
def display(self, logentry, data):
|
||||
return self.plain.format(data['other_email'])
|
||||
@@ -882,6 +890,9 @@ class EventPluginStateLogEntryType(EventLogEntryType):
|
||||
'pretix.event.item.bundles.added': _('A bundled item has been added to this product.'),
|
||||
'pretix.event.item.bundles.removed': _('A bundled item has been removed from this product.'),
|
||||
'pretix.event.item.bundles.changed': _('A bundled item has been changed on this product.'),
|
||||
'pretix.event.item.program_times.added': _('A program time has been added to this product.'),
|
||||
'pretix.event.item.program_times.changed': _('A program time has been changed on this product.'),
|
||||
'pretix.event.item.program_times.removed': _('A program time has been removed from this product.'),
|
||||
})
|
||||
class CoreItemLogEntryType(ItemLogEntryType):
|
||||
pass
|
||||
|
||||
@@ -72,7 +72,7 @@ class PermissionMiddleware:
|
||||
)
|
||||
|
||||
EXCEPTIONS_FORCED_PW_CHANGE = (
|
||||
"user.settings",
|
||||
"user.settings.password.change",
|
||||
"auth.logout"
|
||||
)
|
||||
|
||||
@@ -139,7 +139,7 @@ class PermissionMiddleware:
|
||||
return redirect_to_url(reverse('control:user.reauth') + '?next=' + quote(request.get_full_path()))
|
||||
except SessionPasswordChangeRequired:
|
||||
if url_name not in self.EXCEPTIONS_FORCED_PW_CHANGE:
|
||||
return redirect_to_url(reverse('control:user.settings') + '?next=' + quote(request.get_full_path()))
|
||||
return redirect_to_url(reverse('control:user.settings.password.change') + '?next=' + quote(request.get_full_path()))
|
||||
except Session2FASetupRequired:
|
||||
if url_name not in self.EXCEPTIONS_2FA:
|
||||
return redirect_to_url(reverse('control:user.settings.2fa'))
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<h3>{% trans "Set new password" %}</h3>
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form_errors form type='all' layout='inline' %}
|
||||
{% bootstrap_field form.email %}
|
||||
{% bootstrap_field form.password %}
|
||||
{% bootstrap_field form.password_repeat %}
|
||||
<div class="form-group buttons">
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{% load i18n %}{% blocktrans with url=url|safe messages=messages|safe %}Hello,
|
||||
|
||||
{{ reason }}
|
||||
|
||||
{{ code }}
|
||||
|
||||
Please do never give this code to another person. Our support team will never ask for this code.
|
||||
|
||||
If this code was not requested by you, please contact us immediately.
|
||||
|
||||
Best regards,
|
||||
Your pretix team
|
||||
{% endblocktrans %}
|
||||
@@ -1,35 +0,0 @@
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% load money %}
|
||||
{% load rich_text %}
|
||||
|
||||
{{ request.event.settings.payment_giftcard_public_description|rich_text }}
|
||||
{% if customer_gift_cards %}
|
||||
<p><strong>
|
||||
<span class="sr-only">{% trans "Information" %}</span>
|
||||
{% trans "The following gift cards are available in your customer account:" %}
|
||||
</strong></p>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<ul class="list-group">
|
||||
{% for c in customer_gift_cards %}
|
||||
<li class="list-group-item row row-no-gutters">
|
||||
<div class="col-sm-8 col-md-9" id="gc-code-{{ forloop.counter }}">
|
||||
{{ c }}
|
||||
</div>
|
||||
<div class="col-sm-2 text-right" id="gc-value-{{ forloop.counter }}">
|
||||
{{ c.value|money:c.currency }}
|
||||
</div>
|
||||
<div class="col-sm-2 col-md-1 text-right">
|
||||
<button name="use_giftcard" class="btn btn-primary btn-xs use_giftcard" data-value="{{ c.secret }}"
|
||||
title="{% trans "Use gift card" %}"
|
||||
aria-describedby="gc-code-{{ forloop.counter }} gc-value-{{ forloop.counter }}">
|
||||
{% trans "Apply" %}
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% bootstrap_form form layout='checkout' %}
|
||||
@@ -0,0 +1,70 @@
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% load formset_tags %}
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
With program times, you can set specific dates and times for this product.
|
||||
This is useful if this product represents access to parts of your event that happen at different times than your event in general.
|
||||
This will not affect access control, but will affect calendar invites and ticket output.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<div class="formset" data-formset data-formset-prefix="{{ formset.prefix }}">
|
||||
{{ formset.management_form }}
|
||||
{% bootstrap_formset_errors formset %}
|
||||
<div data-formset-body>
|
||||
{% for form in formset %}
|
||||
<div class="panel panel-default" data-formset-form>
|
||||
<div class="sr-only">
|
||||
{{ form.id }}
|
||||
{% bootstrap_field form.DELETE form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
<h3 class="panel-title">{% trans "Program time" %}</h3>
|
||||
</div>
|
||||
<div class="col-sm-4 text-right flip">
|
||||
<button type="button" class="btn btn-xs btn-danger" data-formset-delete-button>
|
||||
<i class="fa fa-trash"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body form-horizontal">
|
||||
{% bootstrap_form_errors form %}
|
||||
{% bootstrap_field form.start layout="control" %}
|
||||
{% bootstrap_field form.end layout="control" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<script type="form-template" data-formset-empty-form>
|
||||
{% escapescript %}
|
||||
<div class="panel panel-default" data-formset-form>
|
||||
<div class="sr-only">
|
||||
{{ formset.empty_form.id }}
|
||||
{% bootstrap_field formset.empty_form.DELETE form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
<h3 class="panel-title">{% trans "Program time" %}</h3>
|
||||
</div>
|
||||
<div class="col-sm-4 text-right flip">
|
||||
<button type="button" class="btn btn-xs btn-danger" data-formset-delete-button>
|
||||
<i class="fa fa-trash"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body form-horizontal">
|
||||
{% bootstrap_field formset.empty_form.start layout="control" %}
|
||||
{% bootstrap_field formset.empty_form.end layout="control" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endescapescript %}
|
||||
</script>
|
||||
<p>
|
||||
<button type="button" class="btn btn-default" data-formset-add>
|
||||
<i class="fa fa-plus"></i> {% trans "Add a program time" %}</button>
|
||||
</p>
|
||||
</div>
|
||||
@@ -769,10 +769,12 @@
|
||||
</div>
|
||||
<div class="col-md-3 col-xs-6 col-md-offset-5 price">
|
||||
<strong>{{ items.total|money:event.currency }}</strong>
|
||||
<br/>
|
||||
<small class="admin-only">
|
||||
tax_rounding_mode = {{ order.tax_rounding_mode }}
|
||||
</small>
|
||||
{% if django_settings.DEBUG %}
|
||||
<br/>
|
||||
<small class="admin-only">
|
||||
tax_rounding_mode = {{ order.tax_rounding_mode }}
|
||||
</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
{% extends "pretixcontrol/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% block title %}{% trans "Change login email address" %}{% endblock %}
|
||||
{% block content %}
|
||||
<form action="" method="post" class="form centered-form">
|
||||
<h1>
|
||||
{% trans "Change login email address" %}
|
||||
</h1>
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form_errors form %}
|
||||
<p class="text-muted">
|
||||
{% trans "This changes the email address used to login to your account, as well as where we send email notifications." %}
|
||||
</p>
|
||||
{% bootstrap_field form.old_email %}
|
||||
{% bootstrap_field form.new_email %}
|
||||
<p>
|
||||
{% trans "We will send a confirmation code to your new email address, which you need to enter in the next step to confirm the email address is correct." %}
|
||||
</p>
|
||||
<div class="form-group submit-group">
|
||||
<a href="{% url "control:user.settings" %}" class="btn btn-default btn-cancel">
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary btn-save btn-lg">
|
||||
{% trans "Continue" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,24 @@
|
||||
{% extends "pretixcontrol/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% block title %}{% trans "Change password" %}{% endblock %}
|
||||
{% block content %}
|
||||
<form action="" method="post" class="form centered-form">
|
||||
<h1>
|
||||
{% trans "Change password" %}
|
||||
</h1>
|
||||
<br>
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form_errors form %}
|
||||
{% bootstrap_field form.email %}
|
||||
{% bootstrap_field form.old_pw %}
|
||||
{% bootstrap_field form.new_pw %}
|
||||
{% bootstrap_field form.new_pw_repeat %}
|
||||
<div class="form-group submit-group">
|
||||
<a href="{% url "control:user.settings" %}" class="btn btn-default btn-cancel">{% trans "Cancel" %}</a>
|
||||
<button type="submit" class="btn btn-primary btn-save btn-lg">
|
||||
{% trans "Change password" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,21 @@
|
||||
{% extends "pretixcontrol/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% block title %}{% trans "Enter confirmation code" %}{% endblock %}
|
||||
{% block content %}
|
||||
<form action="" method="post" class="form centered-form">
|
||||
<h1>
|
||||
{% trans "Enter confirmation code" %}
|
||||
</h1>
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form_errors form type='all' layout='inline' %}
|
||||
<p>{{ message }}</p>
|
||||
{% bootstrap_field form.code %}
|
||||
<div class="form-group submit-group">
|
||||
<a href="{{ cancel_url }}" class="btn btn-default btn-cancel">{% trans "Cancel" %}</a>
|
||||
<button type="submit" class="btn btn-primary btn-save btn-lg">
|
||||
{% trans "Continue" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -3,8 +3,26 @@
|
||||
{% load bootstrap3 %}
|
||||
{% block title %}{% trans "Account settings" %}{% endblock %}
|
||||
{% block content %}
|
||||
{% if not user.is_verified %}
|
||||
<div class="alert alert-info">
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
Your email address is not confirmed yet. To secure your account, please confirm your email address using
|
||||
a confirmation code we will send to your email address.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<form action="{% url "control:user.settings.email.send_verification_code" %}" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{% trans "Send confirmation email" %}
|
||||
</button>
|
||||
</form>
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
<h1>{% trans "Account settings" %}</h1>
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
<form action="" method="post" class="form-horizontal" data-testid="usersettingsform">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form_errors form %}
|
||||
<fieldset>
|
||||
@@ -13,7 +31,7 @@
|
||||
{% bootstrap_field form.locale layout='horizontal' %}
|
||||
{% bootstrap_field form.timezone layout='horizontal' %}
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label" for="id_new_pw_repeat">{% trans "Notifications" %}</label>
|
||||
<label class="col-md-3 control-label">{% trans "Notifications" %}</label>
|
||||
<div class="col-md-9 static-form-row">
|
||||
{% if request.user.notifications_send and request.user.notification_settings.exists %}
|
||||
<span class="label label-success">
|
||||
@@ -41,8 +59,18 @@
|
||||
{% bootstrap_field form.new_pw layout='horizontal' %}
|
||||
{% bootstrap_field form.new_pw_repeat layout='horizontal' %}
|
||||
{% endif %}
|
||||
{% if user.auth_backend == 'native' %}
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label">{% trans "Password" %}</label>
|
||||
<div class="col-md-9 static-form-row">
|
||||
<a href="{% url "control:user.settings.password.change" %}">
|
||||
<span class="fa fa-edit"></span> {% trans "Change password" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label" for="id_new_pw_repeat">{% trans "Two-factor authentication" %}</label>
|
||||
<label class="col-md-3 control-label">{% trans "Two-factor authentication" %}</label>
|
||||
<div class="col-md-9 static-form-row">
|
||||
{% if user.require_2fa %}
|
||||
<span class="label label-success">{% trans "Enabled" %}</span>
|
||||
@@ -58,7 +86,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label" for="">{% trans "Authorized applications" %}</label>
|
||||
<label class="col-md-3 control-label">{% trans "Authorized applications" %}</label>
|
||||
<div class="col-md-9 static-form-row">
|
||||
<a href="{% url "control:user.settings.oauth.list" %}">
|
||||
<span class="fa fa-plug"></span>
|
||||
@@ -67,7 +95,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label" for="">{% trans "Account history" %}</label>
|
||||
<label class="col-md-3 control-label">{% trans "Account history" %}</label>
|
||||
<div class="col-md-9 static-form-row">
|
||||
<a href="{% url "control:user.settings.history" %}">
|
||||
<span class="fa fa-history"></span>
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
{% if form.new_pw %}
|
||||
{% bootstrap_field form.new_pw layout='control' %}
|
||||
{% bootstrap_field form.new_pw_repeat layout='control' %}
|
||||
{% bootstrap_field form.is_verified layout='control' %}
|
||||
{% endif %}
|
||||
{% bootstrap_field form.last_login layout='control' %}
|
||||
{% bootstrap_field form.require_2fa layout='control' %}
|
||||
|
||||
@@ -110,6 +110,10 @@ urlpatterns = [
|
||||
name='user.settings.2fa.confirm.webauthn'),
|
||||
re_path(r'^settings/2fa/(?P<devicetype>[^/]+)/(?P<device>[0-9]+)/delete', user.User2FADeviceDeleteView.as_view(),
|
||||
name='user.settings.2fa.delete'),
|
||||
re_path(r'^settings/email/confirm$', user.UserEmailConfirmView.as_view(), name='user.settings.email.confirm'),
|
||||
re_path(r'^settings/email/change$', user.UserEmailChangeView.as_view(), name='user.settings.email.change'),
|
||||
re_path(r'^settings/email/verify', user.UserEmailVerifyView.as_view(), name='user.settings.email.send_verification_code'),
|
||||
re_path(r'^settings/password/change$', user.UserPasswordChangeView.as_view(), name='user.settings.password.change'),
|
||||
re_path(r'^organizers/$', organizer.OrganizerList.as_view(), name='organizers'),
|
||||
re_path(r'^organizers/add$', organizer.OrganizerCreate.as_view(), name='organizers.add'),
|
||||
re_path(r'^organizers/select2$', typeahead.organizer_select2, name='organizers.select2'),
|
||||
|
||||
@@ -60,13 +60,14 @@ from django.views.generic.detail import DetailView, SingleObjectMixin
|
||||
from django_countries.fields import Country
|
||||
|
||||
from pretix.api.serializers.item import (
|
||||
ItemAddOnSerializer, ItemBundleSerializer, ItemVariationSerializer,
|
||||
ItemAddOnSerializer, ItemBundleSerializer, ItemProgramTimeSerializer,
|
||||
ItemVariationSerializer,
|
||||
)
|
||||
from pretix.base.forms import I18nFormSet
|
||||
from pretix.base.models import (
|
||||
CartPosition, Item, ItemCategory, ItemVariation, Order, OrderPosition,
|
||||
Question, QuestionAnswer, QuestionOption, Quota, SeatCategoryMapping,
|
||||
Voucher,
|
||||
CartPosition, Item, ItemCategory, ItemProgramTime, ItemVariation, Order,
|
||||
OrderPosition, Question, QuestionAnswer, QuestionOption, Quota,
|
||||
SeatCategoryMapping, Voucher,
|
||||
)
|
||||
from pretix.base.models.event import SubEvent
|
||||
from pretix.base.models.items import ItemAddOn, ItemBundle, ItemMetaValue
|
||||
@@ -75,9 +76,9 @@ from pretix.base.services.tickets import invalidate_cache
|
||||
from pretix.base.signals import quota_availability
|
||||
from pretix.control.forms.item import (
|
||||
CategoryForm, ItemAddOnForm, ItemAddOnsFormSet, ItemBundleForm,
|
||||
ItemBundleFormSet, ItemCreateForm, ItemMetaValueForm, ItemUpdateForm,
|
||||
ItemVariationForm, ItemVariationsFormSet, QuestionForm, QuestionOptionForm,
|
||||
QuotaForm,
|
||||
ItemBundleFormSet, ItemCreateForm, ItemMetaValueForm, ItemProgramTimeForm,
|
||||
ItemProgramTimeFormSet, ItemUpdateForm, ItemVariationForm,
|
||||
ItemVariationsFormSet, QuestionForm, QuestionOptionForm, QuotaForm,
|
||||
)
|
||||
from pretix.control.permissions import (
|
||||
EventPermissionRequiredMixin, event_permission_required,
|
||||
@@ -1431,7 +1432,8 @@ class ItemUpdateGeneral(ItemDetailMixin, EventPermissionRequiredMixin, MetaDataE
|
||||
form.instance.position = i
|
||||
setattr(form.instance, attr, self.get_object())
|
||||
created = not form.instance.pk
|
||||
form.save()
|
||||
if form.has_changed():
|
||||
form.save()
|
||||
if form.has_changed() and any(a for a in form.changed_data if a != 'ORDER'):
|
||||
change_data = {k: form.cleaned_data.get(k) for k in form.changed_data}
|
||||
if key == 'variations':
|
||||
@@ -1497,6 +1499,16 @@ class ItemUpdateGeneral(ItemDetailMixin, EventPermissionRequiredMixin, MetaDataE
|
||||
'bundles', 'bundles', 'base_item', order=False,
|
||||
serializer=ItemBundleSerializer
|
||||
)
|
||||
elif k == 'program_times':
|
||||
self.save_formset(
|
||||
'program_times', 'program_times', order=False,
|
||||
serializer=ItemProgramTimeSerializer
|
||||
)
|
||||
if not change_data:
|
||||
for f in v.forms:
|
||||
if (f in v.deleted_forms and f.instance.pk) or f.has_changed():
|
||||
invalidate_cache.apply_async(kwargs={'event': self.request.event.pk, 'item': self.object.pk})
|
||||
break
|
||||
else:
|
||||
v.save()
|
||||
|
||||
@@ -1559,9 +1571,20 @@ class ItemUpdateGeneral(ItemDetailMixin, EventPermissionRequiredMixin, MetaDataE
|
||||
queryset=ItemBundle.objects.filter(base_item=self.get_object()),
|
||||
event=self.request.event, item=self.item, prefix="bundles"
|
||||
)),
|
||||
('program_times', inlineformset_factory(
|
||||
Item, ItemProgramTime,
|
||||
form=ItemProgramTimeForm, formset=ItemProgramTimeFormSet,
|
||||
can_order=False, can_delete=True, extra=0
|
||||
)(
|
||||
self.request.POST if self.request.method == "POST" else None,
|
||||
queryset=ItemProgramTime.objects.filter(item=self.get_object()),
|
||||
event=self.request.event, prefix="program_times"
|
||||
)),
|
||||
])
|
||||
if not self.object.has_variations:
|
||||
del f['variations']
|
||||
if self.item.event.has_subevents:
|
||||
del f['program_times']
|
||||
|
||||
i = 0
|
||||
for rec, resp in item_formsets.send(sender=self.request.event, item=self.item, request=self.request):
|
||||
|
||||
@@ -2146,7 +2146,8 @@ class OrderChange(OrderView):
|
||||
self.order,
|
||||
user=self.request.user,
|
||||
notify=notify,
|
||||
reissue_invoice=self.other_form.cleaned_data['reissue_invoice'] if self.other_form.is_valid() else True
|
||||
reissue_invoice=self.other_form.cleaned_data['reissue_invoice'] if self.other_form.is_valid() else True,
|
||||
allow_blocked_seats=True,
|
||||
)
|
||||
form_valid = (self._process_add_fees(ocm) and
|
||||
self._process_add_positions(ocm) and
|
||||
|
||||
@@ -44,7 +44,9 @@ from pypdf.errors import PdfReadError
|
||||
from reportlab.lib.units import mm
|
||||
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import CachedFile, InvoiceAddress, OrderPosition
|
||||
from pretix.base.models import (
|
||||
CachedFile, InvoiceAddress, ItemProgramTime, OrderPosition,
|
||||
)
|
||||
from pretix.base.pdf import get_images, get_variables
|
||||
from pretix.base.settings import PERSON_NAME_SCHEMES
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
@@ -95,6 +97,9 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
|
||||
description=_("Sample product description"))
|
||||
item2 = self.request.event.items.create(name=_("Sample workshop"), default_price=Decimal('23.40'))
|
||||
|
||||
ItemProgramTime.objects.create(start=now(), end=now(), item=item)
|
||||
ItemProgramTime.objects.create(start=now(), end=now(), item=item2)
|
||||
|
||||
from pretix.base.models import Order
|
||||
order = self.request.event.orders.create(status=Order.STATUS_PENDING, datetime=now(),
|
||||
email='sample@pretix.eu',
|
||||
|
||||
@@ -44,11 +44,13 @@ from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import update_session_auth_hash
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import BadRequest, PermissionDenied
|
||||
from django.db import transaction
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.urls import reverse
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.html import format_html
|
||||
from django.utils.http import url_has_allowed_host_and_scheme
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@@ -60,8 +62,11 @@ from django_scopes import scopes_disabled
|
||||
from webauthn.helpers import generate_challenge, generate_user_handle
|
||||
|
||||
from pretix.base.auth import get_auth_backends
|
||||
from pretix.base.forms.auth import ReauthForm
|
||||
from pretix.base.forms.user import User2FADeviceAddForm, UserSettingsForm
|
||||
from pretix.base.forms.auth import ConfirmationCodeForm, ReauthForm
|
||||
from pretix.base.forms.user import (
|
||||
User2FADeviceAddForm, UserEmailChangeForm, UserPasswordChangeForm,
|
||||
UserSettingsForm,
|
||||
)
|
||||
from pretix.base.models import (
|
||||
Event, LogEntry, NotificationSetting, U2FDevice, User, WebAuthnDevice,
|
||||
)
|
||||
@@ -237,25 +242,7 @@ class UserSettings(UpdateView):
|
||||
|
||||
data = {}
|
||||
for k in form.changed_data:
|
||||
if k not in ('old_pw', 'new_pw_repeat'):
|
||||
if 'new_pw' == k:
|
||||
data['new_pw'] = True
|
||||
else:
|
||||
data[k] = form.cleaned_data[k]
|
||||
|
||||
msgs = []
|
||||
|
||||
if 'new_pw' in form.changed_data:
|
||||
self.request.user.needs_password_change = False
|
||||
msgs.append(_('Your password has been changed.'))
|
||||
|
||||
if 'email' in form.changed_data:
|
||||
msgs.append(_('Your email address has been changed to {email}.').format(email=form.cleaned_data['email']))
|
||||
|
||||
if msgs:
|
||||
self.request.user.send_security_notice(msgs, email=form.cleaned_data['email'])
|
||||
if self._old_email != form.cleaned_data['email']:
|
||||
self.request.user.send_security_notice(msgs, email=self._old_email)
|
||||
data[k] = form.cleaned_data[k]
|
||||
|
||||
sup = super().form_valid(form)
|
||||
self.request.user.log_action('pretix.user.settings.changed', user=self.request.user, data=data)
|
||||
@@ -834,3 +821,159 @@ class EditStaffSession(StaffMemberRequiredMixin, UpdateView):
|
||||
return get_object_or_404(StaffSession, pk=self.kwargs['id'])
|
||||
else:
|
||||
return get_object_or_404(StaffSession, pk=self.kwargs['id'], user=self.request.user)
|
||||
|
||||
|
||||
class UserPasswordChangeView(FormView):
|
||||
max_time = 300
|
||||
|
||||
form_class = UserPasswordChangeForm
|
||||
template_name = 'pretixcontrol/user/change_password.html'
|
||||
|
||||
def get_form_kwargs(self):
|
||||
if self.request.user.auth_backend != 'native':
|
||||
raise PermissionDenied
|
||||
|
||||
return {
|
||||
**super().get_form_kwargs(),
|
||||
"user": self.request.user,
|
||||
}
|
||||
|
||||
def form_valid(self, form):
|
||||
with transaction.atomic():
|
||||
self.request.user.set_password(form.cleaned_data['new_pw'])
|
||||
self.request.user.needs_password_change = False
|
||||
self.request.user.save()
|
||||
msgs = []
|
||||
msgs.append(_('Your password has been changed.'))
|
||||
self.request.user.send_security_notice(msgs)
|
||||
|
||||
self.request.user.log_action('pretix.user.settings.changed', user=self.request.user, data={'new_pw': True})
|
||||
|
||||
update_session_auth_hash(self.request, self.request.user)
|
||||
|
||||
messages.success(self.request, _('Your changes have been saved.'))
|
||||
return redirect(self.get_success_url())
|
||||
|
||||
def form_invalid(self, form):
|
||||
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
||||
return super().form_invalid(form)
|
||||
|
||||
def get_success_url(self):
|
||||
if "next" in self.request.GET and url_has_allowed_host_and_scheme(self.request.GET.get("next"), allowed_hosts=None):
|
||||
return self.request.GET.get("next")
|
||||
return reverse('control:user.settings')
|
||||
|
||||
|
||||
class UserEmailChangeView(RecentAuthenticationRequiredMixin, FormView):
|
||||
max_time = 300
|
||||
|
||||
form_class = UserEmailChangeForm
|
||||
template_name = 'pretixcontrol/user/change_email.html'
|
||||
|
||||
def get_form_kwargs(self):
|
||||
if self.request.user.auth_backend != 'native':
|
||||
raise PermissionDenied
|
||||
|
||||
return {
|
||||
**super().get_form_kwargs(),
|
||||
"user": self.request.user,
|
||||
}
|
||||
|
||||
def get_initial(self):
|
||||
return {
|
||||
"old_email": self.request.user.email
|
||||
}
|
||||
|
||||
def form_valid(self, form):
|
||||
self.request.user.send_confirmation_code(
|
||||
session=self.request.session,
|
||||
reason='email_change',
|
||||
email=form.cleaned_data['new_email'],
|
||||
state=form.cleaned_data['new_email'],
|
||||
)
|
||||
self.request.session['email_confirmation_destination'] = form.cleaned_data['new_email']
|
||||
return redirect(reverse('control:user.settings.email.confirm', kwargs={}) + '?reason=email_change')
|
||||
|
||||
def form_invalid(self, form):
|
||||
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
||||
return super().form_invalid(form)
|
||||
|
||||
|
||||
class UserEmailVerifyView(View):
|
||||
def post(self, request, *args, **kwargs):
|
||||
if self.request.user.is_verified:
|
||||
messages.success(self.request, _('Your email address was already verified.'))
|
||||
return redirect(reverse('control:user.settings', kwargs={}))
|
||||
|
||||
self.request.user.send_confirmation_code(
|
||||
session=self.request.session,
|
||||
reason='email_verify',
|
||||
email=self.request.user.email,
|
||||
state=self.request.user.email,
|
||||
)
|
||||
self.request.session['email_confirmation_destination'] = self.request.user.email
|
||||
return redirect(reverse('control:user.settings.email.confirm', kwargs={}) + '?reason=email_verify')
|
||||
|
||||
|
||||
class UserEmailConfirmView(FormView):
|
||||
form_class = ConfirmationCodeForm
|
||||
template_name = 'pretixcontrol/user/confirmation_code_dialog.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
return {
|
||||
**super().get_context_data(**kwargs),
|
||||
"cancel_url": reverse('control:user.settings', kwargs={}),
|
||||
"message": format_html(
|
||||
_("Please enter the confirmation code we sent to your email address <strong>{email}</strong>."),
|
||||
email=self.request.session.get('email_confirmation_destination', ''),
|
||||
),
|
||||
}
|
||||
|
||||
@transaction.atomic()
|
||||
def form_valid(self, form):
|
||||
reason = self.request.GET['reason']
|
||||
if reason not in ('email_change', 'email_verify'):
|
||||
raise PermissionDenied
|
||||
try:
|
||||
new_email = self.request.user.check_confirmation_code(
|
||||
session=self.request.session,
|
||||
reason=reason,
|
||||
code=form.cleaned_data['code'],
|
||||
)
|
||||
except PermissionDenied:
|
||||
return self.form_invalid(form)
|
||||
except BadRequest:
|
||||
messages.error(self.request, _(
|
||||
'We were unable to verify your confirmation code. Please try again.'
|
||||
))
|
||||
return redirect(reverse('control:user.settings', kwargs={}))
|
||||
|
||||
log_data = {
|
||||
'email': new_email,
|
||||
'email_verified': True,
|
||||
}
|
||||
if reason == 'email_change':
|
||||
msgs = []
|
||||
msgs.append(_('Your email address has been changed to {email}.').format(email=new_email))
|
||||
log_data['old_email'] = old_email = self.request.user.email
|
||||
self.request.user.send_security_notice(msgs, email=old_email)
|
||||
self.request.user.send_security_notice(msgs, email=new_email)
|
||||
log_action = 'pretix.user.email.changed'
|
||||
else:
|
||||
log_action = 'pretix.user.email.confirmed'
|
||||
|
||||
self.request.user.email = new_email
|
||||
self.request.user.is_verified = True
|
||||
self.request.user.save()
|
||||
self.request.user.log_action(log_action, user=self.request.user, data=log_data)
|
||||
update_session_auth_hash(self.request, self.request.user)
|
||||
|
||||
if reason == 'email_change':
|
||||
messages.success(self.request, _('Your email address has been changed successfully.'))
|
||||
else:
|
||||
messages.success(self.request, _('Your email address has been confirmed successfully.'))
|
||||
return redirect(reverse('control:user.settings', kwargs={}))
|
||||
|
||||
def form_invalid(self, form):
|
||||
messages.error(self.request, _('The entered confirmation code is not correct. Please try again.'))
|
||||
return super().form_invalid(form)
|
||||
|
||||
@@ -8,10 +8,10 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-10-29 07:55+0000\n"
|
||||
"PO-Revision-Date: 2025-10-28 17:00+0000\n"
|
||||
"PO-Revision-Date: 2025-10-31 17:00+0000\n"
|
||||
"Last-Translator: Núria Masclans <nuriamasclansserrat@gmail.com>\n"
|
||||
"Language-Team: Catalan <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
"js/ca/>\n"
|
||||
"Language-Team: Catalan <https://translate.pretix.eu/projects/pretix/"
|
||||
"pretix-js/ca/>\n"
|
||||
"Language: ca\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -651,6 +651,8 @@ msgid ""
|
||||
"Your color has insufficient contrast to white. Accessibility of your site "
|
||||
"will be impacted."
|
||||
msgstr ""
|
||||
"El color no té prou contrast amb el blanc i pot afectar a l'accessibilitat "
|
||||
"del lloc web."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:418
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:438
|
||||
@@ -712,229 +714,226 @@ msgstr[0] "(una data més)"
|
||||
msgstr[1] "({num} dates més)"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:47
|
||||
#, fuzzy
|
||||
#| msgid "The items in your cart are no longer reserved for you."
|
||||
msgid ""
|
||||
"The items in your cart are no longer reserved for you. You can still "
|
||||
"complete your order as long as they’re available."
|
||||
msgstr "El contingut de la cistella ja no el teniu reservat."
|
||||
msgstr ""
|
||||
"Ja no tens reservat el contingut de la cistella. Encara pots completar la "
|
||||
"comanda si els articles seleccionats continuen disponibles."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:49
|
||||
msgid "Cart expired"
|
||||
msgstr "Cistella expirada"
|
||||
msgstr "Cistella caducada"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:58
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:84
|
||||
msgid "Your cart is about to expire."
|
||||
msgstr ""
|
||||
msgstr "La teva cistella està a punt de caducar."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:62
|
||||
#, fuzzy
|
||||
#| msgid "The items in your cart are no longer reserved for you."
|
||||
msgid "The items in your cart are reserved for you for one minute."
|
||||
msgid_plural "The items in your cart are reserved for you for {num} minutes."
|
||||
msgstr[0] "El contingut de la cistella ja no el teniu reservat."
|
||||
msgstr[1] "El contingut de la cistella ja no el teniu reservat."
|
||||
msgstr[0] "El contingut de la teva cistella està reservat durant un minut."
|
||||
msgstr[1] "El contingut de la teva cistella està reservat durant {num} minuts."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:83
|
||||
#, fuzzy
|
||||
#| msgid "Cart expired"
|
||||
msgid "Your cart has expired."
|
||||
msgstr "Cistella expirada"
|
||||
msgstr "La teva cistella ha caducat."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:86
|
||||
#, fuzzy
|
||||
#| msgid "The items in your cart are no longer reserved for you."
|
||||
msgid ""
|
||||
"The items in your cart are no longer reserved for you. You can still "
|
||||
"complete your order as long as they're available."
|
||||
msgstr "El contingut de la cistella ja no el teniu reservat."
|
||||
msgstr ""
|
||||
"Ja no teniu reservat el contingut de la cistella. Encara podeu completar la "
|
||||
"comanda si els articles continuen disponibles."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:87
|
||||
msgid "Do you want to renew the reservation period?"
|
||||
msgstr ""
|
||||
msgstr "Vols ampliar el temps de reserva?"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:90
|
||||
msgid "Renew reservation"
|
||||
msgstr ""
|
||||
msgstr "Renovar reserva"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:194
|
||||
msgid "The organizer keeps %(currency)s %(amount)s"
|
||||
msgstr ""
|
||||
msgstr "L'entitat organitzadora es queda %(currency)s %(amount)s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:202
|
||||
msgid "You get %(currency)s %(amount)s back"
|
||||
msgstr ""
|
||||
msgstr "Rebràs %(currency)s %(amount)s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:218
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
msgstr "Introdueix l’import que es queda l’organitzador."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:570
|
||||
msgid "Your local time:"
|
||||
msgstr ""
|
||||
msgstr "La teva hora local:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/walletdetection.js:39
|
||||
#, fuzzy
|
||||
msgid "Google Pay"
|
||||
msgstr ""
|
||||
msgstr "Google Pay"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:16
|
||||
msgctxt "widget"
|
||||
msgid "Quantity"
|
||||
msgstr ""
|
||||
msgstr "Quantitat"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:17
|
||||
msgctxt "widget"
|
||||
msgid "Decrease quantity"
|
||||
msgstr ""
|
||||
msgstr "Disminueix quantitat"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:18
|
||||
msgctxt "widget"
|
||||
msgid "Increase quantity"
|
||||
msgstr ""
|
||||
msgstr "Augmenta quantitat"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:19
|
||||
msgctxt "widget"
|
||||
msgid "Filter events by"
|
||||
msgstr ""
|
||||
msgstr "Filtra els esdeveniments per"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:20
|
||||
msgctxt "widget"
|
||||
msgid "Filter"
|
||||
msgstr ""
|
||||
msgstr "Filtra"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:21
|
||||
msgctxt "widget"
|
||||
msgid "Price"
|
||||
msgstr ""
|
||||
msgstr "Preu"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:22
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "Original price: %s"
|
||||
msgstr ""
|
||||
msgstr "Preu original: %s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:23
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "New price: %s"
|
||||
msgstr ""
|
||||
msgstr "Nou preu: %s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:24
|
||||
msgctxt "widget"
|
||||
msgid "Select"
|
||||
msgstr ""
|
||||
msgstr "Selecciona"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:25
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "Select %s"
|
||||
msgstr ""
|
||||
msgstr "Selecciona %s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:26
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "Select variant %s"
|
||||
msgstr ""
|
||||
msgstr "Selecciona la variant %s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:27
|
||||
msgctxt "widget"
|
||||
msgid "Sold out"
|
||||
msgstr ""
|
||||
msgstr "Esgotat"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:28
|
||||
msgctxt "widget"
|
||||
msgid "Buy"
|
||||
msgstr ""
|
||||
msgstr "Compra"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:29
|
||||
msgctxt "widget"
|
||||
msgid "Register"
|
||||
msgstr ""
|
||||
msgstr "Registrar-vos"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:30
|
||||
msgctxt "widget"
|
||||
msgid "Reserved"
|
||||
msgstr ""
|
||||
msgstr "Reservat"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:31
|
||||
msgctxt "widget"
|
||||
msgid "FREE"
|
||||
msgstr ""
|
||||
msgstr "GRATUÏT"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:32
|
||||
msgctxt "widget"
|
||||
msgid "from %(currency)s %(price)s"
|
||||
msgstr ""
|
||||
msgstr "des de %(price)s %(currency)s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:33
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "Image of %s"
|
||||
msgstr ""
|
||||
msgstr "Imatge de %s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:34
|
||||
msgctxt "widget"
|
||||
msgid "incl. %(rate)s% %(taxname)s"
|
||||
msgstr ""
|
||||
msgstr "Inclòs %(rate)s% %(taxname)s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:35
|
||||
msgctxt "widget"
|
||||
msgid "plus %(rate)s% %(taxname)s"
|
||||
msgstr ""
|
||||
msgstr "més %(rate)s% %(taxname)s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:36
|
||||
msgctxt "widget"
|
||||
msgid "incl. taxes"
|
||||
msgstr ""
|
||||
msgstr "incl. impostos"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:37
|
||||
msgctxt "widget"
|
||||
msgid "plus taxes"
|
||||
msgstr ""
|
||||
msgstr "més impostos"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "currently available: %s"
|
||||
msgstr ""
|
||||
msgstr "disponibles: %s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:39
|
||||
msgctxt "widget"
|
||||
msgid "Only available with a voucher"
|
||||
msgstr ""
|
||||
msgstr "Només disponible amb un cupó"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:40
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
||||
msgctxt "widget"
|
||||
msgid "Not yet available"
|
||||
msgstr ""
|
||||
msgstr "Encara no disponible"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:41
|
||||
msgctxt "widget"
|
||||
msgid "Not available anymore"
|
||||
msgstr ""
|
||||
msgstr "Ja no està disponible"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:42
|
||||
msgctxt "widget"
|
||||
msgid "Currently not available"
|
||||
msgstr ""
|
||||
msgstr "No disponible"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:44
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "minimum amount to order: %s"
|
||||
msgstr ""
|
||||
msgstr "Comanda mínima: %s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
||||
msgctxt "widget"
|
||||
msgid "Close ticket shop"
|
||||
msgstr ""
|
||||
msgstr "Tanca la botiga d'entrades"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
||||
msgctxt "widget"
|
||||
msgid "The ticket shop could not be loaded."
|
||||
msgstr ""
|
||||
msgstr "No s'ha pogut carregar la botiga d'entrades."
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:47
|
||||
msgctxt "widget"
|
||||
|
||||
@@ -5,7 +5,7 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-10-30 10:55+0000\n"
|
||||
"PO-Revision-Date: 2025-10-29 09:24+0000\n"
|
||||
"PO-Revision-Date: 2025-10-30 20:00+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix/de/"
|
||||
">\n"
|
||||
@@ -9664,15 +9664,15 @@ msgstr "Der Gutschein wurde an {recipient} verschickt."
|
||||
|
||||
#: pretix/base/settings.py:81
|
||||
msgid "Compute taxes for every line individually"
|
||||
msgstr ""
|
||||
msgstr "Berechne Steuern für jede Position einzeln"
|
||||
|
||||
#: pretix/base/settings.py:82
|
||||
msgid "Compute taxes based on net total"
|
||||
msgstr ""
|
||||
msgstr "Berechne Steuern auf Basis der Nettosumme"
|
||||
|
||||
#: pretix/base/settings.py:83
|
||||
msgid "Compute taxes based on net total with stable gross prices"
|
||||
msgstr ""
|
||||
msgstr "Berechne Steuern auf Basis der Nettosumme mit konstanten Bruttopreisen"
|
||||
|
||||
#: pretix/base/settings.py:133
|
||||
msgid "Allow usage of restricted plugins"
|
||||
@@ -9762,13 +9762,8 @@ msgid "Add-on products will not be counted."
|
||||
msgstr "Zusatzprodukte werden nicht mitgezählt."
|
||||
|
||||
#: pretix/base/settings.py:334
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Show net prices instead of gross prices in the product list (not "
|
||||
#| "recommended!)"
|
||||
msgid "Show net prices instead of gross prices in the product list"
|
||||
msgstr ""
|
||||
"Zeige Netto- statt Bruttopreisen in den Produktlisten (nicht empfohlen)"
|
||||
msgstr "Zeigen Nettopreise statt Bruttopreisen in der Produktliste"
|
||||
|
||||
#: pretix/base/settings.py:335
|
||||
msgid ""
|
||||
@@ -9885,10 +9880,8 @@ msgid "Require a phone number per order"
|
||||
msgstr "Telefonnummer pro Bestellung erfordern"
|
||||
|
||||
#: pretix/base/settings.py:481
|
||||
#, fuzzy
|
||||
#| msgid "including all taxes"
|
||||
msgid "Rounding of taxes"
|
||||
msgstr "inklusive aller Steuern"
|
||||
msgstr "Rundung der Steuern"
|
||||
|
||||
#: pretix/base/settings.py:485
|
||||
msgid ""
|
||||
@@ -9896,6 +9889,10 @@ msgid ""
|
||||
"for tax reporting, you need to make sure to account for possible rounding "
|
||||
"differences if your external system rounds differently than pretix."
|
||||
msgstr ""
|
||||
"Bitte beachten Sie, dass bei der Übertragung der Verkaufsdaten in ein "
|
||||
"externes Buchhaltungssystem darauf geachtet werden muss, dass potentielle "
|
||||
"Rundungsabweichungen behandelt werden, wenn das externe System anders rundet "
|
||||
"als pretix."
|
||||
|
||||
#: pretix/base/settings.py:500
|
||||
msgid "Ask for invoice address"
|
||||
@@ -14037,31 +14034,24 @@ msgstr ""
|
||||
"Ausstellung ablaufen."
|
||||
|
||||
#: pretix/control/forms/event.py:806
|
||||
#, fuzzy
|
||||
#| msgid "including all taxes"
|
||||
msgid "Prices including tax"
|
||||
msgstr "inklusive aller Steuern"
|
||||
msgstr "Preis inklusive Steuern"
|
||||
|
||||
#: pretix/control/forms/event.py:807
|
||||
msgid "Recommended if you sell tickets at least partly to consumers."
|
||||
msgstr ""
|
||||
msgstr "Empfohlen, wenn mindestens teilweise an Verbraucher verkauft wird."
|
||||
|
||||
#: pretix/control/forms/event.py:811
|
||||
#, fuzzy
|
||||
#| msgctxt "reporting_timeframe"
|
||||
#| msgid "All future (excluding today)"
|
||||
msgid "Prices excluding tax"
|
||||
msgstr "Zukunft (ab morgen)"
|
||||
msgstr "Preise exklusive Steuern"
|
||||
|
||||
#: pretix/control/forms/event.py:812
|
||||
msgid "Recommended only if you sell tickets primarily to business customers."
|
||||
msgstr ""
|
||||
msgstr "Empfohlen, wenn primär an Geschäftskunden verkauft wird."
|
||||
|
||||
#: pretix/control/forms/event.py:848
|
||||
#, fuzzy
|
||||
#| msgid "Date chosen by customer"
|
||||
msgid "Prices shown to customer"
|
||||
msgstr "Wunschdatum des Kunden"
|
||||
msgstr "Preisanzeige für Kunden"
|
||||
|
||||
#: pretix/control/forms/event.py:852
|
||||
msgid ""
|
||||
@@ -14070,6 +14060,10 @@ msgid ""
|
||||
"product, the total tax amount can differ from when it would be computed from "
|
||||
"the order total."
|
||||
msgstr ""
|
||||
"Empfohlen, wenn keine E-Rechnungen benötigt werden. Jedes Produkt wird zum "
|
||||
"beworbenen Netto- und Bruttopreis verkauft. Im Fall von Bestellungen mit "
|
||||
"mehreren Produkten kann die Gesamt-Steuersumme höher oder niedriger sein als "
|
||||
"die, die sich aus der Bestellsumme berechnen würde."
|
||||
|
||||
#: pretix/control/forms/event.py:857
|
||||
msgid ""
|
||||
@@ -14078,6 +14072,11 @@ msgid ""
|
||||
"may be changed to ensure correct rounding, while the net prices will be kept "
|
||||
"as configured. This may cause the actual payment amount to differ."
|
||||
msgstr ""
|
||||
"Empfohlen für E-Rechnungen, wenn primär an Geschäftskunden verkauft wird und "
|
||||
"Kunden Nettopreise angezeigt werden. Der Bruttopreis einzelner Produkte kann "
|
||||
"sich automatisch ändern, um ein korrektes Rundungsverhalten sicherzustellen, "
|
||||
"während die Nettopreise immer den eingestellten Preisen entsprechen. Der "
|
||||
"resultierende Zahlungsbetrag kann durch die Rundung abweichen."
|
||||
|
||||
#: pretix/control/forms/event.py:863
|
||||
msgid ""
|
||||
@@ -14087,6 +14086,13 @@ msgid ""
|
||||
"configured whenever possible. Gross prices may still change if they are "
|
||||
"impossible to derive from a rounded net price."
|
||||
msgstr ""
|
||||
"Empfohlen für E-Rechnungen, wenn primär an Verbraucher verkauft wird und "
|
||||
"Kunden Bruttopreise angezeigt werden. Der Netto- oder Bruttopreis einzelner "
|
||||
"Produkte kann sich automatisch ändern, um ein korrektes Rundungsverhalten "
|
||||
"sicherzustellen. Dabei versucht das System, die Bruttopreise möglichst bei "
|
||||
"den eingestellten Preisen zu belassen. Die Bruttopreise können sich dennoch "
|
||||
"ändern, wenn sie durch Errechnung von einem gerundeten Nettopreis nicht "
|
||||
"erreichbar sind."
|
||||
|
||||
#: pretix/control/forms/event.py:961
|
||||
msgid "Generate invoices for Sales channels"
|
||||
@@ -21278,6 +21284,10 @@ msgid ""
|
||||
"optionally contain additional rules that depend on the customer's country "
|
||||
"and type."
|
||||
msgstr ""
|
||||
"Steuer-Regeln definieren unterschiedliche Besteuerungsszenarien, die dann "
|
||||
"den verschiedenen Produkten zugewiesen werden können. Jede Steuer-Regel "
|
||||
"enthält einen Standard-Steuersatz und kann optional zusätzliche Regeln "
|
||||
"enthalten, die auf die Art und Herkunft des Kunden Bezug nehmen."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html:21
|
||||
msgid "You haven't created any tax rules yet."
|
||||
@@ -21304,13 +21314,11 @@ msgid "Make default"
|
||||
msgstr "Zum Standard machen"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html:67
|
||||
#, fuzzy, python-format
|
||||
#| msgid "%(count)s event"
|
||||
#| msgid_plural "%(count)s events"
|
||||
#, python-format
|
||||
msgid "%(count)s product"
|
||||
msgid_plural "%(count)s products"
|
||||
msgstr[0] "%(count)s Veranstaltung"
|
||||
msgstr[1] "%(count)s Veranstaltungen"
|
||||
msgstr[0] "%(count)s Produkt"
|
||||
msgstr[1] "%(count)s Produkte"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html:75
|
||||
#, python-format
|
||||
@@ -21323,16 +21331,12 @@ msgid "excl. %(rate)s %%"
|
||||
msgstr "zzgl. %(rate)s %%"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html:80
|
||||
#, fuzzy
|
||||
#| msgid "Custom rules"
|
||||
msgid "with custom rules"
|
||||
msgstr "Eigene Regeln"
|
||||
msgstr "mit eigenen Regeln"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html:110
|
||||
#, fuzzy
|
||||
#| msgid "Base settings"
|
||||
msgid "Tax settings"
|
||||
msgstr "Grundeinstellungen"
|
||||
msgstr "Steuer-Einstellungen"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax_delete.html:4
|
||||
#: pretix/control/templates/pretixcontrol/event/tax_delete.html:6
|
||||
@@ -24149,7 +24153,7 @@ msgstr ""
|
||||
#: pretix/control/templates/pretixcontrol/order/transactions.html:73
|
||||
#, python-format
|
||||
msgid "incl. %(amount)s rounding correction"
|
||||
msgstr ""
|
||||
msgstr "inkl. %(amount)s Rundungskorrektur"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/bulk_action.html:5
|
||||
#: pretix/control/templates/pretixcontrol/orders/bulk_action.html:7
|
||||
|
||||
@@ -362,6 +362,9 @@ Reverse-Proxy
|
||||
Revisionssicherheit
|
||||
Revolut
|
||||
Revolut-App
|
||||
Rundungsabweichungen
|
||||
Rundungskorrektur
|
||||
Rundungsverhalten
|
||||
rückabgewickelt
|
||||
Sa
|
||||
Sammlungsstücken
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-10-30 10:55+0000\n"
|
||||
"PO-Revision-Date: 2025-10-29 09:24+0000\n"
|
||||
"PO-Revision-Date: 2025-10-30 20:00+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
|
||||
"pretix/pretix/de_Informal/>\n"
|
||||
@@ -9651,15 +9651,15 @@ msgstr "Der Gutschein wurde an {recipient} verschickt."
|
||||
|
||||
#: pretix/base/settings.py:81
|
||||
msgid "Compute taxes for every line individually"
|
||||
msgstr ""
|
||||
msgstr "Berechne Steuern für jede Position einzeln"
|
||||
|
||||
#: pretix/base/settings.py:82
|
||||
msgid "Compute taxes based on net total"
|
||||
msgstr ""
|
||||
msgstr "Berechne Steuern auf Basis der Nettosumme"
|
||||
|
||||
#: pretix/base/settings.py:83
|
||||
msgid "Compute taxes based on net total with stable gross prices"
|
||||
msgstr ""
|
||||
msgstr "Berechne Steuern auf Basis der Nettosumme mit konstanten Bruttopreisen"
|
||||
|
||||
#: pretix/base/settings.py:133
|
||||
msgid "Allow usage of restricted plugins"
|
||||
@@ -9749,13 +9749,8 @@ msgid "Add-on products will not be counted."
|
||||
msgstr "Zusatzprodukte werden nicht mitgezählt."
|
||||
|
||||
#: pretix/base/settings.py:334
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Show net prices instead of gross prices in the product list (not "
|
||||
#| "recommended!)"
|
||||
msgid "Show net prices instead of gross prices in the product list"
|
||||
msgstr ""
|
||||
"Zeige Netto- statt Bruttopreisen in den Produktlisten (nicht empfohlen)"
|
||||
msgstr "Zeigen Nettopreise statt Bruttopreisen in der Produktliste"
|
||||
|
||||
#: pretix/base/settings.py:335
|
||||
msgid ""
|
||||
@@ -9872,10 +9867,8 @@ msgid "Require a phone number per order"
|
||||
msgstr "Telefonnummer pro Bestellung erfordern"
|
||||
|
||||
#: pretix/base/settings.py:481
|
||||
#, fuzzy
|
||||
#| msgid "including all taxes"
|
||||
msgid "Rounding of taxes"
|
||||
msgstr "inklusive aller Steuern"
|
||||
msgstr "Rundung der Steuern"
|
||||
|
||||
#: pretix/base/settings.py:485
|
||||
msgid ""
|
||||
@@ -9883,6 +9876,10 @@ msgid ""
|
||||
"for tax reporting, you need to make sure to account for possible rounding "
|
||||
"differences if your external system rounds differently than pretix."
|
||||
msgstr ""
|
||||
"Bitte beachte, dass bei der Übertragung der Verkaufsdaten in ein externes "
|
||||
"Buchhaltungssystem darauf geachtet werden muss, dass potentielle "
|
||||
"Rundungsabweichungen behandelt werden, wenn das externe System anders rundet "
|
||||
"als pretix."
|
||||
|
||||
#: pretix/base/settings.py:500
|
||||
msgid "Ask for invoice address"
|
||||
@@ -14013,31 +14010,24 @@ msgstr ""
|
||||
"Ausstellung ablaufen."
|
||||
|
||||
#: pretix/control/forms/event.py:806
|
||||
#, fuzzy
|
||||
#| msgid "including all taxes"
|
||||
msgid "Prices including tax"
|
||||
msgstr "inklusive aller Steuern"
|
||||
msgstr "Preis inklusive Steuern"
|
||||
|
||||
#: pretix/control/forms/event.py:807
|
||||
msgid "Recommended if you sell tickets at least partly to consumers."
|
||||
msgstr ""
|
||||
msgstr "Empfohlen, wenn mindestens teilweise an Verbraucher verkauft wird."
|
||||
|
||||
#: pretix/control/forms/event.py:811
|
||||
#, fuzzy
|
||||
#| msgctxt "reporting_timeframe"
|
||||
#| msgid "All future (excluding today)"
|
||||
msgid "Prices excluding tax"
|
||||
msgstr "Zukunft (ab morgen)"
|
||||
msgstr "Preise exklusive Steuern"
|
||||
|
||||
#: pretix/control/forms/event.py:812
|
||||
msgid "Recommended only if you sell tickets primarily to business customers."
|
||||
msgstr ""
|
||||
msgstr "Empfohlen, wenn primär an Geschäftskunden verkauft wird."
|
||||
|
||||
#: pretix/control/forms/event.py:848
|
||||
#, fuzzy
|
||||
#| msgid "Date chosen by customer"
|
||||
msgid "Prices shown to customer"
|
||||
msgstr "Wunschdatum des Kunden"
|
||||
msgstr "Preisanzeige für Kunden"
|
||||
|
||||
#: pretix/control/forms/event.py:852
|
||||
msgid ""
|
||||
@@ -14046,6 +14036,10 @@ msgid ""
|
||||
"product, the total tax amount can differ from when it would be computed from "
|
||||
"the order total."
|
||||
msgstr ""
|
||||
"Empfohlen, wenn keine E-Rechnungen benötigt werden. Jedes Produkt wird zum "
|
||||
"beworbenen Netto- und Bruttopreis verkauft. Im Fall von Bestellungen mit "
|
||||
"mehreren Produkten kann die Gesamt-Steuersumme höher oder niedriger sein als "
|
||||
"die, die sich aus der Bestellsumme berechnen würde."
|
||||
|
||||
#: pretix/control/forms/event.py:857
|
||||
msgid ""
|
||||
@@ -14054,6 +14048,11 @@ msgid ""
|
||||
"may be changed to ensure correct rounding, while the net prices will be kept "
|
||||
"as configured. This may cause the actual payment amount to differ."
|
||||
msgstr ""
|
||||
"Empfohlen für E-Rechnungen, wenn primär an Geschäftskunden verkauft wird und "
|
||||
"Kunden Nettopreise angezeigt werden. Der Bruttopreis einzelner Produkte kann "
|
||||
"sich automatisch ändern, um ein korrektes Rundungsverhalten sicherzustellen, "
|
||||
"während die Nettopreise immer den eingestellten Preisen entsprechen. Der "
|
||||
"resultierende Zahlungsbetrag kann durch die Rundung abweichen."
|
||||
|
||||
#: pretix/control/forms/event.py:863
|
||||
msgid ""
|
||||
@@ -14063,6 +14062,13 @@ msgid ""
|
||||
"configured whenever possible. Gross prices may still change if they are "
|
||||
"impossible to derive from a rounded net price."
|
||||
msgstr ""
|
||||
"Empfohlen für E-Rechnungen, wenn primär an Verbraucher verkauft wird und "
|
||||
"Kunden Bruttopreise angezeigt werden. Der Netto- oder Bruttopreis einzelner "
|
||||
"Produkte kann sich automatisch ändern, um ein korrektes Rundungsverhalten "
|
||||
"sicherzustellen. Dabei versucht das System, die Bruttopreise möglichst bei "
|
||||
"den eingestellten Preisen zu belassen. Die Bruttopreise können sich dennoch "
|
||||
"ändern, wenn sie durch Errechnung von einem gerundeten Nettopreis nicht "
|
||||
"erreichbar sind."
|
||||
|
||||
#: pretix/control/forms/event.py:961
|
||||
msgid "Generate invoices for Sales channels"
|
||||
@@ -21248,6 +21254,10 @@ msgid ""
|
||||
"optionally contain additional rules that depend on the customer's country "
|
||||
"and type."
|
||||
msgstr ""
|
||||
"Steuer-Regeln definieren unterschiedliche Besteuerungsszenarien, die dann "
|
||||
"den verschiedenen Produkten zugewiesen werden können. Jede Steuer-Regel "
|
||||
"enthält einen Standard-Steuersatz und kann optional zusätzliche Regeln "
|
||||
"enthalten, die auf die Art und Herkunft des Kunden Bezug nehmen."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html:21
|
||||
msgid "You haven't created any tax rules yet."
|
||||
@@ -21274,13 +21284,11 @@ msgid "Make default"
|
||||
msgstr "Zum Standard machen"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html:67
|
||||
#, fuzzy, python-format
|
||||
#| msgid "%(count)s event"
|
||||
#| msgid_plural "%(count)s events"
|
||||
#, python-format
|
||||
msgid "%(count)s product"
|
||||
msgid_plural "%(count)s products"
|
||||
msgstr[0] "%(count)s Veranstaltung"
|
||||
msgstr[1] "%(count)s Veranstaltungen"
|
||||
msgstr[0] "%(count)s Produkt"
|
||||
msgstr[1] "%(count)s Produkte"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html:75
|
||||
#, python-format
|
||||
@@ -21293,16 +21301,12 @@ msgid "excl. %(rate)s %%"
|
||||
msgstr "zzgl. %(rate)s %%"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html:80
|
||||
#, fuzzy
|
||||
#| msgid "Custom rules"
|
||||
msgid "with custom rules"
|
||||
msgstr "Eigene Regeln"
|
||||
msgstr "mit eigenen Regeln"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html:110
|
||||
#, fuzzy
|
||||
#| msgid "Base settings"
|
||||
msgid "Tax settings"
|
||||
msgstr "Grundeinstellungen"
|
||||
msgstr "Steuer-Einstellungen"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax_delete.html:4
|
||||
#: pretix/control/templates/pretixcontrol/event/tax_delete.html:6
|
||||
@@ -24116,7 +24120,7 @@ msgstr ""
|
||||
#: pretix/control/templates/pretixcontrol/order/transactions.html:73
|
||||
#, python-format
|
||||
msgid "incl. %(amount)s rounding correction"
|
||||
msgstr ""
|
||||
msgstr "inkl. %(amount)s Rundungskorrektur"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/bulk_action.html:5
|
||||
#: pretix/control/templates/pretixcontrol/orders/bulk_action.html:7
|
||||
|
||||
@@ -362,6 +362,9 @@ Reverse-Proxy
|
||||
Revisionssicherheit
|
||||
Revolut
|
||||
Revolut-App
|
||||
Rundungsabweichungen
|
||||
Rundungskorrektur
|
||||
Rundungsverhalten
|
||||
rückabgewickelt
|
||||
Sa
|
||||
Sammlungsstücken
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-10-30 10:55+0000\n"
|
||||
"PO-Revision-Date: 2025-10-22 16:00+0000\n"
|
||||
"PO-Revision-Date: 2025-11-04 10:25+0000\n"
|
||||
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
|
||||
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"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.13.3\n"
|
||||
"X-Generator: Weblate 5.14\n"
|
||||
|
||||
#: pretix/_base_settings.py:87
|
||||
msgid "English"
|
||||
@@ -8346,10 +8346,8 @@ msgid "Event canceled"
|
||||
msgstr "Evento cancelado"
|
||||
|
||||
#: pretix/base/services/cancelevent.py:392
|
||||
#, fuzzy
|
||||
#| msgid "Create configuration"
|
||||
msgid "Bulk-refund confirmation"
|
||||
msgstr "Crear configuración"
|
||||
msgstr "Confirmación de reembolso masivo"
|
||||
|
||||
#: pretix/base/services/cart.py:102 pretix/base/services/modelimport.py:247
|
||||
#: pretix/base/services/orders.py:155
|
||||
@@ -9655,15 +9653,16 @@ msgstr "El vale de compra ha sido enviado a {recipient}."
|
||||
|
||||
#: pretix/base/settings.py:81
|
||||
msgid "Compute taxes for every line individually"
|
||||
msgstr ""
|
||||
msgstr "Calcular los impuestos para cada línea individualmente"
|
||||
|
||||
#: pretix/base/settings.py:82
|
||||
msgid "Compute taxes based on net total"
|
||||
msgstr ""
|
||||
msgstr "Calcular los impuestos basándose en el total neto"
|
||||
|
||||
#: pretix/base/settings.py:83
|
||||
msgid "Compute taxes based on net total with stable gross prices"
|
||||
msgstr ""
|
||||
"Calcular los impuestos basándose en el total neto con precios brutos estables"
|
||||
|
||||
#: pretix/base/settings.py:133
|
||||
msgid "Allow usage of restricted plugins"
|
||||
@@ -9755,14 +9754,9 @@ msgid "Add-on products will not be counted."
|
||||
msgstr "Los productos adicionales no se contarán."
|
||||
|
||||
#: pretix/base/settings.py:334
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Show net prices instead of gross prices in the product list (not "
|
||||
#| "recommended!)"
|
||||
msgid "Show net prices instead of gross prices in the product list"
|
||||
msgstr ""
|
||||
"Mostrar precios netos en lugar de precios brutos en la lista de productos "
|
||||
"(¡no recomendado!)"
|
||||
"Mostrar precios netos en lugar de precios brutos en la lista de productos"
|
||||
|
||||
#: pretix/base/settings.py:335
|
||||
msgid ""
|
||||
@@ -9883,10 +9877,8 @@ msgid "Require a phone number per order"
|
||||
msgstr "Requerir un número de teléfono por pedido"
|
||||
|
||||
#: pretix/base/settings.py:481
|
||||
#, fuzzy
|
||||
#| msgid "including all taxes"
|
||||
msgid "Rounding of taxes"
|
||||
msgstr "impuestos incluidos"
|
||||
msgstr "Redondeo de impuestos"
|
||||
|
||||
#: pretix/base/settings.py:485
|
||||
msgid ""
|
||||
@@ -9894,6 +9886,10 @@ msgid ""
|
||||
"for tax reporting, you need to make sure to account for possible rounding "
|
||||
"differences if your external system rounds differently than pretix."
|
||||
msgstr ""
|
||||
"Tenga en cuenta que si transfiere sus datos de ventas desde pretix a un "
|
||||
"sistema externo para la declaración de impuestos, debe asegurarse de tener "
|
||||
"en cuenta las posibles diferencias de redondeo si su sistema externo "
|
||||
"redondea de forma diferente a pretix."
|
||||
|
||||
#: pretix/base/settings.py:500
|
||||
msgid "Ask for invoice address"
|
||||
@@ -13164,18 +13160,20 @@ msgstr ""
|
||||
msgid ""
|
||||
"You have requested us to cancel an event which includes a larger bulk-refund:"
|
||||
msgstr ""
|
||||
"Nos ha solicitado cancelar un evento que incluye un reembolso masivo de "
|
||||
"mayor importe:"
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/cancel_confirm.txt:6
|
||||
#, fuzzy
|
||||
#| msgid "Initiate a refund of %(amount)s"
|
||||
msgid "Estimated refund amount"
|
||||
msgstr "Iniciar un reembolso de %(amount)s"
|
||||
msgstr "Importe estimado del reembolso"
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/cancel_confirm.txt:8
|
||||
msgid ""
|
||||
"Please confirm that you want to proceed by coping the following confirmation "
|
||||
"code into the cancellation form:"
|
||||
msgstr ""
|
||||
"Confirme que desea continuar copiando el siguiente código de confirmación en "
|
||||
"el formulario de cancelación:"
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/email_footer.html:3
|
||||
#, python-format
|
||||
@@ -14017,31 +14015,25 @@ msgstr ""
|
||||
"año en el que se emite la tarjeta de regalo."
|
||||
|
||||
#: pretix/control/forms/event.py:806
|
||||
#, fuzzy
|
||||
#| msgid "including all taxes"
|
||||
msgid "Prices including tax"
|
||||
msgstr "impuestos incluidos"
|
||||
msgstr "Precios con impuestos incluidos"
|
||||
|
||||
#: pretix/control/forms/event.py:807
|
||||
msgid "Recommended if you sell tickets at least partly to consumers."
|
||||
msgstr ""
|
||||
msgstr "Recomendado si se venden entradas, al menos en parte, a consumidores."
|
||||
|
||||
#: pretix/control/forms/event.py:811
|
||||
#, fuzzy
|
||||
#| msgctxt "reporting_timeframe"
|
||||
#| msgid "All future (excluding today)"
|
||||
msgid "Prices excluding tax"
|
||||
msgstr "Todo el futuro (excepto hoy)"
|
||||
msgstr "Precios sin impuestos"
|
||||
|
||||
#: pretix/control/forms/event.py:812
|
||||
msgid "Recommended only if you sell tickets primarily to business customers."
|
||||
msgstr ""
|
||||
"Recomendado solo si vende entradas principalmente a clientes empresariales."
|
||||
|
||||
#: pretix/control/forms/event.py:848
|
||||
#, fuzzy
|
||||
#| msgid "Date chosen by customer"
|
||||
msgid "Prices shown to customer"
|
||||
msgstr "Fecha elegida por el cliente"
|
||||
msgstr "Precios mostrados a los clientes"
|
||||
|
||||
#: pretix/control/forms/event.py:852
|
||||
msgid ""
|
||||
@@ -14050,6 +14042,10 @@ msgid ""
|
||||
"product, the total tax amount can differ from when it would be computed from "
|
||||
"the order total."
|
||||
msgstr ""
|
||||
"Recomendado cuando no se requiere facturación electrónica. Cada producto se "
|
||||
"venderá con el precio neto y bruto anunciado. Sin embargo, en pedidos de más "
|
||||
"de un producto, el importe total de los impuestos puede diferir del que se "
|
||||
"calcularía a partir del total del pedido."
|
||||
|
||||
#: pretix/control/forms/event.py:857
|
||||
msgid ""
|
||||
@@ -14058,6 +14054,12 @@ msgid ""
|
||||
"may be changed to ensure correct rounding, while the net prices will be kept "
|
||||
"as configured. This may cause the actual payment amount to differ."
|
||||
msgstr ""
|
||||
"Recomendado para la facturación electrónica cuando se vende principalmente a "
|
||||
"clientes empresariales y se muestran los precios a los clientes sin "
|
||||
"impuestos. El precio bruto de algunos productos puede modificarse para "
|
||||
"garantizar un redondeo correcto, mientras que los precios netos se "
|
||||
"mantendrán tal y como están configurados. Esto puede provocar que el importe "
|
||||
"real del pago sea diferente."
|
||||
|
||||
#: pretix/control/forms/event.py:863
|
||||
msgid ""
|
||||
@@ -14067,6 +14069,12 @@ msgid ""
|
||||
"configured whenever possible. Gross prices may still change if they are "
|
||||
"impossible to derive from a rounded net price."
|
||||
msgstr ""
|
||||
"Recomendado para la facturación electrónica cuando se vende principalmente a "
|
||||
"consumidores. El precio bruto o neto de algunos productos puede modificarse "
|
||||
"automáticamente para garantizar el redondeo correcto del total del pedido. "
|
||||
"El sistema intenta mantener los precios brutos tal y como se han configurado "
|
||||
"siempre que sea posible. Los precios brutos pueden seguir cambiando si no es "
|
||||
"posible obtenerlos a partir de un precio neto redondeado."
|
||||
|
||||
#: pretix/control/forms/event.py:961
|
||||
msgid "Generate invoices for Sales channels"
|
||||
@@ -16185,7 +16193,7 @@ msgstr "Confirme que desea cancelar TODAS las fechas de esta serie de eventos."
|
||||
|
||||
#: pretix/control/forms/orders.py:1037
|
||||
msgid "I understand that this is not reversible and want to continue"
|
||||
msgstr ""
|
||||
msgstr "Entiendo que esto no es reversible y deseo continuar"
|
||||
|
||||
#: pretix/control/forms/orders.py:1041
|
||||
#: pretix/control/templates/pretixcontrol/shredder/download.html:53
|
||||
@@ -16196,12 +16204,12 @@ msgstr "Código de confirmación"
|
||||
msgid ""
|
||||
"We have just emailed you a confirmation code to enter to confirm this action"
|
||||
msgstr ""
|
||||
"Acabamos de enviarle por correo electrónico un código de confirmación que "
|
||||
"deberá introducir para confirmar esta acción"
|
||||
|
||||
#: pretix/control/forms/orders.py:1055
|
||||
#, fuzzy
|
||||
#| msgid "The confirm code you entered was incorrect."
|
||||
msgid "The confirmation code is incorrect."
|
||||
msgstr "El código de confirmación que introdujo era incorrecto."
|
||||
msgstr "El código de confirmación es incorrecto."
|
||||
|
||||
#: pretix/control/forms/organizer.py:93
|
||||
msgid "This slug is already in use. Please choose a different one."
|
||||
@@ -21268,6 +21276,10 @@ msgid ""
|
||||
"optionally contain additional rules that depend on the customer's country "
|
||||
"and type."
|
||||
msgstr ""
|
||||
"Las normas fiscales definen diferentes escenarios fiscales que luego se "
|
||||
"pueden asignar a los productos individuales. Cada norma fiscal contiene un "
|
||||
"tipo impositivo predeterminado y, opcionalmente, puede contener normas "
|
||||
"adicionales que dependen del país y el tipo de cliente."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html:21
|
||||
msgid "You haven't created any tax rules yet."
|
||||
@@ -21294,13 +21306,11 @@ msgid "Make default"
|
||||
msgstr "Establecer por defecto"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html:67
|
||||
#, fuzzy, python-format
|
||||
#| msgid "%(count)s event"
|
||||
#| msgid_plural "%(count)s events"
|
||||
#, python-format
|
||||
msgid "%(count)s product"
|
||||
msgid_plural "%(count)s products"
|
||||
msgstr[0] "%(count)s elemento"
|
||||
msgstr[1] "%(count)s elementos"
|
||||
msgstr[0] "%(count)s producto"
|
||||
msgstr[1] "%(count)s productos"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html:75
|
||||
#, python-format
|
||||
@@ -21313,16 +21323,12 @@ msgid "excl. %(rate)s %%"
|
||||
msgstr "excluido %(rate)s %%"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html:80
|
||||
#, fuzzy
|
||||
#| msgid "Custom rules"
|
||||
msgid "with custom rules"
|
||||
msgstr "Reglas personalizadas"
|
||||
msgstr "con reglas personalizadas"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html:110
|
||||
#, fuzzy
|
||||
#| msgid "Base settings"
|
||||
msgid "Tax settings"
|
||||
msgstr "Ajustes básicos"
|
||||
msgstr "Configuración de impuestos"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax_delete.html:4
|
||||
#: pretix/control/templates/pretixcontrol/event/tax_delete.html:6
|
||||
@@ -24141,7 +24147,7 @@ msgstr ""
|
||||
#: pretix/control/templates/pretixcontrol/order/transactions.html:73
|
||||
#, python-format
|
||||
msgid "incl. %(amount)s rounding correction"
|
||||
msgstr ""
|
||||
msgstr "incl. %(amount)s corrección por redondeo"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/bulk_action.html:5
|
||||
#: pretix/control/templates/pretixcontrol/orders/bulk_action.html:7
|
||||
@@ -24247,84 +24253,77 @@ msgstr ""
|
||||
"los que estén en la lista de espera recibirán varios correos electrónicos."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel.html:84
|
||||
#, fuzzy
|
||||
#| msgid "Refund amount"
|
||||
msgid "Preview refund amount"
|
||||
msgstr "Importe del reembolso"
|
||||
msgstr "Previsualizar el importe del reembolso"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel.html:88
|
||||
msgid "Cancel all orders"
|
||||
msgstr "Cancelar todos los pedidos"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:13
|
||||
#, fuzzy
|
||||
#| msgid "If you contact us, please send us the following code:"
|
||||
msgid "If you proceed, the system will do the following:"
|
||||
msgstr ""
|
||||
"Si se pone en contacto con nosotros, por favor envíenos el siguiente código:"
|
||||
msgstr "Si continúa, el sistema hará lo siguiente:"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:19
|
||||
#, python-format
|
||||
msgid "%(count)s order will be canceled fully"
|
||||
msgid_plural "%(count)s orders will be canceled fully"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "%(count)s pedido se cancelará por completo"
|
||||
msgstr[1] "%(count)s pedidos se cancelarán por completo"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:26
|
||||
#, python-format
|
||||
msgid "%(count)s order will be canceled partially"
|
||||
msgid_plural "%(count)s orders will be canceled partially"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "%(count)s pedido se cancelará parcialmente"
|
||||
msgstr[1] "%(count)s pedidos se cancelarán parcialmente"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:36
|
||||
#, python-format
|
||||
msgid "%(amount)s are eligible for refunds."
|
||||
msgstr ""
|
||||
msgstr "%(amount)s son reembolsables."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:40
|
||||
msgid ""
|
||||
"The system will attempt to refund the money automatically if supported by "
|
||||
"the payment method."
|
||||
msgstr ""
|
||||
"El sistema intentará reembolsar el dinero automáticamente si el método de "
|
||||
"pago lo permite."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:42
|
||||
msgid "The system will create manual refunds that you need to execute."
|
||||
msgstr ""
|
||||
msgstr "El sistema creará reembolsos manuales que deberá ejecutar."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:44
|
||||
#, fuzzy
|
||||
#| msgid "Text (if order will not expire automatically)"
|
||||
msgid "Refunds will not happen automatically."
|
||||
msgstr "Texto (si el pedido no caducará automáticamente)"
|
||||
msgstr "Los reembolsos no se realizarán automáticamente."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:52
|
||||
#, fuzzy
|
||||
#| msgid "Send information via email"
|
||||
msgid "Inform all customers via email."
|
||||
msgstr "Enviar información por correo electrónico"
|
||||
msgstr "Informar a todos los clientes por correo electrónico."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:57
|
||||
msgid "Inform all waiting list contacts via email."
|
||||
msgstr ""
|
||||
"Informar a todos los contactos de la lista de espera por correo electrónico."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:62
|
||||
msgid ""
|
||||
"These numbers are estimates and may change if the data in your event "
|
||||
"recently changed."
|
||||
msgstr ""
|
||||
"Estas cifras son estimaciones y pueden variar si los datos del evento han "
|
||||
"cambiado recientemente."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:76
|
||||
#, fuzzy, python-format
|
||||
#| msgid "Initiate a refund of %(amount)s"
|
||||
#, python-format
|
||||
msgid "Proceed and refund approx. %(amount)s"
|
||||
msgstr "Iniciar un reembolso de %(amount)s"
|
||||
msgstr "Proseguir y reembolsar aprox. %(amount)s"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:80
|
||||
#, fuzzy
|
||||
#| msgid "Yes, cancel order"
|
||||
msgid "Proceed and cancel orders"
|
||||
msgstr "Sí, cancelar pedido"
|
||||
msgstr "Proceder y cancelar pedidos"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/export.html:5
|
||||
#: pretix/control/templates/pretixcontrol/orders/export.html:8
|
||||
|
||||
@@ -4,7 +4,7 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-10-30 10:55+0000\n"
|
||||
"PO-Revision-Date: 2025-10-22 16:00+0000\n"
|
||||
"PO-Revision-Date: 2025-11-04 10:25+0000\n"
|
||||
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
|
||||
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix/fr/"
|
||||
">\n"
|
||||
@@ -13,7 +13,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.13.3\n"
|
||||
"X-Generator: Weblate 5.14\n"
|
||||
|
||||
#: pretix/_base_settings.py:87
|
||||
msgid "English"
|
||||
@@ -8397,10 +8397,8 @@ msgid "Event canceled"
|
||||
msgstr "Événement annulé"
|
||||
|
||||
#: pretix/base/services/cancelevent.py:392
|
||||
#, fuzzy
|
||||
#| msgid "Create configuration"
|
||||
msgid "Bulk-refund confirmation"
|
||||
msgstr "Créer une configuration"
|
||||
msgstr "Confirmation de remboursement en bloc"
|
||||
|
||||
#: pretix/base/services/cart.py:102 pretix/base/services/modelimport.py:247
|
||||
#: pretix/base/services/orders.py:155
|
||||
@@ -9720,15 +9718,15 @@ msgstr "Le bon a été envoyé à {recipient}."
|
||||
|
||||
#: pretix/base/settings.py:81
|
||||
msgid "Compute taxes for every line individually"
|
||||
msgstr ""
|
||||
msgstr "Calculer les taxes individuellement pour chaque ligne"
|
||||
|
||||
#: pretix/base/settings.py:82
|
||||
msgid "Compute taxes based on net total"
|
||||
msgstr ""
|
||||
msgstr "Calculer les taxes sur la base du total net"
|
||||
|
||||
#: pretix/base/settings.py:83
|
||||
msgid "Compute taxes based on net total with stable gross prices"
|
||||
msgstr ""
|
||||
msgstr "Calculer les taxes sur la base du total net avec des prix bruts stables"
|
||||
|
||||
#: pretix/base/settings.py:133
|
||||
msgid "Allow usage of restricted plugins"
|
||||
@@ -9821,14 +9819,9 @@ msgid "Add-on products will not be counted."
|
||||
msgstr "Les Add-Ons e sont pas comptabilisés."
|
||||
|
||||
#: pretix/base/settings.py:334
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Show net prices instead of gross prices in the product list (not "
|
||||
#| "recommended!)"
|
||||
msgid "Show net prices instead of gross prices in the product list"
|
||||
msgstr ""
|
||||
"Afficher les prix nets au lieu des prix bruts dans la liste de produits (pas "
|
||||
"recommandé !)"
|
||||
"Afficher les prix nets au lieu des prix bruts dans la liste des produits"
|
||||
|
||||
#: pretix/base/settings.py:335
|
||||
msgid ""
|
||||
@@ -9947,10 +9940,8 @@ msgid "Require a phone number per order"
|
||||
msgstr "Exiger un numéro de téléphone par commande"
|
||||
|
||||
#: pretix/base/settings.py:481
|
||||
#, fuzzy
|
||||
#| msgid "including all taxes"
|
||||
msgid "Rounding of taxes"
|
||||
msgstr "toutes taxes comprises"
|
||||
msgstr "Arrondi des taxes"
|
||||
|
||||
#: pretix/base/settings.py:485
|
||||
msgid ""
|
||||
@@ -9958,6 +9949,10 @@ msgid ""
|
||||
"for tax reporting, you need to make sure to account for possible rounding "
|
||||
"differences if your external system rounds differently than pretix."
|
||||
msgstr ""
|
||||
"Veuillez noter que si vous transférez vos données de vente depuis pretix "
|
||||
"vers un système externe à des fins de déclaration fiscale, vous devez tenir "
|
||||
"compte des éventuelles différences d'arrondi si votre système externe "
|
||||
"arrondit différemment de pretix."
|
||||
|
||||
#: pretix/base/settings.py:500
|
||||
msgid "Ask for invoice address"
|
||||
@@ -13290,18 +13285,20 @@ msgstr ""
|
||||
msgid ""
|
||||
"You have requested us to cancel an event which includes a larger bulk-refund:"
|
||||
msgstr ""
|
||||
"Vous nous avez demandé d'annuler un événement qui implique un remboursement "
|
||||
"groupé important :"
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/cancel_confirm.txt:6
|
||||
#, fuzzy
|
||||
#| msgid "Initiate a refund of %(amount)s"
|
||||
msgid "Estimated refund amount"
|
||||
msgstr "Initier un remboursement de %(amount)s"
|
||||
msgstr "Montant estimé du remboursement"
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/cancel_confirm.txt:8
|
||||
msgid ""
|
||||
"Please confirm that you want to proceed by coping the following confirmation "
|
||||
"code into the cancellation form:"
|
||||
msgstr ""
|
||||
"Veuillez confirmer que vous souhaitez poursuivre en copiant le code de "
|
||||
"confirmation suivant dans le formulaire d'annulation :"
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/email_footer.html:3
|
||||
#, python-format
|
||||
@@ -14151,31 +14148,27 @@ msgstr ""
|
||||
"plus l’année d’émission de la carte-cadeau."
|
||||
|
||||
#: pretix/control/forms/event.py:806
|
||||
#, fuzzy
|
||||
#| msgid "including all taxes"
|
||||
msgid "Prices including tax"
|
||||
msgstr "toutes taxes comprises"
|
||||
msgstr "Prix TTC"
|
||||
|
||||
#: pretix/control/forms/event.py:807
|
||||
msgid "Recommended if you sell tickets at least partly to consumers."
|
||||
msgstr ""
|
||||
"Recommandé si vous vendez des billets au moins en partie à des consommateurs."
|
||||
|
||||
#: pretix/control/forms/event.py:811
|
||||
#, fuzzy
|
||||
#| msgctxt "reporting_timeframe"
|
||||
#| msgid "All future (excluding today)"
|
||||
msgid "Prices excluding tax"
|
||||
msgstr "Tout futur (sauf aujourd’hui)"
|
||||
msgstr "Prix hors taxes"
|
||||
|
||||
#: pretix/control/forms/event.py:812
|
||||
msgid "Recommended only if you sell tickets primarily to business customers."
|
||||
msgstr ""
|
||||
"Recommandé uniquement si vous vendez principalement des billets à des "
|
||||
"clients professionnels."
|
||||
|
||||
#: pretix/control/forms/event.py:848
|
||||
#, fuzzy
|
||||
#| msgid "Date chosen by customer"
|
||||
msgid "Prices shown to customer"
|
||||
msgstr "Date choisie par le client"
|
||||
msgstr "Prix indiqués aux clients"
|
||||
|
||||
#: pretix/control/forms/event.py:852
|
||||
msgid ""
|
||||
@@ -14184,6 +14177,11 @@ msgid ""
|
||||
"product, the total tax amount can differ from when it would be computed from "
|
||||
"the order total."
|
||||
msgstr ""
|
||||
"Recommandé lorsque la facturation électronique n'est pas requise. Chaque "
|
||||
"produit sera vendu au prix net et brut annoncé. Cependant, dans le cas de "
|
||||
"commandes comprenant plusieurs produits, le montant total des taxes peut "
|
||||
"différer de celui qui serait calculé à partir du montant total de la "
|
||||
"commande."
|
||||
|
||||
#: pretix/control/forms/event.py:857
|
||||
msgid ""
|
||||
@@ -14192,6 +14190,12 @@ msgid ""
|
||||
"may be changed to ensure correct rounding, while the net prices will be kept "
|
||||
"as configured. This may cause the actual payment amount to differ."
|
||||
msgstr ""
|
||||
"Recommandé pour la facturation électronique lorsque vous vendez "
|
||||
"principalement à des clients professionnels et affichez les prix hors taxes "
|
||||
"à vos clients. Le prix brut de certains produits peut être modifié afin "
|
||||
"d'assurer un arrondi correct, tandis que les prix nets resteront tels qu'ils "
|
||||
"ont été configurés. Cela peut entraîner une différence entre le montant réel "
|
||||
"du paiement et le montant indiqué."
|
||||
|
||||
#: pretix/control/forms/event.py:863
|
||||
msgid ""
|
||||
@@ -14201,6 +14205,13 @@ msgid ""
|
||||
"configured whenever possible. Gross prices may still change if they are "
|
||||
"impossible to derive from a rounded net price."
|
||||
msgstr ""
|
||||
"Recommandé pour la facturation électronique lorsque vous vendez "
|
||||
"principalement à des consommateurs. Le prix brut ou net de certains produits "
|
||||
"peut être modifié automatiquement afin d'assurer un arrondi correct du total "
|
||||
"de la commande. Le système tente de conserver les prix bruts tels qu'ils ont "
|
||||
"été configurés dans la mesure du possible. Les prix bruts peuvent toutefois "
|
||||
"être modifiés s'il est impossible de les calculer à partir d'un prix net "
|
||||
"arrondi."
|
||||
|
||||
#: pretix/control/forms/event.py:961
|
||||
msgid "Generate invoices for Sales channels"
|
||||
@@ -16332,6 +16343,7 @@ msgstr ""
|
||||
#: pretix/control/forms/orders.py:1037
|
||||
msgid "I understand that this is not reversible and want to continue"
|
||||
msgstr ""
|
||||
"Je comprends que cette opération est irréversible et je souhaite continuer"
|
||||
|
||||
#: pretix/control/forms/orders.py:1041
|
||||
#: pretix/control/templates/pretixcontrol/shredder/download.html:53
|
||||
@@ -16342,12 +16354,12 @@ msgstr "Code de confirmation"
|
||||
msgid ""
|
||||
"We have just emailed you a confirmation code to enter to confirm this action"
|
||||
msgstr ""
|
||||
"Nous venons de vous envoyer par e-mail un code de confirmation à saisir pour "
|
||||
"confirmer cette action"
|
||||
|
||||
#: pretix/control/forms/orders.py:1055
|
||||
#, fuzzy
|
||||
#| msgid "The confirm code you entered was incorrect."
|
||||
msgid "The confirmation code is incorrect."
|
||||
msgstr "Le code de confirmation que vous avez entré était incorrect."
|
||||
msgstr "Le code de confirmation est incorrect."
|
||||
|
||||
#: pretix/control/forms/organizer.py:93
|
||||
msgid "This slug is already in use. Please choose a different one."
|
||||
@@ -21419,6 +21431,10 @@ msgid ""
|
||||
"optionally contain additional rules that depend on the customer's country "
|
||||
"and type."
|
||||
msgstr ""
|
||||
"Les règles fiscales définissent différents scénarios d'imposition qui "
|
||||
"peuvent ensuite être attribués à chaque produit. Chaque règle fiscale "
|
||||
"contient un taux d'imposition par défaut et peut éventuellement contenir des "
|
||||
"règles supplémentaires qui dépendent du pays et du type de client."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html:21
|
||||
msgid "You haven't created any tax rules yet."
|
||||
@@ -21445,13 +21461,11 @@ msgid "Make default"
|
||||
msgstr "Définir par défaut"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html:67
|
||||
#, fuzzy, python-format
|
||||
#| msgid "%(count)s event"
|
||||
#| msgid_plural "%(count)s events"
|
||||
#, python-format
|
||||
msgid "%(count)s product"
|
||||
msgid_plural "%(count)s products"
|
||||
msgstr[0] "%(count)s élement"
|
||||
msgstr[1] "%(count)s élements"
|
||||
msgstr[0] "%(count)s produit"
|
||||
msgstr[1] "%(count)s produits"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html:75
|
||||
#, python-format
|
||||
@@ -21464,16 +21478,12 @@ msgid "excl. %(rate)s %%"
|
||||
msgstr "hors %(rate)s %%"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html:80
|
||||
#, fuzzy
|
||||
#| msgid "Custom rules"
|
||||
msgid "with custom rules"
|
||||
msgstr "Règles personnalisées"
|
||||
msgstr "avec des règles personnalisées"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html:110
|
||||
#, fuzzy
|
||||
#| msgid "Base settings"
|
||||
msgid "Tax settings"
|
||||
msgstr "Réglages de base"
|
||||
msgstr "Paramètres relatifs aux taxes"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax_delete.html:4
|
||||
#: pretix/control/templates/pretixcontrol/event/tax_delete.html:6
|
||||
@@ -24314,7 +24324,7 @@ msgstr ""
|
||||
#: pretix/control/templates/pretixcontrol/order/transactions.html:73
|
||||
#, python-format
|
||||
msgid "incl. %(amount)s rounding correction"
|
||||
msgstr ""
|
||||
msgstr "y compris %(amount)s de correction d'arrondi"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/bulk_action.html:5
|
||||
#: pretix/control/templates/pretixcontrol/orders/bulk_action.html:7
|
||||
@@ -24425,83 +24435,77 @@ msgstr ""
|
||||
"plusieurs courriels."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel.html:84
|
||||
#, fuzzy
|
||||
#| msgid "Refund amount"
|
||||
msgid "Preview refund amount"
|
||||
msgstr "Montant du remboursement"
|
||||
msgstr "Prévisualiser le montant du remboursement"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel.html:88
|
||||
msgid "Cancel all orders"
|
||||
msgstr "Annuler toutes les commandes"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:13
|
||||
#, fuzzy
|
||||
#| msgid "If you contact us, please send us the following code:"
|
||||
msgid "If you proceed, the system will do the following:"
|
||||
msgstr "Si vous nous contactez, veuillez nous envoyer le code suivant :"
|
||||
msgstr "Si vous continuez, le système effectuera les opérations suivantes :"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:19
|
||||
#, python-format
|
||||
msgid "%(count)s order will be canceled fully"
|
||||
msgid_plural "%(count)s orders will be canceled fully"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "%(count)s commande sera totalement annulée"
|
||||
msgstr[1] "%(count)s commandes seront totalement annulées"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:26
|
||||
#, python-format
|
||||
msgid "%(count)s order will be canceled partially"
|
||||
msgid_plural "%(count)s orders will be canceled partially"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "%(count)s commande sera partiellement annulé"
|
||||
msgstr[1] "%(count)s commandes seront partiellement annulées"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:36
|
||||
#, python-format
|
||||
msgid "%(amount)s are eligible for refunds."
|
||||
msgstr ""
|
||||
msgstr "%(amount)s sont susceptibles d'être remboursés."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:40
|
||||
msgid ""
|
||||
"The system will attempt to refund the money automatically if supported by "
|
||||
"the payment method."
|
||||
msgstr ""
|
||||
"Le système tentera de rembourser automatiquement l'argent si le mode de "
|
||||
"paiement le permet."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:42
|
||||
msgid "The system will create manual refunds that you need to execute."
|
||||
msgstr ""
|
||||
msgstr "Le système créera des remboursements manuels que vous devrez exécuter."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:44
|
||||
#, fuzzy
|
||||
#| msgid "Text (if order will not expire automatically)"
|
||||
msgid "Refunds will not happen automatically."
|
||||
msgstr "Texte (si la commande n’expire pas automatiquement)"
|
||||
msgstr "Les remboursements ne se feront pas automatiquement."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:52
|
||||
#, fuzzy
|
||||
#| msgid "Send information via email"
|
||||
msgid "Inform all customers via email."
|
||||
msgstr "Envoyer des informations par e-mail"
|
||||
msgstr "Informez tous les clients par e-mail."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:57
|
||||
msgid "Inform all waiting list contacts via email."
|
||||
msgstr ""
|
||||
"Informez toutes les personnes inscrites sur la liste d'attente par e-mail."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:62
|
||||
msgid ""
|
||||
"These numbers are estimates and may change if the data in your event "
|
||||
"recently changed."
|
||||
msgstr ""
|
||||
"Ces chiffres sont des estimations et peuvent changer si les données "
|
||||
"relatives à votre événement ont récemment été modifiées."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:76
|
||||
#, fuzzy, python-format
|
||||
#| msgid "Initiate a refund of %(amount)s"
|
||||
#, python-format
|
||||
msgid "Proceed and refund approx. %(amount)s"
|
||||
msgstr "Initier un remboursement de %(amount)s"
|
||||
msgstr "Poursuivre et rembourser environ %(amount)s"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:80
|
||||
#, fuzzy
|
||||
#| msgid "Yes, cancel order"
|
||||
msgid "Proceed and cancel orders"
|
||||
msgstr "Oui, annuler la commande"
|
||||
msgstr "Poursuivre et annuler les commandes"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/export.html:5
|
||||
#: pretix/control/templates/pretixcontrol/orders/export.html:8
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-10-30 10:55+0000\n"
|
||||
"PO-Revision-Date: 2025-10-27 12:00+0000\n"
|
||||
"PO-Revision-Date: 2025-11-04 10:25+0000\n"
|
||||
"Last-Translator: Yasunobu YesNo Kawaguchi <kawaguti@gmail.com>\n"
|
||||
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"ja/>\n"
|
||||
@@ -8190,10 +8190,8 @@ msgid "Event canceled"
|
||||
msgstr "イベントがキャンセルされました"
|
||||
|
||||
#: pretix/base/services/cancelevent.py:392
|
||||
#, fuzzy
|
||||
#| msgid "Add confirmation text"
|
||||
msgid "Bulk-refund confirmation"
|
||||
msgstr "確認テキストを追加"
|
||||
msgstr "一括払い戻しの確認"
|
||||
|
||||
#: pretix/base/services/cart.py:102 pretix/base/services/modelimport.py:247
|
||||
#: pretix/base/services/orders.py:155
|
||||
@@ -9431,15 +9429,15 @@ msgstr "バウチャーは、{recipient}あてに送信されました。"
|
||||
|
||||
#: pretix/base/settings.py:81
|
||||
msgid "Compute taxes for every line individually"
|
||||
msgstr ""
|
||||
msgstr "各行ごとに税額を計算する"
|
||||
|
||||
#: pretix/base/settings.py:82
|
||||
msgid "Compute taxes based on net total"
|
||||
msgstr ""
|
||||
msgstr "税抜合計に基づいて税額を計算する"
|
||||
|
||||
#: pretix/base/settings.py:83
|
||||
msgid "Compute taxes based on net total with stable gross prices"
|
||||
msgstr ""
|
||||
msgstr "税込価格を固定して税抜合計に基づいて税額を計算する"
|
||||
|
||||
#: pretix/base/settings.py:133
|
||||
msgid "Allow usage of restricted plugins"
|
||||
@@ -9527,12 +9525,8 @@ msgid "Add-on products will not be counted."
|
||||
msgstr "アドオン製品はカウントされません。"
|
||||
|
||||
#: pretix/base/settings.py:334
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Show net prices instead of gross prices in the product list (not "
|
||||
#| "recommended!)"
|
||||
msgid "Show net prices instead of gross prices in the product list"
|
||||
msgstr "製品リストには、総額ではなく純額を表示してください(お勧めしません!)"
|
||||
msgstr "製品リストに税込価格ではなく税抜価格を表示する"
|
||||
|
||||
#: pretix/base/settings.py:335
|
||||
msgid ""
|
||||
@@ -9646,10 +9640,8 @@ msgid "Require a phone number per order"
|
||||
msgstr "1注文につき1つの電話番号が必要"
|
||||
|
||||
#: pretix/base/settings.py:481
|
||||
#, fuzzy
|
||||
#| msgid "including all taxes"
|
||||
msgid "Rounding of taxes"
|
||||
msgstr "すべての税金を含めて"
|
||||
msgstr "税額の端数処理"
|
||||
|
||||
#: pretix/base/settings.py:485
|
||||
msgid ""
|
||||
@@ -9657,6 +9649,9 @@ msgid ""
|
||||
"for tax reporting, you need to make sure to account for possible rounding "
|
||||
"differences if your external system rounds differently than pretix."
|
||||
msgstr ""
|
||||
"pretixから外部システムに販売データを転送して税務申告を行う場合、外部システム"
|
||||
"の端数処理がpretixと異なる場合は、端数処理の差異を考慮する必要があることに注"
|
||||
"意してください。"
|
||||
|
||||
#: pretix/base/settings.py:500
|
||||
msgid "Ask for invoice address"
|
||||
@@ -12791,19 +12786,18 @@ msgstr "数分以上かかる場合は、このページを更新するか、お
|
||||
#: pretix/base/templates/pretixbase/email/cancel_confirm.txt:2
|
||||
msgid ""
|
||||
"You have requested us to cancel an event which includes a larger bulk-refund:"
|
||||
msgstr ""
|
||||
msgstr "大量の払い戻しを伴うイベントのキャンセルをリクエストされました:"
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/cancel_confirm.txt:6
|
||||
#, fuzzy
|
||||
#| msgid "Initiate a refund of %(amount)s"
|
||||
msgid "Estimated refund amount"
|
||||
msgstr "%(amount)sの払い戻しを開始する"
|
||||
msgstr "推定払い戻し金額"
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/cancel_confirm.txt:8
|
||||
msgid ""
|
||||
"Please confirm that you want to proceed by coping the following confirmation "
|
||||
"code into the cancellation form:"
|
||||
msgstr ""
|
||||
msgstr "以下の確認コードをキャンセルフォームにコピーして、続行することを確認してくだ"
|
||||
"さい:"
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/email_footer.html:3
|
||||
#, python-format
|
||||
@@ -13624,31 +13618,24 @@ msgid ""
|
||||
msgstr "ギフトカードの有効期限を発行年を含めた{}年に設定しました。"
|
||||
|
||||
#: pretix/control/forms/event.py:806
|
||||
#, fuzzy
|
||||
#| msgid "including all taxes"
|
||||
msgid "Prices including tax"
|
||||
msgstr "すべての税金を含めて"
|
||||
msgstr "税込価格"
|
||||
|
||||
#: pretix/control/forms/event.py:807
|
||||
msgid "Recommended if you sell tickets at least partly to consumers."
|
||||
msgstr ""
|
||||
msgstr "少なくとも一部を消費者に販売する場合に推奨されます。"
|
||||
|
||||
#: pretix/control/forms/event.py:811
|
||||
#, fuzzy
|
||||
#| msgctxt "reporting_timeframe"
|
||||
#| msgid "All future (excluding today)"
|
||||
msgid "Prices excluding tax"
|
||||
msgstr "すべての未来(今日を除く)"
|
||||
msgstr "税込価格"
|
||||
|
||||
#: pretix/control/forms/event.py:812
|
||||
msgid "Recommended only if you sell tickets primarily to business customers."
|
||||
msgstr ""
|
||||
msgstr "主に法人顧客にチケットを販売する場合のみ推奨されます。"
|
||||
|
||||
#: pretix/control/forms/event.py:848
|
||||
#, fuzzy
|
||||
#| msgid "Date chosen by customer"
|
||||
msgid "Prices shown to customer"
|
||||
msgstr "お客様が選んだ日付"
|
||||
msgstr "顧客に表示される価格"
|
||||
|
||||
#: pretix/control/forms/event.py:852
|
||||
msgid ""
|
||||
@@ -13657,6 +13644,9 @@ msgid ""
|
||||
"product, the total tax amount can differ from when it would be computed from "
|
||||
"the order total."
|
||||
msgstr ""
|
||||
"電子インボイスが不要な場合に推奨されます。各製品は表示された税抜価格と税込価"
|
||||
"格で販売されます。ただし、複数の製品を含む注文では、合計税額が注文合計から計"
|
||||
"算した場合と異なる可能性があります。"
|
||||
|
||||
#: pretix/control/forms/event.py:857
|
||||
msgid ""
|
||||
@@ -13665,6 +13655,10 @@ msgid ""
|
||||
"may be changed to ensure correct rounding, while the net prices will be kept "
|
||||
"as configured. This may cause the actual payment amount to differ."
|
||||
msgstr ""
|
||||
"主に法人顧客に販売し、顧客に税抜価格を表示する場合の電子インボイスに推奨され"
|
||||
"ます。正確な端数処理を確保するために、一部の製品の税込価格が変更される場合が"
|
||||
"ありますが、税抜価格は設定どおりに保たれます。これにより実際の支払い金額が異"
|
||||
"なる場合があります。"
|
||||
|
||||
#: pretix/control/forms/event.py:863
|
||||
msgid ""
|
||||
@@ -13674,6 +13668,11 @@ msgid ""
|
||||
"configured whenever possible. Gross prices may still change if they are "
|
||||
"impossible to derive from a rounded net price."
|
||||
msgstr ""
|
||||
"主に消費者に販売する場合の電子インボイスに推奨されます。注文合計の正確な端数"
|
||||
"処理を確保するために、一部の製品の税込価格または税抜価格が自動的に変更される"
|
||||
"場合があります。システムは可能な限り設定どおりの税込価格を維持しようとします"
|
||||
"。ただし、端数処理された税抜価格から算出できない場合は、税込価格が変更される"
|
||||
"ことがあります。"
|
||||
|
||||
#: pretix/control/forms/event.py:961
|
||||
msgid "Generate invoices for Sales channels"
|
||||
@@ -14127,7 +14126,7 @@ msgstr "有効な注文"
|
||||
|
||||
#: pretix/control/forms/filter.py:220
|
||||
msgid "Paid (or canceled with paid fee)"
|
||||
msgstr "支払われた(または支払われた手数料でキャンセルされた)"
|
||||
msgstr "支払い済み (または支払い済み手数料でキャンセルされた)"
|
||||
|
||||
#: pretix/control/forms/filter.py:221
|
||||
#: pretix/control/templates/pretixcontrol/items/question.html:27
|
||||
@@ -14154,7 +14153,7 @@ msgstr "キャンセル"
|
||||
|
||||
#: pretix/control/forms/filter.py:226
|
||||
msgid "Canceled (fully)"
|
||||
msgstr "キャンセルされました(完全に)"
|
||||
msgstr "キャンセル済み (全額)"
|
||||
|
||||
#: pretix/control/forms/filter.py:227
|
||||
msgid "Canceled (fully or with paid fee)"
|
||||
@@ -14166,11 +14165,11 @@ msgstr "キャンセル済み(少なくとも1つのポジション)"
|
||||
|
||||
#: pretix/control/forms/filter.py:229
|
||||
msgid "Cancellation requested"
|
||||
msgstr "キャンセルがリクエストされました"
|
||||
msgstr "キャンセル申請済み"
|
||||
|
||||
#: pretix/control/forms/filter.py:230
|
||||
msgid "Fully canceled but invoice not canceled"
|
||||
msgstr "完全にキャンセルされましたが、請求書はキャンセルされていません"
|
||||
msgstr "キャンセル済み(請求書は未キャンセル)"
|
||||
|
||||
#: pretix/control/forms/filter.py:232
|
||||
msgid "Payment process"
|
||||
@@ -14188,11 +14187,11 @@ msgstr "保留中(期限切れ)"
|
||||
|
||||
#: pretix/control/forms/filter.py:236
|
||||
msgid "Overpaid"
|
||||
msgstr "過剰な給料"
|
||||
msgstr "過払い済み"
|
||||
|
||||
#: pretix/control/forms/filter.py:237
|
||||
msgid "Partially paid"
|
||||
msgstr "部分的に支払われました"
|
||||
msgstr "一部支払い済み"
|
||||
|
||||
#: pretix/control/forms/filter.py:238
|
||||
msgid "Underpaid (but confirmed)"
|
||||
@@ -15748,7 +15747,7 @@ msgstr ""
|
||||
|
||||
#: pretix/control/forms/orders.py:1037
|
||||
msgid "I understand that this is not reversible and want to continue"
|
||||
msgstr ""
|
||||
msgstr "これは元に戻せないことを理解し、続行します"
|
||||
|
||||
#: pretix/control/forms/orders.py:1041
|
||||
#: pretix/control/templates/pretixcontrol/shredder/download.html:53
|
||||
@@ -15758,13 +15757,11 @@ msgstr "確認コード"
|
||||
#: pretix/control/forms/orders.py:1042
|
||||
msgid ""
|
||||
"We have just emailed you a confirmation code to enter to confirm this action"
|
||||
msgstr ""
|
||||
msgstr "この操作を確認するための確認コードをメールで送信しました"
|
||||
|
||||
#: pretix/control/forms/orders.py:1055
|
||||
#, fuzzy
|
||||
#| msgid "The confirm code you entered was incorrect."
|
||||
msgid "The confirmation code is incorrect."
|
||||
msgstr "入力した確認コードが間違っています。"
|
||||
msgstr "確認コードが正しくありません。"
|
||||
|
||||
#: pretix/control/forms/organizer.py:93
|
||||
msgid "This slug is already in use. Please choose a different one."
|
||||
@@ -20693,6 +20690,9 @@ msgid ""
|
||||
"optionally contain additional rules that depend on the customer's country "
|
||||
"and type."
|
||||
msgstr ""
|
||||
"税ルールは、個々の製品に割り当てることができるさまざまな課税シナリオを定義し"
|
||||
"ます。各税ルールには既定の税率が含まれ、オプションで顧客の国や種別に応じた追"
|
||||
"加ルールを含めることができます。"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html:21
|
||||
msgid "You haven't created any tax rules yet."
|
||||
@@ -20719,12 +20719,10 @@ msgid "Make default"
|
||||
msgstr "デフォルトに設定"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html:67
|
||||
#, fuzzy, python-format
|
||||
#| msgid "%(count)s event"
|
||||
#| msgid_plural "%(count)s events"
|
||||
#, python-format
|
||||
msgid "%(count)s product"
|
||||
msgid_plural "%(count)s products"
|
||||
msgstr[0] "%(count)s 件のイベント"
|
||||
msgstr[0] "%(count)s個の製品"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html:75
|
||||
#, python-format
|
||||
@@ -20737,16 +20735,12 @@ msgid "excl. %(rate)s %%"
|
||||
msgstr "%(rate)s %%を除く"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html:80
|
||||
#, fuzzy
|
||||
#| msgid "Custom rules"
|
||||
msgid "with custom rules"
|
||||
msgstr "国"
|
||||
msgstr "カスタムルールあり"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html:110
|
||||
#, fuzzy
|
||||
#| msgid "Base settings"
|
||||
msgid "Tax settings"
|
||||
msgstr "ベースの設定"
|
||||
msgstr "税の設定"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/tax_delete.html:4
|
||||
#: pretix/control/templates/pretixcontrol/event/tax_delete.html:6
|
||||
@@ -23487,7 +23481,7 @@ msgstr ""
|
||||
#: pretix/control/templates/pretixcontrol/order/transactions.html:73
|
||||
#, python-format
|
||||
msgid "incl. %(amount)s rounding correction"
|
||||
msgstr ""
|
||||
msgstr "%(amount)sの端数処理調整を含む"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/bulk_action.html:5
|
||||
#: pretix/control/templates/pretixcontrol/orders/bulk_action.html:7
|
||||
@@ -23590,81 +23584,71 @@ msgstr ""
|
||||
"空席待ちリストのすべての人が複数のメールを受け取ることになります。"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel.html:84
|
||||
#, fuzzy
|
||||
#| msgid "Refund amount"
|
||||
msgid "Preview refund amount"
|
||||
msgstr "払い戻し金額"
|
||||
msgstr "払い戻し金額のプレビュー"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel.html:88
|
||||
msgid "Cancel all orders"
|
||||
msgstr "すべての注文をキャンセルします"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:13
|
||||
#, fuzzy
|
||||
#| msgid "If you contact us, please send us the following code:"
|
||||
msgid "If you proceed, the system will do the following:"
|
||||
msgstr "お問い合わせの際は、以下のコードをお送りください:"
|
||||
msgstr "続行すると、システムは以下を実行します:"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:19
|
||||
#, python-format
|
||||
msgid "%(count)s order will be canceled fully"
|
||||
msgid_plural "%(count)s orders will be canceled fully"
|
||||
msgstr[0] ""
|
||||
msgstr[0] "%(count)s件の注文が全額キャンセルされます"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:26
|
||||
#, python-format
|
||||
msgid "%(count)s order will be canceled partially"
|
||||
msgid_plural "%(count)s orders will be canceled partially"
|
||||
msgstr[0] ""
|
||||
msgstr[0] "%(count)s件の注文が一部キャンセルされます"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:36
|
||||
#, python-format
|
||||
msgid "%(amount)s are eligible for refunds."
|
||||
msgstr ""
|
||||
msgstr "%(amount)sが払い戻し対象です。"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:40
|
||||
msgid ""
|
||||
"The system will attempt to refund the money automatically if supported by "
|
||||
"the payment method."
|
||||
msgstr ""
|
||||
msgstr "当該の決済方法に対応している場合、システムは自動的に払い戻しを試みます。"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:42
|
||||
msgid "The system will create manual refunds that you need to execute."
|
||||
msgstr ""
|
||||
msgstr "システムは手動払い戻しを作成しますので、ご自身で実行する必要があります。"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:44
|
||||
#, fuzzy
|
||||
#| msgid "Text (if order will not expire automatically)"
|
||||
msgid "Refunds will not happen automatically."
|
||||
msgstr "テキスト(注文が自動的に期限切れにならない場合)"
|
||||
msgstr "払い戻しは自動的には実行されません。"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:52
|
||||
#, fuzzy
|
||||
#| msgid "Send information via email"
|
||||
msgid "Inform all customers via email."
|
||||
msgstr "メールで情報を送信する"
|
||||
msgstr "メールで情報を送信する。"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:57
|
||||
msgid "Inform all waiting list contacts via email."
|
||||
msgstr ""
|
||||
msgstr "すべての空席待ちリストの連絡先にメールで通知する。"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:62
|
||||
msgid ""
|
||||
"These numbers are estimates and may change if the data in your event "
|
||||
"recently changed."
|
||||
msgstr ""
|
||||
msgstr "これらの数値は推定値であり、イベントのデータが最近変更された場合は変わる可能"
|
||||
"性があります。"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:76
|
||||
#, fuzzy, python-format
|
||||
#| msgid "Initiate a refund of %(amount)s"
|
||||
#, python-format
|
||||
msgid "Proceed and refund approx. %(amount)s"
|
||||
msgstr "%(amount)sの払い戻しを開始する"
|
||||
msgstr "続行して約%(amount)sを払い戻す"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:80
|
||||
#, fuzzy
|
||||
#| msgid "Yes, cancel order"
|
||||
msgid "Proceed and cancel orders"
|
||||
msgstr "はい、注文をキャンセルします"
|
||||
msgstr "続行して注文をキャンセルする"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/export.html:5
|
||||
#: pretix/control/templates/pretixcontrol/orders/export.html:8
|
||||
@@ -24059,7 +24043,7 @@ msgstr "請求書はキャンセルされていません"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/index.html:251
|
||||
msgid "Sum over all pages"
|
||||
msgstr "すべてのページに渡り合計する"
|
||||
msgstr "全ページの合計"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/index.html:254
|
||||
#, python-format
|
||||
@@ -30213,7 +30197,7 @@ msgstr "手動処理用の完全にカスタマイズ可能な支払い方法。
|
||||
#: pretix/plugins/paypal2/payment.py:1097
|
||||
#: pretix/plugins/paypal2/payment.py:1098 pretix/plugins/stripe/payment.py:1816
|
||||
msgid "PayPal"
|
||||
msgstr "ペイパル"
|
||||
msgstr "PayPal"
|
||||
|
||||
#: pretix/plugins/paypal/apps.py:53
|
||||
msgid ""
|
||||
@@ -30233,7 +30217,7 @@ msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal/payment.py:104
|
||||
msgid "PayPal account"
|
||||
msgstr "ペイパルアカウント"
|
||||
msgstr "PayPalアカウント"
|
||||
|
||||
#: pretix/plugins/paypal/payment.py:117 pretix/plugins/paypal2/payment.py:114
|
||||
#, python-brace-format
|
||||
@@ -30330,7 +30314,7 @@ msgstr "PayPalの支払いID"
|
||||
|
||||
#: pretix/plugins/paypal/payment.py:711 pretix/plugins/paypal2/payment.py:1080
|
||||
msgid "PayPal sale ID"
|
||||
msgstr "ペイパルの売上ID"
|
||||
msgstr "PayPalの売上ID"
|
||||
|
||||
#: pretix/plugins/paypal/templates/pretixplugins/paypal/checkout_payment_confirm.html:3
|
||||
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/checkout_payment_confirm.html:19
|
||||
|
||||
@@ -20,10 +20,12 @@
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import datetime
|
||||
from collections import namedtuple
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import vobject
|
||||
from django.conf import settings
|
||||
from django.db.models import prefetch_related_objects
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
@@ -122,61 +124,109 @@ def get_private_icals(event, positions):
|
||||
|
||||
creation_time = datetime.datetime.now(datetime.timezone.utc)
|
||||
calobjects = []
|
||||
calentries = set() # using set for automatic deduplication of CalEntries
|
||||
CalEntry = namedtuple('CalEntry', ['summary', 'description', 'location', 'dtstart', 'dtend', 'uid'])
|
||||
|
||||
evs = set(p.subevent or event for p in positions)
|
||||
for ev in evs:
|
||||
if isinstance(ev, Event):
|
||||
# collecting the positions' calendar entries, preferring the most exact date and time available (positions > subevent > event)
|
||||
prefetch_related_objects(positions, 'item__program_times')
|
||||
for p in positions:
|
||||
ev = p.subevent or event
|
||||
program_times = p.item.program_times.all()
|
||||
if program_times:
|
||||
# if program times have been configured, they are preferred for the position's calendar entries
|
||||
url = build_absolute_uri(event, 'presale:event.index')
|
||||
for index, pt in enumerate(program_times):
|
||||
summary = _('{event} - {item}').format(event=ev, item=p.item.name)
|
||||
if event.settings.mail_attach_ical_description:
|
||||
ctx = get_email_context(event=event, event_or_subevent=ev)
|
||||
description = format_map(str(event.settings.mail_attach_ical_description), ctx)
|
||||
else:
|
||||
# Default description
|
||||
descr = []
|
||||
descr.append(_('Tickets: {url}').format(url=url))
|
||||
descr.append(str(_('Start: {datetime}')).format(
|
||||
datetime=date_format(pt.start.astimezone(tz), 'SHORT_DATETIME_FORMAT')
|
||||
))
|
||||
descr.append(str(_('End: {datetime}')).format(
|
||||
datetime=date_format(pt.end.astimezone(tz), 'SHORT_DATETIME_FORMAT')
|
||||
))
|
||||
# Actual ical organizer field is not useful since it will cause "your invitation was accepted" emails to the organizer
|
||||
descr.append(_('Organizer: {organizer}').format(organizer=event.organizer.name))
|
||||
description = '\n'.join(descr)
|
||||
location = None
|
||||
dtstart = pt.start.astimezone(tz)
|
||||
dtend = pt.end.astimezone(tz)
|
||||
uid = 'pretix-{}-{}-{}-{}@{}'.format(
|
||||
event.organizer.slug,
|
||||
event.slug,
|
||||
p.item.id,
|
||||
index,
|
||||
urlparse(url).netloc
|
||||
)
|
||||
calentries.add(CalEntry(summary, description, location, dtstart, dtend, uid))
|
||||
else:
|
||||
url = build_absolute_uri(event, 'presale:event.index', {
|
||||
'subevent': ev.pk
|
||||
})
|
||||
# without program times, the subevent or event times are used for calendar entries, preferring subevents
|
||||
if p.subevent:
|
||||
url = build_absolute_uri(event, 'presale:event.index', {
|
||||
'subevent': p.subevent.pk
|
||||
})
|
||||
else:
|
||||
url = build_absolute_uri(event, 'presale:event.index')
|
||||
|
||||
if event.settings.mail_attach_ical_description:
|
||||
ctx = get_email_context(event=event, event_or_subevent=ev)
|
||||
description = format_map(str(event.settings.mail_attach_ical_description), ctx)
|
||||
else:
|
||||
# Default description
|
||||
descr = []
|
||||
descr.append(_('Tickets: {url}').format(url=url))
|
||||
if ev.date_admission:
|
||||
descr.append(str(_('Admission: {datetime}')).format(
|
||||
datetime=date_format(ev.date_admission.astimezone(tz), 'SHORT_DATETIME_FORMAT')
|
||||
))
|
||||
if event.settings.mail_attach_ical_description:
|
||||
ctx = get_email_context(event=event, event_or_subevent=ev)
|
||||
description = format_map(str(event.settings.mail_attach_ical_description), ctx)
|
||||
else:
|
||||
# Default description
|
||||
descr = []
|
||||
descr.append(_('Tickets: {url}').format(url=url))
|
||||
if ev.date_admission:
|
||||
descr.append(str(_('Admission: {datetime}')).format(
|
||||
datetime=date_format(ev.date_admission.astimezone(tz), 'SHORT_DATETIME_FORMAT')
|
||||
))
|
||||
|
||||
# Actual ical organizer field is not useful since it will cause "your invitation was accepted" emails to the organizer
|
||||
descr.append(_('Organizer: {organizer}').format(organizer=event.organizer.name))
|
||||
description = '\n'.join(descr)
|
||||
# Actual ical organizer field is not useful since it will cause "your invitation was accepted" emails to the organizer
|
||||
descr.append(_('Organizer: {organizer}').format(organizer=event.organizer.name))
|
||||
description = '\n'.join(descr)
|
||||
summary = str(ev.name)
|
||||
if ev.location:
|
||||
location = ", ".join(l.strip() for l in str(ev.location).splitlines() if l.strip())
|
||||
else:
|
||||
location = None
|
||||
if event.settings.show_times:
|
||||
dtstart = ev.date_from.astimezone(tz)
|
||||
else:
|
||||
dtstart = ev.date_from.astimezone(tz).date()
|
||||
if event.settings.show_date_to and ev.date_to:
|
||||
if event.settings.show_times:
|
||||
dtend = ev.date_to.astimezone(tz)
|
||||
else:
|
||||
# with full-day events date_to in pretix is included (e.g. last day)
|
||||
# whereas dtend in vcalendar is non-inclusive => add one day for export
|
||||
dtend = ev.date_to.astimezone(tz).date() + datetime.timedelta(days=1)
|
||||
else:
|
||||
dtend = None
|
||||
uid = 'pretix-{}-{}-{}@{}'.format(
|
||||
event.organizer.slug,
|
||||
event.slug,
|
||||
ev.pk if p.subevent else '0',
|
||||
urlparse(url).netloc
|
||||
)
|
||||
calentries.add(CalEntry(summary, description, location, dtstart, dtend, uid))
|
||||
|
||||
for calentry in calentries:
|
||||
cal = vobject.iCalendar()
|
||||
cal.add('prodid').value = '-//pretix//{}//'.format(settings.PRETIX_INSTANCE_NAME.replace(" ", "_"))
|
||||
|
||||
vevent = cal.add('vevent')
|
||||
vevent.add('summary').value = str(ev.name)
|
||||
vevent.add('description').value = description
|
||||
vevent.add('summary').value = calentry.summary
|
||||
vevent.add('description').value = calentry.description
|
||||
vevent.add('dtstamp').value = creation_time
|
||||
if ev.location:
|
||||
vevent.add('location').value = ", ".join(l.strip() for l in str(ev.location).splitlines() if l.strip())
|
||||
|
||||
vevent.add('uid').value = 'pretix-{}-{}-{}@{}'.format(
|
||||
event.organizer.slug,
|
||||
event.slug,
|
||||
ev.pk if not isinstance(ev, Event) else '0',
|
||||
urlparse(url).netloc
|
||||
)
|
||||
|
||||
if event.settings.show_times:
|
||||
vevent.add('dtstart').value = ev.date_from.astimezone(tz)
|
||||
else:
|
||||
vevent.add('dtstart').value = ev.date_from.astimezone(tz).date()
|
||||
|
||||
if event.settings.show_date_to and ev.date_to:
|
||||
if event.settings.show_times:
|
||||
vevent.add('dtend').value = ev.date_to.astimezone(tz)
|
||||
else:
|
||||
# with full-day events date_to in pretix is included (e.g. last day)
|
||||
# whereas dtend in vcalendar is non-inclusive => add one day for export
|
||||
vevent.add('dtend').value = ev.date_to.astimezone(tz).date() + datetime.timedelta(days=1)
|
||||
|
||||
if calentry.location:
|
||||
vevent.add('location').value = calentry.location
|
||||
vevent.add('uid').value = calentry.uid
|
||||
vevent.add('dtstart').value = calentry.dtstart
|
||||
if calentry.dtend:
|
||||
vevent.add('dtend').value = calentry.dtend
|
||||
calobjects.append(cal)
|
||||
return calobjects
|
||||
|
||||
@@ -9,34 +9,6 @@
|
||||
{% endblock %}
|
||||
{% block inner %}
|
||||
<h3 class="sr-only">{% trans "Payment" %}</h3>
|
||||
{% if customer_gift_cards %}
|
||||
<p><strong>
|
||||
<span class="sr-only">{% trans "Information" %}</span>
|
||||
{% trans "The following gift cards are available in your customer account:" %}
|
||||
</strong></p>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<ul class="list-group">
|
||||
{% for c in customer_gift_cards %}
|
||||
<li class="list-group-item row">
|
||||
<div class="col-xs-9" id="gc-code-{{ forloop.counter }}">
|
||||
{{ c }}
|
||||
</div>
|
||||
<div class="col-xs-2 text-right" id="gc-value-{{ forloop.counter }}">
|
||||
{{ c.value|money:c.currency }}
|
||||
</div>
|
||||
<div class="col-xs-1 text-right">
|
||||
<button name="use_giftcard" value="{{ c.secret }}" title="{% trans "Use gift card" %}"
|
||||
aria-describedby="gc-code-{{ forloop.counter }} gc-value-{{ forloop.counter }}"
|
||||
class="btn btn-primary btn-xs">
|
||||
{% trans "Apply" %}
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if current_payments %}
|
||||
<p>{% trans "You already selected the following payment methods:" %}</p>
|
||||
<form method="post">
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% load money %}
|
||||
{% load rich_text %}
|
||||
|
||||
{{ request.event.settings.payment_giftcard_public_description|rich_text }}
|
||||
{% if customer_gift_cards %}
|
||||
<p><strong>
|
||||
<span class="sr-only">{% trans "Information" %}</span>
|
||||
{% trans "The following gift cards are available in your customer account:" %}
|
||||
</strong></p>
|
||||
{% csrf_token %}
|
||||
<ul class="list-group">
|
||||
{% for c in customer_gift_cards %}
|
||||
<li class="list-group-item row row-no-gutters">
|
||||
<div class="col-sm-8 col-md-9" id="gc-code-{{ forloop.counter }}">
|
||||
{{ c }}
|
||||
</div>
|
||||
<div class="col-sm-2 text-right" id="gc-value-{{ forloop.counter }}">
|
||||
{{ c.value|money:c.currency }}
|
||||
</div>
|
||||
<div class="col-sm-2 col-md-1 text-right">
|
||||
<button name="use_giftcard" class="btn btn-primary btn-xs use_giftcard" data-value="{{ c.secret }}"
|
||||
title="{% trans "Use gift card" %}"
|
||||
aria-describedby="gc-code-{{ forloop.counter }} gc-value-{{ forloop.counter }}">
|
||||
{% trans "Apply" %}
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% bootstrap_form form layout='checkout' %}
|
||||
371
src/pretix/static/npm_dir/package-lock.json
generated
371
src/pretix/static/npm_dir/package-lock.json
generated
@@ -8,10 +8,10 @@
|
||||
"name": "pretix",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.28.4",
|
||||
"@babel/preset-env": "^7.28.3",
|
||||
"@rollup/plugin-babel": "^6.0.4",
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@babel/core": "^7.28.5",
|
||||
"@babel/preset-env": "^7.28.5",
|
||||
"@rollup/plugin-babel": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^16.0.3",
|
||||
"rollup": "^2.79.1",
|
||||
"rollup-plugin-vue": "^5.0.1",
|
||||
"vue": "^2.7.16",
|
||||
@@ -33,27 +33,27 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/compat-data": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz",
|
||||
"integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz",
|
||||
"integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/core": {
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz",
|
||||
"integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
|
||||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.3",
|
||||
"@babel/generator": "^7.28.5",
|
||||
"@babel/helper-compilation-targets": "^7.27.2",
|
||||
"@babel/helper-module-transforms": "^7.28.3",
|
||||
"@babel/helpers": "^7.28.4",
|
||||
"@babel/parser": "^7.28.4",
|
||||
"@babel/parser": "^7.28.5",
|
||||
"@babel/template": "^7.27.2",
|
||||
"@babel/traverse": "^7.28.4",
|
||||
"@babel/types": "^7.28.4",
|
||||
"@babel/traverse": "^7.28.5",
|
||||
"@babel/types": "^7.28.5",
|
||||
"@jridgewell/remapping": "^2.3.5",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"debug": "^4.1.0",
|
||||
@@ -89,12 +89,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/generator": {
|
||||
"version": "7.28.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz",
|
||||
"integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
|
||||
"integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.28.3",
|
||||
"@babel/types": "^7.28.2",
|
||||
"@babel/parser": "^7.28.5",
|
||||
"@babel/types": "^7.28.5",
|
||||
"@jridgewell/gen-mapping": "^0.3.12",
|
||||
"@jridgewell/trace-mapping": "^0.3.28",
|
||||
"jsesc": "^3.0.2"
|
||||
@@ -348,10 +348,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
|
||||
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
|
||||
"license": "MIT",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
||||
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@@ -392,11 +391,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz",
|
||||
"integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
|
||||
"integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.28.4"
|
||||
"@babel/types": "^7.28.5"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
@@ -406,13 +405,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz",
|
||||
"integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==",
|
||||
"license": "MIT",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz",
|
||||
"integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.27.1",
|
||||
"@babel/traverse": "^7.27.1"
|
||||
"@babel/traverse": "^7.28.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -603,9 +601,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-block-scoping": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.0.tgz",
|
||||
"integrity": "sha512-gKKnwjpdx5sER/wl0WN0efUBFzF/56YZO0RJrSYP4CljXnP31ByY7fol89AzomdlLNzI36AvOTmYHsnZTCkq8Q==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz",
|
||||
"integrity": "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.27.1"
|
||||
},
|
||||
@@ -648,16 +646,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-classes": {
|
||||
"version": "7.28.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.3.tgz",
|
||||
"integrity": "sha512-DoEWC5SuxuARF2KdKmGUq3ghfPMO6ZzR12Dnp5gubwbeWJo4dbNWXJPVlwvh4Zlq6Z7YVvL8VFxeSOJgjsx4Sg==",
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz",
|
||||
"integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==",
|
||||
"dependencies": {
|
||||
"@babel/helper-annotate-as-pure": "^7.27.3",
|
||||
"@babel/helper-compilation-targets": "^7.27.2",
|
||||
"@babel/helper-globals": "^7.28.0",
|
||||
"@babel/helper-plugin-utils": "^7.27.1",
|
||||
"@babel/helper-replace-supers": "^7.27.1",
|
||||
"@babel/traverse": "^7.28.3"
|
||||
"@babel/traverse": "^7.28.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -683,12 +681,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-destructuring": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz",
|
||||
"integrity": "sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz",
|
||||
"integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.27.1",
|
||||
"@babel/traverse": "^7.28.0"
|
||||
"@babel/traverse": "^7.28.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -775,10 +773,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-exponentiation-operator": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz",
|
||||
"integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==",
|
||||
"license": "MIT",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz",
|
||||
"integrity": "sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.27.1"
|
||||
},
|
||||
@@ -868,10 +865,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-logical-assignment-operators": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz",
|
||||
"integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==",
|
||||
"license": "MIT",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz",
|
||||
"integrity": "sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.27.1"
|
||||
},
|
||||
@@ -930,15 +926,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-modules-systemjs": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz",
|
||||
"integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==",
|
||||
"license": "MIT",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz",
|
||||
"integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==",
|
||||
"dependencies": {
|
||||
"@babel/helper-module-transforms": "^7.27.1",
|
||||
"@babel/helper-module-transforms": "^7.28.3",
|
||||
"@babel/helper-plugin-utils": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.27.1",
|
||||
"@babel/traverse": "^7.27.1"
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"@babel/traverse": "^7.28.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -1025,15 +1020,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-object-rest-spread": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.0.tgz",
|
||||
"integrity": "sha512-9VNGikXxzu5eCiQjdE4IZn8sb9q7Xsk5EXLDBKUYg1e/Tve8/05+KJEtcxGxAgCY5t/BpKQM+JEL/yT4tvgiUA==",
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz",
|
||||
"integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==",
|
||||
"dependencies": {
|
||||
"@babel/helper-compilation-targets": "^7.27.2",
|
||||
"@babel/helper-plugin-utils": "^7.27.1",
|
||||
"@babel/plugin-transform-destructuring": "^7.28.0",
|
||||
"@babel/plugin-transform-parameters": "^7.27.7",
|
||||
"@babel/traverse": "^7.28.0"
|
||||
"@babel/traverse": "^7.28.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -1074,10 +1069,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-optional-chaining": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz",
|
||||
"integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==",
|
||||
"license": "MIT",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz",
|
||||
"integrity": "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.27.1",
|
||||
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
|
||||
@@ -1152,9 +1146,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-regenerator": {
|
||||
"version": "7.28.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.3.tgz",
|
||||
"integrity": "sha512-K3/M/a4+ESb5LEldjQb+XSrpY0nF+ZBFlTCbSnKaYAMfD8v33O6PMs4uYnOk19HlcsI8WMu3McdFPTiQHF/1/A==",
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz",
|
||||
"integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.27.1"
|
||||
},
|
||||
@@ -1336,15 +1330,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/preset-env": {
|
||||
"version": "7.28.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.3.tgz",
|
||||
"integrity": "sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.5.tgz",
|
||||
"integrity": "sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==",
|
||||
"dependencies": {
|
||||
"@babel/compat-data": "^7.28.0",
|
||||
"@babel/compat-data": "^7.28.5",
|
||||
"@babel/helper-compilation-targets": "^7.27.2",
|
||||
"@babel/helper-plugin-utils": "^7.27.1",
|
||||
"@babel/helper-validator-option": "^7.27.1",
|
||||
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1",
|
||||
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5",
|
||||
"@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1",
|
||||
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1",
|
||||
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1",
|
||||
@@ -1357,42 +1351,42 @@
|
||||
"@babel/plugin-transform-async-generator-functions": "^7.28.0",
|
||||
"@babel/plugin-transform-async-to-generator": "^7.27.1",
|
||||
"@babel/plugin-transform-block-scoped-functions": "^7.27.1",
|
||||
"@babel/plugin-transform-block-scoping": "^7.28.0",
|
||||
"@babel/plugin-transform-block-scoping": "^7.28.5",
|
||||
"@babel/plugin-transform-class-properties": "^7.27.1",
|
||||
"@babel/plugin-transform-class-static-block": "^7.28.3",
|
||||
"@babel/plugin-transform-classes": "^7.28.3",
|
||||
"@babel/plugin-transform-classes": "^7.28.4",
|
||||
"@babel/plugin-transform-computed-properties": "^7.27.1",
|
||||
"@babel/plugin-transform-destructuring": "^7.28.0",
|
||||
"@babel/plugin-transform-destructuring": "^7.28.5",
|
||||
"@babel/plugin-transform-dotall-regex": "^7.27.1",
|
||||
"@babel/plugin-transform-duplicate-keys": "^7.27.1",
|
||||
"@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1",
|
||||
"@babel/plugin-transform-dynamic-import": "^7.27.1",
|
||||
"@babel/plugin-transform-explicit-resource-management": "^7.28.0",
|
||||
"@babel/plugin-transform-exponentiation-operator": "^7.27.1",
|
||||
"@babel/plugin-transform-exponentiation-operator": "^7.28.5",
|
||||
"@babel/plugin-transform-export-namespace-from": "^7.27.1",
|
||||
"@babel/plugin-transform-for-of": "^7.27.1",
|
||||
"@babel/plugin-transform-function-name": "^7.27.1",
|
||||
"@babel/plugin-transform-json-strings": "^7.27.1",
|
||||
"@babel/plugin-transform-literals": "^7.27.1",
|
||||
"@babel/plugin-transform-logical-assignment-operators": "^7.27.1",
|
||||
"@babel/plugin-transform-logical-assignment-operators": "^7.28.5",
|
||||
"@babel/plugin-transform-member-expression-literals": "^7.27.1",
|
||||
"@babel/plugin-transform-modules-amd": "^7.27.1",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.27.1",
|
||||
"@babel/plugin-transform-modules-systemjs": "^7.27.1",
|
||||
"@babel/plugin-transform-modules-systemjs": "^7.28.5",
|
||||
"@babel/plugin-transform-modules-umd": "^7.27.1",
|
||||
"@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1",
|
||||
"@babel/plugin-transform-new-target": "^7.27.1",
|
||||
"@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1",
|
||||
"@babel/plugin-transform-numeric-separator": "^7.27.1",
|
||||
"@babel/plugin-transform-object-rest-spread": "^7.28.0",
|
||||
"@babel/plugin-transform-object-rest-spread": "^7.28.4",
|
||||
"@babel/plugin-transform-object-super": "^7.27.1",
|
||||
"@babel/plugin-transform-optional-catch-binding": "^7.27.1",
|
||||
"@babel/plugin-transform-optional-chaining": "^7.27.1",
|
||||
"@babel/plugin-transform-optional-chaining": "^7.28.5",
|
||||
"@babel/plugin-transform-parameters": "^7.27.7",
|
||||
"@babel/plugin-transform-private-methods": "^7.27.1",
|
||||
"@babel/plugin-transform-private-property-in-object": "^7.27.1",
|
||||
"@babel/plugin-transform-property-literals": "^7.27.1",
|
||||
"@babel/plugin-transform-regenerator": "^7.28.3",
|
||||
"@babel/plugin-transform-regenerator": "^7.28.4",
|
||||
"@babel/plugin-transform-regexp-modifiers": "^7.27.1",
|
||||
"@babel/plugin-transform-reserved-words": "^7.27.1",
|
||||
"@babel/plugin-transform-shorthand-properties": "^7.27.1",
|
||||
@@ -1454,16 +1448,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz",
|
||||
"integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
|
||||
"integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.3",
|
||||
"@babel/generator": "^7.28.5",
|
||||
"@babel/helper-globals": "^7.28.0",
|
||||
"@babel/parser": "^7.28.4",
|
||||
"@babel/parser": "^7.28.5",
|
||||
"@babel/template": "^7.27.2",
|
||||
"@babel/types": "^7.28.4",
|
||||
"@babel/types": "^7.28.5",
|
||||
"debug": "^4.3.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1471,12 +1465,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz",
|
||||
"integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
|
||||
"integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.27.1"
|
||||
"@babel/helper-validator-identifier": "^7.28.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -1523,9 +1517,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-babel": {
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.0.4.tgz",
|
||||
"integrity": "sha512-YF7Y52kFdFT/xVSuVdjkV5ZdX/3YtmX0QulG+x0taQOtJdHYzVU61aSSkAgVJ7NOv6qPkIYiJSgSWWN/DM5sGw==",
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.1.0.tgz",
|
||||
"integrity": "sha512-dFZNuFD2YRcoomP4oYf+DvQNSUA9ih+A3vUqopQx5EdtPGo3WBnQcI/S8pwpz91UsGfL0HsMSOlaMld8HrbubA==",
|
||||
"dependencies": {
|
||||
"@babel/helper-module-imports": "^7.18.6",
|
||||
"@rollup/pluginutils": "^5.0.1"
|
||||
@@ -1548,10 +1542,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-node-resolve": {
|
||||
"version": "16.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.1.tgz",
|
||||
"integrity": "sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==",
|
||||
"license": "MIT",
|
||||
"version": "16.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz",
|
||||
"integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==",
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^5.0.1",
|
||||
"@types/resolve": "1.20.2",
|
||||
@@ -3790,24 +3783,24 @@
|
||||
}
|
||||
},
|
||||
"@babel/compat-data": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz",
|
||||
"integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw=="
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz",
|
||||
"integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="
|
||||
},
|
||||
"@babel/core": {
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz",
|
||||
"integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
|
||||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.3",
|
||||
"@babel/generator": "^7.28.5",
|
||||
"@babel/helper-compilation-targets": "^7.27.2",
|
||||
"@babel/helper-module-transforms": "^7.28.3",
|
||||
"@babel/helpers": "^7.28.4",
|
||||
"@babel/parser": "^7.28.4",
|
||||
"@babel/parser": "^7.28.5",
|
||||
"@babel/template": "^7.27.2",
|
||||
"@babel/traverse": "^7.28.4",
|
||||
"@babel/types": "^7.28.4",
|
||||
"@babel/traverse": "^7.28.5",
|
||||
"@babel/types": "^7.28.5",
|
||||
"@jridgewell/remapping": "^2.3.5",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"debug": "^4.1.0",
|
||||
@@ -3829,12 +3822,12 @@
|
||||
}
|
||||
},
|
||||
"@babel/generator": {
|
||||
"version": "7.28.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz",
|
||||
"integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
|
||||
"integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
|
||||
"requires": {
|
||||
"@babel/parser": "^7.28.3",
|
||||
"@babel/types": "^7.28.2",
|
||||
"@babel/parser": "^7.28.5",
|
||||
"@babel/types": "^7.28.5",
|
||||
"@jridgewell/gen-mapping": "^0.3.12",
|
||||
"@jridgewell/trace-mapping": "^0.3.28",
|
||||
"jsesc": "^3.0.2"
|
||||
@@ -4011,9 +4004,9 @@
|
||||
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
|
||||
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
||||
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="
|
||||
},
|
||||
"@babel/helper-validator-option": {
|
||||
"version": "7.27.1",
|
||||
@@ -4040,20 +4033,20 @@
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz",
|
||||
"integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
|
||||
"integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.28.4"
|
||||
"@babel/types": "^7.28.5"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz",
|
||||
"integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz",
|
||||
"integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==",
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.27.1",
|
||||
"@babel/traverse": "^7.27.1"
|
||||
"@babel/traverse": "^7.28.5"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-bugfix-safari-class-field-initializer-scope": {
|
||||
@@ -4159,9 +4152,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-block-scoping": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.0.tgz",
|
||||
"integrity": "sha512-gKKnwjpdx5sER/wl0WN0efUBFzF/56YZO0RJrSYP4CljXnP31ByY7fol89AzomdlLNzI36AvOTmYHsnZTCkq8Q==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz",
|
||||
"integrity": "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==",
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.27.1"
|
||||
}
|
||||
@@ -4185,16 +4178,16 @@
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-classes": {
|
||||
"version": "7.28.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.3.tgz",
|
||||
"integrity": "sha512-DoEWC5SuxuARF2KdKmGUq3ghfPMO6ZzR12Dnp5gubwbeWJo4dbNWXJPVlwvh4Zlq6Z7YVvL8VFxeSOJgjsx4Sg==",
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz",
|
||||
"integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==",
|
||||
"requires": {
|
||||
"@babel/helper-annotate-as-pure": "^7.27.3",
|
||||
"@babel/helper-compilation-targets": "^7.27.2",
|
||||
"@babel/helper-globals": "^7.28.0",
|
||||
"@babel/helper-plugin-utils": "^7.27.1",
|
||||
"@babel/helper-replace-supers": "^7.27.1",
|
||||
"@babel/traverse": "^7.28.3"
|
||||
"@babel/traverse": "^7.28.4"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-computed-properties": {
|
||||
@@ -4207,12 +4200,12 @@
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-destructuring": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz",
|
||||
"integrity": "sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz",
|
||||
"integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==",
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.27.1",
|
||||
"@babel/traverse": "^7.28.0"
|
||||
"@babel/traverse": "^7.28.5"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-dotall-regex": {
|
||||
@@ -4259,9 +4252,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-exponentiation-operator": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz",
|
||||
"integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz",
|
||||
"integrity": "sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==",
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.27.1"
|
||||
}
|
||||
@@ -4310,9 +4303,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-logical-assignment-operators": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz",
|
||||
"integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz",
|
||||
"integrity": "sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==",
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.27.1"
|
||||
}
|
||||
@@ -4344,14 +4337,14 @@
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-modules-systemjs": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz",
|
||||
"integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz",
|
||||
"integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==",
|
||||
"requires": {
|
||||
"@babel/helper-module-transforms": "^7.27.1",
|
||||
"@babel/helper-module-transforms": "^7.28.3",
|
||||
"@babel/helper-plugin-utils": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.27.1",
|
||||
"@babel/traverse": "^7.27.1"
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"@babel/traverse": "^7.28.5"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-modules-umd": {
|
||||
@@ -4397,15 +4390,15 @@
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-object-rest-spread": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.0.tgz",
|
||||
"integrity": "sha512-9VNGikXxzu5eCiQjdE4IZn8sb9q7Xsk5EXLDBKUYg1e/Tve8/05+KJEtcxGxAgCY5t/BpKQM+JEL/yT4tvgiUA==",
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz",
|
||||
"integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==",
|
||||
"requires": {
|
||||
"@babel/helper-compilation-targets": "^7.27.2",
|
||||
"@babel/helper-plugin-utils": "^7.27.1",
|
||||
"@babel/plugin-transform-destructuring": "^7.28.0",
|
||||
"@babel/plugin-transform-parameters": "^7.27.7",
|
||||
"@babel/traverse": "^7.28.0"
|
||||
"@babel/traverse": "^7.28.4"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-object-super": {
|
||||
@@ -4426,9 +4419,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-optional-chaining": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz",
|
||||
"integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz",
|
||||
"integrity": "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==",
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.27.1",
|
||||
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
|
||||
@@ -4470,9 +4463,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-regenerator": {
|
||||
"version": "7.28.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.3.tgz",
|
||||
"integrity": "sha512-K3/M/a4+ESb5LEldjQb+XSrpY0nF+ZBFlTCbSnKaYAMfD8v33O6PMs4uYnOk19HlcsI8WMu3McdFPTiQHF/1/A==",
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz",
|
||||
"integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==",
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.27.1"
|
||||
}
|
||||
@@ -4571,15 +4564,15 @@
|
||||
}
|
||||
},
|
||||
"@babel/preset-env": {
|
||||
"version": "7.28.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.3.tgz",
|
||||
"integrity": "sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.5.tgz",
|
||||
"integrity": "sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==",
|
||||
"requires": {
|
||||
"@babel/compat-data": "^7.28.0",
|
||||
"@babel/compat-data": "^7.28.5",
|
||||
"@babel/helper-compilation-targets": "^7.27.2",
|
||||
"@babel/helper-plugin-utils": "^7.27.1",
|
||||
"@babel/helper-validator-option": "^7.27.1",
|
||||
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1",
|
||||
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5",
|
||||
"@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1",
|
||||
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1",
|
||||
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1",
|
||||
@@ -4592,42 +4585,42 @@
|
||||
"@babel/plugin-transform-async-generator-functions": "^7.28.0",
|
||||
"@babel/plugin-transform-async-to-generator": "^7.27.1",
|
||||
"@babel/plugin-transform-block-scoped-functions": "^7.27.1",
|
||||
"@babel/plugin-transform-block-scoping": "^7.28.0",
|
||||
"@babel/plugin-transform-block-scoping": "^7.28.5",
|
||||
"@babel/plugin-transform-class-properties": "^7.27.1",
|
||||
"@babel/plugin-transform-class-static-block": "^7.28.3",
|
||||
"@babel/plugin-transform-classes": "^7.28.3",
|
||||
"@babel/plugin-transform-classes": "^7.28.4",
|
||||
"@babel/plugin-transform-computed-properties": "^7.27.1",
|
||||
"@babel/plugin-transform-destructuring": "^7.28.0",
|
||||
"@babel/plugin-transform-destructuring": "^7.28.5",
|
||||
"@babel/plugin-transform-dotall-regex": "^7.27.1",
|
||||
"@babel/plugin-transform-duplicate-keys": "^7.27.1",
|
||||
"@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1",
|
||||
"@babel/plugin-transform-dynamic-import": "^7.27.1",
|
||||
"@babel/plugin-transform-explicit-resource-management": "^7.28.0",
|
||||
"@babel/plugin-transform-exponentiation-operator": "^7.27.1",
|
||||
"@babel/plugin-transform-exponentiation-operator": "^7.28.5",
|
||||
"@babel/plugin-transform-export-namespace-from": "^7.27.1",
|
||||
"@babel/plugin-transform-for-of": "^7.27.1",
|
||||
"@babel/plugin-transform-function-name": "^7.27.1",
|
||||
"@babel/plugin-transform-json-strings": "^7.27.1",
|
||||
"@babel/plugin-transform-literals": "^7.27.1",
|
||||
"@babel/plugin-transform-logical-assignment-operators": "^7.27.1",
|
||||
"@babel/plugin-transform-logical-assignment-operators": "^7.28.5",
|
||||
"@babel/plugin-transform-member-expression-literals": "^7.27.1",
|
||||
"@babel/plugin-transform-modules-amd": "^7.27.1",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.27.1",
|
||||
"@babel/plugin-transform-modules-systemjs": "^7.27.1",
|
||||
"@babel/plugin-transform-modules-systemjs": "^7.28.5",
|
||||
"@babel/plugin-transform-modules-umd": "^7.27.1",
|
||||
"@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1",
|
||||
"@babel/plugin-transform-new-target": "^7.27.1",
|
||||
"@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1",
|
||||
"@babel/plugin-transform-numeric-separator": "^7.27.1",
|
||||
"@babel/plugin-transform-object-rest-spread": "^7.28.0",
|
||||
"@babel/plugin-transform-object-rest-spread": "^7.28.4",
|
||||
"@babel/plugin-transform-object-super": "^7.27.1",
|
||||
"@babel/plugin-transform-optional-catch-binding": "^7.27.1",
|
||||
"@babel/plugin-transform-optional-chaining": "^7.27.1",
|
||||
"@babel/plugin-transform-optional-chaining": "^7.28.5",
|
||||
"@babel/plugin-transform-parameters": "^7.27.7",
|
||||
"@babel/plugin-transform-private-methods": "^7.27.1",
|
||||
"@babel/plugin-transform-private-property-in-object": "^7.27.1",
|
||||
"@babel/plugin-transform-property-literals": "^7.27.1",
|
||||
"@babel/plugin-transform-regenerator": "^7.28.3",
|
||||
"@babel/plugin-transform-regenerator": "^7.28.4",
|
||||
"@babel/plugin-transform-regexp-modifiers": "^7.27.1",
|
||||
"@babel/plugin-transform-reserved-words": "^7.27.1",
|
||||
"@babel/plugin-transform-shorthand-properties": "^7.27.1",
|
||||
@@ -4675,26 +4668,26 @@
|
||||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz",
|
||||
"integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
|
||||
"integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.3",
|
||||
"@babel/generator": "^7.28.5",
|
||||
"@babel/helper-globals": "^7.28.0",
|
||||
"@babel/parser": "^7.28.4",
|
||||
"@babel/parser": "^7.28.5",
|
||||
"@babel/template": "^7.27.2",
|
||||
"@babel/types": "^7.28.4",
|
||||
"@babel/types": "^7.28.5",
|
||||
"debug": "^4.3.1"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz",
|
||||
"integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
|
||||
"integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
|
||||
"requires": {
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.27.1"
|
||||
"@babel/helper-validator-identifier": "^7.28.5"
|
||||
}
|
||||
},
|
||||
"@jridgewell/gen-mapping": {
|
||||
@@ -4735,18 +4728,18 @@
|
||||
}
|
||||
},
|
||||
"@rollup/plugin-babel": {
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.0.4.tgz",
|
||||
"integrity": "sha512-YF7Y52kFdFT/xVSuVdjkV5ZdX/3YtmX0QulG+x0taQOtJdHYzVU61aSSkAgVJ7NOv6qPkIYiJSgSWWN/DM5sGw==",
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.1.0.tgz",
|
||||
"integrity": "sha512-dFZNuFD2YRcoomP4oYf+DvQNSUA9ih+A3vUqopQx5EdtPGo3WBnQcI/S8pwpz91UsGfL0HsMSOlaMld8HrbubA==",
|
||||
"requires": {
|
||||
"@babel/helper-module-imports": "^7.18.6",
|
||||
"@rollup/pluginutils": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"@rollup/plugin-node-resolve": {
|
||||
"version": "16.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.1.tgz",
|
||||
"integrity": "sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==",
|
||||
"version": "16.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz",
|
||||
"integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==",
|
||||
"requires": {
|
||||
"@rollup/pluginutils": "^5.0.1",
|
||||
"@types/resolve": "1.20.2",
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
"private": true,
|
||||
"scripts": {},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.28.4",
|
||||
"@babel/preset-env": "^7.28.3",
|
||||
"@rollup/plugin-babel": "^6.0.4",
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@babel/core": "^7.28.5",
|
||||
"@babel/preset-env": "^7.28.5",
|
||||
"@rollup/plugin-babel": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^16.0.3",
|
||||
"vue": "^2.7.16",
|
||||
"rollup": "^2.79.1",
|
||||
"rollup-plugin-vue": "^5.0.1",
|
||||
|
||||
@@ -263,3 +263,11 @@ svg.svg-icon {
|
||||
@include table-row-variant('warning', var(--pretix-brand-warning-lighten-40), var(--pretix-brand-warning-lighten-35));
|
||||
@include table-row-variant('danger', var(--pretix-brand-danger-lighten-30), var(--pretix-brand-danger-lighten-25));
|
||||
|
||||
.confirmation-code-input {
|
||||
font-size: 200%;
|
||||
font-family: monospace;
|
||||
font-stretch: expanded;
|
||||
text-align: center;
|
||||
height: 50px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
@@ -938,3 +938,25 @@ details {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media (min-width: $screen-lg-min) {
|
||||
.centered-form {
|
||||
margin: 80px auto;
|
||||
max-width: 800px;
|
||||
border: 1px solid #ddd;
|
||||
padding: 20px 40px 0;
|
||||
border-radius: 4px;
|
||||
box-shadow: 2px 2px 2px #eee;
|
||||
}
|
||||
|
||||
.form.centered-form .submit-group {
|
||||
margin: 25px -40px 0 !important;
|
||||
padding-right: 40px;
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
.centered-form p {
|
||||
margin: 20px 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,8 @@ from tests.const import SAMPLE_PNG
|
||||
|
||||
from pretix.base.models import (
|
||||
CartPosition, InvoiceAddress, Item, ItemAddOn, ItemBundle, ItemCategory,
|
||||
ItemVariation, Order, OrderPosition, Question, QuestionOption, Quota,
|
||||
ItemProgramTime, ItemVariation, Order, OrderPosition, Question,
|
||||
QuestionOption, Quota,
|
||||
)
|
||||
from pretix.base.models.orders import OrderFee
|
||||
|
||||
@@ -331,6 +332,7 @@ TEST_ITEM_RES = {
|
||||
"variations": [],
|
||||
"addons": [],
|
||||
"bundles": [],
|
||||
"program_times": [],
|
||||
"show_quota_left": None,
|
||||
"original_price": None,
|
||||
"free_price_suggestion": None,
|
||||
@@ -509,6 +511,24 @@ def test_item_detail_bundles(token_client, organizer, event, team, item, categor
|
||||
assert res == resp.data
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_item_detail_program_times(token_client, organizer, event, team, item, category):
|
||||
with scopes_disabled():
|
||||
item.program_times.create(item=item, start=datetime(2017, 12, 27, 0, 0, 0, tzinfo=timezone.utc),
|
||||
end=datetime(2017, 12, 28, 0, 0, 0, tzinfo=timezone.utc))
|
||||
res = dict(TEST_ITEM_RES)
|
||||
|
||||
res["id"] = item.pk
|
||||
res["program_times"] = [{
|
||||
"start": "2017-12-27T00:00:00Z",
|
||||
"end": "2017-12-28T00:00:00Z",
|
||||
}]
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug,
|
||||
item.pk))
|
||||
assert resp.status_code == 200
|
||||
assert res == resp.data
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_item_create(token_client, organizer, event, item, category, taxrule, membership_type):
|
||||
resp = token_client.post(
|
||||
@@ -1072,6 +1092,57 @@ def test_item_create_with_bundle(token_client, organizer, event, item, category,
|
||||
assert resp.content.decode() == '{"bundles":["The chosen variation does not belong to this item."]}'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_item_create_with_product_time(token_client, organizer, event, item, category, taxrule):
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/items/'.format(organizer.slug, event.slug),
|
||||
{
|
||||
"category": category.pk,
|
||||
"name": {
|
||||
"en": "Ticket"
|
||||
},
|
||||
"active": True,
|
||||
"description": None,
|
||||
"default_price": "23.00",
|
||||
"free_price": False,
|
||||
"tax_rate": "19.00",
|
||||
"tax_rule": taxrule.pk,
|
||||
"admission": True,
|
||||
"issue_giftcard": False,
|
||||
"position": 0,
|
||||
"picture": None,
|
||||
"available_from": None,
|
||||
"available_until": None,
|
||||
"require_voucher": False,
|
||||
"hide_without_voucher": False,
|
||||
"allow_cancel": True,
|
||||
"min_per_order": None,
|
||||
"max_per_order": None,
|
||||
"checkin_attention": False,
|
||||
"checkin_text": None,
|
||||
"has_variations": False,
|
||||
"program_times": [
|
||||
{
|
||||
"start": "2017-12-27T00:00:00Z",
|
||||
"end": "2017-12-28T00:00:00Z",
|
||||
},
|
||||
{
|
||||
"start": "2017-12-29T00:00:00Z",
|
||||
"end": "2017-12-30T00:00:00Z",
|
||||
}
|
||||
]
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
with scopes_disabled():
|
||||
new_item = Item.objects.get(pk=resp.data['id'])
|
||||
assert new_item.program_times.first().start == datetime(2017, 12, 27, 0, 0, 0, tzinfo=timezone.utc)
|
||||
assert new_item.program_times.first().end == datetime(2017, 12, 28, 0, 0, 0, tzinfo=timezone.utc)
|
||||
assert new_item.program_times.last().start == datetime(2017, 12, 29, 0, 0, 0, tzinfo=timezone.utc)
|
||||
assert new_item.program_times.last().end == datetime(2017, 12, 30, 0, 0, 0, tzinfo=timezone.utc)
|
||||
|
||||
|
||||
@pytest.mark.django_db(transaction=True)
|
||||
def test_item_update(token_client, organizer, event, item, category, item2, category2, taxrule2):
|
||||
resp = token_client.patch(
|
||||
@@ -1147,8 +1218,8 @@ def test_item_update(token_client, organizer, event, item, category, item2, cate
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"non_field_errors":["Updating add-ons, bundles, or variations via PATCH/PUT is not supported. Please use ' \
|
||||
'the dedicated nested endpoint."]}'
|
||||
assert resp.content.decode() == '{"non_field_errors":["Updating add-ons, bundles, program times or variations via ' \
|
||||
'PATCH/PUT is not supported. Please use the dedicated nested endpoint."]}'
|
||||
|
||||
resp = token_client.patch(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk),
|
||||
@@ -1165,8 +1236,8 @@ def test_item_update(token_client, organizer, event, item, category, item2, cate
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"non_field_errors":["Updating add-ons, bundles, or variations via PATCH/PUT is not supported. Please use ' \
|
||||
'the dedicated nested endpoint."]}'
|
||||
assert resp.content.decode() == '{"non_field_errors":["Updating add-ons, bundles, program times or variations via ' \
|
||||
'PATCH/PUT is not supported. Please use the dedicated nested endpoint."]}'
|
||||
|
||||
item.personalized = True
|
||||
item.admission = True
|
||||
@@ -1322,8 +1393,8 @@ def test_item_update_with_variation(token_client, organizer, event, item):
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"non_field_errors":["Updating add-ons, bundles, or variations via PATCH/PUT is not supported. Please use ' \
|
||||
'the dedicated nested endpoint."]}'
|
||||
assert resp.content.decode() == '{"non_field_errors":["Updating add-ons, bundles, program times or variations via ' \
|
||||
'PATCH/PUT is not supported. Please use the dedicated nested endpoint."]}'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -1345,8 +1416,8 @@ def test_item_update_with_addon(token_client, organizer, event, item, category):
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"non_field_errors":["Updating add-ons, bundles, or variations via PATCH/PUT is not supported. Please use ' \
|
||||
'the dedicated nested endpoint."]}'
|
||||
assert resp.content.decode() == '{"non_field_errors":["Updating add-ons, bundles, program times or variations via ' \
|
||||
'PATCH/PUT is not supported. Please use the dedicated nested endpoint."]}'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -1881,6 +1952,123 @@ def test_addons_delete(token_client, organizer, event, item, addon):
|
||||
assert not item.addons.filter(pk=addon.id).exists()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def program_time(item, category):
|
||||
return item.program_times.create(start=datetime(2017, 12, 27, 0, 0, 0, tzinfo=timezone.utc),
|
||||
end=datetime(2017, 12, 28, 0, 0, 0, tzinfo=timezone.utc))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def program_time2(item, category):
|
||||
return item.program_times.create(start=datetime(2017, 12, 29, 0, 0, 0, tzinfo=timezone.utc),
|
||||
end=datetime(2017, 12, 30, 0, 0, 0, tzinfo=timezone.utc))
|
||||
|
||||
|
||||
TEST_PROGRAM_TIMES_RES = {
|
||||
0: {
|
||||
"start": "2017-12-27T00:00:00Z",
|
||||
"end": "2017-12-28T00:00:00Z",
|
||||
},
|
||||
1: {
|
||||
"start": "2017-12-29T00:00:00Z",
|
||||
"end": "2017-12-30T00:00:00Z",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_program_times_list(token_client, organizer, event, item, program_time, program_time2):
|
||||
res = dict(TEST_PROGRAM_TIMES_RES)
|
||||
res[0]["id"] = program_time.pk
|
||||
res[1]["id"] = program_time2.pk
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/items/{}/program_times/'.format(organizer.slug, event.slug,
|
||||
item.pk))
|
||||
assert resp.status_code == 200
|
||||
assert res[0]['start'] == resp.data['results'][0]['start']
|
||||
assert res[0]['end'] == resp.data['results'][0]['end']
|
||||
assert res[0]['id'] == resp.data['results'][0]['id']
|
||||
assert res[1]['start'] == resp.data['results'][1]['start']
|
||||
assert res[1]['end'] == resp.data['results'][1]['end']
|
||||
assert res[1]['id'] == resp.data['results'][1]['id']
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_program_times_detail(token_client, organizer, event, item, program_time):
|
||||
res = dict(TEST_PROGRAM_TIMES_RES)
|
||||
res[0]["id"] = program_time.pk
|
||||
resp = token_client.get(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/program_times/{}/'.format(organizer.slug, event.slug,
|
||||
item.pk, program_time.pk))
|
||||
assert resp.status_code == 200
|
||||
assert res[0] == resp.data
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_program_times_create(token_client, organizer, event, item):
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/program_times/'.format(organizer.slug, event.slug, item.pk),
|
||||
{
|
||||
"start": "2017-12-27T00:00:00Z",
|
||||
"end": "2017-12-28T00:00:00Z"
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
with scopes_disabled():
|
||||
program_time = ItemProgramTime.objects.get(pk=resp.data['id'])
|
||||
assert datetime(2017, 12, 27, 0, 0, 0, tzinfo=timezone.utc) == program_time.start
|
||||
assert datetime(2017, 12, 28, 0, 0, 0, tzinfo=timezone.utc) == program_time.end
|
||||
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/program_times/'.format(organizer.slug, event.slug, item.pk),
|
||||
{
|
||||
"start": "2017-12-28T00:00:00Z",
|
||||
"end": "2017-12-27T00:00:00Z"
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"non_field_errors":["The program end must not be before the program start."]}'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_program_times_update(token_client, organizer, event, item, program_time):
|
||||
resp = token_client.patch(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/program_times/{}/'.format(organizer.slug, event.slug, item.pk,
|
||||
program_time.pk),
|
||||
{
|
||||
"start": "2017-12-26T00:00:00Z"
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
with scopes_disabled():
|
||||
program_time = ItemProgramTime.objects.get(pk=resp.data['id'])
|
||||
assert datetime(2017, 12, 26, 0, 0, 0, tzinfo=timezone.utc) == program_time.start
|
||||
assert datetime(2017, 12, 28, 0, 0, 0, tzinfo=timezone.utc) == program_time.end
|
||||
|
||||
resp = token_client.patch(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/program_times/{}/'.format(organizer.slug, event.slug, item.pk,
|
||||
program_time.pk),
|
||||
{
|
||||
"start": "2017-12-30T00:00:00Z"
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"non_field_errors":["The program end must not be before the program start."]}'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_program_times_delete(token_client, organizer, event, item, program_time):
|
||||
resp = token_client.delete(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/program_times/{}/'.format(organizer.slug, event.slug,
|
||||
item.pk, program_time.pk))
|
||||
assert resp.status_code == 204
|
||||
with scopes_disabled():
|
||||
assert not item.program_times.filter(pk=program_time.id).exists()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def quota(event, item):
|
||||
q = event.quotas.create(name="Budget Quota", size=200)
|
||||
|
||||
@@ -1996,7 +1996,7 @@ def test_pdf_data(token_client, organizer, event, order, django_assert_max_num_q
|
||||
assert not resp.data['positions'][0].get('pdf_data')
|
||||
|
||||
# order list
|
||||
with django_assert_max_num_queries(32):
|
||||
with django_assert_max_num_queries(33):
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/?pdf_data=true'.format(
|
||||
organizer.slug, event.slug
|
||||
))
|
||||
|
||||
@@ -39,7 +39,9 @@ import pytest
|
||||
from django.utils.timezone import now
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.models import Event, Organizer, Question, SeatingPlan
|
||||
from pretix.base.models import (
|
||||
Event, ItemProgramTime, Organizer, Question, SeatingPlan,
|
||||
)
|
||||
from pretix.base.models.items import ItemAddOn, ItemBundle, ItemMetaValue
|
||||
|
||||
|
||||
@@ -78,6 +80,10 @@ def test_full_clone_same_organizer():
|
||||
# todo: test that item pictures are copied, not linked
|
||||
ItemMetaValue.objects.create(item=item1, property=item_meta, value="Foo")
|
||||
assert item1.meta_data
|
||||
ItemProgramTime.objects.create(item=item1,
|
||||
start=datetime.datetime(2017, 12, 27, 0, 0, 0, tzinfo=datetime.timezone.utc),
|
||||
end=datetime.datetime(2017, 12, 28, 0, 0, 0, tzinfo=datetime.timezone.utc))
|
||||
assert item1.program_times
|
||||
item2 = event.items.create(category=category, tax_rule=tax_rule, name="T-shirt", default_price=15,
|
||||
hidden_if_item_available=item1)
|
||||
item2v = item2.variations.create(value="red", default_price=15, all_sales_channels=False)
|
||||
@@ -161,6 +167,8 @@ def test_full_clone_same_organizer():
|
||||
assert copied_item1.category == copied_event.categories.get(name='Tickets')
|
||||
assert copied_item1.limit_sales_channels.get() == sc
|
||||
assert copied_item1.meta_data == item1.meta_data
|
||||
assert copied_item1.program_times.first().start == item1.program_times.first().start
|
||||
assert copied_item1.program_times.first().end == item1.program_times.first().end
|
||||
assert copied_item2.variations.get().meta_data == item2v.meta_data
|
||||
assert copied_item1.hidden_if_available == copied_q2
|
||||
assert copied_item1.grant_membership_type == membership_type
|
||||
|
||||
@@ -1134,7 +1134,7 @@ class PasswordChangeRequiredTest(TestCase):
|
||||
super().setUp()
|
||||
self.user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
|
||||
|
||||
def test_redirect_to_settings(self):
|
||||
def test_redirect_to_password_change(self):
|
||||
self.user.needs_password_change = True
|
||||
self.user.save()
|
||||
self.client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
@@ -1143,9 +1143,9 @@ class PasswordChangeRequiredTest(TestCase):
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
assert self.user.needs_password_change is True
|
||||
self.assertIn('/control/settings?next=/control/events/', response['Location'])
|
||||
self.assertIn('/control/settings/password/change?next=/control/events/', response['Location'])
|
||||
|
||||
def test_redirect_to_2fa_to_settings(self):
|
||||
def test_redirect_to_2fa_to_password_change(self):
|
||||
self.user.require_2fa = True
|
||||
self.user.needs_password_change = True
|
||||
self.user.save()
|
||||
@@ -1168,4 +1168,4 @@ class PasswordChangeRequiredTest(TestCase):
|
||||
response = self.client.get('/control/events/')
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertIn('/control/settings?next=/control/events/', response['Location'])
|
||||
self.assertIn('/control/settings/password/change?next=/control/events/', response['Location'])
|
||||
|
||||
@@ -681,6 +681,10 @@ class ItemsTest(ItemFormTest):
|
||||
self.var2.save()
|
||||
prop = self.event1.item_meta_properties.create(name="Foo")
|
||||
self.item2.meta_values.create(property=prop, value="Bar")
|
||||
self.item2.program_times.create(start=datetime.datetime(2017, 12, 27, 0, 0, 0,
|
||||
tzinfo=datetime.timezone.utc),
|
||||
end=datetime.datetime(2017, 12, 28, 0, 0, 0,
|
||||
tzinfo=datetime.timezone.utc))
|
||||
|
||||
doc = self.get_doc('/control/event/%s/%s/items/add?copy_from=%d' % (self.orga1.slug, self.event1.slug, self.item2.pk))
|
||||
data = extract_form_fields(doc.select("form")[0])
|
||||
@@ -709,6 +713,8 @@ class ItemsTest(ItemFormTest):
|
||||
assert i_new.meta_data == i_old.meta_data == {"Foo": "Bar"}
|
||||
assert set(i_new.questions.all()) == set(i_old.questions.all())
|
||||
assert set([str(v.value) for v in i_new.variations.all()]) == set([str(v.value) for v in i_old.variations.all()])
|
||||
assert i_old.program_times.first().start == i_new.program_times.first().start
|
||||
assert i_old.program_times.first().end == i_new.program_times.first().end
|
||||
|
||||
def test_add_to_existing_quota(self):
|
||||
with scopes_disabled():
|
||||
|
||||
@@ -19,22 +19,11 @@
|
||||
# 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/>.
|
||||
#
|
||||
|
||||
# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of
|
||||
# the Apache License 2.0 can be obtained at <http://www.apache.org/licenses/LICENSE-2.0>.
|
||||
#
|
||||
# This file may have since been changed and any changes are released under the terms of AGPLv3 as described above. A
|
||||
# full history of changes and contributors is available at <https://github.com/pretix/pretix>.
|
||||
#
|
||||
# This file contains Apache-licensed contributions copyrighted by: Jason Estibeiro
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is
|
||||
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
import re
|
||||
import time
|
||||
|
||||
import pytest
|
||||
from django.core import mail as djmail
|
||||
from django.utils.timezone import now
|
||||
from django_otp.oath import TOTP
|
||||
from django_otp.plugins.otp_static.models import StaticDevice
|
||||
@@ -56,7 +45,7 @@ class UserSettingsTest(SoupTest):
|
||||
self.user = User.objects.create_user('dummy@dummy.dummy', 'barfoofoo')
|
||||
self.client.login(email='dummy@dummy.dummy', password='barfoofoo')
|
||||
doc = self.get_doc('/control/settings')
|
||||
self.form_data = extract_form_fields(doc.select('.container-fluid form')[0])
|
||||
self.form_data = extract_form_fields(doc.select('form[data-testid="usersettingsform"]')[0])
|
||||
|
||||
def save(self, data):
|
||||
form_data = self.form_data.copy()
|
||||
@@ -71,33 +60,107 @@ class UserSettingsTest(SoupTest):
|
||||
self.user = User.objects.get(pk=self.user.pk)
|
||||
assert self.user.fullname == 'Peter Miller'
|
||||
|
||||
def test_change_email_require_password(self):
|
||||
def test_set_locale_and_timezone(self):
|
||||
doc = self.save({
|
||||
'email': 'foo@example.com',
|
||||
'locale': 'fr',
|
||||
'timezone': 'Europe/Paris',
|
||||
})
|
||||
assert doc.select(".alert-danger")
|
||||
assert doc.select(".alert-success")
|
||||
self.user = User.objects.get(pk=self.user.pk)
|
||||
assert self.user.email == 'dummy@dummy.dummy'
|
||||
assert self.user.locale == 'fr'
|
||||
assert self.user.timezone == 'Europe/Paris'
|
||||
|
||||
|
||||
class UserEmailChangeTest(SoupTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.user = User.objects.create_user('dummy@dummy.dummy', 'barfoofoo')
|
||||
self.client.login(email='dummy@dummy.dummy', password='barfoofoo')
|
||||
session = self.client.session
|
||||
session['pretix_auth_login_time'] = int(time.time())
|
||||
session.save()
|
||||
doc = self.get_doc('/control/settings/email/change')
|
||||
self.form_data = extract_form_fields(doc.select('.container-fluid form')[0])
|
||||
|
||||
def test_require_reauth(self):
|
||||
session = self.client.session
|
||||
session['pretix_auth_login_time'] = int(time.time()) - 3600 * 2
|
||||
session.save()
|
||||
|
||||
response = self.client.get('/control/settings/email/change')
|
||||
self.assertIn('/control/reauth', response['Location'])
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
response = self.client.post('/control/reauth/?next=/control/settings/email/change', {
|
||||
'password': 'barfoofoo'
|
||||
})
|
||||
self.assertIn('/control/settings/email/change', response['Location'])
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def submit_step_1(self, data):
|
||||
form_data = self.form_data.copy()
|
||||
form_data.update(data)
|
||||
return self.post_doc('/control/settings/email/change', form_data)
|
||||
|
||||
def submit_step_2(self, data):
|
||||
form_data = self.form_data.copy()
|
||||
form_data.update(data)
|
||||
return self.post_doc('/control/settings/email/confirm?reason=email_change', form_data)
|
||||
|
||||
def test_change_email_success(self):
|
||||
doc = self.save({
|
||||
'email': 'foo@example.com',
|
||||
'old_pw': 'barfoofoo'
|
||||
djmail.outbox = []
|
||||
doc = self.submit_step_1({
|
||||
'new_email': 'foo@example.com',
|
||||
})
|
||||
assert len(djmail.outbox) == 1
|
||||
assert djmail.outbox[0].to == ['foo@example.com']
|
||||
code = re.search("[0-9]{7}", djmail.outbox[0].body).group(0)
|
||||
doc = self.submit_step_2({
|
||||
'code': code,
|
||||
})
|
||||
assert doc.select(".alert-success")
|
||||
self.user = User.objects.get(pk=self.user.pk)
|
||||
assert self.user.email == 'foo@example.com'
|
||||
|
||||
def test_change_email_no_duplicates(self):
|
||||
User.objects.create_user('foo@example.com', 'foo')
|
||||
doc = self.save({
|
||||
'email': 'foo@example.com',
|
||||
'old_pw': 'barfoofoo'
|
||||
def test_change_email_wrong_code(self):
|
||||
djmail.outbox = []
|
||||
doc = self.submit_step_1({
|
||||
'new_email': 'foo@example.com',
|
||||
})
|
||||
assert len(djmail.outbox) == 1
|
||||
assert djmail.outbox[0].to == ['foo@example.com']
|
||||
code = re.search("[0-9]{7}", djmail.outbox[0].body).group(0)
|
||||
wrong_code = '0000000' if code == '1234567' else '1234567'
|
||||
doc = self.submit_step_2({
|
||||
'code': wrong_code,
|
||||
})
|
||||
assert doc.select(".alert-danger")
|
||||
self.user = User.objects.get(pk=self.user.pk)
|
||||
assert self.user.email == 'dummy@dummy.dummy'
|
||||
|
||||
def test_change_email_no_duplicates(self):
|
||||
User.objects.create_user('foo@example.com', 'foo')
|
||||
doc = self.submit_step_1({
|
||||
'new_email': 'foo@example.com',
|
||||
})
|
||||
assert doc.select(".alert-danger")
|
||||
self.user = User.objects.get(pk=self.user.pk)
|
||||
assert self.user.email == 'dummy@dummy.dummy'
|
||||
|
||||
|
||||
class UserPasswordChangeTest(SoupTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.user = User.objects.create_user('dummy@dummy.dummy', 'barfoofoo')
|
||||
self.client.login(email='dummy@dummy.dummy', password='barfoofoo')
|
||||
doc = self.get_doc('/control/settings/password/change')
|
||||
self.form_data = extract_form_fields(doc.select('.container-fluid form')[0])
|
||||
|
||||
def save(self, data):
|
||||
form_data = self.form_data.copy()
|
||||
form_data.update(data)
|
||||
return self.post_doc('/control/settings/password/change', form_data)
|
||||
|
||||
def test_change_password_require_password(self):
|
||||
doc = self.save({
|
||||
'new_pw': 'foo',
|
||||
@@ -193,18 +256,6 @@ class UserSettingsTest(SoupTest):
|
||||
})
|
||||
assert doc.select(".alert-danger")
|
||||
|
||||
def test_needs_password_change(self):
|
||||
self.user.needs_password_change = True
|
||||
self.user.save()
|
||||
doc = self.save({
|
||||
'email': 'foo@example.com',
|
||||
'old_pw': 'barfoofoo'
|
||||
})
|
||||
assert doc.select(".alert-success")
|
||||
assert doc.select(".alert-warning")
|
||||
self.user.refresh_from_db()
|
||||
assert self.user.needs_password_change is True
|
||||
|
||||
def test_needs_password_change_changed(self):
|
||||
self.user.needs_password_change = True
|
||||
self.user.save()
|
||||
|
||||
Reference in New Issue
Block a user