Compare commits

..
Author SHA1 Message Date
Raphael Michel 47638fdf95 adjust more tests 2026-07-10 15:28:49 +02:00
Raphael Michel d44083ee33 Fix tests 2026-07-10 14:55:09 +02:00
Raphael Michel 92090b896a Precompile schema validation function to get rid of ajv2020 2026-07-10 13:15:29 +02:00
Raphael Michel df2e82e707 Handle safe types centrally 2026-07-10 12:17:44 +02:00
Raphael Michel 899ff4821e Add proper CSP policy for HTML email preview 2026-07-10 12:17:44 +02:00
dependabot[bot] 779ab360df Bump django-formtools from 2.6.1 to 2.7 (#6371)
Bumps [django-formtools](https://github.com/jazzband/django-formtools) from 2.6.1 to 2.7.
- [Changelog](https://github.com/jazzband/django-formtools/blob/master/docs/changelog.rst)
- [Commits](https://github.com/jazzband/django-formtools/compare/2.6.1...2.7)

---
updated-dependencies:
- dependency-name: django-formtools
  dependency-version: '2.7'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-10 12:17:12 +02:00
Raphael Michel 723c63008d Team invite: Improve error message (Z#23239860) (#6368) 2026-07-10 12:13:44 +02:00
Raphael Michelandpajowu 15c194c0a3 Add base class for historical payment providers (#6336)
* Add base class for historical payment providers

* Update src/pretix/base/payment.py

Co-authored-by: pajowu <engelhardt@pretix.eu>

---------

Co-authored-by: pajowu <engelhardt@pretix.eu>
2026-07-10 11:10:32 +02:00
pajowu 7f847c3dd2 Fix TypeError if task.request.headers is None (#6367) 2026-07-09 18:52:49 +02:00
31 changed files with 132 additions and 61 deletions

No files matched your search

+1 -1
View File
@@ -43,7 +43,7 @@ dependencies = [
"django-countries==8.2.*",
"django-filter==25.1",
"django-formset-js-improved==0.5.0.5",
"django-formtools==2.6.1",
"django-formtools==2.7",
"django-hierarkey==2.0.*,>=2.0.1",
"django-hijack==3.7.*",
"django-i18nfield==1.11.*",
+21
View File
@@ -266,6 +266,22 @@ def _merge_csp(a, b):
class SecurityMiddleware(MiddlewareMixin):
SAFE_TYPES = (
# CSP policies are only used for:
# - HTML and SVG in top-level contexts
# - SVG or JS Workers delivered in embedded contexts
# See: https://www.w3.org/TR/CSP2/#which-policy-applies
# Therefore, we can save bandwidth on not including our (sometimes huge) policy
# on API responses or CSS. We do however include it with other types as a precaution
# (whitelist instead of blacklist) and we also do not whitelist JavaScript in
# we ever add service workers to not break the protection of this feature:
# https://www.w3.org/TR/CSP2/#sandboxing-and-workers
'application/json',
'text/css',
# We used to skip CSP for PDF since it was necessary for inline previews in Safari,
# but at the moment it does not seem to be an issue to just send it.
)
def process_response(self, request, resp):
if settings.DEBUG and resp.status_code >= 400:
# Don't use CSP on debug error page as it breaks of Django's fancy error
@@ -277,6 +293,11 @@ class SecurityMiddleware(MiddlewareMixin):
# https://github.com/pretix/pretix/issues/765
resp['P3P'] = 'CP=\"ALL DSP COR CUR ADM TAI OUR IND COM NAV INT\"'
if "Content-Type" in resp and resp["Content-Type"].split(";")[0] in self.SAFE_TYPES:
if 'Content-Security-Policy' in resp:
del resp['Content-Security-Policy']
return resp
if not getattr(resp, '_csp_ignore', False):
resp['Content-Security-Policy'] = _render_csp(self._build_csp(request, resp))
elif 'Content-Security-Policy' in resp:
+54 -2
View File
@@ -936,7 +936,7 @@ class BasePaymentProvider:
"""
Will be called if the *event administrator* views the details of a payment.
It should return a SafeString containing HTML code, with information regarding the current payment
It should return HTML code containing information regarding the current payment
status and, if applicable, next steps.
The default implementation returns an empty string.
@@ -961,7 +961,7 @@ class BasePaymentProvider:
"""
Will be called if the *event administrator* views the details of a refund.
It should return a SafeString containing HTML code, with information regarding the current refund
It should return HTML code containing information regarding the current refund
status and, if applicable, next steps.
The default implementation returns an empty string.
@@ -1706,6 +1706,58 @@ class GiftCardPayment(BasePaymentProvider):
)
class BaseHistoricalPaymentProvider(BasePaymentProvider):
"""
Base class for payment providers that no longer exist but can't be deleted to make sure historical
payments are shown correctly.
Subclasses are recommended to only implement:
- identifier
- verbose_name
- public_name
- payment_control_render
- payment_control_render_short
- refund_control_render
- refund_control_render_short
- render_invoice_text
- render_invoice_stamp
- api_payment_details
- api_refund_details
- shred_payment_info
- matching_id
- refund_matching_id
"""
@property
def is_enabled(self) -> bool:
return False
@property
def settings_form_fields(self) -> dict:
return {}
def is_allowed(self, request: HttpRequest, total: Decimal=None) -> bool:
return False
def payment_is_valid_session(self, request: HttpRequest, payment: OrderPayment):
return False
def order_change_allowed(self, order: Order, request: HttpRequest=None) -> bool:
return False
def payment_refund_supported(self, payment: OrderPayment) -> bool:
return False
def payment_partial_refund_supported(self, payment: OrderPayment) -> bool:
return False
def execute_payment(self, request: HttpRequest, payment: OrderPayment):
raise PaymentException(_("This payment provider exists for historical purposes only and is no longer usable."))
def execute_refund(self, refund: OrderRefund):
raise PaymentException(_("This payment provider exists for historical purposes only and is no longer usable."))
@receiver(register_payment_providers, dispatch_uid="payment_free")
def register_payment_provider(sender, **kwargs):
return [FreeOrderProvider, BoxOfficeProvider, OffsettingProvider, ManualPayment, GiftCardPayment]
+1 -1
View File
@@ -59,7 +59,7 @@ def on_task_prerun(sender, task_id, task, **kwargs):
from pretix.helpers.logs import local
local.request_id = task_id
if "X-Pretix-Trace" in task.request.headers:
if task.request.headers and "X-Pretix-Trace" in task.request.headers:
local.trace = task.request.headers["X-Pretix-Trace"].split(" ")
else:
local.trace = []
@@ -3,7 +3,6 @@
{% load i18n %}
{% load static %}
{% load compress %}
{% load escapejson %}
{% block content %}
<form class="form-signin" action="" method="post" id="webauthn-form">
{% csrf_token %}
@@ -31,7 +30,8 @@
</form>
{% if jsondata %}
<script type="text/json" id="webauthn-login">
{{ jsondata|escapejson }}
{{ jsondata|safe }}
</script>
{% endif %}
{% compress js %}
@@ -2,7 +2,6 @@
{% load i18n %}
{% load bootstrap3 %}
{% load money %}
{% load wrap_in %}
{% block title %}
{% trans "Cancel order" %}
{% endblock %}
@@ -27,7 +26,7 @@
{% if form.cancellation_fee %}
{% if fee %}
{% with fee|money:request.event.currency as f %}
<p>{% blocktrans trimmed with fee=f|wrap_in:"strong" %}
<p>{% blocktrans trimmed with fee="<strong>"|add:f|add:"</strong>"|safe %}
The configured cancellation fee for a self-service cancellation would be {{ fee }} for this
order, but for a cancellation performed by you, you need to set the cancellation fee here:
{% endblocktrans %}</p>
@@ -903,7 +903,7 @@
<tr>
<td colspan="1"></td>
<td colspan="5">
{{ p.html_info }}
{{ p.html_info|safe }}
{% if staff_session %}
<p>
<a href="" class="btn btn-default btn-xs admin-only" data-expandpayment data-id="{{ p.pk }}">
@@ -1018,7 +1018,7 @@
</dl>
{% endif %}
{% if r.html_info %}
{{ r.html_info }}
{{ r.html_info|safe }}
{% endif %}
{% if staff_session %}
<p>
@@ -2,7 +2,6 @@
{% load i18n %}
{% load static %}
{% load bootstrap3 %}
{% load escapejson %}
{% block inner %}
<h1>{% trans "Connect to device:" %} {{ device.name }}</h1>
@@ -19,7 +18,7 @@
{% trans "Open the app that you want to connect and optionally reset it to the original state." %}
</li>
<li>{% trans "Scan the following configuration code:" %}<br><br>
<script type="application/json" data-replace-with-qr>{{ qrdata|escapejson_dumps }}</script><br>
<script type="text/json" data-replace-with-qr>{{ qrdata|safe }}</script><br>
{% trans "If your app/device does not support scanning a QR code, you can also enter the following information:" %}
<br>
<strong>{% trans "System URL:" %}</strong> <code id="system_url">{{ settings.SITE_URL }}</code>
@@ -560,11 +560,10 @@
</div>
</div>
</div>
<script type="text/plain" id="schema-url">{% static "schema/pdf-layout.schema.json" %}</script>
<script type="text/javascript" src="{% static "pdfjs/pdf.js" %}"></script>
<script type="text/javascript" src="{% static "ajv/ajv2020.bundle.min.js" %}"></script>
<script type="text/javascript" src="{% static "fabric/fabric.min.js" %}"></script>
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/editor.js" %}"></script>
<script type="text/javascript" src="{% static "schema/pdf-layout.validate.js" %}"></script>
<img src="{% static 'pretixpresale/pdf/powered_by_pretix_dark.png' %}" id="poweredby-dark" class="sr-only">
<img src="{% static 'pretixpresale/pdf/powered_by_pretix_white.png' %}" id="poweredby-white" class="sr-only">
{% for family, styles in fonts.items %}
@@ -1,7 +1,6 @@
{% extends "pretixcontrol/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% load escapejson %}
{% block title %}{% trans "Add a two-factor authentication device" %}{% endblock %}
{% block content %}
<h1>{% trans "Add a two-factor authentication device" %}</h1>
@@ -33,7 +32,7 @@
</li>
<li>
{% trans "Add a new account to the app by scanning the following barcode:" %}
<script type="application/json" data-replace-with-qr>{{ qrdata|escapejson_dumps }}</script>
<div class="qrcode-canvas" data-qrdata="#qrdata"></div>
<p>
<a data-toggle="collapse" href="#no_scan">
{% trans "Can't scan the barcode?" %}
@@ -82,4 +81,9 @@
</li>
</ol>
<script type="text/json" id="qrdata">
{{ qrdata|safe }}
</script>
{% endblock %}
@@ -3,7 +3,6 @@
{% load bootstrap3 %}
{% load static %}
{% load compress %}
{% load escapejson %}
{% block title %}{% trans "Add a two-factor authentication device" %}{% endblock %}
{% block content %}
<h1>{% trans "Add a two-factor authentication device" %}</h1>
@@ -27,7 +26,9 @@
{% trans "Device registration failed." %}
</div>
<script type="text/json" id="webauthn-enroll">
{{ jsondata|escapejson }}
{{ jsondata|safe }}
</script>
{% compress js %}
<script type="text/javascript" src="{% static "pretixcontrol/js/base64js.js" %}"></script>
@@ -3,7 +3,6 @@
{% load bootstrap3 %}
{% load compress %}
{% load static %}
{% load escapejson %}
{% block content %}
<form class="form-signin" id="webauthn-form" action="" method="post">
{% csrf_token %}
@@ -44,7 +43,7 @@
{% if jsondata %}
<script type="text/json" id="webauthn-login">
{{ jsondata|escapejson }}
{{ jsondata|safe }}
</script>
{% endif %}
{% compress js %}
+2 -1
View File
@@ -243,7 +243,8 @@ def invite(request, token):
if request.user.is_authenticated:
if inv.team.members.filter(pk=request.user.pk).exists():
messages.error(request, _('You cannot accept the invitation for "{}" as you already are part of '
'this team.').format(inv.team.name))
'this team. If you want to add a different user or create a new account, '
'log out and click the invitation link again.').format(inv.team.name))
return redirect('control:index')
else:
with transaction.atomic():
+6 -1
View File
@@ -952,7 +952,12 @@ class MailSettingsRendererPreview(MailSettingsPreview):
context=context,
)
r = HttpResponse(v, content_type='text/html')
r._csp_ignore = True
r['Content-Security-Policy'] = (
# Plugin-provided email templates will contain inline styles or remote images
# but emails should not contain JS
"style-src 'unsafe-inline'; "
"img-src https: data:"
)
return r
else:
raise Http404(_('Unknown email renderer.'))
+2 -2
View File
@@ -551,10 +551,10 @@ class OrderDetail(OrderView):
ctx['refunds'] = self.order.refunds.select_related('payment').order_by('-created')
for p in ctx['payments']:
if p.payment_provider:
p.html_info = p.payment_provider.payment_control_render(self.request, p) or ""
p.html_info = (p.payment_provider.payment_control_render(self.request, p) or "").strip()
for r in ctx['refunds']:
if r.payment_provider:
r.html_info = r.payment_provider.refund_control_render(self.request, r) or ""
r.html_info = (r.payment_provider.refund_control_render(self.request, r) or "").strip()
ctx['invoices'] = list(self.order.invoices.all().select_related('event'))
ctx['comment_form'] = CommentForm(initial={
'comment': self.order.comment,
-1
View File
@@ -70,7 +70,6 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
if 'placeholders' in request.GET:
return self.get_placeholders_help(request)
resp = super().get(request, *args, **kwargs)
resp._csp_ignore = True
return resp
def get_placeholders_help(self, request):
+1 -1
View File
@@ -47,5 +47,5 @@ def escapejson(value):
@keep_lazy(str, SafeText)
def escapejson_attr(value):
"""Hex encodes characters for use in a html attribute."""
"""Hex encodes characters for use in a html attributw script."""
return mark_safe(force_str(value).translate(_json_escapes_attr))
-1
View File
@@ -162,7 +162,6 @@ class XHRView(View):
paypal_order = prov._create_paypal_order(request, None, cart_total)
r = JsonResponse(paypal_order.dict() if paypal_order else {})
r._csp_ignore = True
return r
@@ -323,7 +323,7 @@ $(function () {
}
}
} else if ($("#stripe_payment_intent_next_action_redirect_url").length) {
let payment_intent_next_action_redirect_url = JSON.parse($("#stripe_payment_intent_next_action_redirect_url").html());
let payment_intent_next_action_redirect_url = $.trim($("#stripe_payment_intent_next_action_redirect_url").html());
pretixstripe.handlePaymentRedirectAction(payment_intent_next_action_redirect_url);
} else if ($.trim($("#stripe_payment_intent_action_type").html()) === "promptpay_display_qr_code") {
waitingDialog.hide();
@@ -432,4 +432,4 @@ $(function () {
}
}
);
});
});
@@ -9,7 +9,7 @@
<script type="text/plain" id="stripe_payment_intent_action_type">{{ payment_intent_action_type }}</script>
<script type="text/plain" id="stripe_payment_intent_client_secret">{{ payment_intent_client_secret }}</script>
{% if payment_intent_next_action_redirect_url %}
{{ payment_intent_next_action_redirect_url|json_script:"stripe_payment_intent_next_action_redirect_url" }}
<script type="text/plain" id="stripe_payment_intent_next_action_redirect_url">{{ payment_intent_next_action_redirect_url|safe }}</script>
{% endif %}
{% if payment_intent_redirect_action_handling %}
<script type="text/plain" id="stripe_payment_intent_redirect_action_handling">{{ payment_intent_redirect_action_handling }}</script>
-1
View File
@@ -48,7 +48,6 @@ def theme_css(request, **kwargs):
obj = getattr(request, "event", request.organizer)
css = get_theme_vars_css(obj, widget=False)
resp = HttpResponse(css, content_type="text/css")
resp._csp_ignore = True
resp["Access-Control-Allow-Origin"] = "*"
if "version" in request.GET:
resp["Expires"] = http_date(time.time() + 3600 * 24 * 30)
-4
View File
@@ -164,7 +164,6 @@ def widget_css(request, version, **kwargs):
css = f"/* v{version} */\n" + theme_css + widget_css
resp = FileResponse(css, content_type='text/css')
resp._csp_ignore = True
resp['Access-Control-Allow-Origin'] = '*'
return resp
@@ -246,7 +245,6 @@ def widget_js(request, version, lang, **kwargs):
cached_js = cache.get(cache_prefix)
if cached_js and not settings.DEBUG:
resp = HttpResponse(cached_js, content_type='text/javascript')
resp._csp_ignore = True
resp['Access-Control-Allow-Origin'] = '*'
return resp
@@ -278,7 +276,6 @@ def widget_js(request, version, lang, **kwargs):
gs.settings.set(checksum_key, checksum)
cache.set(cache_prefix, data, 3600 * 4)
resp = HttpResponse(data, content_type='text/javascript')
resp._csp_ignore = True
resp['Access-Control-Allow-Origin'] = '*'
return resp
@@ -414,7 +411,6 @@ class WidgetAPIProductList(EventListMixin, View):
self.post_process(data)
resp = JsonResponse(data)
resp['Access-Control-Allow-Origin'] = '*'
resp._csp_ignore = True
return resp
def get(self, request, *args, **kwargs):
-7
View File
@@ -1,7 +0,0 @@
!function(e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).ajv2020=e()}(function(){return function a(o,s,n){function i(t,e){if(!s[t]){if(!o[t]){var r="function"==typeof require&&require;if(!e&&r)return r(t,!0);if(c)return c(t,!0);throw(e=new Error("Cannot find module '"+t+"'")).code="MODULE_NOT_FOUND",e}r=s[t]={exports:{}},o[t][0].call(r.exports,function(e){return i(o[t][1][e]||e)},r,r.exports,a,o,s,n)}return s[t].exports}for(var c="function"==typeof require&&require,e=0;e<n.length;e++)i(n[e]);return i}({1:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.regexpCode=r.getEsmExportName=r.getProperty=r.safeStringify=r.stringify=r.strConcat=r.addCodeArg=r.str=r._=r.nil=r._Code=r.Name=r.IDENTIFIER=r._CodeOrName=void 0;class a{}r._CodeOrName=a,r.IDENTIFIER=/^[a-z$_][a-z$_0-9]*$/i;class n extends a{constructor(e){if(super(),!r.IDENTIFIER.test(e))throw new Error("CodeGen: name must be a valid identifier");this.str=e}toString(){return this.str}emptyStr(){return!1}get names(){return{[this.str]:1}}}r.Name=n;class i extends a{constructor(e){super(),this._items="string"==typeof e?[e]:e}toString(){return this.str}emptyStr(){if(1<this._items.length)return!1;var e=this._items[0];return""===e||'""'===e}get str(){var e;return null!=(e=this._str)?e:this._str=this._items.reduce((e,t)=>""+e+t,"")}get names(){var e;return null!=(e=this._names)?e:this._names=this._items.reduce((e,t)=>(t instanceof n&&(e[t.str]=(e[t.str]||0)+1),e),{})}}function o(e,...t){const r=[e[0]];let a=0;for(;a<t.length;)l(r,t[a]),r.push(e[++a]);return new i(r)}r._Code=i,r.nil=new i(""),r._=o;const c=new i("+");function s(e,...t){const r=[d(e[0])];let a=0;for(;a<t.length;)r.push(c),l(r,t[a]),r.push(c,d(e[++a]));{var o=r;let e=1;for(;e<o.length-1;){if(o[e]===c){var s=function(e,t){return'""'===t?e:'""'===e?t:"string"!=typeof e?"string"!=typeof t||'"'!==t[0]||e instanceof n?void 0:'"'+e+t.slice(1):t instanceof n||'"'!==e[e.length-1]?void 0:"string"!=typeof t?""+e.slice(0,-1)+t+'"':'"'===t[0]?e.slice(0,-1)+t.slice(1):void 0}(o[e-1],o[e+1]);if(void 0!==s){o.splice(e-1,3,s);continue}o[e++]="+"}e++}}return new i(r)}function l(e,t){t instanceof i?e.push(...t._items):t instanceof n?e.push(t):e.push("number"==typeof(e=t)||"boolean"==typeof e||null===e?e:d(Array.isArray(e)?e.join(","):e))}function d(e){return JSON.stringify(e).replace(/\u2028/g,"\\u2028").replace(/\u2029/g,"\\u2029")}r.str=s,r.addCodeArg=l,r.strConcat=function(e,t){return t.emptyStr()?e:e.emptyStr()?t:s`${e}${t}`},r.stringify=function(e){return new i(d(e))},r.safeStringify=d,r.getProperty=function(e){return"string"==typeof e&&r.IDENTIFIER.test(e)?new i("."+e):o`[${e}]`},r.getEsmExportName=function(e){if("string"==typeof e&&r.IDENTIFIER.test(e))return new i(""+e);throw new Error(`CodeGen: invalid export name: ${e}, use explicit $id name mapping`)},r.regexpCode=function(e){return new i(e.toString())}},{}],2:[function(e,D,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.or=r.and=r.not=r.CodeGen=r.operators=r.varKinds=r.ValueScopeName=r.ValueScope=r.Scope=r.Name=r.regexpCode=r.stringify=r.getProperty=r.nil=r.strConcat=r.str=r._=void 0;const n=e("./code"),i=e("./scope");var t=e("./code"),a=(Object.defineProperty(r,"_",{enumerable:!0,get:function(){return t._}}),Object.defineProperty(r,"str",{enumerable:!0,get:function(){return t.str}}),Object.defineProperty(r,"strConcat",{enumerable:!0,get:function(){return t.strConcat}}),Object.defineProperty(r,"nil",{enumerable:!0,get:function(){return t.nil}}),Object.defineProperty(r,"getProperty",{enumerable:!0,get:function(){return t.getProperty}}),Object.defineProperty(r,"stringify",{enumerable:!0,get:function(){return t.stringify}}),Object.defineProperty(r,"regexpCode",{enumerable:!0,get:function(){return t.regexpCode}}),Object.defineProperty(r,"Name",{enumerable:!0,get:function(){return t.Name}}),e("./scope"));Object.defineProperty(r,"Scope",{enumerable:!0,get:function(){return a.Scope}}),Object.defineProperty(r,"ValueScope",{enumerable:!0,get:function(){return a.ValueScope}}),Object.defineProperty(r,"ValueScopeName",{enumerable:!0,get:function(){return a.ValueScopeName}}),Object.defineProperty(r,"varKinds",{enumerable:!0,get:function(){return a.varKinds}}),r.operators={GT:new n._Code(">"),GTE:new n._Code(">="),LT:new n._Code("<"),LTE:new n._Code("<="),EQ:new n._Code("==="),NEQ:new n._Code("!=="),NOT:new n._Code("!"),OR:new n._Code("||"),AND:new n._Code("&&"),ADD:new n._Code("+")};class o{optimizeNodes(){return this}optimizeNames(e,t){return this}}class s extends o{constructor(e,t,r){super(),this.varKind=e,this.name=t,this.rhs=r}render({es5:e,_n:t}){var e=e?i.varKinds.var:this.varKind,r=void 0===this.rhs?"":" = "+this.rhs;return`${e} ${this.name}${r};`+t}optimizeNames(e,t){if(e[this.name.str])return this.rhs&&(this.rhs=O(this.rhs,e,t)),Line truncated
|| (${a} == "string" && ${n} && ${n} == +${n})`).assign(i,(0,m._)`+${n}`);case"integer":return s.elseIf((0,m._)`${a} === "boolean" || ${n} === null
|| (${a} === "string" && ${n} && ${n} == +${n} && !(${n} % 1))`).assign(i,(0,m._)`+${n}`);case"boolean":return s.elseIf((0,m._)`${n} === "false" || ${n} === 0 || ${n} === null`).assign(i,!1).elseIf((0,m._)`${n} === "true" || ${n} === 1`).assign(i,!0);case"null":return s.elseIf((0,m._)`${n} === "" || ${n} === 0 || ${n} === false`),s.assign(i,null);case"array":s.elseIf((0,m._)`${a} === "string" || ${a} === "number"
|| ${a} === "boolean" || ${n} === null`).assign(i,(0,m._)`[${n}]`)}}(c);s.else(),h(o),s.endIf(),s.if((0,m._)`${i} !== undefined`,()=>{var e,t,r,a;s.assign(n,i),[{gen:e,parentData:t,parentDataProperty:r},a]=[o,i],e.if((0,m._)`${t} !== undefined`,()=>e.assign((0,m._)`${t}[${r}]`,a))})}else h(l)})),s};const p=new Set(["string","number","integer","boolean","null"]);function l(e,t,r,a=c.Correct){var o=a===c.Correct?m.operators.EQ:m.operators.NEQ;let s;switch(e){case"null":return(0,m._)`${t} ${o} null`;case"array":s=(0,m._)`Array.isArray(${t})`;break;case"object":s=(0,m._)`${t} && typeof ${t} == "object" && !Array.isArray(${t})`;break;case"integer":s=n((0,m._)`!(${t} % 1) && !isNaN(${t})`);break;case"number":s=n();break;default:return(0,m._)`typeof ${t} ${o} ${e}`}return a===c.Correct?s:(0,m.not)(s);function n(e=m.nil){return(0,m.and)((0,m._)`typeof ${t} == "number"`,e,r?(0,m._)`isFinite(${t})`:m.nil)}}function f(e,t,r,a){if(1===e.length)return l(e[0],t,r,a);let o;const s=(0,i.toHash)(e);s.array&&s.object?(e=(0,m._)`typeof ${t} != "object"`,o=s.null?e:(0,m._)`!${t} || ${e}`,delete s.null,delete s.array,delete s.object):o=m.nil,s.number&&delete s.integer;for(const n in s)o=(0,m.and)(o,l(n,t,r,a));return o}r.checkDataType=l,r.checkDataTypes=f;const d={message:({schema:e})=>"must be "+e,params:({schema:e,schemaValue:t})=>"string"==typeof e?(0,m._)`{type: ${e}}`:(0,m._)`{type: ${t}}`};function h(e){e=function(e){var{gen:t,data:r,schema:a}=e,o=(0,i.schemaRefOrVal)(e,a,"type");return{gen:t,keyword:"type",data:r,schema:a.type,schemaCode:o,schemaValue:o,parentSchema:a,params:{},it:e}}(e);(0,o.reportError)(e,d)}r.reportTypeError=h},{"../codegen":2,"../errors":4,"../rules":9,"../util":10,"./applicability":11}],14:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.assignDefaults=void 0;const i=e("../codegen"),c=e("../util");function s(e,t,r){const{gen:a,compositeRule:o,data:s,opts:n}=e;if(void 0!==r){t=(0,i._)`${s}${(0,i.getProperty)(t)}`;if(o)(0,c.checkStrictMode)(e,"default is ignored for: "+t);else{let e=(0,i._)`${t} === undefined`;"empty"===n.useDefaults&&(e=(0,i._)`${e} || ${t} === null || ${t} === ""`),a.if(e,(0,i._)`${t} = ${(0,i.stringify)(r)}`)}}}r.assignDefaults=function(r,e){const{properties:t,items:a}=r.schema;if("object"===e&&t)for(const o in t)s(r,o,t[o].default);else"array"===e&&Array.isArray(a)&&a.forEach((e,t)=>s(r,t,e.default))}},{"../codegen":2,"../util":10}],15:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.getData=r.KeywordCxt=r.validateFunctionCode=void 0;const l=e("./boolSchema"),a=e("./dataType"),E=e("./applicability"),P=e("./dataType"),s=e("./defaults"),o=e("./keyword"),n=e("./subschema"),S=e("../codegen"),j=e("../names"),d=e("../resolve"),N=e("../util"),i=e("../errors");function c({gen:r,validateName:e,schema:a,schemaEnv:t,opts:o},s){o.code.es5?r.func(e,(0,S._)`${j.default.data}, ${j.default.valCxt}`,t.$async,()=>{var e,t;r.code((0,S._)`"use strict"; ${u(a,o)}`),t=o,(e=r).if(j.default.valCxt,()=>{e.var(j.default.instancePath,(0,S._)`${j.default.valCxt}.${j.default.instancePath}`),e.var(j.default.parentData,(0,S._)`${j.default.valCxt}.${j.default.parentData}`),e.var(j.default.parentDataProperty,(0,S._)`${j.default.valCxt}.${j.default.parentDataProperty}`),e.var(j.default.rootData,(0,S._)`${j.default.valCxt}.${j.default.rootData}`),t.dynamicRef&&e.var(j.default.dynamicAnchors,(0,S._)`${j.default.valCxt}.${j.default.dynamicAnchors}`)},()=>{e.var(j.default.instancePath,(0,S._)`""`),e.var(j.default.parentData,(0,S._)`undefined`),e.var(j.default.parentDataProperty,(0,S._)`undefined`),e.var(j.default.rootData,j.default.data),t.dynamicRef&&e.var(j.default.dynamicAnchors,(0,S._)`{}`)}),r.code(s)}):r.func(e,(0,S._)`${j.default.data}, ${e=o,(0,S._)`{${j.default.instancePath}="", ${j.default.parentData}, ${j.default.parentDataProperty}, ${j.default.rootData}=${j.default.data}${e.dynamicRef?(0,S._)`, ${j.default.dynamicAnchors}={}`:S.nil}}={}`}`,t.$async,()=>r.code(u(a,o)).code(s))}function u(e,t){e="object"==typeof e&&e[t.schemaId];return e&&(t.code.source||t.code.process)?(0,S._)`/*# sourceURL=${e} */`:S.nil}function m(e,t){if(f(e)&&($(e),p(e))){var r=e,a=t;const{schema:n,gen:i,opts:c}=r;c.$comment&&n.$comment&&b(r);var o=r,s=o.schema[o.opts.schemaId],o=(s&&(o.baseId=(0,d.resolveUrl)(o.opts.uriResolver,o.baseId,s)),r);if(o.schema.$async&&!o.schemaEnv.$async)throw new Error("async schema in sync schema");s=i.const("_errs",j.default.errors);_(r,s),i.var(a,(0,S._)`${s} === ${j.default.errors}`)}else(0,l.boolOrEmptySchema)(e,t)}function p({schema:e,self:t}){if("boolean"==typeof e)return!e;for(const r in e)if(t.RULES.all[r])return 1}function f(e){return"boolean"!=typeof e.schema}function $(e){(0,N.checkUnknownRules)(e);{const{schema:t,errSchemaPath:r,opts:a,self:o}=e;return void(t.$ref&&a.ignoreKeywordsWithRef&&(0,N.schemaHasRulesButRef)(t,o.RULES)&&o.logger.warn(`$ref: keywords ignored in schema at path "${r}"`))}}function _(e,t){if(e. Line truncated
missingProperty: ${a},
depsCount: ${t},
deps: ${r}}`};e={keyword:"dependencies",type:"object",schemaType:"object",error:r.error,code(e){var[t,r]=function({schema:e}){var t={},r={};for(const a in e)if("__proto__"!==a){const o=Array.isArray(e[a])?t:r;o[a]=e[a]}return[t,r]}(e);a(e,t),o(e,r)}};function a(t,e=t.schema){const{gen:r,data:a,it:o}=t;if(0!==Object.keys(e).length){var s,n=r.let("missing");for(const i in e){const c=e[i];0!==c.length&&(s=(0,d.propertyInData)(r,a,i,o.opts.ownProperties),t.setParams({property:i,depsCount:c.length,deps:c.join(", ")}),o.allErrors?r.if(s,()=>{for(const e of c)(0,d.checkReportMissingProp)(t,e)}):(r.if((0,l._)`${s} && (${(0,d.checkMissingProp)(t,c,n)})`),(0,d.reportMissingProp)(t,n),r.else()))}}}function o(t,e=t.schema){const{gen:r,data:a,keyword:o,it:s}=t,n=r.name("valid");for(const i in e)(0,c.alwaysValidSchema)(s,e[i])||(r.if((0,d.propertyInData)(r,a,i,s.opts.ownProperties),()=>{var e=t.subschema({keyword:o,schemaProp:i},n);t.mergeValidEvaluated(e,n)},()=>r.var(n,!0)),t.ok(n))}r.validatePropertyDeps=a,r.validateSchemaDeps=o,r.default=e},{"../../compile/codegen":2,"../../compile/util":10,"../code":51}],39:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0});const a=e("./dependencies");r.default={keyword:"dependentSchemas",type:"object",schemaType:"object",code:e=>(0,a.validateSchemaDeps)(e)}},{"./dependencies":38}],40:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0});const d=e("../../compile/codegen"),u=e("../../compile/util");function m(e,t){t=e.schema[t];return void 0!==t&&!(0,u.alwaysValidSchema)(e,t)}r.default={keyword:"if",schemaType:["object","boolean"],trackErrors:!0,error:{message:({params:e})=>(0,d.str)`must match "${e.ifClause}" schema`,params:({params:e})=>(0,d._)`{failingKeyword: ${e.ifClause}}`},code(a){const{gen:o,parentSchema:e,it:t}=a;void 0===e.then&&void 0===e.else&&(0,u.checkStrictMode)(t,'"if" without "then" and "else" is ignored');var r,s=m(t,"then"),n=m(t,"else");if(s||n){const c=o.let("valid",!0),l=o.name("_valid");function i(t,r){return()=>{var e=a.subschema({keyword:t},l);o.assign(c,l),a.mergeValidEvaluated(e,c),r?o.assign(r,(0,d._)`${t}`):a.setParams({ifClause:t})}}r=a.subschema({keyword:"if",compositeRule:!0,createErrors:!1,allErrors:!1},l),a.mergeEvaluated(r),a.reset(),s&&n?(r=o.let("ifClause"),a.setParams({ifClause:r}),o.if(l,i("then",r),i("else",r))):s?o.if(l,i("then")):o.if((0,d.not)(l),i("else")),a.pass(c,()=>a.error(!0))}}}},{"../../compile/codegen":2,"../../compile/util":10}],41:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0});const a=e("./additionalItems"),o=e("./prefixItems"),s=e("./items"),n=e("./items2020"),i=e("./contains"),c=e("./dependencies"),l=e("./propertyNames"),d=e("./additionalProperties"),u=e("./properties"),m=e("./patternProperties"),p=e("./not"),f=e("./anyOf"),h=e("./oneOf"),y=e("./allOf"),v=e("./if"),g=e("./thenElse");r.default=function(e=!1){const t=[p.default,f.default,h.default,y.default,v.default,g.default,l.default,d.default,c.default,u.default,m.default];return e?t.push(o.default,n.default):t.push(a.default,s.default),t.push(i.default),t}},{"./additionalItems":33,"./additionalProperties":34,"./allOf":35,"./anyOf":36,"./contains":37,"./dependencies":38,"./if":40,"./items":42,"./items2020":43,"./not":44,"./oneOf":45,"./patternProperties":46,"./prefixItems":47,"./properties":48,"./propertyNames":49,"./thenElse":50}],42:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.validateTuple=void 0;const f=e("../../compile/codegen"),h=e("../../compile/util"),a=e("../code");e={keyword:"items",type:"array",schemaType:["object","array","boolean"],before:"uniqueItems",code(e){const{schema:t,it:r}=e;if(Array.isArray(t))return o(e,"additionalItems",t);r.items=!0,(0,h.alwaysValidSchema)(r,t)||e.ok((0,a.validateArray)(e))}};function o(r,e,t=r.schema){const{gen:a,parentSchema:o,data:s,keyword:n,it:i}=r;var c=o,{opts:l,errSchemaPath:d}=i,u=t.length,c=u===c.minItems&&(u===c.maxItems||!1===c[e]);l.strictTuples&&!c&&(c=`"${n}" is ${u}-tuple, but minItems or maxItems/${e} are not specified or different at path "${d}"`,(0,h.checkStrictMode)(i,c,l.strictTuples)),i.opts.unevaluated&&t.length&&!0!==i.items&&(i.items=h.mergeEvaluated.items(a,t.length,i.items));const m=a.name("valid"),p=a.const("len",(0,f._)`${s}.length`);t.forEach((e,t)=>{(0,h.alwaysValidSchema)(i,e)||(a.if((0,f._)`${p} > ${t}`,()=>r.subschema({keyword:n,schemaProp:t,dataProp:t},m)),r.ok(m))})}r.validateTuple=o,r.default=e},{"../../compile/codegen":2,"../../compile/util":10,"../code":51}],43:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0});const a=e("../../compile/codegen"),s=e("../../compile/util"),n=e("../code"),i=e("./additionalItems");r.default={keyword:"items",type:"array",schemaType:["object","boolean"],before:"uniqueItems",error:{message:({params:{len:e}})=>(0,a.str)`must NOT have more than ${e} items`,params:({params:{len:e}})=>(0,a._)`{limit: ${e}}`},code(e){const{s Line truncated
@@ -1,4 +1,7 @@
/*globals $, gettext, fabric, PDFJS*/
window.module = {} // Hack to load ajv-compiled schema without actual module loading
fabric.Poweredby = fabric.util.createClass(fabric.Image, {
type: 'poweredby',
@@ -219,7 +222,6 @@ var editor = {
uploaded_file_id: null,
_window_loaded: false,
_fabric_loaded: false,
schema: null,
_px2mm: function (v) {
return v / editor.pdf_scale / 72 * editor.pdf_page.userUnit * 25.4;
@@ -1320,14 +1322,11 @@ var editor = {
_source_save: function () {
try {
var Ajv = window.ajv2020
var ajv = new Ajv()
var validate = ajv.compile(editor.schema)
var data = JSON.parse($("#source-textarea").val())
var valid = validate(data)
var valid = validate30(data)
if (!valid) {
console.log(validate.errors)
console.log(validate30.errors)
alert("Invalid input syntax. If you're familiar with this, check out the developer console for a full " +
"error log. Otherwise, please contact support.")
} else {
@@ -1463,10 +1462,6 @@ var editor = {
editor._update_save_button();
});
$("#pdf-info-width, #pdf-info-height").bind('change input', editor._paper_size_warning);
$.getJSON($("#schema-url").text(), function (data) {
editor.schema = data;
})
}
};
@@ -874,6 +874,14 @@ function setup_basics(el) {
});
});
el.find(".qrcode-canvas").each(function () {
$(this).qrcode(
{
text: $.trim($($(this).attr("data-qrdata")).html())
}
);
});
el.find(".propagated-settings-box").find("input, textarea, select").not("[readonly]")
.attr("data-propagated-locked", "true").prop("readonly", true);
@@ -1,5 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$comment": "If the schema is changed, regenerate pdf-layout.validate.js with $ ajv compile -s ./pretix/static/schema/pdf-layout.schema.json --spec=draft2020 -o ./pretix/static/schema/pdf-layout.validate.js",
"title": "Ticket Layout",
"description": "Dynamic elements for a PDF layout",
"type": "array",
@@ -0,0 +1 @@
"use strict";module.exports = validate30;module.exports.default = validate30;const schema42 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$comment":"If the schema is changed, regenerate pdf-layout.validate.js with $ ajv compile -s ./pretix/static/schema/pdf-layout.schema.json --spec=draft2020 -o ./pretix/static/schema/pdf-layout.validate.js","title":"Ticket Layout","description":"Dynamic elements for a PDF layout","type":"array","items":{"type":"object","description":"Layout object","oneOf":[{"required":["type","left","bottom","size"],"properties":{"type":{"type":"string","const":"barcodearea"},"page":{"description":"Page number this will be shown on, defaults to 1.","type":"number"},"left":{"description":"Position of the element on the x axis in millimeters.","oneOf":[{"type":"number"},{"type":"string","pattern":"^-?[0-9]+(\\.[0-9]+)?$"}]},"bottom":{"description":"Position of the element on the y axis in millimeters.","oneOf":[{"type":"number"},{"type":"string","pattern":"^-?[0-9]+(\\.[0-9]+)?$"}]},"content":{"description":"Name of a variable to use. The available values depend on event configuration and installed plugins. Defaults to 'secret'.","type":"string"},"text":{"description":"Custom text. Only used when 'content' is set to 'other'.","type":"string"},"text_i18n":{"description":"Custom text in multiple languages. Only used when 'content' is set to 'other_i18n'.","type":"object","patternProperties":{"[a-zA-Z-]+":{"type":"string"}},"additionalProperties":false},"size":{"description":"Size of the barcode in millimeters.","oneOf":[{"type":"number"},{"type":"string","pattern":"^-?[0-9]+(\\.[0-9]+)?$"}]},"nowhitespace":{"description":"Whether a barcode should be rendered without margins. Only used for type 'barcodearea'.","type":"boolean"},"color":{"description":"QR color as a tuple of three integers in the 0-255 range and one float in the 0-1 range. The last value (alpha) is ignored by the current implementation but might be used in the future.","type":"array","items":{"type":"number","minimum":0,"maximum":255},"minItems":3,"maxItems":4}},"additionalProperties":false},{"required":["type","left","bottom","content","width","height"],"properties":{"type":{"type":"string","const":"imagearea"},"page":{"description":"Page number this will be shown on, defaults to 1.","type":"number"},"left":{"description":"Position of the element on the x axis in millimeters.","oneOf":[{"type":"number"},{"type":"string","pattern":"^-?[0-9]+(\\.[0-9]+)?$"}]},"bottom":{"description":"Position of the element on the y axis in millimeters.","oneOf":[{"type":"number"},{"type":"string","pattern":"^-?[0-9]+(\\.[0-9]+)?$"}]},"content":{"description":"Name of a variable to use. The available values depend on event configuration and installed plugins.","type":"string"},"width":{"description":"Width of the element in millimeters.","oneOf":[{"type":"number"},{"type":"string","pattern":"^-?[0-9]+(\\.[0-9]+)?$"}]},"height":{"description":"Height of the element in millimeters.","oneOf":[{"type":"number"},{"type":"string","pattern":"^-?[0-9]+(\\.[0-9]+)?$"}]}},"additionalProperties":false},{"required":["type","left","bottom","content","width","height","fontsize","fontfamily","bold","italic","align","verticalalign","color"],"properties":{"type":{"type":"string","const":"textcontainer"},"page":{"description":"Page number this will be shown on, defaults to 1.","type":"number"},"left":{"description":"Position of the element on the x axis in millimeters.","oneOf":[{"type":"number"},{"type":"string","pattern":"^-?[0-9]+(\\.[0-9]+)?$"}]},"bottom":{"description":"Position of the element on the y axis in millimeters.","oneOf":[{"type":"number"},{"type":"string","pattern":"^-?[0-9]+(\\.[0-9]+)?$"}]},"width":{"description":"Width of the element in millimeters.","oneOf":[{"type":"number"},{"type":"string","pattern":"^-?[0-9]+(\\.[0-9]+)?$"}]},"height":{"description":"Width of the element in millimeters.","oneOf":[{"type":"number"},{"type":"string","pattern":"^-?[0-9]+(\\.[0-9]+)?$"}]},"content":{"description":"Name of a variable to use. The available values depend on event configuration and installed plugins.","type":"string"},"text":{"description":"Custom text. Only used when 'content' is set to 'other'.","type":"string"},"text_i18n":{"description":"Custom text in multiple languages. Only used when 'content' is set to 'other_i18n'.","type":"object","patternProperties":{"[a-zA-Z-]+":{"type":"string"}},"additionalProperties":false},"locale":{"description":"Locale to render the text in.","type":["string","null"],"pattern":"[a-zA-Z-]*"},"fontsize":{"description":"Font size","oneOf":[{"type":"number"},{"type":"string","pattern":"^-?[0-9]+(\\.[0-9]+)?$"}]},"lineheight":{"description":"Line height","oneOf":[{"type":"number"},{"type":"string","pattern":"^-?[0-9]+(\\.[0-9]+)?$"}]},"fontfamily":{"description":"Font family. The available values depend on installed plugins.","type":"string"},"bold":{"description":"Use bold font variant.","type":"boolLine truncated
+2 -2
View File
@@ -258,7 +258,7 @@ def test_list_list(token_client, organizer, event, clist, item, subevent, django
res["id"] = clist.pk
res["limit_products"] = [item.pk]
with django_assert_num_queries(11):
with django_assert_num_queries(9):
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkinlists/'.format(organizer.slug, event.slug))
assert resp.status_code == 200
assert [res] == resp.data['results']
@@ -437,7 +437,7 @@ def test_list_all_items_positions(token_client, organizer, event, clist, clist_a
p3["addon_to"] = p1["id"]
# All items
with django_assert_num_queries(24):
with django_assert_num_queries(22):
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/?ordering=positionid'.format(
organizer.slug, event.slug, clist_all.pk
))
+4 -4
View File
@@ -1865,7 +1865,7 @@ def test_event_expand_seat_filter_and_querycount(token_client, organizer, event,
assert resp.status_code == 200
event.refresh_from_db()
with assert_num_queries(12):
with assert_num_queries(10):
resp = token_client.get('/api/v1/organizers/{}/events/{}/seats/'
'?expand=orderposition&expand=cartposition&expand=voucher&is_available=true'
.format(organizer.slug, event.slug))
@@ -1875,7 +1875,7 @@ def test_event_expand_seat_filter_and_querycount(token_client, organizer, event,
with scope(organizer=organizer):
v0 = event.vouchers.create(item=item, seat=event.seats.get(seat_guid='0-0'))
with assert_num_queries(14):
with assert_num_queries(12):
resp = token_client.get('/api/v1/organizers/{}/events/{}/seats/'
'?expand=orderposition&expand=cartposition&expand=voucher&is_available=false'
.format(organizer.slug, event.slug))
@@ -1883,7 +1883,7 @@ def test_event_expand_seat_filter_and_querycount(token_client, organizer, event,
assert len(resp.data['results']) == 1
assert resp.data['results'][0]['voucher']['id'] == v0.pk
with assert_num_queries(12):
with assert_num_queries(10):
resp = token_client.get('/api/v1/organizers/{}/events/{}/seats/'
'?expand=orderposition&expand=cartposition&expand=voucher&is_available=true'
.format(organizer.slug, event.slug))
@@ -1894,7 +1894,7 @@ def test_event_expand_seat_filter_and_querycount(token_client, organizer, event,
v1 = event.vouchers.create(item=item, seat=event.seats.get(seat_guid='0-1'))
v2 = event.vouchers.create(item=item, seat=event.seats.get(seat_guid='0-2'))
with assert_num_queries(16):
with assert_num_queries(14):
resp = token_client.get('/api/v1/organizers/{}/events/{}/seats/'
'?expand=orderposition&expand=cartposition&expand=voucher&is_available=false'
.format(organizer.slug, event.slug))
+1 -1
View File
@@ -425,7 +425,7 @@ def test_item_list(token_client, organizer, event, team, item):
@pytest.mark.django_db
def test_item_list_queries(token_client, organizer, event, team, item, item3):
with assert_num_queries(18):
with assert_num_queries(16):
resp = token_client.get('/api/v1/organizers/{}/events/{}/items/'.format(organizer.slug, event.slug))
assert resp.status_code == 200
+2 -2
View File
@@ -1166,10 +1166,10 @@ def test_orderposition_list(
'type': 'entry'
}]
if '/events/' in endpoint:
with django_assert_num_queries(18):
with django_assert_num_queries(16):
resp = token_client.get(endpoint + '?has_checkin=true')
else:
with django_assert_num_queries(17):
with django_assert_num_queries(15):
resp = token_client.get(endpoint + '?has_checkin=true')
assert [res] == resp.data['results']