mirror of
https://github.com/pretix/pretix.git
synced 2026-06-10 01:15:05 +00:00
Compare commits
20 Commits
pajowu/fix
...
pajowu/for
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3ba6f17f0 | ||
|
|
4530d864d3 | ||
|
|
b968266611 | ||
|
|
640518c1b3 | ||
|
|
0715144a31 | ||
|
|
58ea7c8656 | ||
|
|
a8fe6f505e | ||
|
|
baeec92203 | ||
|
|
2f9ac05184 | ||
|
|
4beea63b49 | ||
|
|
5e49df0ef6 | ||
|
|
b3bb9fccb5 | ||
|
|
e3ffd66691 | ||
|
|
0f2ebb8687 | ||
|
|
efd887b439 | ||
|
|
8690d65e99 | ||
|
|
5682d3ed56 | ||
|
|
059ff6c99b | ||
|
|
f46fc7fa69 | ||
|
|
3473fa738d |
@@ -31,7 +31,9 @@ from pretix.api.serializers.order import OrderPositionSerializer
|
||||
from pretix.api.serializers.organizer import (
|
||||
CustomerSerializer, GiftCardSerializer,
|
||||
)
|
||||
from pretix.base.models import Order, OrderPosition, ReusableMedium
|
||||
from pretix.base.models import (
|
||||
Device, Order, OrderPosition, ReusableMedium, TeamAPIToken,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -80,8 +82,7 @@ class ReusableMediaSerializer(I18nAwareModelSerializer):
|
||||
)
|
||||
|
||||
if 'linked_orderposition' in self.context['request'].query_params.getlist('expand'):
|
||||
# No additional permission check performed, documented limitation of the permission system
|
||||
# Would get to complex/unusable otherwise since the permission depends on the event
|
||||
# Permission Check performed in to_representation
|
||||
self.fields['linked_orderposition'] = NestedOrderPositionSerializer(read_only=True)
|
||||
else:
|
||||
self.fields['linked_orderposition'] = serializers.PrimaryKeyRelatedField(
|
||||
@@ -117,6 +118,27 @@ class ReusableMediaSerializer(I18nAwareModelSerializer):
|
||||
)
|
||||
return data
|
||||
|
||||
def to_representation(self, instance):
|
||||
r = super().to_representation(instance)
|
||||
request = self.context.get('request')
|
||||
# late permission evaluations for checks that depend on the actual linked events
|
||||
expand_nested = self.context['request'].query_params.getlist('expand')
|
||||
perm_holder = request.auth if isinstance(request.auth, (Device, TeamAPIToken)) else request.user
|
||||
if 'linked_orderposition' in expand_nested:
|
||||
if instance.linked_orderposition is not None:
|
||||
event = instance.linked_orderposition.order.event
|
||||
if not perm_holder.has_event_permission(event.organizer, event, 'event.orders:read', request):
|
||||
r['linked_orderposition'] = {'id': instance.linked_orderposition.id}
|
||||
|
||||
if 'linked_giftcard.owner_ticket' in expand_nested:
|
||||
gc = instance.linked_giftcard
|
||||
if gc is not None and gc.owner_ticket is not None:
|
||||
event = gc.owner_ticket.order.event
|
||||
if not perm_holder.has_event_permission(event.organizer, event, 'event.orders:read', request):
|
||||
r['linked_giftcard']['owner_ticket'] = {'id': instance.linked_giftcard.owner_ticket.id}
|
||||
|
||||
return r
|
||||
|
||||
class Meta:
|
||||
model = ReusableMedium
|
||||
fields = (
|
||||
|
||||
@@ -286,6 +286,19 @@ class GiftCardSerializer(I18nAwareModelSerializer):
|
||||
)
|
||||
return data
|
||||
|
||||
def to_representation(self, instance):
|
||||
r = super().to_representation(instance)
|
||||
request = self.context.get('request')
|
||||
# late permission evaluations for checks that depend on the actual linked events
|
||||
if 'owner_ticket' in self.context['request'].query_params.getlist('expand'):
|
||||
owner_ticket = instance.owner_ticket
|
||||
if owner_ticket:
|
||||
event = owner_ticket.order.event
|
||||
perm_holder = request.auth if isinstance(request.auth, (Device, TeamAPIToken)) else request.user
|
||||
if not perm_holder.has_event_permission(event.organizer, event, 'event.orders:read', request):
|
||||
r['owner_ticket'] = {'id': instance.owner_ticket.id}
|
||||
return r
|
||||
|
||||
class Meta:
|
||||
model = GiftCard
|
||||
fields = ('id', 'secret', 'issuance', 'value', 'currency', 'testmode', 'expires', 'conditions', 'owner_ticket',
|
||||
|
||||
@@ -381,12 +381,15 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
|
||||
resp = HttpResponse(ct.file.file.read(), content_type='text/uri-list')
|
||||
return resp
|
||||
else:
|
||||
resp = FileResponse(ct.file.file, content_type=ct.type)
|
||||
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}{}"'.format(
|
||||
self.request.event.slug.upper(), order.code,
|
||||
provider.identifier, ct.extension
|
||||
return FileResponse(
|
||||
ct.file.file,
|
||||
filename='{}-{}-{}{}'.format(
|
||||
self.request.event.slug.upper(), order.code,
|
||||
provider.identifier, ct.extension
|
||||
),
|
||||
as_attachment=True,
|
||||
content_type=ct.type
|
||||
)
|
||||
return resp
|
||||
|
||||
@action(detail=True, methods=['POST'])
|
||||
def mark_paid(self, request, **kwargs):
|
||||
@@ -1303,14 +1306,17 @@ class EventOrderPositionViewSet(OrderPositionViewSetMixin, viewsets.ModelViewSet
|
||||
raise NotFound()
|
||||
|
||||
ftype, ignored = mimetypes.guess_type(answer.file.name)
|
||||
resp = FileResponse(answer.file, content_type=ftype or 'application/binary')
|
||||
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}-{}"'.format(
|
||||
self.request.event.slug.upper(),
|
||||
pos.order.code,
|
||||
pos.positionid,
|
||||
os.path.basename(answer.file.name).split('.', 1)[1]
|
||||
return FileResponse(
|
||||
answer.file,
|
||||
filename='{}-{}-{}-{}'.format(
|
||||
self.request.event.slug.upper(),
|
||||
pos.order.code,
|
||||
pos.positionid,
|
||||
os.path.basename(answer.file.name).split('.', 1)[1]
|
||||
),
|
||||
as_attachment=True,
|
||||
content_type=ftype or 'application/binary'
|
||||
)
|
||||
return resp
|
||||
|
||||
@action(detail=True, url_name="printlog", url_path="printlog", methods=["POST"])
|
||||
def printlog(self, request, **kwargs):
|
||||
@@ -1365,15 +1371,18 @@ class EventOrderPositionViewSet(OrderPositionViewSetMixin, viewsets.ModelViewSet
|
||||
if hasattr(image_file, 'seek'):
|
||||
image_file.seek(0)
|
||||
|
||||
resp = FileResponse(image_file, content_type=ftype or 'application/binary')
|
||||
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}-{}.{}"'.format(
|
||||
self.request.event.slug.upper(),
|
||||
pos.order.code,
|
||||
pos.positionid,
|
||||
key,
|
||||
extension,
|
||||
return FileResponse(
|
||||
image_file,
|
||||
filename='{}-{}-{}-{}.{}'.format(
|
||||
self.request.event.slug.upper(),
|
||||
pos.order.code,
|
||||
pos.positionid,
|
||||
key,
|
||||
extension,
|
||||
),
|
||||
as_attachment=True,
|
||||
content_type=ftype or 'application/binary'
|
||||
)
|
||||
return resp
|
||||
|
||||
@action(detail=True, url_name='download', url_path='download/(?P<output>[^/]+)')
|
||||
def download(self, request, output, **kwargs):
|
||||
@@ -1399,12 +1408,15 @@ class EventOrderPositionViewSet(OrderPositionViewSetMixin, viewsets.ModelViewSet
|
||||
resp = HttpResponse(ct.file.file.read(), content_type='text/uri-list')
|
||||
return resp
|
||||
else:
|
||||
resp = FileResponse(ct.file.file, content_type=ct.type)
|
||||
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}-{}{}"'.format(
|
||||
self.request.event.slug.upper(), pos.order.code, pos.positionid,
|
||||
provider.identifier, ct.extension
|
||||
return FileResponse(
|
||||
ct.file.file,
|
||||
filename='{}-{}-{}-{}{}'.format(
|
||||
self.request.event.slug.upper(), pos.order.code, pos.positionid,
|
||||
provider.identifier, ct.extension
|
||||
),
|
||||
as_attachment=True,
|
||||
content_type=ct.type
|
||||
)
|
||||
return resp
|
||||
|
||||
@action(detail=True, methods=['POST'])
|
||||
def regenerate_secrets(self, request, **kwargs):
|
||||
@@ -1986,9 +1998,12 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
if not invoice.file:
|
||||
raise RetryException()
|
||||
|
||||
resp = FileResponse(invoice.file.file, content_type='application/pdf')
|
||||
resp['Content-Disposition'] = 'attachment; filename="{}.pdf"'.format(invoice.number)
|
||||
return resp
|
||||
return FileResponse(
|
||||
invoice.file.file,
|
||||
filename='{}.pdf'.format(invoice.number),
|
||||
as_attachment=True,
|
||||
content_type='application/pdf'
|
||||
)
|
||||
|
||||
@action(detail=True, methods=['POST'])
|
||||
def transmit(self, request, **kwargs):
|
||||
|
||||
@@ -251,7 +251,7 @@ def create_connection(address, timeout=socket.getdefaulttimeout(),
|
||||
for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
|
||||
af, socktype, proto, canonname, sa = res
|
||||
|
||||
if not settings.get("MAIL_CUSTOM_SMTP_ALLOW_PRIVATE_NETWORKS", False):
|
||||
if not getattr(settings, "MAIL_CUSTOM_SMTP_ALLOW_PRIVATE_NETWORKS", False):
|
||||
ip_addr = ipaddress.ip_address(sa[0])
|
||||
if ip_addr.is_multicast:
|
||||
raise socket.error(f"Request to multicast address {sa[0]} blocked")
|
||||
|
||||
@@ -58,6 +58,7 @@ from pretix.base.invoicing.transmission import (
|
||||
from pretix.base.models import (
|
||||
ExchangeRate, Invoice, InvoiceAddress, InvoiceLine, Order, OrderFee,
|
||||
)
|
||||
from pretix.base.models.orders import OrderPayment
|
||||
from pretix.base.models.tax import EU_CURRENCIES
|
||||
from pretix.base.services.tasks import (
|
||||
TransactionAwareProfiledEventTask, TransactionAwareTask,
|
||||
@@ -102,7 +103,7 @@ def build_invoice(invoice: Invoice) -> Invoice:
|
||||
introductory = invoice.event.settings.get('invoice_introductory_text', as_type=LazyI18nString)
|
||||
additional = invoice.event.settings.get('invoice_additional_text', as_type=LazyI18nString)
|
||||
footer = invoice.event.settings.get('invoice_footer_text', as_type=LazyI18nString)
|
||||
if lp and lp.payment_provider:
|
||||
if lp and lp.payment_provider and lp.state not in (OrderPayment.PAYMENT_STATE_FAILED, OrderPayment.PAYMENT_STATE_CANCELED):
|
||||
if 'payment' in inspect.signature(lp.payment_provider.render_invoice_text).parameters:
|
||||
payment = str(lp.payment_provider.render_invoice_text(invoice.order, lp))
|
||||
else:
|
||||
|
||||
@@ -1651,7 +1651,7 @@ class OrderChangeManager:
|
||||
raise RuntimeError("Order position has not been created yet. Call commit() first on OrderChangeManager.")
|
||||
return self._positions
|
||||
|
||||
def __init__(self, order: Order, user=None, auth=None, notify=True, reissue_invoice=True, allow_blocked_seats=False):
|
||||
def __init__(self, order: Order, user=None, auth=None, notify=True, reissue_invoice=True, allow_blocked_seats=False, force_reissue_invoice=False):
|
||||
self.order = order
|
||||
self.user = user
|
||||
self.auth = auth
|
||||
@@ -1659,6 +1659,7 @@ class OrderChangeManager:
|
||||
self.split_order = None
|
||||
self.reissue_invoice = reissue_invoice
|
||||
self.allow_blocked_seats = allow_blocked_seats
|
||||
self.force_reissue_invoice = force_reissue_invoice
|
||||
self._committed = False
|
||||
self._totaldiff_guesstimate = 0
|
||||
self._quotadiff = Counter()
|
||||
@@ -2902,25 +2903,29 @@ class OrderChangeManager:
|
||||
}
|
||||
)
|
||||
|
||||
def invoice_should_be_generated_now(self):
|
||||
i = self.order.invoices.filter(is_cancellation=False).last()
|
||||
return (
|
||||
self.event.settings.invoice_generate == "True" or (
|
||||
self.event.settings.invoice_generate == "paid" and
|
||||
self.open_payment is not None and
|
||||
self.open_payment.payment_provider.requires_invoice_immediately
|
||||
) or (
|
||||
self.event.settings.invoice_generate == "paid" and
|
||||
self.order.status == Order.STATUS_PAID
|
||||
) or (
|
||||
# Backwards-compatible behaviour
|
||||
(self.event.settings.invoice_generate not in ("True", "paid") or self.force_reissue_invoice) and
|
||||
i and
|
||||
not i.canceled
|
||||
)
|
||||
)
|
||||
|
||||
def _reissue_invoice(self):
|
||||
i = self.order.invoices.filter(is_cancellation=False).last()
|
||||
if self.reissue_invoice and self._invoice_dirty:
|
||||
order_now_qualified = invoice_qualified(self.order)
|
||||
invoice_should_be_generated_now = (
|
||||
self.event.settings.invoice_generate == "True" or (
|
||||
self.event.settings.invoice_generate == "paid" and
|
||||
self.open_payment is not None and
|
||||
self.open_payment.payment_provider.requires_invoice_immediately
|
||||
) or (
|
||||
self.event.settings.invoice_generate == "paid" and
|
||||
self.order.status == Order.STATUS_PAID
|
||||
) or (
|
||||
# Backwards-compatible behaviour
|
||||
self.event.settings.invoice_generate not in ("True", "paid") and
|
||||
i and
|
||||
not i.canceled
|
||||
)
|
||||
)
|
||||
invoice_should_be_generated_now = self.invoice_should_be_generated_now()
|
||||
invoice_should_be_generated_later = not invoice_should_be_generated_now and (
|
||||
self.event.settings.invoice_generate in ("True", "paid")
|
||||
)
|
||||
|
||||
@@ -763,12 +763,7 @@ class InvoicePreview(EventPermissionRequiredMixin, View):
|
||||
def get(self, request, *args, **kwargs):
|
||||
fname, ftype, fcontent = build_preview_invoice_pdf(request.event)
|
||||
resp = HttpResponse(fcontent, content_type=ftype)
|
||||
if settings.DEBUG:
|
||||
# attachment is more secure as we're dealing with user-generated stuff here, but inline is much more convenient during debugging
|
||||
resp['Content-Disposition'] = 'inline; filename="{}"'.format(fname)
|
||||
resp._csp_ignore = True
|
||||
else:
|
||||
resp['Content-Disposition'] = 'attachment; filename="{}"'.format(fname)
|
||||
resp['Content-Disposition'] = 'inline; filename="{}"'.format(fname)
|
||||
return resp
|
||||
|
||||
|
||||
|
||||
@@ -300,5 +300,4 @@ class SysReportView(AdministratorPermissionRequiredMixin, TemplateView):
|
||||
resp = HttpResponse(data)
|
||||
resp['Content-Type'] = mime
|
||||
resp['Content-Disposition'] = 'inline; filename="{}"'.format(name)
|
||||
resp._csp_ignore = True
|
||||
return resp
|
||||
|
||||
@@ -710,22 +710,26 @@ class OrderDownload(AsyncAction, OrderView):
|
||||
resp = HttpResponseRedirect(value.file.file.read())
|
||||
return resp
|
||||
else:
|
||||
resp = FileResponse(value.file.file, content_type=value.type)
|
||||
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}-{}{}"'.format(
|
||||
self.request.event.slug.upper(), self.order.code, self.order_position.positionid,
|
||||
self.output.identifier, value.extension
|
||||
return FileResponse(
|
||||
value.file.file,
|
||||
filename='{}-{}-{}-{}{}'.format(
|
||||
self.request.event.slug.upper(), self.order.code, self.order_position.positionid,
|
||||
self.output.identifier, value.extension
|
||||
),
|
||||
content_type=value.type
|
||||
)
|
||||
return resp
|
||||
elif isinstance(value, CachedCombinedTicket):
|
||||
if value.type == 'text/uri-list':
|
||||
resp = HttpResponseRedirect(value.file.file.read())
|
||||
return resp
|
||||
else:
|
||||
resp = FileResponse(value.file.file, content_type=value.type)
|
||||
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}{}"'.format(
|
||||
self.request.event.slug.upper(), self.order.code, self.output.identifier, value.extension
|
||||
return FileResponse(
|
||||
value.file.file,
|
||||
filename='{}-{}-{}{}'.format(
|
||||
self.request.event.slug.upper(), self.order.code, self.output.identifier, value.extension
|
||||
),
|
||||
content_type=value.type
|
||||
)
|
||||
return resp
|
||||
else:
|
||||
return redirect(self.get_self_url())
|
||||
|
||||
@@ -1831,15 +1835,15 @@ class InvoiceDownload(EventPermissionRequiredMixin, View):
|
||||
return redirect(self.get_order_url())
|
||||
|
||||
try:
|
||||
resp = FileResponse(self.invoice.file.file, content_type='application/pdf')
|
||||
return FileResponse(
|
||||
self.invoice.file.file,
|
||||
filename='{}.pdf'.format(re.sub("[^a-zA-Z0-9-_.]+", "_", self.invoice.number)),
|
||||
content_type='application/pdf'
|
||||
)
|
||||
except FileNotFoundError:
|
||||
invoice_pdf_task.apply(args=(self.invoice.pk,))
|
||||
return self.get(request, *args, **kwargs)
|
||||
|
||||
resp['Content-Disposition'] = 'inline; filename="{}.pdf"'.format(re.sub("[^a-zA-Z0-9-_.]+", "_", self.invoice.number))
|
||||
resp._csp_ignore = True # Some browser's PDF readers do not work with CSP
|
||||
return resp
|
||||
|
||||
|
||||
class OrderExtend(OrderView):
|
||||
permission = 'event.orders:write'
|
||||
@@ -1945,8 +1949,11 @@ class OrderChange(OrderView):
|
||||
|
||||
@cached_property
|
||||
def other_form(self):
|
||||
return OtherOperationsForm(prefix='other', order=self.order,
|
||||
form = OtherOperationsForm(prefix='other', order=self.order,
|
||||
data=self.request.POST if self.request.method == "POST" else None)
|
||||
ocm = OrderChangeManager(self.order)
|
||||
form.fields['reissue_invoice'].initial = ocm.invoice_should_be_generated_now()
|
||||
return form
|
||||
|
||||
@cached_property
|
||||
def add_position_formset(self):
|
||||
@@ -2173,6 +2180,8 @@ class OrderChange(OrderView):
|
||||
notify=notify,
|
||||
reissue_invoice=self.other_form.cleaned_data['reissue_invoice'] if self.other_form.is_valid() else True,
|
||||
allow_blocked_seats=True,
|
||||
force_reissue_invoice=self.other_form.cleaned_data['reissue_invoice'] if self.other_form.is_valid() else False,
|
||||
|
||||
)
|
||||
form_valid = (self._process_add_fees(ocm) and
|
||||
self._process_add_positions(ocm) and
|
||||
|
||||
@@ -263,12 +263,7 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
|
||||
|
||||
resp = HttpResponse(data, content_type=mimet)
|
||||
ftype = fname.split(".")[-1]
|
||||
if settings.DEBUG:
|
||||
# attachment is more secure as we're dealing with user-generated stuff here, but inline is much more convenient during debugging
|
||||
resp['Content-Disposition'] = 'inline; filename="ticket-preview.{}"'.format(ftype)
|
||||
resp._csp_ignore = True
|
||||
else:
|
||||
resp['Content-Disposition'] = 'attachment; filename="ticket-preview.{}"'.format(ftype)
|
||||
resp['Content-Disposition'] = 'inline; filename="ticket-preview.{}"'.format(ftype)
|
||||
return resp
|
||||
elif "data" in request.POST:
|
||||
if cf:
|
||||
@@ -309,6 +304,5 @@ class FontsCSSView(TemplateView):
|
||||
class PdfView(TemplateView):
|
||||
def get(self, request, *args, **kwargs):
|
||||
cf = get_object_or_404(CachedFile, id=kwargs.get("filename"), filename="background_preview.pdf")
|
||||
resp = FileResponse(cf.file, content_type='application/pdf')
|
||||
resp['Content-Disposition'] = 'attachment; filename="{}"'.format(cf.filename)
|
||||
resp = FileResponse(cf.file, filename=cf.filename, content_type='application/pdf')
|
||||
return resp
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-30 11:22+0000\n"
|
||||
"PO-Revision-Date: 2026-03-31 17:00+0000\n"
|
||||
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
|
||||
"PO-Revision-Date: 2026-04-17 03:00+0000\n"
|
||||
"Last-Translator: Tim <plicnetwork@gmail.com>\n"
|
||||
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"es/>\n"
|
||||
"Language: es\n"
|
||||
@@ -17,7 +17,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.16.2\n"
|
||||
"X-Generator: Weblate 5.17\n"
|
||||
|
||||
#: pretix/_base_settings.py:87
|
||||
msgid "English"
|
||||
@@ -4150,7 +4150,7 @@ msgstr "Se encontraron varios productos coincidentes."
|
||||
#: pretix/base/modelimport_vouchers.py:205 pretix/base/models/items.py:1257
|
||||
#: pretix/base/models/vouchers.py:266 pretix/base/models/waitinglist.py:99
|
||||
msgid "Product variation"
|
||||
msgstr "Variación del producto"
|
||||
msgstr "Variante de producto"
|
||||
|
||||
#: pretix/base/modelimport_orders.py:161
|
||||
msgid "The variation can be specified by its internal ID or full name."
|
||||
@@ -4312,7 +4312,7 @@ msgstr "Ya existe un vale de compra con este código."
|
||||
#: pretix/base/models/vouchers.py:199 pretix/control/views/vouchers.py:121
|
||||
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html:52
|
||||
msgid "Maximum usages"
|
||||
msgstr "Usos máximos"
|
||||
msgstr "Número máximo de usos"
|
||||
|
||||
#: pretix/base/modelimport_vouchers.py:79
|
||||
msgid "The maximum number of usages must be set."
|
||||
@@ -4333,14 +4333,14 @@ msgstr "Reservar entrada con cargo a la cuota"
|
||||
|
||||
#: pretix/base/modelimport_vouchers.py:127 pretix/base/models/vouchers.py:236
|
||||
msgid "Allow to bypass quota"
|
||||
msgstr "Permitir que se anule la cuota"
|
||||
msgstr "Permitir omitir la cuota"
|
||||
|
||||
#: pretix/base/modelimport_vouchers.py:135 pretix/base/models/vouchers.py:242
|
||||
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html:44
|
||||
#: pretix/control/templates/pretixcontrol/vouchers/detail.html:70
|
||||
#: pretix/control/views/vouchers.py:121
|
||||
msgid "Price effect"
|
||||
msgstr "Efecto sobre los precios"
|
||||
msgstr "Efecto en el precio"
|
||||
|
||||
#: pretix/base/modelimport_vouchers.py:150
|
||||
#, python-brace-format
|
||||
@@ -4351,7 +4351,7 @@ msgstr ""
|
||||
|
||||
#: pretix/base/modelimport_vouchers.py:160 pretix/base/models/vouchers.py:248
|
||||
msgid "Voucher value"
|
||||
msgstr "Valor del vale de compra"
|
||||
msgstr "Valor del vale"
|
||||
|
||||
#: pretix/base/modelimport_vouchers.py:165
|
||||
msgid "It is pointless to set a value without a price effect."
|
||||
@@ -4394,7 +4394,7 @@ msgstr "Etiqueta"
|
||||
|
||||
#: pretix/base/modelimport_vouchers.py:334 pretix/base/models/vouchers.py:300
|
||||
msgid "Shows hidden products that match this voucher"
|
||||
msgstr "Mostrar los ocultados productos válidos con este vale de compra"
|
||||
msgstr "Muestra los productos ocultos vinculados a este vale"
|
||||
|
||||
#: pretix/base/modelimport_vouchers.py:343 pretix/base/models/vouchers.py:304
|
||||
msgid "Offer all add-on products for free when redeeming this voucher"
|
||||
@@ -7322,8 +7322,8 @@ msgid ""
|
||||
"If activated, a holder of this voucher code can buy tickets, even if there "
|
||||
"are none left."
|
||||
msgstr ""
|
||||
"Si se activa, un titular de este vale de compra puede comprar entradas, "
|
||||
"incluso si no queda ninguna."
|
||||
"Si se activa, el poseedor de este código de vale podrá comprar entradas "
|
||||
"incluso si no quedan existencias."
|
||||
|
||||
#: pretix/base/models/vouchers.py:257 pretix/control/forms/vouchers.py:69
|
||||
msgid ""
|
||||
@@ -7338,14 +7338,14 @@ msgstr ""
|
||||
|
||||
#: pretix/base/models/vouchers.py:268
|
||||
msgid "This variation of the product select above is being used."
|
||||
msgstr "Esta variación del producto seleccionado arriba está siendo utilizada."
|
||||
msgstr "Se aplica a la variante del producto seleccionado arriba."
|
||||
|
||||
#: pretix/base/models/vouchers.py:277
|
||||
msgid ""
|
||||
"If enabled, the voucher is valid for any product affected by this quota."
|
||||
msgstr ""
|
||||
"Si está habilitado, el vale de compra es válido para cualquier producto "
|
||||
"afectado por esta cuota."
|
||||
"Si se activa, el vale será válido para cualquier producto incluido en esta "
|
||||
"cuota."
|
||||
|
||||
#: pretix/base/models/vouchers.py:284
|
||||
msgid "Specific seat"
|
||||
@@ -7357,16 +7357,16 @@ msgid ""
|
||||
"same value for multiple vouchers, you can get statistics on how many of them "
|
||||
"have been redeemed etc."
|
||||
msgstr ""
|
||||
"Puede utilizar este campo para agrupar múltiples vales de compra. Si "
|
||||
"introduce el mismo valor para varios vales de compra, puede obtener "
|
||||
"estadísticas sobre cuántos de ellos se han canjeado, etc."
|
||||
"Puedes usar este campo para agrupar varios vales. Si introduces el mismo "
|
||||
"valor en distintos vales, podrás obtener estadísticas sobre cuántos se han "
|
||||
"canjeado, etc."
|
||||
|
||||
#: pretix/base/models/vouchers.py:316 pretix/base/permissions.py:242
|
||||
#: pretix/control/navigation.py:289
|
||||
#: pretix/control/templates/pretixcontrol/vouchers/index.html:6
|
||||
#: pretix/control/templates/pretixcontrol/vouchers/index.html:8
|
||||
msgid "Vouchers"
|
||||
msgstr "Vales de compra"
|
||||
msgstr "Vales"
|
||||
|
||||
#: pretix/base/models/vouchers.py:342
|
||||
msgid "You cannot select a quota that belongs to a different event."
|
||||
@@ -13365,7 +13365,7 @@ msgstr "Esto eliminará todos los números de teléfono de los pedidos."
|
||||
|
||||
#: pretix/base/shredder.py:290
|
||||
msgid "Emails"
|
||||
msgstr "Correos electrónicos"
|
||||
msgstr "Correos"
|
||||
|
||||
#: pretix/base/shredder.py:292
|
||||
msgid ""
|
||||
@@ -15170,7 +15170,7 @@ msgstr "Todos los productos"
|
||||
#: pretix/control/views/typeahead.py:780
|
||||
#, python-brace-format
|
||||
msgid "{product} – Any variation"
|
||||
msgstr "{product} - Cualquier variación"
|
||||
msgstr "{product} – Cualquier variación"
|
||||
|
||||
#: pretix/control/forms/filter.py:566 pretix/control/forms/orders.py:862
|
||||
msgctxt "subevent"
|
||||
@@ -15469,7 +15469,7 @@ msgstr "Buscar vale de compra"
|
||||
#: pretix/control/views/vouchers.py:133
|
||||
#, python-brace-format
|
||||
msgid "Any product in quota \"{quota}\""
|
||||
msgstr "Cualquier producto del contingente \"{quota}\""
|
||||
msgstr "Cualquier producto en la cuota \"{quota}\""
|
||||
|
||||
#: pretix/control/forms/filter.py:2440
|
||||
msgid "Refund status"
|
||||
@@ -17068,15 +17068,15 @@ msgstr "ID de butaca específico"
|
||||
|
||||
#: pretix/control/forms/vouchers.py:200 pretix/presale/forms/waitinglist.py:103
|
||||
msgid "Invalid product selected."
|
||||
msgstr "Producto no válido seleccionado."
|
||||
msgstr "Se ha seleccionado un producto no válido."
|
||||
|
||||
#: pretix/control/forms/vouchers.py:225
|
||||
msgid ""
|
||||
"The voucher only matches hidden products but you have not selected that it "
|
||||
"should show them."
|
||||
msgstr ""
|
||||
"El vale de compra solo coincide con productos ocultos pero no has "
|
||||
"seleccionado que los muestre."
|
||||
"El vale solo coincide con productos ocultos, pero no has seleccionado que "
|
||||
"deba mostrarlos."
|
||||
|
||||
#: pretix/control/forms/vouchers.py:271
|
||||
msgid "Codes"
|
||||
@@ -21474,7 +21474,7 @@ msgstr ""
|
||||
#: pretix/plugins/ticketoutputpdf/views.py:172
|
||||
#: pretix/presale/views/customer.py:544 pretix/presale/views/customer.py:597
|
||||
msgid "Your changes have been saved."
|
||||
msgstr "Los cambios se han guardado."
|
||||
msgstr "Se han guardado los cambios."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/plugins.html:34
|
||||
#: pretix/control/templates/pretixcontrol/organizers/plugins.html:34
|
||||
@@ -28698,7 +28698,7 @@ msgstr "Se ha creado la nueva lista de asistentes."
|
||||
#: pretix/plugins/ticketoutputpdf/views.py:132
|
||||
msgid "We could not save your changes. See below for details."
|
||||
msgstr ""
|
||||
"No hemos podido guardar los cambios. Consulte los detalles a continuación."
|
||||
"No se pudieron guardar los cambios. Consulta los detalles a continuación."
|
||||
|
||||
#: pretix/control/views/checkin.py:421 pretix/control/views/checkin.py:458
|
||||
msgid "The requested list does not exist."
|
||||
@@ -30864,7 +30864,7 @@ msgstr ""
|
||||
|
||||
#: pretix/plugins/badges/forms.py:33
|
||||
msgid "Template"
|
||||
msgstr "Plantilla"
|
||||
msgstr "Template"
|
||||
|
||||
#: pretix/plugins/badges/forms.py:34
|
||||
msgid ""
|
||||
|
||||
@@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-30 11:22+0000\n"
|
||||
"PO-Revision-Date: 2026-04-08 18:00+0000\n"
|
||||
"Last-Translator: Hijiri Umemoto <hijiri@umemoto.org>\n"
|
||||
"PO-Revision-Date: 2026-04-20 08:07+0000\n"
|
||||
"Last-Translator: Yasunobu YesNo Kawaguchi <kawaguti@gmail.com>\n"
|
||||
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"ja/>\n"
|
||||
"Language: ja\n"
|
||||
@@ -17,7 +17,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Weblate 5.16.2\n"
|
||||
"X-Generator: Weblate 5.17\n"
|
||||
|
||||
#: pretix/_base_settings.py:87
|
||||
msgid "English"
|
||||
@@ -664,7 +664,7 @@ msgstr "ギフトカードを取引で使用済み"
|
||||
#: pretix/plugins/banktransfer/payment.py:483
|
||||
#: pretix/presale/forms/customer.py:152
|
||||
msgid "This field is required."
|
||||
msgstr "この項目は必須です。"
|
||||
msgstr "このフィールドは必須です。"
|
||||
|
||||
#: pretix/base/addressvalidation.py:213
|
||||
msgid "Enter a postal code in the format XXX."
|
||||
@@ -3800,7 +3800,7 @@ msgstr "単価:{net_price} 税抜 / {gross_price} 税込"
|
||||
#, python-brace-format
|
||||
msgctxt "invoice"
|
||||
msgid "Single price: {price}"
|
||||
msgstr "単価:{price}"
|
||||
msgstr "単価: {price}"
|
||||
|
||||
#: pretix/base/invoicing/pdf.py:947 pretix/base/invoicing/pdf.py:952
|
||||
msgctxt "invoice"
|
||||
@@ -8206,7 +8206,7 @@ msgstr "参加者の呼びかけに使う名前"
|
||||
#: pretix/base/services/placeholders.py:732
|
||||
#: pretix/control/forms/organizer.py:799
|
||||
msgid "Mr Doe"
|
||||
msgstr "山田様"
|
||||
msgstr "山田 太郎"
|
||||
|
||||
#: pretix/base/pdf.py:672 pretix/base/pdf.py:679
|
||||
#: pretix/plugins/badges/exporters.py:501
|
||||
@@ -11001,7 +11001,7 @@ msgstr ""
|
||||
#: pretix/base/settings.py:1869 pretix/base/settings.py:1877
|
||||
#: pretix/presale/templates/pretixpresale/fragment_calendar_nav.html:8
|
||||
msgid "List"
|
||||
msgstr "リスト"
|
||||
msgstr "一覧"
|
||||
|
||||
#: pretix/base/settings.py:1870 pretix/base/settings.py:1878
|
||||
msgid "Week calendar"
|
||||
@@ -12855,7 +12855,7 @@ msgstr "Dr"
|
||||
|
||||
#: pretix/base/settings.py:3819 pretix/base/settings.py:3836
|
||||
msgid "First name"
|
||||
msgstr "名(First Name)"
|
||||
msgstr "名"
|
||||
|
||||
#: pretix/base/settings.py:3820 pretix/base/settings.py:3837
|
||||
msgid "Middle name"
|
||||
@@ -19423,7 +19423,7 @@ msgstr "カスタムチェックインルール"
|
||||
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html:117
|
||||
#: pretix/plugins/sendmail/templates/pretixplugins/sendmail/send_form.html:85
|
||||
msgid "Edit"
|
||||
msgstr "編集する"
|
||||
msgstr "編集"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/checkin/list_edit.html:89
|
||||
msgid "Visualize"
|
||||
@@ -20280,7 +20280,7 @@ msgstr "地理座標"
|
||||
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:271
|
||||
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:275
|
||||
msgid "Optional"
|
||||
msgstr "オプション(必須でない項目)"
|
||||
msgstr "任意"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/fragment_geodata.html:22
|
||||
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html:58
|
||||
@@ -23636,8 +23636,8 @@ msgid ""
|
||||
"this product was part of the discount calculation for a different product in "
|
||||
"this order."
|
||||
msgstr ""
|
||||
"この製品の価格は自動割引により減額されたか、この製品がこの注文の別の製品の割"
|
||||
"引計算の一部になっています。"
|
||||
"自動割引によりこの商品の価格が引き下げられたか、同じ注文内の別の商品に対する"
|
||||
"割引計算の対象になっています。"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/order/index.html:496
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:103
|
||||
@@ -25008,7 +25008,7 @@ msgstr "デバイスの概要"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/organizers/device_edit.html:6
|
||||
msgid "Device:"
|
||||
msgstr "デバイス:"
|
||||
msgstr "デバイス:"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/organizers/device_edit.html:8
|
||||
msgid "Connect a new device"
|
||||
@@ -25897,7 +25897,7 @@ msgstr "二要素認証が無効です"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/organizers/team_members.html:57
|
||||
msgid "invited, pending response"
|
||||
msgstr "招待済み、返答待ち"
|
||||
msgstr "招待済み、応答待ち"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/organizers/team_members.html:59
|
||||
msgid "resend invite"
|
||||
@@ -33303,7 +33303,7 @@ msgstr "本当にStripeアカウントを切断しますか?"
|
||||
|
||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/oauth_disconnect.html:16
|
||||
msgid "Disconnect"
|
||||
msgstr "切断します"
|
||||
msgstr "切断"
|
||||
|
||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/pending.html:6
|
||||
msgid "Payment instructions"
|
||||
|
||||
@@ -216,7 +216,7 @@ class PayView(PaypalOrderView, TemplateView):
|
||||
|
||||
|
||||
@scopes_disabled()
|
||||
@event_permission_required('event.settings.general:write')
|
||||
@event_permission_required('event.settings.payment:write')
|
||||
def isu_return(request, *args, **kwargs):
|
||||
getparams = ['merchantId', 'merchantIdInPayPal', 'permissionsGranted', 'accountStatus', 'consentStatus', 'productIntentID', 'isEmailConfirmed']
|
||||
sessionparams = ['payment_paypal_isu_event', 'payment_paypal_isu_tracking_id']
|
||||
@@ -526,7 +526,7 @@ def webhook(request, *args, **kwargs):
|
||||
return HttpResponse(status=200)
|
||||
|
||||
|
||||
@event_permission_required('event.settings.general:write')
|
||||
@event_permission_required('event.settings.payment:write')
|
||||
@require_POST
|
||||
def isu_disconnect(request, **kwargs):
|
||||
del request.event.settings.payment_paypal_connect_refresh_token
|
||||
|
||||
@@ -91,6 +91,9 @@ event_patterns = [
|
||||
re_path(r'w/(?P<cart_namespace>[a-zA-Z0-9]{16})/cart/add',
|
||||
csrf_exempt(pretix.presale.views.cart.CartAdd.as_view()),
|
||||
name='event.cart.add'),
|
||||
re_path(r'w/(?P<cart_namespace>[a-zA-Z0-9]{16})/cart/create',
|
||||
csrf_exempt(pretix.presale.views.cart.CartCreate.as_view()),
|
||||
name='event.cart.create'),
|
||||
|
||||
re_path(r'unlock/(?P<hash>[a-z0-9]{64})/$', pretix.presale.views.user.UnlockHashView.as_view(),
|
||||
name='event.payment.unlock'),
|
||||
|
||||
@@ -555,6 +555,18 @@ class CartClear(EventViewMixin, CartActionMixin, AsyncAction, View):
|
||||
request.sales_channel.identifier, time_machine_now(default=None))
|
||||
|
||||
|
||||
@method_decorator(allow_cors_if_namespaced, 'dispatch')
|
||||
class CartCreate(EventViewMixin, CartActionMixin, View):
|
||||
def get(self, request, *args, **kwargs):
|
||||
if 'ajax' in self.request.GET:
|
||||
cart_id = get_or_create_cart_id(self.request, create=True)
|
||||
return JsonResponse({
|
||||
'cart_id': cart_id,
|
||||
})
|
||||
else:
|
||||
return redirect_to_url(self.get_success_url())
|
||||
|
||||
|
||||
@method_decorator(allow_frame_if_namespaced, 'dispatch')
|
||||
class CartExtendReservation(EventViewMixin, CartActionMixin, AsyncAction, View):
|
||||
task = extend_cart_reservation
|
||||
@@ -843,9 +855,13 @@ class AnswerDownload(EventViewMixin, View):
|
||||
return Http404()
|
||||
|
||||
ftype, _ = mimetypes.guess_type(answer.file.name)
|
||||
resp = FileResponse(answer.file, content_type=ftype or 'application/binary')
|
||||
resp['Content-Disposition'] = 'attachment; filename="{}-cart-{}"'.format(
|
||||
filename = '{}-cart-{}'.format(
|
||||
self.request.event.slug.upper(),
|
||||
os.path.basename(answer.file.name).split('.', 1)[1]
|
||||
).encode("ascii", "ignore")
|
||||
)
|
||||
resp = FileResponse(
|
||||
answer.file,
|
||||
filename=filename,
|
||||
content_type=ftype or 'application/binary'
|
||||
)
|
||||
return resp
|
||||
|
||||
@@ -1220,30 +1220,26 @@ class OrderDownloadMixin:
|
||||
resp = HttpResponseRedirect(value.file.file.read())
|
||||
return resp
|
||||
else:
|
||||
resp = FileResponse(value.file.file, content_type=value.type)
|
||||
if self.order_position.subevent:
|
||||
# Subevent date in filename improves accessibility e.g. for screen reader users
|
||||
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}-{}-{}{}"'.format(
|
||||
self.request.event.slug.upper(), self.order.code, self.order_position.positionid,
|
||||
self.order_position.subevent.date_from.strftime('%Y_%m_%d'),
|
||||
self.output.identifier, value.extension
|
||||
)
|
||||
else:
|
||||
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}-{}{}"'.format(
|
||||
self.request.event.slug.upper(), self.order.code, self.order_position.positionid,
|
||||
self.output.identifier, value.extension
|
||||
)
|
||||
return resp
|
||||
name_parts = (
|
||||
self.request.event.slug.upper(),
|
||||
self.order.code,
|
||||
str(self.order_position.positionid),
|
||||
self.order_position.subevent.date_from.strftime('%Y_%m_%d') if self.order_position.subevent else None,
|
||||
self.output.identifier
|
||||
)
|
||||
filename = "-".join(filter(None, name_parts)) + value.extension
|
||||
return FileResponse(value.file.file, filename=filename, content_type=value.type)
|
||||
elif isinstance(value, CachedCombinedTicket):
|
||||
if value.type == 'text/uri-list':
|
||||
resp = HttpResponseRedirect(value.file.file.read())
|
||||
return resp
|
||||
else:
|
||||
resp = FileResponse(value.file.file, content_type=value.type)
|
||||
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}{}"'.format(
|
||||
self.request.event.slug.upper(), self.order.code, self.output.identifier, value.extension
|
||||
return FileResponse(
|
||||
value.file.file,
|
||||
filename="{}-{}-{}{}".format(
|
||||
self.request.event.slug.upper(), self.order.code, self.output.identifier, value.extension),
|
||||
content_type=value.type
|
||||
)
|
||||
return resp
|
||||
else:
|
||||
return redirect(self.get_self_url())
|
||||
|
||||
@@ -1383,13 +1379,14 @@ class InvoiceDownload(EventViewMixin, OrderDetailMixin, View):
|
||||
return redirect(self.get_order_url())
|
||||
|
||||
try:
|
||||
resp = FileResponse(invoice.file.file, content_type='application/pdf')
|
||||
return FileResponse(
|
||||
invoice.file.file,
|
||||
filename='{}.pdf'.format(re.sub("[^a-zA-Z0-9-_.]+", "_", invoice.number)),
|
||||
content_type='application/pdf'
|
||||
)
|
||||
except FileNotFoundError:
|
||||
invoice_pdf_task.apply(args=(invoice.pk,))
|
||||
return self.get(request, *args, **kwargs)
|
||||
resp['Content-Disposition'] = 'inline; filename="{}.pdf"'.format(re.sub("[^a-zA-Z0-9-_.]+", "_", invoice.number))
|
||||
resp._csp_ignore = True # Some browser's PDF readers do not work with CSP
|
||||
return resp
|
||||
|
||||
|
||||
class OrderChangeMixin:
|
||||
|
||||
@@ -110,6 +110,10 @@ var setCookie = function (cname, cvalue, exdays) {
|
||||
var d = new Date();
|
||||
d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
|
||||
var expires = "expires=" + d.toUTCString();
|
||||
if (!cvalue) {
|
||||
var expires = "expires=Thu, 01 Jan 1970 00:00:00 GMT";
|
||||
cvalue = "";
|
||||
}
|
||||
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
|
||||
};
|
||||
var getCookie = function (name) {
|
||||
@@ -726,17 +730,16 @@ var shared_methods = {
|
||||
buy_callback: function (data) {
|
||||
if (data.redirect) {
|
||||
if (data.cart_id) {
|
||||
this.$root.cart_id = data.cart_id;
|
||||
setCookie(this.$root.cookieName, data.cart_id, 30);
|
||||
this.$root.set_cart_id(data.cart_id);
|
||||
}
|
||||
if (data.redirect.substr(0, 1) === '/') {
|
||||
data.redirect = this.$root.target_url.replace(/^([^\/]+:\/\/[^\/]+)\/.*$/, "$1") + data.redirect;
|
||||
}
|
||||
var url = data.redirect;
|
||||
if (url.indexOf('?')) {
|
||||
url = url + '&iframe=1&locale=' + lang + '&take_cart_id=' + this.$root.cart_id;
|
||||
url = url + '&iframe=1&locale=' + lang + '&take_cart_id=' + encodeURIComponent(this.$root.get_cart_id());
|
||||
} else {
|
||||
url = url + '?iframe=1&locale=' + lang + '&take_cart_id=' + this.$root.cart_id;
|
||||
url = url + '?iframe=1&locale=' + lang + '&take_cart_id=' + encodeURIComponent(this.$root.get_cart_id());
|
||||
}
|
||||
url += this.$root.consent_parameter;
|
||||
if (this.$root.additionalURLParams) {
|
||||
@@ -779,15 +782,24 @@ var shared_methods = {
|
||||
}
|
||||
},
|
||||
resume: function () {
|
||||
if (!this.$root.get_cart_id() && this.$root.keep_cart) {
|
||||
// create an empty cart whose id we can persist
|
||||
this.$root.create_cart(this.resume)
|
||||
return;
|
||||
}
|
||||
var redirect_url;
|
||||
redirect_url = this.$root.target_url + 'w/' + widget_id + '/';
|
||||
if (this.$root.subevent && !this.$root.cart_id) {
|
||||
if (this.$root.subevent && this.$root.is_button && this.$root.items.length === 0) {
|
||||
// button with subevent but no items
|
||||
redirect_url += this.$root.subevent + '/';
|
||||
}
|
||||
redirect_url += '?iframe=1&locale=' + lang;
|
||||
if (this.$root.cart_id) {
|
||||
redirect_url += '&take_cart_id=' + this.$root.cart_id;
|
||||
if (this.$root.get_cart_id()) {
|
||||
redirect_url += '&take_cart_id=' + encodeURIComponent(this.$root.get_cart_id());
|
||||
if (this.$root.keep_cart) {
|
||||
// make sure the cart-id is used, even if the cart is currently empty
|
||||
redirect_url += '&ajax=1'
|
||||
}
|
||||
}
|
||||
if (this.$root.widget_data) {
|
||||
redirect_url += '&widget_data=' + encodeURIComponent(this.$root.widget_data_json);
|
||||
@@ -1864,12 +1876,11 @@ var shared_root_methods = {
|
||||
if (this.$root.variation_filter) {
|
||||
url += '&variations=' + encodeURIComponent(this.$root.variation_filter);
|
||||
}
|
||||
var cart_id = getCookie(this.cookieName);
|
||||
if (this.$root.voucher_code) {
|
||||
url += '&voucher=' + encodeURIComponent(this.$root.voucher_code);
|
||||
}
|
||||
if (cart_id) {
|
||||
url += "&cart_id=" + encodeURIComponent(cart_id);
|
||||
if (this.$root.get_cart_id()) {
|
||||
url += "&cart_id=" + encodeURIComponent(this.$root.get_cart_id());
|
||||
}
|
||||
if (this.$root.date !== null) {
|
||||
url += "&date=" + this.$root.date.substr(0, 7);
|
||||
@@ -1939,7 +1950,6 @@ var shared_root_methods = {
|
||||
root.display_add_to_cart = data.display_add_to_cart;
|
||||
root.waiting_list_enabled = data.waiting_list_enabled;
|
||||
root.show_variations_expanded = data.show_variations_expanded || !!root.variation_filter;
|
||||
root.cart_id = cart_id;
|
||||
root.cart_exists = data.cart_exists;
|
||||
root.vouchers_exist = data.vouchers_exist;
|
||||
root.has_seating_plan = data.has_seating_plan;
|
||||
@@ -2004,8 +2014,8 @@ var shared_root_methods = {
|
||||
if (this.$root.voucher_code) {
|
||||
redirect_url += '&voucher=' + encodeURIComponent(this.$root.voucher_code);
|
||||
}
|
||||
if (this.$root.cart_id) {
|
||||
redirect_url += '&take_cart_id=' + this.$root.cart_id;
|
||||
if (this.$root.get_cart_id()) {
|
||||
redirect_url += '&take_cart_id=' + encodeURIComponent(this.$root.get_cart_id());
|
||||
}
|
||||
if (this.$root.widget_data) {
|
||||
redirect_url += '&widget_data=' + encodeURIComponent(this.$root.widget_data_json);
|
||||
@@ -2027,7 +2037,28 @@ var shared_root_methods = {
|
||||
this.$root.subevent = event.subevent;
|
||||
this.$root.loading++;
|
||||
this.$root.reload();
|
||||
}
|
||||
},
|
||||
create_cart: function(callback) {
|
||||
var url = this.$root.target_url + 'w/' + widget_id + '/cart/create?ajax=1';
|
||||
|
||||
this.$root.overlay.frame_loading = true;
|
||||
api._getJSON(url, (data) => {
|
||||
this.$root.set_cart_id(data.cart_id);
|
||||
this.$root.overlay.frame_loading = false;
|
||||
callback()
|
||||
}, () => {
|
||||
this.$root.overlay.error_message = strings['cart_error'];
|
||||
this.$root.overlay.frame_loading = false;
|
||||
})
|
||||
},
|
||||
get_cart_id: function() {
|
||||
if (this.$root.keep_cart) {
|
||||
return getCookie(this.$root.cookieName);
|
||||
}
|
||||
},
|
||||
set_cart_id: function(newValue) {
|
||||
setCookie(this.$root.cookieName, newValue, 30);
|
||||
},
|
||||
};
|
||||
|
||||
var shared_root_computed = {
|
||||
@@ -2049,9 +2080,8 @@ var shared_root_computed = {
|
||||
},
|
||||
voucherFormTarget: function () {
|
||||
var form_target = this.target_url + 'w/' + widget_id + '/redeem?iframe=1&locale=' + lang;
|
||||
var cookie = getCookie(this.cookieName);
|
||||
if (cookie) {
|
||||
form_target += "&take_cart_id=" + cookie;
|
||||
if (this.get_cart_id()) {
|
||||
form_target += "&take_cart_id=" + encodeURIComponent(this.get_cart_id());
|
||||
}
|
||||
if (this.subevent) {
|
||||
form_target += "&subevent=" + this.subevent;
|
||||
@@ -2091,9 +2121,8 @@ var shared_root_computed = {
|
||||
checkout_url += '?' + this.$root.additionalURLParams;
|
||||
}
|
||||
var form_target = this.target_url + 'w/' + widget_id + '/cart/add?iframe=1&next=' + encodeURIComponent(checkout_url);
|
||||
var cookie = getCookie(this.cookieName);
|
||||
if (cookie) {
|
||||
form_target += "&take_cart_id=" + cookie;
|
||||
if (this.get_cart_id()) {
|
||||
form_target += "&take_cart_id=" + encodeURIComponent(this.get_cart_id());
|
||||
}
|
||||
form_target += this.$root.consent_parameter
|
||||
return form_target
|
||||
@@ -2329,6 +2358,7 @@ var create_widget = function (element, html_id=null) {
|
||||
has_seating_plan: false,
|
||||
has_seating_plan_waitinglist: false,
|
||||
meta_filter_fields: [],
|
||||
keep_cart: true,
|
||||
}
|
||||
},
|
||||
created: function () {
|
||||
@@ -2366,6 +2396,7 @@ var create_button = function (element, html_id=null) {
|
||||
var raw_items = element.attributes.items ? element.attributes.items.value : "";
|
||||
var skip_ssl = element.attributes["skip-ssl-check"] ? true : false;
|
||||
var disable_iframe = element.attributes["disable-iframe"] ? true : false;
|
||||
var keep_cart = element.attributes["keep-cart"] ? true : false;
|
||||
var button_text = element.innerHTML;
|
||||
var widget_data = JSON.parse(JSON.stringify(window.PretixWidget.widget_data));
|
||||
for (var i = 0; i < element.attributes.length; i++) {
|
||||
@@ -2417,7 +2448,8 @@ var create_button = function (element, html_id=null) {
|
||||
widget_data: widget_data,
|
||||
widget_id: 'pretix-widget-' + widget_id,
|
||||
html_id: html_id,
|
||||
button_text: button_text
|
||||
button_text: button_text,
|
||||
keep_cart: keep_cart || items.length > 0,
|
||||
}
|
||||
},
|
||||
created: function () {
|
||||
@@ -2426,7 +2458,7 @@ var create_button = function (element, html_id=null) {
|
||||
observer.observe(this.$el, observerOptions);
|
||||
},
|
||||
computed: shared_root_computed,
|
||||
methods: shared_root_methods
|
||||
methods: shared_root_methods,
|
||||
});
|
||||
create_overlay(app);
|
||||
return app;
|
||||
@@ -2492,13 +2524,14 @@ window.PretixWidget.open = function (target_url, voucher, subevent, items, widge
|
||||
frame_dismissed: false,
|
||||
widget_data: all_widget_data,
|
||||
widget_id: 'pretix-widget-' + widget_id,
|
||||
button_text: ""
|
||||
button_text: "",
|
||||
keep_cart: true
|
||||
}
|
||||
},
|
||||
created: function () {
|
||||
},
|
||||
computed: shared_root_computed,
|
||||
methods: shared_root_methods
|
||||
methods: shared_root_methods,
|
||||
});
|
||||
create_overlay(app);
|
||||
app.$nextTick(function () {
|
||||
|
||||
@@ -171,6 +171,35 @@ def test_giftcard_detail_expand(token_client, organizer, event, giftcard):
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_giftcard_detail_expand_without_permissions(team, token_client, organizer, event, giftcard):
|
||||
with scopes_disabled():
|
||||
o = Order.objects.create(
|
||||
code='FOO', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING, datetime=now(), expires=now() + timedelta(days=10),
|
||||
sales_channel=event.organizer.sales_channels.get(identifier="web"),
|
||||
total=14, locale='en'
|
||||
)
|
||||
ticket = event.items.create(name='Early-bird ticket', category=None, default_price=23, admission=True,
|
||||
personalized=True)
|
||||
op = o.positions.create(item=ticket, price=Decimal("14"))
|
||||
giftcard.owner_ticket = op
|
||||
giftcard.save()
|
||||
|
||||
team.all_event_permissions = False
|
||||
team.save()
|
||||
|
||||
res = dict(TEST_GC_RES)
|
||||
res["id"] = giftcard.pk
|
||||
res["issuance"] = giftcard.issuance.isoformat().replace('+00:00', 'Z')
|
||||
resp = token_client.get('/api/v1/organizers/{}/giftcards/{}/?expand=owner_ticket'.format(organizer.slug, giftcard.pk))
|
||||
assert resp.status_code == 200
|
||||
|
||||
assert resp.data["owner_ticket"] == {
|
||||
"id": op.pk,
|
||||
}
|
||||
|
||||
|
||||
TEST_GIFTCARD_CREATE_PAYLOAD = {
|
||||
"secret": "DEFABC",
|
||||
"value": "12.00",
|
||||
|
||||
@@ -252,6 +252,76 @@ def test_medium_detail(token_client, organizer, event, medium, giftcard, custome
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_medium_detail_event_permission_missing(token_client, organizer, event, medium, giftcard, customer, team):
|
||||
team.all_organizer_permissions = False
|
||||
team.limit_organizer_permissions = {
|
||||
"organizer.reusablemedia:read": True,
|
||||
"organizer.customers:read": True,
|
||||
"organizer.giftcards:read": True,
|
||||
}
|
||||
team.all_event_permissions = False
|
||||
team.save()
|
||||
|
||||
with scopes_disabled():
|
||||
o = Order.objects.create(
|
||||
code='FOO', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING, datetime=now(), expires=now() + timedelta(days=10),
|
||||
sales_channel=event.organizer.sales_channels.get(identifier="web"),
|
||||
total=14, locale='en'
|
||||
)
|
||||
ticket = event.items.create(name='Early-bird ticket', category=None, default_price=23, admission=True,
|
||||
personalized=True)
|
||||
op = o.positions.create(item=ticket, price=Decimal("14"))
|
||||
medium.linked_orderposition = op
|
||||
medium.linked_giftcard = giftcard
|
||||
medium.customer = customer
|
||||
medium.save()
|
||||
giftcard.owner_ticket = op
|
||||
giftcard.save()
|
||||
|
||||
resp = token_client.get(
|
||||
'/api/v1/organizers/{}/reusablemedia/{}/?expand=linked_giftcard&expand='
|
||||
'linked_giftcard.owner_ticket&expand=linked_orderposition&expand=customer'.format(
|
||||
organizer.slug, medium.pk
|
||||
)
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
|
||||
assert resp.data["linked_orderposition"] == {
|
||||
"id": op.pk,
|
||||
}
|
||||
|
||||
assert resp.data["linked_giftcard"] == {
|
||||
"id": giftcard.pk,
|
||||
"secret": "ABCDEF",
|
||||
"issuance": giftcard.issuance.isoformat().replace("+00:00", "Z"),
|
||||
"value": "23.00",
|
||||
"currency": "EUR",
|
||||
"testmode": False,
|
||||
"expires": None,
|
||||
"conditions": None,
|
||||
"owner_ticket": {"id": op.pk},
|
||||
"issuer": "dummy",
|
||||
}
|
||||
|
||||
assert resp.data["customer"] == {
|
||||
"identifier": customer.identifier,
|
||||
"external_identifier": None,
|
||||
"email": "foo@example.org",
|
||||
"phone": None,
|
||||
"name": "Foo",
|
||||
"name_parts": {"_legacy": "Foo"},
|
||||
"is_active": True,
|
||||
"is_verified": False,
|
||||
"last_login": None,
|
||||
"date_joined": customer.date_joined.isoformat().replace("+00:00", "Z"),
|
||||
"locale": "en",
|
||||
"last_modified": customer.last_modified.isoformat().replace("+00:00", "Z"),
|
||||
"notes": None
|
||||
}
|
||||
|
||||
|
||||
TEST_MEDIUM_CREATE_PAYLOAD = {
|
||||
"type": "barcode",
|
||||
"identifier": "FOOBAR",
|
||||
|
||||
@@ -610,7 +610,7 @@ PRIVATE_IPS_RES = [
|
||||
|
||||
|
||||
@contextmanager
|
||||
def test_mail_connection(res, should_connect, use_ssl):
|
||||
def assert_mail_connection(res, should_connect, use_ssl):
|
||||
with (
|
||||
mock.patch('socket.socket') as mock_socket,
|
||||
mock.patch('socket.getaddrinfo', return_value=res),
|
||||
@@ -638,14 +638,14 @@ def test_mail_connection(res, should_connect, use_ssl):
|
||||
def test_private_smtp_ip(res, use_ssl, settings):
|
||||
settings.EMAIL_CUSTOM_SMTP_BACKEND = 'pretix.base.email.CheckPrivateNetworkSmtpBackend'
|
||||
settings.MAIL_CUSTOM_SMTP_ALLOW_PRIVATE_NETWORKS = False
|
||||
with test_mail_connection(res=res, should_connect=False, use_ssl=use_ssl), pytest.raises(match="Request to .* blocked"):
|
||||
with assert_mail_connection(res=res, should_connect=False, use_ssl=use_ssl), pytest.raises(match="Request to .* blocked"):
|
||||
connection = djmail.get_connection(backend=settings.EMAIL_CUSTOM_SMTP_BACKEND,
|
||||
host="localhost",
|
||||
use_ssl=use_ssl)
|
||||
connection.open()
|
||||
|
||||
settings.MAIL_CUSTOM_SMTP_ALLOW_PRIVATE_NETWORKS = True
|
||||
with test_mail_connection(res=res, should_connect=True, use_ssl=use_ssl):
|
||||
with assert_mail_connection(res=res, should_connect=True, use_ssl=use_ssl):
|
||||
connection = djmail.get_connection(backend=settings.EMAIL_CUSTOM_SMTP_BACKEND,
|
||||
host="localhost",
|
||||
use_ssl=use_ssl)
|
||||
@@ -662,7 +662,7 @@ def test_public_smtp_ip(use_ssl, allow_private, settings):
|
||||
settings.EMAIL_CUSTOM_SMTP_BACKEND = 'pretix.base.email.CheckPrivateNetworkSmtpBackend'
|
||||
settings.MAIL_CUSTOM_SMTP_ALLOW_PRIVATE_NETWORKS = allow_private
|
||||
|
||||
with test_mail_connection(res=[(socket.AF_INET, socket.SOCK_STREAM, 6, '', ('8.8.8.8', 443))], should_connect=True, use_ssl=use_ssl):
|
||||
with assert_mail_connection(res=[(socket.AF_INET, socket.SOCK_STREAM, 6, '', ('8.8.8.8', 443))], should_connect=True, use_ssl=use_ssl):
|
||||
connection = djmail.get_connection(backend=settings.EMAIL_CUSTOM_SMTP_BACKEND,
|
||||
host="localhost",
|
||||
use_ssl=use_ssl)
|
||||
@@ -702,7 +702,7 @@ def test_send_mail_private_ip(res, use_ssl, allow_private_networks, env):
|
||||
m.refresh_from_db()
|
||||
return m
|
||||
|
||||
with test_mail_connection(res=res, should_connect=allow_private_networks, use_ssl=use_ssl):
|
||||
with assert_mail_connection(res=res, should_connect=allow_private_networks, use_ssl=use_ssl):
|
||||
m = send_mail()
|
||||
if allow_private_networks:
|
||||
assert m.status == OutgoingMail.STATUS_SENT
|
||||
|
||||
@@ -2531,6 +2531,31 @@ class OrderChangeManagerTests(BaseOrderChangeManagerTestCase, TestCase):
|
||||
).confirm()
|
||||
assert self.order.invoices.count() == 3
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_force_reissue_invoice_paid(self):
|
||||
self.event.settings.invoice_generate = "paid"
|
||||
generate_invoice(self.order)
|
||||
assert self.order.invoices.count() == 1
|
||||
self.ocm.force_reissue_invoice = True
|
||||
self.ocm.add_position(self.ticket, None, Decimal('2.00'))
|
||||
self.ocm.commit()
|
||||
assert self.order.invoices.count() == 3
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_force_reissue_invoice_paid_only_after_payment_only_if_enabled(self):
|
||||
self.event.settings.invoice_generate = "False"
|
||||
assert self.order.invoices.count() == 0
|
||||
self.ocm.force_reissue_invoice = True
|
||||
self.ocm.add_position(self.ticket, None, Decimal('2.00'))
|
||||
self.ocm.commit()
|
||||
assert self.order.invoices.count() == 0
|
||||
self.order.refresh_from_db()
|
||||
assert not self.order.invoice_dirty
|
||||
self.order.payments.create(
|
||||
provider='manual', amount=self.order.total
|
||||
).confirm()
|
||||
assert self.order.invoices.count() == 0
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_reissue_invoice_paid_only_after_payment_only_if_enabled(self):
|
||||
self.event.settings.invoice_generate = "False"
|
||||
|
||||
Reference in New Issue
Block a user