This commit is contained in:
Weblate
2022-09-06 14:08:23 +02:00
64 changed files with 1589 additions and 984 deletions
+1
View File
@@ -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;
+7
View File
@@ -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
}
+2 -2
View File
@@ -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
+10
View File
@@ -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.
+1 -1
View File
@@ -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"
+26 -1
View File
@@ -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 []
)
)
+2 -1
View File
@@ -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):
+1
View File
@@ -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
+3 -3
View File
@@ -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)
+40
View File
@@ -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'),
),
)
+14
View File
@@ -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 ''),
+4 -1
View File
@@ -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'),
+14 -10
View File
@@ -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
+1 -1
View File
@@ -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):
+2 -2
View File
@@ -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
View File
@@ -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',
})
+13 -4
View File
@@ -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
View File
@@ -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}')
+8 -1
View File
@@ -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>
&middot; <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;">&nbsp;</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;">&nbsp;</div>
</td>
</tr>
+8 -1
View File
@@ -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
+1 -1
View File
@@ -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,
+2 -1
View File
@@ -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>
+27 -13
View File
@@ -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()
+11 -7
View File
@@ -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:
+5 -5
View File
@@ -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",
+3 -3
View File
@@ -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(
+10 -1
View File
@@ -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)
+21 -15
View File
@@ -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',
})
+1 -1
View File
@@ -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):
+2
View File
@@ -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');
}
},
+2 -2
View File
@@ -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))
+13
View File
@@ -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,
+5
View File
@@ -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(
+1 -4
View File
@@ -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 %}
+7
View File
@@ -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'),
+8 -1
View File
@@ -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,
+10
View File
@@ -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"))
+1 -1
View File
@@ -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]
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -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);
+48 -45
View File
@@ -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
View File
@@ -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'],
+2
View File
@@ -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
+1 -1
View File
@@ -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',
+198
View File
@@ -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
+19 -22
View File
@@ -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)
+5 -5
View File
@@ -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
+41 -44
View File
@@ -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',