mirror of
https://github.com/pretix/pretix.git
synced 2025-12-21 16:42:26 +00:00
Compare commits
25 Commits
recovery-c
...
widget-dia
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81f16dc242 | ||
|
|
aa9c478c30 | ||
|
|
847dc0f992 | ||
|
|
daaae85865 | ||
|
|
06770bcef5 | ||
|
|
dc6eae4708 | ||
|
|
bf8bb78d2a | ||
|
|
091be266fc | ||
|
|
dde655f7d6 | ||
|
|
409e64d5f2 | ||
|
|
5d67a4fa33 | ||
|
|
4eb2c50d95 | ||
|
|
a7e85a157d | ||
|
|
4c3584c788 | ||
|
|
e466c4fb72 | ||
|
|
d0d7670ca5 | ||
|
|
a17a098b15 | ||
|
|
40516ab8e0 | ||
|
|
3ca343fabc | ||
|
|
7304b7f24b | ||
|
|
abaf968103 | ||
|
|
86e2f5a155 | ||
|
|
4c64af02c1 | ||
|
|
11df4398e1 | ||
|
|
2e89fc0a94 |
@@ -92,7 +92,7 @@ dependencies = [
|
|||||||
"redis==6.4.*",
|
"redis==6.4.*",
|
||||||
"reportlab==4.4.*",
|
"reportlab==4.4.*",
|
||||||
"requests==2.32.*",
|
"requests==2.32.*",
|
||||||
"sentry-sdk==2.47.*",
|
"sentry-sdk==2.48.*",
|
||||||
"sepaxml==2.7.*",
|
"sepaxml==2.7.*",
|
||||||
"stripe==7.9.*",
|
"stripe==7.9.*",
|
||||||
"text-unidecode==1.*",
|
"text-unidecode==1.*",
|
||||||
@@ -110,7 +110,7 @@ dev = [
|
|||||||
"aiohttp==3.13.*",
|
"aiohttp==3.13.*",
|
||||||
"coverage",
|
"coverage",
|
||||||
"coveralls",
|
"coveralls",
|
||||||
"fakeredis==2.32.*",
|
"fakeredis==2.33.*",
|
||||||
"flake8==7.3.*",
|
"flake8==7.3.*",
|
||||||
"freezegun",
|
"freezegun",
|
||||||
"isort==6.1.*",
|
"isort==6.1.*",
|
||||||
|
|||||||
@@ -74,6 +74,11 @@ class ExportersMixin:
|
|||||||
@action(detail=True, methods=['GET'], url_name='download', url_path='download/(?P<asyncid>[^/]+)/(?P<cfid>[^/]+)')
|
@action(detail=True, methods=['GET'], url_name='download', url_path='download/(?P<asyncid>[^/]+)/(?P<cfid>[^/]+)')
|
||||||
def download(self, *args, **kwargs):
|
def download(self, *args, **kwargs):
|
||||||
cf = get_object_or_404(CachedFile, id=kwargs['cfid'])
|
cf = get_object_or_404(CachedFile, id=kwargs['cfid'])
|
||||||
|
if not cf.allowed_for_session(self.request, "exporters-api"):
|
||||||
|
return Response(
|
||||||
|
{'status': 'failed', 'message': 'Unknown file ID or export failed'},
|
||||||
|
status=status.HTTP_410_GONE
|
||||||
|
)
|
||||||
if cf.file:
|
if cf.file:
|
||||||
resp = ChunkBasedFileResponse(cf.file.file, content_type=cf.type)
|
resp = ChunkBasedFileResponse(cf.file.file, content_type=cf.type)
|
||||||
resp['Content-Disposition'] = 'attachment; filename="{}"'.format(cf.filename).encode("ascii", "ignore")
|
resp['Content-Disposition'] = 'attachment; filename="{}"'.format(cf.filename).encode("ascii", "ignore")
|
||||||
@@ -109,7 +114,8 @@ class ExportersMixin:
|
|||||||
serializer = JobRunSerializer(exporter=instance, data=self.request.data, **self.get_serializer_kwargs())
|
serializer = JobRunSerializer(exporter=instance, data=self.request.data, **self.get_serializer_kwargs())
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
cf = CachedFile(web_download=False)
|
cf = CachedFile(web_download=True)
|
||||||
|
cf.bind_to_session(self.request, "exporters-api")
|
||||||
cf.date = now()
|
cf.date = now()
|
||||||
cf.expires = now() + timedelta(hours=24)
|
cf.expires = now() + timedelta(hours=24)
|
||||||
cf.save()
|
cf.save()
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ StaticMapping = namedtuple('StaticMapping', ('id', 'pretix_model', 'external_obj
|
|||||||
|
|
||||||
class OutboundSyncProvider:
|
class OutboundSyncProvider:
|
||||||
max_attempts = 5
|
max_attempts = 5
|
||||||
|
list_field_joiner = "," # set to None to keep native lists in properties
|
||||||
|
|
||||||
def __init__(self, event):
|
def __init__(self, event):
|
||||||
self.event = event
|
self.event = event
|
||||||
@@ -281,7 +282,8 @@ class OutboundSyncProvider:
|
|||||||
'Please update value mapping for field "{field_name}" - option "{val}" not assigned'
|
'Please update value mapping for field "{field_name}" - option "{val}" not assigned'
|
||||||
).format(field_name=key, val=val)])
|
).format(field_name=key, val=val)])
|
||||||
|
|
||||||
val = ",".join(val)
|
if self.list_field_joiner:
|
||||||
|
val = self.list_field_joiner.join(val)
|
||||||
return val
|
return val
|
||||||
|
|
||||||
def get_properties(self, inputs: dict, property_mappings: List[dict]):
|
def get_properties(self, inputs: dict, property_mappings: List[dict]):
|
||||||
|
|||||||
@@ -71,15 +71,20 @@ def assign_properties(
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
def _add_to_list(out, field_name, current_value, new_item, list_sep):
|
def _add_to_list(out, field_name, current_value, new_item_input, list_sep):
|
||||||
new_item = str(new_item)
|
|
||||||
if list_sep is not None:
|
if list_sep is not None:
|
||||||
new_item = new_item.replace(list_sep, "")
|
new_items = str(new_item_input).split(list_sep)
|
||||||
current_value = current_value.split(list_sep) if current_value else []
|
current_value = current_value.split(list_sep) if current_value else []
|
||||||
elif not isinstance(current_value, (list, tuple)):
|
else:
|
||||||
current_value = [str(current_value)]
|
new_items = [str(new_item_input)]
|
||||||
if new_item not in current_value:
|
if not isinstance(current_value, (list, tuple)):
|
||||||
new_list = current_value + [new_item]
|
current_value = [str(current_value)]
|
||||||
|
|
||||||
|
new_list = list(current_value)
|
||||||
|
for new_item in new_items:
|
||||||
|
if new_item not in current_value:
|
||||||
|
new_list.append(new_item)
|
||||||
|
if new_list != current_value:
|
||||||
if list_sep is not None:
|
if list_sep is not None:
|
||||||
new_list = list_sep.join(new_list)
|
new_list = list_sep.join(new_list)
|
||||||
out[field_name] = new_list
|
out[field_name] = new_list
|
||||||
|
|||||||
@@ -59,6 +59,37 @@ class CachedFile(models.Model):
|
|||||||
web_download = models.BooleanField(default=True) # allow web download, True for backwards compatibility in plugins
|
web_download = models.BooleanField(default=True) # allow web download, True for backwards compatibility in plugins
|
||||||
session_key = models.TextField(null=True, blank=True) # only allow download in this session
|
session_key = models.TextField(null=True, blank=True) # only allow download in this session
|
||||||
|
|
||||||
|
def session_key_for_request(self, request, salt=None):
|
||||||
|
from ...api.models import OAuthAccessToken, OAuthApplication
|
||||||
|
from .devices import Device
|
||||||
|
from .organizer import TeamAPIToken
|
||||||
|
|
||||||
|
if hasattr(request, "auth") and isinstance(request.auth, OAuthAccessToken):
|
||||||
|
k = f'app:{request.auth.application.pk}'
|
||||||
|
elif hasattr(request, "auth") and isinstance(request.auth, OAuthApplication):
|
||||||
|
k = f'app:{request.auth.pk}'
|
||||||
|
elif hasattr(request, "auth") and isinstance(request.auth, TeamAPIToken):
|
||||||
|
k = f'token:{request.auth.pk}'
|
||||||
|
elif hasattr(request, "auth") and isinstance(request.auth, Device):
|
||||||
|
k = f'device:{request.auth.pk}'
|
||||||
|
elif request.session.session_key:
|
||||||
|
k = request.session.session_key
|
||||||
|
else:
|
||||||
|
raise ValueError("No auth method found to bind to")
|
||||||
|
|
||||||
|
if salt:
|
||||||
|
k = f"{k}!{salt}"
|
||||||
|
return k
|
||||||
|
|
||||||
|
def allowed_for_session(self, request, salt=None):
|
||||||
|
return (
|
||||||
|
not self.session_key or
|
||||||
|
self.session_key_for_request(request, salt) == self.session_key
|
||||||
|
)
|
||||||
|
|
||||||
|
def bind_to_session(self, request, salt=None):
|
||||||
|
self.session_key = self.session_key_for_request(request, salt)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, sender=CachedFile)
|
@receiver(post_delete, sender=CachedFile)
|
||||||
def cached_file_delete(sender, instance, **kwargs):
|
def cached_file_delete(sender, instance, **kwargs):
|
||||||
|
|||||||
@@ -97,6 +97,10 @@ class CartError(Exception):
|
|||||||
super().__init__(msg)
|
super().__init__(msg)
|
||||||
|
|
||||||
|
|
||||||
|
class CartPositionError(CartError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
error_messages = {
|
error_messages = {
|
||||||
'busy': gettext_lazy(
|
'busy': gettext_lazy(
|
||||||
'We were not able to process your request completely as the '
|
'We were not able to process your request completely as the '
|
||||||
@@ -106,6 +110,9 @@ error_messages = {
|
|||||||
'unknown_position': gettext_lazy('Unknown cart position.'),
|
'unknown_position': gettext_lazy('Unknown cart position.'),
|
||||||
'subevent_required': pgettext_lazy('subevent', 'No date was specified.'),
|
'subevent_required': pgettext_lazy('subevent', 'No date was specified.'),
|
||||||
'not_for_sale': gettext_lazy('You selected a product which is not available for sale.'),
|
'not_for_sale': gettext_lazy('You selected a product which is not available for sale.'),
|
||||||
|
'positions_removed': gettext_lazy(
|
||||||
|
'Some products can no longer be purchased and have been removed from your cart for the following reason: %s'
|
||||||
|
),
|
||||||
'unavailable': gettext_lazy(
|
'unavailable': gettext_lazy(
|
||||||
'Some of the products you selected are no longer available. '
|
'Some of the products you selected are no longer available. '
|
||||||
'Please see below for details.'
|
'Please see below for details.'
|
||||||
@@ -258,6 +265,138 @@ def _get_voucher_availability(event, voucher_use_diff, now_dt, exclude_position_
|
|||||||
return vouchers_ok, _voucher_depend_on_cart
|
return vouchers_ok, _voucher_depend_on_cart
|
||||||
|
|
||||||
|
|
||||||
|
def _check_position_constraints(
|
||||||
|
event: Event, item: Item, variation: ItemVariation, voucher: Voucher, subevent: SubEvent,
|
||||||
|
seat: Seat, sales_channel: SalesChannel, already_in_cart: bool, cart_is_expired: bool, real_now_dt: datetime,
|
||||||
|
item_requires_seat: bool, is_addon: bool, is_bundled: bool,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Checks if a cart position with the given constraints can still be sold. This checks configuration and time-based
|
||||||
|
constraints of item, subevent, and voucher.
|
||||||
|
|
||||||
|
It does NOT
|
||||||
|
- check if quota/voucher/seat are still available
|
||||||
|
- check prices
|
||||||
|
- check memberships
|
||||||
|
- perform any checks that go beyond the single line (like item.max_per_order)
|
||||||
|
"""
|
||||||
|
time_machine_now_dt = time_machine_now(real_now_dt)
|
||||||
|
# Item or variation disabled
|
||||||
|
# Item disabled or unavailable by time
|
||||||
|
if not item.is_available(time_machine_now_dt) or (variation and not variation.is_available(time_machine_now_dt)):
|
||||||
|
raise CartPositionError(error_messages['unavailable'])
|
||||||
|
|
||||||
|
# Invalid media policy for online sale
|
||||||
|
if item.media_policy in (Item.MEDIA_POLICY_NEW, Item.MEDIA_POLICY_REUSE_OR_NEW):
|
||||||
|
mt = MEDIA_TYPES[item.media_type]
|
||||||
|
if not mt.medium_created_by_server:
|
||||||
|
raise CartPositionError(error_messages['media_usage_not_implemented'])
|
||||||
|
elif item.media_policy == Item.MEDIA_POLICY_REUSE:
|
||||||
|
raise CartPositionError(error_messages['media_usage_not_implemented'])
|
||||||
|
|
||||||
|
# Item removed from sales channel
|
||||||
|
if not item.all_sales_channels:
|
||||||
|
if sales_channel.identifier not in (s.identifier for s in item.limit_sales_channels.all()):
|
||||||
|
raise CartPositionError(error_messages['unavailable'])
|
||||||
|
|
||||||
|
# Variation removed from sales channel
|
||||||
|
if variation and not variation.all_sales_channels:
|
||||||
|
if sales_channel.identifier not in (s.identifier for s in variation.limit_sales_channels.all()):
|
||||||
|
raise CartPositionError(error_messages['unavailable'])
|
||||||
|
|
||||||
|
# Item disabled or unavailable by time in subevent
|
||||||
|
if subevent and item.pk in subevent.item_overrides and not subevent.item_overrides[item.pk].is_available(time_machine_now_dt):
|
||||||
|
raise CartPositionError(error_messages['not_for_sale'])
|
||||||
|
|
||||||
|
# Variation disabled or unavailable by time in subevent
|
||||||
|
if subevent and variation and variation.pk in subevent.var_overrides and \
|
||||||
|
not subevent.var_overrides[variation.pk].is_available(time_machine_now_dt):
|
||||||
|
raise CartPositionError(error_messages['not_for_sale'])
|
||||||
|
|
||||||
|
# Item requires a variation (should never happen)
|
||||||
|
if item.has_variations and not variation:
|
||||||
|
raise CartPositionError(error_messages['not_for_sale'])
|
||||||
|
|
||||||
|
# Variation belongs to wrong item (should never happen)
|
||||||
|
if variation and variation.item_id != item.pk:
|
||||||
|
raise CartPositionError(error_messages['not_for_sale'])
|
||||||
|
|
||||||
|
# Voucher does not apply to product
|
||||||
|
if voucher and not voucher.applies_to(item, variation):
|
||||||
|
raise CartPositionError(error_messages['voucher_invalid_item'])
|
||||||
|
|
||||||
|
# Voucher does not apply to seat
|
||||||
|
if voucher and voucher.seat and voucher.seat != seat:
|
||||||
|
raise CartPositionError(error_messages['voucher_invalid_seat'])
|
||||||
|
|
||||||
|
# Voucher does not apply to subevent
|
||||||
|
if voucher and voucher.subevent_id and voucher.subevent_id != subevent.pk:
|
||||||
|
raise CartPositionError(error_messages['voucher_invalid_subevent'])
|
||||||
|
|
||||||
|
# Voucher expired
|
||||||
|
if voucher and voucher.valid_until and voucher.valid_until < time_machine_now_dt:
|
||||||
|
raise CartPositionError(error_messages['voucher_expired'])
|
||||||
|
|
||||||
|
# Subevent has been disabled
|
||||||
|
if subevent and not subevent.active:
|
||||||
|
raise CartPositionError(error_messages['inactive_subevent'])
|
||||||
|
|
||||||
|
# Subevent sale not started
|
||||||
|
if subevent and subevent.effective_presale_start and time_machine_now_dt < subevent.effective_presale_start:
|
||||||
|
raise CartPositionError(error_messages['not_started'])
|
||||||
|
|
||||||
|
# Subevent sale has ended
|
||||||
|
if subevent and subevent.presale_has_ended:
|
||||||
|
raise CartPositionError(error_messages['ended'])
|
||||||
|
|
||||||
|
# Payment for subevent no longer possible
|
||||||
|
if subevent:
|
||||||
|
tlv = event.settings.get('payment_term_last', as_type=RelativeDateWrapper)
|
||||||
|
if tlv:
|
||||||
|
term_last = make_aware(datetime.combine(
|
||||||
|
tlv.datetime(subevent).date(),
|
||||||
|
time(hour=23, minute=59, second=59)
|
||||||
|
), event.timezone)
|
||||||
|
if term_last < time_machine_now_dt:
|
||||||
|
raise CartPositionError(error_messages['payment_ended'])
|
||||||
|
|
||||||
|
# Seat required but no seat given
|
||||||
|
if item_requires_seat and not seat:
|
||||||
|
raise CartPositionError(error_messages['seat_invalid'])
|
||||||
|
|
||||||
|
# Seat given but no seat required
|
||||||
|
if seat and not item_requires_seat:
|
||||||
|
raise CartPositionError(error_messages['seat_forbidden'])
|
||||||
|
|
||||||
|
# Item requires to be add-on but is top-level position
|
||||||
|
if item.category and item.category.is_addon and not is_addon:
|
||||||
|
raise CartPositionError(error_messages['addon_only'])
|
||||||
|
|
||||||
|
# Item requires bundling but is top-level position
|
||||||
|
if item.require_bundling and not is_bundled:
|
||||||
|
raise CartPositionError(error_messages['bundled_only'])
|
||||||
|
|
||||||
|
# Seat for wrong product
|
||||||
|
if seat and seat.product != item:
|
||||||
|
raise CartPositionError(error_messages['seat_invalid'])
|
||||||
|
|
||||||
|
# Seat blocked
|
||||||
|
if seat and seat.blocked and sales_channel.identifier not in event.settings.seating_allow_blocked_seats_for_channel:
|
||||||
|
raise CartPositionError(error_messages['seat_invalid'])
|
||||||
|
|
||||||
|
# Item requires voucher but no voucher given
|
||||||
|
if item.require_voucher and voucher is None and not is_bundled:
|
||||||
|
raise CartPositionError(error_messages['voucher_required'])
|
||||||
|
|
||||||
|
# Item or variation is hidden without voucher but no voucher is given
|
||||||
|
if (
|
||||||
|
(item.hide_without_voucher or (variation and variation.hide_without_voucher)) and
|
||||||
|
(voucher is None or not voucher.show_hidden_items) and
|
||||||
|
not is_bundled
|
||||||
|
):
|
||||||
|
raise CartPositionError(error_messages['voucher_required'])
|
||||||
|
|
||||||
|
|
||||||
class CartManager:
|
class CartManager:
|
||||||
AddOperation = namedtuple('AddOperation', ('count', 'item', 'variation', 'voucher', 'quotas',
|
AddOperation = namedtuple('AddOperation', ('count', 'item', 'variation', 'voucher', 'quotas',
|
||||||
'addon_to', 'subevent', 'bundled', 'seat', 'listed_price',
|
'addon_to', 'subevent', 'bundled', 'seat', 'listed_price',
|
||||||
@@ -294,6 +433,7 @@ class CartManager:
|
|||||||
self._widget_data = widget_data or {}
|
self._widget_data = widget_data or {}
|
||||||
self._sales_channel = sales_channel
|
self._sales_channel = sales_channel
|
||||||
self.num_extended_positions = 0
|
self.num_extended_positions = 0
|
||||||
|
self.price_change_for_extended = False
|
||||||
|
|
||||||
if reservation_time:
|
if reservation_time:
|
||||||
self._reservation_time = reservation_time
|
self._reservation_time = reservation_time
|
||||||
@@ -421,14 +561,14 @@ class CartManager:
|
|||||||
if cartsize > limit:
|
if cartsize > limit:
|
||||||
raise CartError(error_messages['max_items'] % limit)
|
raise CartError(error_messages['max_items'] % limit)
|
||||||
|
|
||||||
def _check_item_constraints(self, op, current_ops=[]):
|
def _check_item_constraints(self, op):
|
||||||
if isinstance(op, (self.AddOperation, self.ExtendOperation)):
|
if isinstance(op, (self.AddOperation, self.ExtendOperation)):
|
||||||
if not (
|
if not (
|
||||||
(isinstance(op, self.AddOperation) and op.addon_to == 'FAKE') or
|
(isinstance(op, self.AddOperation) and op.addon_to == 'FAKE') or
|
||||||
(isinstance(op, self.ExtendOperation) and op.position.is_bundled)
|
(isinstance(op, self.ExtendOperation) and op.position.is_bundled)
|
||||||
):
|
):
|
||||||
if op.item.require_voucher and op.voucher is None:
|
if op.item.require_voucher and op.voucher is None:
|
||||||
if getattr(op, 'voucher_ignored', False):
|
if getattr(op, 'voucher_ignored', False): # todo??
|
||||||
raise CartError(error_messages['voucher_redeemed'])
|
raise CartError(error_messages['voucher_redeemed'])
|
||||||
raise CartError(error_messages['voucher_required'])
|
raise CartError(error_messages['voucher_required'])
|
||||||
|
|
||||||
@@ -440,88 +580,39 @@ class CartManager:
|
|||||||
raise CartError(error_messages['voucher_redeemed'])
|
raise CartError(error_messages['voucher_redeemed'])
|
||||||
raise CartError(error_messages['voucher_required'])
|
raise CartError(error_messages['voucher_required'])
|
||||||
|
|
||||||
if not op.item.is_available() or (op.variation and not op.variation.is_available()):
|
if op.seat and op.count > 1:
|
||||||
raise CartError(error_messages['unavailable'])
|
|
||||||
|
|
||||||
if op.item.media_policy in (Item.MEDIA_POLICY_NEW, Item.MEDIA_POLICY_REUSE_OR_NEW):
|
|
||||||
mt = MEDIA_TYPES[op.item.media_type]
|
|
||||||
if not mt.medium_created_by_server:
|
|
||||||
raise CartError(error_messages['media_usage_not_implemented'])
|
|
||||||
elif op.item.media_policy == Item.MEDIA_POLICY_REUSE:
|
|
||||||
raise CartError(error_messages['media_usage_not_implemented'])
|
|
||||||
|
|
||||||
if not op.item.all_sales_channels:
|
|
||||||
if self._sales_channel.identifier not in (s.identifier for s in op.item.limit_sales_channels.all()):
|
|
||||||
raise CartError(error_messages['unavailable'])
|
|
||||||
|
|
||||||
if op.variation and not op.variation.all_sales_channels:
|
|
||||||
if self._sales_channel.identifier not in (s.identifier for s in op.variation.limit_sales_channels.all()):
|
|
||||||
raise CartError(error_messages['unavailable'])
|
|
||||||
|
|
||||||
if op.subevent and op.item.pk in op.subevent.item_overrides and not op.subevent.item_overrides[op.item.pk].is_available():
|
|
||||||
raise CartError(error_messages['not_for_sale'])
|
|
||||||
|
|
||||||
if op.subevent and op.variation and op.variation.pk in op.subevent.var_overrides and \
|
|
||||||
not op.subevent.var_overrides[op.variation.pk].is_available():
|
|
||||||
raise CartError(error_messages['not_for_sale'])
|
|
||||||
|
|
||||||
if op.item.has_variations and not op.variation:
|
|
||||||
raise CartError(error_messages['not_for_sale'])
|
|
||||||
|
|
||||||
if op.variation and op.variation.item_id != op.item.pk:
|
|
||||||
raise CartError(error_messages['not_for_sale'])
|
|
||||||
|
|
||||||
if op.voucher and not op.voucher.applies_to(op.item, op.variation):
|
|
||||||
raise CartError(error_messages['voucher_invalid_item'])
|
|
||||||
|
|
||||||
if op.voucher and op.voucher.seat and op.voucher.seat != op.seat:
|
|
||||||
raise CartError(error_messages['voucher_invalid_seat'])
|
|
||||||
|
|
||||||
if op.voucher and op.voucher.subevent_id and op.voucher.subevent_id != op.subevent.pk:
|
|
||||||
raise CartError(error_messages['voucher_invalid_subevent'])
|
|
||||||
|
|
||||||
if op.subevent and not op.subevent.active:
|
|
||||||
raise CartError(error_messages['inactive_subevent'])
|
|
||||||
|
|
||||||
if op.subevent and op.subevent.presale_start and time_machine_now(self.real_now_dt) < op.subevent.presale_start:
|
|
||||||
raise CartError(error_messages['not_started'])
|
|
||||||
|
|
||||||
if op.subevent and op.subevent.presale_has_ended:
|
|
||||||
raise CartError(error_messages['ended'])
|
|
||||||
|
|
||||||
seated = self._is_seated(op.item, op.subevent)
|
|
||||||
if (
|
|
||||||
seated and (
|
|
||||||
not op.seat or (
|
|
||||||
op.seat.blocked and
|
|
||||||
self._sales_channel.identifier not in self.event.settings.seating_allow_blocked_seats_for_channel
|
|
||||||
)
|
|
||||||
)
|
|
||||||
):
|
|
||||||
raise CartError(error_messages['seat_invalid'])
|
|
||||||
elif op.seat and not seated:
|
|
||||||
raise CartError(error_messages['seat_forbidden'])
|
|
||||||
elif op.seat and op.seat.product != op.item:
|
|
||||||
raise CartError(error_messages['seat_invalid'])
|
|
||||||
elif op.seat and op.count > 1:
|
|
||||||
raise CartError('Invalid request: A seat can only be bought once.')
|
raise CartError('Invalid request: A seat can only be bought once.')
|
||||||
|
|
||||||
if op.subevent:
|
if isinstance(op, self.AddOperation):
|
||||||
tlv = self.event.settings.get('payment_term_last', as_type=RelativeDateWrapper)
|
is_addon = op.addon_to
|
||||||
if tlv:
|
is_bundled = op.addon_to == "FAKE"
|
||||||
term_last = make_aware(datetime.combine(
|
else:
|
||||||
tlv.datetime(op.subevent).date(),
|
is_addon = op.position.addon_to
|
||||||
time(hour=23, minute=59, second=59)
|
is_bundled = op.position.is_bundled
|
||||||
), self.event.timezone)
|
|
||||||
if term_last < time_machine_now(self.real_now_dt):
|
|
||||||
raise CartError(error_messages['payment_ended'])
|
|
||||||
|
|
||||||
if isinstance(op, self.AddOperation):
|
try:
|
||||||
if op.item.category and op.item.category.is_addon and not (op.addon_to and op.addon_to != 'FAKE'):
|
_check_position_constraints(
|
||||||
raise CartError(error_messages['addon_only'])
|
event=self.event,
|
||||||
|
item=op.item,
|
||||||
if op.item.require_bundling and not op.addon_to == 'FAKE':
|
variation=op.variation,
|
||||||
raise CartError(error_messages['bundled_only'])
|
voucher=op.voucher,
|
||||||
|
subevent=op.subevent,
|
||||||
|
seat=op.seat,
|
||||||
|
sales_channel=self._sales_channel,
|
||||||
|
already_in_cart=isinstance(op, self.ExtendOperation),
|
||||||
|
cart_is_expired=isinstance(op, self.ExtendOperation),
|
||||||
|
real_now_dt=self.real_now_dt,
|
||||||
|
item_requires_seat=self._is_seated(op.item, op.subevent),
|
||||||
|
is_addon=is_addon,
|
||||||
|
is_bundled=is_bundled,
|
||||||
|
)
|
||||||
|
# Quota, seat, and voucher availability is checked for in perform_operations
|
||||||
|
# Price changes are checked for in extend_expired_positions
|
||||||
|
except CartPositionError as e:
|
||||||
|
if e.args[0] == error_messages['voucher_required'] and getattr(op, 'voucher_ignored', False):
|
||||||
|
# This is the case where someone clicks +1 on a voucher-only item with a fully redeemed voucher:
|
||||||
|
raise CartPositionError(error_messages['voucher_redeemed'])
|
||||||
|
raise
|
||||||
|
|
||||||
def _get_price(self, item: Item, variation: Optional[ItemVariation],
|
def _get_price(self, item: Item, variation: Optional[ItemVariation],
|
||||||
voucher: Optional[Voucher], custom_price: Optional[Decimal],
|
voucher: Optional[Voucher], custom_price: Optional[Decimal],
|
||||||
@@ -541,7 +632,7 @@ class CartManager:
|
|||||||
else:
|
else:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
def extend_expired_positions(self):
|
def _extend_expired_positions(self):
|
||||||
requires_seat = Exists(
|
requires_seat = Exists(
|
||||||
SeatCategoryMapping.objects.filter(
|
SeatCategoryMapping.objects.filter(
|
||||||
Q(product=OuterRef('item'))
|
Q(product=OuterRef('item'))
|
||||||
@@ -604,10 +695,14 @@ class CartManager:
|
|||||||
quotas=quotas, subevent=cp.subevent, seat=cp.seat, listed_price=listed_price,
|
quotas=quotas, subevent=cp.subevent, seat=cp.seat, listed_price=listed_price,
|
||||||
price_after_voucher=price_after_voucher,
|
price_after_voucher=price_after_voucher,
|
||||||
)
|
)
|
||||||
self._check_item_constraints(op)
|
try:
|
||||||
|
self._check_item_constraints(op)
|
||||||
|
except CartPositionError as e:
|
||||||
|
self._operations.append(self.RemoveOperation(position=cp))
|
||||||
|
err = error_messages['positions_removed'] % str(e)
|
||||||
|
|
||||||
if cp.voucher:
|
if cp.voucher:
|
||||||
self._voucher_use_diff[cp.voucher] += 2
|
self._voucher_use_diff[cp.voucher] += 1
|
||||||
|
|
||||||
self._operations.append(op)
|
self._operations.append(op)
|
||||||
return err
|
return err
|
||||||
@@ -797,7 +892,7 @@ class CartManager:
|
|||||||
custom_price_input_is_net=False,
|
custom_price_input_is_net=False,
|
||||||
voucher_ignored=False,
|
voucher_ignored=False,
|
||||||
)
|
)
|
||||||
self._check_item_constraints(bop, operations)
|
self._check_item_constraints(bop)
|
||||||
bundled.append(bop)
|
bundled.append(bop)
|
||||||
|
|
||||||
listed_price = get_listed_price(item, variation, subevent)
|
listed_price = get_listed_price(item, variation, subevent)
|
||||||
@@ -836,7 +931,7 @@ class CartManager:
|
|||||||
custom_price_input_is_net=self.event.settings.display_net_prices,
|
custom_price_input_is_net=self.event.settings.display_net_prices,
|
||||||
voucher_ignored=voucher_ignored,
|
voucher_ignored=voucher_ignored,
|
||||||
)
|
)
|
||||||
self._check_item_constraints(op, operations)
|
self._check_item_constraints(op)
|
||||||
operations.append(op)
|
operations.append(op)
|
||||||
|
|
||||||
self._quota_diff.update(quota_diff)
|
self._quota_diff.update(quota_diff)
|
||||||
@@ -975,7 +1070,7 @@ class CartManager:
|
|||||||
custom_price_input_is_net=self.event.settings.display_net_prices,
|
custom_price_input_is_net=self.event.settings.display_net_prices,
|
||||||
voucher_ignored=False,
|
voucher_ignored=False,
|
||||||
)
|
)
|
||||||
self._check_item_constraints(op, operations)
|
self._check_item_constraints(op)
|
||||||
operations.append(op)
|
operations.append(op)
|
||||||
|
|
||||||
# Check constraints on the add-on combinations
|
# Check constraints on the add-on combinations
|
||||||
@@ -1172,7 +1267,9 @@ class CartManager:
|
|||||||
op.position.delete()
|
op.position.delete()
|
||||||
|
|
||||||
elif isinstance(op, (self.AddOperation, self.ExtendOperation)):
|
elif isinstance(op, (self.AddOperation, self.ExtendOperation)):
|
||||||
# Create a CartPosition for as much items as we can
|
if isinstance(op, self.ExtendOperation) and (op.position.pk in deleted_positions or not op.position.pk):
|
||||||
|
continue # Already deleted in other operation
|
||||||
|
# Create a CartPosition for as many items as we can
|
||||||
requested_count = quota_available_count = voucher_available_count = op.count
|
requested_count = quota_available_count = voucher_available_count = op.count
|
||||||
|
|
||||||
if op.seat:
|
if op.seat:
|
||||||
@@ -1343,6 +1440,8 @@ class CartManager:
|
|||||||
addons.delete()
|
addons.delete()
|
||||||
op.position.delete()
|
op.position.delete()
|
||||||
elif available_count == 1:
|
elif available_count == 1:
|
||||||
|
if op.price_after_voucher != op.position.price_after_voucher:
|
||||||
|
self.price_change_for_extended = True
|
||||||
op.position.expires = self._expiry
|
op.position.expires = self._expiry
|
||||||
op.position.max_extend = self._max_expiry_extend
|
op.position.max_extend = self._max_expiry_extend
|
||||||
op.position.listed_price = op.listed_price
|
op.position.listed_price = op.listed_price
|
||||||
@@ -1444,15 +1543,24 @@ class CartManager:
|
|||||||
|
|
||||||
return diff
|
return diff
|
||||||
|
|
||||||
|
def _remove_parents_if_bundles_are_removed(self):
|
||||||
|
removed_positions = {op.position.pk for op in self._operations if isinstance(op, self.RemoveOperation)}
|
||||||
|
for op in self._operations:
|
||||||
|
if isinstance(op, self.RemoveOperation):
|
||||||
|
if op.position.is_bundled and op.position.addon_to_id not in removed_positions:
|
||||||
|
self._operations.append(self.RemoveOperation(position=op.position.addon_to))
|
||||||
|
removed_positions.add(op.position.addon_to_id)
|
||||||
|
|
||||||
def commit(self):
|
def commit(self):
|
||||||
self._check_presale_dates()
|
self._check_presale_dates()
|
||||||
self._check_max_cart_size()
|
self._check_max_cart_size()
|
||||||
|
|
||||||
err = self._delete_out_of_timeframe()
|
err = self._delete_out_of_timeframe()
|
||||||
err = self.extend_expired_positions() or err
|
err = self._extend_expired_positions() or err
|
||||||
err = err or self._check_min_per_voucher()
|
err = err or self._check_min_per_voucher()
|
||||||
|
|
||||||
self._extend_expiry_of_valid_existing_positions()
|
self._extend_expiry_of_valid_existing_positions()
|
||||||
|
self._remove_parents_if_bundles_are_removed()
|
||||||
err = self._perform_operations() or err
|
err = self._perform_operations() or err
|
||||||
self.recompute_final_prices_and_taxes()
|
self.recompute_final_prices_and_taxes()
|
||||||
|
|
||||||
@@ -1708,7 +1816,12 @@ def extend_cart_reservation(self, event: Event, cart_id: str=None, locale='en',
|
|||||||
try:
|
try:
|
||||||
cm = CartManager(event=event, cart_id=cart_id, sales_channel=sales_channel)
|
cm = CartManager(event=event, cart_id=cart_id, sales_channel=sales_channel)
|
||||||
cm.commit()
|
cm.commit()
|
||||||
return {"success": cm.num_extended_positions, "expiry": cm._expiry, "max_expiry_extend": cm._max_expiry_extend}
|
return {
|
||||||
|
"success": cm.num_extended_positions,
|
||||||
|
"expiry": cm._expiry,
|
||||||
|
"max_expiry_extend": cm._max_expiry_extend,
|
||||||
|
"price_changed": cm.price_change_for_extended,
|
||||||
|
}
|
||||||
except LockTimeoutException:
|
except LockTimeoutException:
|
||||||
self.retry()
|
self.retry()
|
||||||
except (MaxRetriesExceededError, LockTimeoutException):
|
except (MaxRetriesExceededError, LockTimeoutException):
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ from pretix.base.models.tax import TAXED_ZERO, TaxedPrice, TaxRule
|
|||||||
from pretix.base.payment import GiftCardPayment, PaymentException
|
from pretix.base.payment import GiftCardPayment, PaymentException
|
||||||
from pretix.base.reldate import RelativeDateWrapper
|
from pretix.base.reldate import RelativeDateWrapper
|
||||||
from pretix.base.secrets import assign_ticket_secret
|
from pretix.base.secrets import assign_ticket_secret
|
||||||
from pretix.base.services import tickets
|
from pretix.base.services import cart, tickets
|
||||||
from pretix.base.services.invoices import (
|
from pretix.base.services.invoices import (
|
||||||
generate_cancellation, generate_invoice, invoice_qualified,
|
generate_cancellation, generate_invoice, invoice_qualified,
|
||||||
invoice_transmission_separately, order_invoice_transmission_separately,
|
invoice_transmission_separately, order_invoice_transmission_separately,
|
||||||
@@ -130,6 +130,9 @@ class OrderError(Exception):
|
|||||||
|
|
||||||
|
|
||||||
error_messages = {
|
error_messages = {
|
||||||
|
'positions_removed': gettext_lazy(
|
||||||
|
'Some products can no longer be purchased and have been removed from your cart for the following reason: %s'
|
||||||
|
),
|
||||||
'unavailable': gettext_lazy(
|
'unavailable': gettext_lazy(
|
||||||
'Some of the products you selected were no longer available. '
|
'Some of the products you selected were no longer available. '
|
||||||
'Please see below for details.'
|
'Please see below for details.'
|
||||||
@@ -182,14 +185,6 @@ error_messages = {
|
|||||||
'The voucher code used for one of the items in your cart is not valid for this item. We removed this item from your cart.'
|
'The voucher code used for one of the items in your cart is not valid for this item. We removed this item from your cart.'
|
||||||
),
|
),
|
||||||
'voucher_required': gettext_lazy('You need a valid voucher code to order one of the products.'),
|
'voucher_required': gettext_lazy('You need a valid voucher code to order one of the products.'),
|
||||||
'some_subevent_not_started': gettext_lazy(
|
|
||||||
'The booking period for one of the events in your cart has not yet started. The '
|
|
||||||
'affected positions have been removed from your cart.'
|
|
||||||
),
|
|
||||||
'some_subevent_ended': gettext_lazy(
|
|
||||||
'The booking period for one of the events in your cart has ended. The affected '
|
|
||||||
'positions have been removed from your cart.'
|
|
||||||
),
|
|
||||||
'seat_invalid': gettext_lazy('One of the seats in your order was invalid, we removed the position from your cart.'),
|
'seat_invalid': gettext_lazy('One of the seats in your order was invalid, we removed the position from your cart.'),
|
||||||
'seat_unavailable': gettext_lazy('One of the seats in your order has been taken in the meantime, we removed the position from your cart.'),
|
'seat_unavailable': gettext_lazy('One of the seats in your order has been taken in the meantime, we removed the position from your cart.'),
|
||||||
'country_blocked': gettext_lazy('One of the selected products is not available in the selected country.'),
|
'country_blocked': gettext_lazy('One of the selected products is not available in the selected country.'),
|
||||||
@@ -744,12 +739,37 @@ def _check_positions(event: Event, now_dt: datetime, time_machine_now_dt: dateti
|
|||||||
deleted_positions.add(cp.pk)
|
deleted_positions.add(cp.pk)
|
||||||
cp.delete()
|
cp.delete()
|
||||||
|
|
||||||
sorted_positions = sorted(positions, key=lambda c: (-int(c.is_bundled), c.pk))
|
sorted_positions = list(sorted(positions, key=lambda c: (-int(c.is_bundled), c.pk)))
|
||||||
|
|
||||||
for cp in sorted_positions:
|
for cp in sorted_positions:
|
||||||
cp._cached_quotas = list(cp.quotas)
|
cp._cached_quotas = list(cp.quotas)
|
||||||
|
|
||||||
|
for cp in sorted_positions:
|
||||||
|
try:
|
||||||
|
cart._check_position_constraints(
|
||||||
|
event=event,
|
||||||
|
item=cp.item,
|
||||||
|
variation=cp.variation,
|
||||||
|
voucher=cp.voucher,
|
||||||
|
subevent=cp.subevent,
|
||||||
|
seat=cp.seat,
|
||||||
|
sales_channel=sales_channel,
|
||||||
|
already_in_cart=True,
|
||||||
|
cart_is_expired=cp.expires < now_dt,
|
||||||
|
real_now_dt=now_dt,
|
||||||
|
item_requires_seat=cp.requires_seat,
|
||||||
|
is_addon=bool(cp.addon_to_id),
|
||||||
|
is_bundled=bool(cp.addon_to_id) and cp.is_bundled,
|
||||||
|
)
|
||||||
|
# Quota, seat, and voucher availability is checked for below
|
||||||
|
# Prices are checked for below
|
||||||
|
# Memberships are checked in _create_order
|
||||||
|
except cart.CartPositionError as e:
|
||||||
|
err = error_messages['positions_removed'] % str(e)
|
||||||
|
delete(cp)
|
||||||
|
|
||||||
# Create locks
|
# Create locks
|
||||||
|
sorted_positions = [cp for cp in sorted_positions if cp.pk and cp.pk not in deleted_positions] # eliminate deleted
|
||||||
if any(cp.expires < now() + timedelta(seconds=LOCK_TRUST_WINDOW) for cp in sorted_positions):
|
if any(cp.expires < now() + timedelta(seconds=LOCK_TRUST_WINDOW) for cp in sorted_positions):
|
||||||
# No need to perform any locking if the cart positions still guarantee everything long enough.
|
# No need to perform any locking if the cart positions still guarantee everything long enough.
|
||||||
full_lock_required = any(
|
full_lock_required = any(
|
||||||
@@ -774,15 +794,12 @@ def _check_positions(event: Event, now_dt: datetime, time_machine_now_dt: dateti
|
|||||||
|
|
||||||
# Check availability
|
# Check availability
|
||||||
for i, cp in enumerate(sorted_positions):
|
for i, cp in enumerate(sorted_positions):
|
||||||
if cp.pk in deleted_positions:
|
if cp.pk in deleted_positions or not cp.pk:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not cp.item.is_available() or (cp.variation and not cp.variation.is_available()):
|
|
||||||
err = err or error_messages['unavailable']
|
|
||||||
delete(cp)
|
|
||||||
continue
|
|
||||||
quotas = cp._cached_quotas
|
quotas = cp._cached_quotas
|
||||||
|
|
||||||
|
# Product per order limits
|
||||||
products_seen[cp.item] += 1
|
products_seen[cp.item] += 1
|
||||||
if cp.item.max_per_order and products_seen[cp.item] > cp.item.max_per_order:
|
if cp.item.max_per_order and products_seen[cp.item] > cp.item.max_per_order:
|
||||||
err = error_messages['max_items_per_product'] % {
|
err = error_messages['max_items_per_product'] % {
|
||||||
@@ -792,6 +809,7 @@ def _check_positions(event: Event, now_dt: datetime, time_machine_now_dt: dateti
|
|||||||
delete(cp)
|
delete(cp)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# Voucher availability
|
||||||
if cp.voucher:
|
if cp.voucher:
|
||||||
v_usages[cp.voucher] += 1
|
v_usages[cp.voucher] += 1
|
||||||
if cp.voucher not in v_avail:
|
if cp.voucher not in v_avail:
|
||||||
@@ -806,48 +824,14 @@ def _check_positions(event: Event, now_dt: datetime, time_machine_now_dt: dateti
|
|||||||
delete(cp)
|
delete(cp)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if cp.subevent and cp.subevent.presale_start and time_machine_now_dt < cp.subevent.presale_start:
|
# Check duplicate seats in order
|
||||||
err = err or error_messages['some_subevent_not_started']
|
if cp.seat in seats_seen:
|
||||||
delete(cp)
|
|
||||||
break
|
|
||||||
|
|
||||||
if cp.subevent:
|
|
||||||
tlv = event.settings.get('payment_term_last', as_type=RelativeDateWrapper)
|
|
||||||
if tlv:
|
|
||||||
term_last = make_aware(datetime.combine(
|
|
||||||
tlv.datetime(cp.subevent).date(),
|
|
||||||
time(hour=23, minute=59, second=59)
|
|
||||||
), event.timezone)
|
|
||||||
if term_last < time_machine_now_dt:
|
|
||||||
err = err or error_messages['some_subevent_ended']
|
|
||||||
delete(cp)
|
|
||||||
break
|
|
||||||
|
|
||||||
if cp.subevent and cp.subevent.presale_has_ended:
|
|
||||||
err = err or error_messages['some_subevent_ended']
|
|
||||||
delete(cp)
|
|
||||||
break
|
|
||||||
|
|
||||||
if (cp.requires_seat and not cp.seat) or (cp.seat and not cp.requires_seat) or (cp.seat and cp.seat.product != cp.item) or cp.seat in seats_seen:
|
|
||||||
err = err or error_messages['seat_invalid']
|
err = err or error_messages['seat_invalid']
|
||||||
delete(cp)
|
delete(cp)
|
||||||
break
|
break
|
||||||
|
|
||||||
if cp.seat:
|
if cp.seat:
|
||||||
seats_seen.add(cp.seat)
|
seats_seen.add(cp.seat)
|
||||||
|
|
||||||
if cp.item.require_voucher and cp.voucher is None and not cp.is_bundled:
|
|
||||||
delete(cp)
|
|
||||||
err = err or error_messages['voucher_required']
|
|
||||||
break
|
|
||||||
|
|
||||||
if (cp.item.hide_without_voucher or (cp.variation and cp.variation.hide_without_voucher)) and (
|
|
||||||
cp.voucher is None or not cp.voucher.show_hidden_items or not cp.voucher.applies_to(cp.item, cp.variation)
|
|
||||||
) and not cp.is_bundled:
|
|
||||||
delete(cp)
|
|
||||||
err = error_messages['voucher_required']
|
|
||||||
break
|
|
||||||
|
|
||||||
if cp.seat:
|
|
||||||
# Unlike quotas (which we blindly trust as long as the position is not expired), we check seats every
|
# Unlike quotas (which we blindly trust as long as the position is not expired), we check seats every
|
||||||
# time, since we absolutely can not overbook a seat.
|
# time, since we absolutely can not overbook a seat.
|
||||||
if not cp.seat.is_available(ignore_cart=cp, ignore_voucher_id=cp.voucher_id, sales_channel=sales_channel.identifier):
|
if not cp.seat.is_available(ignore_cart=cp, ignore_voucher_id=cp.voucher_id, sales_channel=sales_channel.identifier):
|
||||||
@@ -855,34 +839,13 @@ def _check_positions(event: Event, now_dt: datetime, time_machine_now_dt: dateti
|
|||||||
delete(cp)
|
delete(cp)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if cp.expires >= now_dt and not cp.voucher:
|
# Check useful quota configuration
|
||||||
# Other checks are not necessary
|
|
||||||
continue
|
|
||||||
|
|
||||||
if len(quotas) == 0:
|
if len(quotas) == 0:
|
||||||
err = err or error_messages['unavailable']
|
err = err or error_messages['unavailable']
|
||||||
delete(cp)
|
delete(cp)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if cp.subevent and cp.item.pk in cp.subevent.item_overrides and not cp.subevent.item_overrides[cp.item.pk].is_available(time_machine_now_dt):
|
|
||||||
err = err or error_messages['unavailable']
|
|
||||||
delete(cp)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if cp.subevent and cp.variation and cp.variation.pk in cp.subevent.var_overrides and \
|
|
||||||
not cp.subevent.var_overrides[cp.variation.pk].is_available(time_machine_now_dt):
|
|
||||||
err = err or error_messages['unavailable']
|
|
||||||
delete(cp)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if cp.voucher:
|
|
||||||
if cp.voucher.valid_until and cp.voucher.valid_until < time_machine_now_dt:
|
|
||||||
err = err or error_messages['voucher_expired']
|
|
||||||
delete(cp)
|
|
||||||
continue
|
|
||||||
|
|
||||||
quota_ok = True
|
quota_ok = True
|
||||||
|
|
||||||
ignore_all_quotas = cp.expires >= now_dt or (
|
ignore_all_quotas = cp.expires >= now_dt or (
|
||||||
cp.voucher and (
|
cp.voucher and (
|
||||||
cp.voucher.allow_ignore_quota or (cp.voucher.block_quota and cp.voucher.quota is None)
|
cp.voucher.allow_ignore_quota or (cp.voucher.block_quota and cp.voucher.quota is None)
|
||||||
@@ -914,7 +877,7 @@ def _check_positions(event: Event, now_dt: datetime, time_machine_now_dt: dateti
|
|||||||
})
|
})
|
||||||
|
|
||||||
# Check prices
|
# Check prices
|
||||||
sorted_positions = [cp for cp in sorted_positions if cp.pk and cp.pk not in deleted_positions]
|
sorted_positions = [cp for cp in sorted_positions if cp.pk and cp.pk not in deleted_positions] # eliminate deleted
|
||||||
old_total = sum(cp.price for cp in sorted_positions)
|
old_total = sum(cp.price for cp in sorted_positions)
|
||||||
for i, cp in enumerate(sorted_positions):
|
for i, cp in enumerate(sorted_positions):
|
||||||
if cp.listed_price is None:
|
if cp.listed_price is None:
|
||||||
@@ -945,7 +908,7 @@ def _check_positions(event: Event, now_dt: datetime, time_machine_now_dt: dateti
|
|||||||
delete(cp)
|
delete(cp)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
sorted_positions = [cp for cp in sorted_positions if cp.pk and cp.pk not in deleted_positions]
|
sorted_positions = [cp for cp in sorted_positions if cp.pk and cp.pk not in deleted_positions] # eliminate deleted
|
||||||
discount_results = apply_discounts(
|
discount_results = apply_discounts(
|
||||||
event,
|
event,
|
||||||
sales_channel.identifier,
|
sales_channel.identifier,
|
||||||
|
|||||||
65
src/pretix/base/templatetags/html_time.py
Normal file
65
src/pretix/base/templatetags/html_time.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pretix (Community Edition).
|
||||||
|
#
|
||||||
|
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||||
|
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||||
|
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||||
|
#
|
||||||
|
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||||
|
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||||
|
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||||
|
# this file, see <https://pretix.eu/about/en/license>.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||||
|
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# 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/>.
|
||||||
|
#
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from django import template
|
||||||
|
from django.utils.html import format_html
|
||||||
|
from django.utils.timezone import get_current_timezone
|
||||||
|
|
||||||
|
from pretix.base.i18n import LazyExpiresDate
|
||||||
|
from pretix.helpers.templatetags.date_fast import date_fast
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def html_time(value: datetime, dt_format: str = "SHORT_DATE_FORMAT", **kwargs):
|
||||||
|
"""
|
||||||
|
Building a <time datetime='{html-datetime}'>{human-readable datetime}</time> html string,
|
||||||
|
where the html-datetime as well as the human-readable datetime can be set
|
||||||
|
to a value from django's FORMAT_SETTINGS or "format_expires".
|
||||||
|
|
||||||
|
If attr_fmt isn’t provided, it will be set to isoformat.
|
||||||
|
|
||||||
|
Usage example:
|
||||||
|
{% html_time event_start "SHORT_DATETIME_FORMAT" %}
|
||||||
|
or
|
||||||
|
{% html_time event_start "TIME_FORMAT" attr_fmt="H:i" %}
|
||||||
|
"""
|
||||||
|
if value in (None, ''):
|
||||||
|
return ''
|
||||||
|
value = value.astimezone(get_current_timezone())
|
||||||
|
attr_fmt = kwargs["attr_fmt"] if kwargs else None
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not attr_fmt:
|
||||||
|
date_html = value.isoformat()
|
||||||
|
else:
|
||||||
|
date_html = date_fast(value, attr_fmt)
|
||||||
|
|
||||||
|
if dt_format == "format_expires":
|
||||||
|
date_human = LazyExpiresDate(value)
|
||||||
|
else:
|
||||||
|
date_human = date_fast(value, dt_format)
|
||||||
|
return format_html("<time datetime='{}'>{}</time>", date_html, date_human)
|
||||||
|
except AttributeError:
|
||||||
|
return ''
|
||||||
@@ -36,9 +36,8 @@ class DownloadView(TemplateView):
|
|||||||
def object(self) -> CachedFile:
|
def object(self) -> CachedFile:
|
||||||
try:
|
try:
|
||||||
o = get_object_or_404(CachedFile, id=self.kwargs['id'], web_download=True)
|
o = get_object_or_404(CachedFile, id=self.kwargs['id'], web_download=True)
|
||||||
if o.session_key:
|
if not o.allowed_for_session(self.request):
|
||||||
if o.session_key != self.request.session.session_key:
|
raise Http404()
|
||||||
raise Http404()
|
|
||||||
return o
|
return o
|
||||||
except (ValueError, ValidationError): # Invalid URLs
|
except (ValueError, ValidationError): # Invalid URLs
|
||||||
raise Http404()
|
raise Http404()
|
||||||
|
|||||||
@@ -61,6 +61,10 @@ from pretix.base.models import (
|
|||||||
SubEvent, SubEventMetaValue, Team, TeamAPIToken, TeamInvite, Voucher,
|
SubEvent, SubEventMetaValue, Team, TeamAPIToken, TeamInvite, Voucher,
|
||||||
)
|
)
|
||||||
from pretix.base.signals import register_payment_providers
|
from pretix.base.signals import register_payment_providers
|
||||||
|
from pretix.base.timeframes import (
|
||||||
|
DateFrameField,
|
||||||
|
resolve_timeframe_to_datetime_start_inclusive_end_exclusive,
|
||||||
|
)
|
||||||
from pretix.control.forms import SplitDateTimeField
|
from pretix.control.forms import SplitDateTimeField
|
||||||
from pretix.control.forms.widgets import Select2, Select2ItemVarQuota
|
from pretix.control.forms.widgets import Select2, Select2ItemVarQuota
|
||||||
from pretix.control.signals import order_search_filter_q
|
from pretix.control.signals import order_search_filter_q
|
||||||
@@ -1219,6 +1223,129 @@ class OrderPaymentSearchFilterForm(forms.Form):
|
|||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
class QuestionAnswerFilterForm(forms.Form):
|
||||||
|
STATUS_VARIANTS = [
|
||||||
|
("", _("All orders")),
|
||||||
|
(Order.STATUS_PAID, _("Paid")),
|
||||||
|
(Order.STATUS_PAID + 'v', _("Paid or confirmed")),
|
||||||
|
(Order.STATUS_PENDING, _("Pending")),
|
||||||
|
(Order.STATUS_PENDING + Order.STATUS_PAID, _("Pending or paid")),
|
||||||
|
("o", _("Pending (overdue)")),
|
||||||
|
(Order.STATUS_EXPIRED, _("Expired")),
|
||||||
|
(Order.STATUS_PENDING + Order.STATUS_EXPIRED, _("Pending or expired")),
|
||||||
|
(Order.STATUS_CANCELED, _("Canceled"))
|
||||||
|
]
|
||||||
|
|
||||||
|
status = forms.ChoiceField(
|
||||||
|
choices=STATUS_VARIANTS,
|
||||||
|
required=False,
|
||||||
|
label=_("Order status"),
|
||||||
|
)
|
||||||
|
item = forms.ChoiceField(
|
||||||
|
choices=[],
|
||||||
|
required=False,
|
||||||
|
label=_("Products"),
|
||||||
|
)
|
||||||
|
subevent = forms.ModelChoiceField(
|
||||||
|
queryset=SubEvent.objects.none(),
|
||||||
|
required=False,
|
||||||
|
empty_label=pgettext_lazy('subevent', 'All dates'),
|
||||||
|
label=pgettext_lazy("subevent", "Date"),
|
||||||
|
)
|
||||||
|
date_range = DateFrameField(
|
||||||
|
required=False,
|
||||||
|
include_future_frames=True,
|
||||||
|
label=_('Event date'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.event = kwargs.pop('event')
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.initial['status'] = Order.STATUS_PENDING + Order.STATUS_PAID
|
||||||
|
|
||||||
|
choices = [('', _('All products'))]
|
||||||
|
for i in self.event.items.prefetch_related('variations').all():
|
||||||
|
variations = list(i.variations.all())
|
||||||
|
if variations:
|
||||||
|
choices.append((str(i.pk), _('{product} – Any variation').format(product=str(i))))
|
||||||
|
for v in variations:
|
||||||
|
choices.append(('%d-%d' % (i.pk, v.pk), '%s – %s' % (str(i), v.value)))
|
||||||
|
else:
|
||||||
|
choices.append((str(i.pk), str(i)))
|
||||||
|
self.fields['item'].choices = choices
|
||||||
|
|
||||||
|
if self.event.has_subevents:
|
||||||
|
self.fields["subevent"].queryset = self.event.subevents.all()
|
||||||
|
self.fields['subevent'].widget = Select2(
|
||||||
|
attrs={
|
||||||
|
'data-model-select2': 'event',
|
||||||
|
'data-select2-url': reverse('control:event.subevents.select2', kwargs={
|
||||||
|
'event': self.event.slug,
|
||||||
|
'organizer': self.event.organizer.slug,
|
||||||
|
}),
|
||||||
|
'data-placeholder': pgettext_lazy('subevent', 'All dates')
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.fields['subevent'].widget.choices = self.fields['subevent'].choices
|
||||||
|
else:
|
||||||
|
del self.fields['subevent']
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
cleaned_data = super().clean()
|
||||||
|
subevent = cleaned_data.get('subevent')
|
||||||
|
date_range = cleaned_data.get('date_range')
|
||||||
|
|
||||||
|
if subevent is not None and date_range is not None:
|
||||||
|
d_start, d_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(now(), date_range, self.event.timezone)
|
||||||
|
if (
|
||||||
|
(d_start and not (d_start <= subevent.date_from)) or
|
||||||
|
(d_end and not (subevent.date_from < d_end))
|
||||||
|
):
|
||||||
|
self.add_error('subevent', pgettext_lazy('subevent', "Date doesn't start in selected date range."))
|
||||||
|
return cleaned_data
|
||||||
|
|
||||||
|
def filter_qs(self, opqs):
|
||||||
|
fdata = self.cleaned_data
|
||||||
|
|
||||||
|
subevent = fdata.get('subevent', None)
|
||||||
|
date_range = fdata.get('date_range', None)
|
||||||
|
|
||||||
|
if subevent is not None:
|
||||||
|
opqs = opqs.filter(subevent=subevent)
|
||||||
|
|
||||||
|
if date_range is not None:
|
||||||
|
d_start, d_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(now(), date_range, self.event.timezone)
|
||||||
|
opqs = opqs.filter(
|
||||||
|
subevent__date_from__gte=d_start,
|
||||||
|
subevent__date_from__lt=d_end
|
||||||
|
)
|
||||||
|
|
||||||
|
s = fdata.get("status", Order.STATUS_PENDING + Order.STATUS_PAID)
|
||||||
|
if s != "":
|
||||||
|
if s == Order.STATUS_PENDING:
|
||||||
|
opqs = opqs.filter(order__status=Order.STATUS_PENDING,
|
||||||
|
order__expires__lt=now().replace(hour=0, minute=0, second=0))
|
||||||
|
elif s == Order.STATUS_PENDING + Order.STATUS_PAID:
|
||||||
|
opqs = opqs.filter(order__status__in=[Order.STATUS_PENDING, Order.STATUS_PAID])
|
||||||
|
elif s == Order.STATUS_PAID + 'v':
|
||||||
|
opqs = opqs.filter(
|
||||||
|
Q(order__status=Order.STATUS_PAID) |
|
||||||
|
Q(order__status=Order.STATUS_PENDING, order__valid_if_pending=True)
|
||||||
|
)
|
||||||
|
elif s == Order.STATUS_PENDING + Order.STATUS_EXPIRED:
|
||||||
|
opqs = opqs.filter(order__status__in=[Order.STATUS_PENDING, Order.STATUS_EXPIRED])
|
||||||
|
else:
|
||||||
|
opqs = opqs.filter(order__status=s)
|
||||||
|
|
||||||
|
if s not in (Order.STATUS_CANCELED, ""):
|
||||||
|
opqs = opqs.filter(canceled=False)
|
||||||
|
if fdata.get("item", "") != "":
|
||||||
|
i = fdata.get("item", "")
|
||||||
|
opqs = opqs.filter(item_id__in=(i,))
|
||||||
|
|
||||||
|
return opqs
|
||||||
|
|
||||||
|
|
||||||
class SubEventFilterForm(FilterForm):
|
class SubEventFilterForm(FilterForm):
|
||||||
orders = {
|
orders = {
|
||||||
'date_from': 'date_from',
|
'date_from': 'date_from',
|
||||||
|
|||||||
@@ -20,35 +20,20 @@
|
|||||||
</div>
|
</div>
|
||||||
<form class="panel-body filter-form" action="" method="get">
|
<form class="panel-body filter-form" action="" method="get">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-2 col-sm-6 col-xs-6">
|
<div class="col-md-2 col-xs-6">
|
||||||
<select name="status" class="form-control">
|
{% bootstrap_field form.status %}
|
||||||
<option value="" {% if request.GET.status == "" %}selected="selected"{% endif %}>{% trans "All orders" %}</option>
|
</div>
|
||||||
<option value="p" {% if request.GET.status == "p" %}selected="selected"{% endif %}>{% trans "Paid" %}</option>
|
<div class="col-md-3 col-xs-6">
|
||||||
<option value="pv" {% if request.GET.status == "pv" %}selected="selected"{% endif %}>{% trans "Paid or confirmed" %}</option>
|
{% bootstrap_field form.item %}
|
||||||
<option value="n" {% if request.GET.status == "n" %}selected="selected"{% endif %}>{% trans "Pending" %}</option>
|
</div>
|
||||||
<option value="np" {% if request.GET.status == "np" or "status" not in request.GET %}selected="selected"{% endif %}>{% trans "Pending or paid" %}</option>
|
{% if has_subevents %}
|
||||||
<option value="o" {% if request.GET.status == "o" %}selected="selected"{% endif %}>{% trans "Pending (overdue)" %}</option>
|
<div class="col-md-3 col-xs-6">
|
||||||
<option value="e" {% if request.GET.status == "e" %}selected="selected"{% endif %}>{% trans "Expired" %}</option>
|
{% bootstrap_field form.subevent %}
|
||||||
<option value="ne" {% if request.GET.status == "ne" %}selected="selected"{% endif %}>{% trans "Pending or expired" %}</option>
|
|
||||||
<option value="c" {% if request.GET.status == "c" %}selected="selected"{% endif %}>{% trans "Canceled" %}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-5 col-sm-6 col-xs-6">
|
<div class="col-md-4 col-xs-6">
|
||||||
<select name="item" class="form-control">
|
{% bootstrap_field form.date_range %}
|
||||||
<option value="">{% trans "All products" %}</option>
|
|
||||||
{% for item in items %}
|
|
||||||
<option value="{{ item.id }}"
|
|
||||||
{% if request.GET.item|add:0 == item.id %}selected="selected"{% endif %}>
|
|
||||||
{{ item.name }}
|
|
||||||
</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
{% if request.event.has_subevents %}
|
{% endif %}
|
||||||
<div class="col-lg-5 col-sm-6 col-xs-6">
|
|
||||||
{% include "pretixcontrol/event/fragment_subevent_choice_simple.html" %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
<button class="btn btn-primary btn-lg" type="submit">
|
<button class="btn btn-primary btn-lg" type="submit">
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ from django.views.decorators.csrf import csrf_exempt
|
|||||||
from django.views.decorators.http import require_http_methods
|
from django.views.decorators.http import require_http_methods
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
from django_otp import match_token
|
from django_otp import match_token
|
||||||
from django_otp.plugins.otp_static.models import StaticDevice
|
|
||||||
from webauthn.helpers import generate_challenge
|
from webauthn.helpers import generate_challenge
|
||||||
|
|
||||||
from pretix.base.auth import get_auth_backends
|
from pretix.base.auth import get_auth_backends
|
||||||
@@ -539,10 +538,6 @@ class Login2FAView(TemplateView):
|
|||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
valid = match_token(self.user, token)
|
valid = match_token(self.user, token)
|
||||||
if isinstance(valid, StaticDevice):
|
|
||||||
self.user.send_security_notice([
|
|
||||||
_("A recovery code for two-factor authentification was used to log in.")
|
|
||||||
])
|
|
||||||
|
|
||||||
if valid:
|
if valid:
|
||||||
logger.info(f"Backend login successful for user {self.user.pk} with 2FA.")
|
logger.info(f"Backend login successful for user {self.user.pk} with 2FA.")
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ from pretix.api.serializers.item import (
|
|||||||
)
|
)
|
||||||
from pretix.base.forms import I18nFormSet
|
from pretix.base.forms import I18nFormSet
|
||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
CartPosition, Item, ItemCategory, ItemProgramTime, ItemVariation, Order,
|
CartPosition, Item, ItemCategory, ItemProgramTime, ItemVariation,
|
||||||
OrderPosition, Question, QuestionAnswer, QuestionOption, Quota,
|
OrderPosition, Question, QuestionAnswer, QuestionOption, Quota,
|
||||||
SeatCategoryMapping, Voucher,
|
SeatCategoryMapping, Voucher,
|
||||||
)
|
)
|
||||||
@@ -74,6 +74,7 @@ from pretix.base.models.items import ItemAddOn, ItemBundle, ItemMetaValue
|
|||||||
from pretix.base.services.quotas import QuotaAvailability
|
from pretix.base.services.quotas import QuotaAvailability
|
||||||
from pretix.base.services.tickets import invalidate_cache
|
from pretix.base.services.tickets import invalidate_cache
|
||||||
from pretix.base.signals import quota_availability
|
from pretix.base.signals import quota_availability
|
||||||
|
from pretix.control.forms.filter import QuestionAnswerFilterForm
|
||||||
from pretix.control.forms.item import (
|
from pretix.control.forms.item import (
|
||||||
CategoryForm, ItemAddOnForm, ItemAddOnsFormSet, ItemBundleForm,
|
CategoryForm, ItemAddOnForm, ItemAddOnsFormSet, ItemBundleForm,
|
||||||
ItemBundleFormSet, ItemCreateForm, ItemMetaValueForm, ItemProgramTimeForm,
|
ItemBundleFormSet, ItemCreateForm, ItemMetaValueForm, ItemProgramTimeForm,
|
||||||
@@ -660,46 +661,26 @@ class QuestionMixin:
|
|||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
class QuestionView(EventPermissionRequiredMixin, QuestionMixin, ChartContainingView, DetailView):
|
class QuestionView(EventPermissionRequiredMixin, ChartContainingView, DetailView):
|
||||||
model = Question
|
model = Question
|
||||||
template_name = 'pretixcontrol/items/question.html'
|
template_name = 'pretixcontrol/items/question.html'
|
||||||
permission = 'can_change_items'
|
permission = 'can_change_items'
|
||||||
template_name_field = 'question'
|
template_name_field = 'question'
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def filter_form(self):
|
||||||
|
return QuestionAnswerFilterForm(event=self.request.event, data=self.request.GET)
|
||||||
|
|
||||||
def get_answer_statistics(self):
|
def get_answer_statistics(self):
|
||||||
opqs = OrderPosition.objects.filter(
|
opqs = OrderPosition.objects.filter(
|
||||||
order__event=self.request.event,
|
order__event=self.request.event,
|
||||||
)
|
)
|
||||||
|
if self.filter_form.is_valid():
|
||||||
|
opqs = self.filter_form.filter_qs(opqs)
|
||||||
|
|
||||||
qs = QuestionAnswer.objects.filter(
|
qs = QuestionAnswer.objects.filter(
|
||||||
question=self.object, orderposition__isnull=False,
|
question=self.object, orderposition__isnull=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.request.GET.get("subevent", "") != "":
|
|
||||||
opqs = opqs.filter(subevent=self.request.GET["subevent"])
|
|
||||||
|
|
||||||
s = self.request.GET.get("status", "np")
|
|
||||||
if s != "":
|
|
||||||
if s == 'o':
|
|
||||||
opqs = opqs.filter(order__status=Order.STATUS_PENDING,
|
|
||||||
order__expires__lt=now().replace(hour=0, minute=0, second=0))
|
|
||||||
elif s == 'np':
|
|
||||||
opqs = opqs.filter(order__status__in=[Order.STATUS_PENDING, Order.STATUS_PAID])
|
|
||||||
elif s == 'pv':
|
|
||||||
opqs = opqs.filter(
|
|
||||||
Q(order__status=Order.STATUS_PAID) |
|
|
||||||
Q(order__status=Order.STATUS_PENDING, order__valid_if_pending=True)
|
|
||||||
)
|
|
||||||
elif s == 'ne':
|
|
||||||
opqs = opqs.filter(order__status__in=[Order.STATUS_PENDING, Order.STATUS_EXPIRED])
|
|
||||||
else:
|
|
||||||
opqs = opqs.filter(order__status=s)
|
|
||||||
|
|
||||||
if s not in (Order.STATUS_CANCELED, ""):
|
|
||||||
opqs = opqs.filter(canceled=False)
|
|
||||||
if self.request.GET.get("item", "") != "":
|
|
||||||
i = self.request.GET.get("item", "")
|
|
||||||
opqs = opqs.filter(item_id__in=(i,))
|
|
||||||
|
|
||||||
qs = qs.filter(orderposition__in=opqs)
|
qs = qs.filter(orderposition__in=opqs)
|
||||||
op_cnt = opqs.filter(item__in=self.object.items.all()).count()
|
op_cnt = opqs.filter(item__in=self.object.items.all()).count()
|
||||||
|
|
||||||
@@ -746,9 +727,11 @@ class QuestionView(EventPermissionRequiredMixin, QuestionMixin, ChartContainingV
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
ctx = super().get_context_data()
|
ctx = super().get_context_data()
|
||||||
ctx['items'] = self.object.items.all()
|
ctx['items'] = self.object.items.exists()
|
||||||
|
ctx['has_subevents'] = self.request.event.has_subevents
|
||||||
stats = self.get_answer_statistics()
|
stats = self.get_answer_statistics()
|
||||||
ctx['stats'], ctx['total'] = stats
|
ctx['stats'], ctx['total'] = stats
|
||||||
|
ctx['form'] = self.filter_form
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
def get_object(self, queryset=None) -> Question:
|
def get_object(self, queryset=None) -> Question:
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ from datetime import timedelta
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
from django.http import Http404
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
@@ -85,6 +86,7 @@ class BaseImportView(TemplateView):
|
|||||||
filename='import.csv',
|
filename='import.csv',
|
||||||
type='text/csv',
|
type='text/csv',
|
||||||
)
|
)
|
||||||
|
cf.bind_to_session(request, "modelimport")
|
||||||
cf.file.save('import.csv', request.FILES['file'])
|
cf.file.save('import.csv', request.FILES['file'])
|
||||||
|
|
||||||
if self.request.POST.get("charset") in ENCODINGS:
|
if self.request.POST.get("charset") in ENCODINGS:
|
||||||
@@ -137,7 +139,10 @@ class BaseProcessView(AsyncAction, FormView):
|
|||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def file(self):
|
def file(self):
|
||||||
return get_object_or_404(CachedFile, pk=self.kwargs.get("file"), filename="import.csv")
|
cf = get_object_or_404(CachedFile, pk=self.kwargs.get("file"), filename="import.csv")
|
||||||
|
if not cf.allowed_for_session(self.request, "modelimport"):
|
||||||
|
raise Http404()
|
||||||
|
return cf
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def parsed(self):
|
def parsed(self):
|
||||||
|
|||||||
@@ -3016,7 +3016,7 @@ class EventCancel(EventPermissionRequiredMixin, AsyncAction, FormView):
|
|||||||
return _('All orders have been canceled.')
|
return _('All orders have been canceled.')
|
||||||
else:
|
else:
|
||||||
return _('The orders have been canceled. An error occurred with {count} orders, please '
|
return _('The orders have been canceled. An error occurred with {count} orders, please '
|
||||||
'check all uncanceled orders.').format(count=value)
|
'check all uncanceled orders.').format(count=value["failed"])
|
||||||
|
|
||||||
def get_success_url(self, value):
|
def get_success_url(self, value):
|
||||||
if settings.HAS_CELERY:
|
if settings.HAS_CELERY:
|
||||||
@@ -3097,7 +3097,7 @@ class EventCancelConfirm(EventPermissionRequiredMixin, AsyncAction, FormView):
|
|||||||
return _('All orders have been canceled.')
|
return _('All orders have been canceled.')
|
||||||
else:
|
else:
|
||||||
return _('The orders have been canceled. An error occurred with {count} orders, please '
|
return _('The orders have been canceled. An error occurred with {count} orders, please '
|
||||||
'check all uncanceled orders.').format(count=value)
|
'check all uncanceled orders.').format(count=value["failed"])
|
||||||
|
|
||||||
def get_success_url(self, value):
|
def get_success_url(self, value):
|
||||||
return reverse('control:event.cancel', kwargs={
|
return reverse('control:event.cancel', kwargs={
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
|
|||||||
cf = None
|
cf = None
|
||||||
if request.POST.get("background", "").strip():
|
if request.POST.get("background", "").strip():
|
||||||
try:
|
try:
|
||||||
cf = CachedFile.objects.get(id=request.POST.get("background"))
|
cf = CachedFile.objects.get(id=request.POST.get("background"), web_download=True)
|
||||||
except CachedFile.DoesNotExist:
|
except CachedFile.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ from collections import OrderedDict
|
|||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.http import Http404
|
||||||
|
from django.shortcuts import redirect
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.translation import get_language, gettext_lazy as _
|
from django.utils.translation import get_language, gettext_lazy as _
|
||||||
@@ -94,6 +95,8 @@ class ShredDownloadView(RecentAuthenticationRequiredMixin, EventPermissionRequir
|
|||||||
cf = CachedFile.objects.get(pk=kwargs['file'])
|
cf = CachedFile.objects.get(pk=kwargs['file'])
|
||||||
except CachedFile.DoesNotExist:
|
except CachedFile.DoesNotExist:
|
||||||
raise ShredError(_("The download file could no longer be found on the server, please try to start again."))
|
raise ShredError(_("The download file could no longer be found on the server, please try to start again."))
|
||||||
|
if not cf.allowed_for_session(self.request):
|
||||||
|
raise Http404()
|
||||||
|
|
||||||
with ZipFile(cf.file.file, 'r') as zipfile:
|
with ZipFile(cf.file.file, 'r') as zipfile:
|
||||||
indexdata = json.loads(zipfile.read('index.json').decode())
|
indexdata = json.loads(zipfile.read('index.json').decode())
|
||||||
@@ -111,7 +114,7 @@ class ShredDownloadView(RecentAuthenticationRequiredMixin, EventPermissionRequir
|
|||||||
ctx = super().get_context_data(**kwargs)
|
ctx = super().get_context_data(**kwargs)
|
||||||
ctx['shredders'] = self.shredders
|
ctx['shredders'] = self.shredders
|
||||||
ctx['download_on_shred'] = any(shredder.require_download_confirmation for shredder in shredders)
|
ctx['download_on_shred'] = any(shredder.require_download_confirmation for shredder in shredders)
|
||||||
ctx['file'] = get_object_or_404(CachedFile, pk=kwargs.get("file"))
|
ctx['file'] = cf
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -165,9 +165,6 @@ class UserEmergencyTokenView(AdministratorPermissionRequiredMixin, RecentAuthent
|
|||||||
d, __ = StaticDevice.objects.get_or_create(user=self.object, name='emergency')
|
d, __ = StaticDevice.objects.get_or_create(user=self.object, name='emergency')
|
||||||
token = d.token_set.create(token=get_random_string(length=12, allowed_chars='1234567890'))
|
token = d.token_set.create(token=get_random_string(length=12, allowed_chars='1234567890'))
|
||||||
self.object.log_action('pretix.user.settings.2fa.emergency', user=self.request.user)
|
self.object.log_action('pretix.user.settings.2fa.emergency', user=self.request.user)
|
||||||
self.object.send_security_notice([
|
|
||||||
_('A two-factor emergency code has been generated by a system administrator.')
|
|
||||||
])
|
|
||||||
|
|
||||||
messages.success(request, _(
|
messages.success(request, _(
|
||||||
'The emergency token for this user is "{token}". It can only be used once. Please make sure to transmit '
|
'The emergency token for this user is "{token}". It can only be used once. Please make sure to transmit '
|
||||||
|
|||||||
@@ -5,16 +5,16 @@ msgstr ""
|
|||||||
"Project-Id-Version: 1\n"
|
"Project-Id-Version: 1\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-11-27 13:57+0000\n"
|
"POT-Creation-Date: 2025-11-27 13:57+0000\n"
|
||||||
"PO-Revision-Date: 2025-11-27 14:28+0000\n"
|
"PO-Revision-Date: 2025-12-18 15:04+0000\n"
|
||||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||||
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix/de/"
|
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||||
">\n"
|
"de/>\n"
|
||||||
"Language: de\n"
|
"Language: de\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||||
"X-Generator: Weblate 5.14.3\n"
|
"X-Generator: Weblate 5.15\n"
|
||||||
"X-Poedit-Bookmarks: -1,-1,904,-1,-1,-1,-1,-1,-1,-1\n"
|
"X-Poedit-Bookmarks: -1,-1,904,-1,-1,-1,-1,-1,-1,-1\n"
|
||||||
|
|
||||||
#: pretix/_base_settings.py:87
|
#: pretix/_base_settings.py:87
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-11-27 13:57+0000\n"
|
"POT-Creation-Date: 2025-11-27 13:57+0000\n"
|
||||||
"PO-Revision-Date: 2025-12-05 18:00+0000\n"
|
"PO-Revision-Date: 2025-12-15 20:00+0000\n"
|
||||||
"Last-Translator: sandra r <sandrarial@gestiontickets.online>\n"
|
"Last-Translator: sandra r <sandrarial@gestiontickets.online>\n"
|
||||||
"Language-Team: Galician <https://translate.pretix.eu/projects/pretix/pretix/"
|
"Language-Team: Galician <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||||
"gl/>\n"
|
"gl/>\n"
|
||||||
@@ -6506,7 +6506,6 @@ msgstr "pendiente"
|
|||||||
|
|
||||||
#: pretix/base/models/orders.py:203 pretix/base/payment.py:570
|
#: pretix/base/models/orders.py:203 pretix/base/payment.py:570
|
||||||
#: pretix/base/services/invoices.py:581
|
#: pretix/base/services/invoices.py:581
|
||||||
#, fuzzy
|
|
||||||
msgid "paid"
|
msgid "paid"
|
||||||
msgstr "pagado"
|
msgstr "pagado"
|
||||||
|
|
||||||
@@ -6840,9 +6839,8 @@ msgid "This reference will be printed on your invoice for your convenience."
|
|||||||
msgstr "Esta referencia imprimirase na súa factura para a súa conveniencia."
|
msgstr "Esta referencia imprimirase na súa factura para a súa conveniencia."
|
||||||
|
|
||||||
#: pretix/base/models/orders.py:3534
|
#: pretix/base/models/orders.py:3534
|
||||||
#, fuzzy
|
|
||||||
msgid "Transmission type"
|
msgid "Transmission type"
|
||||||
msgstr "Código de transacción"
|
msgstr "Medio de contacto"
|
||||||
|
|
||||||
#: pretix/base/models/orders.py:3632
|
#: pretix/base/models/orders.py:3632
|
||||||
#: pretix/plugins/badges/templates/pretixplugins/badges/control_order_position_buttons.html:9
|
#: pretix/plugins/badges/templates/pretixplugins/badges/control_order_position_buttons.html:9
|
||||||
@@ -9295,10 +9293,10 @@ msgid "Your exported data exceeded the size limit for scheduled exports."
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/base/services/invoices.py:116
|
#: pretix/base/services/invoices.py:116
|
||||||
#, fuzzy, python-brace-format
|
#, python-brace-format
|
||||||
msgctxt "invoice"
|
msgctxt "invoice"
|
||||||
msgid "Please complete your payment before {expire_date}."
|
msgid "Please complete your payment before {expire_date}."
|
||||||
msgstr "Por favor complete su pago antes de {expire_date}."
|
msgstr "Complete o seu pago antes de {expire_date}."
|
||||||
|
|
||||||
#: pretix/base/services/invoices.py:128
|
#: pretix/base/services/invoices.py:128
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@@ -21609,9 +21607,8 @@ msgid "Placed order"
|
|||||||
msgstr "Pedido realizado"
|
msgstr "Pedido realizado"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/event/mail.html:93
|
#: pretix/control/templates/pretixcontrol/event/mail.html:93
|
||||||
#, fuzzy
|
|
||||||
msgid "Paid order"
|
msgid "Paid order"
|
||||||
msgstr "Orden de pago"
|
msgstr "Pedido pagado"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/event/mail.html:96
|
#: pretix/control/templates/pretixcontrol/event/mail.html:96
|
||||||
msgid "Free order"
|
msgid "Free order"
|
||||||
@@ -24216,9 +24213,8 @@ msgstr "Sí, aprobar la orden"
|
|||||||
#: pretix/control/templates/pretixcontrol/order/index.html:166
|
#: pretix/control/templates/pretixcontrol/order/index.html:166
|
||||||
#: pretix/presale/templates/pretixpresale/event/order.html:483
|
#: pretix/presale/templates/pretixpresale/event/order.html:483
|
||||||
#: pretix/presale/templates/pretixpresale/event/order_cancel.html:7
|
#: pretix/presale/templates/pretixpresale/event/order_cancel.html:7
|
||||||
#, fuzzy
|
|
||||||
msgid "Cancel order"
|
msgid "Cancel order"
|
||||||
msgstr "Cancelar orden"
|
msgstr "Cancelar a orde"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/order/cancel.html:12
|
#: pretix/control/templates/pretixcontrol/order/cancel.html:12
|
||||||
#: pretix/control/templates/pretixcontrol/order/deny.html:11
|
#: pretix/control/templates/pretixcontrol/order/deny.html:11
|
||||||
@@ -24978,9 +24974,8 @@ msgstr "Cambiar"
|
|||||||
#: pretix/control/templates/pretixcontrol/order/index.html:1034
|
#: pretix/control/templates/pretixcontrol/order/index.html:1034
|
||||||
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:90
|
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:90
|
||||||
#: pretix/presale/templates/pretixpresale/event/order.html:318
|
#: pretix/presale/templates/pretixpresale/event/order.html:318
|
||||||
#, fuzzy
|
|
||||||
msgid "ZIP code and city"
|
msgid "ZIP code and city"
|
||||||
msgstr "Código postal y ciudad"
|
msgstr "Código postal e cidade"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/order/index.html:1047
|
#: pretix/control/templates/pretixcontrol/order/index.html:1047
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
@@ -25437,9 +25432,8 @@ msgid "Preview refund amount"
|
|||||||
msgstr "Reembolso"
|
msgstr "Reembolso"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/orders/cancel.html:88
|
#: pretix/control/templates/pretixcontrol/orders/cancel.html:88
|
||||||
#, fuzzy
|
|
||||||
msgid "Cancel all orders"
|
msgid "Cancel all orders"
|
||||||
msgstr "Cancelar orden"
|
msgstr "Cancelar todos os pedidos"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:13
|
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:13
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
@@ -33254,9 +33248,8 @@ msgid "Payment reversed."
|
|||||||
msgstr "Pago anulado."
|
msgstr "Pago anulado."
|
||||||
|
|
||||||
#: pretix/plugins/paypal2/signals.py:62
|
#: pretix/plugins/paypal2/signals.py:62
|
||||||
#, fuzzy
|
|
||||||
msgid "Payment pending."
|
msgid "Payment pending."
|
||||||
msgstr "Pago pendiente."
|
msgstr "Pago pendente."
|
||||||
|
|
||||||
#: pretix/plugins/paypal2/signals.py:63
|
#: pretix/plugins/paypal2/signals.py:63
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
@@ -33398,9 +33391,8 @@ msgstr "Por favor, inténtalo de nuevo."
|
|||||||
|
|
||||||
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/pay.html:29
|
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/pay.html:29
|
||||||
#: pretix/presale/templates/pretixpresale/event/checkout_payment.html:57
|
#: pretix/presale/templates/pretixpresale/event/checkout_payment.html:57
|
||||||
#, fuzzy
|
|
||||||
msgid "Please select how you want to pay."
|
msgid "Please select how you want to pay."
|
||||||
msgstr "Por favor, seleccione cómo desea pagar."
|
msgstr "Por favor, seleccione cómo desexa pagar."
|
||||||
|
|
||||||
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/pending.html:10
|
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/pending.html:10
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
@@ -34550,7 +34542,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/plugins/stripe/payment.py:337
|
#: pretix/plugins/stripe/payment.py:337
|
||||||
msgid "Credit card payments"
|
msgid "Credit card payments"
|
||||||
msgstr "Pagos con cartón de crédito"
|
msgstr "Pagos con tarxeta de crédito"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/payment.py:342 pretix/plugins/stripe/payment.py:1527
|
#: pretix/plugins/stripe/payment.py:342 pretix/plugins/stripe/payment.py:1527
|
||||||
msgid "iDEAL"
|
msgid "iDEAL"
|
||||||
@@ -35103,18 +35095,12 @@ msgstr "Titular de la cuenta"
|
|||||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_simple.html:7
|
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_simple.html:7
|
||||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_simple_messaging_noform.html:13
|
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_simple_messaging_noform.html:13
|
||||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_simple_noform.html:5
|
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_simple_noform.html:5
|
||||||
#, fuzzy
|
|
||||||
#| msgid ""
|
|
||||||
#| "After you submitted your order, we will redirect you to the payment "
|
|
||||||
#| "service provider to complete your payment. You will then be redirected "
|
|
||||||
#| "back here to get your tickets."
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"After you submitted your order, we will redirect you to the payment service "
|
"After you submitted your order, we will redirect you to the payment service "
|
||||||
"provider to complete your payment. You will then be redirected back here."
|
"provider to complete your payment. You will then be redirected back here."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Despois de que enviase o seu pedido, redirixirémoslle ao provedor de "
|
"Despois de enviar o teu pedido, redirixirémoste ao provedor de servizos de "
|
||||||
"servizos de pago para completar o seu pago. A continuación, redirixiráselle "
|
"pagamento para completar o pago. Despois, serás redirixido de novo aquí."
|
||||||
"de novo aquí para obter as súas entradas."
|
|
||||||
|
|
||||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_card.html:9
|
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_card.html:9
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -35490,9 +35476,8 @@ msgid "Download tickets (PDF)"
|
|||||||
msgstr "Descargar tickets (PDF)"
|
msgstr "Descargar tickets (PDF)"
|
||||||
|
|
||||||
#: pretix/plugins/ticketoutputpdf/ticketoutput.py:66
|
#: pretix/plugins/ticketoutputpdf/ticketoutput.py:66
|
||||||
#, fuzzy
|
|
||||||
msgid "Download ticket (PDF)"
|
msgid "Download ticket (PDF)"
|
||||||
msgstr "Descargar ticket"
|
msgstr "Descargar ticket (PDF)"
|
||||||
|
|
||||||
#: pretix/plugins/ticketoutputpdf/views.py:62
|
#: pretix/plugins/ticketoutputpdf/views.py:62
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
@@ -35631,7 +35616,6 @@ msgstr ""
|
|||||||
"selecciona un método de pago."
|
"selecciona un método de pago."
|
||||||
|
|
||||||
#: pretix/presale/checkoutflow.py:1393 pretix/presale/views/order.py:679
|
#: pretix/presale/checkoutflow.py:1393 pretix/presale/views/order.py:679
|
||||||
#, fuzzy
|
|
||||||
msgid "Please select a payment method."
|
msgid "Please select a payment method."
|
||||||
msgstr "Por favor seleccione un método de pago."
|
msgstr "Por favor seleccione un método de pago."
|
||||||
|
|
||||||
@@ -36092,9 +36076,8 @@ msgid "Cart expired"
|
|||||||
msgstr "O carro da compra caducou"
|
msgstr "O carro da compra caducou"
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/checkout_base.html:36
|
#: pretix/presale/templates/pretixpresale/event/checkout_base.html:36
|
||||||
#, fuzzy
|
|
||||||
msgid "Show full cart"
|
msgid "Show full cart"
|
||||||
msgstr "Mostrar información"
|
msgstr "Mostrar o carro completo"
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/checkout_base.html:52
|
#: pretix/presale/templates/pretixpresale/event/checkout_base.html:52
|
||||||
#: pretix/presale/templates/pretixpresale/event/index.html:86
|
#: pretix/presale/templates/pretixpresale/event/index.html:86
|
||||||
@@ -36186,9 +36169,8 @@ msgstr ""
|
|||||||
"un enlace que puede utilizar para pagar."
|
"un enlace que puede utilizar para pagar."
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:215
|
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:215
|
||||||
#, fuzzy
|
|
||||||
msgid "Place binding order"
|
msgid "Place binding order"
|
||||||
msgstr "Colocar orden de compra"
|
msgstr "Realizar orde"
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:217
|
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:217
|
||||||
msgid "Submit registration"
|
msgid "Submit registration"
|
||||||
@@ -36787,9 +36769,9 @@ msgstr[0] "Unha entrada"
|
|||||||
msgstr[1] "%(num)s entradas"
|
msgstr[1] "%(num)s entradas"
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:485
|
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:485
|
||||||
#, fuzzy, python-format
|
#, python-format
|
||||||
msgid "incl. %(tax_sum)s taxes"
|
msgid "incl. %(tax_sum)s taxes"
|
||||||
msgstr "incl. %(tax_sum)s impuestos"
|
msgstr "incl. %(tax_sum)s IVE"
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:505
|
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:505
|
||||||
#, python-format
|
#, python-format
|
||||||
@@ -37112,9 +37094,8 @@ msgid "Confirmed"
|
|||||||
msgstr "Confirmado"
|
msgstr "Confirmado"
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html:15
|
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html:15
|
||||||
#, fuzzy
|
|
||||||
msgid "Payment pending"
|
msgid "Payment pending"
|
||||||
msgstr "Pago pendiente"
|
msgstr "Pago pendente"
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:21
|
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:21
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
@@ -37138,11 +37119,11 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:131
|
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:131
|
||||||
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:288
|
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:288
|
||||||
#, fuzzy, python-format
|
#, python-format
|
||||||
msgid "%(amount)s× in your cart"
|
msgid "%(amount)s× in your cart"
|
||||||
msgid_plural "%(amount)s× in your cart"
|
msgid_plural "%(amount)s× in your cart"
|
||||||
msgstr[0] "%(count)s elementos"
|
msgstr[0] "%(amount)s× no teu carro"
|
||||||
msgstr[1] "%(count)s elementos"
|
msgstr[1] "%(amount)s× no teu carro"
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:209
|
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:209
|
||||||
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:374
|
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:374
|
||||||
@@ -37373,9 +37354,9 @@ msgstr "O seu pedido foi procesado con éxito! Ver abaixo para máis detalles."
|
|||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/order.html:19
|
#: pretix/presale/templates/pretixpresale/event/order.html:19
|
||||||
#: pretix/presale/templates/pretixpresale/event/order.html:50
|
#: pretix/presale/templates/pretixpresale/event/order.html:50
|
||||||
#, fuzzy
|
|
||||||
msgid "We successfully received your payment. See below for details."
|
msgid "We successfully received your payment. See below for details."
|
||||||
msgstr "Hemos recibido con éxito su pago. Ver abajo para más detalles."
|
msgstr ""
|
||||||
|
"Recibimos o teu pagamento correctamente. Consulta os detalles a continuación."
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/order.html:35
|
#: pretix/presale/templates/pretixpresale/event/order.html:35
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
@@ -37396,24 +37377,18 @@ msgstr ""
|
|||||||
"organizador del evento antes de que pueda pagar y completar este pedido."
|
"organizador del evento antes de que pueda pagar y completar este pedido."
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/order.html:43
|
#: pretix/presale/templates/pretixpresale/event/order.html:43
|
||||||
#, fuzzy
|
|
||||||
msgid "Please note that we still await your payment to complete the process."
|
msgid "Please note that we still await your payment to complete the process."
|
||||||
msgstr "Tenga en cuenta que aún esperamos su pago para completar el proceso."
|
msgstr "Ten en conta que aínda agardamos o teu pago para completar o proceso."
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/order.html:55
|
#: pretix/presale/templates/pretixpresale/event/order.html:55
|
||||||
#, fuzzy
|
|
||||||
#| msgid ""
|
|
||||||
#| "Please bookmark or save the link to this exact page if you want to access "
|
|
||||||
#| "your order later. We also sent you an email containing the link to the "
|
|
||||||
#| "address you specified."
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Please bookmark or save the link to this exact page if you want to access "
|
"Please bookmark or save the link to this exact page if you want to access "
|
||||||
"your order later. We also sent you an email to the address you specified "
|
"your order later. We also sent you an email to the address you specified "
|
||||||
"containing the link to this page."
|
"containing the link to this page."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Por favor, marque ou garde a ligazón a esta páxina exacta se desexa acceder "
|
"Por favor, garda a ligazón a esta páxina exacta se queres acceder ao teu "
|
||||||
"ao seu pedido máis tarde. Tamén lle enviamos un correo electrónico coa "
|
"pedido máis tarde. Tamén che enviamos un correo electrónico ao enderezo que "
|
||||||
"ligazón ao enderezo que vostede especificou."
|
"especificaches coa ligazón a esta páxina."
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/order.html:59
|
#: pretix/presale/templates/pretixpresale/event/order.html:59
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
@@ -37435,9 +37410,9 @@ msgid "View in backend"
|
|||||||
msgstr "Ver en el backend"
|
msgstr "Ver en el backend"
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/order.html:91
|
#: pretix/presale/templates/pretixpresale/event/order.html:91
|
||||||
#, fuzzy, python-format
|
#, python-format
|
||||||
msgid "A payment of %(total)s is still pending for this order."
|
msgid "A payment of %(total)s is still pending for this order."
|
||||||
msgstr "Un pago de %(total)s todavía está pendiente para esta orden."
|
msgstr "Un pago de %(total)s aínda está pendente para esta orde."
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/order.html:96
|
#: pretix/presale/templates/pretixpresale/event/order.html:96
|
||||||
#, fuzzy, python-format
|
#, fuzzy, python-format
|
||||||
@@ -37546,10 +37521,9 @@ msgid "Change your order"
|
|||||||
msgstr "Cancelar orden"
|
msgstr "Cancelar orden"
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/order.html:358
|
#: pretix/presale/templates/pretixpresale/event/order.html:358
|
||||||
#, fuzzy
|
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Cancel your order"
|
msgid "Cancel your order"
|
||||||
msgstr "Cancelar orden"
|
msgstr "Cancela o teu pedido"
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/order.html:366
|
#: pretix/presale/templates/pretixpresale/event/order.html:366
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -37665,9 +37639,9 @@ msgid "Request cancellation: %(code)s"
|
|||||||
msgstr "Pedido cancelado: %(code)s"
|
msgstr "Pedido cancelado: %(code)s"
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/order_cancel.html:15
|
#: pretix/presale/templates/pretixpresale/event/order_cancel.html:15
|
||||||
#, fuzzy, python-format
|
#, python-format
|
||||||
msgid "Cancel order: %(code)s"
|
msgid "Cancel order: %(code)s"
|
||||||
msgstr "Cancelar orden: %(code)s"
|
msgstr "Cancelar orde: %(code)s"
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/order_cancel.html:38
|
#: pretix/presale/templates/pretixpresale/event/order_cancel.html:38
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -37753,14 +37727,12 @@ msgid "Modify order: %(code)s"
|
|||||||
msgstr "Modificar pedido: %(code)s"
|
msgstr "Modificar pedido: %(code)s"
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/order_modify.html:18
|
#: pretix/presale/templates/pretixpresale/event/order_modify.html:18
|
||||||
#, fuzzy
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Modifying your invoice address will not automatically generate a new "
|
"Modifying your invoice address will not automatically generate a new "
|
||||||
"invoice. Please contact us if you need a new invoice."
|
"invoice. Please contact us if you need a new invoice."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"La modificación de la dirección de facturación no generará automáticamente "
|
"A modificación do enderezo de facturación non xerará automaticamente unha "
|
||||||
"una nueva factura. Póngase en contacto con nosotros si necesita una nueva "
|
"nova factura. Póñase en contacto connosco se precisa unha nova factura."
|
||||||
"factura."
|
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/order_modify.html:88
|
#: pretix/presale/templates/pretixpresale/event/order_modify.html:88
|
||||||
#: pretix/presale/templates/pretixpresale/event/position_modify.html:49
|
#: pretix/presale/templates/pretixpresale/event/position_modify.html:49
|
||||||
@@ -38609,10 +38581,8 @@ msgid "Your cart is now empty."
|
|||||||
msgstr "Baleirouse o seu pedido."
|
msgstr "Baleirouse o seu pedido."
|
||||||
|
|
||||||
#: pretix/presale/views/cart.py:569
|
#: pretix/presale/views/cart.py:569
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Your cart has been updated."
|
|
||||||
msgid "Your cart timeout was extended."
|
msgid "Your cart timeout was extended."
|
||||||
msgstr "O seu pedido actualizouse."
|
msgstr "Ampliouse o tempo de espera do teu carro."
|
||||||
|
|
||||||
#: pretix/presale/views/cart.py:584
|
#: pretix/presale/views/cart.py:584
|
||||||
msgid "The products have been successfully added to your cart."
|
msgid "The products have been successfully added to your cart."
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-11-27 13:57+0000\n"
|
"POT-Creation-Date: 2025-11-27 13:57+0000\n"
|
||||||
"PO-Revision-Date: 2025-12-11 01:00+0000\n"
|
"PO-Revision-Date: 2025-12-18 01:00+0000\n"
|
||||||
"Last-Translator: Renne Rocha <renne@rocha.dev.br>\n"
|
"Last-Translator: Renne Rocha <renne@rocha.dev.br>\n"
|
||||||
"Language-Team: Portuguese (Brazil) <https://translate.pretix.eu/projects/"
|
"Language-Team: Portuguese (Brazil) <https://translate.pretix.eu/projects/"
|
||||||
"pretix/pretix/pt_BR/>\n"
|
"pretix/pretix/pt_BR/>\n"
|
||||||
@@ -17,7 +17,7 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||||
"X-Generator: Weblate 5.14.3\n"
|
"X-Generator: Weblate 5.15\n"
|
||||||
|
|
||||||
#: pretix/_base_settings.py:87
|
#: pretix/_base_settings.py:87
|
||||||
msgid "English"
|
msgid "English"
|
||||||
@@ -9157,6 +9157,8 @@ msgid ""
|
|||||||
"Inconsistent data in row {row}: Column {col} contains value \"{val_line}\", "
|
"Inconsistent data in row {row}: Column {col} contains value \"{val_line}\", "
|
||||||
"but for this order, the value has already been set to \"{val_order}\"."
|
"but for this order, the value has already been set to \"{val_order}\"."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Dado inconsistente na linha {row}: Coluna {col} contém o valor \"{val_line}"
|
||||||
|
"\", mas para este pedido, o valor está definido como \"{val_order}\"."
|
||||||
|
|
||||||
#: pretix/base/services/modelimport.py:168
|
#: pretix/base/services/modelimport.py:168
|
||||||
#: pretix/base/services/modelimport.py:289
|
#: pretix/base/services/modelimport.py:289
|
||||||
@@ -9622,7 +9624,7 @@ msgstr "O cupom foi enviado para {recipient}."
|
|||||||
|
|
||||||
#: pretix/base/settings.py:82
|
#: pretix/base/settings.py:82
|
||||||
msgid "Compute taxes for every line individually"
|
msgid "Compute taxes for every line individually"
|
||||||
msgstr ""
|
msgstr "Computar impostos de cada linha individualmente"
|
||||||
|
|
||||||
#: pretix/base/settings.py:83
|
#: pretix/base/settings.py:83
|
||||||
msgid "Compute taxes based on net total"
|
msgid "Compute taxes based on net total"
|
||||||
@@ -10384,7 +10386,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/base/settings.py:1139 pretix/base/settings.py:1150
|
#: pretix/base/settings.py:1139 pretix/base/settings.py:1150
|
||||||
msgid "Automatic, but prefer invoice date over event date"
|
msgid "Automatic, but prefer invoice date over event date"
|
||||||
msgstr ""
|
msgstr "Automático, mas preferir data da fatura ao invés da data do evento"
|
||||||
|
|
||||||
#: pretix/base/settings.py:1142 pretix/base/settings.py:1153
|
#: pretix/base/settings.py:1142 pretix/base/settings.py:1153
|
||||||
msgid "Invoice date"
|
msgid "Invoice date"
|
||||||
@@ -10399,6 +10401,8 @@ msgid ""
|
|||||||
"This controls what dates are shown on the invoice, but is especially "
|
"This controls what dates are shown on the invoice, but is especially "
|
||||||
"important for electronic invoicing."
|
"important for electronic invoicing."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Controla quais datas são exibidas na fatura, mas é especialmente importante "
|
||||||
|
"para faturamento eletrônico."
|
||||||
|
|
||||||
#: pretix/base/settings.py:1166
|
#: pretix/base/settings.py:1166
|
||||||
msgid "Automatically cancel and reissue invoice on address changes"
|
msgid "Automatically cancel and reissue invoice on address changes"
|
||||||
@@ -13984,6 +13988,8 @@ msgstr "Preços excluindo impostos"
|
|||||||
#: pretix/control/forms/event.py:819
|
#: pretix/control/forms/event.py:819
|
||||||
msgid "Recommended only if you sell tickets primarily to business customers."
|
msgid "Recommended only if you sell tickets primarily to business customers."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Recomendado apenas se você vende ingressos majoritariamente para clientes "
|
||||||
|
"corporativos."
|
||||||
|
|
||||||
#: pretix/control/forms/event.py:855
|
#: pretix/control/forms/event.py:855
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-11-27 13:57+0000\n"
|
"POT-Creation-Date: 2025-11-27 13:57+0000\n"
|
||||||
"PO-Revision-Date: 2025-12-09 22:00+0000\n"
|
"PO-Revision-Date: 2025-12-14 00:00+0000\n"
|
||||||
"Last-Translator: Lachlan Struthers <lachlan.struthers@om.org>\n"
|
"Last-Translator: Lachlan Struthers <lachlan.struthers@om.org>\n"
|
||||||
"Language-Team: Albanian <https://translate.pretix.eu/projects/pretix/pretix/"
|
"Language-Team: Albanian <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||||
"sq/>\n"
|
"sq/>\n"
|
||||||
@@ -25,19 +25,19 @@ msgstr "Anglisht"
|
|||||||
|
|
||||||
#: pretix/_base_settings.py:88
|
#: pretix/_base_settings.py:88
|
||||||
msgid "German"
|
msgid "German"
|
||||||
msgstr ""
|
msgstr "Gjermanisht"
|
||||||
|
|
||||||
#: pretix/_base_settings.py:89
|
#: pretix/_base_settings.py:89
|
||||||
msgid "German (informal)"
|
msgid "German (informal)"
|
||||||
msgstr ""
|
msgstr "Gjermanisht (joformale)"
|
||||||
|
|
||||||
#: pretix/_base_settings.py:90
|
#: pretix/_base_settings.py:90
|
||||||
msgid "Arabic"
|
msgid "Arabic"
|
||||||
msgstr ""
|
msgstr "Arabisht"
|
||||||
|
|
||||||
#: pretix/_base_settings.py:91
|
#: pretix/_base_settings.py:91
|
||||||
msgid "Basque"
|
msgid "Basque"
|
||||||
msgstr ""
|
msgstr "Baskisht"
|
||||||
|
|
||||||
#: pretix/_base_settings.py:92
|
#: pretix/_base_settings.py:92
|
||||||
msgid "Catalan"
|
msgid "Catalan"
|
||||||
@@ -2954,7 +2954,7 @@ msgstr ""
|
|||||||
#: pretix/plugins/reports/accountingreport.py:105
|
#: pretix/plugins/reports/accountingreport.py:105
|
||||||
#: pretix/plugins/sendmail/templates/pretixplugins/sendmail/rule_list.html:67
|
#: pretix/plugins/sendmail/templates/pretixplugins/sendmail/rule_list.html:67
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr ""
|
msgstr "Të gjitha"
|
||||||
|
|
||||||
#: pretix/base/exporters/orderlist.py:1329 pretix/control/forms/filter.py:1450
|
#: pretix/base/exporters/orderlist.py:1329 pretix/control/forms/filter.py:1450
|
||||||
msgid "Live"
|
msgid "Live"
|
||||||
@@ -7434,7 +7434,7 @@ msgstr ""
|
|||||||
#: pretix/base/pdf.py:278 pretix/base/pdf.py:307
|
#: pretix/base/pdf.py:278 pretix/base/pdf.py:307
|
||||||
#: pretix/base/services/checkin.py:362 pretix/control/forms/filter.py:1271
|
#: pretix/base/services/checkin.py:362 pretix/control/forms/filter.py:1271
|
||||||
msgid "Friday"
|
msgid "Friday"
|
||||||
msgstr ""
|
msgstr "E Premte"
|
||||||
|
|
||||||
#: pretix/base/pdf.py:282
|
#: pretix/base/pdf.py:282
|
||||||
msgid "Event end date and time"
|
msgid "Event end date and time"
|
||||||
@@ -8132,27 +8132,27 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/base/services/checkin.py:358 pretix/control/forms/filter.py:1267
|
#: pretix/base/services/checkin.py:358 pretix/control/forms/filter.py:1267
|
||||||
msgid "Monday"
|
msgid "Monday"
|
||||||
msgstr ""
|
msgstr "E Hënë"
|
||||||
|
|
||||||
#: pretix/base/services/checkin.py:359 pretix/control/forms/filter.py:1268
|
#: pretix/base/services/checkin.py:359 pretix/control/forms/filter.py:1268
|
||||||
msgid "Tuesday"
|
msgid "Tuesday"
|
||||||
msgstr ""
|
msgstr "E Martë"
|
||||||
|
|
||||||
#: pretix/base/services/checkin.py:360 pretix/control/forms/filter.py:1269
|
#: pretix/base/services/checkin.py:360 pretix/control/forms/filter.py:1269
|
||||||
msgid "Wednesday"
|
msgid "Wednesday"
|
||||||
msgstr ""
|
msgstr "E Mërkurë"
|
||||||
|
|
||||||
#: pretix/base/services/checkin.py:361 pretix/control/forms/filter.py:1270
|
#: pretix/base/services/checkin.py:361 pretix/control/forms/filter.py:1270
|
||||||
msgid "Thursday"
|
msgid "Thursday"
|
||||||
msgstr ""
|
msgstr "E Enjte"
|
||||||
|
|
||||||
#: pretix/base/services/checkin.py:363 pretix/control/forms/filter.py:1272
|
#: pretix/base/services/checkin.py:363 pretix/control/forms/filter.py:1272
|
||||||
msgid "Saturday"
|
msgid "Saturday"
|
||||||
msgstr ""
|
msgstr "E Shtunë"
|
||||||
|
|
||||||
#: pretix/base/services/checkin.py:364 pretix/control/forms/filter.py:1273
|
#: pretix/base/services/checkin.py:364 pretix/control/forms/filter.py:1273
|
||||||
msgid "Sunday"
|
msgid "Sunday"
|
||||||
msgstr ""
|
msgstr "E Diel"
|
||||||
|
|
||||||
#: pretix/base/services/checkin.py:368
|
#: pretix/base/services/checkin.py:368
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@@ -13044,7 +13044,7 @@ msgstr ""
|
|||||||
#: pretix/control/forms/filter.py:2052 pretix/control/forms/filter.py:2054
|
#: pretix/control/forms/filter.py:2052 pretix/control/forms/filter.py:2054
|
||||||
#: pretix/control/forms/filter.py:2620 pretix/control/forms/filter.py:2622
|
#: pretix/control/forms/filter.py:2620 pretix/control/forms/filter.py:2622
|
||||||
msgid "Search query"
|
msgid "Search query"
|
||||||
msgstr ""
|
msgstr "Kërkim"
|
||||||
|
|
||||||
#: pretix/control/forms/filter.py:1528 pretix/control/forms/filter.py:1600
|
#: pretix/control/forms/filter.py:1528 pretix/control/forms/filter.py:1600
|
||||||
#: pretix/control/templates/pretixcontrol/organizers/customer.html:47
|
#: pretix/control/templates/pretixcontrol/organizers/customer.html:47
|
||||||
@@ -18274,7 +18274,7 @@ msgstr ""
|
|||||||
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html:50
|
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html:50
|
||||||
#: pretix/control/templates/pretixcontrol/organizers/logs.html:80
|
#: pretix/control/templates/pretixcontrol/organizers/logs.html:80
|
||||||
msgid "No results"
|
msgid "No results"
|
||||||
msgstr ""
|
msgstr "S'ka rezultate"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/event/mail.html:7
|
#: pretix/control/templates/pretixcontrol/event/mail.html:7
|
||||||
#: pretix/control/templates/pretixcontrol/organizers/mail.html:11
|
#: pretix/control/templates/pretixcontrol/organizers/mail.html:11
|
||||||
@@ -19326,51 +19326,51 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/global_sysreport.html:16
|
#: pretix/control/templates/pretixcontrol/global_sysreport.html:16
|
||||||
msgid "January"
|
msgid "January"
|
||||||
msgstr ""
|
msgstr "Janar"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/global_sysreport.html:17
|
#: pretix/control/templates/pretixcontrol/global_sysreport.html:17
|
||||||
msgid "February"
|
msgid "February"
|
||||||
msgstr ""
|
msgstr "Shkurt"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/global_sysreport.html:18
|
#: pretix/control/templates/pretixcontrol/global_sysreport.html:18
|
||||||
msgid "March"
|
msgid "March"
|
||||||
msgstr ""
|
msgstr "Mars"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/global_sysreport.html:19
|
#: pretix/control/templates/pretixcontrol/global_sysreport.html:19
|
||||||
msgid "April"
|
msgid "April"
|
||||||
msgstr ""
|
msgstr "Prill"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/global_sysreport.html:20
|
#: pretix/control/templates/pretixcontrol/global_sysreport.html:20
|
||||||
msgid "May"
|
msgid "May"
|
||||||
msgstr ""
|
msgstr "Maj"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/global_sysreport.html:21
|
#: pretix/control/templates/pretixcontrol/global_sysreport.html:21
|
||||||
msgid "June"
|
msgid "June"
|
||||||
msgstr ""
|
msgstr "Qershor"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/global_sysreport.html:22
|
#: pretix/control/templates/pretixcontrol/global_sysreport.html:22
|
||||||
msgid "July"
|
msgid "July"
|
||||||
msgstr ""
|
msgstr "Korrik"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/global_sysreport.html:23
|
#: pretix/control/templates/pretixcontrol/global_sysreport.html:23
|
||||||
msgid "August"
|
msgid "August"
|
||||||
msgstr ""
|
msgstr "Gusht"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/global_sysreport.html:24
|
#: pretix/control/templates/pretixcontrol/global_sysreport.html:24
|
||||||
msgid "September"
|
msgid "September"
|
||||||
msgstr ""
|
msgstr "Shtator"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/global_sysreport.html:25
|
#: pretix/control/templates/pretixcontrol/global_sysreport.html:25
|
||||||
msgid "October"
|
msgid "October"
|
||||||
msgstr ""
|
msgstr "Tetor"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/global_sysreport.html:26
|
#: pretix/control/templates/pretixcontrol/global_sysreport.html:26
|
||||||
msgid "November"
|
msgid "November"
|
||||||
msgstr ""
|
msgstr "Nëntor"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/global_sysreport.html:27
|
#: pretix/control/templates/pretixcontrol/global_sysreport.html:27
|
||||||
msgid "December"
|
msgid "December"
|
||||||
msgstr ""
|
msgstr "Dhjetor"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/global_sysreport.html:32
|
#: pretix/control/templates/pretixcontrol/global_sysreport.html:32
|
||||||
msgid "Generate report"
|
msgid "Generate report"
|
||||||
@@ -20098,7 +20098,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/items/question.html:91
|
#: pretix/control/templates/pretixcontrol/items/question.html:91
|
||||||
msgid "Count"
|
msgid "Count"
|
||||||
msgstr ""
|
msgstr "Sasia"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/items/question.html:92
|
#: pretix/control/templates/pretixcontrol/items/question.html:92
|
||||||
#, python-format
|
#, python-format
|
||||||
@@ -23098,7 +23098,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/pdf/index.html:52
|
#: pretix/control/templates/pretixcontrol/pdf/index.html:52
|
||||||
msgid "Text box"
|
msgid "Text box"
|
||||||
msgstr ""
|
msgstr "Kutia teksti"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/pdf/index.html:59
|
#: pretix/control/templates/pretixcontrol/pdf/index.html:59
|
||||||
msgid "QR Code"
|
msgid "QR Code"
|
||||||
@@ -29765,7 +29765,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/plugins/ticketoutputpdf/templates/pretixplugins/ticketoutputpdf/edit.html:23
|
#: pretix/plugins/ticketoutputpdf/templates/pretixplugins/ticketoutputpdf/edit.html:23
|
||||||
msgid "Ticket design"
|
msgid "Ticket design"
|
||||||
msgstr ""
|
msgstr "Dizajni i biletës"
|
||||||
|
|
||||||
#: pretix/plugins/ticketoutputpdf/templates/pretixplugins/ticketoutputpdf/edit.html:27
|
#: pretix/plugins/ticketoutputpdf/templates/pretixplugins/ticketoutputpdf/edit.html:27
|
||||||
msgid "You can modify the design after you saved this page."
|
msgid "You can modify the design after you saved this page."
|
||||||
@@ -30328,7 +30328,7 @@ msgstr ""
|
|||||||
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:27
|
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:27
|
||||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart_box.html:18
|
#: pretix/presale/templates/pretixpresale/event/fragment_cart_box.html:18
|
||||||
msgid "Cart expired"
|
msgid "Cart expired"
|
||||||
msgstr ""
|
msgstr "Shporta juaj u skadua"
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/checkout_base.html:36
|
#: pretix/presale/templates/pretixpresale/event/checkout_base.html:36
|
||||||
msgid "Show full cart"
|
msgid "Show full cart"
|
||||||
@@ -30936,11 +30936,13 @@ msgid ""
|
|||||||
"The items in your cart are no longer reserved for you. You can still "
|
"The items in your cart are no longer reserved for you. You can still "
|
||||||
"complete your order as long as they’re available."
|
"complete your order as long as they’re available."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Sendet në shportën tuaj nuk janë të rezervuar më për ju. Ju mund t'a "
|
||||||
|
"përmbushni porosinë tuaj derisa janë ende të disponueshëm."
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:514
|
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:514
|
||||||
#: pretix/presale/templates/pretixpresale/fragment_modals.html:48
|
#: pretix/presale/templates/pretixpresale/fragment_modals.html:48
|
||||||
msgid "Renew reservation"
|
msgid "Renew reservation"
|
||||||
msgstr ""
|
msgstr "Rivendosni rezervimin"
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:526
|
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:526
|
||||||
msgid "Reservation renewed"
|
msgid "Reservation renewed"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-11-20 10:37+0000\n"
|
"POT-Creation-Date: 2025-11-20 10:37+0000\n"
|
||||||
"PO-Revision-Date: 2025-12-09 22:00+0000\n"
|
"PO-Revision-Date: 2025-12-14 00:00+0000\n"
|
||||||
"Last-Translator: Lachlan Struthers <lachlan.struthers@om.org>\n"
|
"Last-Translator: Lachlan Struthers <lachlan.struthers@om.org>\n"
|
||||||
"Language-Team: Albanian <https://translate.pretix.eu/projects/pretix/"
|
"Language-Team: Albanian <https://translate.pretix.eu/projects/pretix/"
|
||||||
"pretix-js/sq/>\n"
|
"pretix-js/sq/>\n"
|
||||||
@@ -597,356 +597,367 @@ msgstr "Kodi QR për check-in"
|
|||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/editor.js:549
|
#: pretix/static/pretixcontrol/js/ui/editor.js:549
|
||||||
msgid "The PDF background file could not be loaded for the following reason:"
|
msgid "The PDF background file could not be loaded for the following reason:"
|
||||||
msgstr ""
|
msgstr "Faili bazë PDF nuk mund të shkarkohej për këto arsye:"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/editor.js:904
|
#: pretix/static/pretixcontrol/js/ui/editor.js:904
|
||||||
msgid "Group of objects"
|
msgid "Group of objects"
|
||||||
msgstr ""
|
msgstr "Grupi i sendeve"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/editor.js:909
|
#: pretix/static/pretixcontrol/js/ui/editor.js:909
|
||||||
msgid "Text object (deprecated)"
|
msgid "Text object (deprecated)"
|
||||||
msgstr ""
|
msgstr "Objekt teksti (i dalë nga përdorimi)"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/editor.js:911
|
#: pretix/static/pretixcontrol/js/ui/editor.js:911
|
||||||
msgid "Text box"
|
msgid "Text box"
|
||||||
msgstr ""
|
msgstr "Kutia teksti"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/editor.js:913
|
#: pretix/static/pretixcontrol/js/ui/editor.js:913
|
||||||
msgid "Barcode area"
|
msgid "Barcode area"
|
||||||
msgstr ""
|
msgstr "Zona e barkodit"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/editor.js:915
|
#: pretix/static/pretixcontrol/js/ui/editor.js:915
|
||||||
msgid "Image area"
|
msgid "Image area"
|
||||||
msgstr ""
|
msgstr "Zona për imazhe"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/editor.js:917
|
#: pretix/static/pretixcontrol/js/ui/editor.js:917
|
||||||
msgid "Powered by pretix"
|
msgid "Powered by pretix"
|
||||||
msgstr ""
|
msgstr "Mundësuar nga pretix"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/editor.js:919
|
#: pretix/static/pretixcontrol/js/ui/editor.js:919
|
||||||
msgid "Object"
|
msgid "Object"
|
||||||
msgstr ""
|
msgstr "Objekt"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/editor.js:923
|
#: pretix/static/pretixcontrol/js/ui/editor.js:923
|
||||||
msgid "Ticket design"
|
msgid "Ticket design"
|
||||||
msgstr ""
|
msgstr "Dizajni i biletës"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1292
|
#: pretix/static/pretixcontrol/js/ui/editor.js:1292
|
||||||
msgid "Saving failed."
|
msgid "Saving failed."
|
||||||
msgstr ""
|
msgstr "Ruajtja nuk u krye."
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1412
|
#: pretix/static/pretixcontrol/js/ui/editor.js:1412
|
||||||
msgid "Error while uploading your PDF file, please try again."
|
msgid "Error while uploading your PDF file, please try again."
|
||||||
msgstr ""
|
msgstr "Pati gabim në ngarkimin e failit tuaj PDF, ju lutemi, provoni përsëri."
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1395
|
#: pretix/static/pretixcontrol/js/ui/editor.js:1395
|
||||||
msgid "Do you really want to leave the editor without saving your changes?"
|
msgid "Do you really want to leave the editor without saving your changes?"
|
||||||
msgstr ""
|
msgstr "A vërtetë dëshironi të dilni nga redaktori pa ruajtur ndryshimet tuaja?"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/mail.js:19
|
#: pretix/static/pretixcontrol/js/ui/mail.js:19
|
||||||
msgid "An error has occurred."
|
msgid "An error has occurred."
|
||||||
msgstr ""
|
msgstr "Një gabim ka ndodhur."
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/mail.js:52
|
#: pretix/static/pretixcontrol/js/ui/mail.js:52
|
||||||
msgid "Generating messages …"
|
msgid "Generating messages …"
|
||||||
msgstr ""
|
msgstr "Duke prodhuar mesazhe …"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:69
|
#: pretix/static/pretixcontrol/js/ui/main.js:69
|
||||||
msgid "Unknown error."
|
msgid "Unknown error."
|
||||||
msgstr ""
|
msgstr "Gabim i panjohur."
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:309
|
#: pretix/static/pretixcontrol/js/ui/main.js:309
|
||||||
msgid "Your color has great contrast and will provide excellent accessibility."
|
msgid "Your color has great contrast and will provide excellent accessibility."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Ngjyra juaj ka kontrast shumë të mirë dhe do të ofrojë aksesueshmëri të "
|
||||||
|
"shkëlqyer."
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:313
|
#: pretix/static/pretixcontrol/js/ui/main.js:313
|
||||||
msgid ""
|
msgid ""
|
||||||
"Your color has decent contrast and is sufficient for minimum accessibility "
|
"Your color has decent contrast and is sufficient for minimum accessibility "
|
||||||
"requirements."
|
"requirements."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Ngjyra juaj ka kontrast të mirë dhe përmbush kërkesat minimale të "
|
||||||
|
"aksesueshmërisë."
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:317
|
#: pretix/static/pretixcontrol/js/ui/main.js:317
|
||||||
msgid ""
|
msgid ""
|
||||||
"Your color has insufficient contrast to white. Accessibility of your site "
|
"Your color has insufficient contrast to white. Accessibility of your site "
|
||||||
"will be impacted."
|
"will be impacted."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Ngjyra juaj nuk ka kontrast të mjaftueshëm me të bardhën. Kjo mund të "
|
||||||
|
"ndikojë në aksesueshmërinë e faqes suaj."
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:443
|
#: pretix/static/pretixcontrol/js/ui/main.js:443
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:463
|
#: pretix/static/pretixcontrol/js/ui/main.js:463
|
||||||
msgid "Search query"
|
msgid "Search query"
|
||||||
msgstr ""
|
msgstr "Kërkim"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:461
|
#: pretix/static/pretixcontrol/js/ui/main.js:461
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr ""
|
msgstr "Të gjitha"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:462
|
#: pretix/static/pretixcontrol/js/ui/main.js:462
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr ""
|
msgstr "Asnjë"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:466
|
#: pretix/static/pretixcontrol/js/ui/main.js:466
|
||||||
msgid "Selected only"
|
msgid "Selected only"
|
||||||
msgstr ""
|
msgstr "Vetëm të zghedhura"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:839
|
#: pretix/static/pretixcontrol/js/ui/main.js:839
|
||||||
msgid "Enter page number between 1 and %(max)s."
|
msgid "Enter page number between 1 and %(max)s."
|
||||||
msgstr ""
|
msgstr "Shkruani numrin e faqes midis 1 dhe %(max)s."
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:842
|
#: pretix/static/pretixcontrol/js/ui/main.js:842
|
||||||
msgid "Invalid page number."
|
msgid "Invalid page number."
|
||||||
msgstr ""
|
msgstr "Numër faqe i pavlefshëm."
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:1000
|
#: pretix/static/pretixcontrol/js/ui/main.js:1000
|
||||||
msgid "Use a different name internally"
|
msgid "Use a different name internally"
|
||||||
msgstr ""
|
msgstr "Përdorni një emër tjetër brenda sistemit"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:1040
|
#: pretix/static/pretixcontrol/js/ui/main.js:1040
|
||||||
msgid "Click to close"
|
msgid "Click to close"
|
||||||
msgstr ""
|
msgstr "Shtypni për të mbyllur"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:1121
|
#: pretix/static/pretixcontrol/js/ui/main.js:1121
|
||||||
msgid "You have unsaved changes!"
|
msgid "You have unsaved changes!"
|
||||||
msgstr ""
|
msgstr "Ju keni ndryshime të paruajtur!"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/orderchange.js:25
|
#: pretix/static/pretixcontrol/js/ui/orderchange.js:25
|
||||||
msgid "Calculating default price…"
|
msgid "Calculating default price…"
|
||||||
msgstr ""
|
msgstr "Duke e llogaritur çmimin e parazgjedhur…"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/plugins.js:69
|
#: pretix/static/pretixcontrol/js/ui/plugins.js:69
|
||||||
msgid "No results"
|
msgid "No results"
|
||||||
msgstr ""
|
msgstr "S'ka rezultate"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/question.js:41
|
#: pretix/static/pretixcontrol/js/ui/question.js:41
|
||||||
msgid "Others"
|
msgid "Others"
|
||||||
msgstr ""
|
msgstr "Të tjera"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/question.js:81
|
#: pretix/static/pretixcontrol/js/ui/question.js:81
|
||||||
msgid "Count"
|
msgid "Count"
|
||||||
msgstr ""
|
msgstr "Sasia"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/subevent.js:112
|
#: pretix/static/pretixcontrol/js/ui/subevent.js:112
|
||||||
msgid "(one more date)"
|
msgid "(one more date)"
|
||||||
msgid_plural "({num} more dates)"
|
msgid_plural "({num} more dates)"
|
||||||
msgstr[0] ""
|
msgstr[0] "(edhe një datë)"
|
||||||
msgstr[1] ""
|
msgstr[1] "(edhe {num} më shumë data)"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/ui/cart.js:47
|
#: pretix/static/pretixpresale/js/ui/cart.js:47
|
||||||
msgid ""
|
msgid ""
|
||||||
"The items in your cart are no longer reserved for you. You can still "
|
"The items in your cart are no longer reserved for you. You can still "
|
||||||
"complete your order as long as they’re available."
|
"complete your order as long as they’re available."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Sendet në shportën tuaj nuk janë të rezervuar më për ju. Ju mund t'a "
|
||||||
|
"përmbushni porosinë tuaj derisa janë ende të disponueshëm."
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/ui/cart.js:49
|
#: pretix/static/pretixpresale/js/ui/cart.js:49
|
||||||
msgid "Cart expired"
|
msgid "Cart expired"
|
||||||
msgstr ""
|
msgstr "Shporta juaj u skadua"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/ui/cart.js:58
|
#: pretix/static/pretixpresale/js/ui/cart.js:58
|
||||||
#: pretix/static/pretixpresale/js/ui/cart.js:84
|
#: pretix/static/pretixpresale/js/ui/cart.js:84
|
||||||
msgid "Your cart is about to expire."
|
msgid "Your cart is about to expire."
|
||||||
msgstr ""
|
msgstr "Shporta juaj do të skadohet së shpejti."
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/ui/cart.js:62
|
#: pretix/static/pretixpresale/js/ui/cart.js:62
|
||||||
msgid "The items in your cart are reserved for you for one minute."
|
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."
|
msgid_plural "The items in your cart are reserved for you for {num} minutes."
|
||||||
msgstr[0] ""
|
msgstr[0] "Sendet në shportën tuaj janë të rezervuar për ju për një minutë."
|
||||||
msgstr[1] ""
|
msgstr[1] "Sendet në shportën tuaj janë të rezervuar për ju për {num} minuta."
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/ui/cart.js:83
|
#: pretix/static/pretixpresale/js/ui/cart.js:83
|
||||||
msgid "Your cart has expired."
|
msgid "Your cart has expired."
|
||||||
msgstr ""
|
msgstr "Shporta juaj është skaduar."
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/ui/cart.js:86
|
#: pretix/static/pretixpresale/js/ui/cart.js:86
|
||||||
msgid ""
|
msgid ""
|
||||||
"The items in your cart are no longer reserved for you. You can still "
|
"The items in your cart are no longer reserved for you. You can still "
|
||||||
"complete your order as long as they're available."
|
"complete your order as long as they're available."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Sendet në shportën tuaj nuk janë të rezervuar më për ju. Ju mund t'a "
|
||||||
|
"përmbushni porosinë derisa janë ende të disponueshëm."
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/ui/cart.js:87
|
#: pretix/static/pretixpresale/js/ui/cart.js:87
|
||||||
msgid "Do you want to renew the reservation period?"
|
msgid "Do you want to renew the reservation period?"
|
||||||
msgstr ""
|
msgstr "A dëshironi t'a rivendosni periudhën e rezervimit?"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/ui/cart.js:90
|
#: pretix/static/pretixpresale/js/ui/cart.js:90
|
||||||
msgid "Renew reservation"
|
msgid "Renew reservation"
|
||||||
msgstr ""
|
msgstr "Rivendosni rezervimin"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/ui/main.js:194
|
#: pretix/static/pretixpresale/js/ui/main.js:194
|
||||||
msgid "The organizer keeps %(currency)s %(amount)s"
|
msgid "The organizer keeps %(currency)s %(amount)s"
|
||||||
msgstr ""
|
msgstr "Organizatori mban %(currency)s %(amount)s"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/ui/main.js:202
|
#: pretix/static/pretixpresale/js/ui/main.js:202
|
||||||
msgid "You get %(currency)s %(amount)s back"
|
msgid "You get %(currency)s %(amount)s back"
|
||||||
msgstr ""
|
msgstr "Ju merrni %(currency)s %(amount)s kusur"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/ui/main.js:218
|
#: pretix/static/pretixpresale/js/ui/main.js:218
|
||||||
msgid "Please enter the amount the organizer can keep."
|
msgid "Please enter the amount the organizer can keep."
|
||||||
msgstr ""
|
msgstr "Ju lutemi të shkruani shumën që mund të mbajë organizatori."
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/ui/main.js:577
|
#: pretix/static/pretixpresale/js/ui/main.js:577
|
||||||
msgid "Your local time:"
|
msgid "Your local time:"
|
||||||
msgstr ""
|
msgstr "Koha juaj vendase:"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/walletdetection.js:39
|
#: pretix/static/pretixpresale/js/walletdetection.js:39
|
||||||
|
#, fuzzy
|
||||||
msgid "Google Pay"
|
msgid "Google Pay"
|
||||||
msgstr ""
|
msgstr "Google Pay"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:16
|
#: pretix/static/pretixpresale/js/widget/widget.js:16
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Quantity"
|
msgid "Quantity"
|
||||||
msgstr ""
|
msgstr "Sasi"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:17
|
#: pretix/static/pretixpresale/js/widget/widget.js:17
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Decrease quantity"
|
msgid "Decrease quantity"
|
||||||
msgstr ""
|
msgstr "Ulni sasinë"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:18
|
#: pretix/static/pretixpresale/js/widget/widget.js:18
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Increase quantity"
|
msgid "Increase quantity"
|
||||||
msgstr ""
|
msgstr "Rrisni sasinë"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:19
|
#: pretix/static/pretixpresale/js/widget/widget.js:19
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Filter events by"
|
msgid "Filter events by"
|
||||||
msgstr ""
|
msgstr "Filtroni ngjarjet nga"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:20
|
#: pretix/static/pretixpresale/js/widget/widget.js:20
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Filter"
|
msgid "Filter"
|
||||||
msgstr ""
|
msgstr "Filtri"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:21
|
#: pretix/static/pretixpresale/js/widget/widget.js:21
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Price"
|
msgid "Price"
|
||||||
msgstr ""
|
msgstr "Çmimi"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:22
|
#: pretix/static/pretixpresale/js/widget/widget.js:22
|
||||||
#, javascript-format
|
#, javascript-format
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Original price: %s"
|
msgid "Original price: %s"
|
||||||
msgstr ""
|
msgstr "Çmimi origjinal:%s"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:23
|
#: pretix/static/pretixpresale/js/widget/widget.js:23
|
||||||
#, javascript-format
|
#, javascript-format
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "New price: %s"
|
msgid "New price: %s"
|
||||||
msgstr ""
|
msgstr "Çmimi i ri:%s"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:24
|
#: pretix/static/pretixpresale/js/widget/widget.js:24
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Select"
|
msgid "Select"
|
||||||
msgstr ""
|
msgstr "Zgjidhni"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:25
|
#: pretix/static/pretixpresale/js/widget/widget.js:25
|
||||||
#, javascript-format
|
#, javascript-format
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Select %s"
|
msgid "Select %s"
|
||||||
msgstr ""
|
msgstr "Zgjidhni%s"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:26
|
#: pretix/static/pretixpresale/js/widget/widget.js:26
|
||||||
#, javascript-format
|
#, javascript-format
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Select variant %s"
|
msgid "Select variant %s"
|
||||||
msgstr ""
|
msgstr "Zgjidhni variant %s"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:27
|
#: pretix/static/pretixpresale/js/widget/widget.js:27
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Sold out"
|
msgid "Sold out"
|
||||||
msgstr ""
|
msgstr "Shitur të gjitha"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:28
|
#: pretix/static/pretixpresale/js/widget/widget.js:28
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Buy"
|
msgid "Buy"
|
||||||
msgstr ""
|
msgstr "Blini"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:29
|
#: pretix/static/pretixpresale/js/widget/widget.js:29
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Register"
|
msgid "Register"
|
||||||
msgstr ""
|
msgstr "Regjistrohuni"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:30
|
#: pretix/static/pretixpresale/js/widget/widget.js:30
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Reserved"
|
msgid "Reserved"
|
||||||
msgstr ""
|
msgstr "Të rezervuar"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:31
|
#: pretix/static/pretixpresale/js/widget/widget.js:31
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "FREE"
|
msgid "FREE"
|
||||||
msgstr ""
|
msgstr "FALAS"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:32
|
#: pretix/static/pretixpresale/js/widget/widget.js:32
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "from %(currency)s %(price)s"
|
msgid "from %(currency)s %(price)s"
|
||||||
msgstr ""
|
msgstr "nga %(currency)s %(price)s"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:33
|
#: pretix/static/pretixpresale/js/widget/widget.js:33
|
||||||
#, javascript-format
|
#, javascript-format
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Image of %s"
|
msgid "Image of %s"
|
||||||
msgstr ""
|
msgstr "Imazh i %s"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:34
|
#: pretix/static/pretixpresale/js/widget/widget.js:34
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "incl. %(rate)s% %(taxname)s"
|
msgid "incl. %(rate)s% %(taxname)s"
|
||||||
msgstr ""
|
msgstr "përfsh. %(rate)s% %(taxname)s"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:35
|
#: pretix/static/pretixpresale/js/widget/widget.js:35
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "plus %(rate)s% %(taxname)s"
|
msgid "plus %(rate)s% %(taxname)s"
|
||||||
msgstr ""
|
msgstr "shtesë %(rate)s% %(taxname)s"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:36
|
#: pretix/static/pretixpresale/js/widget/widget.js:36
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "incl. taxes"
|
msgid "incl. taxes"
|
||||||
msgstr ""
|
msgstr "përfsh. taksat"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:37
|
#: pretix/static/pretixpresale/js/widget/widget.js:37
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "plus taxes"
|
msgid "plus taxes"
|
||||||
msgstr ""
|
msgstr "duke shtuar taksat"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
||||||
#, javascript-format
|
#, javascript-format
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "currently available: %s"
|
msgid "currently available: %s"
|
||||||
msgstr ""
|
msgstr "aktualisht të disponueshëm: %s"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:39
|
#: pretix/static/pretixpresale/js/widget/widget.js:39
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Only available with a voucher"
|
msgid "Only available with a voucher"
|
||||||
msgstr ""
|
msgstr "Të disponueshëm vetëm me një kupon"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:40
|
#: pretix/static/pretixpresale/js/widget/widget.js:40
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Not yet available"
|
msgid "Not yet available"
|
||||||
msgstr ""
|
msgstr "Të padisponushëm ende"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:41
|
#: pretix/static/pretixpresale/js/widget/widget.js:41
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Not available anymore"
|
msgid "Not available anymore"
|
||||||
msgstr ""
|
msgstr "Nuk është të disponueshëm më"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:42
|
#: pretix/static/pretixpresale/js/widget/widget.js:42
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Currently not available"
|
msgid "Currently not available"
|
||||||
msgstr ""
|
msgstr "Të padisponueshëm për momentin"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:44
|
#: pretix/static/pretixpresale/js/widget/widget.js:44
|
||||||
#, javascript-format
|
#, javascript-format
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "minimum amount to order: %s"
|
msgid "minimum amount to order: %s"
|
||||||
msgstr ""
|
msgstr "sasia minimale për porosi: %s"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Close ticket shop"
|
msgid "Close ticket shop"
|
||||||
msgstr ""
|
msgstr "Mbyllni dyqanin e biletave"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "The ticket shop could not be loaded."
|
msgid "The ticket shop could not be loaded."
|
||||||
msgstr ""
|
msgstr "Dyqani i biletave nuk mund të hapej."
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:47
|
#: pretix/static/pretixpresale/js/widget/widget.js:47
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
@@ -954,21 +965,23 @@ msgid ""
|
|||||||
"There are currently a lot of users in this ticket shop. Please open the shop "
|
"There are currently a lot of users in this ticket shop. Please open the shop "
|
||||||
"in a new tab to continue."
|
"in a new tab to continue."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Ka shumë përdorës në dyqanin e biletave tani. Ju lutemi, hapni dyqanin në "
|
||||||
|
"një skedë të re për të vazhduar."
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:49
|
#: pretix/static/pretixpresale/js/widget/widget.js:49
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Open ticket shop"
|
msgid "Open ticket shop"
|
||||||
msgstr ""
|
msgstr "Hapni dyqanin e biletave"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:50
|
#: pretix/static/pretixpresale/js/widget/widget.js:50
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Checkout"
|
msgid "Checkout"
|
||||||
msgstr ""
|
msgstr "Pagesa"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:51
|
#: pretix/static/pretixpresale/js/widget/widget.js:51
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "The cart could not be created. Please try again later"
|
msgid "The cart could not be created. Please try again later"
|
||||||
msgstr ""
|
msgstr "Shporta juaj nuk mund të krijohej. Ju lutemi, provoni përsëri më vonë"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:52
|
#: pretix/static/pretixpresale/js/widget/widget.js:52
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
@@ -976,11 +989,14 @@ msgid ""
|
|||||||
"We could not create your cart, since there are currently too many users in "
|
"We could not create your cart, since there are currently too many users in "
|
||||||
"this ticket shop. Please click \"Continue\" to retry in a new tab."
|
"this ticket shop. Please click \"Continue\" to retry in a new tab."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Nuk mund të krijonim shportën tuaj sepse ka më tepër përdorës në këtë dyqan "
|
||||||
|
"të biletave. Ju lutemi, shtypni \"Vazhdoni\" të provoni përsëri në një skedë "
|
||||||
|
"të re."
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:54
|
#: pretix/static/pretixpresale/js/widget/widget.js:54
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Waiting list"
|
msgid "Waiting list"
|
||||||
msgstr ""
|
msgstr "Lista e pritjes"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:55
|
#: pretix/static/pretixpresale/js/widget/widget.js:55
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
@@ -988,96 +1004,98 @@ msgid ""
|
|||||||
"You currently have an active cart for this event. If you select more "
|
"You currently have an active cart for this event. If you select more "
|
||||||
"products, they will be added to your existing cart."
|
"products, they will be added to your existing cart."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Ju keni tashmë një shportë aktive për këtë ngjarje. Nëse zgjidhni më shumë "
|
||||||
|
"produkte, do të shtohen në shportën tuaj aktuale."
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:57
|
#: pretix/static/pretixpresale/js/widget/widget.js:57
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Resume checkout"
|
msgid "Resume checkout"
|
||||||
msgstr ""
|
msgstr "Vazhdoni pagesën"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:58
|
#: pretix/static/pretixpresale/js/widget/widget.js:58
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Redeem a voucher"
|
msgid "Redeem a voucher"
|
||||||
msgstr ""
|
msgstr "Përdorni një kupon"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:59
|
#: pretix/static/pretixpresale/js/widget/widget.js:59
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Redeem"
|
msgid "Redeem"
|
||||||
msgstr ""
|
msgstr "Përdorni"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:60
|
#: pretix/static/pretixpresale/js/widget/widget.js:60
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Voucher code"
|
msgid "Voucher code"
|
||||||
msgstr ""
|
msgstr "Kodi i kuponit"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:61
|
#: pretix/static/pretixpresale/js/widget/widget.js:61
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Close"
|
msgid "Close"
|
||||||
msgstr ""
|
msgstr "Mbyllni"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:62
|
#: pretix/static/pretixpresale/js/widget/widget.js:62
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Close checkout"
|
msgid "Close checkout"
|
||||||
msgstr ""
|
msgstr "Kthehuni nga pagesa"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:63
|
#: pretix/static/pretixpresale/js/widget/widget.js:63
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "You cannot cancel this operation. Please wait for loading to finish."
|
msgid "You cannot cancel this operation. Please wait for loading to finish."
|
||||||
msgstr ""
|
msgstr "Ju nuk mund t'a anuloni këtë veprim. Ju lutemi, prisni për ngarkimin."
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:64
|
#: pretix/static/pretixpresale/js/widget/widget.js:64
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Continue"
|
msgid "Continue"
|
||||||
msgstr ""
|
msgstr "Vazhdoni"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:65
|
#: pretix/static/pretixpresale/js/widget/widget.js:65
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Show variants"
|
msgid "Show variants"
|
||||||
msgstr ""
|
msgstr "Tregoni variantet"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:66
|
#: pretix/static/pretixpresale/js/widget/widget.js:66
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Hide variants"
|
msgid "Hide variants"
|
||||||
msgstr ""
|
msgstr "Fshehni variantet"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:67
|
#: pretix/static/pretixpresale/js/widget/widget.js:67
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Choose a different event"
|
msgid "Choose a different event"
|
||||||
msgstr ""
|
msgstr "Zgjidhni një ngjarje tjetër"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:68
|
#: pretix/static/pretixpresale/js/widget/widget.js:68
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Choose a different date"
|
msgid "Choose a different date"
|
||||||
msgstr ""
|
msgstr "Zgjidhni një datë tjetër"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:69
|
#: pretix/static/pretixpresale/js/widget/widget.js:69
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Back"
|
msgid "Back"
|
||||||
msgstr ""
|
msgstr "Kthehuni"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:70
|
#: pretix/static/pretixpresale/js/widget/widget.js:70
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Next month"
|
msgid "Next month"
|
||||||
msgstr ""
|
msgstr "Muaji tjetër"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:71
|
#: pretix/static/pretixpresale/js/widget/widget.js:71
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Previous month"
|
msgid "Previous month"
|
||||||
msgstr ""
|
msgstr "Muaji i fundit"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:72
|
#: pretix/static/pretixpresale/js/widget/widget.js:72
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Next week"
|
msgid "Next week"
|
||||||
msgstr ""
|
msgstr "Java tjetër"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:73
|
#: pretix/static/pretixpresale/js/widget/widget.js:73
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Previous week"
|
msgid "Previous week"
|
||||||
msgstr ""
|
msgstr "Java e fundit"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:74
|
#: pretix/static/pretixpresale/js/widget/widget.js:74
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Open seat selection"
|
msgid "Open seat selection"
|
||||||
msgstr ""
|
msgstr "Hapni zgjedhjen e sendileve"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:75
|
#: pretix/static/pretixpresale/js/widget/widget.js:75
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
@@ -1086,112 +1104,115 @@ msgid ""
|
|||||||
"add yourself to the waiting list. We will then notify if seats are available "
|
"add yourself to the waiting list. We will then notify if seats are available "
|
||||||
"again."
|
"again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Disa ose të gjitha kategoritë e biletave janë të shitura. Nëse dëshironi, "
|
||||||
|
"mund të shtoheni në listën e pritjes. Ne do t'ju njoftojmë nëse sendile "
|
||||||
|
"bëhen të disponueshme përsëri."
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:76
|
#: pretix/static/pretixpresale/js/widget/widget.js:76
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid "Load more"
|
msgid "Load more"
|
||||||
msgstr ""
|
msgstr "Ngarkoni më shumë"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:78
|
#: pretix/static/pretixpresale/js/widget/widget.js:78
|
||||||
msgid "Mo"
|
msgid "Mo"
|
||||||
msgstr ""
|
msgstr "Hë"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:79
|
#: pretix/static/pretixpresale/js/widget/widget.js:79
|
||||||
msgid "Tu"
|
msgid "Tu"
|
||||||
msgstr ""
|
msgstr "Ma"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:80
|
#: pretix/static/pretixpresale/js/widget/widget.js:80
|
||||||
msgid "We"
|
msgid "We"
|
||||||
msgstr ""
|
msgstr "Më"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:81
|
#: pretix/static/pretixpresale/js/widget/widget.js:81
|
||||||
msgid "Th"
|
msgid "Th"
|
||||||
msgstr ""
|
msgstr "En"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:82
|
#: pretix/static/pretixpresale/js/widget/widget.js:82
|
||||||
msgid "Fr"
|
msgid "Fr"
|
||||||
msgstr ""
|
msgstr "Pr"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:83
|
#: pretix/static/pretixpresale/js/widget/widget.js:83
|
||||||
msgid "Sa"
|
msgid "Sa"
|
||||||
msgstr ""
|
msgstr "Sh"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:84
|
#: pretix/static/pretixpresale/js/widget/widget.js:84
|
||||||
msgid "Su"
|
msgid "Su"
|
||||||
msgstr ""
|
msgstr "Di"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:85
|
#: pretix/static/pretixpresale/js/widget/widget.js:85
|
||||||
msgid "Monday"
|
msgid "Monday"
|
||||||
msgstr ""
|
msgstr "E Hënë"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:86
|
#: pretix/static/pretixpresale/js/widget/widget.js:86
|
||||||
msgid "Tuesday"
|
msgid "Tuesday"
|
||||||
msgstr ""
|
msgstr "E Martë"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:87
|
#: pretix/static/pretixpresale/js/widget/widget.js:87
|
||||||
msgid "Wednesday"
|
msgid "Wednesday"
|
||||||
msgstr ""
|
msgstr "E Mërkurë"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:88
|
#: pretix/static/pretixpresale/js/widget/widget.js:88
|
||||||
msgid "Thursday"
|
msgid "Thursday"
|
||||||
msgstr ""
|
msgstr "E Enjte"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:89
|
#: pretix/static/pretixpresale/js/widget/widget.js:89
|
||||||
msgid "Friday"
|
msgid "Friday"
|
||||||
msgstr ""
|
msgstr "E Premte"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:90
|
#: pretix/static/pretixpresale/js/widget/widget.js:90
|
||||||
msgid "Saturday"
|
msgid "Saturday"
|
||||||
msgstr ""
|
msgstr "E Shtunë"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:91
|
#: pretix/static/pretixpresale/js/widget/widget.js:91
|
||||||
msgid "Sunday"
|
msgid "Sunday"
|
||||||
msgstr ""
|
msgstr "E Diel"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:94
|
#: pretix/static/pretixpresale/js/widget/widget.js:94
|
||||||
msgid "January"
|
msgid "January"
|
||||||
msgstr ""
|
msgstr "Janar"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:95
|
#: pretix/static/pretixpresale/js/widget/widget.js:95
|
||||||
msgid "February"
|
msgid "February"
|
||||||
msgstr ""
|
msgstr "Shkurt"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:96
|
#: pretix/static/pretixpresale/js/widget/widget.js:96
|
||||||
msgid "March"
|
msgid "March"
|
||||||
msgstr ""
|
msgstr "Mars"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:97
|
#: pretix/static/pretixpresale/js/widget/widget.js:97
|
||||||
msgid "April"
|
msgid "April"
|
||||||
msgstr ""
|
msgstr "Prill"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:98
|
#: pretix/static/pretixpresale/js/widget/widget.js:98
|
||||||
msgid "May"
|
msgid "May"
|
||||||
msgstr ""
|
msgstr "Maj"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:99
|
#: pretix/static/pretixpresale/js/widget/widget.js:99
|
||||||
msgid "June"
|
msgid "June"
|
||||||
msgstr ""
|
msgstr "Qershor"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:100
|
#: pretix/static/pretixpresale/js/widget/widget.js:100
|
||||||
msgid "July"
|
msgid "July"
|
||||||
msgstr ""
|
msgstr "Korrik"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:101
|
#: pretix/static/pretixpresale/js/widget/widget.js:101
|
||||||
msgid "August"
|
msgid "August"
|
||||||
msgstr ""
|
msgstr "Gusht"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:102
|
#: pretix/static/pretixpresale/js/widget/widget.js:102
|
||||||
msgid "September"
|
msgid "September"
|
||||||
msgstr ""
|
msgstr "Shtator"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:103
|
#: pretix/static/pretixpresale/js/widget/widget.js:103
|
||||||
msgid "October"
|
msgid "October"
|
||||||
msgstr ""
|
msgstr "Tetor"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:104
|
#: pretix/static/pretixpresale/js/widget/widget.js:104
|
||||||
msgid "November"
|
msgid "November"
|
||||||
msgstr ""
|
msgstr "Nëntor"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:105
|
#: pretix/static/pretixpresale/js/widget/widget.js:105
|
||||||
msgid "December"
|
msgid "December"
|
||||||
msgstr ""
|
msgstr "Dhjetor"
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
{% load html_time %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load icon %}
|
{% load icon %}
|
||||||
{% load eventurl %}
|
{% load eventurl %}
|
||||||
@@ -21,20 +22,18 @@
|
|||||||
{% if event.settings.show_times %}
|
{% if event.settings.show_times %}
|
||||||
<br>
|
<br>
|
||||||
<span data-time="{{ ev.date_from.isoformat }}" data-timezone="{{ request.event.timezone }}">
|
<span data-time="{{ ev.date_from.isoformat }}" data-timezone="{{ request.event.timezone }}">
|
||||||
{% with time_human=ev.date_from|date:"TIME_FORMAT" time_24=ev.date_from|time:"H:i" %}
|
{% html_time ev.date_from "TIME_FORMAT" attr_fmt="H:i" as time%}
|
||||||
{% blocktrans trimmed with time='<time datetime="'|add:time_24|add:'">'|add:time_human|add:"</time>"|safe %}
|
{% blocktrans trimmed with time=time %}
|
||||||
Begin: {{ time }}
|
Begin: {{ time }}
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
{% endwith %}
|
|
||||||
</span>
|
</span>
|
||||||
{% if event.settings.show_date_to and ev.date_to %}
|
{% if event.settings.show_date_to and ev.date_to %}
|
||||||
<br>
|
<br>
|
||||||
<span data-time="{{ ev.date_to.isoformat }}" data-timezone="{{ request.event.timezone }}">
|
<span data-time="{{ ev.date_to.isoformat }}" data-timezone="{{ request.event.timezone }}">
|
||||||
{% with time_human=ev.date_to|date:"TIME_FORMAT" time_24=ev.date_to|time:"H:i" %}
|
{% html_time ev.date_to "TIME_FORMAT" attr_fmt="H:i" as time%}
|
||||||
{% blocktrans trimmed with time='<time datetime="'|add:time_24|add:'">'|add:time_human|add:"</time>"|safe %}
|
{% blocktrans trimmed with time=time %}
|
||||||
End: {{ time }}
|
End: {{ time }}
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
{% endwith %}
|
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -42,19 +41,17 @@
|
|||||||
<br>
|
<br>
|
||||||
{% if ev.date_admission|date:"SHORT_DATE_FORMAT" == ev.date_from|date:"SHORT_DATE_FORMAT" %}
|
{% if ev.date_admission|date:"SHORT_DATE_FORMAT" == ev.date_from|date:"SHORT_DATE_FORMAT" %}
|
||||||
<span data-time="{{ ev.date_admission.isoformat }}" data-timezone="{{ request.event.timezone }}">
|
<span data-time="{{ ev.date_admission.isoformat }}" data-timezone="{{ request.event.timezone }}">
|
||||||
{% with time_human=ev.date_admission|date:"TIME_FORMAT" time_24=ev.date_admission|time:"H:i" %}
|
{% html_time ev.date_admission "TIME_FORMAT" attr_fmt="H:i" as time%}
|
||||||
{% blocktrans trimmed with time='<time datetime="'|add:time_24|add:'">'|add:time_human|add:"</time>"|safe %}
|
{% blocktrans trimmed with time=time %}
|
||||||
Admission: {{ time }}
|
Admission: {{ time }}
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
{% endwith %}
|
|
||||||
</span>
|
</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span data-time="{{ ev.date_admission.isoformat }}" data-timezone="{{ request.event.timezone }}">
|
<span data-time="{{ ev.date_admission.isoformat }}" data-timezone="{{ request.event.timezone }}">
|
||||||
{% with datetime_human=ev.date_admission|date:"SHORT_DATETIME_FORMAT" datetime_iso=ev.date_admission|time:"Y-m-d H:i" %}
|
{% html_time ev.date_admission "SHORT_DATETIME_FORMAT" attr_fmt="Y-m-d H:i" as datetime%}
|
||||||
{% blocktrans trimmed with datetime='<time datetime="'|add:datetime_iso|add:'">'|add:datetime_human|add:"</time>"|safe %}
|
{% blocktrans trimmed with datetime=datetime %}
|
||||||
Admission: {{ datetime }}
|
Admission: {{ datetime }}
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
{% endwith %}
|
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{% extends "pretixpresale/event/base.html" %}
|
{% extends "pretixpresale/event/base.html" %}
|
||||||
|
{% load html_time %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load bootstrap3 %}
|
{% load bootstrap3 %}
|
||||||
{% load eventsignal %}
|
{% load eventsignal %}
|
||||||
@@ -92,11 +93,10 @@
|
|||||||
A payment of {{ total }} is still pending for this order.
|
A payment of {{ total }} is still pending for this order.
|
||||||
{% endblocktrans %}</strong>
|
{% endblocktrans %}</strong>
|
||||||
<strong>
|
<strong>
|
||||||
{% with date_human=order|format_expires|safe date_iso=order.expires|date:"c" %}
|
{% html_time order.expires "format_expires" as date %}
|
||||||
{% blocktrans trimmed with date='<time datetime="'|add:date_iso|add:'">'|add:date_human|add:"</time>"|safe %}
|
{% blocktrans trimmed with date=date %}
|
||||||
Please complete your payment before {{ date }}
|
Please complete your payment before {{ date }}
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
{% endwith %}
|
|
||||||
</strong>
|
</strong>
|
||||||
</p>
|
</p>
|
||||||
{% if last_payment %}
|
{% if last_payment %}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
action="{% eventurl request.event "presale:event.cart.add" cart_namespace=cart_namespace %}?next={{ cart_redirect|urlencode }}">
|
action="{% eventurl request.event "presale:event.cart.add" cart_namespace=cart_namespace %}?next={{ cart_redirect|urlencode }}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="subevent" value="{{ subevent.id|default_if_none:"" }}"/>
|
<input type="hidden" name="subevent" value="{{ subevent.id|default_if_none:"" }}"/>
|
||||||
|
<input type="hidden" name="_voucher_code" value="{{ voucher.code|default_if_none:"" }}">
|
||||||
{% if event.has_subevents %}
|
{% if event.has_subevents %}
|
||||||
{% eventsignal event "pretix.presale.signals.render_seating_plan" request=request subevent=subevent voucher=voucher %}
|
{% eventsignal event "pretix.presale.signals.render_seating_plan" request=request subevent=subevent voucher=voucher %}
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
{% load html_time %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load date_fast %}
|
{% load date_fast %}
|
||||||
{% load calendarhead %}
|
{% load calendarhead %}
|
||||||
@@ -55,7 +56,7 @@
|
|||||||
running
|
running
|
||||||
{% elif event.event.presale_has_ended %}
|
{% elif event.event.presale_has_ended %}
|
||||||
over
|
over
|
||||||
{% elif event.event.settings.presale_start_show_date and event.event.presale_start %}
|
{% elif event.event.settings.presale_start_show_date and event.event.effective_presale_start %}
|
||||||
soon
|
soon
|
||||||
{% else %}
|
{% else %}
|
||||||
soon
|
soon
|
||||||
@@ -108,13 +109,12 @@
|
|||||||
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Book now" %}
|
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Book now" %}
|
||||||
{% elif event.event.presale_has_ended %}
|
{% elif event.event.presale_has_ended %}
|
||||||
{% trans "Sale over" %}
|
{% trans "Sale over" %}
|
||||||
{% elif event.event.settings.presale_start_show_date and event.event.presale_start %}
|
{% elif event.event.settings.presale_start_show_date and event.event.effective_presale_start %}
|
||||||
<span class="fa fa-ticket" aria-hidden="true"></span>
|
<span class="fa fa-ticket" aria-hidden="true"></span>
|
||||||
{% with date_human=event.event.presale_start|date_fast:"SHORT_DATE_FORMAT" date_iso=event.event.presale_start|date_fast:"c" %}
|
{% html_time event.event.effective_presale_start "SHORT_DATE_FORMAT" as start_date %}
|
||||||
{% blocktrans with start_date="<time datetime='"|add:date_iso|add:"'>"|add:date_human|add:"</time>"|safe %}
|
{% blocktrans with start_date=start_date %}
|
||||||
from {{ start_date }}
|
from {{ start_date }}
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
{% endwith %}
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Soon" %}
|
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Soon" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
{% load html_time %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load eventurl %}
|
{% load eventurl %}
|
||||||
<div class="day-calendar cal-size-{{ raster_to_shortest_ratio }}{% if no_headlines %} no-headlines{% endif %}"
|
<div class="day-calendar cal-size-{{ raster_to_shortest_ratio }}{% if no_headlines %} no-headlines{% endif %}"
|
||||||
@@ -52,7 +53,7 @@
|
|||||||
running
|
running
|
||||||
{% elif event.event.presale_has_ended %}
|
{% elif event.event.presale_has_ended %}
|
||||||
over
|
over
|
||||||
{% elif event.event.settings.presale_start_show_date and event.event.presale_start %}
|
{% elif event.event.settings.presale_start_show_date and event.event.effective_presale_start %}
|
||||||
soon
|
soon
|
||||||
{% else %}
|
{% else %}
|
||||||
soon
|
soon
|
||||||
@@ -114,9 +115,10 @@
|
|||||||
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Book now" %}
|
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Book now" %}
|
||||||
{% elif event.event.presale_has_ended %}
|
{% elif event.event.presale_has_ended %}
|
||||||
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Sale over" %}
|
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Sale over" %}
|
||||||
{% elif event.event.settings.presale_start_show_date and event.event.presale_start %}
|
{% elif event.event.settings.presale_start_show_date and event.event.effective_presale_start %}
|
||||||
<span class="fa fa-ticket" aria-hidden="true"></span>
|
<span class="fa fa-ticket" aria-hidden="true"></span>
|
||||||
{% blocktrans with start_date=event.event.presale_start|date:"SHORT_DATE_FORMAT" %}
|
{% html_time event.event.effective_presale_start "SHORT_DATE_FORMAT" as start_date %}
|
||||||
|
{% blocktrans with start_date=start_date %}
|
||||||
from {{ start_date }}
|
from {{ start_date }}
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
{% load html_time %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load icon %}
|
{% load icon %}
|
||||||
{% load textbubble %}
|
{% load textbubble %}
|
||||||
@@ -52,11 +53,10 @@
|
|||||||
{% endtextbubble %}
|
{% endtextbubble %}
|
||||||
{% if event.settings.presale_start_show_date %}
|
{% if event.settings.presale_start_show_date %}
|
||||||
<br><span class="text-muted">
|
<br><span class="text-muted">
|
||||||
{% with date_iso=event.effective_presale_start.isoformat date_human=event.effective_presale_start|date:"SHORT_DATE_FORMAT" %}
|
{% html_time event.event.effective_presale_start "SHORT_DATE_FORMAT" as date %}
|
||||||
{% blocktrans trimmed with date='<time datetime="'|add:date_iso|add:'">'|add:date_human|add:"</time>"|safe %}
|
{% blocktrans trimmed with date=date %}
|
||||||
Sale starts {{ date }}
|
Sale starts {{ date }}
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
{% endwith %}
|
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
{% load html_time %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load date_fast %}
|
{% load date_fast %}
|
||||||
<div class="week-calendar">
|
<div class="week-calendar">
|
||||||
@@ -24,7 +25,7 @@
|
|||||||
running
|
running
|
||||||
{% elif event.event.presale_has_ended %}
|
{% elif event.event.presale_has_ended %}
|
||||||
over
|
over
|
||||||
{% elif event.event.settings.presale_start_show_date and event.event.presale_start %}
|
{% elif event.event.settings.presale_start_show_date and event.event.effective_presale_start %}
|
||||||
soon
|
soon
|
||||||
{% else %}
|
{% else %}
|
||||||
soon
|
soon
|
||||||
@@ -77,9 +78,10 @@
|
|||||||
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Book now" %}
|
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Book now" %}
|
||||||
{% elif event.event.presale_has_ended %}
|
{% elif event.event.presale_has_ended %}
|
||||||
{% trans "Sale over" %}
|
{% trans "Sale over" %}
|
||||||
{% elif event.event.settings.presale_start_show_date and event.event.presale_start %}
|
{% elif event.event.settings.presale_start_show_date and event.event.effective_presale_start %}
|
||||||
<span class="fa fa-ticket" aria-hidden="true"></span>
|
<span class="fa fa-ticket" aria-hidden="true"></span>
|
||||||
{% blocktrans with start_date=event.event.presale_start|date_fast:"SHORT_DATE_FORMAT" %}
|
{% html_time event.event.effective_presale_start "SHORT_DATE_FORMAT" as start_date %}
|
||||||
|
{% blocktrans with start_date=start_date %}
|
||||||
from {{ start_date }}
|
from {{ start_date }}
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@@ -435,7 +435,7 @@ def cart_session(request):
|
|||||||
@method_decorator(allow_frame_if_namespaced, 'dispatch')
|
@method_decorator(allow_frame_if_namespaced, 'dispatch')
|
||||||
class CartApplyVoucher(EventViewMixin, CartActionMixin, AsyncAction, View):
|
class CartApplyVoucher(EventViewMixin, CartActionMixin, AsyncAction, View):
|
||||||
task = apply_voucher
|
task = apply_voucher
|
||||||
known_errortypes = ['CartError']
|
known_errortypes = ['CartError', 'CartPositionError']
|
||||||
|
|
||||||
def get_success_message(self, value):
|
def get_success_message(self, value):
|
||||||
return _('We applied the voucher to as many products in your cart as we could.')
|
return _('We applied the voucher to as many products in your cart as we could.')
|
||||||
@@ -513,7 +513,7 @@ class CartApplyVoucher(EventViewMixin, CartActionMixin, AsyncAction, View):
|
|||||||
@method_decorator(allow_frame_if_namespaced, 'dispatch')
|
@method_decorator(allow_frame_if_namespaced, 'dispatch')
|
||||||
class CartRemove(EventViewMixin, CartActionMixin, AsyncAction, View):
|
class CartRemove(EventViewMixin, CartActionMixin, AsyncAction, View):
|
||||||
task = remove_cart_position
|
task = remove_cart_position
|
||||||
known_errortypes = ['CartError']
|
known_errortypes = ['CartError', 'CartPositionError']
|
||||||
|
|
||||||
def get_success_message(self, value):
|
def get_success_message(self, value):
|
||||||
if CartPosition.objects.filter(cart_id=get_or_create_cart_id(self.request)).exists():
|
if CartPosition.objects.filter(cart_id=get_or_create_cart_id(self.request)).exists():
|
||||||
@@ -542,7 +542,7 @@ class CartRemove(EventViewMixin, CartActionMixin, AsyncAction, View):
|
|||||||
@method_decorator(allow_frame_if_namespaced, 'dispatch')
|
@method_decorator(allow_frame_if_namespaced, 'dispatch')
|
||||||
class CartClear(EventViewMixin, CartActionMixin, AsyncAction, View):
|
class CartClear(EventViewMixin, CartActionMixin, AsyncAction, View):
|
||||||
task = clear_cart
|
task = clear_cart
|
||||||
known_errortypes = ['CartError']
|
known_errortypes = ['CartError', 'CartPositionError']
|
||||||
|
|
||||||
def get_success_message(self, value):
|
def get_success_message(self, value):
|
||||||
create_empty_cart_id(self.request)
|
create_empty_cart_id(self.request)
|
||||||
@@ -556,7 +556,7 @@ class CartClear(EventViewMixin, CartActionMixin, AsyncAction, View):
|
|||||||
@method_decorator(allow_frame_if_namespaced, 'dispatch')
|
@method_decorator(allow_frame_if_namespaced, 'dispatch')
|
||||||
class CartExtendReservation(EventViewMixin, CartActionMixin, AsyncAction, View):
|
class CartExtendReservation(EventViewMixin, CartActionMixin, AsyncAction, View):
|
||||||
task = extend_cart_reservation
|
task = extend_cart_reservation
|
||||||
known_errortypes = ['CartError']
|
known_errortypes = ['CartError', 'CartPositionError']
|
||||||
|
|
||||||
def _ajax_response_data(self, value):
|
def _ajax_response_data(self, value):
|
||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
@@ -566,7 +566,11 @@ class CartExtendReservation(EventViewMixin, CartActionMixin, AsyncAction, View):
|
|||||||
|
|
||||||
def get_success_message(self, value):
|
def get_success_message(self, value):
|
||||||
if value['success'] > 0:
|
if value['success'] > 0:
|
||||||
return _('Your cart timeout was extended.')
|
if value.get('price_changed'):
|
||||||
|
return _('Your cart timeout was extended. Please note that some of the prices in your cart '
|
||||||
|
'changed.')
|
||||||
|
else:
|
||||||
|
return _('Your cart timeout was extended.')
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
return self.do(self.request.event.id, get_or_create_cart_id(self.request), translation.get_language(),
|
return self.do(self.request.event.id, get_or_create_cart_id(self.request), translation.get_language(),
|
||||||
@@ -578,7 +582,7 @@ class CartExtendReservation(EventViewMixin, CartActionMixin, AsyncAction, View):
|
|||||||
@method_decorator(iframe_entry_view_wrapper, 'dispatch')
|
@method_decorator(iframe_entry_view_wrapper, 'dispatch')
|
||||||
class CartAdd(EventViewMixin, CartActionMixin, AsyncAction, View):
|
class CartAdd(EventViewMixin, CartActionMixin, AsyncAction, View):
|
||||||
task = add_items_to_cart
|
task = add_items_to_cart
|
||||||
known_errortypes = ['CartError']
|
known_errortypes = ['CartError', 'CartPositionError']
|
||||||
|
|
||||||
def get_success_message(self, value):
|
def get_success_message(self, value):
|
||||||
return _('The products have been successfully added to your cart.')
|
return _('The products have been successfully added to your cart.')
|
||||||
|
|||||||
@@ -108,6 +108,8 @@ def indent(s):
|
|||||||
|
|
||||||
|
|
||||||
def widget_css_etag(request, version, **kwargs):
|
def widget_css_etag(request, version, **kwargs):
|
||||||
|
if version > version_max:
|
||||||
|
return None
|
||||||
if version < version_min:
|
if version < version_min:
|
||||||
version = version_min
|
version = version_min
|
||||||
# This makes sure a new version of the theme is loaded whenever settings or the source files have changed
|
# This makes sure a new version of the theme is loaded whenever settings or the source files have changed
|
||||||
@@ -471,10 +473,11 @@ class WidgetAPIProductList(EventListMixin, View):
|
|||||||
availability['color'] = 'red'
|
availability['color'] = 'red'
|
||||||
availability['text'] = gettext('Sale over')
|
availability['text'] = gettext('Sale over')
|
||||||
availability['reason'] = 'over'
|
availability['reason'] = 'over'
|
||||||
elif event.settings.presale_start_show_date and ev.presale_start:
|
elif event.settings.presale_start_show_date and ev.effective_presale_start:
|
||||||
availability['color'] = 'orange'
|
availability['color'] = 'orange'
|
||||||
availability['text'] = gettext('from %(start_date)s') % {
|
availability['text'] = gettext('from %(start_date)s') % {
|
||||||
'start_date': date_format(ev.presale_start.astimezone(tz or event.timezone), "SHORT_DATE_FORMAT")
|
'start_date': date_format(ev.effective_presale_start.astimezone(tz or event.timezone),
|
||||||
|
"SHORT_DATE_FORMAT")
|
||||||
}
|
}
|
||||||
availability['reason'] = 'soon'
|
availability['reason'] = 'soon'
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -848,6 +848,9 @@ $table-bg-accent: rgba(128, 128, 128, 0.05);
|
|||||||
outline: 2px solid $brand-primary;
|
outline: 2px solid $brand-primary;
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
&:not([open]) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.pretix-widget-frame-isloading:focus {
|
.pretix-widget-frame-isloading:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[flake8]
|
[flake8]
|
||||||
ignore = N802,W503,E402,C901,E722,W504,E252,N812,N806,N818,E741
|
ignore = N802,W503,E402,C901,E722,W504,E252,N812,N806,N818,E741
|
||||||
max-line-length = 160
|
max-line-length = 160
|
||||||
exclude = migrations,.ropeproject,static,mt940.py,_static,build,make_testdata.py,*/testutils/settings.py,tests/settings.py,pretix/base/models/__init__.py,pretix/base/secretgenerators/pretix_sig1_pb2.py,.eggs/*
|
exclude = data/*,migrations,.ropeproject,static,mt940.py,_static,build,make_testdata.py,*/testutils/settings.py,tests/settings.py,pretix/base/models/__init__.py,pretix/base/secretgenerators/pretix_sig1_pb2.py,.eggs/*
|
||||||
max-complexity = 11
|
max-complexity = 11
|
||||||
|
|
||||||
[isort]
|
[isort]
|
||||||
@@ -13,7 +13,7 @@ extra_standard_library = typing,enum,mimetypes
|
|||||||
multi_line_output = 5
|
multi_line_output = 5
|
||||||
line_length = 79
|
line_length = 79
|
||||||
honor_noqa = true
|
honor_noqa = true
|
||||||
skip_glob = make_testdata.py,wsgi.py,bootstrap,celery_app.py,pretix/settings.py,tests/settings.py,pretix/testutils/settings.py,.eggs/**
|
skip_glob = data/**,make_testdata.py,wsgi.py,bootstrap,celery_app.py,pretix/settings.py,tests/settings.py,pretix/testutils/settings.py,.eggs/**
|
||||||
|
|
||||||
[tool:pytest]
|
[tool:pytest]
|
||||||
DJANGO_SETTINGS_MODULE = tests.settings
|
DJANGO_SETTINGS_MODULE = tests.settings
|
||||||
|
|||||||
@@ -745,6 +745,8 @@ def test_use_membership(event, customer, membership, requiring_ticket):
|
|||||||
item=requiring_ticket, price=23, expires=now() + timedelta(days=1), event=event, cart_id="123",
|
item=requiring_ticket, price=23, expires=now() + timedelta(days=1), event=event, cart_id="123",
|
||||||
used_membership=membership
|
used_membership=membership
|
||||||
)
|
)
|
||||||
|
q = event.quotas.create(size=None, name="foo")
|
||||||
|
q.items.add(requiring_ticket)
|
||||||
order = _create_order(event, email='dummy@example.org', positions=[cp1],
|
order = _create_order(event, email='dummy@example.org', positions=[cp1],
|
||||||
now_dt=now(),
|
now_dt=now(),
|
||||||
sales_channel=event.organizer.sales_channels.get(identifier="web"),
|
sales_channel=event.organizer.sales_channels.get(identifier="web"),
|
||||||
@@ -767,6 +769,8 @@ def test_use_membership_invalid(event, customer, membership, requiring_ticket):
|
|||||||
membership.date_start -= timedelta(days=100)
|
membership.date_start -= timedelta(days=100)
|
||||||
membership.date_end -= timedelta(days=100)
|
membership.date_end -= timedelta(days=100)
|
||||||
membership.save()
|
membership.save()
|
||||||
|
q = event.quotas.create(size=None, name="foo")
|
||||||
|
q.items.add(requiring_ticket)
|
||||||
cp1 = CartPosition.objects.create(
|
cp1 = CartPosition.objects.create(
|
||||||
item=requiring_ticket, price=23, expires=now() + timedelta(days=1), event=event, cart_id="123",
|
item=requiring_ticket, price=23, expires=now() + timedelta(days=1), event=event, cart_id="123",
|
||||||
used_membership=membership
|
used_membership=membership
|
||||||
|
|||||||
@@ -42,10 +42,8 @@ from django.contrib.auth.tokens import (
|
|||||||
)
|
)
|
||||||
from django.core import mail as djmail
|
from django.core import mail as djmail
|
||||||
from django.test import RequestFactory, TestCase, override_settings
|
from django.test import RequestFactory, TestCase, override_settings
|
||||||
from django.utils.crypto import get_random_string
|
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django_otp.oath import TOTP
|
from django_otp.oath import TOTP
|
||||||
from django_otp.plugins.otp_static.models import StaticDevice
|
|
||||||
from django_otp.plugins.otp_totp.models import TOTPDevice
|
from django_otp.plugins.otp_totp.models import TOTPDevice
|
||||||
from webauthn.authentication.verify_authentication_response import (
|
from webauthn.authentication.verify_authentication_response import (
|
||||||
VerifiedAuthentication,
|
VerifiedAuthentication,
|
||||||
@@ -494,20 +492,6 @@ class Login2FAFormTest(TestCase):
|
|||||||
|
|
||||||
m.undo()
|
m.undo()
|
||||||
|
|
||||||
def test_recovery_code_valid(self):
|
|
||||||
djmail.outbox = []
|
|
||||||
d, __ = StaticDevice.objects.get_or_create(user=self.user, name='emergency')
|
|
||||||
token = d.token_set.create(token=get_random_string(length=12, allowed_chars='1234567890'))
|
|
||||||
|
|
||||||
response = self.client.get('/control/login/2fa')
|
|
||||||
assert 'token' in response.content.decode()
|
|
||||||
response = self.client.post('/control/login/2fa', {
|
|
||||||
'token': token.token,
|
|
||||||
})
|
|
||||||
self.assertEqual(response.status_code, 302)
|
|
||||||
self.assertIn('/control/', response['Location'])
|
|
||||||
assert "recovery code" in djmail.outbox[0].body
|
|
||||||
|
|
||||||
|
|
||||||
class FakeRedis(object):
|
class FakeRedis(object):
|
||||||
def get_redis_connection(self, connection_string):
|
def get_redis_connection(self, connection_string):
|
||||||
|
|||||||
@@ -1428,6 +1428,27 @@ class CartTest(CartTestMixin, TestCase):
|
|||||||
self.assertEqual(cp2.expires, now() + self.cart_reservation_time)
|
self.assertEqual(cp2.expires, now() + self.cart_reservation_time)
|
||||||
self.assertEqual(cp2.max_extend, now() + 11 * self.cart_reservation_time)
|
self.assertEqual(cp2.max_extend, now() + 11 * self.cart_reservation_time)
|
||||||
|
|
||||||
|
def test_expired_cart_extend_price_change_note(self):
|
||||||
|
start_time = datetime.datetime(2024, 1, 1, 10, 00, 00, tzinfo=datetime.timezone.utc)
|
||||||
|
max_extend = start_time + 11 * self.cart_reservation_time
|
||||||
|
with scopes_disabled():
|
||||||
|
cp1 = CartPosition.objects.create(
|
||||||
|
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||||
|
price=23, expires=max_extend, max_extend=max_extend
|
||||||
|
)
|
||||||
|
cp1.update_listed_price_and_voucher()
|
||||||
|
self.ticket.default_price = Decimal("25.00")
|
||||||
|
self.ticket.save()
|
||||||
|
with freezegun.freeze_time(max_extend + timedelta(hours=1)):
|
||||||
|
response = self.client.post('/%s/%s/cart/extend' % (self.orga.slug, self.event.slug), {
|
||||||
|
}, follow=True)
|
||||||
|
doc = BeautifulSoup(response.rendered_content, "lxml")
|
||||||
|
self.assertIn('some of the prices in your cart changed', doc.select('.alert-success')[0].text)
|
||||||
|
with scopes_disabled():
|
||||||
|
cp1.refresh_from_db()
|
||||||
|
self.assertEqual(cp1.price, Decimal("25.00"))
|
||||||
|
self.assertEqual(cp1.expires, now() + self.cart_reservation_time)
|
||||||
|
|
||||||
def test_expired_cart_extend_fails_partially_on_bundled(self):
|
def test_expired_cart_extend_fails_partially_on_bundled(self):
|
||||||
start_time = datetime.datetime(2024, 1, 1, 10, 00, 00, tzinfo=datetime.timezone.utc)
|
start_time = datetime.datetime(2024, 1, 1, 10, 00, 00, tzinfo=datetime.timezone.utc)
|
||||||
max_extend = start_time + 11 * self.cart_reservation_time
|
max_extend = start_time + 11 * self.cart_reservation_time
|
||||||
@@ -2709,7 +2730,6 @@ class CartAddonTest(CartTestMixin, TestCase):
|
|||||||
item=self.workshop1, price=Decimal('0.00'),
|
item=self.workshop1, price=Decimal('0.00'),
|
||||||
event=self.event, cart_id=self.session_key, addon_to=cp1
|
event=self.event, cart_id=self.session_key, addon_to=cp1
|
||||||
)
|
)
|
||||||
self.cm.extend_expired_positions()
|
|
||||||
self.cm.commit()
|
self.cm.commit()
|
||||||
cp2.refresh_from_db()
|
cp2.refresh_from_db()
|
||||||
assert cp2.expires > now()
|
assert cp2.expires > now()
|
||||||
@@ -2732,7 +2752,6 @@ class CartAddonTest(CartTestMixin, TestCase):
|
|||||||
item=self.workshop1, price=Decimal('0.00'),
|
item=self.workshop1, price=Decimal('0.00'),
|
||||||
event=self.event, cart_id=self.session_key, addon_to=cp1
|
event=self.event, cart_id=self.session_key, addon_to=cp1
|
||||||
)
|
)
|
||||||
self.cm.extend_expired_positions()
|
|
||||||
with self.assertRaises(CartError):
|
with self.assertRaises(CartError):
|
||||||
self.cm.commit()
|
self.cm.commit()
|
||||||
assert CartPosition.objects.count() == 0
|
assert CartPosition.objects.count() == 0
|
||||||
@@ -3400,7 +3419,6 @@ class CartAddonTest(CartTestMixin, TestCase):
|
|||||||
item=self.workshop1, price=Decimal('12.00'),
|
item=self.workshop1, price=Decimal('12.00'),
|
||||||
event=self.event, cart_id=self.session_key, addon_to=cp1
|
event=self.event, cart_id=self.session_key, addon_to=cp1
|
||||||
)
|
)
|
||||||
self.cm.extend_expired_positions()
|
|
||||||
self.cm.commit()
|
self.cm.commit()
|
||||||
cp1.refresh_from_db()
|
cp1.refresh_from_db()
|
||||||
cp2.refresh_from_db()
|
cp2.refresh_from_db()
|
||||||
@@ -3408,16 +3426,30 @@ class CartAddonTest(CartTestMixin, TestCase):
|
|||||||
assert cp2.expires > now()
|
assert cp2.expires > now()
|
||||||
assert cp2.addon_to_id == cp1.pk
|
assert cp2.addon_to_id == cp1.pk
|
||||||
|
|
||||||
|
@classscope(attr='orga')
|
||||||
|
def test_expand_expired_price_change(self):
|
||||||
|
cp1 = CartPosition.objects.create(
|
||||||
|
expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time,
|
||||||
|
item=self.ticket, price=Decimal('23.00'),
|
||||||
|
event=self.event, cart_id=self.session_key
|
||||||
|
)
|
||||||
|
self.ticket.default_price = Decimal("25.00")
|
||||||
|
self.ticket.save()
|
||||||
|
self.cm.commit()
|
||||||
|
cp1.refresh_from_db()
|
||||||
|
assert cp1.expires > now()
|
||||||
|
assert cp1.listed_price == Decimal("25.00")
|
||||||
|
assert cp1.price == Decimal("25.00")
|
||||||
|
|
||||||
@classscope(attr='orga')
|
@classscope(attr='orga')
|
||||||
def test_expand_expired_refresh_voucher(self):
|
def test_expand_expired_refresh_voucher(self):
|
||||||
v = Voucher.objects.create(item=self.ticket, value=Decimal('20.00'), event=self.event, price_mode='set',
|
v = Voucher.objects.create(item=self.ticket, value=Decimal('20.00'), event=self.event, price_mode='set',
|
||||||
valid_until=now() + timedelta(days=2), max_usages=999, redeemed=0)
|
valid_until=now() + timedelta(days=2), max_usages=1, redeemed=0)
|
||||||
cp1 = CartPosition.objects.create(
|
cp1 = CartPosition.objects.create(
|
||||||
expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time,
|
expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time,
|
||||||
item=self.ticket, price=Decimal('21.50'),
|
item=self.ticket, price=Decimal('21.50'),
|
||||||
event=self.event, cart_id=self.session_key, voucher=v
|
event=self.event, cart_id=self.session_key, voucher=v
|
||||||
)
|
)
|
||||||
self.cm.extend_expired_positions()
|
|
||||||
self.cm.commit()
|
self.cm.commit()
|
||||||
cp1.refresh_from_db()
|
cp1.refresh_from_db()
|
||||||
assert cp1.expires > now()
|
assert cp1.expires > now()
|
||||||
@@ -4080,6 +4112,8 @@ class CartBundleTest(CartTestMixin, TestCase):
|
|||||||
|
|
||||||
@classscope(attr='orga')
|
@classscope(attr='orga')
|
||||||
def test_extend_bundled_and_addon(self):
|
def test_extend_bundled_and_addon(self):
|
||||||
|
self.trans.require_bundling = False
|
||||||
|
self.trans.save()
|
||||||
cp = CartPosition.objects.create(
|
cp = CartPosition.objects.create(
|
||||||
event=self.event, cart_id=self.session_key, item=self.ticket,
|
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||||
price=21.5, expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time
|
price=21.5, expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time
|
||||||
|
|||||||
@@ -2669,7 +2669,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
|||||||
self.event.has_subevents = True
|
self.event.has_subevents = True
|
||||||
self.event.save()
|
self.event.save()
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
se = self.event.subevents.create(name='Foo', date_from=now())
|
se = self.event.subevents.create(name='Foo', date_from=now(), active=True)
|
||||||
q = se.quotas.create(name="foo", size=None, event=self.event)
|
q = se.quotas.create(name="foo", size=None, event=self.event)
|
||||||
q.items.add(self.ticket)
|
q.items.add(self.ticket)
|
||||||
cr1 = CartPosition.objects.create(
|
cr1 = CartPosition.objects.create(
|
||||||
@@ -2839,8 +2839,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
|||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
self.event.has_subevents = True
|
self.event.has_subevents = True
|
||||||
self.event.save()
|
self.event.save()
|
||||||
se = self.event.subevents.create(name='Foo', date_from=now())
|
se = self.event.subevents.create(name='Foo', date_from=now(), active=True)
|
||||||
se2 = self.event.subevents.create(name='Foo', date_from=now())
|
se2 = self.event.subevents.create(name='Foo', date_from=now(), active=True)
|
||||||
self.quota_tickets.size = 0
|
self.quota_tickets.size = 0
|
||||||
self.quota_tickets.subevent = se2
|
self.quota_tickets.subevent = se2
|
||||||
self.quota_tickets.save()
|
self.quota_tickets.save()
|
||||||
@@ -2880,7 +2880,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
|||||||
self.event.has_subevents = True
|
self.event.has_subevents = True
|
||||||
self.event.save()
|
self.event.save()
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
se = self.event.subevents.create(name='Foo', date_from=now())
|
se = self.event.subevents.create(name='Foo', date_from=now(), active=True)
|
||||||
q = se.quotas.create(name="foo", size=None, event=self.event)
|
q = se.quotas.create(name="foo", size=None, event=self.event)
|
||||||
q.items.add(self.ticket)
|
q.items.add(self.ticket)
|
||||||
SubEventItem.objects.create(subevent=se, item=self.ticket, price=24)
|
SubEventItem.objects.create(subevent=se, item=self.ticket, price=24)
|
||||||
@@ -2901,7 +2901,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
|||||||
self.event.has_subevents = True
|
self.event.has_subevents = True
|
||||||
self.event.save()
|
self.event.save()
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
se = self.event.subevents.create(name='Foo', date_from=now())
|
se = self.event.subevents.create(name='Foo', date_from=now(), active=True)
|
||||||
q = se.quotas.create(name="foo", size=None, event=self.event)
|
q = se.quotas.create(name="foo", size=None, event=self.event)
|
||||||
q.items.add(self.ticket)
|
q.items.add(self.ticket)
|
||||||
SubEventItem.objects.create(subevent=se, item=self.ticket, price=24, disabled=True)
|
SubEventItem.objects.create(subevent=se, item=self.ticket, price=24, disabled=True)
|
||||||
@@ -2919,7 +2919,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
|||||||
self.event.has_subevents = True
|
self.event.has_subevents = True
|
||||||
self.event.save()
|
self.event.save()
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
se = self.event.subevents.create(name='Foo', date_from=now())
|
se = self.event.subevents.create(name='Foo', date_from=now(), active=True)
|
||||||
q = se.quotas.create(name="foo", size=None, event=self.event)
|
q = se.quotas.create(name="foo", size=None, event=self.event)
|
||||||
q.items.add(self.workshop2)
|
q.items.add(self.workshop2)
|
||||||
q.variations.add(self.workshop2b)
|
q.variations.add(self.workshop2b)
|
||||||
@@ -2938,7 +2938,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
|||||||
self.event.has_subevents = True
|
self.event.has_subevents = True
|
||||||
self.event.save()
|
self.event.save()
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
se = self.event.subevents.create(name='Foo', date_from=now())
|
se = self.event.subevents.create(name='Foo', date_from=now(), active=True)
|
||||||
q = se.quotas.create(name="foo", size=None, event=self.event)
|
q = se.quotas.create(name="foo", size=None, event=self.event)
|
||||||
q.items.add(self.ticket)
|
q.items.add(self.ticket)
|
||||||
SubEventItem.objects.create(subevent=se, item=self.ticket, price=24, available_until=now() - timedelta(days=1))
|
SubEventItem.objects.create(subevent=se, item=self.ticket, price=24, available_until=now() - timedelta(days=1))
|
||||||
@@ -2956,7 +2956,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
|||||||
self.event.has_subevents = True
|
self.event.has_subevents = True
|
||||||
self.event.save()
|
self.event.save()
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
se = self.event.subevents.create(name='Foo', date_from=now())
|
se = self.event.subevents.create(name='Foo', date_from=now(), active=True)
|
||||||
q = se.quotas.create(name="foo", size=None, event=self.event)
|
q = se.quotas.create(name="foo", size=None, event=self.event)
|
||||||
q.items.add(self.workshop2)
|
q.items.add(self.workshop2)
|
||||||
q.variations.add(self.workshop2b)
|
q.variations.add(self.workshop2b)
|
||||||
@@ -3735,8 +3735,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
|||||||
self.event.has_subevents = True
|
self.event.has_subevents = True
|
||||||
self.event.save()
|
self.event.save()
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
se = self.event.subevents.create(name='Foo', date_from=now())
|
se = self.event.subevents.create(name='Foo', date_from=now(), active=True)
|
||||||
se2 = self.event.subevents.create(name='Foo', date_from=now())
|
se2 = self.event.subevents.create(name='Foo', date_from=now(), active=True)
|
||||||
self.quota_tickets.size = 10
|
self.quota_tickets.size = 10
|
||||||
self.quota_tickets.subevent = se2
|
self.quota_tickets.subevent = se2
|
||||||
self.quota_tickets.save()
|
self.quota_tickets.save()
|
||||||
@@ -4165,7 +4165,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
|||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
self.event.has_subevents = True
|
self.event.has_subevents = True
|
||||||
self.event.save()
|
self.event.save()
|
||||||
se = self.event.subevents.create(name='Foo', date_from=now())
|
se = self.event.subevents.create(name='Foo', date_from=now(), active=True)
|
||||||
self.workshopquota.size = 1
|
self.workshopquota.size = 1
|
||||||
self.workshopquota.subevent = se
|
self.workshopquota.subevent = se
|
||||||
self.workshopquota.save()
|
self.workshopquota.save()
|
||||||
@@ -4214,7 +4214,10 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
|||||||
self.event.has_subevents = True
|
self.event.has_subevents = True
|
||||||
self.event.settings.display_net_prices = True
|
self.event.settings.display_net_prices = True
|
||||||
self.event.save()
|
self.event.save()
|
||||||
se = self.event.subevents.create(name='Foo', date_from=now(), presale_start=now() + datetime.timedelta(days=1))
|
se = self.event.subevents.create(name='Foo', date_from=now(), presale_start=now() + datetime.timedelta(days=1),
|
||||||
|
active=True)
|
||||||
|
q = se.quotas.create(name="foo", size=None, event=self.event)
|
||||||
|
q.items.add(self.ticket)
|
||||||
CartPosition.objects.create(
|
CartPosition.objects.create(
|
||||||
event=self.event, cart_id=self.session_key, item=self.ticket,
|
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||||
price=23, expires=now() + timedelta(minutes=10), subevent=se
|
price=23, expires=now() + timedelta(minutes=10), subevent=se
|
||||||
@@ -4224,7 +4227,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
|||||||
response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
|
response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
|
||||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||||
self.assertGreaterEqual(len(doc.select(".alert-danger")), 1)
|
self.assertGreaterEqual(len(doc.select(".alert-danger")), 1)
|
||||||
assert 'booking period for one of the events in your cart has not yet started.' in response.content.decode()
|
assert 'booking period for this event has not yet started.' in response.content.decode()
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
assert not CartPosition.objects.filter(cart_id=self.session_key).exists()
|
assert not CartPosition.objects.filter(cart_id=self.session_key).exists()
|
||||||
|
|
||||||
@@ -4233,7 +4236,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
|||||||
self.event.has_subevents = True
|
self.event.has_subevents = True
|
||||||
self.event.settings.display_net_prices = True
|
self.event.settings.display_net_prices = True
|
||||||
self.event.save()
|
self.event.save()
|
||||||
se = self.event.subevents.create(name='Foo', date_from=now(), presale_end=now() - datetime.timedelta(days=1))
|
se = self.event.subevents.create(name='Foo', date_from=now(), presale_end=now() - datetime.timedelta(days=1), active=True)
|
||||||
CartPosition.objects.create(
|
CartPosition.objects.create(
|
||||||
event=self.event, cart_id=self.session_key, item=self.ticket,
|
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||||
price=23, expires=now() + timedelta(minutes=10), subevent=se
|
price=23, expires=now() + timedelta(minutes=10), subevent=se
|
||||||
@@ -4243,7 +4246,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
|||||||
response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
|
response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
|
||||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||||
self.assertGreaterEqual(len(doc.select(".alert-danger")), 1)
|
self.assertGreaterEqual(len(doc.select(".alert-danger")), 1)
|
||||||
assert 'booking period for one of the events in your cart has ended.' in response.content.decode()
|
assert 'The booking period for this event has ended.' in response.content.decode()
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
assert not CartPosition.objects.filter(cart_id=self.session_key).exists()
|
assert not CartPosition.objects.filter(cart_id=self.session_key).exists()
|
||||||
|
|
||||||
@@ -4253,7 +4256,9 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
|||||||
self.event.settings.display_net_prices = True
|
self.event.settings.display_net_prices = True
|
||||||
self.event.save()
|
self.event.save()
|
||||||
self.event.settings.payment_term_last = 'RELDATE/1/23:59:59/date_from/'
|
self.event.settings.payment_term_last = 'RELDATE/1/23:59:59/date_from/'
|
||||||
se = self.event.subevents.create(name='Foo', date_from=now())
|
se = self.event.subevents.create(name='Foo', date_from=now(), active=True)
|
||||||
|
q = se.quotas.create(name="foo", size=None, event=self.event)
|
||||||
|
q.items.add(self.ticket)
|
||||||
CartPosition.objects.create(
|
CartPosition.objects.create(
|
||||||
event=self.event, cart_id=self.session_key, item=self.ticket,
|
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||||
price=23, expires=now() + timedelta(minutes=10), subevent=se
|
price=23, expires=now() + timedelta(minutes=10), subevent=se
|
||||||
@@ -4263,7 +4268,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
|||||||
response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
|
response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
|
||||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||||
self.assertGreaterEqual(len(doc.select(".alert-danger")), 1)
|
self.assertGreaterEqual(len(doc.select(".alert-danger")), 1)
|
||||||
assert 'booking period for one of the events in your cart has ended.' in response.content.decode()
|
assert 'All payments for this event need to be confirmed already, so no new orders can be created.' in response.content.decode()
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
assert not CartPosition.objects.filter(cart_id=self.session_key).exists()
|
assert not CartPosition.objects.filter(cart_id=self.session_key).exists()
|
||||||
|
|
||||||
@@ -4272,7 +4277,10 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
|||||||
self.event.date_to = now() - datetime.timedelta(days=1)
|
self.event.date_to = now() - datetime.timedelta(days=1)
|
||||||
self.event.save()
|
self.event.save()
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
se = self.event.subevents.create(name='Foo', date_from=now(), presale_end=now() + datetime.timedelta(days=1))
|
se = self.event.subevents.create(name='Foo', date_from=now(), presale_end=now() + datetime.timedelta(days=1),
|
||||||
|
active=True)
|
||||||
|
q = se.quotas.create(name="foo", size=None, event=self.event)
|
||||||
|
q.items.add(self.ticket)
|
||||||
CartPosition.objects.create(
|
CartPosition.objects.create(
|
||||||
event=self.event, cart_id=self.session_key, item=self.ticket,
|
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||||
price=23, expires=now() + timedelta(minutes=10), subevent=se
|
price=23, expires=now() + timedelta(minutes=10), subevent=se
|
||||||
@@ -4283,6 +4291,25 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
|||||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||||
self.assertEqual(len(doc.select(".thank-you")), 1)
|
self.assertEqual(len(doc.select(".thank-you")), 1)
|
||||||
|
|
||||||
|
def test_confirm_subevent_disabled(self):
|
||||||
|
with scopes_disabled():
|
||||||
|
self.event.has_subevents = True
|
||||||
|
self.event.settings.display_net_prices = True
|
||||||
|
self.event.save()
|
||||||
|
se = self.event.subevents.create(name='Foo', date_from=now(), active=False)
|
||||||
|
CartPosition.objects.create(
|
||||||
|
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||||
|
price=23, expires=now() + timedelta(minutes=10), subevent=se
|
||||||
|
)
|
||||||
|
|
||||||
|
self._set_payment()
|
||||||
|
response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
|
||||||
|
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||||
|
self.assertGreaterEqual(len(doc.select(".alert-danger")), 1)
|
||||||
|
assert 'selected event date is not active.' in response.content.decode()
|
||||||
|
with scopes_disabled():
|
||||||
|
assert not CartPosition.objects.filter(cart_id=self.session_key).exists()
|
||||||
|
|
||||||
def test_before_presale_timemachine(self):
|
def test_before_presale_timemachine(self):
|
||||||
self._login_with_permission(self.orga)
|
self._login_with_permission(self.orga)
|
||||||
self._enable_test_mode()
|
self._enable_test_mode()
|
||||||
@@ -4497,6 +4524,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
|||||||
self.assertEqual(Order.objects.first().locale, 'de')
|
self.assertEqual(Order.objects.first().locale, 'de')
|
||||||
|
|
||||||
def test_variation_require_approval(self):
|
def test_variation_require_approval(self):
|
||||||
|
self.workshop2.category = None
|
||||||
|
self.workshop2.save()
|
||||||
self.workshop2a.require_approval = True
|
self.workshop2a.require_approval = True
|
||||||
self.workshop2a.save()
|
self.workshop2a.save()
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
@@ -4518,6 +4547,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
|||||||
|
|
||||||
def test_item_with_variations_require_approval(self):
|
def test_item_with_variations_require_approval(self):
|
||||||
self.workshop2.require_approval = True
|
self.workshop2.require_approval = True
|
||||||
|
self.workshop2.category = None
|
||||||
self.workshop2.save()
|
self.workshop2.save()
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
cr1 = CartPosition.objects.create(
|
cr1 = CartPosition.objects.create(
|
||||||
|
|||||||
Reference in New Issue
Block a user