mirror of
https://github.com/pretix/pretix.git
synced 2026-08-02 09:27:50 +00:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb8e3feb3d | ||
|
|
eb5ff1f34a | ||
|
|
94aec6f511 | ||
|
|
ed25a8b073 | ||
|
|
c9eb936d45 | ||
|
|
7237ece1ca | ||
|
|
18485f5d95 |
+2
-2
@@ -74,11 +74,11 @@ dependencies = [
|
||||
"packaging",
|
||||
"paypalrestsdk==1.13.*",
|
||||
"paypal-checkout-serversdk==1.0.*",
|
||||
"PyJWT==2.12.*",
|
||||
"PyJWT==2.13.*",
|
||||
"phonenumberslite==9.0.*",
|
||||
"Pillow==12.2.*",
|
||||
"pretix-plugin-build",
|
||||
"protobuf==7.34.*",
|
||||
"protobuf==7.35.*",
|
||||
"psycopg2-binary",
|
||||
"pycountry",
|
||||
"pycparser==3.0",
|
||||
|
||||
@@ -1078,7 +1078,7 @@
|
||||
<dt>{% trans "VAT ID" %}</dt>
|
||||
<dd>
|
||||
{{ order.invoice_address.vat_id }}
|
||||
{% if order.invoice_address.vat_id_validated %}
|
||||
{% if order.invoice_address.vat_id and order.invoice_address.vat_id_validated %}
|
||||
<span class="fa fa-check" data-toggle="tooltip" title="{% blocktrans trimmed %}Valid EU VAT ID{% endblocktrans %}"></span>
|
||||
{% elif order.invoice_address.vat_id %}
|
||||
<form class="form-inline helper-display-inline" method="post"
|
||||
|
||||
@@ -72,6 +72,9 @@
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-5">
|
||||
{% bootstrap_field form.key layout='inline' form_group_class="" %}
|
||||
{% if form.key.help_text %}
|
||||
<span class="help-block">{{ form.key.help_text|safe }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-5">
|
||||
{% bootstrap_field form.label layout='inline' form_group_class="" %}
|
||||
|
||||
@@ -69,7 +69,7 @@ from django.utils.functional import cached_property
|
||||
from django.utils.html import format_html
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.timezone import get_current_timezone, now
|
||||
from django.utils.translation import gettext, gettext_lazy as _
|
||||
from django.utils.translation import gettext, gettext_lazy as _, ngettext
|
||||
from django.views import View
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from django.views.generic import (
|
||||
@@ -91,7 +91,9 @@ from pretix.base.models import (
|
||||
ReusableMedium, ScheduledOrganizerExport, Team, TeamInvite, User,
|
||||
)
|
||||
from pretix.base.models.customers import CustomerSSOClient, CustomerSSOProvider
|
||||
from pretix.base.models.event import Event, EventMetaProperty, EventMetaValue
|
||||
from pretix.base.models.event import (
|
||||
Event, EventMetaProperty, EventMetaValue, SubEvent, SubEventMetaValue,
|
||||
)
|
||||
from pretix.base.models.giftcards import (
|
||||
GiftCardAcceptance, GiftCardTransaction, gen_giftcard_secret,
|
||||
)
|
||||
@@ -2466,11 +2468,51 @@ class EventMetaPropertyEditorMixin:
|
||||
|
||||
@cached_property
|
||||
def formset(self):
|
||||
return EventMetaPropertyAllowedValueFormSet(
|
||||
formset = EventMetaPropertyAllowedValueFormSet(
|
||||
data=self.request.POST if self.request.method == "POST" else None,
|
||||
organizer=self.request.organizer,
|
||||
initial=(self.object.choices or []) if self.object else [],
|
||||
)
|
||||
if self.event_value_counts or self.subevent_value_counts:
|
||||
for form in formset.initial_forms:
|
||||
uses = []
|
||||
key = form.initial['key']
|
||||
if key in self.event_value_counts:
|
||||
count = self.event_value_counts[key]
|
||||
uses += [ngettext("%d event", "%d events", count) % count]
|
||||
if key in self.subevent_value_counts:
|
||||
count = self.subevent_value_counts[key]
|
||||
uses += [ngettext("%d subevent", "%d subevents", count) % count]
|
||||
if uses:
|
||||
form.fields['key'].help_text = _("Value can not be changed because it is in use (%s).") % (", ".join(uses))
|
||||
form.fields['key'].widget.attrs['readonly'] = True
|
||||
return formset
|
||||
|
||||
@cached_property
|
||||
def event_value_counts(self):
|
||||
if self.object:
|
||||
return {
|
||||
d['attr_value']: d['count']
|
||||
for d in self.request.organizer.events.annotate(
|
||||
attr_value=Subquery(EventMetaValue.objects.filter(
|
||||
event=OuterRef('pk'),
|
||||
property__name=self.object.name
|
||||
).values('value')), count=Count('attr_value')
|
||||
).values('attr_value', 'count')
|
||||
}
|
||||
|
||||
@cached_property
|
||||
def subevent_value_counts(self):
|
||||
if self.object:
|
||||
return {
|
||||
d['attr_value']: d['count']
|
||||
for d in SubEvent.objects.filter(event__organizer=self.request.organizer).annotate(
|
||||
attr_value=Subquery(SubEventMetaValue.objects.filter(
|
||||
subevent=OuterRef('pk'),
|
||||
property__name=self.object.name,
|
||||
).values('value')), count=Count('attr_value')
|
||||
).values('attr_value', 'count')
|
||||
}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
@@ -2493,10 +2535,28 @@ class EventMetaPropertyEditorMixin:
|
||||
return False
|
||||
return True
|
||||
|
||||
def all_existing_values_valid(self):
|
||||
if not self.event_value_counts and not self.subevent_value_counts:
|
||||
return True
|
||||
choice_keys = set(
|
||||
f.cleaned_data.get("key") for f in self.formset.ordered_forms if f not in self.formset.deleted_forms
|
||||
)
|
||||
if not choice_keys:
|
||||
return True
|
||||
existing_values = (self.event_value_counts.keys() | self.subevent_value_counts.keys()) - {None}
|
||||
missing_choices = existing_values - choice_keys
|
||||
if missing_choices:
|
||||
messages.error(self.request, _(
|
||||
"When restricting the allowed values, you need to allow all values that already exist "
|
||||
"on your events. Missing values: %s"
|
||||
) % (", ".join(missing_choices)))
|
||||
return False
|
||||
return True
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(self.get_queryset())
|
||||
self.form = self.get_form()
|
||||
if self.form.is_valid() and self.formset.is_valid() and self.is_default_valid():
|
||||
if self.form.is_valid() and self.formset.is_valid() and self.is_default_valid() and self.all_existing_values_valid():
|
||||
return self.form_valid(self.form)
|
||||
else:
|
||||
return self.form_invalid(self.form)
|
||||
|
||||
@@ -32,7 +32,10 @@
|
||||
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
import logging
|
||||
import time
|
||||
from datetime import datetime
|
||||
from http.cookies import Morsel
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from django.conf import settings
|
||||
@@ -58,6 +61,8 @@ from pretix.base.models import Event, Organizer
|
||||
from pretix.helpers.cookies import set_cookie_without_samesite
|
||||
from pretix.multidomain.models import KnownDomain
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
LOCAL_HOST_NAMES = ('testserver', 'localhost')
|
||||
|
||||
|
||||
@@ -254,6 +259,9 @@ class CsrfViewMiddleware(BaseCsrfMiddleware):
|
||||
if is_secure and settings.CSRF_COOKIE_NAME in request.COOKIES: # remove legacy cookie
|
||||
response.delete_cookie(settings.CSRF_COOKIE_NAME)
|
||||
response.delete_cookie(settings.CSRF_COOKIE_NAME, samesite="None")
|
||||
|
||||
handle_duplicated_csrftoken(request, response)
|
||||
|
||||
set_cookie_without_samesite(
|
||||
request, response,
|
||||
'__Host-' + settings.CSRF_COOKIE_NAME if is_secure else settings.CSRF_COOKIE_NAME,
|
||||
@@ -265,3 +273,55 @@ class CsrfViewMiddleware(BaseCsrfMiddleware):
|
||||
)
|
||||
# Content varies with the CSRF cookie, so set the Vary header.
|
||||
patch_vary_headers(response, ('Cookie',))
|
||||
|
||||
|
||||
def handle_duplicated_csrftoken(request, response):
|
||||
# Due to a Safari bug, in some browser, two csrftoken cookies with different values
|
||||
# exist: one unpartitioned, one partitioned. This function generates an additional
|
||||
# Set-Cookie header to get rid of the unpartitioned one.
|
||||
|
||||
cookie_name = '__Host-' + settings.CSRF_COOKIE_NAME
|
||||
|
||||
if request.scheme == 'https' and cookie_name in request.COOKIES:
|
||||
values = get_all_values_of_cookie(request.headers.get('Cookie'), cookie_name)
|
||||
if len(values) > 1:
|
||||
logger.info('Trying to remove duplicated %s cookies: %r', cookie_name, values)
|
||||
|
||||
# Make sure the set_cookie_without_samesite below will add a new item in the dictionary, placing
|
||||
# it below our deletion header.
|
||||
response.cookies.pop(cookie_name, None)
|
||||
|
||||
# Add the deletion Set-Cookie header to the cookie dict under a wrong name, so it doesn't get
|
||||
# overwritten by the set_cookie_without_samesite call below. This works because the code in
|
||||
# django.core.handlers.wsgi/asgi, that generates the actual Set-Cookie headers, only iterates
|
||||
# over cookie.values(), ignoring the keys.
|
||||
response.cookies['___DELETECOOKIE___' + cookie_name] = make_delete_morsel(cookie_name)
|
||||
|
||||
|
||||
def get_all_values_of_cookie(cookie_header, cookie_name):
|
||||
# like django.http.cookie.parse_cookie, but returns all values of duplicated cookies instead of only the last
|
||||
values = list()
|
||||
if not cookie_header:
|
||||
return values
|
||||
for chunk in cookie_header.split(";"):
|
||||
if "=" in chunk:
|
||||
key, val = chunk.split("=", 1)
|
||||
else:
|
||||
# Assume an empty name per
|
||||
# https://bugzilla.mozilla.org/show_bug.cgi?id=169091
|
||||
key, val = "", chunk
|
||||
key, val = key.strip(), val.strip()
|
||||
if key == cookie_name:
|
||||
values.append(val)
|
||||
return values
|
||||
|
||||
|
||||
def make_delete_morsel(name):
|
||||
m = Morsel()
|
||||
m.set(name, '', '')
|
||||
m['expires'] = datetime.utcfromtimestamp(0).strftime("%a, %d %b %Y %H:%M:%S GMT")
|
||||
m['samesite'] = 'None'
|
||||
m['secure'] = True
|
||||
m['path'] = settings.CSRF_COOKIE_PATH
|
||||
m['httponly'] = settings.CSRF_COOKIE_HTTPONLY
|
||||
return m
|
||||
|
||||
@@ -100,14 +100,19 @@ watch(() => props.value, (newval, oldval) => {
|
||||
}
|
||||
})
|
||||
|
||||
let rawSelectEl: HTMLSelectElement | null = null
|
||||
|
||||
onMounted(() => {
|
||||
rawSelectEl = select.value
|
||||
build()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
$(select.value)
|
||||
if (!rawSelectEl) return
|
||||
$(rawSelectEl)
|
||||
.off()
|
||||
.select2('destroy')
|
||||
rawSelectEl = null
|
||||
})
|
||||
</script>
|
||||
<template lang="pug">
|
||||
|
||||
Reference in New Issue
Block a user