mirror of
https://github.com/pretix/pretix.git
synced 2026-08-03 09:37:51 +00:00
Merge branch 'master' of https://github.com/pretix/pretix
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
file=/tmp/supervisor.sock
|
||||
|
||||
[supervisord]
|
||||
environment = AUTOMIGRATE="skip"
|
||||
logfile=/dev/stdout
|
||||
logfile_maxbytes=0
|
||||
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 ipv6only=on default_server;
|
||||
server_name pretix.mydomain.com;
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
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 ipv6only=on default_server;
|
||||
server_name pretix.mydomain.com;
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
server {
|
||||
listen 443 default_server;
|
||||
|
||||
@@ -26,10 +26,16 @@ date_joined datetime Date and time o
|
||||
locale string Preferred language of the customer
|
||||
last_modified datetime Date and time of modification of the record
|
||||
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
|
||||
|
||||
.. versionchanged:: 4.3
|
||||
|
||||
Passwords can now be set through the API during customer creation.
|
||||
|
||||
Endpoints
|
||||
---------
|
||||
|
||||
@@ -146,6 +152,7 @@ Endpoints
|
||||
|
||||
{
|
||||
"email": "test@example.org",
|
||||
"password": "verysecret",
|
||||
"send_email": true
|
||||
}
|
||||
|
||||
|
||||
@@ -428,7 +428,7 @@ List of all orders
|
||||
``last_modified``, and ``status``. Default: ``datetime``
|
||||
: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 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 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``
|
||||
@@ -853,7 +853,7 @@ Creating orders
|
||||
|
||||
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
|
||||
``"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
|
||||
|
||||
@@ -36,10 +36,16 @@ The following values for ``action_types`` are valid with pretix core:
|
||||
* ``pretix.event.order.canceled``
|
||||
* ``pretix.event.order.reactivated``
|
||||
* ``pretix.event.order.expired``
|
||||
* ``pretix.event.order.expirychanged``
|
||||
* ``pretix.event.order.modified``
|
||||
* ``pretix.event.order.contact.changed``
|
||||
* ``pretix.event.order.changed.*``
|
||||
* ``pretix.event.order.refund.created``
|
||||
* ``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.denied``
|
||||
* ``pretix.event.checkin``
|
||||
@@ -50,6 +56,10 @@ The following values for ``action_types`` are valid with pretix core:
|
||||
* ``pretix.subevent.added``
|
||||
* ``pretix.subevent.changed``
|
||||
* ``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.
|
||||
|
||||
|
||||
@@ -19,4 +19,4 @@
|
||||
# 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/>.
|
||||
#
|
||||
__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 (
|
||||
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
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -777,6 +780,7 @@ class EventSettingsSerializer(SettingsSerializer):
|
||||
'logo_image_large',
|
||||
'logo_show_title',
|
||||
'og_image',
|
||||
'name_scheme',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -842,4 +846,25 @@ class DeviceEventSettingsSerializer(EventSettingsSerializer):
|
||||
'invoice_address_from_country',
|
||||
'invoice_address_from_tax_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):
|
||||
send_email = serializers.BooleanField(default=False, required=False, allow_null=True)
|
||||
password = serializers.CharField(write_only=True, required=False, allow_null=True)
|
||||
|
||||
class Meta:
|
||||
model = Customer
|
||||
fields = CustomerSerializer.Meta.fields + ('send_email',)
|
||||
fields = CustomerSerializer.Meta.fields + ('send_email', 'password')
|
||||
|
||||
|
||||
class MembershipTypeSerializer(I18nAwareModelSerializer):
|
||||
|
||||
@@ -1322,6 +1322,7 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
new_data = serializer.data
|
||||
instance.order.create_transactions()
|
||||
|
||||
if old_data != new_data:
|
||||
log_data = self.request.data
|
||||
|
||||
@@ -515,8 +515,8 @@ class CustomerViewSet(viewsets.ModelViewSet):
|
||||
raise MethodNotAllowed("Customers cannot be deleted.")
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_create(self, serializer, send_email=False):
|
||||
customer = serializer.save(organizer=self.request.organizer, password=make_password(None))
|
||||
def perform_create(self, serializer, send_email=False, password=None):
|
||||
customer = serializer.save(organizer=self.request.organizer, password=make_password(password))
|
||||
serializer.instance.log_action(
|
||||
'pretix.customer.created',
|
||||
user=self.request.user,
|
||||
@@ -530,7 +530,7 @@ class CustomerViewSet(viewsets.ModelViewSet):
|
||||
def create(self, request, *args, **kwargs):
|
||||
serializer = CustomerCreateSerializer(data=request.data, context=self.get_serializer_context())
|
||||
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)
|
||||
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',
|
||||
_('Order expired'),
|
||||
),
|
||||
ParametrizedOrderWebhookEvent(
|
||||
'pretix.event.order.expirychanged',
|
||||
_('Order expiry date changed'),
|
||||
),
|
||||
ParametrizedOrderWebhookEvent(
|
||||
'pretix.event.order.modified',
|
||||
_('Order information changed'),
|
||||
@@ -231,10 +235,30 @@ def register_default_webhook_events(sender, **kwargs):
|
||||
'pretix.event.order.changed.*',
|
||||
_('Order changed'),
|
||||
),
|
||||
ParametrizedOrderWebhookEvent(
|
||||
'pretix.event.order.refund.created',
|
||||
_('Refund of payment created'),
|
||||
),
|
||||
ParametrizedOrderWebhookEvent(
|
||||
'pretix.event.order.refund.created.externally',
|
||||
_('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(
|
||||
'pretix.event.order.approved',
|
||||
_('Order approved'),
|
||||
@@ -275,6 +299,22 @@ def register_default_webhook_events(sender, **kwargs):
|
||||
'pretix.subevent.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,
|
||||
)
|
||||
from pretix.base.models import Event
|
||||
from pretix.base.reldate import RelativeDateWrapper
|
||||
from pretix.base.settings import PERSON_NAME_SCHEMES
|
||||
from pretix.base.signals import (
|
||||
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(
|
||||
'event_location', ['event_or_subevent'], lambda event_or_subevent: str(event_or_subevent.location or ''),
|
||||
lambda event: str(event.location or ''),
|
||||
|
||||
@@ -610,7 +610,10 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
for k, label, w in name_scheme['fields']:
|
||||
headers.append(_('Invoice address name') + ': ' + str(label))
|
||||
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 += [
|
||||
_('Sales channel'), _('Order locale'),
|
||||
|
||||
@@ -746,6 +746,19 @@ class Order(LockModel, LoggedModel):
|
||||
length += 1
|
||||
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
|
||||
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):
|
||||
return False
|
||||
|
||||
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))
|
||||
]
|
||||
modify_deadline = min(dates) if dates else None
|
||||
elif modify_deadline:
|
||||
modify_deadline = modify_deadline.datetime(self.event)
|
||||
|
||||
modify_deadline = self.modify_deadline
|
||||
if modify_deadline is not None and now() > modify_deadline:
|
||||
return False
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ EU_CURRENCIES = {
|
||||
'RO': 'RON',
|
||||
'SE': 'SEK'
|
||||
}
|
||||
VAT_ID_COUNTRIES = EU_COUNTRIES | {'CH'}
|
||||
VAT_ID_COUNTRIES = EU_COUNTRIES | {'CH', 'NO'}
|
||||
|
||||
|
||||
def is_eu_country(cc):
|
||||
|
||||
@@ -392,7 +392,7 @@ class InvoiceAddressCountry(ImportColumn):
|
||||
return list(countries)
|
||||
|
||||
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."))
|
||||
return value
|
||||
|
||||
@@ -538,7 +538,7 @@ class AttendeeCountry(ImportColumn):
|
||||
return list(countries)
|
||||
|
||||
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."))
|
||||
return value
|
||||
|
||||
|
||||
+11
-11
@@ -57,7 +57,7 @@ from django.utils.html import conditional_escape
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _, pgettext
|
||||
from i18nfield.strings import LazyI18nString
|
||||
from PyPDF2 import PdfFileReader
|
||||
from PyPDF2 import PdfReader
|
||||
from pytz import timezone
|
||||
from reportlab.graphics import renderPDF
|
||||
from reportlab.graphics.barcode.qr import QrCodeWidget
|
||||
@@ -646,7 +646,7 @@ class Renderer:
|
||||
self.event = event
|
||||
if self.background_file:
|
||||
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:
|
||||
self.bg_bytes = None
|
||||
self.bg_pdf = None
|
||||
@@ -861,7 +861,7 @@ class Renderer:
|
||||
canvas.restoreState()
|
||||
|
||||
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:
|
||||
raise ValueError("only_page=None and show_page=False cannot be combined")
|
||||
@@ -881,7 +881,7 @@ class Renderer:
|
||||
elif o['type'] == "poweredby":
|
||||
self._draw_poweredby(canvas, op, o)
|
||||
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:
|
||||
canvas.showPage()
|
||||
|
||||
@@ -905,17 +905,17 @@ class Renderer:
|
||||
with open(os.path.join(d, 'out.pdf'), 'rb') as f:
|
||||
return BytesIO(f.read())
|
||||
else:
|
||||
from PyPDF2 import PdfFileReader, PdfFileWriter
|
||||
from PyPDF2 import PdfReader, PdfWriter
|
||||
buffer.seek(0)
|
||||
new_pdf = PdfFileReader(buffer)
|
||||
output = PdfFileWriter()
|
||||
new_pdf = PdfReader(buffer)
|
||||
output = PdfWriter()
|
||||
|
||||
for i, page in enumerate(new_pdf.pages):
|
||||
bg_page = copy.copy(self.bg_pdf.getPage(i))
|
||||
bg_page.mergePage(page)
|
||||
output.addPage(bg_page)
|
||||
bg_page = copy.copy(self.bg_pdf.pages[i])
|
||||
bg_page.merge_page(page)
|
||||
output.add_page(bg_page)
|
||||
|
||||
output.addMetadata({
|
||||
output.add_metadata({
|
||||
'/Title': str(title),
|
||||
'/Creator': 'pretix',
|
||||
})
|
||||
|
||||
@@ -36,6 +36,7 @@ from pretix.base.models import (
|
||||
from pretix.base.models.orders import Transaction
|
||||
from pretix.base.orderimport import get_all_columns
|
||||
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.signals import order_paid, order_placed
|
||||
from pretix.celery_app import app
|
||||
@@ -85,9 +86,9 @@ def setif(record, obj, attr, setting):
|
||||
|
||||
@app.task(base=ProfiledEventTask, throws=(DataImportError,))
|
||||
def import_orders(event: Event, fileid: str, settings: dict, locale: str, user) -> None:
|
||||
# TODO: quotacheck?
|
||||
cf = CachedFile.objects.get(id=fileid)
|
||||
user = User.objects.get(pk=user)
|
||||
seats_used = False
|
||||
with language(locale, event.settings.region):
|
||||
cols = get_all_columns(event)
|
||||
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.attendee_name_parts = {'_scheme': event.settings.name_scheme}
|
||||
position.meta_info = {}
|
||||
if position.seat is not None:
|
||||
seats_used = True
|
||||
order._positions.append(position)
|
||||
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))
|
||||
)
|
||||
|
||||
# quota check?
|
||||
with event.lock():
|
||||
with transaction.atomic():
|
||||
# We don't support vouchers, quotas, or memberships here, so we only need to lock if seats
|
||||
# are in use
|
||||
lockfn = event.lock if seats_used else NoLockManager
|
||||
|
||||
try:
|
||||
with lockfn(), transaction.atomic():
|
||||
save_transactions = []
|
||||
for o in orders:
|
||||
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()
|
||||
if gen_invoice:
|
||||
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()
|
||||
|
||||
+115
-27
@@ -22,9 +22,9 @@
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from urllib.error import HTTPError
|
||||
from xml.etree import ElementTree
|
||||
|
||||
import vat_moss.errors
|
||||
import requests
|
||||
import vat_moss.id
|
||||
from django.conf import settings
|
||||
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
|
||||
|
||||
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):
|
||||
@@ -50,33 +60,104 @@ class VATIDTemporaryError(VATIDError):
|
||||
pass
|
||||
|
||||
|
||||
def _validate_vat_id_EU(vat_id, country_code):
|
||||
if vat_id[:2] != cc_to_vat_prefix(country_code):
|
||||
raise VATIDFinalError(_('Your VAT ID does not match the selected country.'))
|
||||
def _validate_vat_id_NO(vat_id, country_code):
|
||||
# Inspired by vat_moss library
|
||||
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:
|
||||
result = vat_moss.id.validate(vat_id)
|
||||
if result:
|
||||
country_code, normalized_id, company_name = result
|
||||
return normalized_id
|
||||
except (vat_moss.errors.InvalidError, ValueError):
|
||||
raise VATIDFinalError(_('This VAT ID is not valid. Please re-check your input.'))
|
||||
except vat_moss.errors.WebServiceUnavailableError:
|
||||
response = requests.get(validation_url, timeout=10)
|
||||
if response.status_code in (404, 400):
|
||||
raise VATIDFinalError(error_messages['invalid'])
|
||||
|
||||
response.raise_for_status()
|
||||
|
||||
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))
|
||||
raise VATIDTemporaryError(_(
|
||||
'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.'
|
||||
))
|
||||
except (vat_moss.errors.WebServiceError, HTTPError):
|
||||
raise VATIDTemporaryError(error_messages['unavailable'])
|
||||
else:
|
||||
return vat_id
|
||||
|
||||
|
||||
def _validate_vat_id_EU(vat_id, country_code):
|
||||
# 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))
|
||||
raise VATIDTemporaryError(_(
|
||||
'Your VAT ID could not be checked, as the VAT checking service of '
|
||||
'your country returned an incorrect result. We will therefore '
|
||||
'need to charge VAT on your invoice. Please contact support to '
|
||||
'resolve this manually.'
|
||||
))
|
||||
raise VATIDTemporaryError(error_messages['unavailable'])
|
||||
else:
|
||||
return vat_id
|
||||
|
||||
|
||||
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', ''))
|
||||
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(
|
||||
'https://www.uid-wse.admin.ch/V5.0/PublicServices.svc?wsdl',
|
||||
transport=transport
|
||||
transport=transport,
|
||||
)
|
||||
result = client.service.ValidateUID(uid=vat_id)
|
||||
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):
|
||||
if not vat_id:
|
||||
return vat_id
|
||||
country_code = str(country_code)
|
||||
if is_eu_country(country_code):
|
||||
return _validate_vat_id_EU(vat_id, country_code)
|
||||
elif country_code == 'CH':
|
||||
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}')
|
||||
|
||||
@@ -57,6 +57,7 @@ from django_countries.fields import Country
|
||||
from hierarkey.models import GlobalSettingsBase, Hierarkey
|
||||
from i18nfield.forms import I18nFormField, I18nTextarea, I18nTextInput
|
||||
from i18nfield.strings import LazyI18nString
|
||||
from phonenumbers import PhoneNumber, parse
|
||||
from rest_framework import serializers
|
||||
|
||||
from pretix.api.serializers.fields import (
|
||||
@@ -2594,7 +2595,9 @@ Your {organizer} team"""))
|
||||
},
|
||||
'name_scheme': {
|
||||
'default': 'full', # default for new events is 'given_family'
|
||||
'type': str
|
||||
'type': str,
|
||||
'serializer_class': serializers.ChoiceField,
|
||||
'serializer_kwargs': {},
|
||||
},
|
||||
'giftcard_length': {
|
||||
'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 = {
|
||||
# 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
|
||||
@@ -3025,6 +3031,7 @@ settings_hierarkey.add_type(LazyI18nStringList,
|
||||
settings_hierarkey.add_type(RelativeDateWrapper,
|
||||
serialize=lambda rdw: rdw.to_string(),
|
||||
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')
|
||||
|
||||
@@ -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>
|
||||
<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="
|
||||
style="max-height: 60px;">
|
||||
style="max-height: 60px;" alt="">
|
||||
</td>
|
||||
</tr>
|
||||
<!--<![endif]-->
|
||||
@@ -233,7 +233,7 @@
|
||||
<td style="line-height: 0">
|
||||
<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="
|
||||
style="max-height: 60px;">
|
||||
style="max-height: 60px;" alt="">
|
||||
</td>
|
||||
</tr>
|
||||
<!--<![endif]-->
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<td>
|
||||
<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"
|
||||
style="max-height: 4px;">
|
||||
style="max-height: 4px;" alt="">
|
||||
<div style="line-height: 18px;height: 18px;"> </div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -216,7 +216,8 @@ class AsyncFormView(AsyncMixin, FormView):
|
||||
task_base = ProfiledEventTask
|
||||
|
||||
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()
|
||||
form_kwargs['data'] = QueryDict(form_kwargs['data'])
|
||||
req = RequestFactory().post(
|
||||
@@ -225,6 +226,8 @@ class AsyncFormView(AsyncMixin, FormView):
|
||||
content_type='application/x-www-form-urlencoded'
|
||||
)
|
||||
view_instance.request = req
|
||||
view_instance.kwargs = url_kwargs
|
||||
view_instance.args = url_args
|
||||
if event:
|
||||
view_instance.request.event = event
|
||||
view_instance.request.organizer = event.organizer
|
||||
@@ -284,6 +287,8 @@ class AsyncFormView(AsyncMixin, FormView):
|
||||
'request_path': self.request.path,
|
||||
'query_string': self.request.GET.urlencode(),
|
||||
'form_kwargs': form_kwargs,
|
||||
'url_args': self.args,
|
||||
'url_kwargs': self.kwargs,
|
||||
'locale': get_language(),
|
||||
'tz': get_current_timezone().zone,
|
||||
}
|
||||
@@ -336,6 +341,8 @@ class AsyncPostView(AsyncMixin, View):
|
||||
content_type='application/x-www-form-urlencoded'
|
||||
)
|
||||
view_instance.request = req
|
||||
view_instance.kwargs = url_kwargs
|
||||
view_instance.args = url_args
|
||||
if event:
|
||||
view_instance.request.event = event
|
||||
view_instance.request.organizer = event.organizer
|
||||
|
||||
@@ -71,7 +71,7 @@ def _default_context(request):
|
||||
except Resolver404:
|
||||
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 {}
|
||||
ctx = {
|
||||
'url_name': url.url_name,
|
||||
|
||||
@@ -39,7 +39,7 @@ from urllib.parse import urlencode, urlparse
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
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.forms import (
|
||||
CheckboxSelectMultiple, formset_factory, inlineformset_factory,
|
||||
@@ -848,6 +848,7 @@ class InvoiceSettingsForm(SettingsForm):
|
||||
self.fields['invoice_generate_sales_channels'].choices = (
|
||||
(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):
|
||||
data = super().clean()
|
||||
|
||||
@@ -207,14 +207,16 @@
|
||||
{% bootstrap_field sform.logo_show_title layout="control" %}
|
||||
{% bootstrap_field sform.og_image layout="control" %}
|
||||
{% 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" %}
|
||||
{% bootstrap_field sform.primary_color layout="control" %}
|
||||
{% bootstrap_field sform.theme_color_success layout="control" %}
|
||||
{% bootstrap_field sform.theme_color_danger layout="control" %}
|
||||
{% bootstrap_field sform.theme_color_background layout="control" %}
|
||||
{% bootstrap_field sform.theme_round_borders layout="control" %}
|
||||
{% bootstrap_field sform.primary_font layout="control" %}
|
||||
{% endpropagated %}
|
||||
{% with org_url|add:"#tab-0-4-open" as org_url_tab %}
|
||||
{% 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.primary_color layout="control" %}
|
||||
{% bootstrap_field sform.theme_color_success layout="control" %}
|
||||
{% bootstrap_field sform.theme_color_danger layout="control" %}
|
||||
{% bootstrap_field sform.theme_color_background layout="control" %}
|
||||
{% bootstrap_field sform.theme_round_borders layout="control" %}
|
||||
{% bootstrap_field sform.primary_font layout="control" %}
|
||||
{% endpropagated %}
|
||||
{% endwith %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Timeline" %}</legend>
|
||||
|
||||
@@ -55,7 +55,7 @@ class PropagatedNode(Node):
|
||||
</div>
|
||||
<div class="panel-body help-text">
|
||||
{text_expl}<br>
|
||||
<a href="{url}" target="_blank">
|
||||
<a href="{url}" target="_blank" class="btn btn-default">
|
||||
{text_orga}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1344,22 +1344,36 @@ class ItemUpdateGeneral(ItemDetailMixin, EventPermissionRequiredMixin, MetaDataE
|
||||
def form_valid(self, form):
|
||||
self.save_meta()
|
||||
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 = {
|
||||
k: form.cleaned_data.get(k)
|
||||
for k in form.changed_data
|
||||
}
|
||||
for f in self.plugin_forms:
|
||||
data.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
|
||||
})
|
||||
|
||||
change_data = {
|
||||
k: form.cleaned_data.get(k)
|
||||
for k in form.changed_data
|
||||
}
|
||||
for f in self.plugin_forms:
|
||||
change_data.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
|
||||
})
|
||||
|
||||
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(
|
||||
'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})
|
||||
|
||||
for f in self.plugin_forms:
|
||||
f.save()
|
||||
|
||||
|
||||
@@ -1186,13 +1186,17 @@ class OrderTransition(OrderView):
|
||||
if ps == Decimal('0.00') and self.order.pending_sum <= Decimal('0.00'):
|
||||
p = self.order.payments.filter(state=OrderPayment.PAYMENT_STATE_CONFIRMED).last()
|
||||
if p:
|
||||
p._mark_order_paid(
|
||||
user=self.request.user,
|
||||
send_mail=self.mark_paid_form.cleaned_data['send_email'],
|
||||
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.'))
|
||||
try:
|
||||
p._mark_order_paid(
|
||||
user=self.request.user,
|
||||
send_mail=self.mark_paid_form.cleaned_data['send_email'],
|
||||
force=self.mark_paid_form.cleaned_data.get('force', False),
|
||||
payment_refund_sum=self.order.payment_refund_sum,
|
||||
)
|
||||
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())
|
||||
|
||||
try:
|
||||
|
||||
@@ -39,8 +39,8 @@ from django.utils.crypto import get_random_string
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic import TemplateView
|
||||
from PyPDF2 import PdfFileReader, PdfFileWriter
|
||||
from PyPDF2.utils import PdfReadError
|
||||
from PyPDF2 import PdfReader, PdfWriter
|
||||
from PyPDF2.errors import PdfReadError
|
||||
from reportlab.lib.units import mm
|
||||
|
||||
from pretix.base.i18n import language
|
||||
@@ -153,9 +153,9 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
if "emptybackground" in request.POST:
|
||||
p = PdfFileWriter()
|
||||
p = PdfWriter()
|
||||
try:
|
||||
p.addBlankPage(
|
||||
p.add_blank_page(
|
||||
width=float(request.POST.get('width')) * mm,
|
||||
height=float(request.POST.get('height')) * mm,
|
||||
)
|
||||
@@ -203,7 +203,7 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
|
||||
|
||||
try:
|
||||
bg_bytes = c.file.read()
|
||||
PdfFileReader(BytesIO(bg_bytes), strict=False)
|
||||
PdfReader(BytesIO(bg_bytes), strict=False)
|
||||
except PdfReadError as e:
|
||||
return JsonResponse({
|
||||
"status": "error",
|
||||
|
||||
@@ -228,9 +228,9 @@ def nav_context_list(request):
|
||||
if 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(
|
||||
code__icontains=query
|
||||
code__istartswith=query
|
||||
).select_related('event', 'event__organizer').only('event', 'code', 'pk').order_by()
|
||||
if not request.user.has_active_staff_session(request.session.session_key):
|
||||
qs_orders = qs_orders.filter(
|
||||
@@ -241,7 +241,7 @@ def nav_context_list(request):
|
||||
)
|
||||
|
||||
qs_vouchers = Voucher.objects.filter(
|
||||
code__icontains=query
|
||||
code__istartswith=query
|
||||
).select_related('event', 'event__organizer').only('event', 'code', 'pk').order_by()
|
||||
if not request.user.has_active_staff_session(request.session.session_key):
|
||||
qs_vouchers = qs_vouchers.filter(
|
||||
|
||||
@@ -43,6 +43,7 @@ from django.core.cache import cache
|
||||
from django.core.exceptions import DisallowedHost
|
||||
from django.http.request import split_domain_port
|
||||
from django.middleware.csrf import CsrfViewMiddleware as BaseCsrfMiddleware
|
||||
from django.shortcuts import render
|
||||
from django.urls import set_urlconf
|
||||
from django.utils.cache import patch_vary_headers
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
@@ -112,7 +113,15 @@ class MultiDomainMiddleware(MiddlewareMixin):
|
||||
elif settings.DEBUG or domain in LOCAL_HOST_NAMES:
|
||||
request.urlconf = "pretix.multidomain.maindomain_urlconf"
|
||||
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:
|
||||
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.utils.timezone import make_aware
|
||||
from django.utils.translation import gettext as _, gettext_lazy
|
||||
from PyPDF2 import Transformation
|
||||
from reportlab.lib import pagesizes
|
||||
from reportlab.lib.units import mm
|
||||
from reportlab.pdfgen import canvas
|
||||
@@ -153,11 +154,19 @@ OPTIONS = OrderedDict([
|
||||
'offsets': [95 * mm, 55 * mm],
|
||||
'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):
|
||||
from PyPDF2 import PdfFileReader, PdfFileWriter
|
||||
from PyPDF2 import PdfReader, PdfWriter
|
||||
Renderer._register_fonts()
|
||||
|
||||
renderermap = {
|
||||
@@ -168,7 +177,7 @@ def render_pdf(event, positions, opt):
|
||||
default_renderer = _renderer(event, event.badge_layouts.get(default=True))
|
||||
except BadgeLayout.DoesNotExist:
|
||||
default_renderer = None
|
||||
output_pdf_writer = PdfFileWriter()
|
||||
output_pdf_writer = PdfWriter()
|
||||
|
||||
any = False
|
||||
npp = opt['cols'] * opt['rows']
|
||||
@@ -189,22 +198,19 @@ def render_pdf(event, positions, opt):
|
||||
p.showPage()
|
||||
p.save()
|
||||
buffer.seek(0)
|
||||
canvas_pdf_reader = PdfFileReader(buffer)
|
||||
empty_pdf_page = output_pdf_writer.addBlankPage(
|
||||
width=opt['pagesize'][0] if opt['pagesize'] else positions[0][1].bg_pdf.getPage(0).mediaBox[2],
|
||||
height=opt['pagesize'][1] if opt['pagesize'] else positions[0][1].bg_pdf.getPage(0).mediaBox[3],
|
||||
canvas_pdf_reader = PdfReader(buffer)
|
||||
empty_pdf_page = output_pdf_writer.add_blank_page(
|
||||
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.pages[0].mediabox[3],
|
||||
)
|
||||
for i, (op, r) in enumerate(positions):
|
||||
bg_page = copy.copy(r.bg_pdf.getPage(0))
|
||||
bg_page.trimBox = bg_page.mediaBox
|
||||
bg_page = copy.copy(r.bg_pdf.pages[0])
|
||||
bg_page.trimbox = bg_page.mediabox
|
||||
offsetx = opt['margins'][3] + (i % opt['cols']) * opt['offsets'][0]
|
||||
offsety = opt['margins'][2] + (opt['rows'] - 1 - i // opt['cols']) * opt['offsets'][1]
|
||||
empty_pdf_page.mergeTranslatedPage(
|
||||
bg_page,
|
||||
tx=offsetx,
|
||||
ty=offsety
|
||||
)
|
||||
empty_pdf_page.mergePage(canvas_pdf_reader.getPage(0))
|
||||
bg_page.add_transformation(Transformation().translate(offsetx, offsety))
|
||||
empty_pdf_page.merge_page(bg_page)
|
||||
empty_pdf_page.merge_page(canvas_pdf_reader.pages[0])
|
||||
|
||||
pagebuffer = []
|
||||
outbuffer = BytesIO()
|
||||
@@ -221,7 +227,7 @@ def render_pdf(event, positions, opt):
|
||||
if pagebuffer:
|
||||
render_page(pagebuffer)
|
||||
|
||||
output_pdf_writer.addMetadata({
|
||||
output_pdf_writer.add_metadata({
|
||||
'/Title': 'Badges',
|
||||
'/Creator': 'pretix',
|
||||
})
|
||||
|
||||
@@ -582,7 +582,7 @@ class PaypalMethod(BasePaymentProvider):
|
||||
on the 'confirm order' page.
|
||||
"""
|
||||
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)
|
||||
|
||||
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 (
|
||||
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.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 (
|
||||
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.namespace == "plugins:paypal2" and url.url_name == "pay")
|
||||
):
|
||||
|
||||
@@ -141,11 +141,11 @@ var pretixpaypal = {
|
||||
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());
|
||||
}
|
||||
|
||||
if ($('#paypal-button-container').data('paypage')) {
|
||||
} else if ($(".payment-redo-form").length) {
|
||||
pretixpaypal.renderButton($("input[name=payment][value^='paypal']").val());
|
||||
} else if ($('#paypal-button-container').data('paypage')) {
|
||||
pretixpaypal.renderButton('paypal_apm');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -283,7 +283,7 @@ def success(request, *args, **kwargs):
|
||||
else:
|
||||
payment = None
|
||||
|
||||
if request.session.get('payment_paypal_id', None):
|
||||
if request.session.get('payment_paypal_oid', None):
|
||||
if payment:
|
||||
prov = Paypal(request.event)
|
||||
try:
|
||||
@@ -296,7 +296,7 @@ def success(request, *args, **kwargs):
|
||||
return resp
|
||||
else:
|
||||
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'
|
||||
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.urls import resolve, reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_scopes import scope, scopes_disabled
|
||||
|
||||
@@ -134,6 +135,7 @@ def sendmail_run_rules(sender, **kwargs):
|
||||
with scopes_disabled():
|
||||
mails = ScheduledMail.objects.all()
|
||||
|
||||
unchanged = []
|
||||
for m in mails.filter(Q(last_computed__isnull=True)
|
||||
| Q(subevent__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()
|
||||
if m.computed_datetime != previous:
|
||||
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(
|
||||
state=ScheduledMail.STATE_SCHEDULED,
|
||||
|
||||
@@ -166,6 +166,11 @@ class StripeSettingsHolder(BasePaymentProvider):
|
||||
label=_('Stripe account'),
|
||||
disabled=True
|
||||
)),
|
||||
('connect_user_id',
|
||||
forms.CharField(
|
||||
label=_('Stripe account'),
|
||||
disabled=True
|
||||
)),
|
||||
('endpoint',
|
||||
forms.ChoiceField(
|
||||
label=_('Endpoint'),
|
||||
|
||||
@@ -43,7 +43,7 @@ from django.db.models import Q
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.utils.timezone import make_aware
|
||||
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.i18n import language
|
||||
@@ -105,7 +105,7 @@ class AllTicketsPDF(BaseExporter):
|
||||
return d
|
||||
|
||||
def render(self, form_data):
|
||||
merger = PdfFileMerger()
|
||||
merger = PdfMerger()
|
||||
qs = OrderPosition.objects.filter(
|
||||
order__event__in=self.events
|
||||
).prefetch_related(
|
||||
|
||||
@@ -44,7 +44,7 @@ from django.http import HttpRequest
|
||||
from django.template.loader import get_template
|
||||
from django.utils.functional import cached_property
|
||||
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.models import Order, OrderPosition
|
||||
@@ -112,7 +112,7 @@ class PdfTicketOutput(BaseTicketOutput):
|
||||
return renderer.render_background(buffer, _('Ticket'))
|
||||
|
||||
def generate_order(self, order: Order):
|
||||
merger = PdfFileMerger()
|
||||
merger = PdfMerger()
|
||||
with language(order.locale, self.event.settings.region):
|
||||
for op in order.positions_with_tickets:
|
||||
layout = override_layout.send_chained(
|
||||
|
||||
@@ -221,15 +221,13 @@ class MembershipForm(forms.Form):
|
||||
else:
|
||||
types = self.position.item.require_membership_types.all()
|
||||
|
||||
initial = None
|
||||
|
||||
memberships = [
|
||||
m for m in self.memberships
|
||||
if m.is_valid(ev) and m.membership_type in types
|
||||
]
|
||||
|
||||
if len(memberships) == 1:
|
||||
initial = str(memberships[0].pk)
|
||||
self.initial['membership'] = str(memberships[0].pk)
|
||||
|
||||
self.fields['membership'] = forms.ChoiceField(
|
||||
label=_('Membership'),
|
||||
@@ -237,7 +235,6 @@ class MembershipForm(forms.Form):
|
||||
(str(m.pk), self._label_from_instance(m))
|
||||
for m in memberships
|
||||
],
|
||||
initial=initial,
|
||||
widget=forms.RadioSelect,
|
||||
)
|
||||
self.is_empty = not memberships
|
||||
|
||||
@@ -58,9 +58,9 @@
|
||||
{% endblocktrans %}
|
||||
</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>
|
||||
{% else %}
|
||||
{% elif not item.mandatory_priced_addons %}
|
||||
{{ item.min_price|money:event.currency }}
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -144,7 +144,9 @@
|
||||
</div>
|
||||
<p>
|
||||
{% 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 %}
|
||||
{{ var.display_price.net|money:event.currency }}
|
||||
{% else %}
|
||||
@@ -276,7 +278,9 @@
|
||||
</div>
|
||||
<p>
|
||||
{% 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 %}
|
||||
{{ item.display_price.net|money:event.currency }}
|
||||
{% else %}
|
||||
|
||||
@@ -27,7 +27,11 @@
|
||||
{% block custom_header %}
|
||||
{{ block.super }}
|
||||
<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 %}
|
||||
<meta property="og:url" content="{% abseventurl request.event "presale:event.index" subevent=subevent.pk %}" />
|
||||
{% else %}
|
||||
|
||||
@@ -93,6 +93,10 @@ event_patterns = [
|
||||
name='event.payment.unlock'),
|
||||
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(),
|
||||
name='event.order.open'),
|
||||
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 = [
|
||||
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/$',
|
||||
pretix.presale.views.organizer.OrganizerIcalDownload.as_view(),
|
||||
name='organizer.ical'),
|
||||
|
||||
@@ -65,7 +65,7 @@ from pretix.base.models import (
|
||||
)
|
||||
from pretix.base.models.event import SubEvent
|
||||
from pretix.base.models.items import (
|
||||
ItemBundle, SubEventItem, SubEventItemVariation,
|
||||
ItemAddOn, ItemBundle, SubEventItem, SubEventItemVariation,
|
||||
)
|
||||
from pretix.base.services.quotas import QuotaAvailability
|
||||
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,
|
||||
)
|
||||
),
|
||||
mandatory_priced_addons=Exists(
|
||||
ItemAddOn.objects.filter(
|
||||
base_item_id=OuterRef('pk'),
|
||||
min_count__gte=1,
|
||||
price_included=False
|
||||
)
|
||||
),
|
||||
requires_seat=requires_seat,
|
||||
).filter(
|
||||
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.http import Http404, HttpResponse
|
||||
from django.shortcuts import redirect
|
||||
from django.templatetags.static import static
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.formats import date_format, get_format
|
||||
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 (
|
||||
SHORT_MONTH_DAY_FORMAT, WEEK_FORMAT,
|
||||
)
|
||||
from pretix.helpers.thumb import get_thumbnail
|
||||
from pretix.multidomain.urlreverse import eventreverse
|
||||
from pretix.presale.ical import get_public_ical
|
||||
from pretix.presale.views import OrganizerViewMixin
|
||||
@@ -1170,3 +1172,11 @@ class OrganizerIcalDownload(OrganizerViewMixin, View):
|
||||
if request.organizer.settings.meta_noindex:
|
||||
resp['X-Robots-Tag'] = 'noindex'
|
||||
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_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('/'):
|
||||
SITE_URL = SITE_URL[:-1]
|
||||
|
||||
|
||||
+710
-688
File diff suppressed because it is too large
Load Diff
@@ -4,13 +4,13 @@
|
||||
"private": true,
|
||||
"scripts": {},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.18.6",
|
||||
"@babel/preset-env": "^7.18.6",
|
||||
"@babel/core": "^7.18.10",
|
||||
"@babel/preset-env": "^7.18.9",
|
||||
"@rollup/plugin-babel": "^5.3.1",
|
||||
"@rollup/plugin-node-resolve": "^13.3.0",
|
||||
"vue": "^2.7.0",
|
||||
"rollup": "^2.75.7",
|
||||
"vue": "^2.7.8",
|
||||
"rollup": "^2.77.2",
|
||||
"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
|
||||
editor.pdf.getPage(page_number).then(function (page) {
|
||||
console.log('Page loaded');
|
||||
var canvas = document.getElementById('pdf-canvas');
|
||||
|
||||
var scale = editor.$cva.width() / page.getViewport(1.0).width;
|
||||
@@ -326,7 +325,6 @@ var editor = {
|
||||
editor.pdf_page_number = page_number
|
||||
editor._init_page_nav();
|
||||
|
||||
console.log('Page rendered');
|
||||
if (dump || !editor._fabric_loaded) {
|
||||
editor._init_fabric(dump);
|
||||
} else {
|
||||
@@ -349,7 +347,6 @@ var editor = {
|
||||
}
|
||||
$("#page_nav").append($li)
|
||||
$a.on("click", function (event) {
|
||||
console.log("switch to page", $(this).attr("data-page"));
|
||||
editor.fabric.deactivateAll();
|
||||
editor._load_page(parseInt($(this).attr("data-page")));
|
||||
event.preventDefault();
|
||||
@@ -369,7 +366,6 @@ var editor = {
|
||||
// Asynchronous download of PDF
|
||||
var loadingTask = PDFJS.getDocument(url);
|
||||
loadingTask.promise.then(function (pdf) {
|
||||
console.log('PDF loaded');
|
||||
|
||||
editor.pdf = pdf;
|
||||
editor.pdf_page_count = pdf.numPages;
|
||||
@@ -422,7 +418,6 @@ var editor = {
|
||||
}
|
||||
|
||||
editor._fabric_loaded = true;
|
||||
console.log("Fabric loaded");
|
||||
if (editor._window_loaded) {
|
||||
editor._ready();
|
||||
}
|
||||
@@ -430,7 +425,6 @@ var editor = {
|
||||
|
||||
_window_load_event: function () {
|
||||
editor._window_loaded = true;
|
||||
console.log("Window loaded");
|
||||
if (editor._fabric_loaded) {
|
||||
editor._ready();
|
||||
}
|
||||
@@ -520,7 +514,7 @@ var editor = {
|
||||
}
|
||||
},
|
||||
|
||||
_update_values_from_toolbox: function () {
|
||||
_update_values_from_toolbox: function (e) {
|
||||
var o = editor.fabric.getActiveObject();
|
||||
if (!o) {
|
||||
o = editor.fabric.getActiveGroup();
|
||||
@@ -553,8 +547,16 @@ var editor = {
|
||||
$("#toolbox-content-other-help").toggle($("#toolbox-content").val() === "other" || $("#toolbox-content").val() === "other_i18n");
|
||||
o.content = $("#toolbox-content").val();
|
||||
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();
|
||||
} 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 = {}
|
||||
$("#toolbox-content-other-i18n textarea").each(function () {
|
||||
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");
|
||||
o.content = $("#toolbox-content").val();
|
||||
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());
|
||||
} 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 = {}
|
||||
$("#toolbox-content-other-i18n textarea").each(function () {
|
||||
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();
|
||||
editor.fabric.renderAll();
|
||||
},
|
||||
@@ -1055,14 +1073,14 @@ var editor = {
|
||||
$("#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._create_savepoint);
|
||||
$("#toolbox button.toggling").bind('click change', function () {
|
||||
$("#toolbox button.toggling").bind('click change', function (e) {
|
||||
if ($(this).is(".option")) {
|
||||
$(this).addClass("active");
|
||||
$(this).parent().siblings().find("button").removeClass("active");
|
||||
} else {
|
||||
$(this).toggleClass("active");
|
||||
}
|
||||
editor._update_values_from_toolbox();
|
||||
editor._update_values_from_toolbox(e);
|
||||
editor._create_savepoint();
|
||||
});
|
||||
$("#toolbox .colorpickerfield").bind('changeColor', editor._update_values_from_toolbox);
|
||||
|
||||
@@ -665,33 +665,8 @@ var form_handlers = function (el) {
|
||||
questions_init_photos(el);
|
||||
};
|
||||
|
||||
$(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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$("#sumtoggle").find("button").click(function () {
|
||||
function setup_basics(el) {
|
||||
el.find("#sumtoggle").find("button").click(function () {
|
||||
$(".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 .count").toggle($(this).attr("data-target") === ".count");
|
||||
@@ -700,18 +675,18 @@ $(function () {
|
||||
$(this).addClass("active");
|
||||
});
|
||||
|
||||
$('.collapsible').collapse();
|
||||
$("input[data-toggle=radiocollapse]").change(function () {
|
||||
el.find('.collapsible').collapse();
|
||||
el.find("input[data-toggle=radiocollapse]").change(function () {
|
||||
$($(this).attr("data-parent")).find(".collapse.in").collapse('hide');
|
||||
$($(this).attr("data-target")).collapse('show');
|
||||
});
|
||||
$("div.collapsed").removeClass("collapsed").addClass("collapse");
|
||||
$(".has-error").each(function () {
|
||||
el.find("div.collapsed").removeClass("collapsed").addClass("collapse");
|
||||
el.find(".has-error").each(function () {
|
||||
$(this).closest("div.panel-collapse").collapse("show");
|
||||
});
|
||||
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
$('[data-toggle="tooltip_html"]').tooltip({
|
||||
el.find('[data-toggle="tooltip"]').tooltip();
|
||||
el.find('[data-toggle="tooltip_html"]').tooltip({
|
||||
'html': true,
|
||||
'whiteList': {
|
||||
// Global attributes allowed on any supplied element below.
|
||||
@@ -733,7 +708,7 @@ $(function () {
|
||||
if (url.match('#')) {
|
||||
$('.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) {
|
||||
// only append hash if not inside a .panel
|
||||
window.location.hash = this.hash;
|
||||
@@ -741,7 +716,7 @@ $(function () {
|
||||
});
|
||||
|
||||
// Event wizard
|
||||
$("#event-slug-random-generate").click(function () {
|
||||
el.find("#event-slug-random-generate").click(function () {
|
||||
var url = $(this).attr("data-rng-url");
|
||||
$("#id_basics-slug").val("Generating...");
|
||||
$.getJSON(url, function (data) {
|
||||
@@ -749,10 +724,7 @@ $(function () {
|
||||
});
|
||||
});
|
||||
|
||||
form_handlers($("body"));
|
||||
$(document).trigger("pretix:bind-forms");
|
||||
|
||||
$(".qrcode-canvas").each(function () {
|
||||
el.find(".qrcode-canvas").each(function () {
|
||||
$(this).qrcode(
|
||||
{
|
||||
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);
|
||||
|
||||
$(".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");
|
||||
$box.find("input[name=decouple]").val($(this).val());
|
||||
$box.find("[data-propagated-locked]").prop("readonly", false);
|
||||
@@ -773,7 +745,7 @@ $(function () {
|
||||
});
|
||||
|
||||
// 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 $actionButtons = $(".batch-select-actions button", this.form);
|
||||
var countLabels = $("<span></span>").appendTo($actionButtons);
|
||||
@@ -852,7 +824,7 @@ $(function () {
|
||||
if (!nrOfChecked) countLabels.empty();
|
||||
else countLabels.text(" ("+nrOfChecked+")");
|
||||
|
||||
if (!allChecked) $selectAll.find("input").prop("checked", false);
|
||||
if (!allChecked) $selectAll.find("input").prop("checked", false);
|
||||
|
||||
$actionButtons.attr("disabled", !nrOfChecked);
|
||||
$toggle.prop("checked", allChecked).prop("indeterminate", nrOfChecked > 0 && !allChecked);
|
||||
@@ -875,7 +847,7 @@ $(function () {
|
||||
});
|
||||
|
||||
// Items and categories
|
||||
$(".internal-name-wrapper").each(function () {
|
||||
el.find(".internal-name-wrapper").each(function () {
|
||||
if ($(this).find("input").val() === "") {
|
||||
var $fg = $(this).find(".form-group");
|
||||
$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();
|
||||
var $current = $(".qr-code-overlay[data-qrcode='" + $(this).attr("data-qrcode") + "']");
|
||||
if ($current.length) {
|
||||
@@ -926,6 +898,37 @@ $(function () {
|
||||
});
|
||||
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);
|
||||
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"));
|
||||
$container.find("li:not(.query-holder)").remove();
|
||||
var lastQuery = "";
|
||||
|
||||
$query.on("change", function () {
|
||||
if ($container.attr("data-typeahead-field") && $query.val() === "") {
|
||||
$container.removeClass('focused');
|
||||
$container.find("li:not(.query-holder)").remove();
|
||||
return;
|
||||
}
|
||||
var runQueryTimeout = null;
|
||||
function runQuery() {
|
||||
lastQuery = $query.val();
|
||||
var thisQuery = $query.val();
|
||||
$.getJSON(
|
||||
@@ -119,6 +114,17 @@ $(function () {
|
||||
$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) {
|
||||
var $selected = $container.find(".active");
|
||||
|
||||
@@ -191,6 +191,7 @@ function setup_basics(el) {
|
||||
else $(":input", this).get(0).focus();
|
||||
});
|
||||
el.find(".alert-danger").first().each(function() {
|
||||
var container = this;
|
||||
var content = $("<ul></ul>").click(function(e) {
|
||||
var input = $(e.target.hash).get(0);
|
||||
if (input) input.focus();
|
||||
@@ -199,13 +200,14 @@ function setup_basics(el) {
|
||||
});
|
||||
$(".has-error").each(function() {
|
||||
var target = target = $(":input", this);
|
||||
if (!target || !target.attr("aria-describedby")) return;
|
||||
var desc = $("#" + target.attr("aria-describedby").split(' ', 1)[0]);
|
||||
var desc = target && target.attr("aria-describedby") ? document.getElementById(target.attr("aria-describedby").split(' ', 1)[0]) : null;
|
||||
if (!target || !desc || desc == container) return;
|
||||
|
||||
// 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 $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))
|
||||
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.
|
||||
# We can upgrade markdown again once django-bootstrap3 upgrades or once we drop Python 3.6 and 3.7
|
||||
'mt-940==4.23.*',
|
||||
'oauthlib==3.1.*',
|
||||
'oauthlib==3.2.*',
|
||||
'openpyxl==3.0.*',
|
||||
'packaging',
|
||||
'paypalrestsdk==1.13.*',
|
||||
@@ -211,7 +211,7 @@ setup(
|
||||
'psycopg2-binary',
|
||||
'pycountry',
|
||||
'pycparser==2.21',
|
||||
'PyPDF2==1.27.9',
|
||||
'PyPDF2==2.9.*',
|
||||
'python-bidi==0.4.*', # Support for Arabic in reportlab
|
||||
'python-dateutil==2.8.*',
|
||||
'python-u2flib-server==4.*',
|
||||
@@ -248,11 +248,11 @@ setup(
|
||||
'pytest-cache',
|
||||
'pytest-cov',
|
||||
'pytest-django==4.*',
|
||||
'pytest-mock>=2.0,<3.7',
|
||||
'pytest-rerunfailures>=9,<11',
|
||||
'pytest-mock==3.8.*',
|
||||
'pytest-rerunfailures==10.2',
|
||||
'pytest-sugar',
|
||||
'pytest-xdist==1.31.*',
|
||||
'pytest==6.*',
|
||||
'pytest-xdist==2.5.*',
|
||||
'pytest==7.*',
|
||||
'responses',
|
||||
],
|
||||
'memcached': ['pylibmc'],
|
||||
|
||||
@@ -82,6 +82,7 @@ def test_customer_create(token_client, organizer):
|
||||
data={
|
||||
'identifier': 'IGNORED',
|
||||
'email': 'bar@example.com',
|
||||
'password': 'foobar',
|
||||
'name_parts': {
|
||||
"_scheme": "given_family",
|
||||
'given_name': 'John',
|
||||
@@ -99,6 +100,7 @@ def test_customer_create(token_client, organizer):
|
||||
assert customer.is_active
|
||||
assert customer.name == 'John Doe'
|
||||
assert customer.is_verified
|
||||
assert customer.check_password('foobar')
|
||||
assert len(djmail.outbox) == 0
|
||||
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ def inputfile_factory():
|
||||
'D': 'Test',
|
||||
'E': 'Baz',
|
||||
'F': '0.00',
|
||||
'G': 'AU',
|
||||
'G': 'XK',
|
||||
'H': '',
|
||||
'I': 'Foo,Bar',
|
||||
'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 (
|
||||
generate_cancellation, generate_invoice,
|
||||
)
|
||||
from pretix.base.services.tax import VATIDFinalError, VATIDTemporaryError
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -1563,8 +1564,8 @@ def test_check_vatid(client, env):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
with scopes_disabled():
|
||||
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:
|
||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
||||
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||
mock_validate.return_value = 'AT123456'
|
||||
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
|
||||
assert 'alert-success' in response.content.decode()
|
||||
ia.refresh_from_db()
|
||||
@@ -1576,8 +1577,8 @@ def test_check_vatid_no_entered(client, env):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
with scopes_disabled():
|
||||
ia = InvoiceAddress.objects.create(order=env[2], is_business=True, country=Country('AT'))
|
||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
||||
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||
mock_validate.return_value = 'AT123456'
|
||||
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
|
||||
assert 'alert-danger' in response.content.decode()
|
||||
ia.refresh_from_db()
|
||||
@@ -1589,12 +1590,10 @@ def test_check_vatid_invalid_country(client, env):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
with scopes_disabled():
|
||||
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:
|
||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
||||
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
|
||||
assert 'alert-danger' in response.content.decode()
|
||||
ia.refresh_from_db()
|
||||
assert not ia.vat_id_validated
|
||||
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
|
||||
assert 'alert-danger' in response.content.decode()
|
||||
ia.refresh_from_db()
|
||||
assert not ia.vat_id_validated
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -1602,8 +1601,8 @@ def test_check_vatid_noneu_country(client, env):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
with scopes_disabled():
|
||||
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:
|
||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
||||
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||
mock_validate.return_value = 'AT123456'
|
||||
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
|
||||
assert 'alert-danger' in response.content.decode()
|
||||
ia.refresh_from_db()
|
||||
@@ -1615,8 +1614,8 @@ def test_check_vatid_no_country(client, env):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
with scopes_disabled():
|
||||
ia = InvoiceAddress.objects.create(order=env[2], is_business=True, vat_id='ATU1234567')
|
||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
||||
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||
mock_validate.return_value = 'AT123456'
|
||||
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
|
||||
assert 'alert-danger' in response.content.decode()
|
||||
ia.refresh_from_db()
|
||||
@@ -1626,8 +1625,8 @@ def test_check_vatid_no_country(client, env):
|
||||
@pytest.mark.django_db
|
||||
def test_check_vatid_no_invoiceaddress(client, env):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
||||
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||
mock_validate.return_value = 'AT123456'
|
||||
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
|
||||
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')
|
||||
with scopes_disabled():
|
||||
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):
|
||||
import vat_moss.errors
|
||||
raise vat_moss.errors.InvalidError('Fail')
|
||||
raise VATIDFinalError('Fail')
|
||||
|
||||
mock_validate.side_effect = raiser
|
||||
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')
|
||||
with scopes_disabled():
|
||||
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):
|
||||
import vat_moss.errors
|
||||
raise vat_moss.errors.WebServiceUnavailableError('Fail')
|
||||
raise VATIDTemporaryError('Fail')
|
||||
|
||||
mock_validate.side_effect = raiser
|
||||
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
|
||||
|
||||
@@ -39,7 +39,7 @@ from io import BytesIO
|
||||
import pytest
|
||||
from django.utils.timezone import now
|
||||
from django_scopes import scope
|
||||
from PyPDF2 import PdfFileReader
|
||||
from PyPDF2 import PdfReader
|
||||
|
||||
from pretix.base.models import (
|
||||
Event, Item, ItemVariation, Order, OrderPosition, Organizer,
|
||||
@@ -100,8 +100,8 @@ def test_generate_pdf(env):
|
||||
'include_pending': True
|
||||
})
|
||||
assert ftype == 'application/pdf'
|
||||
pdf = PdfFileReader(BytesIO(buf))
|
||||
assert pdf.numPages == 2
|
||||
pdf = PdfReader(BytesIO(buf))
|
||||
assert len(pdf.pages) == 2
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -115,5 +115,5 @@ def test_generate_pdf_multi(env):
|
||||
'include_pending': True
|
||||
})
|
||||
assert ftype == 'application/pdf'
|
||||
pdf = PdfFileReader(BytesIO(buf))
|
||||
assert pdf.numPages == 1
|
||||
pdf = PdfReader(BytesIO(buf))
|
||||
assert len(pdf.pages) == 1
|
||||
|
||||
@@ -26,7 +26,7 @@ from io import BytesIO
|
||||
import pytest
|
||||
from django.utils.timezone import now
|
||||
from django_scopes import scope
|
||||
from PyPDF2 import PdfFileReader
|
||||
from PyPDF2 import PdfReader
|
||||
|
||||
from pretix.base.models import (
|
||||
Event, Item, ItemVariation, Order, OrderPosition, Organizer,
|
||||
@@ -70,5 +70,5 @@ def test_generate_pdf(env0):
|
||||
o = PdfTicketOutput(event)
|
||||
fname, ftype, buf = o.generate(order.positions.first())
|
||||
assert ftype == 'application/pdf'
|
||||
pdf = PdfFileReader(BytesIO(buf))
|
||||
assert pdf.numPages == 1
|
||||
pdf = PdfReader(BytesIO(buf))
|
||||
assert len(pdf.pages) == 1
|
||||
|
||||
@@ -47,6 +47,7 @@ from pretix.base.models.items import (
|
||||
ItemAddOn, ItemBundle, ItemVariation, SubEventItem, SubEventItemVariation,
|
||||
)
|
||||
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.sessions import get_cart_session_key
|
||||
|
||||
@@ -139,8 +140,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
price=23, expires=now() + timedelta(minutes=10)
|
||||
)
|
||||
|
||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
||||
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||
mock_validate.return_value = 'AT123456'
|
||||
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||
'is_business': 'business',
|
||||
'company': 'Foo',
|
||||
@@ -163,8 +164,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
def test_reverse_charge_enable_then_disable(self):
|
||||
self.test_reverse_charge()
|
||||
|
||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
||||
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||
mock_validate.return_value = 'AT123456'
|
||||
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||
'is_business': 'individual',
|
||||
'name': 'Bar',
|
||||
@@ -195,10 +196,9 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
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):
|
||||
import vat_moss.errors
|
||||
raise vat_moss.errors.InvalidError()
|
||||
raise VATIDFinalError('final')
|
||||
|
||||
mock_validate.side_effect = raiser
|
||||
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)
|
||||
)
|
||||
|
||||
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')
|
||||
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||
'is_business': 'business',
|
||||
@@ -263,8 +263,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
price=23, expires=now() + timedelta(minutes=10)
|
||||
)
|
||||
|
||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
||||
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||
mock_validate.return_value = 'AT123456'
|
||||
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||
'is_business': 'business',
|
||||
'company': 'Foo',
|
||||
@@ -296,20 +296,18 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
price=23, expires=now() + timedelta(minutes=10)
|
||||
)
|
||||
|
||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
||||
resp = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||
'is_business': 'business',
|
||||
'company': 'Foo',
|
||||
'name': 'Bar',
|
||||
'street': 'Baz',
|
||||
'zipcode': '12345',
|
||||
'city': 'Here',
|
||||
'country': 'FR',
|
||||
'vat_id': 'AT123456',
|
||||
'email': 'admin@localhost'
|
||||
}, follow=True)
|
||||
assert 'alert-danger' in resp.content.decode()
|
||||
resp = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||
'is_business': 'business',
|
||||
'company': 'Foo',
|
||||
'name': 'Bar',
|
||||
'street': 'Baz',
|
||||
'zipcode': '12345',
|
||||
'city': 'Here',
|
||||
'country': 'FR',
|
||||
'vat_id': 'AT123456',
|
||||
'email': 'admin@localhost'
|
||||
}, follow=True)
|
||||
assert 'alert-danger' in resp.content.decode()
|
||||
|
||||
cr1.refresh_from_db()
|
||||
assert cr1.price == Decimal('23.00')
|
||||
@@ -326,10 +324,9 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
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):
|
||||
import vat_moss.errors
|
||||
raise vat_moss.errors.WebServiceUnavailableError('Fail')
|
||||
raise VATIDTemporaryError('temp')
|
||||
|
||||
mock_validate.side_effect = raiser
|
||||
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)
|
||||
)
|
||||
|
||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
||||
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||
mock_validate.return_value = 'AT123456'
|
||||
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||
'is_business': 'business',
|
||||
'company': 'Foo',
|
||||
@@ -401,8 +398,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
price=23, expires=now() + timedelta(minutes=10)
|
||||
)
|
||||
|
||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
||||
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||
mock_validate.return_value = 'AT123456'
|
||||
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||
'is_business': 'business',
|
||||
'company': 'Foo',
|
||||
@@ -418,7 +415,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
cr1.refresh_from_db()
|
||||
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')
|
||||
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||
'is_business': 'business',
|
||||
@@ -465,8 +462,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
cr1.refresh_from_db()
|
||||
assert cr1.price == Decimal('23.00')
|
||||
|
||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
||||
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||
mock_validate.return_value = 'AT123456'
|
||||
r = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||
'is_business': 'business',
|
||||
'company': 'Foo',
|
||||
@@ -525,8 +522,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
price=23, expires=now() + timedelta(minutes=10)
|
||||
)
|
||||
|
||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
||||
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||
mock_validate.return_value = 'AT123456'
|
||||
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||
'is_business': 'individual',
|
||||
'name': 'Bar',
|
||||
@@ -577,8 +574,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
voucher=self.event.vouchers.create()
|
||||
)
|
||||
|
||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
||||
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||
mock_validate.return_value = 'AT123456'
|
||||
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||
'is_business': 'individual',
|
||||
'name': 'Bar',
|
||||
@@ -605,8 +602,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
def test_country_taxing_switch(self):
|
||||
self._test_country_taxing()
|
||||
|
||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
||||
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||
mock_validate.return_value = 'AT123456'
|
||||
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||
'is_business': 'individual',
|
||||
'name': 'Bar',
|
||||
@@ -657,8 +654,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
assert cr1.price == Decimal('28.56')
|
||||
assert cr1.tax_rate == Decimal('19.00')
|
||||
|
||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
||||
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||
mock_validate.return_value = 'AT123456'
|
||||
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||
'is_business': 'business',
|
||||
'company': 'Foo',
|
||||
@@ -721,8 +718,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
assert cr1.price == Decimal('47.60')
|
||||
assert cr1.tax_rate == Decimal('19.00')
|
||||
|
||||
with mock.patch('vat_moss.id.validate') as mock_validate:
|
||||
mock_validate.return_value = ('AT', 'AT123456', 'Foo')
|
||||
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||
mock_validate.return_value = 'AT123456'
|
||||
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||
'is_business': 'business',
|
||||
'company': 'Foo',
|
||||
|
||||
Reference in New Issue
Block a user