mirror of
https://github.com/pretix/pretix.git
synced 2026-08-27 13:24:41 +00:00
Compare commits
24
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ba1439a19 | ||
|
|
6496fbedfd | ||
|
|
bd80bd169a | ||
|
|
f1074ac1c7 | ||
|
|
31de823bc8 | ||
|
|
8a47ba7ff5 | ||
|
|
dd2a74557d | ||
|
|
bb9c4fa18d | ||
|
|
ac356acd29 | ||
|
|
54eadaffcc | ||
|
|
617a548b19 | ||
|
|
fe0f7864e7 | ||
|
|
fc4a2f5508 | ||
|
|
4cb32a753f | ||
|
|
3ae9aabcfb | ||
|
|
3270c4e583 | ||
|
|
3b285a89dd | ||
|
|
c1683df1fd | ||
|
|
6fdcbcebd2 | ||
|
|
ddbea7c32e | ||
|
|
3f8ed0f722 | ||
|
|
388eb7de96 | ||
|
|
df4c83d849 | ||
|
|
fa4cec8a2d |
@@ -864,6 +864,9 @@ Generating new secrets
|
||||
|
||||
Triggers generation of new ``secret`` and ``web_secret`` attributes for both the order and all order positions.
|
||||
|
||||
Ticket secrets of order positions that have been used to issue a gift card can not
|
||||
be changed. Only the link (``web_secret``) will be changed in this case.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
@@ -895,6 +898,9 @@ Generating new secrets
|
||||
|
||||
Triggers generation of a new ``secret`` and ``web_secret`` attribute for a single order position.
|
||||
|
||||
Ticket secrets of order positions that have been used to issue a gift card can not
|
||||
be changed. Only the link (``web_secret``) will be changed in this case.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
+4
-4
@@ -54,7 +54,7 @@ dependencies = [
|
||||
"django-otp==1.7.*",
|
||||
"django-phonenumber-field==8.4.*",
|
||||
"django-querytagger==0.0.3",
|
||||
"django-redis==6.0.*",
|
||||
"django-redis==7.0.*",
|
||||
"django-scopes==2.0.*",
|
||||
"django-statici18n==2.7.*",
|
||||
"djangorestframework==3.17.*",
|
||||
@@ -77,7 +77,7 @@ dependencies = [
|
||||
"paypal-checkout-serversdk==1.0.*",
|
||||
"PyJWT==2.13.*",
|
||||
"phonenumberslite==9.0.*",
|
||||
"Pillow==12.2.*",
|
||||
"Pillow==12.3.*",
|
||||
"pretix-plugin-build",
|
||||
"protobuf==7.35.*",
|
||||
"psycopg2-binary",
|
||||
@@ -92,8 +92,8 @@ dependencies = [
|
||||
"pyuca",
|
||||
"qrcode==8.2",
|
||||
"redis==7.4.*",
|
||||
"reportlab==4.5.*",
|
||||
"requests==2.32.*",
|
||||
"reportlab==5.0.*",
|
||||
"requests==2.34.*",
|
||||
"sentry-sdk==2.64.*",
|
||||
"sepaxml==2.7.*",
|
||||
"stripe==7.9.*",
|
||||
|
||||
@@ -747,6 +747,7 @@ class EventSettingsSerializer(SettingsSerializer):
|
||||
'max_items_per_order',
|
||||
'reservation_time',
|
||||
'contact_mail',
|
||||
'contact_url',
|
||||
'show_variations_expanded',
|
||||
'hide_sold_out',
|
||||
'meta_noindex',
|
||||
|
||||
@@ -27,7 +27,7 @@ from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db import transaction
|
||||
from django.db.models import Q
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import gettext, gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
@@ -492,7 +492,9 @@ class TeamInviteSerializer(serializers.ModelSerializer):
|
||||
def _send_invite(self, instance):
|
||||
mail(
|
||||
instance.email,
|
||||
_('Account invitation'),
|
||||
gettext('You\'ve been invited to join %(organizer)s') % {
|
||||
'organizer': self.context['organizer'].name,
|
||||
},
|
||||
'pretixcontrol/email/invitation.txt',
|
||||
{
|
||||
'instance': settings.PRETIX_INSTANCE_NAME,
|
||||
@@ -574,6 +576,7 @@ class OrganizerSettingsSerializer(SettingsSerializer):
|
||||
'customer_accounts_require_login_for_order_access',
|
||||
'invoice_regenerate_allowed',
|
||||
'contact_mail',
|
||||
'contact_url',
|
||||
'imprint_url',
|
||||
'organizer_info_text',
|
||||
'event_list_type',
|
||||
|
||||
@@ -1398,7 +1398,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
||||
elif self.validate_vat_id and vat_id_applicable:
|
||||
try:
|
||||
normalized_id = validate_vat_id(data.get('vat_id'), str(data.get('country')))
|
||||
self.instance.vat_id_validated = True
|
||||
self.instance.vat_id_validated = bool(normalized_id)
|
||||
self.instance.vat_id = data['vat_id'] = normalized_id
|
||||
except VATIDFinalError as e:
|
||||
if self.all_optional:
|
||||
|
||||
@@ -373,7 +373,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||
|
||||
mail(
|
||||
email or self.email,
|
||||
_('Account information changed'),
|
||||
_('Changes to your account'),
|
||||
'pretixcontrol/email/security_notice.txt',
|
||||
{
|
||||
'user': self,
|
||||
@@ -400,12 +400,13 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||
|
||||
with language(self.locale):
|
||||
if reason == 'email_change':
|
||||
msg = str(_('to confirm changing your email address from {old_email}\nto {new_email}, use the following code:').format(
|
||||
msg = str(_('To change your email address from {old_email} to {new_email}, use the following code:').format(
|
||||
old_email=self.email, new_email=email,
|
||||
))
|
||||
elif reason == 'email_verify':
|
||||
msg = str(_('to confirm that your email address {email} belongs to your pretix account, use the following code:').format(
|
||||
msg = str(_('To verify your email address {email} on {instance}, use the following code:').format(
|
||||
email=self.email,
|
||||
instance=settings.PRETIX_INSTANCE_NAME,
|
||||
))
|
||||
else:
|
||||
raise Exception('Invalid confirmation code reason')
|
||||
@@ -418,7 +419,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||
}
|
||||
mail(
|
||||
email or self.email,
|
||||
_('pretix confirmation code'),
|
||||
_('Your confirmation code'),
|
||||
'pretixcontrol/email/confirmation_code.txt',
|
||||
{
|
||||
'user': self,
|
||||
@@ -462,7 +463,9 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||
from pretix.base.services.mail import mail
|
||||
|
||||
mail(
|
||||
self.email, _('Password recovery'), 'pretixcontrol/email/forgot.txt',
|
||||
self.email,
|
||||
_('Reset your password'),
|
||||
'pretixcontrol/email/forgot.txt',
|
||||
{
|
||||
'instance': settings.PRETIX_INSTANCE_NAME,
|
||||
'user': self,
|
||||
|
||||
@@ -1403,12 +1403,15 @@ class Event(EventMixin, LoggedModel):
|
||||
|
||||
for mp in self.organizer.meta_properties.all():
|
||||
if mp.required and not self.meta_data.get(mp.name):
|
||||
issues.append(format_html(
|
||||
'<a href="{href}{href_hash}">{text}</a>',
|
||||
text=gettext('You need to fill the meta parameter "{property}".').format(property=mp.name),
|
||||
href=reverse('control:event.settings', kwargs={'organizer': self.organizer.slug, 'event': self.slug}),
|
||||
href_hash=f'#id_prop-{mp.pk}-value',
|
||||
))
|
||||
issues.append(
|
||||
('<a {a_attr}>' + gettext('You need to fill the meta parameter "{property}".') + '</a>').format(
|
||||
property=mp.name,
|
||||
a_attr='href="%s#id_prop-%d-value"' % (
|
||||
reverse('control:event.settings', kwargs={'organizer': self.organizer.slug, 'event': self.slug}),
|
||||
mp.pk
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
responses = event_live_issues.send(self)
|
||||
for receiver, response in sorted(responses, key=lambda r: str(r[0])):
|
||||
|
||||
@@ -834,7 +834,7 @@ class BasePaymentProvider:
|
||||
"""
|
||||
raise NotImplementedError() # NOQA
|
||||
|
||||
def execute_payment(self, request: HttpRequest, payment: OrderPayment) -> str:
|
||||
def execute_payment(self, request: HttpRequest, payment: OrderPayment) -> str | None:
|
||||
"""
|
||||
After the user has confirmed their purchase, this method will be called to complete
|
||||
the payment process. This is the place to actually move the money if applicable.
|
||||
|
||||
+10
-5
@@ -77,6 +77,7 @@ from reportlab.platypus import Paragraph
|
||||
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import Checkin, Event, Order, OrderPosition, Question
|
||||
from pretix.base.services.placeholders import PlaceholderContext
|
||||
from pretix.base.settings import PERSON_NAME_SCHEMES
|
||||
from pretix.base.signals import layout_image_variables, layout_text_variables
|
||||
from pretix.base.templatetags.money import money_filter
|
||||
@@ -401,11 +402,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"editor_sample": _("Event organizer info text"),
|
||||
"evaluate": lambda op, order, ev: str(order.event.settings.organizer_info_text)
|
||||
}),
|
||||
("event_info_text", {
|
||||
"label": _("Event info text"),
|
||||
"editor_sample": _("Event info text"),
|
||||
"evaluate": lambda op, order, ev: str(order.event.settings.event_info_text)
|
||||
}),
|
||||
("event_info_text", {}), # Placeholder to "reserve" position, defined later in `get_variables`
|
||||
("now_date", {
|
||||
"label": _("Printing date"),
|
||||
"editor_sample": _("2017-05-31"),
|
||||
@@ -670,6 +667,14 @@ def get_images(event):
|
||||
def get_variables(event):
|
||||
v = copy.copy(DEFAULT_VARIABLES)
|
||||
|
||||
templating_context = PlaceholderContext(event=event)
|
||||
v['event_info_text'] = {
|
||||
"label": _("Event info text"),
|
||||
"editor_sample": _("Event info text"),
|
||||
"evaluate": lambda op, order, ev:
|
||||
templating_context.format(str(order.event.settings.event_info_text))
|
||||
}
|
||||
|
||||
scheme = PERSON_NAME_SCHEMES[event.settings.name_scheme]
|
||||
|
||||
concatenation_for_salutation = scheme.get("concatenation_for_salutation", scheme["concatenation"])
|
||||
|
||||
@@ -245,6 +245,9 @@ def recv_classic(sender, **kwargs):
|
||||
|
||||
|
||||
def assign_ticket_secret(event, position, force_invalidate_if_revokation_list_used=False, force_invalidate=False, save=True):
|
||||
if position.pk and position.issued_gift_cards.exists():
|
||||
return
|
||||
|
||||
gen = event.ticket_secret_generator
|
||||
if gen.use_revocation_list and force_invalidate_if_revokation_list_used:
|
||||
force_invalidate = True
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
import logging
|
||||
from decimal import Decimal
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
from django.db.models import Count, Exists, IntegerField, OuterRef, Q, Subquery
|
||||
from django.utils.crypto import get_random_string
|
||||
@@ -377,12 +378,13 @@ def cancel_event(self, event: Event, subevent: int, auto_refund: bool,
|
||||
confirmation_code = get_random_string(8, allowed_chars="01234567890")
|
||||
mail(
|
||||
user.email,
|
||||
subject=gettext('Bulk-refund confirmation'),
|
||||
subject=gettext('Confirm event cancellation and bulk refund'),
|
||||
template='pretixbase/email/cancel_confirm.txt',
|
||||
context={
|
||||
"event": str(event),
|
||||
"amount": money_filter(refund_total, event.currency),
|
||||
"confirmation_code": confirmation_code,
|
||||
"instance": settings.PRETIX_INSTANCE_NAME,
|
||||
},
|
||||
locale=user.locale,
|
||||
)
|
||||
|
||||
@@ -340,12 +340,13 @@ def _run_scheduled_export(schedule, context: Union[Event, Organizer], exporter,
|
||||
if schedule.owner.is_active:
|
||||
mail(
|
||||
email=schedule.owner.email,
|
||||
subject=gettext('Export failed'),
|
||||
subject=gettext('Scheduled export failed'),
|
||||
template='pretixbase/email/export_failed.txt',
|
||||
context={
|
||||
'configuration_url': config_url,
|
||||
'reason': msg,
|
||||
'soft': soft,
|
||||
'instance': settings.PRETIX_INSTANCE_NAME,
|
||||
},
|
||||
event=context if isinstance(context, Event) else None,
|
||||
organizer=context.organizer if isinstance(context, Event) else context,
|
||||
|
||||
@@ -1599,6 +1599,7 @@ class OrderChangeManager:
|
||||
'seat_forbidden': gettext_lazy('The selected product does not allow to select a seat.'),
|
||||
'tax_rule_country_blocked': gettext_lazy('The selected country is blocked by your tax rule.'),
|
||||
'gift_card_change': gettext_lazy('You cannot change the price of a position that has been used to issue a gift card.'),
|
||||
'gift_card_secret': gettext_lazy('You cannot change the ticket secret of a position that has been used to issue a gift card.'),
|
||||
'max_items_per_product': ngettext_lazy(
|
||||
"You cannot select more than %(max)s item of the product %(product)s.",
|
||||
"You cannot select more than %(max)s items of the product %(product)s.",
|
||||
@@ -1756,6 +1757,9 @@ class OrderChangeManager:
|
||||
self._operations.append(self.RegenerateSecretOperation(position))
|
||||
|
||||
def change_ticket_secret(self, position: OrderPosition, new_secret: str):
|
||||
if position.issued_gift_cards.exists():
|
||||
raise OrderError(self.error_messages['gift_card_secret'])
|
||||
|
||||
self._operations.append(self.ChangeSecretOperation(position, new_secret))
|
||||
|
||||
def change_valid_from(self, position: OrderPosition, new_value: datetime):
|
||||
|
||||
@@ -44,7 +44,7 @@ from django.conf import settings
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import gettext, gettext_lazy as _
|
||||
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import CachedFile, Event, User, cachedfile_name
|
||||
@@ -171,15 +171,16 @@ def shred(self, event: Event, fileid: str, confirm_code: str, user: int=None, lo
|
||||
|
||||
if user:
|
||||
with language(user.locale):
|
||||
event_name = str(event.name)
|
||||
mail(
|
||||
user.email,
|
||||
_('Data shredding completed'),
|
||||
gettext('Data shredding completed for %(event)s') % {'event': event_name},
|
||||
'pretixbase/email/shred_completed.txt',
|
||||
{
|
||||
'instance': settings.PRETIX_INSTANCE_NAME,
|
||||
'user': user,
|
||||
'organizer': event.organizer.name,
|
||||
'event': str(event.name),
|
||||
'event': event_name,
|
||||
'start_time': date_format(parse(indexdata['time']).astimezone(event.timezone), 'SHORT_DATETIME_FORMAT'),
|
||||
'shredders': ', '.join([str(s.verbose_name) for s in shredders])
|
||||
},
|
||||
|
||||
@@ -1276,7 +1276,7 @@ DEFAULTS = {
|
||||
'serializer_class': serializers.BooleanField,
|
||||
'write_permission': 'event.settings.invoicing:write',
|
||||
'form_kwargs': dict(
|
||||
label=_("Allow to update existing invoices"),
|
||||
label=_("Allow updating existing invoices"),
|
||||
help_text=_("By default, invoices can never again be changed once they are issued. In most countries, we "
|
||||
"recommend to leave this option turned off and always issue a new invoice if a change needs "
|
||||
"to be made."),
|
||||
@@ -2284,6 +2284,17 @@ DEFAULTS = {
|
||||
help_text=_("We'll show this publicly to allow attendees to contact you.")
|
||||
)
|
||||
},
|
||||
'contact_url': {
|
||||
'default': None,
|
||||
'type': str,
|
||||
'serializer_class': serializers.URLField,
|
||||
'form_class': forms.URLField,
|
||||
'form_kwargs': dict(
|
||||
label=_("Contact URL"),
|
||||
help_text=_("If you set this, the footer contact link will point here instead of using the email address above. "
|
||||
"Please note that you still need to add a contact email address that will be shared with all emails you send.")
|
||||
)
|
||||
},
|
||||
'imprint_url': {
|
||||
'default': None,
|
||||
'type': str,
|
||||
|
||||
@@ -535,9 +535,8 @@ EventPluginRegistry = PluginAwareRegistry # for backwards compatibility
|
||||
event_live_issues = EventPluginSignal()
|
||||
"""
|
||||
This signal is sent out to determine whether an event can be taken live. If you want to
|
||||
prevent the event from going live, return an error message to display to the user (either
|
||||
as a SafeString containing HTML, or a string that will be HTML-escaped). If you don't,
|
||||
your receiver should return ``None``.
|
||||
prevent the event from going live, return a string that will be displayed to the user
|
||||
as the error message. If you don't, your receiver should return ``None``.
|
||||
|
||||
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
"""
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
{% load i18n %}
|
||||
{% trans "You have requested us to cancel an event which includes a larger bulk-refund:" %}
|
||||
{% trans "You requested to cancel an event that involves a large bulk refund:" %}
|
||||
|
||||
{% trans "Event" %}: {{ event }}
|
||||
- {% trans "Event" %}: {{ event }}
|
||||
- {% trans "Estimated refund" %}: **{{ amount }}**
|
||||
|
||||
{% trans "Estimated refund amount" %}: **{{ amount }}**
|
||||
{% trans "To confirm, paste the following code into the cancellation form:" %}
|
||||
|
||||
{% trans "Please confirm that you want to proceed by coping the following confirmation code into the cancellation form:" %}
|
||||
{{ confirmation_code }}
|
||||
|
||||
**{{ confirmation_code }}**
|
||||
{% blocktrans with instance=instance %}Don't share this code with anyone. The {{ instance }} team will never ask you for it.{% endblocktrans %}
|
||||
|
||||
{% blocktrans with instance=instance %}Thanks,
|
||||
The {{ instance }} Team{% endblocktrans %}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
{% load i18n %}
|
||||
{% trans "Your export failed." %}
|
||||
{% trans "Your scheduled export failed." %}
|
||||
|
||||
{% trans "Reason:" %} {{ reason }}
|
||||
- {% trans "Reason" %}: {{ reason }}
|
||||
|
||||
{% if not soft %}
|
||||
{% trans "If your export fails five times in a row, it will no longer be sent." %}
|
||||
{% endif %}
|
||||
{% if not soft %}{% trans "If an export fails five times in a row, we'll stop sending it." %}{% endif %}
|
||||
|
||||
{% trans "Configuration link:" %}
|
||||
{% trans "You can adjust or remove this export here:" %}
|
||||
|
||||
{{ configuration_url }}
|
||||
|
||||
{% blocktrans with instance=instance %}Thanks,
|
||||
The {{ instance }} Team{% endblocktrans %}
|
||||
|
||||
@@ -52,13 +52,13 @@
|
||||
<table cellpadding="20"><tr><td>
|
||||
<![endif]-->
|
||||
<div class="content">
|
||||
{% trans "You receive these emails based on your notification settings." %}<br>
|
||||
{% trans "You're receiving this email based on your notification settings." %}<br>
|
||||
<a href="{{ settings_url }}">
|
||||
{% trans "Click here to view and change your notification settings" %}
|
||||
{% trans "Manage settings" %}
|
||||
</a>
|
||||
{% if disable_url %}<br>
|
||||
<a href="{{ disable_url }}">
|
||||
{% trans "Click here disable all notifications immediately." %}
|
||||
{% trans "Disable all notifications" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
{% load i18n %}
|
||||
{{ notification.title }}{% if notification.detail %}
|
||||
|
||||
{{ notification.detail }}
|
||||
{% endif %}{% if notification.url %}
|
||||
{{ notification.detail }}{% endif %}{% if notification.url %}
|
||||
|
||||
{{ notification.url }}{% endif %}{% for attr in notification.attributes %}
|
||||
{{ notification.url }}{% endif %}{% if notification.attributes %}
|
||||
|
||||
{{ attr.title }}: {{ attr.value }}{% endfor %}{% for action in notification.actions %}
|
||||
{% for attr in notification.attributes %}- {{ attr.title }}: {{ attr.value }}
|
||||
{% endfor %}{% endif %}{% for action in notification.actions %}
|
||||
|
||||
{{ action.label }}
|
||||
{{ action.url }}{% endfor %}
|
||||
{{ action.label }}:
|
||||
|
||||
{% trans "You receive these emails based on your notification settings." %}
|
||||
{% trans "Click here to view and change your notification settings:" %}
|
||||
{{ settings_url }}
|
||||
{% if disable_url %}{% trans "Click here disable all notifications immediately:" %}
|
||||
{{ disable_url }}
|
||||
{{ action.url }}{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
{% trans "You're receiving this email based on your notification settings." %}
|
||||
|
||||
- {% trans "Manage settings" %}: {{ settings_url }}
|
||||
{% if disable_url %}- {% trans "Disable all notifications" %}: {{ disable_url }}
|
||||
{% endif %}
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
{% load i18n %}
|
||||
{% load i18n %}{% blocktrans with url=url|safe %}Hello,
|
||||
{% load i18n %}{% blocktrans %}Hello,
|
||||
|
||||
we hereby confirm that the following data shredding job has been completed:
|
||||
The following data shredding job has been completed:
|
||||
|
||||
Organizer: {{ organizer }}
|
||||
- Organizer: {{ organizer }}
|
||||
- Event: {{ event }}
|
||||
- Data selection: {{ shredders }}
|
||||
- Start time: {{ start_time }}
|
||||
|
||||
Event: {{ event }}
|
||||
Any data added to the event after the start time may not have been deleted.
|
||||
|
||||
Data selection: {{ shredders }}
|
||||
|
||||
Start time: {{ start_time }} (new data added after this time might not have been deleted)
|
||||
|
||||
Best regards,
|
||||
|
||||
Your {{ instance }} team
|
||||
Thanks,
|
||||
The {{ instance }} Team
|
||||
{% endblocktrans %}
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
import importlib
|
||||
|
||||
from django import template
|
||||
from django.utils.html import conditional_escape
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from pretix.base.models import Event
|
||||
@@ -45,7 +44,7 @@ def eventsignal(event: Event, signame: str, **kwargs):
|
||||
_html = []
|
||||
for receiver, response in signal.send(event, **kwargs):
|
||||
if response:
|
||||
_html.append(conditional_escape(response))
|
||||
_html.append(response)
|
||||
return mark_safe("".join(_html))
|
||||
|
||||
|
||||
@@ -64,5 +63,5 @@ def signal(signame: str, request, **kwargs):
|
||||
_html = []
|
||||
for receiver, response in signal.send(request, **kwargs):
|
||||
if response:
|
||||
_html.append(conditional_escape(response))
|
||||
_html.append(response)
|
||||
return mark_safe("".join(_html))
|
||||
|
||||
@@ -625,6 +625,7 @@ class EventSettingsForm(EventSettingsValidationMixin, FormPlaceholderMixin, Sett
|
||||
'max_items_per_order',
|
||||
'reservation_time',
|
||||
'contact_mail',
|
||||
'contact_url',
|
||||
'show_variations_expanded',
|
||||
'hide_sold_out',
|
||||
'meta_noindex',
|
||||
@@ -671,6 +672,11 @@ class EventSettingsForm(EventSettingsValidationMixin, FormPlaceholderMixin, Sett
|
||||
|
||||
base_context = {
|
||||
'frontpage_text': ['event'],
|
||||
'presale_has_ended_text': ['event'],
|
||||
'voucher_explanation_text': ['event'],
|
||||
'banner_text': ['event'],
|
||||
'banner_text_bottom': ['event'],
|
||||
'event_info_text': ['event'],
|
||||
}
|
||||
|
||||
def _resolve_virtual_keys_input(self, data, prefix=''):
|
||||
@@ -1899,6 +1905,12 @@ class QuickSetupForm(I18nForm):
|
||||
required=False,
|
||||
help_text=_("We'll show this publicly to allow attendees to contact you.")
|
||||
)
|
||||
contact_url = forms.URLField(
|
||||
label=_("Contact URL"),
|
||||
required=False,
|
||||
help_text=_("If you set this, the footer contact link will point here instead of using the email address above. "
|
||||
"Please note that you still need to add a contact email address that will be shared with all emails you send.")
|
||||
)
|
||||
total_quota = forms.IntegerField(
|
||||
label=_("Total capacity"),
|
||||
min_value=0,
|
||||
|
||||
@@ -604,6 +604,7 @@ class OrganizerSettingsForm(SettingsForm):
|
||||
'customer_accounts_require_login_for_order_access',
|
||||
'invoice_regenerate_allowed',
|
||||
'contact_mail',
|
||||
'contact_url',
|
||||
'imprint_url',
|
||||
'organizer_info_text',
|
||||
'event_list_type',
|
||||
|
||||
@@ -106,6 +106,11 @@ class VoucherForm(I18nModelForm):
|
||||
pass
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields['tag'].widget.attrs['data-typeahead-url'] = reverse('control:event.vouchers.tags.typeahead', kwargs={
|
||||
'event': instance.event.slug,
|
||||
'organizer': instance.event.organizer.slug,
|
||||
})
|
||||
|
||||
if instance.event.has_subevents:
|
||||
self.fields['subevent'].queryset = instance.event.subevents.all()
|
||||
self.fields['subevent'].widget = Select2(
|
||||
|
||||
@@ -39,8 +39,7 @@ from pretix.base.signals import (
|
||||
html_page_start = GlobalSignal()
|
||||
"""
|
||||
This signal allows you to put code in the beginning of the main page for every
|
||||
page in the backend. You are expected to return a SafeString containing HTML, or
|
||||
a string that will be HTML-escaped.
|
||||
page in the backend. You are expected to return HTML.
|
||||
|
||||
The ``sender`` keyword argument will contain the request.
|
||||
"""
|
||||
@@ -130,7 +129,7 @@ event_dashboard_top = EventPluginSignal()
|
||||
Arguments: 'request'
|
||||
|
||||
This signal is sent out to include custom HTML in the top part of the the event dashboard.
|
||||
Receivers should return a SafeString containing HTML, or a string that will be HTML-escaped.
|
||||
Receivers should return HTML.
|
||||
|
||||
As with all event plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
An additional keyword argument ``subevent`` *can* contain a sub-event.
|
||||
@@ -173,7 +172,6 @@ Arguments: 'form'
|
||||
|
||||
This signal allows you to add additional HTML to the form that is used for modifying vouchers.
|
||||
You receive the form object in the ``form`` keyword argument.
|
||||
Receivers should return a SafeString containing HTML, or a string that will be HTML-escaped.
|
||||
|
||||
As with all event plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
"""
|
||||
@@ -211,7 +209,6 @@ Arguments: 'quota'
|
||||
|
||||
This signal allows you to append HTML to a Quota's detail view. You receive the
|
||||
quota as argument in the ``quota`` keyword argument.
|
||||
Receivers should return a SafeString containing HTML, or a string that will be HTML-escaped.
|
||||
|
||||
As with all event plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
"""
|
||||
@@ -222,7 +219,6 @@ Arguments: 'subevent'
|
||||
|
||||
This signal allows you to append HTML to a SubEvent's detail view. You receive the
|
||||
subevent as argument in the ``subevent`` keyword argument.
|
||||
Receivers should return a SafeString containing HTML, or a string that will be HTML-escaped.
|
||||
|
||||
As with all event plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
"""
|
||||
@@ -269,8 +265,7 @@ order_info = EventPluginSignal()
|
||||
"""
|
||||
Arguments: ``order``, ``request``
|
||||
|
||||
This signal is sent out to display additional information on the order detail page.
|
||||
Receivers should return a SafeString containing HTML, or a string that will be HTML-escaped.
|
||||
This signal is sent out to display additional information on the order detail page
|
||||
|
||||
As with all event plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
Additionally, the argument ``order`` and ``request`` are available.
|
||||
@@ -280,8 +275,7 @@ order_approve_info = EventPluginSignal()
|
||||
"""
|
||||
Arguments: ``order``, ``request``
|
||||
|
||||
This signal is sent out to display additional information on the order approve page.
|
||||
Receivers should return a SafeString containing HTML, or a string that will be HTML-escaped.
|
||||
This signal is sent out to display additional information on the order approve page
|
||||
|
||||
As with all event plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
Additionally, the argument ``order`` and ``request`` are available.
|
||||
@@ -292,7 +286,6 @@ order_position_buttons = EventPluginSignal()
|
||||
Arguments: ``order``, ``position``, ``request``
|
||||
|
||||
This signal is sent out to display additional buttons for a single position of an order.
|
||||
Receivers should return a SafeString containing HTML, or a string that will be HTML-escaped.
|
||||
|
||||
As with all event plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
Additionally, the argument ``order`` and ``request`` are available.
|
||||
@@ -322,7 +315,6 @@ Arguments: 'request'
|
||||
|
||||
This signal is sent out to include template snippets on the settings page of an event
|
||||
that allows generating a pretix Widget code.
|
||||
Receivers should return a SafeString containing HTML, or a string that will be HTML-escaped.
|
||||
|
||||
As with all event plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
A second keyword argument ``request`` will contain the request object.
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{% load i18n %}{% blocktrans with url=url|safe messages=messages|safe %}Hello,
|
||||
{% load i18n %}{% blocktrans with code=code reason=reason instance=instance %}Hello,
|
||||
|
||||
{{ reason }}
|
||||
|
||||
{{ code }}
|
||||
|
||||
Please do never give this code to another person. Our support team will never ask for this code.
|
||||
Don't share this code with anyone. The {{ instance }} team will never ask you for it.
|
||||
|
||||
If this code was not requested by you, please contact us immediately.
|
||||
If you didn't request this code, please contact us immediately.
|
||||
|
||||
Best regards,
|
||||
Your {{ instance }} team
|
||||
Thanks,
|
||||
The {{ instance }} Team
|
||||
{% endblocktrans %}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
{% load i18n %}{% blocktrans with code=code instance=instance %}Hello,
|
||||
{% load i18n %}{% blocktrans with code=code address=address instance=instance %}Hello,
|
||||
|
||||
someone requested to use {{ address }} as a sender address on {{ instance }}.
|
||||
This will allow them to send emails that are shown to originate from this email address.
|
||||
If that was you, please enter the following confirmation code:
|
||||
Someone requested to use {{ address }} as a sender address on {{ instance }}. Once verified, emails sent from {{ instance }} can show this address as the sender.
|
||||
|
||||
{{ code }}
|
||||
If this was you, enter the following code in the setup form:
|
||||
|
||||
If this was not requested by you, you can safely ignore this email.
|
||||
{{ code }}
|
||||
|
||||
Best regards,
|
||||
Don't share this code with anyone. The {{ instance }} team will never ask you for it.
|
||||
|
||||
Your {{ instance }} team
|
||||
{% endblocktrans %}
|
||||
If you didn't request this, you can safely ignore this email.
|
||||
|
||||
Thanks,
|
||||
The {{ instance }} Team
|
||||
{% endblocktrans %}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
{% load i18n %}{% blocktrans with url=url|safe %}Hello,
|
||||
|
||||
you requested a new password. Please go to the following page to reset your password:
|
||||
We received a request to reset the password for your {{ instance }} account. To choose a new password, follow the link below:
|
||||
|
||||
{{ url }}
|
||||
|
||||
Best regards,
|
||||
Your {{ instance }} team
|
||||
If you didn't request this, you can safely ignore this email — your password won't change.
|
||||
|
||||
Thanks,
|
||||
The {{ instance }} Team
|
||||
{% endblocktrans %}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
{% load i18n %}{% blocktrans with url=url|safe %}Hello,
|
||||
|
||||
you have been invited to a team on {{ instance }}, a platform to perform event
|
||||
ticket sales.
|
||||
You've been invited to join a team on {{ instance }}, an event ticket sales platform.
|
||||
|
||||
Organizer: {{ organizer }}
|
||||
Team: {{ team }}
|
||||
- Organizer: {{ organizer }}
|
||||
- Team: {{ team }}
|
||||
|
||||
To accept, follow the link below:
|
||||
|
||||
If you want to join that team, just click on the following link:
|
||||
{{ url }}
|
||||
|
||||
If you do not want to join, you can safely ignore or delete this email.
|
||||
If you don't want to join, you can safely ignore this email.
|
||||
|
||||
Best regards,
|
||||
|
||||
Your {{ instance }} team
|
||||
Thanks,
|
||||
The {{ instance }} Team
|
||||
{% endblocktrans %}
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
{% load i18n %}{% blocktrans with url=url|safe os=source.os_type agent=source.agent_type %}Hello,
|
||||
{% load i18n %}{% blocktrans %}Hello,
|
||||
|
||||
a login to your {{ instance }} account from an unusual or new location was detected. The login was performed using {{ agent }} on {{ os }} from {{ country }}.
|
||||
We noticed a new sign-in to your {{ instance }} account:
|
||||
{% endblocktrans %}
|
||||
- {% trans "Time" %}: {{ when }}
|
||||
- {% trans "Browser" %}: {{ agent }}
|
||||
- {% trans "Operating system" %}: {{ os }}
|
||||
{% if device %}- {% trans "Device" %}: {{ device }}
|
||||
{% endif %}{% if country %}- {% trans "Country" %}: {{ country }}
|
||||
{% endif %}
|
||||
{% blocktrans with url=url|safe %}If it was you, no action is needed.
|
||||
|
||||
If this was you, you can safely ignore this email.
|
||||
|
||||
If this was not you, we recommend that you change your password in your account settings:
|
||||
If you don't recognise this sign-in, please change your password immediately:
|
||||
|
||||
{{ url }}
|
||||
|
||||
Best regards,
|
||||
Your {{ instance }} team
|
||||
Thanks,
|
||||
The {{ instance }} Team
|
||||
{% endblocktrans %}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
{% load i18n %}{% blocktrans with url=url|safe messages=messages|safe %}Hello,
|
||||
|
||||
this is to inform you that the account information of your {{ instance }} account has been
|
||||
changed. In particular, the following changes have been performed:
|
||||
The following changes were made to your {{ instance }} account:
|
||||
|
||||
{{ messages }}
|
||||
|
||||
If this change was not performed by you, please contact us immediately.
|
||||
If you didn't make these changes, please contact the {{ instance }} support team immediately.
|
||||
|
||||
You can review and change your account settings here:
|
||||
You can review your account settings here:
|
||||
|
||||
{{ url }}
|
||||
|
||||
Best regards,
|
||||
Your {{ instance }} team
|
||||
Thanks,
|
||||
The {{ instance }} Team
|
||||
{% endblocktrans %}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</p>
|
||||
<ul>
|
||||
{% for issue in issues %}
|
||||
<li>{{ issue }}</li>
|
||||
<li>{{ issue|safe }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
@@ -42,7 +42,7 @@
|
||||
</p>
|
||||
<ul>
|
||||
{% for issue in issues %}
|
||||
<li>{{ issue }}</li>
|
||||
<li>{{ issue|safe }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -193,6 +193,7 @@
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
{% bootstrap_field form.contact_mail layout="control" %}
|
||||
{% bootstrap_field form.contact_url layout="control" %}
|
||||
{% bootstrap_field form.imprint_url layout="control" %}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
{% bootstrap_field form.date_admission layout="control" %}
|
||||
{% bootstrap_field form.currency layout="control" %}
|
||||
{% bootstrap_field sform.contact_mail layout="control" %}
|
||||
{% bootstrap_field sform.contact_url layout="control" %}
|
||||
{% bootstrap_field sform.imprint_url layout="control" %}
|
||||
{% bootstrap_field form.is_public layout="control" %}
|
||||
{% bootstrap_field form.all_sales_channels layout="control" %}
|
||||
|
||||
@@ -284,6 +284,14 @@
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
{% bootstrap_field position.form.operation_secret layout='inline' %}
|
||||
{% if position.issued_gift_cards.exists %}
|
||||
<div class="alert alert-info">
|
||||
{% blocktrans trimmed %}
|
||||
Ticket secrets of order positions that have been used to issue a gift card can not
|
||||
be changed. Only the link will be changed in this case.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
{% endif %}
|
||||
{% bootstrap_field sform.imprint_url layout="control" %}
|
||||
{% bootstrap_field sform.contact_mail layout="control" %}
|
||||
{% bootstrap_field sform.contact_url layout="control" %}
|
||||
{% bootstrap_field sform.organizer_info_text layout="control" %}
|
||||
{% bootstrap_field sform.event_team_provisioning layout="control" %}
|
||||
{% if sform.allowed_restricted_plugins %}
|
||||
|
||||
@@ -370,6 +370,7 @@ urlpatterns = [
|
||||
re_path(r'^discounts/add$', discounts.DiscountCreate.as_view(), name='event.items.discounts.add'),
|
||||
re_path(r'^vouchers/$', vouchers.VoucherList.as_view(), name='event.vouchers'),
|
||||
re_path(r'^vouchers/tags/$', vouchers.VoucherTags.as_view(), name='event.vouchers.tags'),
|
||||
re_path(r'^vouchers/tags/typeahead$', typeahead.voucher_tag_typeahead, name='event.vouchers.tags.typeahead'),
|
||||
re_path(r'^vouchers/rng$', vouchers.VoucherRNG.as_view(), name='event.vouchers.rng'),
|
||||
re_path(r'^vouchers/item_select$', typeahead.itemvarquota_select2, name='event.vouchers.itemselect2'),
|
||||
re_path(r'^vouchers/(?P<voucher>\d+)/$', vouchers.VoucherUpdate.as_view(), name='event.voucher'),
|
||||
|
||||
@@ -28,7 +28,7 @@ from django.core.mail import get_connection
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import gettext, gettext_lazy as _
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from pretix.base import email
|
||||
@@ -216,13 +216,14 @@ class MailSettingsSetupView(TemplateView):
|
||||
messages.error(request, _('The verification code was incorrect, please try again.'))
|
||||
else:
|
||||
self.request.session[session_key] = get_random_string(length=6, allowed_chars='1234567890')
|
||||
sender_address = self.simple_form.cleaned_data.get('mail_from')
|
||||
mail(
|
||||
self.simple_form.cleaned_data.get('mail_from'),
|
||||
_('Sender address verification'),
|
||||
sender_address,
|
||||
gettext('Confirm %(address)s as a sender address') % {'address': sender_address},
|
||||
'pretixcontrol/email/email_setup.txt',
|
||||
{
|
||||
'code': self.request.session[session_key],
|
||||
'address': self.simple_form.cleaned_data.get('mail_from'),
|
||||
'address': sender_address,
|
||||
'instance': settings.PRETIX_INSTANCE_NAME,
|
||||
},
|
||||
None,
|
||||
|
||||
@@ -64,7 +64,7 @@ from django.urls import reverse
|
||||
from django.utils import formats
|
||||
from django.utils.formats import date_format, get_format
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.html import conditional_escape, escape
|
||||
from django.utils.html import conditional_escape, escape, format_html
|
||||
from django.utils.http import url_has_allowed_host_and_scheme
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.timezone import make_aware, now
|
||||
@@ -79,7 +79,7 @@ from pretix.base.email import get_email_context
|
||||
from pretix.base.exporter import MultiSheetListExporter
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import (
|
||||
CachedFile, CachedTicket, Checkin, Invoice, InvoiceAddress, Item,
|
||||
CachedFile, CachedTicket, Checkin, GiftCard, Invoice, InvoiceAddress, Item,
|
||||
ItemVariation, LogEntry, Order, QuestionAnswer, Quota,
|
||||
ScheduledEventExport, generate_secret,
|
||||
)
|
||||
@@ -1994,7 +1994,7 @@ class OrderChange(OrderView):
|
||||
positions = list(self.order.positions.select_related(
|
||||
'item', 'item__tax_rule', 'used_membership', 'used_membership__membership_type', 'tax_rule',
|
||||
'seat', 'subevent',
|
||||
).prefetch_related('granted_memberships'))
|
||||
).prefetch_related('granted_memberships', 'issued_gift_cards'))
|
||||
for p in positions:
|
||||
p.form = OrderPositionChangeForm(prefix='op-{}'.format(p.pk), instance=p, items=self.items,
|
||||
initial={'seat': p.seat.seat_guid if p.seat else None},
|
||||
@@ -2261,6 +2261,12 @@ class OrderContactChange(OrderView):
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data()
|
||||
ctx['form'] = self.form
|
||||
if self.order.all_positions.filter(Exists(GiftCard.objects.filter(issued_in=OuterRef('pk')))).exists():
|
||||
self.form.fields['regenerate_secrets'].help_text = format_html(
|
||||
'{}<br><br><strong><span class="fa fa-warning"></span> {}</strong>',
|
||||
self.form.fields['regenerate_secrets'].help_text,
|
||||
_("Ticket secrets of order positions that have been used to issue a gift card can not be changed. Only the link will be changed in this case."),
|
||||
)
|
||||
return ctx
|
||||
|
||||
@cached_property
|
||||
|
||||
@@ -1032,7 +1032,9 @@ class TeamMemberView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
|
||||
def _send_invite(self, instance):
|
||||
mail(
|
||||
instance.email,
|
||||
_('Account invitation'),
|
||||
gettext('You\'ve been invited to join %(organizer)s') % {
|
||||
'organizer': self.request.organizer.name,
|
||||
},
|
||||
'pretixcontrol/email/invitation.txt',
|
||||
{
|
||||
'instance': settings.PRETIX_INSTANCE_NAME,
|
||||
|
||||
@@ -975,6 +975,21 @@ def subevent_meta_values(request, organizer, event):
|
||||
})
|
||||
|
||||
|
||||
@event_permission_required('event.vouchers:read')
|
||||
def voucher_tag_typeahead(request, **kwargs):
|
||||
q = request.GET.get('q', '')
|
||||
tags = request.event.vouchers.filter(
|
||||
tag__isnull=False,
|
||||
waitinglistentries__isnull=True,
|
||||
).filter(
|
||||
tag__icontains=q,
|
||||
).values_list('tag', flat=True).distinct().order_by('tag')[:10]
|
||||
|
||||
return JsonResponse({
|
||||
'results': [{'name': t} for t in tags]
|
||||
})
|
||||
|
||||
|
||||
def item_meta_values(request, organizer, event):
|
||||
q = request.GET.get('q')
|
||||
propname = request.GET.get('property')
|
||||
|
||||
@@ -29,36 +29,61 @@ class EnvOrParserConfig:
|
||||
self.cp = configparser
|
||||
|
||||
def _envkey(self, section, option):
|
||||
section = re.sub('[^a-zA-Z0-9]', '_', section.upper())
|
||||
option = re.sub('[^a-zA-Z0-9]', '_', option.upper())
|
||||
return f'PRETIX_{section}_{option}'
|
||||
section = re.sub("[^a-zA-Z0-9]", "_", section.upper())
|
||||
option = re.sub("[^a-zA-Z0-9]", "_", option.upper())
|
||||
return f"PRETIX_{section}_{option}"
|
||||
|
||||
def _file_envkey(self, section, option):
|
||||
section = re.sub("[^a-zA-Z0-9]", "_", section.upper())
|
||||
option = re.sub("[^a-zA-Z0-9]", "_", option.upper())
|
||||
return f"FILE__PRETIX_{section}_{option}"
|
||||
|
||||
def get(self, section, option, *, raw=False, vars=None, fallback=_UNSET):
|
||||
if self._file_envkey(section, option) in os.environ:
|
||||
with open(os.environ[self._file_envkey(section, option)], "r") as f:
|
||||
return f.read().strip()
|
||||
if self._envkey(section, option) in os.environ:
|
||||
return os.environ[self._envkey(section, option)]
|
||||
return self.cp.get(section, option, raw=raw, vars=vars, fallback=fallback)
|
||||
|
||||
def getint(self, section, option, *, raw=False, vars=None, fallback=_UNSET):
|
||||
if self._file_envkey(section, option) in os.environ:
|
||||
with open(os.environ[self._file_envkey(section, option)], "r") as f:
|
||||
return int(f.read().strip())
|
||||
if self._envkey(section, option) in os.environ:
|
||||
return int(os.environ[self._envkey(section, option)])
|
||||
return self.cp.getint(section, option, raw=raw, vars=vars, fallback=fallback)
|
||||
|
||||
def getfloat(self, section, option, *, raw=False, vars=None, fallback=_UNSET):
|
||||
if self._file_envkey(section, option) in os.environ:
|
||||
with open(os.environ[self._file_envkey(section, option)], "r") as f:
|
||||
return float(f.read().strip())
|
||||
if self._envkey(section, option) in os.environ:
|
||||
return float(os.environ[self._envkey(section, option)])
|
||||
return self.cp.getfloat(section, option, raw=raw, vars=vars, fallback=fallback)
|
||||
|
||||
def getboolean(self, section, option, *, raw=False, vars=None, fallback=_UNSET):
|
||||
if self._file_envkey(section, option) in os.environ:
|
||||
with open(os.environ[self._file_envkey(section, option)], "r") as f:
|
||||
return self.cp._convert_to_boolean(f.read().strip())
|
||||
if self._envkey(section, option) in os.environ:
|
||||
return self.cp._convert_to_boolean(os.environ[self._envkey(section, option)])
|
||||
return self.cp.getboolean(section, option, raw=raw, vars=vars, fallback=fallback)
|
||||
return self.cp._convert_to_boolean(
|
||||
os.environ[self._envkey(section, option)]
|
||||
)
|
||||
return self.cp.getboolean(
|
||||
section, option, raw=raw, vars=vars, fallback=fallback
|
||||
)
|
||||
|
||||
def has_section(self, section):
|
||||
if any(k.startswith(self._envkey(section, '')) for k in os.environ):
|
||||
if any(k.startswith(self._file_envkey(section, "")) for k in os.environ):
|
||||
return True
|
||||
if any(k.startswith(self._envkey(section, "")) for k in os.environ):
|
||||
return True
|
||||
return self.cp.has_section(section)
|
||||
|
||||
def has_option(self, section, option):
|
||||
if self._file_envkey(section, option) in os.environ:
|
||||
return True
|
||||
if self._envkey(section, option) in os.environ:
|
||||
return True
|
||||
return self.cp.has_option(section, option)
|
||||
|
||||
@@ -25,6 +25,19 @@ import text_unidecode
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
EPC_QR_ALLOWED_CHARS = set(
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789/-?:().,'+ "
|
||||
)
|
||||
|
||||
|
||||
def epc_qr_field(value):
|
||||
return ''.join(
|
||||
char for char in text_unidecode.unidecode(str(value or ''))
|
||||
if char in EPC_QR_ALLOWED_CHARS
|
||||
)
|
||||
|
||||
|
||||
def dotdecimal(value):
|
||||
return str(value).replace(",", ".")
|
||||
@@ -77,7 +90,7 @@ def euro_epc_qr(
|
||||
return {
|
||||
"id": "girocode",
|
||||
"label": "EPC-QR",
|
||||
"qr_data": "\n".join(text_unidecode.unidecode(str(d or '')) for d in [
|
||||
"qr_data": "\n".join(epc_qr_field(d) for d in [
|
||||
"BCD", # Service Tag: ‘BCD’
|
||||
"002", # Version: V2
|
||||
"2", # Character set: ISO 8859-1
|
||||
|
||||
@@ -27,6 +27,7 @@ from django.conf import settings
|
||||
from django.contrib.auth import login as auth_login
|
||||
from django.contrib.gis import geoip2
|
||||
from django.core.cache import cache
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_countries.fields import Country
|
||||
@@ -169,11 +170,15 @@ def handle_login_source(user, request):
|
||||
with language(user.locale):
|
||||
mail(
|
||||
user.email,
|
||||
_('Login from new source detected'),
|
||||
_('New sign-in to your account'),
|
||||
'pretixcontrol/email/login_notice.txt',
|
||||
{
|
||||
'source': src,
|
||||
'country': Country(str(country)).name if country else _('Unknown country'),
|
||||
'when': date_format(src.last_seen, 'DATETIME_FORMAT'),
|
||||
'agent': src.agent_type,
|
||||
'os': src.os_type,
|
||||
# ua-parser returns "Other" for unidentified desktop devices.
|
||||
'device': src.device_type if src.device_type and src.device_type != 'Other' else None,
|
||||
'country': Country(str(country)).name if country else None,
|
||||
'instance': settings.PRETIX_INSTANCE_NAME,
|
||||
'url': mainreverse_absolute('control:user.settings')
|
||||
},
|
||||
|
||||
+8707
-9999
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+8841
-10040
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+8707
-9999
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+8832
-10049
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+8850
-10034
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+8727
-10005
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+8871
-10037
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+8966
-10041
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+8707
-9999
File diff suppressed because it is too large
Load Diff
+595
-216
File diff suppressed because it is too large
Load Diff
+8845
-10048
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+8707
-9999
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+8968
-10041
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+8745
-10018
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+8707
-9999
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+8812
-10029
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+8809
-10023
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+8729
-10006
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+8969
-10043
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+8829
-10044
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+8899
-10040
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+8811
-10023
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+8775
-10027
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+8857
-10039
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+8840
-10041
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+8961
-10036
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+8848
-10043
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+8766
-10020
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user