forked from CGM_Public/pretix_original
Compare commits
3 Commits
walletdete
...
v2023.6.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
669f622ce6 | ||
|
|
d166c7ee68 | ||
|
|
0282cfbdd5 |
@@ -32,8 +32,6 @@ as well as the type of underlying hardware. Example:
|
|||||||
"token": "kpp4jn8g2ynzonp6",
|
"token": "kpp4jn8g2ynzonp6",
|
||||||
"hardware_brand": "Samsung",
|
"hardware_brand": "Samsung",
|
||||||
"hardware_model": "Galaxy S",
|
"hardware_model": "Galaxy S",
|
||||||
"os_name": "Android",
|
|
||||||
"os_version": "2.3.6",
|
|
||||||
"software_brand": "pretixdroid",
|
"software_brand": "pretixdroid",
|
||||||
"software_version": "4.0.0"
|
"software_version": "4.0.0"
|
||||||
}
|
}
|
||||||
@@ -100,8 +98,6 @@ following endpoint:
|
|||||||
{
|
{
|
||||||
"hardware_brand": "Samsung",
|
"hardware_brand": "Samsung",
|
||||||
"hardware_model": "Galaxy S",
|
"hardware_model": "Galaxy S",
|
||||||
"os_name": "Android",
|
|
||||||
"os_version": "2.3.6",
|
|
||||||
"software_brand": "pretixdroid",
|
"software_brand": "pretixdroid",
|
||||||
"software_version": "4.1.0",
|
"software_version": "4.1.0",
|
||||||
"info": {"arbitrary": "data"}
|
"info": {"arbitrary": "data"}
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ all_events boolean Whether this de
|
|||||||
limit_events list List of event slugs this device has access to
|
limit_events list List of event slugs this device has access to
|
||||||
hardware_brand string Device hardware manufacturer (read-only)
|
hardware_brand string Device hardware manufacturer (read-only)
|
||||||
hardware_model string Device hardware model (read-only)
|
hardware_model string Device hardware model (read-only)
|
||||||
os_name string Device operating system name (read-only)
|
|
||||||
os_version string Device operating system version (read-only)
|
|
||||||
software_brand string Device software product (read-only)
|
software_brand string Device software product (read-only)
|
||||||
software_version string Device software version (read-only)
|
software_version string Device software version (read-only)
|
||||||
created datetime Creation time
|
created datetime Creation time
|
||||||
@@ -78,8 +76,6 @@ Device endpoints
|
|||||||
"security_profile": "full",
|
"security_profile": "full",
|
||||||
"hardware_brand": "Zebra",
|
"hardware_brand": "Zebra",
|
||||||
"hardware_model": "TC25",
|
"hardware_model": "TC25",
|
||||||
"os_name": "Android",
|
|
||||||
"os_version": "8.1.0",
|
|
||||||
"software_brand": "pretixSCAN",
|
"software_brand": "pretixSCAN",
|
||||||
"software_version": "1.5.1"
|
"software_version": "1.5.1"
|
||||||
}
|
}
|
||||||
@@ -127,8 +123,6 @@ Device endpoints
|
|||||||
"security_profile": "full",
|
"security_profile": "full",
|
||||||
"hardware_brand": "Zebra",
|
"hardware_brand": "Zebra",
|
||||||
"hardware_model": "TC25",
|
"hardware_model": "TC25",
|
||||||
"os_name": "Android",
|
|
||||||
"os_version": "8.1.0",
|
|
||||||
"software_brand": "pretixSCAN",
|
"software_brand": "pretixSCAN",
|
||||||
"software_version": "1.5.1"
|
"software_version": "1.5.1"
|
||||||
}
|
}
|
||||||
@@ -179,8 +173,6 @@ Device endpoints
|
|||||||
"initialized": null
|
"initialized": null
|
||||||
"hardware_brand": null,
|
"hardware_brand": null,
|
||||||
"hardware_model": null,
|
"hardware_model": null,
|
||||||
"os_name": null,
|
|
||||||
"os_version": null,
|
|
||||||
"software_brand": null,
|
"software_brand": null,
|
||||||
"software_version": null
|
"software_version": null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,8 +70,6 @@ The provider class
|
|||||||
|
|
||||||
.. autoattribute:: settings_form_fields
|
.. autoattribute:: settings_form_fields
|
||||||
|
|
||||||
.. autoattribute:: walletqueries
|
|
||||||
|
|
||||||
.. automethod:: settings_form_clean
|
.. automethod:: settings_form_clean
|
||||||
|
|
||||||
.. automethod:: settings_content_render
|
.. automethod:: settings_content_render
|
||||||
|
|||||||
@@ -19,4 +19,4 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||||
# <https://www.gnu.org/licenses/>.
|
# <https://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
__version__ = "2023.7.0.dev0"
|
__version__ = "2023.6.1"
|
||||||
|
|||||||
@@ -728,7 +728,6 @@ class EventSettingsSerializer(SettingsSerializer):
|
|||||||
'payment_term_minutes',
|
'payment_term_minutes',
|
||||||
'payment_term_last',
|
'payment_term_last',
|
||||||
'payment_term_expire_automatically',
|
'payment_term_expire_automatically',
|
||||||
'payment_term_expire_delay_days',
|
|
||||||
'payment_term_accept_late',
|
'payment_term_accept_late',
|
||||||
'payment_explanation',
|
'payment_explanation',
|
||||||
'payment_pending_hidden',
|
'payment_pending_hidden',
|
||||||
|
|||||||
@@ -251,8 +251,6 @@ class DeviceSerializer(serializers.ModelSerializer):
|
|||||||
unique_serial = serializers.CharField(read_only=True)
|
unique_serial = serializers.CharField(read_only=True)
|
||||||
hardware_brand = serializers.CharField(read_only=True)
|
hardware_brand = serializers.CharField(read_only=True)
|
||||||
hardware_model = serializers.CharField(read_only=True)
|
hardware_model = serializers.CharField(read_only=True)
|
||||||
os_name = serializers.CharField(read_only=True)
|
|
||||||
os_version = serializers.CharField(read_only=True)
|
|
||||||
software_brand = serializers.CharField(read_only=True)
|
software_brand = serializers.CharField(read_only=True)
|
||||||
software_version = serializers.CharField(read_only=True)
|
software_version = serializers.CharField(read_only=True)
|
||||||
created = serializers.DateTimeField(read_only=True)
|
created = serializers.DateTimeField(read_only=True)
|
||||||
@@ -265,7 +263,7 @@ class DeviceSerializer(serializers.ModelSerializer):
|
|||||||
fields = (
|
fields = (
|
||||||
'device_id', 'unique_serial', 'initialization_token', 'all_events', 'limit_events',
|
'device_id', 'unique_serial', 'initialization_token', 'all_events', 'limit_events',
|
||||||
'revoked', 'name', 'created', 'initialized', 'hardware_brand', 'hardware_model',
|
'revoked', 'name', 'created', 'initialized', 'hardware_brand', 'hardware_model',
|
||||||
'os_name', 'os_version', 'software_brand', 'software_version', 'security_profile'
|
'software_brand', 'software_version', 'security_profile'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -42,8 +42,6 @@ class InitializationRequestSerializer(serializers.Serializer):
|
|||||||
token = serializers.CharField(max_length=190)
|
token = serializers.CharField(max_length=190)
|
||||||
hardware_brand = serializers.CharField(max_length=190)
|
hardware_brand = serializers.CharField(max_length=190)
|
||||||
hardware_model = serializers.CharField(max_length=190)
|
hardware_model = serializers.CharField(max_length=190)
|
||||||
os_name = serializers.CharField(max_length=190, required=False, allow_null=True)
|
|
||||||
os_version = serializers.CharField(max_length=190, required=False, allow_null=True)
|
|
||||||
software_brand = serializers.CharField(max_length=190)
|
software_brand = serializers.CharField(max_length=190)
|
||||||
software_version = serializers.CharField(max_length=190)
|
software_version = serializers.CharField(max_length=190)
|
||||||
info = serializers.JSONField(required=False, allow_null=True)
|
info = serializers.JSONField(required=False, allow_null=True)
|
||||||
@@ -52,8 +50,6 @@ class InitializationRequestSerializer(serializers.Serializer):
|
|||||||
class UpdateRequestSerializer(serializers.Serializer):
|
class UpdateRequestSerializer(serializers.Serializer):
|
||||||
hardware_brand = serializers.CharField(max_length=190)
|
hardware_brand = serializers.CharField(max_length=190)
|
||||||
hardware_model = serializers.CharField(max_length=190)
|
hardware_model = serializers.CharField(max_length=190)
|
||||||
os_name = serializers.CharField(max_length=190, required=False, allow_null=True)
|
|
||||||
os_version = serializers.CharField(max_length=190, required=False, allow_null=True)
|
|
||||||
software_brand = serializers.CharField(max_length=190)
|
software_brand = serializers.CharField(max_length=190)
|
||||||
software_version = serializers.CharField(max_length=190)
|
software_version = serializers.CharField(max_length=190)
|
||||||
info = serializers.JSONField(required=False, allow_null=True)
|
info = serializers.JSONField(required=False, allow_null=True)
|
||||||
@@ -103,8 +99,6 @@ class InitializeView(APIView):
|
|||||||
device.initialized = now()
|
device.initialized = now()
|
||||||
device.hardware_brand = serializer.validated_data.get('hardware_brand')
|
device.hardware_brand = serializer.validated_data.get('hardware_brand')
|
||||||
device.hardware_model = serializer.validated_data.get('hardware_model')
|
device.hardware_model = serializer.validated_data.get('hardware_model')
|
||||||
device.os_name = serializer.validated_data.get('os_name')
|
|
||||||
device.os_version = serializer.validated_data.get('os_version')
|
|
||||||
device.software_brand = serializer.validated_data.get('software_brand')
|
device.software_brand = serializer.validated_data.get('software_brand')
|
||||||
device.software_version = serializer.validated_data.get('software_version')
|
device.software_version = serializer.validated_data.get('software_version')
|
||||||
device.info = serializer.validated_data.get('info')
|
device.info = serializer.validated_data.get('info')
|
||||||
@@ -126,8 +120,6 @@ class UpdateView(APIView):
|
|||||||
device = request.auth
|
device = request.auth
|
||||||
device.hardware_brand = serializer.validated_data.get('hardware_brand')
|
device.hardware_brand = serializer.validated_data.get('hardware_brand')
|
||||||
device.hardware_model = serializer.validated_data.get('hardware_model')
|
device.hardware_model = serializer.validated_data.get('hardware_model')
|
||||||
device.os_name = serializer.validated_data.get('os_name')
|
|
||||||
device.os_version = serializer.validated_data.get('os_version')
|
|
||||||
device.software_brand = serializer.validated_data.get('software_brand')
|
device.software_brand = serializer.validated_data.get('software_brand')
|
||||||
device.software_version = serializer.validated_data.get('software_version')
|
device.software_version = serializer.validated_data.get('software_version')
|
||||||
device.info = serializer.validated_data.get('info')
|
device.info = serializer.validated_data.get('info')
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ class SecurityMiddleware(MiddlewareMixin):
|
|||||||
|
|
||||||
h = {
|
h = {
|
||||||
'default-src': ["{static}"],
|
'default-src': ["{static}"],
|
||||||
'script-src': ['{static}', 'https://checkout.stripe.com', 'https://js.stripe.com', 'https://pay.google.com'],
|
'script-src': ['{static}', 'https://checkout.stripe.com', 'https://js.stripe.com'],
|
||||||
'object-src': ["'none'"],
|
'object-src': ["'none'"],
|
||||||
'frame-src': ['{static}', 'https://checkout.stripe.com', 'https://js.stripe.com'],
|
'frame-src': ['{static}', 'https://checkout.stripe.com', 'https://js.stripe.com'],
|
||||||
'style-src': ["{static}", "{media}"],
|
'style-src': ["{static}", "{media}"],
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
# Generated by Django 4.1.9 on 2023-06-26 10:59
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('pretixbase', '0242_auto_20230512_1008'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='device',
|
|
||||||
name='os_name',
|
|
||||||
field=models.CharField(max_length=190, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='device',
|
|
||||||
name='os_version',
|
|
||||||
field=models.CharField(max_length=190, null=True),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -143,14 +143,6 @@ class Device(LoggedModel):
|
|||||||
max_length=190,
|
max_length=190,
|
||||||
null=True, blank=True
|
null=True, blank=True
|
||||||
)
|
)
|
||||||
os_name = models.CharField(
|
|
||||||
max_length=190,
|
|
||||||
null=True, blank=True
|
|
||||||
)
|
|
||||||
os_version = models.CharField(
|
|
||||||
max_length=190,
|
|
||||||
null=True, blank=True
|
|
||||||
)
|
|
||||||
software_brand = models.CharField(
|
software_brand = models.CharField(
|
||||||
max_length=190,
|
max_length=190,
|
||||||
null=True, blank=True
|
null=True, blank=True
|
||||||
|
|||||||
@@ -896,28 +896,6 @@ class Order(LockModel, LoggedModel):
|
|||||||
), tz)
|
), tz)
|
||||||
return term_last
|
return term_last
|
||||||
|
|
||||||
@property
|
|
||||||
def payment_term_expire_date(self):
|
|
||||||
delay = self.event.settings.get('payment_term_expire_delay_days', as_type=int)
|
|
||||||
if not delay: # performance saver + backwards compatibility
|
|
||||||
return self.expires
|
|
||||||
|
|
||||||
term_last = self.payment_term_last
|
|
||||||
if term_last and self.expires > term_last: # backwards compatibility
|
|
||||||
return self.expires
|
|
||||||
|
|
||||||
expires = self.expires.date() + timedelta(days=delay)
|
|
||||||
|
|
||||||
tz = ZoneInfo(self.event.settings.timezone)
|
|
||||||
expires = make_aware(datetime.combine(
|
|
||||||
expires,
|
|
||||||
time(hour=23, minute=59, second=59)
|
|
||||||
), tz)
|
|
||||||
if term_last:
|
|
||||||
return min(expires, term_last)
|
|
||||||
else:
|
|
||||||
return expires
|
|
||||||
|
|
||||||
def _can_be_paid(self, count_waitinglist=True, ignore_date=False, force=False) -> Union[bool, str]:
|
def _can_be_paid(self, count_waitinglist=True, ignore_date=False, force=False) -> Union[bool, str]:
|
||||||
error_messages = {
|
error_messages = {
|
||||||
'late_lastdate': _("The payment can not be accepted as the last date of payments configured in the "
|
'late_lastdate': _("The payment can not be accepted as the last date of payments configured in the "
|
||||||
|
|||||||
@@ -830,12 +830,13 @@ class QuestionColumn(ImportColumn):
|
|||||||
class CustomerColumn(ImportColumn):
|
class CustomerColumn(ImportColumn):
|
||||||
identifier = 'customer'
|
identifier = 'customer'
|
||||||
verbose_name = gettext_lazy('Customer')
|
verbose_name = gettext_lazy('Customer')
|
||||||
|
default_value = None
|
||||||
|
|
||||||
def clean(self, value, previous_values):
|
def clean(self, value, previous_values):
|
||||||
if value:
|
if value:
|
||||||
try:
|
try:
|
||||||
value = self.event.organizer.customers.get(
|
value = self.event.organizer.customers.get(
|
||||||
Q(identifier=value) | Q(email__iexact=value) | Q(external_identifier=value)
|
Q(identifier=value) | Q(email=value) | Q(external_identifier=value)
|
||||||
)
|
)
|
||||||
except Customer.MultipleObjectsReturned:
|
except Customer.MultipleObjectsReturned:
|
||||||
value = self.event.organizer.customers.get(
|
value = self.event.organizer.customers.get(
|
||||||
|
|||||||
@@ -78,16 +78,6 @@ from pretix.presale.views.cart import cart_session, get_or_create_cart_id
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class WalletQueries:
|
|
||||||
APPLEPAY = 'applepay'
|
|
||||||
GOOGLEPAY = 'googlepay'
|
|
||||||
|
|
||||||
WALLETS = (
|
|
||||||
(APPLEPAY, pgettext_lazy('payment', 'Apple Pay')),
|
|
||||||
(GOOGLEPAY, pgettext_lazy('payment', 'Google Pay')),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class PaymentProviderForm(Form):
|
class PaymentProviderForm(Form):
|
||||||
def clean(self):
|
def clean(self):
|
||||||
cleaned_data = super().clean()
|
cleaned_data = super().clean()
|
||||||
@@ -446,19 +436,6 @@ class BasePaymentProvider:
|
|||||||
d['_restrict_to_sales_channels']._as_type = list
|
d['_restrict_to_sales_channels']._as_type = list
|
||||||
return d
|
return d
|
||||||
|
|
||||||
@property
|
|
||||||
def walletqueries(self):
|
|
||||||
"""
|
|
||||||
.. warning:: This property is considered **experimental**. It might change or get removed at any time without
|
|
||||||
prior notice.
|
|
||||||
|
|
||||||
A list of wallet payment methods that should be dynamically joined to the public name of the payment method,
|
|
||||||
if they are available to the user.
|
|
||||||
The detection is made on a best effort basis with no guarantees of correctness and actual availability.
|
|
||||||
Wallets that pretix can check for are exposed through ``pretix.base.payment.WalletQueries``.
|
|
||||||
"""
|
|
||||||
return []
|
|
||||||
|
|
||||||
def settings_form_clean(self, cleaned_data):
|
def settings_form_clean(self, cleaned_data):
|
||||||
"""
|
"""
|
||||||
Overriding this method allows you to inject custom validation into the settings form.
|
Overriding this method allows you to inject custom validation into the settings form.
|
||||||
|
|||||||
@@ -939,7 +939,7 @@ class Renderer:
|
|||||||
|
|
||||||
# reportlab does not support unicode combination characters
|
# reportlab does not support unicode combination characters
|
||||||
# It's important we do this before we use ArabicReshaper
|
# It's important we do this before we use ArabicReshaper
|
||||||
text = unicodedata.normalize("NFC", text)
|
text = unicodedata.normalize("NFKC", text)
|
||||||
|
|
||||||
# reportlab does not support RTL, ligature-heavy scripts like Arabic. Therefore, we use ArabicReshaper
|
# reportlab does not support RTL, ligature-heavy scripts like Arabic. Therefore, we use ArabicReshaper
|
||||||
# to resolve all ligatures and python-bidi to switch RTL texts.
|
# to resolve all ligatures and python-bidi to switch RTL texts.
|
||||||
|
|||||||
@@ -1270,12 +1270,12 @@ def expire_orders(sender, **kwargs):
|
|||||||
Exists(
|
Exists(
|
||||||
OrderFee.objects.filter(order_id=OuterRef('pk'), fee_type=OrderFee.FEE_TYPE_CANCELLATION)
|
OrderFee.objects.filter(order_id=OuterRef('pk'), fee_type=OrderFee.FEE_TYPE_CANCELLATION)
|
||||||
)
|
)
|
||||||
).prefetch_related('event').order_by('event_id')
|
).select_related('event').order_by('event_id')
|
||||||
for o in qs:
|
for o in qs:
|
||||||
if o.event_id != event_id:
|
if o.event_id != event_id:
|
||||||
expire = o.event.settings.get('payment_term_expire_automatically', as_type=bool)
|
expire = o.event.settings.get('payment_term_expire_automatically', as_type=bool)
|
||||||
event_id = o.event_id
|
event_id = o.event_id
|
||||||
if expire and now() >= o.payment_term_expire_date:
|
if expire:
|
||||||
mark_order_expired(o)
|
mark_order_expired(o)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -893,28 +893,6 @@ DEFAULTS = {
|
|||||||
"the pool and can be ordered by other people."),
|
"the pool and can be ordered by other people."),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
'payment_term_expire_delay_days': {
|
|
||||||
'default': '0',
|
|
||||||
'type': int,
|
|
||||||
'form_class': forms.IntegerField,
|
|
||||||
'serializer_class': serializers.IntegerField,
|
|
||||||
'form_kwargs': dict(
|
|
||||||
label=_('Expiration delay'),
|
|
||||||
help_text=_("The order will only actually expire this many days after the expiration date communicated "
|
|
||||||
"to the customer. However, this will not delay beyond the \"last date of payments\" "
|
|
||||||
"configured above, which is always enforced. The delay may also end on a weekend regardless "
|
|
||||||
"of the other settings above."),
|
|
||||||
# Every order in between the official expiry date and the delayed expiry date has a performance penalty
|
|
||||||
# for the cron job, so we limit this feature to 30 days to prevent arbitrary numbers of orders needing
|
|
||||||
# to be checked.
|
|
||||||
min_value=0,
|
|
||||||
max_value=30,
|
|
||||||
),
|
|
||||||
'serializer_kwargs': dict(
|
|
||||||
min_value=0,
|
|
||||||
max_value=30,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
'payment_pending_hidden': {
|
'payment_pending_hidden': {
|
||||||
'default': 'False',
|
'default': 'False',
|
||||||
'type': bool,
|
'type': bool,
|
||||||
|
|||||||
@@ -48,8 +48,6 @@ from django.utils.http import url_has_allowed_host_and_scheme
|
|||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from markdown import Extension
|
from markdown import Extension
|
||||||
from markdown.inlinepatterns import SubstituteTagInlineProcessor
|
from markdown.inlinepatterns import SubstituteTagInlineProcessor
|
||||||
from markdown.postprocessors import Postprocessor
|
|
||||||
from markdown.treeprocessors import UnescapeTreeprocessor
|
|
||||||
from tlds import tld_set
|
from tlds import tld_set
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
@@ -110,8 +108,6 @@ URL_RE = SimpleLazyObject(lambda: build_url_re(tlds=sorted(tld_set, key=len, rev
|
|||||||
|
|
||||||
EMAIL_RE = SimpleLazyObject(lambda: build_email_re(tlds=sorted(tld_set, key=len, reverse=True)))
|
EMAIL_RE = SimpleLazyObject(lambda: build_email_re(tlds=sorted(tld_set, key=len, reverse=True)))
|
||||||
|
|
||||||
DOT_ESCAPE = "|escaped-dot-sGnY9LMK|"
|
|
||||||
|
|
||||||
|
|
||||||
def safelink_callback(attrs, new=False):
|
def safelink_callback(attrs, new=False):
|
||||||
"""
|
"""
|
||||||
@@ -189,109 +185,6 @@ class EmailNl2BrExtension(Extension):
|
|||||||
md.inlinePatterns.register(br_tag, 'nl', 5)
|
md.inlinePatterns.register(br_tag, 'nl', 5)
|
||||||
|
|
||||||
|
|
||||||
class LinkifyPostprocessor(Postprocessor):
|
|
||||||
def __init__(self, linker):
|
|
||||||
self.linker = linker
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
def run(self, text):
|
|
||||||
return self.linker.linkify(text)
|
|
||||||
|
|
||||||
|
|
||||||
class CleanPostprocessor(Postprocessor):
|
|
||||||
def __init__(self, tags, attributes, protocols, strip):
|
|
||||||
self.tags = tags
|
|
||||||
self.attributes = attributes
|
|
||||||
self.protocols = protocols
|
|
||||||
self.strip = strip
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
def run(self, text):
|
|
||||||
return bleach.clean(
|
|
||||||
text,
|
|
||||||
tags=self.tags,
|
|
||||||
attributes=self.attributes,
|
|
||||||
protocols=self.protocols,
|
|
||||||
strip=self.strip
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CustomUnescapeTreeprocessor(UnescapeTreeprocessor):
|
|
||||||
"""
|
|
||||||
This un-escapes everything except \\.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _unescape(self, m):
|
|
||||||
if m.group(1) == "46": # 46 is the ASCII position of .
|
|
||||||
return DOT_ESCAPE
|
|
||||||
return chr(int(m.group(1)))
|
|
||||||
|
|
||||||
|
|
||||||
class CustomUnescapePostprocessor(Postprocessor):
|
|
||||||
"""
|
|
||||||
Restore escaped .
|
|
||||||
"""
|
|
||||||
|
|
||||||
def run(self, text):
|
|
||||||
return text.replace(DOT_ESCAPE, ".")
|
|
||||||
|
|
||||||
|
|
||||||
class LinkifyAndCleanExtension(Extension):
|
|
||||||
r"""
|
|
||||||
We want to do:
|
|
||||||
|
|
||||||
input --> markdown --> bleach clean --> linkify --> output
|
|
||||||
|
|
||||||
Internally, the markdown library does:
|
|
||||||
|
|
||||||
source --> parse --> (tree|inline)processors --> serializing --> postprocessors
|
|
||||||
|
|
||||||
All escaped characters such as \. will be turned to something like <STX>46<ETX> in the processors
|
|
||||||
step and then will be converted to . back again in the last tree processor, before serialization.
|
|
||||||
Therefore, linkify does not see the escaped character anymore. This is annoying for the one case
|
|
||||||
where you want to type "rich_text.py" and *not* have it turned into a link, since you can't type
|
|
||||||
"rich_text\.py" either.
|
|
||||||
|
|
||||||
A simple solution would be to run linkify before markdown, but that may cause other issues when
|
|
||||||
linkify messes with the markdown syntax and it makes handling our attributes etc. harder.
|
|
||||||
|
|
||||||
So we do a weird hack where we modify the unescape processor to unescape everything EXCEPT for the
|
|
||||||
dot and then unescape that one manually after linkify. However, to make things even harder, the bleach
|
|
||||||
clean step removes any invisible characters, so we need to cheat a bit more.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, linker, tags, attributes, protocols, strip):
|
|
||||||
self.linker = linker
|
|
||||||
self.tags = tags
|
|
||||||
self.attributes = attributes
|
|
||||||
self.protocols = protocols
|
|
||||||
self.strip = strip
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
def extendMarkdown(self, md):
|
|
||||||
md.treeprocessors.deregister('unescape')
|
|
||||||
md.treeprocessors.register(
|
|
||||||
CustomUnescapeTreeprocessor(md),
|
|
||||||
'unescape',
|
|
||||||
0
|
|
||||||
)
|
|
||||||
md.postprocessors.register(
|
|
||||||
CleanPostprocessor(self.tags, self.attributes, self.protocols, self.strip),
|
|
||||||
'clean',
|
|
||||||
2
|
|
||||||
)
|
|
||||||
md.postprocessors.register(
|
|
||||||
LinkifyPostprocessor(self.linker),
|
|
||||||
'linkify',
|
|
||||||
1
|
|
||||||
)
|
|
||||||
md.postprocessors.register(
|
|
||||||
CustomUnescapePostprocessor(self.linker),
|
|
||||||
'unescape_dot',
|
|
||||||
0
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def markdown_compile_email(source):
|
def markdown_compile_email(source):
|
||||||
linker = bleach.Linker(
|
linker = bleach.Linker(
|
||||||
url_re=URL_RE,
|
url_re=URL_RE,
|
||||||
@@ -299,20 +192,18 @@ def markdown_compile_email(source):
|
|||||||
callbacks=DEFAULT_CALLBACKS + [truelink_callback, abslink_callback],
|
callbacks=DEFAULT_CALLBACKS + [truelink_callback, abslink_callback],
|
||||||
parse_email=True
|
parse_email=True
|
||||||
)
|
)
|
||||||
return markdown.markdown(
|
return linker.linkify(bleach.clean(
|
||||||
source,
|
markdown.markdown(
|
||||||
extensions=[
|
source,
|
||||||
'markdown.extensions.sane_lists',
|
extensions=[
|
||||||
EmailNl2BrExtension(),
|
'markdown.extensions.sane_lists',
|
||||||
LinkifyAndCleanExtension(
|
EmailNl2BrExtension(),
|
||||||
linker,
|
]
|
||||||
tags=ALLOWED_TAGS,
|
),
|
||||||
attributes=ALLOWED_ATTRIBUTES,
|
tags=ALLOWED_TAGS,
|
||||||
protocols=ALLOWED_PROTOCOLS,
|
attributes=ALLOWED_ATTRIBUTES,
|
||||||
strip=False,
|
protocols=ALLOWED_PROTOCOLS,
|
||||||
)
|
))
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SnippetExtension(markdown.extensions.Extension):
|
class SnippetExtension(markdown.extensions.Extension):
|
||||||
@@ -322,24 +213,23 @@ class SnippetExtension(markdown.extensions.Extension):
|
|||||||
md.parser.blockprocessors.deregister('quote')
|
md.parser.blockprocessors.deregister('quote')
|
||||||
|
|
||||||
|
|
||||||
def markdown_compile(source, linker, snippet=False):
|
def markdown_compile(source, snippet=False):
|
||||||
tags = ALLOWED_TAGS_SNIPPET if snippet else ALLOWED_TAGS
|
tags = ALLOWED_TAGS_SNIPPET if snippet else ALLOWED_TAGS
|
||||||
exts = [
|
exts = [
|
||||||
'markdown.extensions.sane_lists',
|
'markdown.extensions.sane_lists',
|
||||||
'markdown.extensions.nl2br',
|
'markdown.extensions.nl2br'
|
||||||
LinkifyAndCleanExtension(
|
|
||||||
linker,
|
|
||||||
tags=tags,
|
|
||||||
attributes=ALLOWED_ATTRIBUTES,
|
|
||||||
protocols=ALLOWED_PROTOCOLS,
|
|
||||||
strip=snippet,
|
|
||||||
)
|
|
||||||
]
|
]
|
||||||
if snippet:
|
if snippet:
|
||||||
exts.append(SnippetExtension())
|
exts.append(SnippetExtension())
|
||||||
return markdown.markdown(
|
return bleach.clean(
|
||||||
source,
|
markdown.markdown(
|
||||||
extensions=exts
|
source,
|
||||||
|
extensions=exts
|
||||||
|
),
|
||||||
|
strip=snippet,
|
||||||
|
tags=tags,
|
||||||
|
attributes=ALLOWED_ATTRIBUTES,
|
||||||
|
protocols=ALLOWED_PROTOCOLS,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -355,7 +245,7 @@ def rich_text(text: str, **kwargs):
|
|||||||
callbacks=DEFAULT_CALLBACKS + ([truelink_callback, safelink_callback] if kwargs.get('safelinks', True) else [truelink_callback, abslink_callback]),
|
callbacks=DEFAULT_CALLBACKS + ([truelink_callback, safelink_callback] if kwargs.get('safelinks', True) else [truelink_callback, abslink_callback]),
|
||||||
parse_email=True
|
parse_email=True
|
||||||
)
|
)
|
||||||
body_md = markdown_compile(text, linker)
|
body_md = linker.linkify(markdown_compile(text))
|
||||||
return mark_safe(body_md)
|
return mark_safe(body_md)
|
||||||
|
|
||||||
|
|
||||||
@@ -371,5 +261,5 @@ def rich_text_snippet(text: str, **kwargs):
|
|||||||
callbacks=DEFAULT_CALLBACKS + ([truelink_callback, safelink_callback] if kwargs.get('safelinks', True) else [truelink_callback, abslink_callback]),
|
callbacks=DEFAULT_CALLBACKS + ([truelink_callback, safelink_callback] if kwargs.get('safelinks', True) else [truelink_callback, abslink_callback]),
|
||||||
parse_email=True
|
parse_email=True
|
||||||
)
|
)
|
||||||
body_md = markdown_compile(text, linker, snippet=True)
|
body_md = linker.linkify(markdown_compile(text, snippet=True))
|
||||||
return mark_safe(body_md)
|
return mark_safe(body_md)
|
||||||
|
|||||||
@@ -749,7 +749,6 @@ class PaymentSettingsForm(EventSettingsValidationMixin, SettingsForm):
|
|||||||
'payment_term_minutes',
|
'payment_term_minutes',
|
||||||
'payment_term_last',
|
'payment_term_last',
|
||||||
'payment_term_expire_automatically',
|
'payment_term_expire_automatically',
|
||||||
'payment_term_expire_delay_days',
|
|
||||||
'payment_term_accept_late',
|
'payment_term_accept_late',
|
||||||
'payment_pending_hidden',
|
'payment_pending_hidden',
|
||||||
'payment_explanation',
|
'payment_explanation',
|
||||||
|
|||||||
@@ -65,8 +65,6 @@
|
|||||||
{% bootstrap_field form.payment_term_minutes layout="control" %}
|
{% bootstrap_field form.payment_term_minutes layout="control" %}
|
||||||
{% bootstrap_field form.payment_term_last layout="control" %}
|
{% bootstrap_field form.payment_term_last layout="control" %}
|
||||||
{% bootstrap_field form.payment_term_expire_automatically layout="control" %}
|
{% bootstrap_field form.payment_term_expire_automatically layout="control" %}
|
||||||
{% trans "days" context "unit" as days %}
|
|
||||||
{% bootstrap_field form.payment_term_expire_delay_days layout="control" addon_after=days %}
|
|
||||||
{% bootstrap_field form.payment_term_accept_late layout="control" %}
|
{% bootstrap_field form.payment_term_accept_late layout="control" %}
|
||||||
{% bootstrap_field form.payment_pending_hidden layout="control" %}
|
{% bootstrap_field form.payment_pending_hidden layout="control" %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|||||||
@@ -1918,7 +1918,7 @@ class OrderContactChange(OrderView):
|
|||||||
'pretix.event.order.contact.changed',
|
'pretix.event.order.contact.changed',
|
||||||
data={
|
data={
|
||||||
'old_email': old_email,
|
'old_email': old_email,
|
||||||
'new_email': self.form.cleaned_data.get('email'),
|
'new_email': self.form.cleaned_data['email'],
|
||||||
},
|
},
|
||||||
user=self.request.user,
|
user=self.request.user,
|
||||||
)
|
)
|
||||||
@@ -1930,7 +1930,7 @@ class OrderContactChange(OrderView):
|
|||||||
'pretix.event.order.phone.changed',
|
'pretix.event.order.phone.changed',
|
||||||
data={
|
data={
|
||||||
'old_phone': old_phone,
|
'old_phone': old_phone,
|
||||||
'new_phone': self.form.cleaned_data.get('phone'),
|
'new_phone': self.form.cleaned_data['phone'],
|
||||||
},
|
},
|
||||||
user=self.request.user,
|
user=self.request.user,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ msgstr ""
|
|||||||
"Project-Id-Version: 1\n"
|
"Project-Id-Version: 1\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-06-27 12:51+0000\n"
|
"POT-Creation-Date: 2023-06-27 12:51+0000\n"
|
||||||
"PO-Revision-Date: 2023-06-29 05:00+0000\n"
|
"PO-Revision-Date: 2023-06-27 14:53+0000\n"
|
||||||
"Last-Translator: Moritz Lerch <dev@moritz-lerch.de>\n"
|
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||||
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix/de/"
|
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix/de/"
|
||||||
">\n"
|
">\n"
|
||||||
"Language: de\n"
|
"Language: de\n"
|
||||||
@@ -16352,7 +16352,7 @@ msgstr "Terminal-ID"
|
|||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html:96
|
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html:96
|
||||||
msgid "Card holder"
|
msgid "Card holder"
|
||||||
msgstr "Karteninhaber*in"
|
msgstr "Karteninhaber"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html:100
|
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html:100
|
||||||
msgid "Card expiration"
|
msgid "Card expiration"
|
||||||
@@ -21206,7 +21206,7 @@ msgstr "Import durchführen"
|
|||||||
#: pretix/control/templates/pretixcontrol/orders/import_start.html:10
|
#: pretix/control/templates/pretixcontrol/orders/import_start.html:10
|
||||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_form.html:16
|
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_form.html:16
|
||||||
msgid "Upload a new file"
|
msgid "Upload a new file"
|
||||||
msgstr "Neue Datei hochladen"
|
msgstr "Neuen Datei hochladen"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/orders/import_start.html:16
|
#: pretix/control/templates/pretixcontrol/orders/import_start.html:16
|
||||||
msgid ""
|
msgid ""
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ msgstr ""
|
|||||||
"Project-Id-Version: 1\n"
|
"Project-Id-Version: 1\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-06-27 12:51+0000\n"
|
"POT-Creation-Date: 2023-06-27 12:51+0000\n"
|
||||||
"PO-Revision-Date: 2023-06-29 05:00+0000\n"
|
"PO-Revision-Date: 2023-06-27 14:53+0000\n"
|
||||||
"Last-Translator: Moritz Lerch <dev@moritz-lerch.de>\n"
|
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||||
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
|
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
|
||||||
"pretix/pretix/de_Informal/>\n"
|
"pretix/pretix/de_Informal/>\n"
|
||||||
"Language: de_Informal\n"
|
"Language: de_Informal\n"
|
||||||
@@ -16324,7 +16324,7 @@ msgstr "Terminal-ID"
|
|||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html:96
|
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html:96
|
||||||
msgid "Card holder"
|
msgid "Card holder"
|
||||||
msgstr "Karteninhaber*in"
|
msgstr "Karteninhaber"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html:100
|
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html:100
|
||||||
msgid "Card expiration"
|
msgid "Card expiration"
|
||||||
@@ -21172,7 +21172,7 @@ msgstr "Import durchführen"
|
|||||||
#: pretix/control/templates/pretixcontrol/orders/import_start.html:10
|
#: pretix/control/templates/pretixcontrol/orders/import_start.html:10
|
||||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_form.html:16
|
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_form.html:16
|
||||||
msgid "Upload a new file"
|
msgid "Upload a new file"
|
||||||
msgstr "Neue Datei hochladen"
|
msgstr "Neuen Datei hochladen"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/orders/import_start.html:16
|
#: pretix/control/templates/pretixcontrol/orders/import_start.html:16
|
||||||
msgid ""
|
msgid ""
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ msgstr ""
|
|||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-06-27 12:51+0000\n"
|
"POT-Creation-Date: 2023-06-27 12:51+0000\n"
|
||||||
"PO-Revision-Date: 2023-06-28 06:00+0000\n"
|
"PO-Revision-Date: 2023-06-27 07:40+0000\n"
|
||||||
"Last-Translator: Yucheng Lin <yuchenglinedu@gmail.com>\n"
|
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||||
"Language-Team: Chinese (Traditional) <https://translate.pretix.eu/projects/"
|
"Language-Team: Chinese (Traditional) <https://translate.pretix.eu/projects/"
|
||||||
"pretix/pretix/zh_Hant/>\n"
|
"pretix/pretix/zh_Hant/>\n"
|
||||||
"Language: zh_Hant\n"
|
"Language: zh_Hant\n"
|
||||||
@@ -522,20 +522,24 @@ msgid "Test-Mode of shop has been deactivated"
|
|||||||
msgstr "商店的測試模式已啟動"
|
msgstr "商店的測試模式已啟動"
|
||||||
|
|
||||||
#: pretix/api/webhooks.py:339
|
#: pretix/api/webhooks.py:339
|
||||||
|
#, fuzzy
|
||||||
msgid "Waiting list entry added"
|
msgid "Waiting list entry added"
|
||||||
msgstr "候補名單條目已添加"
|
msgstr "候補名單條目"
|
||||||
|
|
||||||
#: pretix/api/webhooks.py:343
|
#: pretix/api/webhooks.py:343
|
||||||
|
#, fuzzy
|
||||||
msgid "Waiting list entry changed"
|
msgid "Waiting list entry changed"
|
||||||
msgstr "候補名單條目已更改"
|
msgstr "候補名單條目"
|
||||||
|
|
||||||
#: pretix/api/webhooks.py:347
|
#: pretix/api/webhooks.py:347
|
||||||
|
#, fuzzy
|
||||||
msgid "Waiting list entry deleted"
|
msgid "Waiting list entry deleted"
|
||||||
msgstr "候補名單條目已刪除"
|
msgstr "候補名單條目"
|
||||||
|
|
||||||
#: pretix/api/webhooks.py:351
|
#: pretix/api/webhooks.py:351
|
||||||
|
#, fuzzy
|
||||||
msgid "Waiting list entry received voucher"
|
msgid "Waiting list entry received voucher"
|
||||||
msgstr "候補名單條目收到憑證"
|
msgstr "候補名單條目"
|
||||||
|
|
||||||
#: pretix/base/addressvalidation.py:100 pretix/base/addressvalidation.py:103
|
#: pretix/base/addressvalidation.py:100 pretix/base/addressvalidation.py:103
|
||||||
#: pretix/base/addressvalidation.py:108 pretix/base/forms/questions.py:938
|
#: pretix/base/addressvalidation.py:108 pretix/base/forms/questions.py:938
|
||||||
@@ -5343,8 +5347,9 @@ msgid "Seat {number}"
|
|||||||
msgstr "座位 {number}"
|
msgstr "座位 {number}"
|
||||||
|
|
||||||
#: pretix/base/models/tax.py:157
|
#: pretix/base/models/tax.py:157
|
||||||
|
#, fuzzy
|
||||||
msgid "Your set of rules is not valid. Error message: {}"
|
msgid "Your set of rules is not valid. Error message: {}"
|
||||||
msgstr "你的條例規則並非有效。錯誤訊息:{}"
|
msgstr "你的樣式檔案並非有效樣式。錯誤訊息:{}"
|
||||||
|
|
||||||
#: pretix/base/models/tax.py:168 pretix/base/models/tax.py:146
|
#: pretix/base/models/tax.py:168 pretix/base/models/tax.py:146
|
||||||
msgid "Official name"
|
msgid "Official name"
|
||||||
@@ -7637,8 +7642,9 @@ msgid "Something happened in your event after the export, please try again."
|
|||||||
msgstr "匯出後你的活動中發生一些事情,請重試。"
|
msgstr "匯出後你的活動中發生一些事情,請重試。"
|
||||||
|
|
||||||
#: pretix/base/services/shredder.py:177
|
#: pretix/base/services/shredder.py:177
|
||||||
|
#, fuzzy
|
||||||
msgid "Data shredding completed"
|
msgid "Data shredding completed"
|
||||||
msgstr "數據粉碎完成"
|
msgstr "完成抓取。"
|
||||||
|
|
||||||
#: pretix/base/services/stats.py:210
|
#: pretix/base/services/stats.py:210
|
||||||
msgid "Uncategorized"
|
msgid "Uncategorized"
|
||||||
@@ -10714,20 +10720,6 @@ msgid ""
|
|||||||
"\n"
|
"\n"
|
||||||
"Your pretix team\n"
|
"Your pretix team\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"你好\n"
|
|
||||||
"\n"
|
|
||||||
"我們特此確認以下數據粉碎工作已完成:\n"
|
|
||||||
"\n"
|
|
||||||
"召集人:%(organizer)s\n"
|
|
||||||
"\n"
|
|
||||||
"事件: %(event)s\n"
|
|
||||||
"\n"
|
|
||||||
"數據選擇: %(shredders)s\n"
|
|
||||||
"開始時間:%(start_time)s(在此時間之後添加的新資料可能尚未刪除)\n"
|
|
||||||
"\n"
|
|
||||||
"敬上\n"
|
|
||||||
"\n"
|
|
||||||
"你的卓越團隊\n"
|
|
||||||
|
|
||||||
#: pretix/base/templates/pretixbase/forms/widgets/portrait_image.html:10
|
#: pretix/base/templates/pretixbase/forms/widgets/portrait_image.html:10
|
||||||
msgid "Upload photo"
|
msgid "Upload photo"
|
||||||
@@ -13921,11 +13913,11 @@ msgstr "活動已被刪除。"
|
|||||||
|
|
||||||
#: pretix/control/logdisplay.py:375
|
#: pretix/control/logdisplay.py:375
|
||||||
msgid "A removal process for personal data has been started."
|
msgid "A removal process for personal data has been started."
|
||||||
msgstr "個人數據的刪除過程已啟動。"
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/control/logdisplay.py:376
|
#: pretix/control/logdisplay.py:376
|
||||||
msgid "A removal process for personal data has been completed."
|
msgid "A removal process for personal data has been completed."
|
||||||
msgstr "個人數據的刪除過程已完成。"
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/control/logdisplay.py:377 pretix/control/logdisplay.py:375
|
#: pretix/control/logdisplay.py:377 pretix/control/logdisplay.py:375
|
||||||
msgid "The order details have been changed."
|
msgid "The order details have been changed."
|
||||||
@@ -21596,8 +21588,7 @@ msgstr "確認碼"
|
|||||||
msgid ""
|
msgid ""
|
||||||
"Depending on the amount of data in your event, the following step may take a "
|
"Depending on the amount of data in your event, the following step may take a "
|
||||||
"while to complete. We will inform you via email once it has been completed."
|
"while to complete. We will inform you via email once it has been completed."
|
||||||
msgstr "根據事件中的數據量,以下步驟可能需要一段時間才能完成。完成後,我們將通過電子"
|
msgstr ""
|
||||||
"郵件通知你。"
|
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/shredder/index.html:11
|
#: pretix/control/templates/pretixcontrol/shredder/index.html:11
|
||||||
msgid ""
|
msgid ""
|
||||||
|
|||||||
@@ -59,9 +59,7 @@ from pretix import __version__
|
|||||||
from pretix.base.decimal import round_decimal
|
from pretix.base.decimal import round_decimal
|
||||||
from pretix.base.forms import SecretKeySettingsField
|
from pretix.base.forms import SecretKeySettingsField
|
||||||
from pretix.base.models import Event, OrderPayment, OrderRefund, Quota
|
from pretix.base.models import Event, OrderPayment, OrderRefund, Quota
|
||||||
from pretix.base.payment import (
|
from pretix.base.payment import BasePaymentProvider, PaymentException
|
||||||
BasePaymentProvider, PaymentException, WalletQueries,
|
|
||||||
)
|
|
||||||
from pretix.base.plugins import get_all_plugins
|
from pretix.base.plugins import get_all_plugins
|
||||||
from pretix.base.services.mail import SendMailException
|
from pretix.base.services.mail import SendMailException
|
||||||
from pretix.base.settings import SettingsSandbox
|
from pretix.base.settings import SettingsSandbox
|
||||||
@@ -221,20 +219,6 @@ class StripeSettingsHolder(BasePaymentProvider):
|
|||||||
]
|
]
|
||||||
|
|
||||||
extra_fields = [
|
extra_fields = [
|
||||||
('walletdetection',
|
|
||||||
forms.BooleanField(
|
|
||||||
label=mark_safe(
|
|
||||||
_('Check for Apple Pay/Google Pay') +
|
|
||||||
' ' +
|
|
||||||
'<span class="label label-info">{}</span>'.format(_('experimental'))
|
|
||||||
),
|
|
||||||
help_text=_("pretix will attempt to check if the customer's webbrowser supports wallet-based payment "
|
|
||||||
"methods like Apple Pay or Google Pay and display them prominently with the credit card"
|
|
||||||
"payment method. This detection does not take into consideration if Google Pay/Apple Pay "
|
|
||||||
"has been disabled in the Stripe Dashboard."),
|
|
||||||
initial=True,
|
|
||||||
required=False,
|
|
||||||
)),
|
|
||||||
('postfix',
|
('postfix',
|
||||||
forms.CharField(
|
forms.CharField(
|
||||||
label=_('Statement descriptor postfix'),
|
label=_('Statement descriptor postfix'),
|
||||||
@@ -763,15 +747,6 @@ class StripeCC(StripeMethod):
|
|||||||
public_name = _('Credit card')
|
public_name = _('Credit card')
|
||||||
method = 'cc'
|
method = 'cc'
|
||||||
|
|
||||||
@property
|
|
||||||
def walletqueries(self):
|
|
||||||
# ToDo: Check against Stripe API, if ApplePay and GooglePay are even activated/available
|
|
||||||
# This is probably only really feasable once the Payment Methods Configuration API is out of beta
|
|
||||||
# https://stripe.com/docs/connect/payment-method-configurations
|
|
||||||
if self.settings.get("walletdetection", True, as_type=bool):
|
|
||||||
return [WalletQueries.APPLEPAY, WalletQueries.GOOGLEPAY]
|
|
||||||
return []
|
|
||||||
|
|
||||||
def payment_form_render(self, request, total) -> str:
|
def payment_form_render(self, request, total) -> str:
|
||||||
account = get_stripe_account_key(self)
|
account = get_stripe_account_key(self)
|
||||||
if not RegisteredApplePayDomain.objects.filter(account=account, domain=request.host).exists():
|
if not RegisteredApplePayDomain.objects.filter(account=account, domain=request.host).exists():
|
||||||
|
|||||||
@@ -3,10 +3,6 @@
|
|||||||
{% load money %}
|
{% load money %}
|
||||||
{% load bootstrap3 %}
|
{% load bootstrap3 %}
|
||||||
{% load rich_text %}
|
{% load rich_text %}
|
||||||
{% block custom_header %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% include "pretixpresale/event/fragment_walletdetection_head.html" %}
|
|
||||||
{% endblock %}
|
|
||||||
{% block inner %}
|
{% block inner %}
|
||||||
{% if current_payments %}
|
{% if current_payments %}
|
||||||
<p>{% trans "You already selected the following payment methods:" %}</p>
|
<p>{% trans "You already selected the following payment methods:" %}</p>
|
||||||
@@ -75,8 +71,7 @@
|
|||||||
{% if selected == p.provider.identifier %}checked="checked"{% endif %}
|
{% if selected == p.provider.identifier %}checked="checked"{% endif %}
|
||||||
id="input_payment_{{ p.provider.identifier }}"
|
id="input_payment_{{ p.provider.identifier }}"
|
||||||
aria-describedby="payment_{{ p.provider.identifier }}"
|
aria-describedby="payment_{{ p.provider.identifier }}"
|
||||||
data-toggle="radiocollapse" data-target="#payment_{{ p.provider.identifier }}"
|
data-toggle="radiocollapse" data-target="#payment_{{ p.provider.identifier }}"/>
|
||||||
data-wallets="{{ p.provider.walletqueries|join:"|" }}" />
|
|
||||||
<label for="input_payment_{{ p.provider.identifier }}"><strong>{{ p.provider.public_name }}</strong></label>
|
<label for="input_payment_{{ p.provider.identifier }}"><strong>{{ p.provider.public_name }}</strong></label>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
{% load static %}
|
|
||||||
{% load compress %}
|
|
||||||
|
|
||||||
{% compress js %}
|
|
||||||
<script type="text/javascript" src="{% static "pretixpresale/js/walletdetection.js" %}"></script>
|
|
||||||
{% endcompress %}
|
|
||||||
@@ -3,10 +3,6 @@
|
|||||||
{% load eventurl %}
|
{% load eventurl %}
|
||||||
{% load money %}
|
{% load money %}
|
||||||
{% block title %}{% trans "Change payment method" %}{% endblock %}
|
{% block title %}{% trans "Change payment method" %}{% endblock %}
|
||||||
{% block custom_header %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% include "pretixpresale/event/fragment_walletdetection_head.html" %}
|
|
||||||
{% endblock %}
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>
|
<h2>
|
||||||
{% blocktrans trimmed with code=order.code %}
|
{% blocktrans trimmed with code=order.code %}
|
||||||
@@ -33,8 +29,7 @@
|
|||||||
<input type="radio" name="payment" value="{{ p.provider.identifier }}"
|
<input type="radio" name="payment" value="{{ p.provider.identifier }}"
|
||||||
data-parent="#payment_accordion"
|
data-parent="#payment_accordion"
|
||||||
{% if selected == p.provider.identifier %}checked="checked"{% endif %}
|
{% if selected == p.provider.identifier %}checked="checked"{% endif %}
|
||||||
data-toggle="radiocollapse" data-target="#payment_{{ p.provider.identifier }}"
|
data-toggle="radiocollapse" data-target="#payment_{{ p.provider.identifier }}" />
|
||||||
data-wallets="{{ p.provider.walletqueries|join:"|" }}"/>
|
|
||||||
<strong>{{ p.provider.public_name }}</strong>
|
<strong>{{ p.provider.public_name }}</strong>
|
||||||
</label>
|
</label>
|
||||||
</h4>
|
</h4>
|
||||||
|
|||||||
@@ -350,9 +350,7 @@ def _detect_event(request, require_live=True, require_plugin=None):
|
|||||||
)
|
)
|
||||||
pathparts = request.get_full_path().split('/')
|
pathparts = request.get_full_path().split('/')
|
||||||
pathparts[1] = event.slug
|
pathparts[1] = event.slug
|
||||||
r = redirect('/'.join(pathparts))
|
return redirect('/'.join(pathparts))
|
||||||
r['Access-Control-Allow-Origin'] = '*'
|
|
||||||
return r
|
|
||||||
else:
|
else:
|
||||||
if 'event' in url.kwargs and 'organizer' in url.kwargs:
|
if 'event' in url.kwargs and 'organizer' in url.kwargs:
|
||||||
event = Event.objects.select_related('organizer').get(
|
event = Event.objects.select_related('organizer').get(
|
||||||
@@ -362,9 +360,7 @@ def _detect_event(request, require_live=True, require_plugin=None):
|
|||||||
pathparts = request.get_full_path().split('/')
|
pathparts = request.get_full_path().split('/')
|
||||||
pathparts[1] = event.organizer.slug
|
pathparts[1] = event.organizer.slug
|
||||||
pathparts[2] = event.slug
|
pathparts[2] = event.slug
|
||||||
r = redirect('/'.join(pathparts))
|
return redirect('/'.join(pathparts))
|
||||||
r['Access-Control-Allow-Origin'] = '*'
|
|
||||||
return r
|
|
||||||
except Event.DoesNotExist:
|
except Event.DoesNotExist:
|
||||||
raise Http404(_('The selected event was not found.'))
|
raise Http404(_('The selected event was not found.'))
|
||||||
raise Http404(_('The selected event was not found.'))
|
raise Http404(_('The selected event was not found.'))
|
||||||
@@ -378,9 +374,7 @@ def _detect_event(request, require_live=True, require_plugin=None):
|
|||||||
raise Http404(_('The selected organizer was not found.'))
|
raise Http404(_('The selected organizer was not found.'))
|
||||||
pathparts = request.get_full_path().split('/')
|
pathparts = request.get_full_path().split('/')
|
||||||
pathparts[1] = organizer.slug
|
pathparts[1] = organizer.slug
|
||||||
r = redirect('/'.join(pathparts))
|
return redirect('/'.join(pathparts))
|
||||||
r['Access-Control-Allow-Origin'] = '*'
|
|
||||||
return r
|
|
||||||
raise Http404(_('The selected organizer was not found.'))
|
raise Http404(_('The selected organizer was not found.'))
|
||||||
|
|
||||||
request._event_detected = True
|
request._event_detected = True
|
||||||
|
|||||||
@@ -212,14 +212,11 @@ def price_dict(item, price):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_picture(event, picture, size=None):
|
def get_picture(event, picture):
|
||||||
thumb = None
|
try:
|
||||||
if size:
|
thumb = get_thumbnail(picture.name, '60x60^').thumb.url
|
||||||
try:
|
except:
|
||||||
thumb = get_thumbnail(picture.name, size).thumb.url
|
logger.exception(f'Failed to create thumbnail of {picture.name}')
|
||||||
except:
|
|
||||||
logger.exception(f'Failed to create thumbnail of {picture.name}')
|
|
||||||
if not thumb:
|
|
||||||
thumb = default_storage.url(picture.name)
|
thumb = default_storage.url(picture.name)
|
||||||
return urljoin(build_absolute_uri(event, 'presale:event.index'), thumb)
|
return urljoin(build_absolute_uri(event, 'presale:event.index'), thumb)
|
||||||
|
|
||||||
@@ -267,8 +264,7 @@ class WidgetAPIProductList(EventListMixin, View):
|
|||||||
{
|
{
|
||||||
'id': item.pk,
|
'id': item.pk,
|
||||||
'name': str(item.name),
|
'name': str(item.name),
|
||||||
'picture': get_picture(self.request.event, item.picture, '60x60^') if item.picture else None,
|
'picture': get_picture(self.request.event, item.picture) if item.picture else None,
|
||||||
'picture_fullsize': get_picture(self.request.event, item.picture) if item.picture else None,
|
|
||||||
'description': str(rich_text(item.description, safelinks=False)) if item.description else None,
|
'description': str(rich_text(item.description, safelinks=False)) if item.description else None,
|
||||||
'has_variations': item.has_variations,
|
'has_variations': item.has_variations,
|
||||||
'require_voucher': item.require_voucher,
|
'require_voucher': item.require_voucher,
|
||||||
|
|||||||
@@ -165,13 +165,13 @@ if SITE_URL.endswith('/'):
|
|||||||
|
|
||||||
CSRF_TRUSTED_ORIGINS = [urlparse(SITE_URL).scheme + '://' + urlparse(SITE_URL).hostname]
|
CSRF_TRUSTED_ORIGINS = [urlparse(SITE_URL).scheme + '://' + urlparse(SITE_URL).hostname]
|
||||||
|
|
||||||
TRUST_X_FORWARDED_FOR = config.get('pretix', 'trust_x_forwarded_for', fallback=False)
|
TRUST_X_FORWARDED_FOR = config.getboolean('pretix', 'trust_x_forwarded_for', fallback=False)
|
||||||
USE_X_FORWARDED_HOST = config.get('pretix', 'trust_x_forwarded_host', fallback=False)
|
USE_X_FORWARDED_HOST = config.getboolean('pretix', 'trust_x_forwarded_host', fallback=False)
|
||||||
|
|
||||||
|
|
||||||
REQUEST_ID_HEADER = config.get('pretix', 'request_id_header', fallback=False)
|
REQUEST_ID_HEADER = config.get('pretix', 'request_id_header', fallback=False)
|
||||||
|
|
||||||
if config.get('pretix', 'trust_x_forwarded_proto', fallback=False):
|
if config.getboolean('pretix', 'trust_x_forwarded_proto', fallback=False):
|
||||||
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||||
|
|
||||||
PRETIX_PLUGINS_DEFAULT = config.get('pretix', 'plugins_default',
|
PRETIX_PLUGINS_DEFAULT = config.get('pretix', 'plugins_default',
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var walletdetection = {
|
|
||||||
applepay: async function () {
|
|
||||||
// This is a weak check for Apple Pay - in order to do a proper check, we would need to also call
|
|
||||||
// canMakePaymentsWithActiveCard(merchantIdentifier)
|
|
||||||
|
|
||||||
return !!(window.ApplePaySession && window.ApplePaySession.canMakePayments());
|
|
||||||
},
|
|
||||||
googlepay: async function () {
|
|
||||||
// Checking for Google Pay is a little bit more involved, since it requires including the Google Pay JS SDK, and
|
|
||||||
// providing a lot of information.
|
|
||||||
// So for the time being, we only check if Google Pay is available in TEST-mode, which should hopefully give us a
|
|
||||||
// good enough idea if Google Pay could be present on this device; even though there are still a lot of other
|
|
||||||
// factors that could inhibit Google Pay from actually being offered to the customer.
|
|
||||||
|
|
||||||
return $.ajax({
|
|
||||||
url: 'https://pay.google.com/gp/p/js/pay.js',
|
|
||||||
dataType: 'script',
|
|
||||||
}).then(function() {
|
|
||||||
const paymentsClient = new google.payments.api.PaymentsClient({environment: 'TEST'});
|
|
||||||
return paymentsClient.isReadyToPay({
|
|
||||||
apiVersion: 2,
|
|
||||||
apiVersionMinor: 0,
|
|
||||||
allowedPaymentMethods: [{
|
|
||||||
type: 'CARD',
|
|
||||||
parameters: {
|
|
||||||
allowedAuthMethods: ["PAN_ONLY", "CRYPTOGRAM_3DS"],
|
|
||||||
allowedCardNetworks: ["AMEX", "DISCOVER", "INTERAC", "JCB", "MASTERCARD", "VISA"]
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
})
|
|
||||||
}).then(function (response) {
|
|
||||||
return !!response.result;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
name_map: {
|
|
||||||
applepay: gettext('Apple Pay'),
|
|
||||||
googlepay: gettext('Google Pay'),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$(function () {
|
|
||||||
const wallets = $('[data-wallets]')
|
|
||||||
.map(function(index, pm) {
|
|
||||||
return pm.getAttribute("data-wallets").split("|");
|
|
||||||
})
|
|
||||||
.get()
|
|
||||||
.flat()
|
|
||||||
.filter(function(item, pos, self) {
|
|
||||||
// filter out empty or duplicate values
|
|
||||||
return item && self.indexOf(item) == pos;
|
|
||||||
});
|
|
||||||
|
|
||||||
wallets.forEach(function(wallet) {
|
|
||||||
const labels = $('[data-wallets*='+wallet+'] + label strong, [data-wallets*='+wallet+'] + strong')
|
|
||||||
.append('<span class="wallet wallet-loading"> <i aria-hidden="true" class="fa fa-cog fa-spin"></i></span>')
|
|
||||||
walletdetection[wallet]()
|
|
||||||
.then(function(result) {
|
|
||||||
const spans = labels.find(".wallet-loading:nth-of-type(1)");
|
|
||||||
if (result) {
|
|
||||||
spans.removeClass('wallet-loading').hide().text(', ' + walletdetection.name_map[wallet]).fadeIn(300);
|
|
||||||
} else {
|
|
||||||
spans.remove();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function(result) {
|
|
||||||
labels.find(".wallet-loading:nth-of-type(1)").remove();
|
|
||||||
})
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -450,7 +450,7 @@ Vue.component('item', {
|
|||||||
|
|
||||||
// Product description
|
// Product description
|
||||||
+ '<div class="pretix-widget-item-info-col">'
|
+ '<div class="pretix-widget-item-info-col">'
|
||||||
+ '<a :href="item.picture_fullsize" v-if="item.picture" class="pretix-widget-item-picture-link" @click.prevent.stop="lightbox"><img :src="item.picture" class="pretix-widget-item-picture"></a>'
|
+ '<img :src="item.picture" v-if="item.picture" class="pretix-widget-item-picture">'
|
||||||
+ '<div class="pretix-widget-item-title-and-description">'
|
+ '<div class="pretix-widget-item-title-and-description">'
|
||||||
+ '<a v-if="item.has_variations && show_toggle" class="pretix-widget-item-title" :href="\'#\' + item.id + \'-variants\'"'
|
+ '<a v-if="item.has_variations && show_toggle" class="pretix-widget-item-title" :href="\'#\' + item.id + \'-variants\'"'
|
||||||
+ ' @click.prevent.stop="expand" role="button" tabindex="0"'
|
+ ' @click.prevent.stop="expand" role="button" tabindex="0"'
|
||||||
@@ -530,12 +530,6 @@ Vue.component('item', {
|
|||||||
methods: {
|
methods: {
|
||||||
expand: function () {
|
expand: function () {
|
||||||
this.expanded = !this.expanded;
|
this.expanded = !this.expanded;
|
||||||
},
|
|
||||||
lightbox: function () {
|
|
||||||
this.$root.overlay.lightbox = {
|
|
||||||
image: this.item.picture_fullsize,
|
|
||||||
description: this.item.name,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -790,44 +784,12 @@ var shared_alert_fragment = (
|
|||||||
+ '</div>'
|
+ '</div>'
|
||||||
);
|
);
|
||||||
|
|
||||||
var shared_lightbox_fragment = (
|
|
||||||
'<div :class="lightboxClasses" role="dialog" aria-modal="true" v-if="$root.lightbox" @click="lightboxClose">'
|
|
||||||
+ '<div class="pretix-widget-lightbox-loading" v-if="$root.lightbox?.loading">'
|
|
||||||
+ '<svg width="256" height="256" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path class="pretix-widget-primary-color" d="M1152 896q0-106-75-181t-181-75-181 75-75 181 75 181 181 75 181-75 75-181zm512-109v222q0 12-8 23t-20 13l-185 28q-19 54-39 91 35 50 107 138 10 12 10 25t-9 23q-27 37-99 108t-94 71q-12 0-26-9l-138-108q-44 23-91 38-16 136-29 186-7 28-36 28h-222q-14 0-24.5-8.5t-11.5-21.5l-28-184q-49-16-90-37l-141 107q-10 9-25 9-14 0-25-11-126-114-165-168-7-10-7-23 0-12 8-23 15-21 51-66.5t54-70.5q-27-50-41-99l-183-27q-13-2-21-12.5t-8-23.5v-222q0-12 8-23t19-13l186-28q14-46 39-92-40-57-107-138-10-12-10-24 0-10 9-23 26-36 98.5-107.5t94.5-71.5q13 0 26 10l138 107q44-23 91-38 16-136 29-186 7-28 36-28h222q14 0 24.5 8.5t11.5 21.5l28 184q49 16 90 37l142-107q9-9 24-9 13 0 25 10 129 119 165 170 7 8 7 22 0 12-8 23-15 21-51 66.5t-54 70.5q26 50 41 98l183 28q13 2 21 12.5t8 23.5z"/></svg>'
|
|
||||||
+ '</div>'
|
|
||||||
+ '<div class="pretix-widget-lightbox-inner" @click.stop="">'
|
|
||||||
+ '<figure class="pretix-widget-lightbox-image">'
|
|
||||||
+ '<img :src="$root.lightbox.image" :alt="$root.lightbox.description" @load="lightboxLoaded" ref="lightboxImage">'
|
|
||||||
+ '<figcaption v-if="$root.lightbox.description">{{$root.lightbox.description}}</figcaption>'
|
|
||||||
+ '</figure>'
|
|
||||||
+ '<button type="button" class="pretix-widget-lightbox-close" @click="lightboxClose" aria-label="'+strings.close+'">'
|
|
||||||
+ '<svg height="16" viewBox="0 0 512 512" width="16" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M437.5,386.6L306.9,256l130.6-130.6c14.1-14.1,14.1-36.8,0-50.9c-14.1-14.1-36.8-14.1-50.9,0L256,205.1L125.4,74.5 c-14.1-14.1-36.8-14.1-50.9,0c-14.1,14.1-14.1,36.8,0,50.9L205.1,256L74.5,386.6c-14.1,14.1-14.1,36.8,0,50.9 c14.1,14.1,36.8,14.1,50.9,0L256,306.9l130.6,130.6c14.1,14.1,36.8,14.1,50.9,0C451.5,423.4,451.5,400.6,437.5,386.6z"/></svg>'
|
|
||||||
+ '</button>'
|
|
||||||
+ '</div>'
|
|
||||||
+ '</div>'
|
|
||||||
);
|
|
||||||
|
|
||||||
Vue.component('pretix-overlay', {
|
Vue.component('pretix-overlay', {
|
||||||
template: ('<div class="pretix-widget-overlay">'
|
template: ('<div class="pretix-widget-overlay">'
|
||||||
+ shared_iframe_fragment
|
+ shared_iframe_fragment
|
||||||
+ shared_alert_fragment
|
+ shared_alert_fragment
|
||||||
+ shared_lightbox_fragment
|
|
||||||
+ '</div>'
|
+ '</div>'
|
||||||
),
|
),
|
||||||
watch: {
|
|
||||||
'$root.lightbox': function (newValue, oldValue) {
|
|
||||||
if (newValue) {
|
|
||||||
if (newValue.image != oldValue?.image) {
|
|
||||||
this.$set(newValue, "loading", true);
|
|
||||||
}
|
|
||||||
if (!oldValue) {
|
|
||||||
window.addEventListener('keyup', this.lightboxCloseOnKeyup);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
window.removeEventListener('keyup', this.lightboxCloseOnKeyup);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
frameClasses: function () {
|
frameClasses: function () {
|
||||||
return {
|
return {
|
||||||
@@ -841,27 +803,8 @@ Vue.component('pretix-overlay', {
|
|||||||
'pretix-widget-alert-shown': this.$root.error_message,
|
'pretix-widget-alert-shown': this.$root.error_message,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
lightboxClasses: function () {
|
|
||||||
return {
|
|
||||||
'pretix-widget-lightbox-holder': true,
|
|
||||||
'pretix-widget-lightbox-shown': this.$root.lightbox,
|
|
||||||
'pretix-widget-lightbox-isloading': this.$root.lightbox?.loading,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
lightboxCloseOnKeyup: function (event) {
|
|
||||||
if (event.keyCode === 27) {
|
|
||||||
// abort on ESC-key
|
|
||||||
this.lightboxClose();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
lightboxClose: function () {
|
|
||||||
this.$root.lightbox = null;
|
|
||||||
},
|
|
||||||
lightboxLoaded: function () {
|
|
||||||
this.$root.lightbox.loading = false;
|
|
||||||
},
|
|
||||||
errorClose: function () {
|
errorClose: function () {
|
||||||
this.$root.error_message = null;
|
this.$root.error_message = null;
|
||||||
this.$root.error_url_after = null;
|
this.$root.error_url_after = null;
|
||||||
@@ -1852,7 +1795,6 @@ var create_overlay = function (app) {
|
|||||||
error_url_after: null,
|
error_url_after: null,
|
||||||
error_url_after_new_tab: true,
|
error_url_after_new_tab: true,
|
||||||
error_message: null,
|
error_message: null,
|
||||||
lightbox: null,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@@ -179,7 +179,3 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.wallet-loading + .wallet-loading {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -768,102 +768,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pretix-widget-lightbox-holder {
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: rgba(255, 255, 255, 0.8);
|
|
||||||
z-index: 16777271;
|
|
||||||
visibility: hidden;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.5s, visibility 0.5s;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
.pretix-widget-lightbox-loading svg {
|
|
||||||
margin: 40px;
|
|
||||||
-webkit-animation: pretix-widget-spin 6s linear infinite;
|
|
||||||
-moz-animation: pretix-widget-spin 6s linear infinite;
|
|
||||||
animation: pretix-widget-spin 6s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.pretix-widget-lightbox-shown {
|
|
||||||
visibility: visible;
|
|
||||||
opacity: 1;
|
|
||||||
transition: opacity 0.5s, visibility 0.5s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pretix-widget-lightbox-inner {
|
|
||||||
position: relative;
|
|
||||||
background: white;
|
|
||||||
border-radius: 5px 5px 5px 5px;
|
|
||||||
-moz-border-radius: 5px 5px 5px 5px;
|
|
||||||
-webkit-border-radius: 5px 5px 5px 5px;
|
|
||||||
box-shadow: 0 4px 18px 0 rgba(0, 0, 0, 0.1), 0 6px 20px 0 rgba(0, 0, 0, 0.09);
|
|
||||||
-webkit-box-shadow: 0 4px 18px 0 rgba(0, 0, 0, 0.1), 0 6px 20px 0 rgba(0, 0, 0, 0.09);
|
|
||||||
-moz-box-shadow: 0 4px 18px 0 rgba(0, 0, 0, 0.1), 0 6px 20px 0 rgba(0, 0, 0, 0.09);
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 10px;
|
|
||||||
max-width: 90%;
|
|
||||||
max-height: 90%;
|
|
||||||
}
|
|
||||||
&.pretix-widget-lightbox-isloading .pretix-widget-lightbox-inner {
|
|
||||||
position: absolute;
|
|
||||||
width: 1px;
|
|
||||||
height: 1px;
|
|
||||||
padding: 0;
|
|
||||||
margin: -1px;
|
|
||||||
overflow: hidden;
|
|
||||||
clip: rect(0,0,0,0);
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pretix-widget-lightbox-image {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.pretix-widget-lightbox-image img {
|
|
||||||
max-width: 80vw;
|
|
||||||
max-height: 80vh;
|
|
||||||
object-fit: scale-down;
|
|
||||||
}
|
|
||||||
.pretix-widget-lightbox-image figcaption {
|
|
||||||
margin: 0.5em 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pretix-widget-lightbox-close {
|
|
||||||
position: absolute;
|
|
||||||
right: -12px;
|
|
||||||
top: -12px;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
background: $brand-primary;
|
|
||||||
margin: 0;
|
|
||||||
border: none;
|
|
||||||
border-radius: 12px;
|
|
||||||
-moz-border-radius: 12px;
|
|
||||||
-webkit-border-radius: 12px;
|
|
||||||
text-align: center;
|
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
font-family: sans-serif;
|
|
||||||
text-decoration: none;
|
|
||||||
padding: 4px 0;
|
|
||||||
display: inline-block;
|
|
||||||
line-height: 16px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pretix-widget-lightbox-close svg {
|
|
||||||
display: inline-block;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.pretix-widget-primary-color {
|
.pretix-widget-primary-color {
|
||||||
/* in SVG */
|
/* in SVG */
|
||||||
fill: $brand-primary;
|
fill: $brand-primary;
|
||||||
|
|||||||
@@ -94,8 +94,6 @@ def test_initialize_valid_token(client, new_device: Device):
|
|||||||
'hardware_brand': 'Samsung',
|
'hardware_brand': 'Samsung',
|
||||||
'hardware_model': 'Galaxy S',
|
'hardware_model': 'Galaxy S',
|
||||||
'software_brand': 'pretixdroid',
|
'software_brand': 'pretixdroid',
|
||||||
'os_name': 'Android',
|
|
||||||
'os_version': '2.3.3',
|
|
||||||
'software_version': '4.0.0'
|
'software_version': '4.0.0'
|
||||||
})
|
})
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
@@ -107,7 +105,6 @@ def test_initialize_valid_token(client, new_device: Device):
|
|||||||
new_device.refresh_from_db()
|
new_device.refresh_from_db()
|
||||||
assert new_device.api_token
|
assert new_device.api_token
|
||||||
assert new_device.initialized
|
assert new_device.initialized
|
||||||
assert new_device.os_version == "2.3.3"
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@@ -145,8 +142,6 @@ def test_update_valid_fields(device_client, device: Device):
|
|||||||
resp = device_client.post('/api/v1/device/update', {
|
resp = device_client.post('/api/v1/device/update', {
|
||||||
'hardware_brand': 'Samsung',
|
'hardware_brand': 'Samsung',
|
||||||
'hardware_model': 'Galaxy S',
|
'hardware_model': 'Galaxy S',
|
||||||
'os_name': 'Android',
|
|
||||||
'os_version': '2.3.3',
|
|
||||||
'software_brand': 'pretixdroid',
|
'software_brand': 'pretixdroid',
|
||||||
'software_version': '5.0.0',
|
'software_version': '5.0.0',
|
||||||
'info': {
|
'info': {
|
||||||
@@ -156,23 +151,9 @@ def test_update_valid_fields(device_client, device: Device):
|
|||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
device.refresh_from_db()
|
device.refresh_from_db()
|
||||||
assert device.software_version == '5.0.0'
|
assert device.software_version == '5.0.0'
|
||||||
assert device.os_version == '2.3.3'
|
|
||||||
assert device.info == {'foo': 'bar'}
|
assert device.info == {'foo': 'bar'}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
|
||||||
def test_update_valid_without_optional_fields(device_client, device: Device):
|
|
||||||
resp = device_client.post('/api/v1/device/update', {
|
|
||||||
'hardware_brand': 'Samsung',
|
|
||||||
'hardware_model': 'Galaxy S',
|
|
||||||
'software_brand': 'pretixdroid',
|
|
||||||
'software_version': '5.0.0',
|
|
||||||
}, format='json')
|
|
||||||
assert resp.status_code == 200
|
|
||||||
device.refresh_from_db()
|
|
||||||
assert device.software_version == '5.0.0'
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_keyroll_required_auth(client, token_client, device: Device):
|
def test_keyroll_required_auth(client, token_client, device: Device):
|
||||||
resp = client.post('/api/v1/device/roll', {})
|
resp = client.post('/api/v1/device/roll', {})
|
||||||
|
|||||||
@@ -35,8 +35,6 @@ def device(organizer, event):
|
|||||||
unique_serial="UOS3GNZ27O39V3QS",
|
unique_serial="UOS3GNZ27O39V3QS",
|
||||||
initialization_token="frkso3m2w58zuw70",
|
initialization_token="frkso3m2w58zuw70",
|
||||||
hardware_model="TC25",
|
hardware_model="TC25",
|
||||||
os_name="Android",
|
|
||||||
os_version="8.1.0",
|
|
||||||
software_brand="pretixSCAN",
|
software_brand="pretixSCAN",
|
||||||
software_version="1.5.1",
|
software_version="1.5.1",
|
||||||
initialized=now(),
|
initialized=now(),
|
||||||
@@ -60,8 +58,6 @@ TEST_DEV_RES = {
|
|||||||
"initialized": "2020-09-18T14:17:44.190021Z",
|
"initialized": "2020-09-18T14:17:44.190021Z",
|
||||||
"hardware_brand": "Zebra",
|
"hardware_brand": "Zebra",
|
||||||
"hardware_model": "TC25",
|
"hardware_model": "TC25",
|
||||||
"os_name": "Android",
|
|
||||||
"os_version": "8.1.0",
|
|
||||||
"software_brand": "pretixSCAN",
|
"software_brand": "pretixSCAN",
|
||||||
"software_version": "1.5.1",
|
"software_version": "1.5.1",
|
||||||
"security_profile": "full"
|
"security_profile": "full"
|
||||||
|
|||||||
@@ -20,8 +20,7 @@
|
|||||||
# <https://www.gnu.org/licenses/>.
|
# <https://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
import json
|
import json
|
||||||
import zoneinfo
|
from datetime import datetime, timedelta
|
||||||
from datetime import date, datetime, timedelta
|
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
@@ -32,7 +31,6 @@ from django.test import TestCase
|
|||||||
from django.utils.timezone import make_aware, now
|
from django.utils.timezone import make_aware, now
|
||||||
from django_countries.fields import Country
|
from django_countries.fields import Country
|
||||||
from django_scopes import scope
|
from django_scopes import scope
|
||||||
from freezegun import freeze_time
|
|
||||||
from tests.testdummy.signals import FoobazSalesChannel
|
from tests.testdummy.signals import FoobazSalesChannel
|
||||||
|
|
||||||
from pretix.base.decimal import round_decimal
|
from pretix.base.decimal import round_decimal
|
||||||
@@ -408,56 +406,6 @@ def test_expiring_auto_disabled(event):
|
|||||||
assert o2.status == Order.STATUS_PENDING
|
assert o2.status == Order.STATUS_PENDING
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
|
||||||
def test_expiring_auto_delayed(event):
|
|
||||||
event.settings.set('payment_term_expire_delay_days', 3)
|
|
||||||
event.settings.set('payment_term_last', date(2023, 7, 2))
|
|
||||||
event.settings.set('timezone', 'Europe/Berlin')
|
|
||||||
o1 = Order.objects.create(
|
|
||||||
code='FOO', event=event, email='dummy@dummy.test',
|
|
||||||
status=Order.STATUS_PENDING,
|
|
||||||
datetime=datetime(2023, 6, 22, 12, 13, 14, tzinfo=zoneinfo.ZoneInfo("Europe/Berlin")),
|
|
||||||
expires=datetime(2023, 6, 30, 23, 59, 59, tzinfo=zoneinfo.ZoneInfo("Europe/Berlin")),
|
|
||||||
total=0,
|
|
||||||
)
|
|
||||||
o2 = Order.objects.create(
|
|
||||||
code='FO2', event=event, email='dummy@dummy.test',
|
|
||||||
status=Order.STATUS_PENDING,
|
|
||||||
datetime=datetime(2023, 6, 22, 12, 13, 14, tzinfo=zoneinfo.ZoneInfo("Europe/Berlin")),
|
|
||||||
expires=datetime(2023, 6, 28, 23, 59, 59, tzinfo=zoneinfo.ZoneInfo("Europe/Berlin")),
|
|
||||||
total=0,
|
|
||||||
)
|
|
||||||
assert o1.payment_term_expire_date == o1.expires + timedelta(days=2) # limited by term_last
|
|
||||||
assert o2.payment_term_expire_date == o2.expires + timedelta(days=3)
|
|
||||||
with freeze_time("2023-06-29T00:01:00+02:00"):
|
|
||||||
expire_orders(None)
|
|
||||||
o1 = Order.objects.get(id=o1.id)
|
|
||||||
assert o1.status == Order.STATUS_PENDING
|
|
||||||
o2 = Order.objects.get(id=o2.id)
|
|
||||||
assert o2.status == Order.STATUS_PENDING
|
|
||||||
|
|
||||||
with freeze_time("2023-07-01T23:50:00+02:00"):
|
|
||||||
expire_orders(None)
|
|
||||||
o1 = Order.objects.get(id=o1.id)
|
|
||||||
assert o1.status == Order.STATUS_PENDING
|
|
||||||
o2 = Order.objects.get(id=o2.id)
|
|
||||||
assert o2.status == Order.STATUS_PENDING
|
|
||||||
|
|
||||||
with freeze_time("2023-07-02T00:01:00+02:00"):
|
|
||||||
expire_orders(None)
|
|
||||||
o1 = Order.objects.get(id=o1.id)
|
|
||||||
assert o1.status == Order.STATUS_PENDING
|
|
||||||
o2 = Order.objects.get(id=o2.id)
|
|
||||||
assert o2.status == Order.STATUS_EXPIRED
|
|
||||||
|
|
||||||
with freeze_time("2023-07-03T00:01:00+02:00"):
|
|
||||||
expire_orders(None)
|
|
||||||
o1 = Order.objects.get(id=o1.id)
|
|
||||||
assert o1.status == Order.STATUS_EXPIRED
|
|
||||||
o2 = Order.objects.get(id=o2.id)
|
|
||||||
assert o2.status == Order.STATUS_EXPIRED
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_do_not_expire_if_approval_pending(event):
|
def test_do_not_expire_if_approval_pending(event):
|
||||||
o1 = Order.objects.create(
|
o1 = Order.objects.create(
|
||||||
|
|||||||
@@ -30,8 +30,6 @@ from pretix.base.templatetags.rich_text import (
|
|||||||
# Test link detection
|
# Test link detection
|
||||||
("google.com",
|
("google.com",
|
||||||
'<a href="http://google.com" rel="noopener" target="_blank">google.com</a>'),
|
'<a href="http://google.com" rel="noopener" target="_blank">google.com</a>'),
|
||||||
# Test link escaping
|
|
||||||
("google\\.com", 'google.com'),
|
|
||||||
# Test abslink_callback
|
# Test abslink_callback
|
||||||
("[Call](tel:+12345)",
|
("[Call](tel:+12345)",
|
||||||
'<a href="tel:+12345" rel="nofollow">Call</a>'),
|
'<a href="tel:+12345" rel="nofollow">Call</a>'),
|
||||||
@@ -81,20 +79,3 @@ def test_newline_handling(content, result):
|
|||||||
])
|
])
|
||||||
def test_newline_handling_email(content, result):
|
def test_newline_handling_email(content, result):
|
||||||
assert markdown_compile_email(content) == result
|
assert markdown_compile_email(content) == result
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("content,result,result_snippet", [
|
|
||||||
# attributes
|
|
||||||
('<a onclick="javascript:foo()">foo</a>', '<p><a>foo</a></p>', '<a>foo</a>'),
|
|
||||||
('<strong color="red">foo</strong>',
|
|
||||||
'<p><strong>foo</strong></p>',
|
|
||||||
'<strong>foo</strong>'),
|
|
||||||
# protocols
|
|
||||||
('<a href="javascript:foo()">foo</a>', '<p><a>foo</a></p>', '<a>foo</a>'),
|
|
||||||
# tags
|
|
||||||
('<script>foo</script>', '<script>foo</script>', 'foo'),
|
|
||||||
])
|
|
||||||
def test_cleanup(content, result, result_snippet):
|
|
||||||
assert rich_text(content) == result
|
|
||||||
assert rich_text_snippet(content) == result_snippet
|
|
||||||
assert markdown_compile_email(content) == result
|
|
||||||
|
|||||||
@@ -185,7 +185,6 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
|||||||
"max_price": None,
|
"max_price": None,
|
||||||
"price": {"gross": "23.00", "net": "19.33", "tax": "3.67", "name": "", "rate": "19.00", "includes_mixed_tax_rate": False},
|
"price": {"gross": "23.00", "net": "19.33", "tax": "3.67", "name": "", "rate": "19.00", "includes_mixed_tax_rate": False},
|
||||||
"picture": None,
|
"picture": None,
|
||||||
"picture_fullsize": None,
|
|
||||||
"has_variations": 0,
|
"has_variations": 0,
|
||||||
"allow_waitinglist": True,
|
"allow_waitinglist": True,
|
||||||
"mandatory_priced_addons": False,
|
"mandatory_priced_addons": False,
|
||||||
@@ -205,7 +204,6 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
|||||||
"max_price": "14.00",
|
"max_price": "14.00",
|
||||||
"price": None,
|
"price": None,
|
||||||
"picture": None,
|
"picture": None,
|
||||||
"picture_fullsize": None,
|
|
||||||
"has_variations": 4,
|
"has_variations": 4,
|
||||||
"allow_waitinglist": True,
|
"allow_waitinglist": True,
|
||||||
"mandatory_priced_addons": False,
|
"mandatory_priced_addons": False,
|
||||||
@@ -267,7 +265,6 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
|||||||
"price": {"gross": "23.00", "net": "19.33", "tax": "3.67", "name": "", "rate": "19.00",
|
"price": {"gross": "23.00", "net": "19.33", "tax": "3.67", "name": "", "rate": "19.00",
|
||||||
"includes_mixed_tax_rate": False},
|
"includes_mixed_tax_rate": False},
|
||||||
"picture": None,
|
"picture": None,
|
||||||
"picture_fullsize": None,
|
|
||||||
"has_variations": 0,
|
"has_variations": 0,
|
||||||
"allow_waitinglist": True,
|
"allow_waitinglist": True,
|
||||||
"mandatory_priced_addons": False,
|
"mandatory_priced_addons": False,
|
||||||
@@ -313,7 +310,6 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
|||||||
"max_price": "14.00",
|
"max_price": "14.00",
|
||||||
"price": None,
|
"price": None,
|
||||||
"picture": None,
|
"picture": None,
|
||||||
"picture_fullsize": None,
|
|
||||||
"has_variations": 4,
|
"has_variations": 4,
|
||||||
"allow_waitinglist": True,
|
"allow_waitinglist": True,
|
||||||
"mandatory_priced_addons": False,
|
"mandatory_priced_addons": False,
|
||||||
@@ -375,7 +371,6 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
|||||||
"max_price": None,
|
"max_price": None,
|
||||||
"price": {"gross": "23.00", "net": "19.33", "tax": "3.67", "name": "", "rate": "19.00", "includes_mixed_tax_rate": False},
|
"price": {"gross": "23.00", "net": "19.33", "tax": "3.67", "name": "", "rate": "19.00", "includes_mixed_tax_rate": False},
|
||||||
"picture": None,
|
"picture": None,
|
||||||
"picture_fullsize": None,
|
|
||||||
"has_variations": 0,
|
"has_variations": 0,
|
||||||
"allow_waitinglist": True,
|
"allow_waitinglist": True,
|
||||||
"mandatory_priced_addons": False,
|
"mandatory_priced_addons": False,
|
||||||
@@ -430,7 +425,6 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
|||||||
'id': self.shirt.pk,
|
'id': self.shirt.pk,
|
||||||
'name': 'T-Shirt',
|
'name': 'T-Shirt',
|
||||||
'picture': None,
|
'picture': None,
|
||||||
"picture_fullsize": None,
|
|
||||||
'description': None,
|
'description': None,
|
||||||
'has_variations': 2,
|
'has_variations': 2,
|
||||||
"allow_waitinglist": True,
|
"allow_waitinglist": True,
|
||||||
|
|||||||
Reference in New Issue
Block a user