mirror of
https://github.com/pretix/pretix.git
synced 2026-08-28 13:34:40 +00:00
Merge branch 'master' of https://github.com/pretix/pretix
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
file=/tmp/supervisor.sock
|
file=/tmp/supervisor.sock
|
||||||
|
|
||||||
[supervisord]
|
[supervisord]
|
||||||
|
environment = AUTOMIGRATE="skip"
|
||||||
logfile=/dev/stdout
|
logfile=/dev/stdout
|
||||||
logfile_maxbytes=0
|
logfile_maxbytes=0
|
||||||
loglevel=info
|
loglevel=info
|
||||||
|
|||||||
@@ -240,6 +240,9 @@ The following snippet is an example on how to configure a nginx proxy for pretix
|
|||||||
listen 80 default_server;
|
listen 80 default_server;
|
||||||
listen [::]:80 ipv6only=on default_server;
|
listen [::]:80 ipv6only=on default_server;
|
||||||
server_name pretix.mydomain.com;
|
server_name pretix.mydomain.com;
|
||||||
|
location / {
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
server {
|
server {
|
||||||
listen 443 default_server;
|
listen 443 default_server;
|
||||||
|
|||||||
@@ -225,6 +225,9 @@ The following snippet is an example on how to configure a nginx proxy for pretix
|
|||||||
listen 80 default_server;
|
listen 80 default_server;
|
||||||
listen [::]:80 ipv6only=on default_server;
|
listen [::]:80 ipv6only=on default_server;
|
||||||
server_name pretix.mydomain.com;
|
server_name pretix.mydomain.com;
|
||||||
|
location / {
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
server {
|
server {
|
||||||
listen 443 default_server;
|
listen 443 default_server;
|
||||||
|
|||||||
@@ -26,10 +26,16 @@ date_joined datetime Date and time o
|
|||||||
locale string Preferred language of the customer
|
locale string Preferred language of the customer
|
||||||
last_modified datetime Date and time of modification of the record
|
last_modified datetime Date and time of modification of the record
|
||||||
notes string Internal notes and comments (or ``null``)
|
notes string Internal notes and comments (or ``null``)
|
||||||
|
password string Can only be set during creation of a new customer, will
|
||||||
|
not be included in any responses.
|
||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
.. versionadded:: 4.0
|
.. versionadded:: 4.0
|
||||||
|
|
||||||
|
.. versionchanged:: 4.3
|
||||||
|
|
||||||
|
Passwords can now be set through the API during customer creation.
|
||||||
|
|
||||||
Endpoints
|
Endpoints
|
||||||
---------
|
---------
|
||||||
|
|
||||||
@@ -146,6 +152,7 @@ Endpoints
|
|||||||
|
|
||||||
{
|
{
|
||||||
"email": "test@example.org",
|
"email": "test@example.org",
|
||||||
|
"password": "verysecret",
|
||||||
"send_email": true
|
"send_email": true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -428,7 +428,7 @@ List of all orders
|
|||||||
``last_modified``, and ``status``. Default: ``datetime``
|
``last_modified``, and ``status``. Default: ``datetime``
|
||||||
:query string code: Only return orders that match the given order code
|
:query string code: Only return orders that match the given order code
|
||||||
:query string status: Only return orders in the given order status (see above)
|
:query string status: Only return orders in the given order status (see above)
|
||||||
:query string search: Only return orders matching a given search query
|
:query string search: Only return orders matching a given search query (matching for names, email addresses, and company names)
|
||||||
:query integer item: Only return orders with a position that contains this item ID. *Warning:* Result will also include orders if they contain mixed items, and it will even return orders where the item is only contained in a canceled position.
|
:query integer item: Only return orders with a position that contains this item ID. *Warning:* Result will also include orders if they contain mixed items, and it will even return orders where the item is only contained in a canceled position.
|
||||||
:query integer variation: Only return orders with a position that contains this variation ID. *Warning:* Result will also include orders if they contain mixed items and variations, and it will even return orders where the variation is only contained in a canceled position.
|
:query integer variation: Only return orders with a position that contains this variation ID. *Warning:* Result will also include orders if they contain mixed items and variations, and it will even return orders where the variation is only contained in a canceled position.
|
||||||
:query boolean testmode: Only return orders with ``testmode`` set to ``true`` or ``false``
|
:query boolean testmode: Only return orders with ``testmode`` set to ``true`` or ``false``
|
||||||
@@ -853,7 +853,7 @@ Creating orders
|
|||||||
|
|
||||||
You can supply the following fields of the resource:
|
You can supply the following fields of the resource:
|
||||||
|
|
||||||
* ``code`` (optional)
|
* ``code`` (optional) – Only ``A-Z`` and ``0-9``, but without ``O`` and ``1``.
|
||||||
* ``status`` (optional) – Defaults to pending for non-free orders and paid for free orders. You can only set this to
|
* ``status`` (optional) – Defaults to pending for non-free orders and paid for free orders. You can only set this to
|
||||||
``"n"`` for pending or ``"p"`` for paid. We will create a payment object for this order either in state ``created``
|
``"n"`` for pending or ``"p"`` for paid. We will create a payment object for this order either in state ``created``
|
||||||
or in state ``confirmed``, depending on this value. If you create a paid order, the ``order_paid`` signal will
|
or in state ``confirmed``, depending on this value. If you create a paid order, the ``order_paid`` signal will
|
||||||
|
|||||||
@@ -36,10 +36,16 @@ The following values for ``action_types`` are valid with pretix core:
|
|||||||
* ``pretix.event.order.canceled``
|
* ``pretix.event.order.canceled``
|
||||||
* ``pretix.event.order.reactivated``
|
* ``pretix.event.order.reactivated``
|
||||||
* ``pretix.event.order.expired``
|
* ``pretix.event.order.expired``
|
||||||
|
* ``pretix.event.order.expirychanged``
|
||||||
* ``pretix.event.order.modified``
|
* ``pretix.event.order.modified``
|
||||||
* ``pretix.event.order.contact.changed``
|
* ``pretix.event.order.contact.changed``
|
||||||
* ``pretix.event.order.changed.*``
|
* ``pretix.event.order.changed.*``
|
||||||
|
* ``pretix.event.order.refund.created``
|
||||||
* ``pretix.event.order.refund.created.externally``
|
* ``pretix.event.order.refund.created.externally``
|
||||||
|
* ``pretix.event.order.refund.requested``
|
||||||
|
* ``pretix.event.order.refund.done``
|
||||||
|
* ``pretix.event.order.refund.canceled``
|
||||||
|
* ``pretix.event.order.refund.failed``
|
||||||
* ``pretix.event.order.approved``
|
* ``pretix.event.order.approved``
|
||||||
* ``pretix.event.order.denied``
|
* ``pretix.event.order.denied``
|
||||||
* ``pretix.event.checkin``
|
* ``pretix.event.checkin``
|
||||||
@@ -50,6 +56,10 @@ The following values for ``action_types`` are valid with pretix core:
|
|||||||
* ``pretix.subevent.added``
|
* ``pretix.subevent.added``
|
||||||
* ``pretix.subevent.changed``
|
* ``pretix.subevent.changed``
|
||||||
* ``pretix.subevent.deleted``
|
* ``pretix.subevent.deleted``
|
||||||
|
* ``pretix.event.live.activated``
|
||||||
|
* ``pretix.event.live.deactivated``
|
||||||
|
* ``pretix.event.testmode.activated``
|
||||||
|
* ``pretix.event.testmode.deactivated``
|
||||||
|
|
||||||
Installed plugins might register more valid values.
|
Installed plugins might register more valid values.
|
||||||
|
|
||||||
|
|||||||
@@ -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__ = "4.12.0.dev1"
|
__version__ = "4.13.0.dev0"
|
||||||
|
|||||||
@@ -54,7 +54,10 @@ from pretix.base.models.items import SubEventItem, SubEventItemVariation
|
|||||||
from pretix.base.services.seating import (
|
from pretix.base.services.seating import (
|
||||||
SeatProtected, generate_seats, validate_plan_change,
|
SeatProtected, generate_seats, validate_plan_change,
|
||||||
)
|
)
|
||||||
from pretix.base.settings import LazyI18nStringList, validate_event_settings
|
from pretix.base.settings import (
|
||||||
|
PERSON_NAME_SALUTATIONS, PERSON_NAME_SCHEMES, PERSON_NAME_TITLE_GROUPS,
|
||||||
|
LazyI18nStringList, validate_event_settings,
|
||||||
|
)
|
||||||
from pretix.base.signals import api_event_settings_fields
|
from pretix.base.signals import api_event_settings_fields
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -777,6 +780,7 @@ class EventSettingsSerializer(SettingsSerializer):
|
|||||||
'logo_image_large',
|
'logo_image_large',
|
||||||
'logo_show_title',
|
'logo_show_title',
|
||||||
'og_image',
|
'og_image',
|
||||||
|
'name_scheme',
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@@ -842,4 +846,25 @@ class DeviceEventSettingsSerializer(EventSettingsSerializer):
|
|||||||
'invoice_address_from_country',
|
'invoice_address_from_country',
|
||||||
'invoice_address_from_tax_id',
|
'invoice_address_from_tax_id',
|
||||||
'invoice_address_from_vat_id',
|
'invoice_address_from_vat_id',
|
||||||
|
'name_scheme',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.fields['_name_scheme_fields'] = serializers.JSONField(
|
||||||
|
read_only=True,
|
||||||
|
default=[{"key": k, "label": str(v), "weight": w} for k, v, w, *__ in PERSON_NAME_SCHEMES.get(self.event.settings.name_scheme)['fields']]
|
||||||
|
)
|
||||||
|
self.fields['_name_scheme_salutations'] = serializers.JSONField(
|
||||||
|
read_only=True,
|
||||||
|
default=[{"key": k, "label": str(v)} for k, v in PERSON_NAME_SALUTATIONS]
|
||||||
|
)
|
||||||
|
self.fields['_name_scheme_titles'] = serializers.JSONField(
|
||||||
|
read_only=True,
|
||||||
|
default=(
|
||||||
|
[{"key": k, "label": k}
|
||||||
|
for k in PERSON_NAME_TITLE_GROUPS.get(self.event.settings.name_scheme_titles)[1]]
|
||||||
|
if self.event.settings.name_scheme_titles
|
||||||
|
else []
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|||||||
@@ -77,10 +77,11 @@ class CustomerSerializer(I18nAwareModelSerializer):
|
|||||||
|
|
||||||
class CustomerCreateSerializer(CustomerSerializer):
|
class CustomerCreateSerializer(CustomerSerializer):
|
||||||
send_email = serializers.BooleanField(default=False, required=False, allow_null=True)
|
send_email = serializers.BooleanField(default=False, required=False, allow_null=True)
|
||||||
|
password = serializers.CharField(write_only=True, required=False, allow_null=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Customer
|
model = Customer
|
||||||
fields = CustomerSerializer.Meta.fields + ('send_email',)
|
fields = CustomerSerializer.Meta.fields + ('send_email', 'password')
|
||||||
|
|
||||||
|
|
||||||
class MembershipTypeSerializer(I18nAwareModelSerializer):
|
class MembershipTypeSerializer(I18nAwareModelSerializer):
|
||||||
|
|||||||
@@ -1322,6 +1322,7 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
|
|||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
serializer.save()
|
serializer.save()
|
||||||
new_data = serializer.data
|
new_data = serializer.data
|
||||||
|
instance.order.create_transactions()
|
||||||
|
|
||||||
if old_data != new_data:
|
if old_data != new_data:
|
||||||
log_data = self.request.data
|
log_data = self.request.data
|
||||||
|
|||||||
@@ -515,8 +515,8 @@ class CustomerViewSet(viewsets.ModelViewSet):
|
|||||||
raise MethodNotAllowed("Customers cannot be deleted.")
|
raise MethodNotAllowed("Customers cannot be deleted.")
|
||||||
|
|
||||||
@transaction.atomic()
|
@transaction.atomic()
|
||||||
def perform_create(self, serializer, send_email=False):
|
def perform_create(self, serializer, send_email=False, password=None):
|
||||||
customer = serializer.save(organizer=self.request.organizer, password=make_password(None))
|
customer = serializer.save(organizer=self.request.organizer, password=make_password(password))
|
||||||
serializer.instance.log_action(
|
serializer.instance.log_action(
|
||||||
'pretix.customer.created',
|
'pretix.customer.created',
|
||||||
user=self.request.user,
|
user=self.request.user,
|
||||||
@@ -530,7 +530,7 @@ class CustomerViewSet(viewsets.ModelViewSet):
|
|||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
serializer = CustomerCreateSerializer(data=request.data, context=self.get_serializer_context())
|
serializer = CustomerCreateSerializer(data=request.data, context=self.get_serializer_context())
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
self.perform_create(serializer, send_email=serializer.validated_data.pop('send_email', False))
|
self.perform_create(serializer, send_email=serializer.validated_data.pop('send_email', False), password=serializer.validated_data.pop('password', None))
|
||||||
headers = self.get_success_headers(serializer.data)
|
headers = self.get_success_headers(serializer.data)
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
||||||
|
|
||||||
|
|||||||
@@ -219,6 +219,10 @@ def register_default_webhook_events(sender, **kwargs):
|
|||||||
'pretix.event.order.expired',
|
'pretix.event.order.expired',
|
||||||
_('Order expired'),
|
_('Order expired'),
|
||||||
),
|
),
|
||||||
|
ParametrizedOrderWebhookEvent(
|
||||||
|
'pretix.event.order.expirychanged',
|
||||||
|
_('Order expiry date changed'),
|
||||||
|
),
|
||||||
ParametrizedOrderWebhookEvent(
|
ParametrizedOrderWebhookEvent(
|
||||||
'pretix.event.order.modified',
|
'pretix.event.order.modified',
|
||||||
_('Order information changed'),
|
_('Order information changed'),
|
||||||
@@ -231,10 +235,30 @@ def register_default_webhook_events(sender, **kwargs):
|
|||||||
'pretix.event.order.changed.*',
|
'pretix.event.order.changed.*',
|
||||||
_('Order changed'),
|
_('Order changed'),
|
||||||
),
|
),
|
||||||
|
ParametrizedOrderWebhookEvent(
|
||||||
|
'pretix.event.order.refund.created',
|
||||||
|
_('Refund of payment created'),
|
||||||
|
),
|
||||||
ParametrizedOrderWebhookEvent(
|
ParametrizedOrderWebhookEvent(
|
||||||
'pretix.event.order.refund.created.externally',
|
'pretix.event.order.refund.created.externally',
|
||||||
_('External refund of payment'),
|
_('External refund of payment'),
|
||||||
),
|
),
|
||||||
|
ParametrizedOrderWebhookEvent(
|
||||||
|
'pretix.event.order.refund.requested',
|
||||||
|
_('Refund of payment requested by customer'),
|
||||||
|
),
|
||||||
|
ParametrizedOrderWebhookEvent(
|
||||||
|
'pretix.event.order.refund.done',
|
||||||
|
_('Refund of payment completed'),
|
||||||
|
),
|
||||||
|
ParametrizedOrderWebhookEvent(
|
||||||
|
'pretix.event.order.refund.canceled',
|
||||||
|
_('Refund of payment canceled'),
|
||||||
|
),
|
||||||
|
ParametrizedOrderWebhookEvent(
|
||||||
|
'pretix.event.order.refund.failed',
|
||||||
|
_('Refund of payment failed'),
|
||||||
|
),
|
||||||
ParametrizedOrderWebhookEvent(
|
ParametrizedOrderWebhookEvent(
|
||||||
'pretix.event.order.approved',
|
'pretix.event.order.approved',
|
||||||
_('Order approved'),
|
_('Order approved'),
|
||||||
@@ -275,6 +299,22 @@ def register_default_webhook_events(sender, **kwargs):
|
|||||||
'pretix.subevent.deleted',
|
'pretix.subevent.deleted',
|
||||||
pgettext_lazy('subevent', 'Event series date deleted'),
|
pgettext_lazy('subevent', 'Event series date deleted'),
|
||||||
),
|
),
|
||||||
|
ParametrizedEventWebhookEvent(
|
||||||
|
'pretix.event.live.activated',
|
||||||
|
_('Shop taken live'),
|
||||||
|
),
|
||||||
|
ParametrizedEventWebhookEvent(
|
||||||
|
'pretix.event.live.deactivated',
|
||||||
|
_('Shop taken offline'),
|
||||||
|
),
|
||||||
|
ParametrizedEventWebhookEvent(
|
||||||
|
'pretix.event.testmode.activated',
|
||||||
|
_('Testmode of shop has been activated'),
|
||||||
|
),
|
||||||
|
ParametrizedEventWebhookEvent(
|
||||||
|
'pretix.event.testmode.deactivated',
|
||||||
|
_('Testmode of shop has been deactivated'),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ from pretix.base.i18n import (
|
|||||||
LazyCurrencyNumber, LazyDate, LazyExpiresDate, LazyNumber,
|
LazyCurrencyNumber, LazyDate, LazyExpiresDate, LazyNumber,
|
||||||
)
|
)
|
||||||
from pretix.base.models import Event
|
from pretix.base.models import Event
|
||||||
|
from pretix.base.reldate import RelativeDateWrapper
|
||||||
from pretix.base.settings import PERSON_NAME_SCHEMES
|
from pretix.base.settings import PERSON_NAME_SCHEMES
|
||||||
from pretix.base.signals import (
|
from pretix.base.signals import (
|
||||||
register_html_mail_renderers, register_mail_placeholders,
|
register_html_mail_renderers, register_mail_placeholders,
|
||||||
@@ -469,6 +470,19 @@ def base_placeholders(sender, **kwargs):
|
|||||||
}
|
}
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
SimpleFunctionalMailTextPlaceholder(
|
||||||
|
'order_modification_deadline_date_and_time', ['order', 'event'],
|
||||||
|
lambda order, event:
|
||||||
|
date_format(order.modify_deadline.astimezone(event.timezone), 'SHORT_DATETIME_FORMAT')
|
||||||
|
if order.modify_deadline
|
||||||
|
else '',
|
||||||
|
lambda event: date_format(
|
||||||
|
event.settings.get(
|
||||||
|
'last_order_modification_date', as_type=RelativeDateWrapper
|
||||||
|
).datetime(event).astimezone(event.timezone),
|
||||||
|
'SHORT_DATETIME_FORMAT'
|
||||||
|
) if event.settings.get('last_order_modification_date') else '',
|
||||||
|
),
|
||||||
SimpleFunctionalMailTextPlaceholder(
|
SimpleFunctionalMailTextPlaceholder(
|
||||||
'event_location', ['event_or_subevent'], lambda event_or_subevent: str(event_or_subevent.location or ''),
|
'event_location', ['event_or_subevent'], lambda event_or_subevent: str(event_or_subevent.location or ''),
|
||||||
lambda event: str(event.location or ''),
|
lambda event: str(event.location or ''),
|
||||||
|
|||||||
@@ -610,7 +610,10 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
for k, label, w in name_scheme['fields']:
|
for k, label, w in name_scheme['fields']:
|
||||||
headers.append(_('Invoice address name') + ': ' + str(label))
|
headers.append(_('Invoice address name') + ': ' + str(label))
|
||||||
headers += [
|
headers += [
|
||||||
_('Address'), _('ZIP code'), _('City'), _('Country'), pgettext('address', 'State'), _('VAT ID'),
|
_('Invoice address street'), _('Invoice address ZIP code'), _('Invoice address city'),
|
||||||
|
_('Invoice address country'),
|
||||||
|
pgettext('address', 'Invoice address state'),
|
||||||
|
_('VAT ID'),
|
||||||
]
|
]
|
||||||
headers += [
|
headers += [
|
||||||
_('Sales channel'), _('Order locale'),
|
_('Sales channel'), _('Order locale'),
|
||||||
|
|||||||
@@ -746,6 +746,19 @@ class Order(LockModel, LoggedModel):
|
|||||||
length += 1
|
length += 1
|
||||||
iteration = 0
|
iteration = 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def modify_deadline(self):
|
||||||
|
modify_deadline = self.event.settings.get('last_order_modification_date', as_type=RelativeDateWrapper)
|
||||||
|
if self.event.has_subevents and modify_deadline:
|
||||||
|
dates = [
|
||||||
|
modify_deadline.datetime(se)
|
||||||
|
for se in self.event.subevents.filter(id__in=self.positions.values_list('subevent', flat=True))
|
||||||
|
]
|
||||||
|
return min(dates) if dates else None
|
||||||
|
elif modify_deadline:
|
||||||
|
return modify_deadline.datetime(self.event)
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def can_modify_answers(self) -> bool:
|
def can_modify_answers(self) -> bool:
|
||||||
"""
|
"""
|
||||||
@@ -758,16 +771,7 @@ class Order(LockModel, LoggedModel):
|
|||||||
if self.status not in (Order.STATUS_PENDING, Order.STATUS_PAID, Order.STATUS_EXPIRED):
|
if self.status not in (Order.STATUS_PENDING, Order.STATUS_PAID, Order.STATUS_EXPIRED):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
modify_deadline = self.event.settings.get('last_order_modification_date', as_type=RelativeDateWrapper)
|
modify_deadline = self.modify_deadline
|
||||||
if self.event.has_subevents and modify_deadline:
|
|
||||||
dates = [
|
|
||||||
modify_deadline.datetime(se)
|
|
||||||
for se in self.event.subevents.filter(id__in=self.positions.values_list('subevent', flat=True))
|
|
||||||
]
|
|
||||||
modify_deadline = min(dates) if dates else None
|
|
||||||
elif modify_deadline:
|
|
||||||
modify_deadline = modify_deadline.datetime(self.event)
|
|
||||||
|
|
||||||
if modify_deadline is not None and now() > modify_deadline:
|
if modify_deadline is not None and now() > modify_deadline:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ EU_CURRENCIES = {
|
|||||||
'RO': 'RON',
|
'RO': 'RON',
|
||||||
'SE': 'SEK'
|
'SE': 'SEK'
|
||||||
}
|
}
|
||||||
VAT_ID_COUNTRIES = EU_COUNTRIES | {'CH'}
|
VAT_ID_COUNTRIES = EU_COUNTRIES | {'CH', 'NO'}
|
||||||
|
|
||||||
|
|
||||||
def is_eu_country(cc):
|
def is_eu_country(cc):
|
||||||
|
|||||||
@@ -392,7 +392,7 @@ class InvoiceAddressCountry(ImportColumn):
|
|||||||
return list(countries)
|
return list(countries)
|
||||||
|
|
||||||
def clean(self, value, previous_values):
|
def clean(self, value, previous_values):
|
||||||
if value and not Country(value).numeric:
|
if value and not (Country(value).numeric or value in settings.COUNTRIES_OVERRIDE):
|
||||||
raise ValidationError(_("Please enter a valid country code."))
|
raise ValidationError(_("Please enter a valid country code."))
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@@ -538,7 +538,7 @@ class AttendeeCountry(ImportColumn):
|
|||||||
return list(countries)
|
return list(countries)
|
||||||
|
|
||||||
def clean(self, value, previous_values):
|
def clean(self, value, previous_values):
|
||||||
if value and not Country(value).numeric:
|
if value and not (Country(value).numeric or value in settings.COUNTRIES_OVERRIDE):
|
||||||
raise ValidationError(_("Please enter a valid country code."))
|
raise ValidationError(_("Please enter a valid country code."))
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|||||||
+11
-11
@@ -57,7 +57,7 @@ from django.utils.html import conditional_escape
|
|||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.utils.translation import gettext_lazy as _, pgettext
|
from django.utils.translation import gettext_lazy as _, pgettext
|
||||||
from i18nfield.strings import LazyI18nString
|
from i18nfield.strings import LazyI18nString
|
||||||
from PyPDF2 import PdfFileReader
|
from PyPDF2 import PdfReader
|
||||||
from pytz import timezone
|
from pytz import timezone
|
||||||
from reportlab.graphics import renderPDF
|
from reportlab.graphics import renderPDF
|
||||||
from reportlab.graphics.barcode.qr import QrCodeWidget
|
from reportlab.graphics.barcode.qr import QrCodeWidget
|
||||||
@@ -646,7 +646,7 @@ class Renderer:
|
|||||||
self.event = event
|
self.event = event
|
||||||
if self.background_file:
|
if self.background_file:
|
||||||
self.bg_bytes = self.background_file.read()
|
self.bg_bytes = self.background_file.read()
|
||||||
self.bg_pdf = PdfFileReader(BytesIO(self.bg_bytes), strict=False)
|
self.bg_pdf = PdfReader(BytesIO(self.bg_bytes), strict=False)
|
||||||
else:
|
else:
|
||||||
self.bg_bytes = None
|
self.bg_bytes = None
|
||||||
self.bg_pdf = None
|
self.bg_pdf = None
|
||||||
@@ -861,7 +861,7 @@ class Renderer:
|
|||||||
canvas.restoreState()
|
canvas.restoreState()
|
||||||
|
|
||||||
def draw_page(self, canvas: Canvas, order: Order, op: OrderPosition, show_page=True, only_page=None):
|
def draw_page(self, canvas: Canvas, order: Order, op: OrderPosition, show_page=True, only_page=None):
|
||||||
page_count = self.bg_pdf.getNumPages()
|
page_count = len(self.bg_pdf.pages)
|
||||||
|
|
||||||
if not only_page and not show_page:
|
if not only_page and not show_page:
|
||||||
raise ValueError("only_page=None and show_page=False cannot be combined")
|
raise ValueError("only_page=None and show_page=False cannot be combined")
|
||||||
@@ -881,7 +881,7 @@ class Renderer:
|
|||||||
elif o['type'] == "poweredby":
|
elif o['type'] == "poweredby":
|
||||||
self._draw_poweredby(canvas, op, o)
|
self._draw_poweredby(canvas, op, o)
|
||||||
if self.bg_pdf:
|
if self.bg_pdf:
|
||||||
canvas.setPageSize((self.bg_pdf.getPage(page).mediaBox[2], self.bg_pdf.getPage(page).mediaBox[3]))
|
canvas.setPageSize((self.bg_pdf.pages[0].mediabox[2], self.bg_pdf.pages[0].mediabox[3]))
|
||||||
if show_page:
|
if show_page:
|
||||||
canvas.showPage()
|
canvas.showPage()
|
||||||
|
|
||||||
@@ -905,17 +905,17 @@ class Renderer:
|
|||||||
with open(os.path.join(d, 'out.pdf'), 'rb') as f:
|
with open(os.path.join(d, 'out.pdf'), 'rb') as f:
|
||||||
return BytesIO(f.read())
|
return BytesIO(f.read())
|
||||||
else:
|
else:
|
||||||
from PyPDF2 import PdfFileReader, PdfFileWriter
|
from PyPDF2 import PdfReader, PdfWriter
|
||||||
buffer.seek(0)
|
buffer.seek(0)
|
||||||
new_pdf = PdfFileReader(buffer)
|
new_pdf = PdfReader(buffer)
|
||||||
output = PdfFileWriter()
|
output = PdfWriter()
|
||||||
|
|
||||||
for i, page in enumerate(new_pdf.pages):
|
for i, page in enumerate(new_pdf.pages):
|
||||||
bg_page = copy.copy(self.bg_pdf.getPage(i))
|
bg_page = copy.copy(self.bg_pdf.pages[i])
|
||||||
bg_page.mergePage(page)
|
bg_page.merge_page(page)
|
||||||
output.addPage(bg_page)
|
output.add_page(bg_page)
|
||||||
|
|
||||||
output.addMetadata({
|
output.add_metadata({
|
||||||
'/Title': str(title),
|
'/Title': str(title),
|
||||||
'/Creator': 'pretix',
|
'/Creator': 'pretix',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ from pretix.base.models import (
|
|||||||
from pretix.base.models.orders import Transaction
|
from pretix.base.models.orders import Transaction
|
||||||
from pretix.base.orderimport import get_all_columns
|
from pretix.base.orderimport import get_all_columns
|
||||||
from pretix.base.services.invoices import generate_invoice, invoice_qualified
|
from pretix.base.services.invoices import generate_invoice, invoice_qualified
|
||||||
|
from pretix.base.services.locking import NoLockManager
|
||||||
from pretix.base.services.tasks import ProfiledEventTask
|
from pretix.base.services.tasks import ProfiledEventTask
|
||||||
from pretix.base.signals import order_paid, order_placed
|
from pretix.base.signals import order_paid, order_placed
|
||||||
from pretix.celery_app import app
|
from pretix.celery_app import app
|
||||||
@@ -85,9 +86,9 @@ def setif(record, obj, attr, setting):
|
|||||||
|
|
||||||
@app.task(base=ProfiledEventTask, throws=(DataImportError,))
|
@app.task(base=ProfiledEventTask, throws=(DataImportError,))
|
||||||
def import_orders(event: Event, fileid: str, settings: dict, locale: str, user) -> None:
|
def import_orders(event: Event, fileid: str, settings: dict, locale: str, user) -> None:
|
||||||
# TODO: quotacheck?
|
|
||||||
cf = CachedFile.objects.get(id=fileid)
|
cf = CachedFile.objects.get(id=fileid)
|
||||||
user = User.objects.get(pk=user)
|
user = User.objects.get(pk=user)
|
||||||
|
seats_used = False
|
||||||
with language(locale, event.settings.region):
|
with language(locale, event.settings.region):
|
||||||
cols = get_all_columns(event)
|
cols = get_all_columns(event)
|
||||||
parsed = parse_csv(cf.file)
|
parsed = parse_csv(cf.file)
|
||||||
@@ -133,6 +134,8 @@ def import_orders(event: Event, fileid: str, settings: dict, locale: str, user)
|
|||||||
position = OrderPosition(positionid=len(order._positions) + 1)
|
position = OrderPosition(positionid=len(order._positions) + 1)
|
||||||
position.attendee_name_parts = {'_scheme': event.settings.name_scheme}
|
position.attendee_name_parts = {'_scheme': event.settings.name_scheme}
|
||||||
position.meta_info = {}
|
position.meta_info = {}
|
||||||
|
if position.seat is not None:
|
||||||
|
seats_used = True
|
||||||
order._positions.append(position)
|
order._positions.append(position)
|
||||||
position.assign_pseudonymization_id()
|
position.assign_pseudonymization_id()
|
||||||
|
|
||||||
@@ -144,9 +147,12 @@ def import_orders(event: Event, fileid: str, settings: dict, locale: str, user)
|
|||||||
_('Invalid data in row {row}: {message}').format(row=i, message=str(e))
|
_('Invalid data in row {row}: {message}').format(row=i, message=str(e))
|
||||||
)
|
)
|
||||||
|
|
||||||
# quota check?
|
# We don't support vouchers, quotas, or memberships here, so we only need to lock if seats
|
||||||
with event.lock():
|
# are in use
|
||||||
with transaction.atomic():
|
lockfn = event.lock if seats_used else NoLockManager
|
||||||
|
|
||||||
|
try:
|
||||||
|
with lockfn(), transaction.atomic():
|
||||||
save_transactions = []
|
save_transactions = []
|
||||||
for o in orders:
|
for o in orders:
|
||||||
o.total = sum([c.price for c in o._positions]) # currently no support for fees
|
o.total = sum([c.price for c in o._positions]) # currently no support for fees
|
||||||
@@ -204,4 +210,7 @@ def import_orders(event: Event, fileid: str, settings: dict, locale: str, user)
|
|||||||
) and not o.invoices.last()
|
) and not o.invoices.last()
|
||||||
if gen_invoice:
|
if gen_invoice:
|
||||||
generate_invoice(o, trigger_pdf=True)
|
generate_invoice(o, trigger_pdf=True)
|
||||||
|
except DataImportError:
|
||||||
|
raise ValidationError(_('We were not able to process your request completely as the server was too busy. '
|
||||||
|
'Please try again.'))
|
||||||
cf.delete()
|
cf.delete()
|
||||||
|
|||||||
+115
-27
@@ -22,9 +22,9 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from urllib.error import HTTPError
|
from xml.etree import ElementTree
|
||||||
|
|
||||||
import vat_moss.errors
|
import requests
|
||||||
import vat_moss.id
|
import vat_moss.id
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@@ -35,6 +35,16 @@ from zeep.exceptions import Fault
|
|||||||
from pretix.base.models.tax import cc_to_vat_prefix, is_eu_country
|
from pretix.base.models.tax import cc_to_vat_prefix, is_eu_country
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
error_messages = {
|
||||||
|
'unavailable': _(
|
||||||
|
'Your VAT ID could not be checked, as the VAT checking service of '
|
||||||
|
'your country is currently not available. We will therefore '
|
||||||
|
'need to charge VAT on your invoice. You can get the tax amount '
|
||||||
|
'back via the VAT reimbursement process.'
|
||||||
|
),
|
||||||
|
'invalid': _('This VAT ID is not valid. Please re-check your input.'),
|
||||||
|
'country_mismatch': _('Your VAT ID does not match the selected country.'),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class VATIDError(Exception):
|
class VATIDError(Exception):
|
||||||
@@ -50,33 +60,104 @@ class VATIDTemporaryError(VATIDError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _validate_vat_id_EU(vat_id, country_code):
|
def _validate_vat_id_NO(vat_id, country_code):
|
||||||
if vat_id[:2] != cc_to_vat_prefix(country_code):
|
# Inspired by vat_moss library
|
||||||
raise VATIDFinalError(_('Your VAT ID does not match the selected country.'))
|
vat_id = vat_moss.id.normalize(vat_id)
|
||||||
|
|
||||||
|
if not vat_id or len(vat_id) < 3 or not re.match('^\\d{9}MVA$', vat_id[2:]):
|
||||||
|
raise VATIDFinalError(error_messages['invalid'])
|
||||||
|
|
||||||
|
organization_number = vat_id[2:].replace('MVA', '')
|
||||||
|
validation_url = 'https://data.brreg.no/enhetsregisteret/api/enheter/%s' % organization_number
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = vat_moss.id.validate(vat_id)
|
response = requests.get(validation_url, timeout=10)
|
||||||
if result:
|
if response.status_code in (404, 400):
|
||||||
country_code, normalized_id, company_name = result
|
raise VATIDFinalError(error_messages['invalid'])
|
||||||
return normalized_id
|
|
||||||
except (vat_moss.errors.InvalidError, ValueError):
|
response.raise_for_status()
|
||||||
raise VATIDFinalError(_('This VAT ID is not valid. Please re-check your input.'))
|
|
||||||
except vat_moss.errors.WebServiceUnavailableError:
|
info = response.json()
|
||||||
|
# This should never happen, but keeping it incase the API is changed
|
||||||
|
if 'organisasjonsnummer' not in info or info['organisasjonsnummer'] != organization_number:
|
||||||
|
logger.warning(
|
||||||
|
'VAT ID checking failed for Norway due to missing or mismatching organisasjonsnummer in repsonse'
|
||||||
|
)
|
||||||
|
raise VATIDFinalError(error_messages['invalid'])
|
||||||
|
except requests.RequestException:
|
||||||
logger.exception('VAT ID checking failed for country {}'.format(country_code))
|
logger.exception('VAT ID checking failed for country {}'.format(country_code))
|
||||||
raise VATIDTemporaryError(_(
|
raise VATIDTemporaryError(error_messages['unavailable'])
|
||||||
'Your VAT ID could not be checked, as the VAT checking service of '
|
else:
|
||||||
'your country is currently not available. We will therefore '
|
return vat_id
|
||||||
'need to charge VAT on your invoice. You can get the tax amount '
|
|
||||||
'back via the VAT reimbursement process.'
|
|
||||||
))
|
def _validate_vat_id_EU(vat_id, country_code):
|
||||||
except (vat_moss.errors.WebServiceError, HTTPError):
|
# Inspired by vat_moss library
|
||||||
|
try:
|
||||||
|
vat_id = vat_moss.id.normalize(vat_id)
|
||||||
|
except ValueError:
|
||||||
|
raise VATIDFinalError(error_messages['invalid'])
|
||||||
|
|
||||||
|
if not vat_id or len(vat_id) < 3:
|
||||||
|
raise VATIDFinalError(error_messages['invalid'])
|
||||||
|
|
||||||
|
number = vat_id[2:]
|
||||||
|
|
||||||
|
if vat_id[:2] != cc_to_vat_prefix(country_code):
|
||||||
|
raise VATIDFinalError(error_messages['country_mismatch'])
|
||||||
|
|
||||||
|
if not re.match(vat_moss.id.ID_PATTERNS[cc_to_vat_prefix(country_code)]['regex'], number):
|
||||||
|
raise VATIDFinalError(error_messages['invalid'])
|
||||||
|
|
||||||
|
payload = """
|
||||||
|
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:ec.europa.eu:taxud:vies:services:checkVat:types">
|
||||||
|
<soapenv:Header/>
|
||||||
|
<soapenv:Body>
|
||||||
|
<urn:checkVat>
|
||||||
|
<urn:countryCode>%s</urn:countryCode>
|
||||||
|
<urn:vatNumber>%s</urn:vatNumber>
|
||||||
|
</urn:checkVat>
|
||||||
|
</soapenv:Body>
|
||||||
|
</soapenv:Envelope>
|
||||||
|
""".strip() % (country_code, number)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(
|
||||||
|
'https://ec.europa.eu/taxation_customs/vies/services/checkVatService',
|
||||||
|
data=payload,
|
||||||
|
timeout=10,
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
return_xml = response.text
|
||||||
|
|
||||||
|
try:
|
||||||
|
envelope = ElementTree.fromstring(return_xml)
|
||||||
|
except ElementTree.ParseError:
|
||||||
|
logger.error(
|
||||||
|
f'VAT ID checking failed for {country_code} due to XML parse error'
|
||||||
|
)
|
||||||
|
raise VATIDTemporaryError(error_messages['unavailable'])
|
||||||
|
|
||||||
|
namespaces = {
|
||||||
|
'soap': 'http://schemas.xmlsoap.org/soap/envelope/',
|
||||||
|
'vat': 'urn:ec.europa.eu:taxud:vies:services:checkVat:types'
|
||||||
|
}
|
||||||
|
valid_elements = envelope.findall('./soap:Body/vat:checkVatResponse/vat:valid', namespaces)
|
||||||
|
if not valid_elements:
|
||||||
|
logger.error(
|
||||||
|
f'VAT ID checking failed for {country_code} due to missing <valid> tag'
|
||||||
|
)
|
||||||
|
raise VATIDTemporaryError(error_messages['unavailable'])
|
||||||
|
|
||||||
|
if valid_elements[0].text.lower() != 'true':
|
||||||
|
raise VATIDFinalError(error_messages['invalid'])
|
||||||
|
|
||||||
|
except requests.RequestException:
|
||||||
logger.exception('VAT ID checking failed for country {}'.format(country_code))
|
logger.exception('VAT ID checking failed for country {}'.format(country_code))
|
||||||
raise VATIDTemporaryError(_(
|
raise VATIDTemporaryError(error_messages['unavailable'])
|
||||||
'Your VAT ID could not be checked, as the VAT checking service of '
|
else:
|
||||||
'your country returned an incorrect result. We will therefore '
|
return vat_id
|
||||||
'need to charge VAT on your invoice. Please contact support to '
|
|
||||||
'resolve this manually.'
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
def _validate_vat_id_CH(vat_id, country_code):
|
def _validate_vat_id_CH(vat_id, country_code):
|
||||||
@@ -85,10 +166,13 @@ def _validate_vat_id_CH(vat_id, country_code):
|
|||||||
|
|
||||||
vat_id = re.sub('[^A-Z0-9]', '', vat_id.replace('HR', '').replace('MWST', ''))
|
vat_id = re.sub('[^A-Z0-9]', '', vat_id.replace('HR', '').replace('MWST', ''))
|
||||||
try:
|
try:
|
||||||
transport = Transport(cache=SqliteCache(os.path.join(settings.CACHE_DIR, "validate_vat_id_ch_zeep_cache.db")))
|
transport = Transport(
|
||||||
|
cache=SqliteCache(os.path.join(settings.CACHE_DIR, "validate_vat_id_ch_zeep_cache.db")),
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
client = Client(
|
client = Client(
|
||||||
'https://www.uid-wse.admin.ch/V5.0/PublicServices.svc?wsdl',
|
'https://www.uid-wse.admin.ch/V5.0/PublicServices.svc?wsdl',
|
||||||
transport=transport
|
transport=transport,
|
||||||
)
|
)
|
||||||
result = client.service.ValidateUID(uid=vat_id)
|
result = client.service.ValidateUID(uid=vat_id)
|
||||||
except Fault as e:
|
except Fault as e:
|
||||||
@@ -125,10 +209,14 @@ def _validate_vat_id_CH(vat_id, country_code):
|
|||||||
|
|
||||||
|
|
||||||
def validate_vat_id(vat_id, country_code):
|
def validate_vat_id(vat_id, country_code):
|
||||||
|
if not vat_id:
|
||||||
|
return vat_id
|
||||||
country_code = str(country_code)
|
country_code = str(country_code)
|
||||||
if is_eu_country(country_code):
|
if is_eu_country(country_code):
|
||||||
return _validate_vat_id_EU(vat_id, country_code)
|
return _validate_vat_id_EU(vat_id, country_code)
|
||||||
elif country_code == 'CH':
|
elif country_code == 'CH':
|
||||||
return _validate_vat_id_CH(vat_id, country_code)
|
return _validate_vat_id_CH(vat_id, country_code)
|
||||||
|
elif country_code == 'NO':
|
||||||
|
return _validate_vat_id_NO(vat_id, country_code)
|
||||||
|
|
||||||
raise VATIDTemporaryError(f'VAT ID should not be entered for country {country_code}')
|
raise VATIDTemporaryError(f'VAT ID should not be entered for country {country_code}')
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ from django_countries.fields import Country
|
|||||||
from hierarkey.models import GlobalSettingsBase, Hierarkey
|
from hierarkey.models import GlobalSettingsBase, Hierarkey
|
||||||
from i18nfield.forms import I18nFormField, I18nTextarea, I18nTextInput
|
from i18nfield.forms import I18nFormField, I18nTextarea, I18nTextInput
|
||||||
from i18nfield.strings import LazyI18nString
|
from i18nfield.strings import LazyI18nString
|
||||||
|
from phonenumbers import PhoneNumber, parse
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from pretix.api.serializers.fields import (
|
from pretix.api.serializers.fields import (
|
||||||
@@ -2594,7 +2595,9 @@ Your {organizer} team"""))
|
|||||||
},
|
},
|
||||||
'name_scheme': {
|
'name_scheme': {
|
||||||
'default': 'full', # default for new events is 'given_family'
|
'default': 'full', # default for new events is 'given_family'
|
||||||
'type': str
|
'type': str,
|
||||||
|
'serializer_class': serializers.ChoiceField,
|
||||||
|
'serializer_kwargs': {},
|
||||||
},
|
},
|
||||||
'giftcard_length': {
|
'giftcard_length': {
|
||||||
'default': settings.ENTROPY['giftcard_secret'],
|
'default': settings.ENTROPY['giftcard_secret'],
|
||||||
@@ -2989,6 +2992,9 @@ PERSON_NAME_SCHEMES = OrderedDict([
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
DEFAULTS['name_scheme']['serializer_kwargs']['choices'] = ((k, k) for k in PERSON_NAME_SCHEMES)
|
||||||
|
|
||||||
COUNTRIES_WITH_STATE_IN_ADDRESS = {
|
COUNTRIES_WITH_STATE_IN_ADDRESS = {
|
||||||
# Source: http://www.bitboost.com/ref/international-address-formats.html
|
# Source: http://www.bitboost.com/ref/international-address-formats.html
|
||||||
# This is not a list of countries that *have* states, this is a list of countries where states
|
# This is not a list of countries that *have* states, this is a list of countries where states
|
||||||
@@ -3025,6 +3031,7 @@ settings_hierarkey.add_type(LazyI18nStringList,
|
|||||||
settings_hierarkey.add_type(RelativeDateWrapper,
|
settings_hierarkey.add_type(RelativeDateWrapper,
|
||||||
serialize=lambda rdw: rdw.to_string(),
|
serialize=lambda rdw: rdw.to_string(),
|
||||||
unserialize=lambda s: RelativeDateWrapper.from_string(s))
|
unserialize=lambda s: RelativeDateWrapper.from_string(s))
|
||||||
|
settings_hierarkey.add_type(PhoneNumber, lambda pn: pn.as_international, lambda s: parse(s))
|
||||||
|
|
||||||
|
|
||||||
@settings_hierarkey.set_global(cache_namespace='global')
|
@settings_hierarkey.set_global(cache_namespace='global')
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
{% extends "error.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
|
{% block title %}{% trans "Unknown host" %}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<i class="fa fa-question-circle-o fa-fw big-icon"></i>
|
||||||
|
<div class="error-details">
|
||||||
|
<h1>{% trans "Unknown host" %}</h1>
|
||||||
|
<p>
|
||||||
|
{% blocktrans trimmed with host=header_host %}
|
||||||
|
Your browser told us that you want to access "{{ header_host }}". Unfortunately, we don't have
|
||||||
|
any content for this domain.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</p>
|
||||||
|
{% if is_fresh_install %}
|
||||||
|
<p>
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
It looks like this is a fresh installation of pretix. This error message is probably caused due to
|
||||||
|
the fact that either your configuration includes the wrong site URL or your reverse proxy is sending
|
||||||
|
the wrong header.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</p>
|
||||||
|
<dl>
|
||||||
|
<dt>{% trans "Expected host according to configuration" %}</dt>
|
||||||
|
<dd><code>{{ site_host }}</code></dd>
|
||||||
|
<dt>{% trans "Received headers" %}</dt>
|
||||||
|
<dd>
|
||||||
|
<code>Host: {{ request.headers.Host }}</code>
|
||||||
|
{% if xfh %}
|
||||||
|
<br>
|
||||||
|
<code>X-Forwarded-For: {{ xfh }}</code>
|
||||||
|
{% if not settings.USE_X_FORWARDED_HOST %}({% trans "ignored" %}){% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</dd>
|
||||||
|
<dt>{% trans "Derived host from headers" %}</dt>
|
||||||
|
<dd><code>{{ header_host }}</code></dd>
|
||||||
|
</dl>
|
||||||
|
{% else %}
|
||||||
|
<p>
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
If you just configured this as a domain for your ticket shop, you now need to set this up as a "custom domain"
|
||||||
|
in your organizer account.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
<p class="links">
|
||||||
|
<a id='goback' href='#'>{% trans "Take a step back" %}</a>
|
||||||
|
· <a id='reload' href='#'>{% trans "Try again" %}</a>
|
||||||
|
</p>
|
||||||
|
<img src="{% static "pretixbase/img/pretix-logo.svg" %}" class="logo"/>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -199,7 +199,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td style="line-height: 0">
|
<td style="line-height: 0">
|
||||||
<img class="wide" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAlgAAAA8CAAAAACf95tlAAAAAXNCSVQI5gpbmQAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAG/SURBVHja7dvRboMwDIXhvf/DLiQQAwkku9+qDgq2hPyfN6j1qTlx06/uMunbLMnnhL98fuzRDtYILEeZ7GBNwAIWsIB1LdkOVgaWo4gdLAGWo6x2sFZgOUq1g1WB5SjNDlYDlqcEK1dDB5anmK3eE7C4FnIpBNbVFLo7sB7d3huwKFlULGA9pWQJsJxls4G1ActbooWr2IHlLbMFrBlY7rJbwNqBxb2QZ8nAuiUGO9ICLOo71R1YN0X9td8KLJ8ZeDEDrAd+Za3A4mLIz4TAujGqv+tUYPmN4v8LcweW3zS1t++hActzCrtRYD3pMJQOLOeJ7NyBpZFdoWaFDVjuJ6BRswpTBZbCAn5hpsDq/fbHpDMTBZbC1TAzT2ApyMIVsDROQ2GWwFJo8PR2YP3eOtywzwrsGYD1J9vlHXzcmSKw7q/wU2OEwHpdtALHILA00jJfV8DSaVofvYOPlckB658sp/8VNrBkANahqnXqfhhXJgasgymHD8REZwfWmezzga+tQdhcAet0qry1FYV3osD6dP1QJL3YbYUkhfUCsK6einWRPI0pxjROWZbK+QcsAiwCLEKARYBFgEXIu/wAYbjtwujw8KwAAAAASUVORK5CYII="
|
<img class="wide" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAlgAAAA8CAAAAACf95tlAAAAAXNCSVQI5gpbmQAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAG/SURBVHja7dvRboMwDIXhvf/DLiQQAwkku9+qDgq2hPyfN6j1qTlx06/uMunbLMnnhL98fuzRDtYILEeZ7GBNwAIWsIB1LdkOVgaWo4gdLAGWo6x2sFZgOUq1g1WB5SjNDlYDlqcEK1dDB5anmK3eE7C4FnIpBNbVFLo7sB7d3huwKFlULGA9pWQJsJxls4G1ActbooWr2IHlLbMFrBlY7rJbwNqBxb2QZ8nAuiUGO9ICLOo71R1YN0X9td8KLJ8ZeDEDrAd+Za3A4mLIz4TAujGqv+tUYPmN4v8LcweW3zS1t++hActzCrtRYD3pMJQOLOeJ7NyBpZFdoWaFDVjuJ6BRswpTBZbCAn5hpsDq/fbHpDMTBZbC1TAzT2ApyMIVsDROQ2GWwFJo8PR2YP3eOtywzwrsGYD1J9vlHXzcmSKw7q/wU2OEwHpdtALHILA00jJfV8DSaVofvYOPlckB658sp/8VNrBkANahqnXqfhhXJgasgymHD8REZwfWmezzga+tQdhcAet0qry1FYV3osD6dP1QJL3YbYUkhfUCsK6einWRPI0pxjROWZbK+QcsAiwCLEKARYBFgEXIu/wAYbjtwujw8KwAAAAASUVORK5CYII="
|
||||||
style="max-height: 60px;">
|
style="max-height: 60px;" alt="">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<!--<![endif]-->
|
<!--<![endif]-->
|
||||||
@@ -233,7 +233,7 @@
|
|||||||
<td style="line-height: 0">
|
<td style="line-height: 0">
|
||||||
<br>
|
<br>
|
||||||
<img class="wide" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAlgAAAA8CAYAAAC6nMS5AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAPnSURBVHic7d3dbuJIEAbQsg2Ecd7/TQeDf3svVuFmdjJLxsGm+xwJKXcpqS76U3VTVCmlFAAArKbeugAAgNwIWAAAKxOwAABWJmABAKxMwAIAWNlh6wIAIiKWZYllWWKe5/vfEREppfsnIqKqqvsnIqKu66jrOpqmuf8NsDUBC3i6lFJM0xTjOMY0TbEsS6y1MaaqqqjrOg6HQxyPxzgcDvcwBvAslT1YwDN8BKpxHGOe56f+76Zp4ng83gMXwHcTsIBvsyxLDMMQfd/fr/y2Vtd1nE6nOJ1O0TTN1uUAmRKwgNV9hKppmrYu5VOHwyHO53Mcj8etSwEyI2ABqxmGIW6329OvAP9WXddxPp/j7e1t61KATAhYwF/r+z5ut9turgG/StAC1iJgAV82z3N0Xbf7q8BHNU0Tbdt6EA98mYAFPCylFNfrNfq+37qUb3U6naJtW2segIcJWMBDhmGIrutW21u1d1VVRdu2cTqdti4FeCECFvC/lDK1+h3TLOARAhbwR/M8x+VyeblvB66taZp4f3+3Pwv4IwEL+NQ4jnG5XIq5EvyTqqri/f3d7izgUwIW8FvDMMTlctm6jF1q29Y6B+C3BCzgP91ut7her1uXsWvn8zl+/PixdRnADglYwC+6riv2Mfuj3t7eom3brcsAdqbeugBgX4Srx/R9H13XbV0GsDMCFnB3u92Eqy/4+KkggA8CFhAR/z5o9+bq60reEQb8SsAC7qsY+Dtd18U4jluXAeyAgAWFW5ZFuFqRhaxAhIAFxfv586cloitKKVnMCghYULKu60xbvsE8z96zQeEELCjUOI4eZX+jvu+9x4KCCVhQoI9rLL6Xq0Iol4AFBbperw7+J0gpuSqEQglYUJh5nl0NPlHf9zFN09ZlAE8mYEFh/KzL85liQXkELCiIaco2pmmKYRi2LgN4IgELCuL38rZjigVlEbCgEMMwxLIsW5dRrGVZTLGgIAIWFML0ant6AOUQsKAA4zja2L4D8zxbPgqFELCgACYn+6EXUAYBCzK3LItvDu7INE3ewkEBBCzInIfV+6MnkD8BCzLnMN8fPYH8CViQsXmePW7fIX2B/AlYkDGTkv3SG8ibgAUZ87h9v/QG8iZgQaZSSg7xHZumKVJKW5cBfBMBCzIlXO2fHkG+BCzIlMN7//QI8iVgQaYc3vunR5AvAQsyZQ3A/tnoDvkSsCBDKSUPqF/Asiz6BJkSsCBDplevQ68gTwIWZMjV0+vQK8iTgAUZMhV5HXoFeRKwIEPe9bwOvYI8CViQIYf269AryJOABQCwMgELMmQq8jr0CvIkYEGGHNqvQ68gT/8AETAn3pyLgvsAAAAASUVORK5CYII="
|
<img class="wide" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAlgAAAA8CAYAAAC6nMS5AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAPnSURBVHic7d3dbuJIEAbQsg2Ecd7/TQeDf3svVuFmdjJLxsGm+xwJKXcpqS76U3VTVCmlFAAArKbeugAAgNwIWAAAKxOwAABWJmABAKxMwAIAWNlh6wIAIiKWZYllWWKe5/vfEREppfsnIqKqqvsnIqKu66jrOpqmuf8NsDUBC3i6lFJM0xTjOMY0TbEsS6y1MaaqqqjrOg6HQxyPxzgcDvcwBvAslT1YwDN8BKpxHGOe56f+76Zp4ng83gMXwHcTsIBvsyxLDMMQfd/fr/y2Vtd1nE6nOJ1O0TTN1uUAmRKwgNV9hKppmrYu5VOHwyHO53Mcj8etSwEyI2ABqxmGIW6329OvAP9WXddxPp/j7e1t61KATAhYwF/r+z5ut9turgG/StAC1iJgAV82z3N0Xbf7q8BHNU0Tbdt6EA98mYAFPCylFNfrNfq+37qUb3U6naJtW2segIcJWMBDhmGIrutW21u1d1VVRdu2cTqdti4FeCECFvC/lDK1+h3TLOARAhbwR/M8x+VyeblvB66taZp4f3+3Pwv4IwEL+NQ4jnG5XIq5EvyTqqri/f3d7izgUwIW8FvDMMTlctm6jF1q29Y6B+C3BCzgP91ut7her1uXsWvn8zl+/PixdRnADglYwC+6riv2Mfuj3t7eom3brcsAdqbeugBgX4Srx/R9H13XbV0GsDMCFnB3u92Eqy/4+KkggA8CFhAR/z5o9+bq60reEQb8SsAC7qsY+Dtd18U4jluXAeyAgAWFW5ZFuFqRhaxAhIAFxfv586cloitKKVnMCghYULKu60xbvsE8z96zQeEELCjUOI4eZX+jvu+9x4KCCVhQoI9rLL6Xq0Iol4AFBbperw7+J0gpuSqEQglYUJh5nl0NPlHf9zFN09ZlAE8mYEFh/KzL85liQXkELCiIaco2pmmKYRi2LgN4IgELCuL38rZjigVlEbCgEMMwxLIsW5dRrGVZTLGgIAIWFML0ant6AOUQsKAA4zja2L4D8zxbPgqFELCgACYn+6EXUAYBCzK3LItvDu7INE3ewkEBBCzInIfV+6MnkD8BCzLnMN8fPYH8CViQsXmePW7fIX2B/AlYkDGTkv3SG8ibgAUZ87h9v/QG8iZgQaZSSg7xHZumKVJKW5cBfBMBCzIlXO2fHkG+BCzIlMN7//QI8iVgQaYc3vunR5AvAQsyZQ3A/tnoDvkSsCBDKSUPqF/Asiz6BJkSsCBDplevQ68gTwIWZMjV0+vQK8iTgAUZMhV5HXoFeRKwIEPe9bwOvYI8CViQIYf269AryJOABQCwMgELMmQq8jr0CvIkYEGGHNqvQ68gT/8AETAn3pyLgvsAAAAASUVORK5CYII="
|
||||||
style="max-height: 60px;">
|
style="max-height: 60px;" alt="">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<!--<![endif]-->
|
<!--<![endif]-->
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<td>
|
<td>
|
||||||
<div style="line-height: 18px;height: 18px;"> </div>
|
<div style="line-height: 18px;height: 18px;"> </div>
|
||||||
<img class="wide" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAlgAAAAEBAMAAACgm1xKAAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV/TSkUrDu0g4pChOlkQleKoVShChVArtOpgcukXNGlIUlwcBdeCgx+LVQcXZ10dXAVB8APExdVJ0UVK/F9aaBHjwXE/3t173L0DhEaFaVZgAtB020wnE2I2tyoGXxFCAP0IIy4zy5iTpBQ8x9c9fHy9i/Es73N/jgE1bzHAJxLPMsO0iTeI45u2wXmfOMJKskp8Tjxu0gWJH7mutPiNc9FlgWdGzEx6njhCLBa7WOliVjI14mniqKrplC9kW6xy3uKsVWqsfU/+wlBeX1nmOs0RJLGIJUgQoaCGMiqwEaNVJ8VCmvYTHv5h1y+RSyFXGYwcC6hCg+z6wf/gd7dWYWqylRRKAD0vjvMxCgR3gWbdcb6PHad5AvifgSu94682gJlP0usdLXoEDG4DF9cdTdkDLneAoSdDNmVX8tMUCgXg/Yy+KQeEb4G+tVZv7X2cPgAZ6ip1AxwcAmNFyl73eHdvd2//nmn39wNhNnKgJpT5BQAAAC1QTFRF7u7u7+/v8PDw8fHx8vLy9PT09fX19/f3+Pj4+fn5+vr6/Pz8/f39/v7+////BLnnfgAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB+MMBAsUDD3bzUUAAABhSURBVDjLY2BAgLp3CNCAJO6HJH4ASZwXSfwxkjgnkvhzJHFWJPHXSOKMSOLvFJAk1iGJJyCJ5yGJL0AS10MSf4Akzo0k/hRJnB1J/CWSOAuS+FsG7GA0sEYDazSwBiSwAPzzGpfLqBMlAAAAAElFTkSuQmCC"
|
<img class="wide" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAlgAAAAEBAMAAACgm1xKAAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV/TSkUrDu0g4pChOlkQleKoVShChVArtOpgcukXNGlIUlwcBdeCgx+LVQcXZ10dXAVB8APExdVJ0UVK/F9aaBHjwXE/3t173L0DhEaFaVZgAtB020wnE2I2tyoGXxFCAP0IIy4zy5iTpBQ8x9c9fHy9i/Es73N/jgE1bzHAJxLPMsO0iTeI45u2wXmfOMJKskp8Tjxu0gWJH7mutPiNc9FlgWdGzEx6njhCLBa7WOliVjI14mniqKrplC9kW6xy3uKsVWqsfU/+wlBeX1nmOs0RJLGIJUgQoaCGMiqwEaNVJ8VCmvYTHv5h1y+RSyFXGYwcC6hCg+z6wf/gd7dWYWqylRRKAD0vjvMxCgR3gWbdcb6PHad5AvifgSu94682gJlP0usdLXoEDG4DF9cdTdkDLneAoSdDNmVX8tMUCgXg/Yy+KQeEb4G+tVZv7X2cPgAZ6ip1AxwcAmNFyl73eHdvd2//nmn39wNhNnKgJpT5BQAAAC1QTFRF7u7u7+/v8PDw8fHx8vLy9PT09fX19/f3+Pj4+fn5+vr6/Pz8/f39/v7+////BLnnfgAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB+MMBAsUDD3bzUUAAABhSURBVDjLY2BAgLp3CNCAJO6HJH4ASZwXSfwxkjgnkvhzJHFWJPHXSOKMSOLvFJAk1iGJJyCJ5yGJL0AS10MSf4Akzo0k/hRJnB1J/CWSOAuS+FsG7GA0sEYDazSwBiSwAPzzGpfLqBMlAAAAAElFTkSuQmCC"
|
||||||
style="max-height: 4px;">
|
style="max-height: 4px;" alt="">
|
||||||
<div style="line-height: 18px;height: 18px;"> </div>
|
<div style="line-height: 18px;height: 18px;"> </div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -216,7 +216,8 @@ class AsyncFormView(AsyncMixin, FormView):
|
|||||||
task_base = ProfiledEventTask
|
task_base = ProfiledEventTask
|
||||||
|
|
||||||
def __init_subclass__(cls):
|
def __init_subclass__(cls):
|
||||||
def async_execute(self, *, request_path, query_string, form_kwargs, locale, tz, organizer=None, event=None, user=None, session_key=None):
|
def async_execute(self, *, request_path, query_string, form_kwargs, locale, tz, url_kwargs=None, url_args=None,
|
||||||
|
organizer=None, event=None, user=None, session_key=None):
|
||||||
view_instance = cls()
|
view_instance = cls()
|
||||||
form_kwargs['data'] = QueryDict(form_kwargs['data'])
|
form_kwargs['data'] = QueryDict(form_kwargs['data'])
|
||||||
req = RequestFactory().post(
|
req = RequestFactory().post(
|
||||||
@@ -225,6 +226,8 @@ class AsyncFormView(AsyncMixin, FormView):
|
|||||||
content_type='application/x-www-form-urlencoded'
|
content_type='application/x-www-form-urlencoded'
|
||||||
)
|
)
|
||||||
view_instance.request = req
|
view_instance.request = req
|
||||||
|
view_instance.kwargs = url_kwargs
|
||||||
|
view_instance.args = url_args
|
||||||
if event:
|
if event:
|
||||||
view_instance.request.event = event
|
view_instance.request.event = event
|
||||||
view_instance.request.organizer = event.organizer
|
view_instance.request.organizer = event.organizer
|
||||||
@@ -284,6 +287,8 @@ class AsyncFormView(AsyncMixin, FormView):
|
|||||||
'request_path': self.request.path,
|
'request_path': self.request.path,
|
||||||
'query_string': self.request.GET.urlencode(),
|
'query_string': self.request.GET.urlencode(),
|
||||||
'form_kwargs': form_kwargs,
|
'form_kwargs': form_kwargs,
|
||||||
|
'url_args': self.args,
|
||||||
|
'url_kwargs': self.kwargs,
|
||||||
'locale': get_language(),
|
'locale': get_language(),
|
||||||
'tz': get_current_timezone().zone,
|
'tz': get_current_timezone().zone,
|
||||||
}
|
}
|
||||||
@@ -336,6 +341,8 @@ class AsyncPostView(AsyncMixin, View):
|
|||||||
content_type='application/x-www-form-urlencoded'
|
content_type='application/x-www-form-urlencoded'
|
||||||
)
|
)
|
||||||
view_instance.request = req
|
view_instance.request = req
|
||||||
|
view_instance.kwargs = url_kwargs
|
||||||
|
view_instance.args = url_args
|
||||||
if event:
|
if event:
|
||||||
view_instance.request.event = event
|
view_instance.request.event = event
|
||||||
view_instance.request.organizer = event.organizer
|
view_instance.request.organizer = event.organizer
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ def _default_context(request):
|
|||||||
except Resolver404:
|
except Resolver404:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
if not request.path.startswith(get_script_prefix() + 'control'):
|
if not request.path.startswith(get_script_prefix() + 'control') or not hasattr(request, 'user'):
|
||||||
return {}
|
return {}
|
||||||
ctx = {
|
ctx = {
|
||||||
'url_name': url.url_name,
|
'url_name': url.url_name,
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ from urllib.parse import urlencode, urlparse
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.validators import validate_email
|
from django.core.validators import MaxValueValidator, validate_email
|
||||||
from django.db.models import Prefetch, Q, prefetch_related_objects
|
from django.db.models import Prefetch, Q, prefetch_related_objects
|
||||||
from django.forms import (
|
from django.forms import (
|
||||||
CheckboxSelectMultiple, formset_factory, inlineformset_factory,
|
CheckboxSelectMultiple, formset_factory, inlineformset_factory,
|
||||||
@@ -848,6 +848,7 @@ class InvoiceSettingsForm(SettingsForm):
|
|||||||
self.fields['invoice_generate_sales_channels'].choices = (
|
self.fields['invoice_generate_sales_channels'].choices = (
|
||||||
(c.identifier, c.verbose_name) for c in get_all_sales_channels().values()
|
(c.identifier, c.verbose_name) for c in get_all_sales_channels().values()
|
||||||
)
|
)
|
||||||
|
self.fields['invoice_numbers_counter_length'].validators.append(MaxValueValidator(15))
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
data = super().clean()
|
data = super().clean()
|
||||||
|
|||||||
@@ -207,14 +207,16 @@
|
|||||||
{% bootstrap_field sform.logo_show_title layout="control" %}
|
{% bootstrap_field sform.logo_show_title layout="control" %}
|
||||||
{% bootstrap_field sform.og_image layout="control" %}
|
{% bootstrap_field sform.og_image layout="control" %}
|
||||||
{% url "control:organizer.edit" organizer=request.organizer.slug as org_url %}
|
{% url "control:organizer.edit" organizer=request.organizer.slug as org_url %}
|
||||||
{% propagated request.event org_url "primary_color" "primary_font" "theme_color_success" "theme_color_danger" "theme_color_background" "theme_round_borders" %}
|
{% with org_url|add:"#tab-0-4-open" as org_url_tab %}
|
||||||
{% bootstrap_field sform.primary_color layout="control" %}
|
{% propagated request.event org_url_tab "primary_color" "primary_font" "theme_color_success" "theme_color_danger" "theme_color_background" "theme_round_borders" %}
|
||||||
{% bootstrap_field sform.theme_color_success layout="control" %}
|
{% bootstrap_field sform.primary_color layout="control" %}
|
||||||
{% bootstrap_field sform.theme_color_danger layout="control" %}
|
{% bootstrap_field sform.theme_color_success layout="control" %}
|
||||||
{% bootstrap_field sform.theme_color_background layout="control" %}
|
{% bootstrap_field sform.theme_color_danger layout="control" %}
|
||||||
{% bootstrap_field sform.theme_round_borders layout="control" %}
|
{% bootstrap_field sform.theme_color_background layout="control" %}
|
||||||
{% bootstrap_field sform.primary_font layout="control" %}
|
{% bootstrap_field sform.theme_round_borders layout="control" %}
|
||||||
{% endpropagated %}
|
{% bootstrap_field sform.primary_font layout="control" %}
|
||||||
|
{% endpropagated %}
|
||||||
|
{% endwith %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{% trans "Timeline" %}</legend>
|
<legend>{% trans "Timeline" %}</legend>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class PropagatedNode(Node):
|
|||||||
</div>
|
</div>
|
||||||
<div class="panel-body help-text">
|
<div class="panel-body help-text">
|
||||||
{text_expl}<br>
|
{text_expl}<br>
|
||||||
<a href="{url}" target="_blank">
|
<a href="{url}" target="_blank" class="btn btn-default">
|
||||||
{text_orga}
|
{text_orga}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1344,22 +1344,36 @@ class ItemUpdateGeneral(ItemDetailMixin, EventPermissionRequiredMixin, MetaDataE
|
|||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
self.save_meta()
|
self.save_meta()
|
||||||
messages.success(self.request, _('Your changes have been saved.'))
|
messages.success(self.request, _('Your changes have been saved.'))
|
||||||
if form.has_changed() or any(f.has_changed() for f in self.plugin_forms):
|
|
||||||
data = {
|
change_data = {
|
||||||
k: form.cleaned_data.get(k)
|
k: form.cleaned_data.get(k)
|
||||||
for k in form.changed_data
|
for k in form.changed_data
|
||||||
}
|
}
|
||||||
for f in self.plugin_forms:
|
for f in self.plugin_forms:
|
||||||
data.update({
|
change_data.update({
|
||||||
k: (f.cleaned_data.get(k).name
|
k: (f.cleaned_data.get(k).name
|
||||||
if isinstance(f.cleaned_data.get(k), File)
|
if isinstance(f.cleaned_data.get(k), File)
|
||||||
else f.cleaned_data.get(k))
|
else f.cleaned_data.get(k))
|
||||||
for k in f.changed_data
|
for k in f.changed_data
|
||||||
})
|
})
|
||||||
|
|
||||||
|
meta_changed = {}
|
||||||
|
for f in self.meta_forms:
|
||||||
|
meta_changed.update({
|
||||||
|
k: (f.cleaned_data.get(k).name
|
||||||
|
if isinstance(f.cleaned_data.get(k), File)
|
||||||
|
else f.cleaned_data.get(k))
|
||||||
|
for k in f.changed_data
|
||||||
|
})
|
||||||
|
if meta_changed:
|
||||||
|
change_data['meta_data'] = meta_changed
|
||||||
|
|
||||||
|
if change_data:
|
||||||
self.object.log_action(
|
self.object.log_action(
|
||||||
'pretix.event.item.changed', user=self.request.user, data=data
|
'pretix.event.item.changed', user=self.request.user, data=change_data
|
||||||
)
|
)
|
||||||
invalidate_cache.apply_async(kwargs={'event': self.request.event.pk, 'item': self.object.pk})
|
invalidate_cache.apply_async(kwargs={'event': self.request.event.pk, 'item': self.object.pk})
|
||||||
|
|
||||||
for f in self.plugin_forms:
|
for f in self.plugin_forms:
|
||||||
f.save()
|
f.save()
|
||||||
|
|
||||||
|
|||||||
@@ -1186,13 +1186,17 @@ class OrderTransition(OrderView):
|
|||||||
if ps == Decimal('0.00') and self.order.pending_sum <= Decimal('0.00'):
|
if ps == Decimal('0.00') and self.order.pending_sum <= Decimal('0.00'):
|
||||||
p = self.order.payments.filter(state=OrderPayment.PAYMENT_STATE_CONFIRMED).last()
|
p = self.order.payments.filter(state=OrderPayment.PAYMENT_STATE_CONFIRMED).last()
|
||||||
if p:
|
if p:
|
||||||
p._mark_order_paid(
|
try:
|
||||||
user=self.request.user,
|
p._mark_order_paid(
|
||||||
send_mail=self.mark_paid_form.cleaned_data['send_email'],
|
user=self.request.user,
|
||||||
force=self.mark_paid_form.cleaned_data.get('force', False),
|
send_mail=self.mark_paid_form.cleaned_data['send_email'],
|
||||||
payment_refund_sum=self.order.payment_refund_sum,
|
force=self.mark_paid_form.cleaned_data.get('force', False),
|
||||||
)
|
payment_refund_sum=self.order.payment_refund_sum,
|
||||||
messages.success(self.request, _('The order has been marked as paid.'))
|
)
|
||||||
|
except Quota.QuotaExceededException as e:
|
||||||
|
messages.error(self.request, str(e))
|
||||||
|
else:
|
||||||
|
messages.success(self.request, _('The order has been marked as paid.'))
|
||||||
return redirect(self.get_order_url())
|
return redirect(self.get_order_url())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ from django.utils.crypto import get_random_string
|
|||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
from PyPDF2 import PdfFileReader, PdfFileWriter
|
from PyPDF2 import PdfReader, PdfWriter
|
||||||
from PyPDF2.utils import PdfReadError
|
from PyPDF2.errors import PdfReadError
|
||||||
from reportlab.lib.units import mm
|
from reportlab.lib.units import mm
|
||||||
|
|
||||||
from pretix.base.i18n import language
|
from pretix.base.i18n import language
|
||||||
@@ -153,9 +153,9 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
|
|||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
if "emptybackground" in request.POST:
|
if "emptybackground" in request.POST:
|
||||||
p = PdfFileWriter()
|
p = PdfWriter()
|
||||||
try:
|
try:
|
||||||
p.addBlankPage(
|
p.add_blank_page(
|
||||||
width=float(request.POST.get('width')) * mm,
|
width=float(request.POST.get('width')) * mm,
|
||||||
height=float(request.POST.get('height')) * mm,
|
height=float(request.POST.get('height')) * mm,
|
||||||
)
|
)
|
||||||
@@ -203,7 +203,7 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
bg_bytes = c.file.read()
|
bg_bytes = c.file.read()
|
||||||
PdfFileReader(BytesIO(bg_bytes), strict=False)
|
PdfReader(BytesIO(bg_bytes), strict=False)
|
||||||
except PdfReadError as e:
|
except PdfReadError as e:
|
||||||
return JsonResponse({
|
return JsonResponse({
|
||||||
"status": "error",
|
"status": "error",
|
||||||
|
|||||||
@@ -228,9 +228,9 @@ def nav_context_list(request):
|
|||||||
if query:
|
if query:
|
||||||
qs_orga = qs_orga.filter(Q(name__icontains=query) | Q(slug__icontains=query))
|
qs_orga = qs_orga.filter(Q(name__icontains=query) | Q(slug__icontains=query))
|
||||||
|
|
||||||
if query:
|
if query and len(query) >= 3:
|
||||||
qs_orders = Order.objects.filter(
|
qs_orders = Order.objects.filter(
|
||||||
code__icontains=query
|
code__istartswith=query
|
||||||
).select_related('event', 'event__organizer').only('event', 'code', 'pk').order_by()
|
).select_related('event', 'event__organizer').only('event', 'code', 'pk').order_by()
|
||||||
if not request.user.has_active_staff_session(request.session.session_key):
|
if not request.user.has_active_staff_session(request.session.session_key):
|
||||||
qs_orders = qs_orders.filter(
|
qs_orders = qs_orders.filter(
|
||||||
@@ -241,7 +241,7 @@ def nav_context_list(request):
|
|||||||
)
|
)
|
||||||
|
|
||||||
qs_vouchers = Voucher.objects.filter(
|
qs_vouchers = Voucher.objects.filter(
|
||||||
code__icontains=query
|
code__istartswith=query
|
||||||
).select_related('event', 'event__organizer').only('event', 'code', 'pk').order_by()
|
).select_related('event', 'event__organizer').only('event', 'code', 'pk').order_by()
|
||||||
if not request.user.has_active_staff_session(request.session.session_key):
|
if not request.user.has_active_staff_session(request.session.session_key):
|
||||||
qs_vouchers = qs_vouchers.filter(
|
qs_vouchers = qs_vouchers.filter(
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ from django.core.cache import cache
|
|||||||
from django.core.exceptions import DisallowedHost
|
from django.core.exceptions import DisallowedHost
|
||||||
from django.http.request import split_domain_port
|
from django.http.request import split_domain_port
|
||||||
from django.middleware.csrf import CsrfViewMiddleware as BaseCsrfMiddleware
|
from django.middleware.csrf import CsrfViewMiddleware as BaseCsrfMiddleware
|
||||||
|
from django.shortcuts import render
|
||||||
from django.urls import set_urlconf
|
from django.urls import set_urlconf
|
||||||
from django.utils.cache import patch_vary_headers
|
from django.utils.cache import patch_vary_headers
|
||||||
from django.utils.deprecation import MiddlewareMixin
|
from django.utils.deprecation import MiddlewareMixin
|
||||||
@@ -112,7 +113,15 @@ class MultiDomainMiddleware(MiddlewareMixin):
|
|||||||
elif settings.DEBUG or domain in LOCAL_HOST_NAMES:
|
elif settings.DEBUG or domain in LOCAL_HOST_NAMES:
|
||||||
request.urlconf = "pretix.multidomain.maindomain_urlconf"
|
request.urlconf = "pretix.multidomain.maindomain_urlconf"
|
||||||
else:
|
else:
|
||||||
raise DisallowedHost("Unknown host: %r" % host)
|
with scopes_disabled():
|
||||||
|
is_fresh_install = not Event.objects.exists()
|
||||||
|
return render(request, '400_hostname.html', {
|
||||||
|
'header_host': domain,
|
||||||
|
'site_host': default_domain,
|
||||||
|
'settings': settings,
|
||||||
|
'xfh': request.headers.get('X-Forwarded-Host'),
|
||||||
|
'is_fresh_install': is_fresh_install,
|
||||||
|
}, status=400)
|
||||||
else:
|
else:
|
||||||
raise DisallowedHost("Invalid HTTP_HOST header: %r." % host)
|
raise DisallowedHost("Invalid HTTP_HOST header: %r." % host)
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ from django.db.models import Exists, OuterRef, Q
|
|||||||
from django.db.models.functions import Coalesce
|
from django.db.models.functions import Coalesce
|
||||||
from django.utils.timezone import make_aware
|
from django.utils.timezone import make_aware
|
||||||
from django.utils.translation import gettext as _, gettext_lazy
|
from django.utils.translation import gettext as _, gettext_lazy
|
||||||
|
from PyPDF2 import Transformation
|
||||||
from reportlab.lib import pagesizes
|
from reportlab.lib import pagesizes
|
||||||
from reportlab.lib.units import mm
|
from reportlab.lib.units import mm
|
||||||
from reportlab.pdfgen import canvas
|
from reportlab.pdfgen import canvas
|
||||||
@@ -153,11 +154,19 @@ OPTIONS = OrderedDict([
|
|||||||
'offsets': [95 * mm, 55 * mm],
|
'offsets': [95 * mm, 55 * mm],
|
||||||
'pagesize': pagesizes.A4,
|
'pagesize': pagesizes.A4,
|
||||||
}),
|
}),
|
||||||
|
('herma_40x40', {
|
||||||
|
'name': 'HERMA 40 x 40 mm (9642)',
|
||||||
|
'cols': 4,
|
||||||
|
'rows': 6,
|
||||||
|
'margins': [13.5 * mm, 15 * mm, 13.5 * mm, 15 * mm],
|
||||||
|
'offsets': [46 * mm, 46 * mm],
|
||||||
|
'pagesize': pagesizes.A4,
|
||||||
|
}),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
def render_pdf(event, positions, opt):
|
def render_pdf(event, positions, opt):
|
||||||
from PyPDF2 import PdfFileReader, PdfFileWriter
|
from PyPDF2 import PdfReader, PdfWriter
|
||||||
Renderer._register_fonts()
|
Renderer._register_fonts()
|
||||||
|
|
||||||
renderermap = {
|
renderermap = {
|
||||||
@@ -168,7 +177,7 @@ def render_pdf(event, positions, opt):
|
|||||||
default_renderer = _renderer(event, event.badge_layouts.get(default=True))
|
default_renderer = _renderer(event, event.badge_layouts.get(default=True))
|
||||||
except BadgeLayout.DoesNotExist:
|
except BadgeLayout.DoesNotExist:
|
||||||
default_renderer = None
|
default_renderer = None
|
||||||
output_pdf_writer = PdfFileWriter()
|
output_pdf_writer = PdfWriter()
|
||||||
|
|
||||||
any = False
|
any = False
|
||||||
npp = opt['cols'] * opt['rows']
|
npp = opt['cols'] * opt['rows']
|
||||||
@@ -189,22 +198,19 @@ def render_pdf(event, positions, opt):
|
|||||||
p.showPage()
|
p.showPage()
|
||||||
p.save()
|
p.save()
|
||||||
buffer.seek(0)
|
buffer.seek(0)
|
||||||
canvas_pdf_reader = PdfFileReader(buffer)
|
canvas_pdf_reader = PdfReader(buffer)
|
||||||
empty_pdf_page = output_pdf_writer.addBlankPage(
|
empty_pdf_page = output_pdf_writer.add_blank_page(
|
||||||
width=opt['pagesize'][0] if opt['pagesize'] else positions[0][1].bg_pdf.getPage(0).mediaBox[2],
|
width=opt['pagesize'][0] if opt['pagesize'] else positions[0][1].bg_pdf.pages[0].mediabox[2],
|
||||||
height=opt['pagesize'][1] if opt['pagesize'] else positions[0][1].bg_pdf.getPage(0).mediaBox[3],
|
height=opt['pagesize'][1] if opt['pagesize'] else positions[0][1].bg_pdf.pages[0].mediabox[3],
|
||||||
)
|
)
|
||||||
for i, (op, r) in enumerate(positions):
|
for i, (op, r) in enumerate(positions):
|
||||||
bg_page = copy.copy(r.bg_pdf.getPage(0))
|
bg_page = copy.copy(r.bg_pdf.pages[0])
|
||||||
bg_page.trimBox = bg_page.mediaBox
|
bg_page.trimbox = bg_page.mediabox
|
||||||
offsetx = opt['margins'][3] + (i % opt['cols']) * opt['offsets'][0]
|
offsetx = opt['margins'][3] + (i % opt['cols']) * opt['offsets'][0]
|
||||||
offsety = opt['margins'][2] + (opt['rows'] - 1 - i // opt['cols']) * opt['offsets'][1]
|
offsety = opt['margins'][2] + (opt['rows'] - 1 - i // opt['cols']) * opt['offsets'][1]
|
||||||
empty_pdf_page.mergeTranslatedPage(
|
bg_page.add_transformation(Transformation().translate(offsetx, offsety))
|
||||||
bg_page,
|
empty_pdf_page.merge_page(bg_page)
|
||||||
tx=offsetx,
|
empty_pdf_page.merge_page(canvas_pdf_reader.pages[0])
|
||||||
ty=offsety
|
|
||||||
)
|
|
||||||
empty_pdf_page.mergePage(canvas_pdf_reader.getPage(0))
|
|
||||||
|
|
||||||
pagebuffer = []
|
pagebuffer = []
|
||||||
outbuffer = BytesIO()
|
outbuffer = BytesIO()
|
||||||
@@ -221,7 +227,7 @@ def render_pdf(event, positions, opt):
|
|||||||
if pagebuffer:
|
if pagebuffer:
|
||||||
render_page(pagebuffer)
|
render_page(pagebuffer)
|
||||||
|
|
||||||
output_pdf_writer.addMetadata({
|
output_pdf_writer.add_metadata({
|
||||||
'/Title': 'Badges',
|
'/Title': 'Badges',
|
||||||
'/Creator': 'pretix',
|
'/Creator': 'pretix',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -582,7 +582,7 @@ class PaypalMethod(BasePaymentProvider):
|
|||||||
on the 'confirm order' page.
|
on the 'confirm order' page.
|
||||||
"""
|
"""
|
||||||
template = get_template('pretixplugins/paypal2/checkout_payment_confirm.html')
|
template = get_template('pretixplugins/paypal2/checkout_payment_confirm.html')
|
||||||
ctx = {'request': request, 'event': self.event, 'settings': self.settings}
|
ctx = {'request': request, 'event': self.event, 'settings': self.settings, 'method': self.method}
|
||||||
return template.render(ctx)
|
return template.render(ctx)
|
||||||
|
|
||||||
def execute_payment(self, request: HttpRequest, payment: OrderPayment):
|
def execute_payment(self, request: HttpRequest, payment: OrderPayment):
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ def html_head_presale(sender, request=None, **kwargs):
|
|||||||
|
|
||||||
if provider.settings.get('_enabled', as_type=bool) and (
|
if provider.settings.get('_enabled', as_type=bool) and (
|
||||||
url.url_name == "event.order.pay.change" or
|
url.url_name == "event.order.pay.change" or
|
||||||
|
url.url_name == "event.order.pay" or
|
||||||
(url.url_name == "event.checkout" and url.kwargs['step'] == "payment") or
|
(url.url_name == "event.checkout" and url.kwargs['step'] == "payment") or
|
||||||
(url.namespace == "plugins:paypal2" and url.url_name == "pay")
|
(url.namespace == "plugins:paypal2" and url.url_name == "pay")
|
||||||
):
|
):
|
||||||
@@ -137,6 +138,7 @@ def signal_process_response(sender, request: HttpRequest, response: HttpResponse
|
|||||||
|
|
||||||
if provider.settings.get('_enabled', as_type=bool) and (
|
if provider.settings.get('_enabled', as_type=bool) and (
|
||||||
url.url_name == "event.order.pay.change" or
|
url.url_name == "event.order.pay.change" or
|
||||||
|
url.url_name == "event.order.pay" or
|
||||||
(url.url_name == "event.checkout" and url.kwargs['step'] == "payment") or
|
(url.url_name == "event.checkout" and url.kwargs['step'] == "payment") or
|
||||||
(url.namespace == "plugins:paypal2" and url.url_name == "pay")
|
(url.namespace == "plugins:paypal2" and url.url_name == "pay")
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -141,11 +141,11 @@ var pretixpaypal = {
|
|||||||
pretixpaypal.restore();
|
pretixpaypal.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
if ($("input[name=payment][value^='paypal']").is(':checked') || $(".payment-redo-form").length) {
|
if ($("input[name=payment][value^='paypal']").is(':checked')) {
|
||||||
pretixpaypal.renderButton($("input[name=payment][value^='paypal']:checked").val());
|
pretixpaypal.renderButton($("input[name=payment][value^='paypal']:checked").val());
|
||||||
}
|
} else if ($(".payment-redo-form").length) {
|
||||||
|
pretixpaypal.renderButton($("input[name=payment][value^='paypal']").val());
|
||||||
if ($('#paypal-button-container').data('paypage')) {
|
} else if ($('#paypal-button-container').data('paypage')) {
|
||||||
pretixpaypal.renderButton('paypal_apm');
|
pretixpaypal.renderButton('paypal_apm');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ def success(request, *args, **kwargs):
|
|||||||
else:
|
else:
|
||||||
payment = None
|
payment = None
|
||||||
|
|
||||||
if request.session.get('payment_paypal_id', None):
|
if request.session.get('payment_paypal_oid', None):
|
||||||
if payment:
|
if payment:
|
||||||
prov = Paypal(request.event)
|
prov = Paypal(request.event)
|
||||||
try:
|
try:
|
||||||
@@ -296,7 +296,7 @@ def success(request, *args, **kwargs):
|
|||||||
return resp
|
return resp
|
||||||
else:
|
else:
|
||||||
messages.error(request, _('Invalid response from PayPal received.'))
|
messages.error(request, _('Invalid response from PayPal received.'))
|
||||||
logger.error('Session did not contain payment_paypal_id')
|
logger.error('Session did not contain payment_paypal_oid')
|
||||||
urlkwargs['step'] = 'payment'
|
urlkwargs['step'] = 'payment'
|
||||||
return redirect(eventreverse(request.event, 'presale:event.checkout', kwargs=urlkwargs))
|
return redirect(eventreverse(request.event, 'presale:event.checkout', kwargs=urlkwargs))
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ from django.db.models.signals import post_save
|
|||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.urls import resolve, reverse
|
from django.urls import resolve, reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.utils.timezone import now
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django_scopes import scope, scopes_disabled
|
from django_scopes import scope, scopes_disabled
|
||||||
|
|
||||||
@@ -134,6 +135,7 @@ def sendmail_run_rules(sender, **kwargs):
|
|||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
mails = ScheduledMail.objects.all()
|
mails = ScheduledMail.objects.all()
|
||||||
|
|
||||||
|
unchanged = []
|
||||||
for m in mails.filter(Q(last_computed__isnull=True)
|
for m in mails.filter(Q(last_computed__isnull=True)
|
||||||
| Q(subevent__last_modified__gt=F('last_computed'))
|
| Q(subevent__last_modified__gt=F('last_computed'))
|
||||||
| Q(event__last_modified__gt=F('last_computed'))):
|
| Q(event__last_modified__gt=F('last_computed'))):
|
||||||
@@ -141,6 +143,17 @@ def sendmail_run_rules(sender, **kwargs):
|
|||||||
m.recompute()
|
m.recompute()
|
||||||
if m.computed_datetime != previous:
|
if m.computed_datetime != previous:
|
||||||
m.save(update_fields=['last_computed', 'computed_datetime'])
|
m.save(update_fields=['last_computed', 'computed_datetime'])
|
||||||
|
else:
|
||||||
|
unchanged.append(m.pk)
|
||||||
|
|
||||||
|
if unchanged:
|
||||||
|
# Theoretically, we don't need to write back the unchanged ones to the database… but that will cause us to
|
||||||
|
# recompute them on every run until eternity. So we want to set their last_computed date to something more
|
||||||
|
# recent... but not for all of them at once, in case it's millions, so we don't stress the database without
|
||||||
|
# cause
|
||||||
|
batch_size = max(connection.ops.bulk_batch_size(['id'], unchanged) - 2, 100)
|
||||||
|
for i in range(max(1, 5000 // batch_size)):
|
||||||
|
ScheduledMail.objects.filter(pk__in=unchanged[i * batch_size:batch_size]).update(last_computed=now())
|
||||||
|
|
||||||
mails.filter(
|
mails.filter(
|
||||||
state=ScheduledMail.STATE_SCHEDULED,
|
state=ScheduledMail.STATE_SCHEDULED,
|
||||||
|
|||||||
@@ -166,6 +166,11 @@ class StripeSettingsHolder(BasePaymentProvider):
|
|||||||
label=_('Stripe account'),
|
label=_('Stripe account'),
|
||||||
disabled=True
|
disabled=True
|
||||||
)),
|
)),
|
||||||
|
('connect_user_id',
|
||||||
|
forms.CharField(
|
||||||
|
label=_('Stripe account'),
|
||||||
|
disabled=True
|
||||||
|
)),
|
||||||
('endpoint',
|
('endpoint',
|
||||||
forms.ChoiceField(
|
forms.ChoiceField(
|
||||||
label=_('Endpoint'),
|
label=_('Endpoint'),
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ from django.db.models import Q
|
|||||||
from django.db.models.functions import Coalesce
|
from django.db.models.functions import Coalesce
|
||||||
from django.utils.timezone import make_aware
|
from django.utils.timezone import make_aware
|
||||||
from django.utils.translation import gettext as _, gettext_lazy
|
from django.utils.translation import gettext as _, gettext_lazy
|
||||||
from PyPDF2.merger import PdfFileMerger
|
from PyPDF2 import PdfMerger
|
||||||
|
|
||||||
from pretix.base.exporter import BaseExporter
|
from pretix.base.exporter import BaseExporter
|
||||||
from pretix.base.i18n import language
|
from pretix.base.i18n import language
|
||||||
@@ -105,7 +105,7 @@ class AllTicketsPDF(BaseExporter):
|
|||||||
return d
|
return d
|
||||||
|
|
||||||
def render(self, form_data):
|
def render(self, form_data):
|
||||||
merger = PdfFileMerger()
|
merger = PdfMerger()
|
||||||
qs = OrderPosition.objects.filter(
|
qs = OrderPosition.objects.filter(
|
||||||
order__event__in=self.events
|
order__event__in=self.events
|
||||||
).prefetch_related(
|
).prefetch_related(
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ from django.http import HttpRequest
|
|||||||
from django.template.loader import get_template
|
from django.template.loader import get_template
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from PyPDF2 import PdfFileMerger
|
from PyPDF2 import PdfMerger
|
||||||
|
|
||||||
from pretix.base.i18n import language
|
from pretix.base.i18n import language
|
||||||
from pretix.base.models import Order, OrderPosition
|
from pretix.base.models import Order, OrderPosition
|
||||||
@@ -112,7 +112,7 @@ class PdfTicketOutput(BaseTicketOutput):
|
|||||||
return renderer.render_background(buffer, _('Ticket'))
|
return renderer.render_background(buffer, _('Ticket'))
|
||||||
|
|
||||||
def generate_order(self, order: Order):
|
def generate_order(self, order: Order):
|
||||||
merger = PdfFileMerger()
|
merger = PdfMerger()
|
||||||
with language(order.locale, self.event.settings.region):
|
with language(order.locale, self.event.settings.region):
|
||||||
for op in order.positions_with_tickets:
|
for op in order.positions_with_tickets:
|
||||||
layout = override_layout.send_chained(
|
layout = override_layout.send_chained(
|
||||||
|
|||||||
@@ -221,15 +221,13 @@ class MembershipForm(forms.Form):
|
|||||||
else:
|
else:
|
||||||
types = self.position.item.require_membership_types.all()
|
types = self.position.item.require_membership_types.all()
|
||||||
|
|
||||||
initial = None
|
|
||||||
|
|
||||||
memberships = [
|
memberships = [
|
||||||
m for m in self.memberships
|
m for m in self.memberships
|
||||||
if m.is_valid(ev) and m.membership_type in types
|
if m.is_valid(ev) and m.membership_type in types
|
||||||
]
|
]
|
||||||
|
|
||||||
if len(memberships) == 1:
|
if len(memberships) == 1:
|
||||||
initial = str(memberships[0].pk)
|
self.initial['membership'] = str(memberships[0].pk)
|
||||||
|
|
||||||
self.fields['membership'] = forms.ChoiceField(
|
self.fields['membership'] = forms.ChoiceField(
|
||||||
label=_('Membership'),
|
label=_('Membership'),
|
||||||
@@ -237,7 +235,6 @@ class MembershipForm(forms.Form):
|
|||||||
(str(m.pk), self._label_from_instance(m))
|
(str(m.pk), self._label_from_instance(m))
|
||||||
for m in memberships
|
for m in memberships
|
||||||
],
|
],
|
||||||
initial=initial,
|
|
||||||
widget=forms.RadioSelect,
|
widget=forms.RadioSelect,
|
||||||
)
|
)
|
||||||
self.is_empty = not memberships
|
self.is_empty = not memberships
|
||||||
|
|||||||
@@ -58,9 +58,9 @@
|
|||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</span>
|
</span>
|
||||||
<span aria-hidden="true">{{ item.min_price|money:event.currency }} – {{ item.max_price|money:event.currency }}</span>
|
<span aria-hidden="true">{{ item.min_price|money:event.currency }} – {{ item.max_price|money:event.currency }}</span>
|
||||||
{% elif not item.min_price and not item.max_price %}
|
{% elif not item.min_price and not item.max_price and not item.mandatory_priced_addons %}
|
||||||
<span class="text-uppercase">{% trans "free" context "price" %}</span>
|
<span class="text-uppercase">{% trans "free" context "price" %}</span>
|
||||||
{% else %}
|
{% elif not item.mandatory_priced_addons %}
|
||||||
{{ item.min_price|money:event.currency }}
|
{{ item.min_price|money:event.currency }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -144,7 +144,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
{% elif not var.display_price.gross %}
|
{% elif not var.display_price.gross %}
|
||||||
<span class="text-uppercase">{% trans "free" context "price" %}</span>
|
{% if not item.mandatory_priced_addons %}
|
||||||
|
<span class="text-uppercase">{% trans "free" context "price" %}</span>
|
||||||
|
{% endif %}
|
||||||
{% elif event.settings.display_net_prices %}
|
{% elif event.settings.display_net_prices %}
|
||||||
{{ var.display_price.net|money:event.currency }}
|
{{ var.display_price.net|money:event.currency }}
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -276,7 +278,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
{% elif not item.display_price.gross %}
|
{% elif not item.display_price.gross %}
|
||||||
<span class="text-uppercase">{% trans "free" context "price" %}</span>
|
{% if not item.mandatory_priced_addons %}
|
||||||
|
<span class="text-uppercase">{% trans "free" context "price" %}</span>
|
||||||
|
{% endif %}
|
||||||
{% elif event.settings.display_net_prices %}
|
{% elif event.settings.display_net_prices %}
|
||||||
{{ item.display_price.net|money:event.currency }}
|
{{ item.display_price.net|money:event.currency }}
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@@ -27,7 +27,11 @@
|
|||||||
{% block custom_header %}
|
{% block custom_header %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
<meta property="og:title" content="{{ ev.name }}" />
|
<meta property="og:title" content="{{ ev.name }}" />
|
||||||
<meta property="og:description" content="{{ ev.get_date_range_display }}" />
|
{% if request.event.has_subevents and not subevent %}
|
||||||
|
<meta property="og:description" content="{% trans "Event series" %}" />
|
||||||
|
{% else %}
|
||||||
|
<meta property="og:description" content="{{ ev.get_date_range_display }}" />
|
||||||
|
{% endif %}
|
||||||
{% if subevent %}
|
{% if subevent %}
|
||||||
<meta property="og:url" content="{% abseventurl request.event "presale:event.index" subevent=subevent.pk %}" />
|
<meta property="og:url" content="{% abseventurl request.event "presale:event.index" subevent=subevent.pk %}" />
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@@ -93,6 +93,10 @@ event_patterns = [
|
|||||||
name='event.payment.unlock'),
|
name='event.payment.unlock'),
|
||||||
re_path(r'resend/$', pretix.presale.views.user.ResendLinkView.as_view(), name='event.resend_link'),
|
re_path(r'resend/$', pretix.presale.views.user.ResendLinkView.as_view(), name='event.resend_link'),
|
||||||
|
|
||||||
|
re_path(r'^favicon.ico/?$',
|
||||||
|
pretix.presale.views.organizer.OrganizerFavicon.as_view(),
|
||||||
|
name='event.favicon'),
|
||||||
|
|
||||||
re_path(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/open/(?P<hash>[a-z0-9]+)/$', pretix.presale.views.order.OrderOpen.as_view(),
|
re_path(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/open/(?P<hash>[a-z0-9]+)/$', pretix.presale.views.order.OrderOpen.as_view(),
|
||||||
name='event.order.open'),
|
name='event.order.open'),
|
||||||
re_path(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/$', pretix.presale.views.order.OrderDetails.as_view(),
|
re_path(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/$', pretix.presale.views.order.OrderDetails.as_view(),
|
||||||
@@ -164,6 +168,9 @@ event_patterns = [
|
|||||||
|
|
||||||
organizer_patterns = [
|
organizer_patterns = [
|
||||||
re_path(r'^$', pretix.presale.views.organizer.OrganizerIndex.as_view(), name='organizer.index'),
|
re_path(r'^$', pretix.presale.views.organizer.OrganizerIndex.as_view(), name='organizer.index'),
|
||||||
|
re_path(r'^favicon.ico/?$',
|
||||||
|
pretix.presale.views.organizer.OrganizerFavicon.as_view(),
|
||||||
|
name='organizer.favicon'),
|
||||||
re_path(r'^events/ical/$',
|
re_path(r'^events/ical/$',
|
||||||
pretix.presale.views.organizer.OrganizerIcalDownload.as_view(),
|
pretix.presale.views.organizer.OrganizerIcalDownload.as_view(),
|
||||||
name='organizer.ical'),
|
name='organizer.ical'),
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ from pretix.base.models import (
|
|||||||
)
|
)
|
||||||
from pretix.base.models.event import SubEvent
|
from pretix.base.models.event import SubEvent
|
||||||
from pretix.base.models.items import (
|
from pretix.base.models.items import (
|
||||||
ItemBundle, SubEventItem, SubEventItemVariation,
|
ItemAddOn, ItemBundle, SubEventItem, SubEventItemVariation,
|
||||||
)
|
)
|
||||||
from pretix.base.services.quotas import QuotaAvailability
|
from pretix.base.services.quotas import QuotaAvailability
|
||||||
from pretix.helpers.compat import date_fromisocalendar
|
from pretix.helpers.compat import date_fromisocalendar
|
||||||
@@ -183,6 +183,13 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require
|
|||||||
subevent=subevent,
|
subevent=subevent,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
mandatory_priced_addons=Exists(
|
||||||
|
ItemAddOn.objects.filter(
|
||||||
|
base_item_id=OuterRef('pk'),
|
||||||
|
min_count__gte=1,
|
||||||
|
price_included=False
|
||||||
|
)
|
||||||
|
),
|
||||||
requires_seat=requires_seat,
|
requires_seat=requires_seat,
|
||||||
).filter(
|
).filter(
|
||||||
quotac__gt=0, subevent_disabled=False,
|
quotac__gt=0, subevent_disabled=False,
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ from django.db.models import Exists, Max, Min, OuterRef, Prefetch, Q
|
|||||||
from django.db.models.functions import Coalesce, Greatest
|
from django.db.models.functions import Coalesce, Greatest
|
||||||
from django.http import Http404, HttpResponse
|
from django.http import Http404, HttpResponse
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
|
from django.templatetags.static import static
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.formats import date_format, get_format
|
from django.utils.formats import date_format, get_format
|
||||||
from django.utils.timezone import get_current_timezone, now
|
from django.utils.timezone import get_current_timezone, now
|
||||||
@@ -66,6 +67,7 @@ from pretix.helpers.daterange import daterange
|
|||||||
from pretix.helpers.formats.en.formats import (
|
from pretix.helpers.formats.en.formats import (
|
||||||
SHORT_MONTH_DAY_FORMAT, WEEK_FORMAT,
|
SHORT_MONTH_DAY_FORMAT, WEEK_FORMAT,
|
||||||
)
|
)
|
||||||
|
from pretix.helpers.thumb import get_thumbnail
|
||||||
from pretix.multidomain.urlreverse import eventreverse
|
from pretix.multidomain.urlreverse import eventreverse
|
||||||
from pretix.presale.ical import get_public_ical
|
from pretix.presale.ical import get_public_ical
|
||||||
from pretix.presale.views import OrganizerViewMixin
|
from pretix.presale.views import OrganizerViewMixin
|
||||||
@@ -1170,3 +1172,11 @@ class OrganizerIcalDownload(OrganizerViewMixin, View):
|
|||||||
if request.organizer.settings.meta_noindex:
|
if request.organizer.settings.meta_noindex:
|
||||||
resp['X-Robots-Tag'] = 'noindex'
|
resp['X-Robots-Tag'] = 'noindex'
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
class OrganizerFavicon(View):
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
if self.request.organizer.settings.favicon:
|
||||||
|
return redirect(get_thumbnail(self.request.organizer.settings.favicon, '32x32^').thumb.url)
|
||||||
|
else:
|
||||||
|
return redirect(static("pretixbase/img/favicon.ico"))
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ PRETIX_SESSION_TIMEOUT_RELATIVE = 3600 * 3
|
|||||||
PRETIX_SESSION_TIMEOUT_ABSOLUTE = 3600 * 12
|
PRETIX_SESSION_TIMEOUT_ABSOLUTE = 3600 * 12
|
||||||
PRETIX_PRIMARY_COLOR = '#8E44B3'
|
PRETIX_PRIMARY_COLOR = '#8E44B3'
|
||||||
|
|
||||||
SITE_URL = config.get('pretix', 'url', fallback='http://localhost')
|
SITE_URL = config.get('pretix', 'url', fallback='http://localhost:8000')
|
||||||
if SITE_URL.endswith('/'):
|
if SITE_URL.endswith('/'):
|
||||||
SITE_URL = SITE_URL[:-1]
|
SITE_URL = SITE_URL[:-1]
|
||||||
|
|
||||||
|
|||||||
+710
-688
File diff suppressed because it is too large
Load Diff
@@ -4,13 +4,13 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.18.6",
|
"@babel/core": "^7.18.10",
|
||||||
"@babel/preset-env": "^7.18.6",
|
"@babel/preset-env": "^7.18.9",
|
||||||
"@rollup/plugin-babel": "^5.3.1",
|
"@rollup/plugin-babel": "^5.3.1",
|
||||||
"@rollup/plugin-node-resolve": "^13.3.0",
|
"@rollup/plugin-node-resolve": "^13.3.0",
|
||||||
"vue": "^2.7.0",
|
"vue": "^2.7.8",
|
||||||
"rollup": "^2.75.7",
|
"rollup": "^2.77.2",
|
||||||
"rollup-plugin-vue": "^5.0.1",
|
"rollup-plugin-vue": "^5.0.1",
|
||||||
"vue-template-compiler": "^2.7.0"
|
"vue-template-compiler": "^2.7.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -300,7 +300,6 @@ var editor = {
|
|||||||
|
|
||||||
// Fetch the required page
|
// Fetch the required page
|
||||||
editor.pdf.getPage(page_number).then(function (page) {
|
editor.pdf.getPage(page_number).then(function (page) {
|
||||||
console.log('Page loaded');
|
|
||||||
var canvas = document.getElementById('pdf-canvas');
|
var canvas = document.getElementById('pdf-canvas');
|
||||||
|
|
||||||
var scale = editor.$cva.width() / page.getViewport(1.0).width;
|
var scale = editor.$cva.width() / page.getViewport(1.0).width;
|
||||||
@@ -326,7 +325,6 @@ var editor = {
|
|||||||
editor.pdf_page_number = page_number
|
editor.pdf_page_number = page_number
|
||||||
editor._init_page_nav();
|
editor._init_page_nav();
|
||||||
|
|
||||||
console.log('Page rendered');
|
|
||||||
if (dump || !editor._fabric_loaded) {
|
if (dump || !editor._fabric_loaded) {
|
||||||
editor._init_fabric(dump);
|
editor._init_fabric(dump);
|
||||||
} else {
|
} else {
|
||||||
@@ -349,7 +347,6 @@ var editor = {
|
|||||||
}
|
}
|
||||||
$("#page_nav").append($li)
|
$("#page_nav").append($li)
|
||||||
$a.on("click", function (event) {
|
$a.on("click", function (event) {
|
||||||
console.log("switch to page", $(this).attr("data-page"));
|
|
||||||
editor.fabric.deactivateAll();
|
editor.fabric.deactivateAll();
|
||||||
editor._load_page(parseInt($(this).attr("data-page")));
|
editor._load_page(parseInt($(this).attr("data-page")));
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -369,7 +366,6 @@ var editor = {
|
|||||||
// Asynchronous download of PDF
|
// Asynchronous download of PDF
|
||||||
var loadingTask = PDFJS.getDocument(url);
|
var loadingTask = PDFJS.getDocument(url);
|
||||||
loadingTask.promise.then(function (pdf) {
|
loadingTask.promise.then(function (pdf) {
|
||||||
console.log('PDF loaded');
|
|
||||||
|
|
||||||
editor.pdf = pdf;
|
editor.pdf = pdf;
|
||||||
editor.pdf_page_count = pdf.numPages;
|
editor.pdf_page_count = pdf.numPages;
|
||||||
@@ -422,7 +418,6 @@ var editor = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
editor._fabric_loaded = true;
|
editor._fabric_loaded = true;
|
||||||
console.log("Fabric loaded");
|
|
||||||
if (editor._window_loaded) {
|
if (editor._window_loaded) {
|
||||||
editor._ready();
|
editor._ready();
|
||||||
}
|
}
|
||||||
@@ -430,7 +425,6 @@ var editor = {
|
|||||||
|
|
||||||
_window_load_event: function () {
|
_window_load_event: function () {
|
||||||
editor._window_loaded = true;
|
editor._window_loaded = true;
|
||||||
console.log("Window loaded");
|
|
||||||
if (editor._fabric_loaded) {
|
if (editor._fabric_loaded) {
|
||||||
editor._ready();
|
editor._ready();
|
||||||
}
|
}
|
||||||
@@ -520,7 +514,7 @@ var editor = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_update_values_from_toolbox: function () {
|
_update_values_from_toolbox: function (e) {
|
||||||
var o = editor.fabric.getActiveObject();
|
var o = editor.fabric.getActiveObject();
|
||||||
if (!o) {
|
if (!o) {
|
||||||
o = editor.fabric.getActiveGroup();
|
o = editor.fabric.getActiveGroup();
|
||||||
@@ -553,8 +547,16 @@ var editor = {
|
|||||||
$("#toolbox-content-other-help").toggle($("#toolbox-content").val() === "other" || $("#toolbox-content").val() === "other_i18n");
|
$("#toolbox-content-other-help").toggle($("#toolbox-content").val() === "other" || $("#toolbox-content").val() === "other_i18n");
|
||||||
o.content = $("#toolbox-content").val();
|
o.content = $("#toolbox-content").val();
|
||||||
if ($("#toolbox-content").val() === "other") {
|
if ($("#toolbox-content").val() === "other") {
|
||||||
|
if (e.target.id === "toolbox-content") {
|
||||||
|
// user used dropdown to switch content-type, update value with value from i18n textarea
|
||||||
|
$("#toolbox-content-other").val($("#toolbox-content-other-i18n textarea").val());
|
||||||
|
}
|
||||||
o.text = $("#toolbox-content-other").val();
|
o.text = $("#toolbox-content-other").val();
|
||||||
} else if ($("#toolbox-content").val() === "other_i18n") {
|
} else if ($("#toolbox-content").val() === "other_i18n") {
|
||||||
|
if (e.target.id === "toolbox-content") {
|
||||||
|
// user used dropdown to switch content-type, update value with value from "other" textarea
|
||||||
|
$("#toolbox-content-other-i18n textarea").val($("#toolbox-content-other").val());
|
||||||
|
}
|
||||||
o.text_i18n = {}
|
o.text_i18n = {}
|
||||||
$("#toolbox-content-other-i18n textarea").each(function () {
|
$("#toolbox-content-other-i18n textarea").each(function () {
|
||||||
o.text_i18n[$(this).attr("lang")] = $(this).val();
|
o.text_i18n[$(this).attr("lang")] = $(this).val();
|
||||||
@@ -608,8 +610,16 @@ var editor = {
|
|||||||
$("#toolbox-content-other-help").toggle($("#toolbox-content").val() === "other" || $("#toolbox-content").val() === "other_i18n");
|
$("#toolbox-content-other-help").toggle($("#toolbox-content").val() === "other" || $("#toolbox-content").val() === "other_i18n");
|
||||||
o.content = $("#toolbox-content").val();
|
o.content = $("#toolbox-content").val();
|
||||||
if ($("#toolbox-content").val() === "other") {
|
if ($("#toolbox-content").val() === "other") {
|
||||||
|
if (e.target.id === "toolbox-content") {
|
||||||
|
// user used dropdown to switch content-type, update value with value from i18n textarea
|
||||||
|
$("#toolbox-content-other").val($("#toolbox-content-other-i18n textarea").val());
|
||||||
|
}
|
||||||
o.setText($("#toolbox-content-other").val());
|
o.setText($("#toolbox-content-other").val());
|
||||||
} else if ($("#toolbox-content").val() === "other_i18n") {
|
} else if ($("#toolbox-content").val() === "other_i18n") {
|
||||||
|
if (e.target.id === "toolbox-content") {
|
||||||
|
// user used dropdown to switch content-type, update value with value from "other" textarea
|
||||||
|
$("#toolbox-content-other-i18n textarea").val($("#toolbox-content-other").val());
|
||||||
|
}
|
||||||
o.text_i18n = {}
|
o.text_i18n = {}
|
||||||
$("#toolbox-content-other-i18n textarea").each(function () {
|
$("#toolbox-content-other-i18n textarea").each(function () {
|
||||||
o.text_i18n[$(this).attr("lang")] = $(this).val();
|
o.text_i18n[$(this).attr("lang")] = $(this).val();
|
||||||
@@ -620,6 +630,14 @@ var editor = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// empty text-inputs if not in use
|
||||||
|
if ($("#toolbox-content").val() !== "other") {
|
||||||
|
$("#toolbox-content-other").val("");
|
||||||
|
}
|
||||||
|
if ($("#toolbox-content").val() !== "other_i18n") {
|
||||||
|
$("#toolbox-content-other-i18n textarea").val("");
|
||||||
|
}
|
||||||
|
|
||||||
o.setCoords();
|
o.setCoords();
|
||||||
editor.fabric.renderAll();
|
editor.fabric.renderAll();
|
||||||
},
|
},
|
||||||
@@ -1055,14 +1073,14 @@ var editor = {
|
|||||||
$("#toolbox label.btn").bind('click change', editor._update_values_from_toolbox);
|
$("#toolbox label.btn").bind('click change', editor._update_values_from_toolbox);
|
||||||
$("#toolbox select").bind('change', editor._update_values_from_toolbox);
|
$("#toolbox select").bind('change', editor._update_values_from_toolbox);
|
||||||
$("#toolbox select").bind('change', editor._create_savepoint);
|
$("#toolbox select").bind('change', editor._create_savepoint);
|
||||||
$("#toolbox button.toggling").bind('click change', function () {
|
$("#toolbox button.toggling").bind('click change', function (e) {
|
||||||
if ($(this).is(".option")) {
|
if ($(this).is(".option")) {
|
||||||
$(this).addClass("active");
|
$(this).addClass("active");
|
||||||
$(this).parent().siblings().find("button").removeClass("active");
|
$(this).parent().siblings().find("button").removeClass("active");
|
||||||
} else {
|
} else {
|
||||||
$(this).toggleClass("active");
|
$(this).toggleClass("active");
|
||||||
}
|
}
|
||||||
editor._update_values_from_toolbox();
|
editor._update_values_from_toolbox(e);
|
||||||
editor._create_savepoint();
|
editor._create_savepoint();
|
||||||
});
|
});
|
||||||
$("#toolbox .colorpickerfield").bind('changeColor', editor._update_values_from_toolbox);
|
$("#toolbox .colorpickerfield").bind('changeColor', editor._update_values_from_toolbox);
|
||||||
|
|||||||
@@ -665,33 +665,8 @@ var form_handlers = function (el) {
|
|||||||
questions_init_photos(el);
|
questions_init_photos(el);
|
||||||
};
|
};
|
||||||
|
|
||||||
$(function () {
|
function setup_basics(el) {
|
||||||
"use strict";
|
el.find("#sumtoggle").find("button").click(function () {
|
||||||
|
|
||||||
$("body").removeClass("nojs");
|
|
||||||
lightbox.init();
|
|
||||||
|
|
||||||
$(document).on("click", ".variations .variations-select-all", function (e) {
|
|
||||||
$(this).parent().parent().find("input[type=checkbox]").prop("checked", true).change();
|
|
||||||
e.stopPropagation();
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
$(document).on("click", ".variations .variations-select-none", function (e) {
|
|
||||||
$(this).parent().parent().find("input[type=checkbox]").prop("checked", false).change();
|
|
||||||
e.stopPropagation();
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
if ($(".items-on-quota").length) {
|
|
||||||
$(".items-on-quota .panel").each(function () {
|
|
||||||
var $panel = $(this);
|
|
||||||
$panel.toggleClass("panel-success", $panel.find("input:checked").length > 0);
|
|
||||||
$(this).find("input").change(function () {
|
|
||||||
$panel.toggleClass("panel-success", $panel.find("input:checked").length > 0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$("#sumtoggle").find("button").click(function () {
|
|
||||||
$(".table-product-overview .sum-gross").toggle($(this).attr("data-target") === ".sum-gross");
|
$(".table-product-overview .sum-gross").toggle($(this).attr("data-target") === ".sum-gross");
|
||||||
$(".table-product-overview .sum-net").toggle($(this).attr("data-target") === ".sum-net");
|
$(".table-product-overview .sum-net").toggle($(this).attr("data-target") === ".sum-net");
|
||||||
$(".table-product-overview .count").toggle($(this).attr("data-target") === ".count");
|
$(".table-product-overview .count").toggle($(this).attr("data-target") === ".count");
|
||||||
@@ -700,18 +675,18 @@ $(function () {
|
|||||||
$(this).addClass("active");
|
$(this).addClass("active");
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.collapsible').collapse();
|
el.find('.collapsible').collapse();
|
||||||
$("input[data-toggle=radiocollapse]").change(function () {
|
el.find("input[data-toggle=radiocollapse]").change(function () {
|
||||||
$($(this).attr("data-parent")).find(".collapse.in").collapse('hide');
|
$($(this).attr("data-parent")).find(".collapse.in").collapse('hide');
|
||||||
$($(this).attr("data-target")).collapse('show');
|
$($(this).attr("data-target")).collapse('show');
|
||||||
});
|
});
|
||||||
$("div.collapsed").removeClass("collapsed").addClass("collapse");
|
el.find("div.collapsed").removeClass("collapsed").addClass("collapse");
|
||||||
$(".has-error").each(function () {
|
el.find(".has-error").each(function () {
|
||||||
$(this).closest("div.panel-collapse").collapse("show");
|
$(this).closest("div.panel-collapse").collapse("show");
|
||||||
});
|
});
|
||||||
|
|
||||||
$('[data-toggle="tooltip"]').tooltip();
|
el.find('[data-toggle="tooltip"]').tooltip();
|
||||||
$('[data-toggle="tooltip_html"]').tooltip({
|
el.find('[data-toggle="tooltip_html"]').tooltip({
|
||||||
'html': true,
|
'html': true,
|
||||||
'whiteList': {
|
'whiteList': {
|
||||||
// Global attributes allowed on any supplied element below.
|
// Global attributes allowed on any supplied element below.
|
||||||
@@ -733,7 +708,7 @@ $(function () {
|
|||||||
if (url.match('#')) {
|
if (url.match('#')) {
|
||||||
$('.nav-tabs a[href="#' + url.split('#')[1] + '"]').tab('show');
|
$('.nav-tabs a[href="#' + url.split('#')[1] + '"]').tab('show');
|
||||||
}
|
}
|
||||||
$('a[data-toggle="tab"]').on('click', function (e) {
|
el.find('a[data-toggle="tab"]').on('click', function (e) {
|
||||||
if (!$(this).closest(".tab-content").length) {
|
if (!$(this).closest(".tab-content").length) {
|
||||||
// only append hash if not inside a .panel
|
// only append hash if not inside a .panel
|
||||||
window.location.hash = this.hash;
|
window.location.hash = this.hash;
|
||||||
@@ -741,7 +716,7 @@ $(function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Event wizard
|
// Event wizard
|
||||||
$("#event-slug-random-generate").click(function () {
|
el.find("#event-slug-random-generate").click(function () {
|
||||||
var url = $(this).attr("data-rng-url");
|
var url = $(this).attr("data-rng-url");
|
||||||
$("#id_basics-slug").val("Generating...");
|
$("#id_basics-slug").val("Generating...");
|
||||||
$.getJSON(url, function (data) {
|
$.getJSON(url, function (data) {
|
||||||
@@ -749,10 +724,7 @@ $(function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
form_handlers($("body"));
|
el.find(".qrcode-canvas").each(function () {
|
||||||
$(document).trigger("pretix:bind-forms");
|
|
||||||
|
|
||||||
$(".qrcode-canvas").each(function () {
|
|
||||||
$(this).qrcode(
|
$(this).qrcode(
|
||||||
{
|
{
|
||||||
text: $.trim($($(this).attr("data-qrdata")).html())
|
text: $.trim($($(this).attr("data-qrdata")).html())
|
||||||
@@ -760,10 +732,10 @@ $(function () {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
$(".propagated-settings-box").find("input, textarea, select").not("[readonly]")
|
el.find(".propagated-settings-box").find("input, textarea, select").not("[readonly]")
|
||||||
.attr("data-propagated-locked", "true").prop("readonly", true);
|
.attr("data-propagated-locked", "true").prop("readonly", true);
|
||||||
|
|
||||||
$(".propagated-settings-box button[data-action=unlink]").click(function (ev) {
|
el.find(".propagated-settings-box button[data-action=unlink]").click(function (ev) {
|
||||||
var $box = $(this).closest(".propagated-settings-box");
|
var $box = $(this).closest(".propagated-settings-box");
|
||||||
$box.find("input[name=decouple]").val($(this).val());
|
$box.find("input[name=decouple]").val($(this).val());
|
||||||
$box.find("[data-propagated-locked]").prop("readonly", false);
|
$box.find("[data-propagated-locked]").prop("readonly", false);
|
||||||
@@ -773,7 +745,7 @@ $(function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Tables with bulk selection, e.g. subevent list
|
// Tables with bulk selection, e.g. subevent list
|
||||||
$("input[data-toggle-table]").each(function (ev) {
|
el.find("input[data-toggle-table]").each(function (ev) {
|
||||||
var $toggle = $(this);
|
var $toggle = $(this);
|
||||||
var $actionButtons = $(".batch-select-actions button", this.form);
|
var $actionButtons = $(".batch-select-actions button", this.form);
|
||||||
var countLabels = $("<span></span>").appendTo($actionButtons);
|
var countLabels = $("<span></span>").appendTo($actionButtons);
|
||||||
@@ -852,7 +824,7 @@ $(function () {
|
|||||||
if (!nrOfChecked) countLabels.empty();
|
if (!nrOfChecked) countLabels.empty();
|
||||||
else countLabels.text(" ("+nrOfChecked+")");
|
else countLabels.text(" ("+nrOfChecked+")");
|
||||||
|
|
||||||
if (!allChecked) $selectAll.find("input").prop("checked", false);
|
if (!allChecked) $selectAll.find("input").prop("checked", false);
|
||||||
|
|
||||||
$actionButtons.attr("disabled", !nrOfChecked);
|
$actionButtons.attr("disabled", !nrOfChecked);
|
||||||
$toggle.prop("checked", allChecked).prop("indeterminate", nrOfChecked > 0 && !allChecked);
|
$toggle.prop("checked", allChecked).prop("indeterminate", nrOfChecked > 0 && !allChecked);
|
||||||
@@ -875,7 +847,7 @@ $(function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Items and categories
|
// Items and categories
|
||||||
$(".internal-name-wrapper").each(function () {
|
el.find(".internal-name-wrapper").each(function () {
|
||||||
if ($(this).find("input").val() === "") {
|
if ($(this).find("input").val() === "") {
|
||||||
var $fg = $(this).find(".form-group");
|
var $fg = $(this).find(".form-group");
|
||||||
$fg.hide();
|
$fg.hide();
|
||||||
@@ -896,7 +868,7 @@ $(function () {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$("button[data-toggle=qrcode]").click(function (e) {
|
el.find("button[data-toggle=qrcode]").click(function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var $current = $(".qr-code-overlay[data-qrcode='" + $(this).attr("data-qrcode") + "']");
|
var $current = $(".qr-code-overlay[data-qrcode='" + $(this).attr("data-qrcode") + "']");
|
||||||
if ($current.length) {
|
if ($current.length) {
|
||||||
@@ -926,6 +898,37 @@ $(function () {
|
|||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
$("body").removeClass("nojs");
|
||||||
|
lightbox.init();
|
||||||
|
|
||||||
|
$(document).on("click", ".variations .variations-select-all", function (e) {
|
||||||
|
$(this).parent().parent().find("input[type=checkbox]").prop("checked", true).change();
|
||||||
|
e.stopPropagation();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
$(document).on("click", ".variations .variations-select-none", function (e) {
|
||||||
|
$(this).parent().parent().find("input[type=checkbox]").prop("checked", false).change();
|
||||||
|
e.stopPropagation();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
if ($(".items-on-quota").length) {
|
||||||
|
$(".items-on-quota .panel").each(function () {
|
||||||
|
var $panel = $(this);
|
||||||
|
$panel.toggleClass("panel-success", $panel.find("input:checked").length > 0);
|
||||||
|
$(this).find("input").change(function () {
|
||||||
|
$panel.toggleClass("panel-success", $panel.find("input:checked").length > 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_basics($("body"));
|
||||||
|
form_handlers($("body"));
|
||||||
|
$(document).trigger("pretix:bind-forms");
|
||||||
|
|
||||||
$("#ajaxerr").on("click", ".ajaxerr-close", ajaxErrDialog.hide);
|
$("#ajaxerr").on("click", ".ajaxerr-close", ajaxErrDialog.hide);
|
||||||
moment.locale($("body").attr("data-datetimelocale"));
|
moment.locale($("body").attr("data-datetimelocale"));
|
||||||
|
|||||||
@@ -12,13 +12,8 @@ $(function () {
|
|||||||
var $query = $(this).find('[data-typeahead-query]').length ? $(this).find('[data-typeahead-query]') : $($(this).attr("data-typeahead-field"));
|
var $query = $(this).find('[data-typeahead-query]').length ? $(this).find('[data-typeahead-query]') : $($(this).attr("data-typeahead-field"));
|
||||||
$container.find("li:not(.query-holder)").remove();
|
$container.find("li:not(.query-holder)").remove();
|
||||||
var lastQuery = "";
|
var lastQuery = "";
|
||||||
|
var runQueryTimeout = null;
|
||||||
$query.on("change", function () {
|
function runQuery() {
|
||||||
if ($container.attr("data-typeahead-field") && $query.val() === "") {
|
|
||||||
$container.removeClass('focused');
|
|
||||||
$container.find("li:not(.query-holder)").remove();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
lastQuery = $query.val();
|
lastQuery = $query.val();
|
||||||
var thisQuery = $query.val();
|
var thisQuery = $query.val();
|
||||||
$.getJSON(
|
$.getJSON(
|
||||||
@@ -119,6 +114,17 @@ $(function () {
|
|||||||
$container.toggleClass('focused', $query.is(":focus") && $container.children().length > 0);
|
$container.toggleClass('focused', $query.is(":focus") && $container.children().length > 0);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
$query.on("change", function () {
|
||||||
|
if ($container.attr("data-typeahead-field") && $query.val() === "") {
|
||||||
|
$container.removeClass('focused');
|
||||||
|
$container.find("li:not(.query-holder)").remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (runQueryTimeout != null) {
|
||||||
|
window.clearTimeout(runQueryTimeout)
|
||||||
|
}
|
||||||
|
runQueryTimeout = window.setTimeout(runQuery, 250)
|
||||||
});
|
});
|
||||||
$query.on("keydown", function (event) {
|
$query.on("keydown", function (event) {
|
||||||
var $selected = $container.find(".active");
|
var $selected = $container.find(".active");
|
||||||
|
|||||||
@@ -191,6 +191,7 @@ function setup_basics(el) {
|
|||||||
else $(":input", this).get(0).focus();
|
else $(":input", this).get(0).focus();
|
||||||
});
|
});
|
||||||
el.find(".alert-danger").first().each(function() {
|
el.find(".alert-danger").first().each(function() {
|
||||||
|
var container = this;
|
||||||
var content = $("<ul></ul>").click(function(e) {
|
var content = $("<ul></ul>").click(function(e) {
|
||||||
var input = $(e.target.hash).get(0);
|
var input = $(e.target.hash).get(0);
|
||||||
if (input) input.focus();
|
if (input) input.focus();
|
||||||
@@ -199,13 +200,14 @@ function setup_basics(el) {
|
|||||||
});
|
});
|
||||||
$(".has-error").each(function() {
|
$(".has-error").each(function() {
|
||||||
var target = target = $(":input", this);
|
var target = target = $(":input", this);
|
||||||
if (!target || !target.attr("aria-describedby")) return;
|
var desc = target && target.attr("aria-describedby") ? document.getElementById(target.attr("aria-describedby").split(' ', 1)[0]) : null;
|
||||||
var desc = $("#" + target.attr("aria-describedby").split(' ', 1)[0]);
|
if (!target || !desc || desc == container) return;
|
||||||
|
|
||||||
// multi-input fields have a role=group with aria-labelledby
|
// multi-input fields have a role=group with aria-labelledby
|
||||||
var label = this.hasAttribute("aria-labelledby") ? $("#" + this.getAttribute("aria-labelledby")) : $("[for="+target.attr("id")+"]");
|
var label = this.hasAttribute("aria-labelledby") ? $("#" + this.getAttribute("aria-labelledby")) : $("[for="+target.attr("id")+"]");
|
||||||
|
|
||||||
var $li = $("<li>");
|
var $li = $("<li>");
|
||||||
$li.text(": " + desc.text())
|
$li.text(": " + desc.textContent)
|
||||||
$li.prepend($("<a>").attr("href", "#" + target.attr("id")).text(label.get(0).childNodes[0].nodeValue))
|
$li.prepend($("<a>").attr("href", "#" + target.attr("id")).text(label.get(0).childNodes[0].nodeValue))
|
||||||
content.append($li);
|
content.append($li);
|
||||||
});
|
});
|
||||||
|
|||||||
+6
-6
@@ -199,7 +199,7 @@ setup(
|
|||||||
'markdown==3.3.4', # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
|
'markdown==3.3.4', # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
|
||||||
# We can upgrade markdown again once django-bootstrap3 upgrades or once we drop Python 3.6 and 3.7
|
# We can upgrade markdown again once django-bootstrap3 upgrades or once we drop Python 3.6 and 3.7
|
||||||
'mt-940==4.23.*',
|
'mt-940==4.23.*',
|
||||||
'oauthlib==3.1.*',
|
'oauthlib==3.2.*',
|
||||||
'openpyxl==3.0.*',
|
'openpyxl==3.0.*',
|
||||||
'packaging',
|
'packaging',
|
||||||
'paypalrestsdk==1.13.*',
|
'paypalrestsdk==1.13.*',
|
||||||
@@ -211,7 +211,7 @@ setup(
|
|||||||
'psycopg2-binary',
|
'psycopg2-binary',
|
||||||
'pycountry',
|
'pycountry',
|
||||||
'pycparser==2.21',
|
'pycparser==2.21',
|
||||||
'PyPDF2==1.27.9',
|
'PyPDF2==2.9.*',
|
||||||
'python-bidi==0.4.*', # Support for Arabic in reportlab
|
'python-bidi==0.4.*', # Support for Arabic in reportlab
|
||||||
'python-dateutil==2.8.*',
|
'python-dateutil==2.8.*',
|
||||||
'python-u2flib-server==4.*',
|
'python-u2flib-server==4.*',
|
||||||
@@ -248,11 +248,11 @@ setup(
|
|||||||
'pytest-cache',
|
'pytest-cache',
|
||||||
'pytest-cov',
|
'pytest-cov',
|
||||||
'pytest-django==4.*',
|
'pytest-django==4.*',
|
||||||
'pytest-mock>=2.0,<3.7',
|
'pytest-mock==3.8.*',
|
||||||
'pytest-rerunfailures>=9,<11',
|
'pytest-rerunfailures==10.2',
|
||||||
'pytest-sugar',
|
'pytest-sugar',
|
||||||
'pytest-xdist==1.31.*',
|
'pytest-xdist==2.5.*',
|
||||||
'pytest==6.*',
|
'pytest==7.*',
|
||||||
'responses',
|
'responses',
|
||||||
],
|
],
|
||||||
'memcached': ['pylibmc'],
|
'memcached': ['pylibmc'],
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ def test_customer_create(token_client, organizer):
|
|||||||
data={
|
data={
|
||||||
'identifier': 'IGNORED',
|
'identifier': 'IGNORED',
|
||||||
'email': 'bar@example.com',
|
'email': 'bar@example.com',
|
||||||
|
'password': 'foobar',
|
||||||
'name_parts': {
|
'name_parts': {
|
||||||
"_scheme": "given_family",
|
"_scheme": "given_family",
|
||||||
'given_name': 'John',
|
'given_name': 'John',
|
||||||
@@ -99,6 +100,7 @@ def test_customer_create(token_client, organizer):
|
|||||||
assert customer.is_active
|
assert customer.is_active
|
||||||
assert customer.name == 'John Doe'
|
assert customer.name == 'John Doe'
|
||||||
assert customer.is_verified
|
assert customer.is_verified
|
||||||
|
assert customer.check_password('foobar')
|
||||||
assert len(djmail.outbox) == 0
|
assert len(djmail.outbox) == 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ def inputfile_factory():
|
|||||||
'D': 'Test',
|
'D': 'Test',
|
||||||
'E': 'Baz',
|
'E': 'Baz',
|
||||||
'F': '0.00',
|
'F': '0.00',
|
||||||
'G': 'AU',
|
'G': 'XK',
|
||||||
'H': '',
|
'H': '',
|
||||||
'I': 'Foo,Bar',
|
'I': 'Foo,Bar',
|
||||||
'J': '2021-06-28 11:00:00',
|
'J': '2021-06-28 11:00:00',
|
||||||
|
|||||||
@@ -0,0 +1,198 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pretix (Community Edition).
|
||||||
|
#
|
||||||
|
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||||
|
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||||
|
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||||
|
#
|
||||||
|
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||||
|
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||||
|
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||||
|
# this file, see <https://pretix.eu/about/en/license>.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||||
|
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# 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/>.
|
||||||
|
#
|
||||||
|
import pytest
|
||||||
|
import responses
|
||||||
|
from requests import Timeout
|
||||||
|
|
||||||
|
from pretix.base.services.tax import (
|
||||||
|
VATIDFinalError, VATIDTemporaryError, validate_vat_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_unknown_country():
|
||||||
|
with pytest.raises(VATIDTemporaryError):
|
||||||
|
validate_vat_id('TR12345', 'TR')
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
def test_eu_invalid_format():
|
||||||
|
with pytest.raises(VATIDFinalError):
|
||||||
|
validate_vat_id('AT12345', 'AT')
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
def test_eu_no_prefix():
|
||||||
|
with pytest.raises(VATIDFinalError):
|
||||||
|
validate_vat_id('12345', 'AT')
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
def test_eu_country_mismatch():
|
||||||
|
with pytest.raises(VATIDFinalError):
|
||||||
|
validate_vat_id('AT12345', 'DE')
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
def test_eu_server_down():
|
||||||
|
def _callback(request):
|
||||||
|
raise Timeout
|
||||||
|
|
||||||
|
responses.add_callback(
|
||||||
|
responses.POST,
|
||||||
|
'https://ec.europa.eu/taxation_customs/vies/services/checkVatService',
|
||||||
|
callback=_callback
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(VATIDTemporaryError):
|
||||||
|
validate_vat_id('ATU36801500', 'AT')
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
def test_eu_server_error():
|
||||||
|
responses.add(
|
||||||
|
responses.POST,
|
||||||
|
'https://ec.europa.eu/taxation_customs/vies/services/checkVatService',
|
||||||
|
body='error',
|
||||||
|
status=500
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(VATIDTemporaryError):
|
||||||
|
validate_vat_id('ATU36801500', 'AT')
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
def test_eu_id_invalid():
|
||||||
|
responses.add(
|
||||||
|
responses.POST,
|
||||||
|
'https://ec.europa.eu/taxation_customs/vies/services/checkVatService',
|
||||||
|
body="""<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
|
||||||
|
<soap:Body>
|
||||||
|
<checkVatResponse xmlns="urn:ec.europa.eu:taxud:vies:services:checkVat:types">
|
||||||
|
<countryCode>AT</countryCode>
|
||||||
|
<vatNumber>U36801500</vatNumber>
|
||||||
|
<requestDate>2014-12-17+01:00</requestDate>
|
||||||
|
<valid>false</valid>
|
||||||
|
<name>STADT WIEN</name>
|
||||||
|
<address>UNKNOWN</address>
|
||||||
|
</checkVatResponse>
|
||||||
|
</soap:Body>
|
||||||
|
</soap:Envelope>""",
|
||||||
|
status=200
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(VATIDFinalError):
|
||||||
|
validate_vat_id('ATU36801500', 'AT')
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
def test_eu_id_valid():
|
||||||
|
responses.add(
|
||||||
|
responses.POST,
|
||||||
|
'https://ec.europa.eu/taxation_customs/vies/services/checkVatService',
|
||||||
|
body="""<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
|
||||||
|
<soap:Body>
|
||||||
|
<checkVatResponse xmlns="urn:ec.europa.eu:taxud:vies:services:checkVat:types">
|
||||||
|
<countryCode>AT</countryCode>
|
||||||
|
<vatNumber>U36801500</vatNumber>
|
||||||
|
<requestDate>2014-12-17+01:00</requestDate>
|
||||||
|
<valid>true</valid>
|
||||||
|
<name>STADT WIEN</name>
|
||||||
|
<address>UNKNOWN</address>
|
||||||
|
</checkVatResponse>
|
||||||
|
</soap:Body>
|
||||||
|
</soap:Envelope>""",
|
||||||
|
status=200
|
||||||
|
)
|
||||||
|
|
||||||
|
assert validate_vat_id('ATU36801500', 'AT') == 'ATU36801500'
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
def test_NO_invalid_format():
|
||||||
|
with pytest.raises(VATIDFinalError):
|
||||||
|
validate_vat_id('NO12345', 'NO')
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
def test_NO_server_down():
|
||||||
|
def _callback(request):
|
||||||
|
raise Timeout
|
||||||
|
|
||||||
|
responses.add_callback(
|
||||||
|
responses.GET,
|
||||||
|
'https://data.brreg.no/enhetsregisteret/api/enheter/974760673',
|
||||||
|
callback=_callback
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(VATIDTemporaryError):
|
||||||
|
validate_vat_id('NO974760673 MVA', 'NO')
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
def test_NO_server_error():
|
||||||
|
responses.add(
|
||||||
|
responses.GET,
|
||||||
|
'https://data.brreg.no/enhetsregisteret/api/enheter/974760673',
|
||||||
|
body='error',
|
||||||
|
status=500
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(VATIDTemporaryError):
|
||||||
|
validate_vat_id('NO974760673 MVA', 'NO')
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
def test_NO_id_invalid():
|
||||||
|
responses.add(
|
||||||
|
responses.GET,
|
||||||
|
'https://data.brreg.no/enhetsregisteret/api/enheter/974760673',
|
||||||
|
body="",
|
||||||
|
status=404
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(VATIDFinalError):
|
||||||
|
validate_vat_id('NO974760673 MVA', 'NO')
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
def test_NO_id_valid():
|
||||||
|
responses.add(
|
||||||
|
responses.GET,
|
||||||
|
'https://data.brreg.no/enhetsregisteret/api/enheter/974760673',
|
||||||
|
body='{"organisasjonsnummer":"974760673","navn":"REGISTERENHETEN I BRØNNØYSUND","organisasjonsform":{"kode":'
|
||||||
|
'"ORGL","beskrivelse":"Organisasjonsledd","_links":{"self":{"href":"https://data.brreg.no/enhetsregisteret/api/'
|
||||||
|
'organisasjonsformer/ORGL"}}},"hjemmeside":"www.brreg.no","postadresse":{"land":"Norge","landkode":"NO","postn'
|
||||||
|
'ummer":"8910","poststed":"BRØNNØYSUND","adresse":["Postboks 900"],"kommune":"BRØNNØY","kommunenummer":"1813"}'
|
||||||
|
',"registreringsdatoEnhetsregisteret":"1995-08-09","registrertIMvaregisteret":false,"naeringskode1":{"beskrivels'
|
||||||
|
'e":"Generell offentlig administrasjon","kode":"84.110"},"antallAnsatte":455,"overordnetEnhet":"912660680","for'
|
||||||
|
'retningsadresse":{"land":"Norge","landkode":"NO","postnummer":"8900","poststed":"BRØNNØYSUND","adresse":["Havn'
|
||||||
|
'egata 48"],"kommune":"BRØNNØY","kommunenummer":"1813"},"institusjonellSektorkode":{"kode":"6100","beskrivelse'
|
||||||
|
'":"Statsforvaltningen"},"registrertIForetaksregisteret":false,"registrertIStiftelsesregisteret":false,"registr'
|
||||||
|
'ertIFrivillighetsregisteret":false,"konkurs":false,"underAvvikling":false,"underTvangsavviklingEllerTvangsopp'
|
||||||
|
'losning":false,"maalform":"Bokmål","_links":{"self":{"href":"https://data.brreg.no/enhetsregisteret/api/enheter'
|
||||||
|
'/974760673"},"overordnetEnhet":{"href":"https://data.brreg.no/enhetsregisteret/api/enheter/912660680"}}}',
|
||||||
|
status=200
|
||||||
|
)
|
||||||
|
|
||||||
|
assert validate_vat_id('NO974760673 MVA', 'NO') == 'NO974760673MVA'
|
||||||
|
|
||||||
|
# No tests for CH currently since it's harder to mock Zeep
|
||||||
@@ -54,6 +54,7 @@ from pretix.base.payment import PaymentException
|
|||||||
from pretix.base.services.invoices import (
|
from pretix.base.services.invoices import (
|
||||||
generate_cancellation, generate_invoice,
|
generate_cancellation, generate_invoice,
|
||||||
)
|
)
|
||||||
|
from pretix.base.services.tax import VATIDFinalError, VATIDTemporaryError
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -1563,8 +1564,8 @@ def test_check_vatid(client, env):
|
|||||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
ia = InvoiceAddress.objects.create(order=env[2], is_business=True, vat_id='ATU1234567', country=Country('AT'))
|
ia = InvoiceAddress.objects.create(order=env[2], is_business=True, vat_id='ATU1234567', country=Country('AT'))
|
||||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
mock_validate.return_value = 'AT123456'
|
||||||
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
|
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
|
||||||
assert 'alert-success' in response.content.decode()
|
assert 'alert-success' in response.content.decode()
|
||||||
ia.refresh_from_db()
|
ia.refresh_from_db()
|
||||||
@@ -1576,8 +1577,8 @@ def test_check_vatid_no_entered(client, env):
|
|||||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
ia = InvoiceAddress.objects.create(order=env[2], is_business=True, country=Country('AT'))
|
ia = InvoiceAddress.objects.create(order=env[2], is_business=True, country=Country('AT'))
|
||||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
mock_validate.return_value = 'AT123456'
|
||||||
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
|
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
|
||||||
assert 'alert-danger' in response.content.decode()
|
assert 'alert-danger' in response.content.decode()
|
||||||
ia.refresh_from_db()
|
ia.refresh_from_db()
|
||||||
@@ -1589,12 +1590,10 @@ def test_check_vatid_invalid_country(client, env):
|
|||||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
ia = InvoiceAddress.objects.create(order=env[2], is_business=True, vat_id='ATU1234567', country=Country('FR'))
|
ia = InvoiceAddress.objects.create(order=env[2], is_business=True, vat_id='ATU1234567', country=Country('FR'))
|
||||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
|
||||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
assert 'alert-danger' in response.content.decode()
|
||||||
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
|
ia.refresh_from_db()
|
||||||
assert 'alert-danger' in response.content.decode()
|
assert not ia.vat_id_validated
|
||||||
ia.refresh_from_db()
|
|
||||||
assert not ia.vat_id_validated
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@@ -1602,8 +1601,8 @@ def test_check_vatid_noneu_country(client, env):
|
|||||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
ia = InvoiceAddress.objects.create(order=env[2], is_business=True, vat_id='CHU1234567', country=Country('CH'))
|
ia = InvoiceAddress.objects.create(order=env[2], is_business=True, vat_id='CHU1234567', country=Country('CH'))
|
||||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
mock_validate.return_value = 'AT123456'
|
||||||
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
|
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
|
||||||
assert 'alert-danger' in response.content.decode()
|
assert 'alert-danger' in response.content.decode()
|
||||||
ia.refresh_from_db()
|
ia.refresh_from_db()
|
||||||
@@ -1615,8 +1614,8 @@ def test_check_vatid_no_country(client, env):
|
|||||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
ia = InvoiceAddress.objects.create(order=env[2], is_business=True, vat_id='ATU1234567')
|
ia = InvoiceAddress.objects.create(order=env[2], is_business=True, vat_id='ATU1234567')
|
||||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
mock_validate.return_value = 'AT123456'
|
||||||
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
|
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
|
||||||
assert 'alert-danger' in response.content.decode()
|
assert 'alert-danger' in response.content.decode()
|
||||||
ia.refresh_from_db()
|
ia.refresh_from_db()
|
||||||
@@ -1626,8 +1625,8 @@ def test_check_vatid_no_country(client, env):
|
|||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_check_vatid_no_invoiceaddress(client, env):
|
def test_check_vatid_no_invoiceaddress(client, env):
|
||||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
mock_validate.return_value = 'AT123456'
|
||||||
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
|
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
|
||||||
assert 'alert-danger' in response.content.decode()
|
assert 'alert-danger' in response.content.decode()
|
||||||
|
|
||||||
@@ -1637,10 +1636,9 @@ def test_check_vatid_invalid(client, env):
|
|||||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
ia = InvoiceAddress.objects.create(order=env[2], is_business=True, vat_id='ATU1234567', country=Country('AT'))
|
ia = InvoiceAddress.objects.create(order=env[2], is_business=True, vat_id='ATU1234567', country=Country('AT'))
|
||||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||||
def raiser(*args, **kwargs):
|
def raiser(*args, **kwargs):
|
||||||
import vat_moss.errors
|
raise VATIDFinalError('Fail')
|
||||||
raise vat_moss.errors.InvalidError('Fail')
|
|
||||||
|
|
||||||
mock_validate.side_effect = raiser
|
mock_validate.side_effect = raiser
|
||||||
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
|
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
|
||||||
@@ -1654,10 +1652,9 @@ def test_check_vatid_unavailable(client, env):
|
|||||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
ia = InvoiceAddress.objects.create(order=env[2], is_business=True, vat_id='ATU1234567', country=Country('AT'))
|
ia = InvoiceAddress.objects.create(order=env[2], is_business=True, vat_id='ATU1234567', country=Country('AT'))
|
||||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||||
def raiser(*args, **kwargs):
|
def raiser(*args, **kwargs):
|
||||||
import vat_moss.errors
|
raise VATIDTemporaryError('Fail')
|
||||||
raise vat_moss.errors.WebServiceUnavailableError('Fail')
|
|
||||||
|
|
||||||
mock_validate.side_effect = raiser
|
mock_validate.side_effect = raiser
|
||||||
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
|
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ from io import BytesIO
|
|||||||
import pytest
|
import pytest
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django_scopes import scope
|
from django_scopes import scope
|
||||||
from PyPDF2 import PdfFileReader
|
from PyPDF2 import PdfReader
|
||||||
|
|
||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
Event, Item, ItemVariation, Order, OrderPosition, Organizer,
|
Event, Item, ItemVariation, Order, OrderPosition, Organizer,
|
||||||
@@ -100,8 +100,8 @@ def test_generate_pdf(env):
|
|||||||
'include_pending': True
|
'include_pending': True
|
||||||
})
|
})
|
||||||
assert ftype == 'application/pdf'
|
assert ftype == 'application/pdf'
|
||||||
pdf = PdfFileReader(BytesIO(buf))
|
pdf = PdfReader(BytesIO(buf))
|
||||||
assert pdf.numPages == 2
|
assert len(pdf.pages) == 2
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@@ -115,5 +115,5 @@ def test_generate_pdf_multi(env):
|
|||||||
'include_pending': True
|
'include_pending': True
|
||||||
})
|
})
|
||||||
assert ftype == 'application/pdf'
|
assert ftype == 'application/pdf'
|
||||||
pdf = PdfFileReader(BytesIO(buf))
|
pdf = PdfReader(BytesIO(buf))
|
||||||
assert pdf.numPages == 1
|
assert len(pdf.pages) == 1
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ from io import BytesIO
|
|||||||
import pytest
|
import pytest
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django_scopes import scope
|
from django_scopes import scope
|
||||||
from PyPDF2 import PdfFileReader
|
from PyPDF2 import PdfReader
|
||||||
|
|
||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
Event, Item, ItemVariation, Order, OrderPosition, Organizer,
|
Event, Item, ItemVariation, Order, OrderPosition, Organizer,
|
||||||
@@ -70,5 +70,5 @@ def test_generate_pdf(env0):
|
|||||||
o = PdfTicketOutput(event)
|
o = PdfTicketOutput(event)
|
||||||
fname, ftype, buf = o.generate(order.positions.first())
|
fname, ftype, buf = o.generate(order.positions.first())
|
||||||
assert ftype == 'application/pdf'
|
assert ftype == 'application/pdf'
|
||||||
pdf = PdfFileReader(BytesIO(buf))
|
pdf = PdfReader(BytesIO(buf))
|
||||||
assert pdf.numPages == 1
|
assert len(pdf.pages) == 1
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ from pretix.base.models.items import (
|
|||||||
ItemAddOn, ItemBundle, ItemVariation, SubEventItem, SubEventItemVariation,
|
ItemAddOn, ItemBundle, ItemVariation, SubEventItem, SubEventItemVariation,
|
||||||
)
|
)
|
||||||
from pretix.base.services.orders import OrderError, _perform_order
|
from pretix.base.services.orders import OrderError, _perform_order
|
||||||
|
from pretix.base.services.tax import VATIDFinalError, VATIDTemporaryError
|
||||||
from pretix.testutils.scope import classscope
|
from pretix.testutils.scope import classscope
|
||||||
from pretix.testutils.sessions import get_cart_session_key
|
from pretix.testutils.sessions import get_cart_session_key
|
||||||
|
|
||||||
@@ -139,8 +140,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
|||||||
price=23, expires=now() + timedelta(minutes=10)
|
price=23, expires=now() + timedelta(minutes=10)
|
||||||
)
|
)
|
||||||
|
|
||||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
mock_validate.return_value = 'AT123456'
|
||||||
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||||
'is_business': 'business',
|
'is_business': 'business',
|
||||||
'company': 'Foo',
|
'company': 'Foo',
|
||||||
@@ -163,8 +164,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
|||||||
def test_reverse_charge_enable_then_disable(self):
|
def test_reverse_charge_enable_then_disable(self):
|
||||||
self.test_reverse_charge()
|
self.test_reverse_charge()
|
||||||
|
|
||||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
mock_validate.return_value = 'AT123456'
|
||||||
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||||
'is_business': 'individual',
|
'is_business': 'individual',
|
||||||
'name': 'Bar',
|
'name': 'Bar',
|
||||||
@@ -195,10 +196,9 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
|||||||
price=23, expires=now() + timedelta(minutes=10)
|
price=23, expires=now() + timedelta(minutes=10)
|
||||||
)
|
)
|
||||||
|
|
||||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||||
def raiser(*args, **kwargs):
|
def raiser(*args, **kwargs):
|
||||||
import vat_moss.errors
|
raise VATIDFinalError('final')
|
||||||
raise vat_moss.errors.InvalidError()
|
|
||||||
|
|
||||||
mock_validate.side_effect = raiser
|
mock_validate.side_effect = raiser
|
||||||
resp = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
resp = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||||
@@ -229,7 +229,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
|||||||
price=23, expires=now() + timedelta(minutes=10)
|
price=23, expires=now() + timedelta(minutes=10)
|
||||||
)
|
)
|
||||||
|
|
||||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||||
mock_validate.return_value = ('AU', 'AU123456', 'Foo')
|
mock_validate.return_value = ('AU', 'AU123456', 'Foo')
|
||||||
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||||
'is_business': 'business',
|
'is_business': 'business',
|
||||||
@@ -263,8 +263,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
|||||||
price=23, expires=now() + timedelta(minutes=10)
|
price=23, expires=now() + timedelta(minutes=10)
|
||||||
)
|
)
|
||||||
|
|
||||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
mock_validate.return_value = 'AT123456'
|
||||||
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||||
'is_business': 'business',
|
'is_business': 'business',
|
||||||
'company': 'Foo',
|
'company': 'Foo',
|
||||||
@@ -296,20 +296,18 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
|||||||
price=23, expires=now() + timedelta(minutes=10)
|
price=23, expires=now() + timedelta(minutes=10)
|
||||||
)
|
)
|
||||||
|
|
||||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
resp = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
'is_business': 'business',
|
||||||
resp = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
'company': 'Foo',
|
||||||
'is_business': 'business',
|
'name': 'Bar',
|
||||||
'company': 'Foo',
|
'street': 'Baz',
|
||||||
'name': 'Bar',
|
'zipcode': '12345',
|
||||||
'street': 'Baz',
|
'city': 'Here',
|
||||||
'zipcode': '12345',
|
'country': 'FR',
|
||||||
'city': 'Here',
|
'vat_id': 'AT123456',
|
||||||
'country': 'FR',
|
'email': 'admin@localhost'
|
||||||
'vat_id': 'AT123456',
|
}, follow=True)
|
||||||
'email': 'admin@localhost'
|
assert 'alert-danger' in resp.content.decode()
|
||||||
}, follow=True)
|
|
||||||
assert 'alert-danger' in resp.content.decode()
|
|
||||||
|
|
||||||
cr1.refresh_from_db()
|
cr1.refresh_from_db()
|
||||||
assert cr1.price == Decimal('23.00')
|
assert cr1.price == Decimal('23.00')
|
||||||
@@ -326,10 +324,9 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
|||||||
price=23, expires=now() + timedelta(minutes=10)
|
price=23, expires=now() + timedelta(minutes=10)
|
||||||
)
|
)
|
||||||
|
|
||||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||||
def raiser(*args, **kwargs):
|
def raiser(*args, **kwargs):
|
||||||
import vat_moss.errors
|
raise VATIDTemporaryError('temp')
|
||||||
raise vat_moss.errors.WebServiceUnavailableError('Fail')
|
|
||||||
|
|
||||||
mock_validate.side_effect = raiser
|
mock_validate.side_effect = raiser
|
||||||
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||||
@@ -364,8 +361,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
|||||||
price=23, expires=now() + timedelta(minutes=10)
|
price=23, expires=now() + timedelta(minutes=10)
|
||||||
)
|
)
|
||||||
|
|
||||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
mock_validate.return_value = 'AT123456'
|
||||||
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||||
'is_business': 'business',
|
'is_business': 'business',
|
||||||
'company': 'Foo',
|
'company': 'Foo',
|
||||||
@@ -401,8 +398,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
|||||||
price=23, expires=now() + timedelta(minutes=10)
|
price=23, expires=now() + timedelta(minutes=10)
|
||||||
)
|
)
|
||||||
|
|
||||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
mock_validate.return_value = 'AT123456'
|
||||||
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||||
'is_business': 'business',
|
'is_business': 'business',
|
||||||
'company': 'Foo',
|
'company': 'Foo',
|
||||||
@@ -418,7 +415,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
|||||||
cr1.refresh_from_db()
|
cr1.refresh_from_db()
|
||||||
assert cr1.price == Decimal('19.33')
|
assert cr1.price == Decimal('19.33')
|
||||||
|
|
||||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||||
mock_validate.return_value = ('DE', 'DE123456', 'Foo')
|
mock_validate.return_value = ('DE', 'DE123456', 'Foo')
|
||||||
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||||
'is_business': 'business',
|
'is_business': 'business',
|
||||||
@@ -465,8 +462,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
|||||||
cr1.refresh_from_db()
|
cr1.refresh_from_db()
|
||||||
assert cr1.price == Decimal('23.00')
|
assert cr1.price == Decimal('23.00')
|
||||||
|
|
||||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
mock_validate.return_value = 'AT123456'
|
||||||
r = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
r = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||||
'is_business': 'business',
|
'is_business': 'business',
|
||||||
'company': 'Foo',
|
'company': 'Foo',
|
||||||
@@ -525,8 +522,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
|||||||
price=23, expires=now() + timedelta(minutes=10)
|
price=23, expires=now() + timedelta(minutes=10)
|
||||||
)
|
)
|
||||||
|
|
||||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
mock_validate.return_value = 'AT123456'
|
||||||
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||||
'is_business': 'individual',
|
'is_business': 'individual',
|
||||||
'name': 'Bar',
|
'name': 'Bar',
|
||||||
@@ -577,8 +574,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
|||||||
voucher=self.event.vouchers.create()
|
voucher=self.event.vouchers.create()
|
||||||
)
|
)
|
||||||
|
|
||||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
mock_validate.return_value = 'AT123456'
|
||||||
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||||
'is_business': 'individual',
|
'is_business': 'individual',
|
||||||
'name': 'Bar',
|
'name': 'Bar',
|
||||||
@@ -605,8 +602,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
|||||||
def test_country_taxing_switch(self):
|
def test_country_taxing_switch(self):
|
||||||
self._test_country_taxing()
|
self._test_country_taxing()
|
||||||
|
|
||||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
mock_validate.return_value = 'AT123456'
|
||||||
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||||
'is_business': 'individual',
|
'is_business': 'individual',
|
||||||
'name': 'Bar',
|
'name': 'Bar',
|
||||||
@@ -657,8 +654,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
|||||||
assert cr1.price == Decimal('28.56')
|
assert cr1.price == Decimal('28.56')
|
||||||
assert cr1.tax_rate == Decimal('19.00')
|
assert cr1.tax_rate == Decimal('19.00')
|
||||||
|
|
||||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
mock_validate.return_value = 'AT123456'
|
||||||
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||||
'is_business': 'business',
|
'is_business': 'business',
|
||||||
'company': 'Foo',
|
'company': 'Foo',
|
||||||
@@ -721,8 +718,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
|||||||
assert cr1.price == Decimal('47.60')
|
assert cr1.price == Decimal('47.60')
|
||||||
assert cr1.tax_rate == Decimal('19.00')
|
assert cr1.tax_rate == Decimal('19.00')
|
||||||
|
|
||||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
mock_validate.return_value = 'AT123456'
|
||||||
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||||
'is_business': 'business',
|
'is_business': 'business',
|
||||||
'company': 'Foo',
|
'company': 'Foo',
|
||||||
|
|||||||
Reference in New Issue
Block a user