mirror of
https://github.com/pretix/pretix.git
synced 2026-09-11 15:54:42 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
231120f7d1 |
@@ -123,24 +123,7 @@ jobs:
|
||||
working-directory: ./src
|
||||
run: make all compress
|
||||
- name: Install Playwright browsers
|
||||
run: playwright install --with-deps
|
||||
run: playwright install
|
||||
- name: Run E2E tests
|
||||
working-directory: ./src
|
||||
run: PRETIX_CONFIG_FILE=tests/ci_postgres.cfg py.test tests/e2e/ -v --maxfail=10 --tracing=retain-on-failure
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
name: playwright-traces
|
||||
path: test-results/
|
||||
- name: Log trace instructions
|
||||
if: steps.check-traces.outputs.found == 'true'
|
||||
run: |
|
||||
{
|
||||
echo "## 🎭 Playwright traces available"
|
||||
echo ""
|
||||
echo "Some tests failed or retried and produced traces."
|
||||
echo ""
|
||||
echo "1. Download the **playwright-traces-${{ github.run_id }}** artifact from this run (link in the **Summary** tab, under Artifacts)."
|
||||
echo "2. Unzip it."
|
||||
echo "3. Go to https://trace.playwright.dev and drag \`trace.zip\` into the page — or run \`npx playwright show-trace trace.zip\` locally."
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
run: PRETIX_CONFIG_FILE=tests/ci_postgres.cfg py.test tests/e2e/ -v --maxfail=10
|
||||
|
||||
@@ -1,244 +0,0 @@
|
||||
Event Meta Properties
|
||||
=====================
|
||||
|
||||
Resource description
|
||||
--------------------
|
||||
|
||||
An event meta property is used to to define meta information fields for its events.
|
||||
This information can be re-used, for example, in ticket layouts.
|
||||
|
||||
The event meta property resource contains the following public fields:
|
||||
|
||||
.. rst-class:: rest-resource-table
|
||||
|
||||
===================================== ========================== =======================================================
|
||||
Field Type Description
|
||||
===================================== ========================== =======================================================
|
||||
id integer Unique ID for this property
|
||||
name string Name of the property
|
||||
default string Value of the default option
|
||||
required boolean If ``true``, an event can only be taken live if the
|
||||
property is set. In event series, it's always optional
|
||||
to set a value for individual dates
|
||||
protected boolean If ``true``, the value for an event can only be changed
|
||||
by organizer-level administrators
|
||||
filter_public boolean If ``true``, this property will be shown to filter
|
||||
events in the public event list and calendar
|
||||
public_label string Public name of the property
|
||||
filter_allowed boolean If ``true``, this property will be shown to filter
|
||||
events or reports in the backend, and it can also be
|
||||
used for hidden filter parameters in the frontend
|
||||
choices list of objects List of JSON objects representing all permitted values
|
||||
for this property, or ``null`` for no limitation.
|
||||
Each choice object has a required internal name named
|
||||
``key`` and optional public name named ``label``
|
||||
consisting of a dictionary of i18n string translations,
|
||||
as well as other implementation based key-value-pairs
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
Endpoints
|
||||
---------
|
||||
|
||||
.. http:get:: /api/v1/organizers/(organizer)/event_meta_properties/
|
||||
|
||||
Returns a list of all meta properties for the organizer.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /api/v1/organizers/bigevents/meta_properties/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"count": 1,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Color",
|
||||
"default": "blue",
|
||||
"required": false,
|
||||
"protected": false,
|
||||
"filter_public": false,
|
||||
"public_label": {},
|
||||
"filter_allowed": true,
|
||||
"choices": [
|
||||
{
|
||||
"key": "blue",
|
||||
"ORDER": 1,
|
||||
"label": {
|
||||
"en": "Blue"
|
||||
},
|
||||
"DELETE": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer
|
||||
:statuscode 200: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer does not exist **or** you have no permission to view this resource.
|
||||
|
||||
.. http:get:: /api/v1/organizers/(organizer)/event_meta_properties/(id)/
|
||||
|
||||
Returns information on one property, identified by its id.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /api/v1/organizers/bigevents/event_meta_properties/1/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Color",
|
||||
"default": "blue",
|
||||
"required": false,
|
||||
"protected": false,
|
||||
"filter_public": false,
|
||||
"public_label": {},
|
||||
"filter_allowed": true,
|
||||
"choices": null
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer
|
||||
:param id: The ``id`` field of the meta property to retrieve
|
||||
:statuscode 200: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer does not exist **or** you have no permission to view this resource.
|
||||
|
||||
.. http:post:: /api/v1/organizers/(organizer)/event_meta_properties/
|
||||
|
||||
Creates a new meta property
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/v1/organizers/bigevents/event_meta_properties/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "ref-code",
|
||||
"default": "abcde",
|
||||
"required": true,
|
||||
"choices": null
|
||||
}
|
||||
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
{
|
||||
"id": 2,
|
||||
"name": "reference",
|
||||
"default": "abcde",
|
||||
"required": true,
|
||||
"protected": false,
|
||||
"filter_public": false,
|
||||
"public_label": null,
|
||||
"filter_allowed": true,
|
||||
"choices": null
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer
|
||||
:statuscode 201: no error
|
||||
:statuscode 400: The meta property could not be created due to invalid submitted data.
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer does not exist **or** you have no permission to create this resource.
|
||||
|
||||
.. http:patch:: /api/v1/organizers/(organizer)/event_meta_properties/(id)/
|
||||
|
||||
Update a meta property. You can also use ``PUT`` instead of ``PATCH``. With ``PUT``, you have to provide
|
||||
all fields of the resource, other fields will be reset to default. With ``PATCH``, you only need to provide the
|
||||
fields that you want to change.
|
||||
|
||||
You can change all fields of the resource except the ``id`` field.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
PATCH /api/v1/organizers/bigevents/event_meta_properties/2/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
Content-Type: application/json
|
||||
Content-Length: 94
|
||||
|
||||
{
|
||||
"required": false
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": 3,
|
||||
"name": "reference",
|
||||
"default": "abcde",
|
||||
"required": false,
|
||||
"protected": false,
|
||||
"filter_public": false,
|
||||
"public_label": null,
|
||||
"filter_allowed": true,
|
||||
"choices": null
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer
|
||||
:param id: The ``id`` field of the meta property to modify
|
||||
:statuscode 200: no error
|
||||
:statuscode 400: The property could not be modified due to invalid submitted data
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer does not exist **or** you have no permission to change this resource.
|
||||
|
||||
.. http:delete:: /api/v1/organizers/(organizer)/event_meta_properties/(id)/
|
||||
|
||||
Delete a meta property.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
DELETE /api/v1/organizers/bigevents/event_meta_properties/1/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 204 No Content
|
||||
Vary: Accept
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer
|
||||
:param id: The ``id`` field of the meta property to delete
|
||||
:statuscode 204: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer does not exist **or** you have no permission to delete this resource.
|
||||
@@ -12,7 +12,6 @@ at :ref:`plugin-docs`.
|
||||
organizers
|
||||
events
|
||||
subevents
|
||||
event_meta_properties
|
||||
taxrules
|
||||
categories
|
||||
items
|
||||
|
||||
@@ -40,10 +40,9 @@ from pretix.api.serializers.settings import SettingsSerializer
|
||||
from pretix.base.auth import get_auth_backends
|
||||
from pretix.base.i18n import get_language_without_region
|
||||
from pretix.base.models import (
|
||||
Customer, Device, EventMetaProperty, GiftCard, GiftCardAcceptance,
|
||||
GiftCardTransaction, Membership, MembershipType, OrderPosition, Organizer,
|
||||
ReusableMedium, SalesChannel, SeatingPlan, Team, TeamAPIToken, TeamInvite,
|
||||
User,
|
||||
Customer, Device, GiftCard, GiftCardAcceptance, GiftCardTransaction,
|
||||
Membership, MembershipType, OrderPosition, Organizer, ReusableMedium,
|
||||
SalesChannel, SeatingPlan, Team, TeamAPIToken, TeamInvite, User,
|
||||
)
|
||||
from pretix.base.models.seating import SeatingPlanLayoutValidator
|
||||
from pretix.base.permissions import (
|
||||
@@ -641,12 +640,3 @@ class OrganizerSettingsSerializer(SettingsSerializer):
|
||||
)
|
||||
# TODO: make sure pub is always correct
|
||||
return 'pub/' + fname
|
||||
|
||||
|
||||
class EventMetaPropertiesSerializer(I18nAwareModelSerializer):
|
||||
class Meta:
|
||||
model = EventMetaProperty
|
||||
fields = (
|
||||
'id', 'name', 'default', 'required', 'protected', 'filter_public', 'public_label', 'filter_allowed',
|
||||
'choices'
|
||||
)
|
||||
|
||||
@@ -68,7 +68,6 @@ orga_router.register(r'scheduled_exports', exporters.ScheduledOrganizerExportVie
|
||||
orga_router.register(r'exporters', exporters.OrganizerExportersViewSet, basename='exporters')
|
||||
orga_router.register(r'transactions', order.OrganizerTransactionViewSet)
|
||||
orga_router.register(r'orderpositions', order.OrganizerOrderPositionViewSet, basename='orderpositions')
|
||||
orga_router.register(r'event_meta_properties', organizer.EventMetaPropertiesViewSet)
|
||||
|
||||
team_router = routers.DefaultRouter()
|
||||
team_router.register(r'members', organizer.TeamMemberViewSet)
|
||||
|
||||
@@ -44,16 +44,15 @@ from pretix.api.models import OAuthAccessToken
|
||||
from pretix.api.pagination import TotalOrderingFilter
|
||||
from pretix.api.serializers.organizer import (
|
||||
CustomerCreateSerializer, CustomerSerializer, DeviceSerializer,
|
||||
EventMetaPropertiesSerializer, GiftCardSerializer,
|
||||
GiftCardTransactionSerializer, MembershipSerializer,
|
||||
GiftCardSerializer, GiftCardTransactionSerializer, MembershipSerializer,
|
||||
MembershipTypeSerializer, OrganizerSerializer, OrganizerSettingsSerializer,
|
||||
SalesChannelSerializer, SeatingPlanSerializer, TeamAPITokenSerializer,
|
||||
TeamInviteSerializer, TeamMemberSerializer, TeamSerializer,
|
||||
)
|
||||
from pretix.base.models import (
|
||||
Customer, Device, Event, EventMetaProperty, GiftCard, GiftCardTransaction,
|
||||
LogEntry, Membership, MembershipType, Organizer, SalesChannel, SeatingPlan,
|
||||
Team, TeamAPIToken, TeamInvite, User,
|
||||
Customer, Device, Event, GiftCard, GiftCardTransaction, LogEntry,
|
||||
Membership, MembershipType, Organizer, SalesChannel, SeatingPlan, Team,
|
||||
TeamAPIToken, TeamInvite, User,
|
||||
)
|
||||
from pretix.base.plugins import (
|
||||
PLUGIN_LEVEL_EVENT, PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID,
|
||||
@@ -847,50 +846,3 @@ class SalesChannelViewSet(viewsets.ModelViewSet):
|
||||
data={'id': instance.pk}
|
||||
)
|
||||
instance.delete()
|
||||
|
||||
|
||||
class EventMetaPropertiesViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = EventMetaPropertiesSerializer
|
||||
queryset = EventMetaProperty.objects.none()
|
||||
write_permission = 'organizer.settings.general:write'
|
||||
|
||||
def get_queryset(self):
|
||||
qs = EventMetaProperty.objects.all()
|
||||
return qs
|
||||
|
||||
def get_serializer_context(self):
|
||||
ctx = super().get_serializer_context()
|
||||
ctx['organizer'] = self.request.organizer
|
||||
return ctx
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_destroy(self, instance):
|
||||
instance.log_action(
|
||||
'pretix.property.deleted',
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data={'id': instance.pk}
|
||||
)
|
||||
instance.delete()
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_create(self, serializer):
|
||||
inst = serializer.save(organizer_id=self.request.organizer.pk)
|
||||
serializer.instance.log_action(
|
||||
'pretix.property.created',
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data=self.request.data,
|
||||
)
|
||||
return inst
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_update(self, serializer):
|
||||
inst = serializer.save(organizer_id=self.request.organizer.pk)
|
||||
serializer.instance.log_action(
|
||||
'pretix.property.changed',
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data=self.request.data,
|
||||
)
|
||||
return inst
|
||||
|
||||
@@ -899,7 +899,7 @@ class BaseQuestionsForm(forms.Form):
|
||||
field.widget.attrs['data-question-dependency-values'] = escapejson_attr(json.dumps(q.dependency_values))
|
||||
if q.type != 'M':
|
||||
field.widget.attrs['required'] = q.required and not self.all_optional
|
||||
field._required = q.required and not self.all_optional
|
||||
field._required = q.required and not self.all_optional
|
||||
field.required = False
|
||||
return field
|
||||
|
||||
@@ -1497,8 +1497,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
||||
pass # Skip re-validation if it is validated
|
||||
elif self.validate_vat_id and vat_id_applicable:
|
||||
try:
|
||||
requester_id = self.request.event.settings.invoice_address_from_vat_id
|
||||
normalized_id = validate_vat_id(data.get('vat_id'), str(data.get('country')), requester_id)
|
||||
normalized_id = validate_vat_id(data.get('vat_id'), str(data.get('country')))
|
||||
self.instance.vat_id_validated = bool(normalized_id)
|
||||
self.instance.vat_id = data['vat_id'] = normalized_id
|
||||
except VATIDFinalError as e:
|
||||
|
||||
@@ -343,66 +343,6 @@ def _validate_vat_id_EU(vat_id, country_code):
|
||||
return vat_id
|
||||
|
||||
|
||||
def _validate_vat_id_EU_fallback_germany(vat_id, country_code, requester_id):
|
||||
# We can skip most static validation checks because _validate_vat_id_EU always runs before
|
||||
vat_id = normalize_vat_id(vat_id, country_code)
|
||||
|
||||
# The VIES service of the European commission is overused and down due to rate limits A LOT. There is another
|
||||
# API by German BZSt, but it only works if the requester is German and the requested is not.
|
||||
# https://www.bzst.de/DE/Unternehmen/Identifikationsnummern/Umsatzsteuer-Identifikationsnummer/AuslaendischeUSt-IdNr/auslaendische_ust_idnr_node.html
|
||||
try:
|
||||
r = requests.post(
|
||||
"https://api.evatr.vies.bzst.de/app/v1/abfrage",
|
||||
json={
|
||||
"anfragendeUstid": requester_id,
|
||||
"angefragteUstid": vat_id,
|
||||
},
|
||||
timeout=10,
|
||||
)
|
||||
d = r.json()
|
||||
if r.status_code == 200:
|
||||
if d['status'] in ('evatr-0000', 'evatr-2008'):
|
||||
# evatr-0000: Die angefragte Ust-IdNr. ist zum Anfragezeitpunkt gültig.
|
||||
# evatr-2008: Die angefragte Ust-IdNr. ist zum Anfragezeitpunkt gültig.
|
||||
# Für die qualifizierte Bestätigungsanfrage liegt einer Besonderheit vor.
|
||||
# Für Rückfragen wenden Sie sich an das BZSt.
|
||||
return vat_id
|
||||
# evatr-2002: Die angefragte USt-IdNr. ist zum Anfragezeitpunkt nicht gültig.
|
||||
# Sie ist erst gültig ab dem Datum im Feld gueltigAb.
|
||||
# evatr-2006: Die angefragte Ust-IdNr. ist zum Anfragezeitpunkt nicht gültig.
|
||||
# Sie war gültig im Zeitraum, der durch die Werte in den Feldern gueltigAb und gueltigBis beschrieben ist.
|
||||
raise VATIDFinalError(error_messages['invalid'])
|
||||
elif r.status_code == 400:
|
||||
if d['status'] in ('evatr-0002', 'evatr-0004', 'evatr-0008'):
|
||||
# evatr-0002: Mindestens eins der Pflichtfelder ist nicht besetzt.
|
||||
# evatr-0004: Die anfragende DE Ust-IdNr. ist syntaktisch falsch. Sie passt nicht in das deutsche Erzeugungsschema.
|
||||
# evatr-0008: Die maximale Anzahl von qualifizierten Bestätigungsabfragen für diese Session wurde erreicht.
|
||||
# Bitte starten Sie erneut mit einer einfachen Bestätigungsabfrage.
|
||||
raise VATIDTemporaryError(error_messages['unavailable'])
|
||||
# evatr-0005: Die angegebene angefragte Ust-IdNr. ist syntaktisch falsch.
|
||||
# evatr-0012: Die angefrage USt-IdNr. ist syntaktisch falsch. Sie passt nicht in das Erzeugungsschema.
|
||||
# evatr-2003: Das angegebene Länderkennzeichen der angefragten USt-IdNr. ist nicht gültig.
|
||||
raise VATIDFinalError(error_messages['invalid'])
|
||||
elif r.status_code == 403:
|
||||
# evatr-0006: Die anfragende DE USt-IdNr. ist nicht berechtigt eine DE Ust-IdNr. anzufragen.
|
||||
# evatr-0007: Fehlerhafter Aufruf.
|
||||
raise VATIDTemporaryError(error_messages['unavailable'])
|
||||
elif r.status_code == 404:
|
||||
if d['status'] in ('evatr-2005'):
|
||||
# evatr-2005: Die angegebene eigene DE Ust-IdNr. ist zum Anfragezeitpunkt nicht gültig.
|
||||
raise VATIDTemporaryError(error_messages['unavailable'])
|
||||
# evatr-2001: Die angefragte USt-IdNr. ist zum Anfragezeitpunkt nicht vergeben.
|
||||
raise VATIDFinalError(error_messages['invalid'])
|
||||
else: # 500, 503
|
||||
raise VATIDTemporaryError(error_messages['unavailable'])
|
||||
except requests.RequestException:
|
||||
logger.exception('VAT ID checking failed for country {}'.format(country_code))
|
||||
raise VATIDTemporaryError(error_messages['unavailable'])
|
||||
except ValueError: # JSON parsing failed
|
||||
logger.exception('VAT ID checking failed for country {}'.format(country_code))
|
||||
raise VATIDTemporaryError(error_messages['unavailable'])
|
||||
|
||||
|
||||
def _validate_vat_id_CH(vat_id, country_code):
|
||||
if vat_id[:3] != 'CHE':
|
||||
raise VATIDFinalError(error_messages['country_mismatch'])
|
||||
@@ -454,18 +394,12 @@ def _validate_vat_id_CH(vat_id, country_code):
|
||||
return vat_id
|
||||
|
||||
|
||||
def validate_vat_id(vat_id, country_code, requester_id=None):
|
||||
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):
|
||||
try:
|
||||
return _validate_vat_id_EU(vat_id, country_code)
|
||||
except VATIDTemporaryError:
|
||||
if requester_id and requester_id.startswith("DE") and not vat_id.startswith("DE"):
|
||||
return _validate_vat_id_EU_fallback_germany(vat_id, country_code, requester_id)
|
||||
else:
|
||||
raise
|
||||
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':
|
||||
|
||||
@@ -1930,6 +1930,8 @@ DEFAULTS = {
|
||||
'serializer_class': serializers.BooleanField,
|
||||
'form_kwargs': dict(
|
||||
label=_("Hide all unavailable dates from calendar or list views"),
|
||||
help_text=_("This option currently only affects the calendar of this event series, not the organizer-wide "
|
||||
"calendar.")
|
||||
)
|
||||
},
|
||||
'event_calendar_future_only': {
|
||||
|
||||
@@ -135,8 +135,6 @@ class BaseQuestionsViewMixin:
|
||||
question_field.initial = getattr(question_field, 'initial', None) or src['initial']
|
||||
if 'validators' in src:
|
||||
question_field.validators += src['validators']
|
||||
if 'label' in src:
|
||||
question_field.label = src['label']
|
||||
|
||||
if len(form.fields) > 0:
|
||||
formlist.append(form)
|
||||
|
||||
@@ -717,10 +717,6 @@ class CoreUserImpersonatedLogEntryType(UserImpersonatedLogEntryType):
|
||||
'pretix.organizer.export.schedule.failed': _('A scheduled export has failed: {reason}.'),
|
||||
'pretix.organizer.outgoingmails.retried': _('Failed emails have been scheduled to be retried.'),
|
||||
'pretix.organizer.outgoingmails.aborted': _('Queued emails have been aborted.'),
|
||||
'pretix.property.created': _('An organizer meta property has been created.'),
|
||||
'pretix.property.deleted': _('An organizer meta property has been deleted.'),
|
||||
'pretix.property.changed': _('An organizer meta property has been changed.'),
|
||||
'pretix.property.reordered': _('An organizer meta property has been reordered.'),
|
||||
'pretix.giftcards.acceptance.added': _('Gift card acceptance for another organizer has been added.'),
|
||||
'pretix.giftcards.acceptance.removed': _('Gift card acceptance for another organizer has been removed.'),
|
||||
'pretix.giftcards.acceptance.acceptor.invited': _('A new gift card acceptor has been invited.'),
|
||||
|
||||
@@ -1646,8 +1646,7 @@ class OrderCheckVATID(OrderView):
|
||||
return redirect(self.get_order_url())
|
||||
|
||||
try:
|
||||
requester_id = self.request.event.settings.invoice_address_from_vat_id
|
||||
normalized_id = validate_vat_id(ia.vat_id, str(ia.country), requester_id)
|
||||
normalized_id = validate_vat_id(ia.vat_id, str(ia.country))
|
||||
with transaction.atomic():
|
||||
ia.vat_id_validated = True
|
||||
ia.vat_id = normalized_id
|
||||
|
||||
@@ -494,7 +494,6 @@ def webhook(request, *args, **kwargs):
|
||||
elif payment.state in (OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED,
|
||||
OrderPayment.PAYMENT_STATE_CANCELED, OrderPayment.PAYMENT_STATE_FAILED):
|
||||
if sale['status'] == 'COMPLETED':
|
||||
any_captures = False
|
||||
all_captures_completed = True
|
||||
any_pending_review = False
|
||||
any_failed = None
|
||||
@@ -506,7 +505,6 @@ def webhook(request, *args, **kwargs):
|
||||
except ReferencedPayPalObject.MultipleObjectsReturned:
|
||||
pass
|
||||
|
||||
any_captures = True
|
||||
if capture['status'] in ('COMPLETED', 'REFUNDED', 'PARTIALLY_REFUNDED'):
|
||||
pass
|
||||
elif capture['status'] in ("DECLINED", "FAILED"):
|
||||
@@ -518,7 +516,7 @@ def webhook(request, *args, **kwargs):
|
||||
any_pending_review = True
|
||||
else:
|
||||
raise ValueError("Unknown paypal capture state: {}".format(capture['status']))
|
||||
if any_captures and all_captures_completed:
|
||||
if all_captures_completed:
|
||||
try:
|
||||
payment.confirm()
|
||||
prov.log_payment_duration(payment)
|
||||
|
||||
@@ -840,8 +840,6 @@ class QuestionsStep(CartQuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
f.fields[fname].disabled = val['disabled']
|
||||
if 'validators' in val and fname in f.fields:
|
||||
f.fields[fname].validators += val['validators']
|
||||
if 'label' in val and fname in f.fields:
|
||||
f.fields[fname].label = val['label']
|
||||
|
||||
return f
|
||||
|
||||
@@ -946,8 +944,6 @@ class QuestionsStep(CartQuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
f.fields[fname].disabled = val['disabled']
|
||||
if 'validators' in val and fname in f.fields:
|
||||
f.fields[fname].validators += val['validators']
|
||||
if 'label' in val and fname in f.fields:
|
||||
f.fields[fname].label = val['label']
|
||||
|
||||
return f
|
||||
|
||||
|
||||
@@ -233,7 +233,7 @@ Arguments: ``request``, ``order``
|
||||
This signal allows you to override fields of the contact form that is presented during checkout
|
||||
and by default only asks for the email address. It is also being used for the invoice address
|
||||
form. You are supposed to return a dictionary of dictionaries with globally unique keys. The
|
||||
value-dictionary should contain one or more of the following keys: ``label``, ``initial``, ``disabled``,
|
||||
value-dictionary should contain one or more of the following keys: ``initial``, ``disabled``,
|
||||
``validators``. The key of the dictionary should be the name of the form field.
|
||||
|
||||
As with all event plugin signals, the ``sender`` keyword argument will contain the event. A ``request``
|
||||
@@ -264,9 +264,9 @@ Arguments: ``position``, ``request``
|
||||
This signal allows you to override fields of the questions form that is presented during checkout
|
||||
and by default only asks for the questions configured in the backend. You are supposed to return a
|
||||
dictionary of dictionaries with globally unique keys. The value-dictionary should contain one or
|
||||
more of the following keys: ``label``, ``initial``, ``disabled``, ``validators``. The key of the
|
||||
dictionary should be the form field name for system fields (e.g. ``company``), or the question's
|
||||
``identifier`` for user-defined questions.
|
||||
more of the following keys: ``initial``, ``disabled``, ``validators``. The key of the dictionary
|
||||
should be the form field name for system fields (e.g. ``company``), or the question's ``identifier``
|
||||
for user-defined questions.
|
||||
|
||||
The ``position`` keyword argument will contain a ``CartPosition`` or ``OrderPosition`` object.
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ from pretix.presale.signals import seatingframe_html_head
|
||||
from pretix.presale.views.organizer import (
|
||||
EventListMixin, add_subevents_for_days, days_for_template,
|
||||
filter_qs_by_attr, filter_subevents_with_plugins, has_before_after,
|
||||
should_hide_subevent, weeks_for_template,
|
||||
weeks_for_template,
|
||||
)
|
||||
|
||||
from . import (
|
||||
@@ -443,10 +443,12 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
|
||||
)
|
||||
)
|
||||
subevents = filter_subevents_with_plugins(list(subevents), self.request.sales_channel)
|
||||
context['subevent_list'] = [
|
||||
se for se in subevents
|
||||
if not should_hide_subevent(self.request.event.settings, se, voucher)
|
||||
]
|
||||
context['subevent_list'] = subevents
|
||||
if self.request.event.settings.event_list_available_only and not voucher:
|
||||
context['subevent_list'] = [
|
||||
se for se in subevents
|
||||
if not se.presale_has_ended and (se.best_availability_state is None or se.best_availability_state >= Quota.AVAILABILITY_RESERVED)
|
||||
]
|
||||
context['visible_events'] = len(subevents) > 0
|
||||
return context
|
||||
|
||||
|
||||
@@ -601,32 +601,6 @@ def filter_subevents_with_plugins(subevents, sales_channel=None):
|
||||
return subevents
|
||||
|
||||
|
||||
def should_hide_subevent(settings, subevent, voucher=None):
|
||||
hide = False
|
||||
if settings.event_list_available_only:
|
||||
hide = (
|
||||
# Presale is over → the subevent is not available → hide
|
||||
subevent.presale_has_ended or
|
||||
# Not a single product is available on this sales channel → hide
|
||||
# Note that means there could be products which are ignored for calendar availability (Quota.ignore_for_event_availability)
|
||||
# or products only visible with a voucher. However, for customers with these scenarios, the event_list_available_only
|
||||
# makes only very little sense as it would never do anything, so the flag can just be removed -- or the products should
|
||||
# be made visible so people know why there are no products. In case a voucher is already entered on the calendar view,
|
||||
# this is already respected and subevents are shown correctly.
|
||||
subevent.best_availability_state is None or
|
||||
(
|
||||
# Sold out → hide, unless we have a voucher active that can bypass all quotas
|
||||
(not voucher or not voucher.allow_ignore_quota) and
|
||||
subevent.best_availability_state < Quota.AVAILABILITY_RESERVED
|
||||
)
|
||||
)
|
||||
|
||||
if settings.event_calendar_future_only:
|
||||
if (subevent.date_to or subevent.date_from) < time_machine_now():
|
||||
hide = True
|
||||
return hide
|
||||
|
||||
|
||||
def add_subevents_for_days(qs, before, after, ebd, timezones, sales_channel, event=None, cart_namespace=None,
|
||||
voucher=None):
|
||||
qs = qs.filter(active=True, is_public=True).filter(
|
||||
@@ -666,8 +640,19 @@ def add_subevents_for_days(qs, before, after, ebd, timezones, sales_channel, eve
|
||||
kwargs['cart_namespace'] = cart_namespace
|
||||
|
||||
s = event.settings if event else se.event.settings
|
||||
if should_hide_subevent(s, se, voucher):
|
||||
continue
|
||||
|
||||
if s.event_list_available_only:
|
||||
hide = se.presale_has_ended or (
|
||||
(not voucher or not voucher.allow_ignore_quota) and
|
||||
se.best_availability_state is not None and
|
||||
se.best_availability_state < Quota.AVAILABILITY_RESERVED
|
||||
)
|
||||
if hide:
|
||||
continue
|
||||
|
||||
if s.event_calendar_future_only:
|
||||
if (se.date_to or se.date_from) < time_machine_now():
|
||||
continue
|
||||
|
||||
timezones.add(s.timezone)
|
||||
tz = ZoneInfo(s.timezone)
|
||||
|
||||
@@ -75,7 +75,7 @@ from pretix.presale.views.cart import get_or_create_cart_id
|
||||
from pretix.presale.views.organizer import (
|
||||
EventListMixin, add_events_for_days, add_subevents_for_days,
|
||||
days_for_template, filter_qs_by_attr, filter_subevents_with_plugins,
|
||||
should_hide_subevent, weeks_for_template,
|
||||
weeks_for_template,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -757,10 +757,14 @@ class WidgetAPIProductList(EventListMixin, View):
|
||||
evs = evs[:limit]
|
||||
|
||||
tz = request.event.timezone
|
||||
evs = [
|
||||
se for se in evs
|
||||
if not should_hide_subevent(self.request.event.settings, se)
|
||||
]
|
||||
if self.request.event.settings.event_list_available_only:
|
||||
evs = [
|
||||
se for se in evs
|
||||
if not se.presale_has_ended and (
|
||||
se.best_availability_state is not None and
|
||||
se.best_availability_state >= Quota.AVAILABILITY_RESERVED
|
||||
)
|
||||
]
|
||||
|
||||
data['events'] = [
|
||||
{
|
||||
|
||||
@@ -345,7 +345,7 @@ Vue.component('pricebox', {
|
||||
+ ' :min="display_price_nonlocalized" :value="suggested_price_nonlocalized" :name="field_name"'
|
||||
+ ' step="any" v-bind:aria-labelledby="aria_labelledby" v-bind:aria-describedby="price_desc_id">'
|
||||
+ '</div>'
|
||||
+ '<small class="pretix-widget-pricebox-tax" :id="price_desc_id" v-if="show_taxline">'
|
||||
+ '<small class="pretix-widget-pricebox-tax" :id="price_desc_id" v-if="price.rate != \'0\' && price.gross != \'0.00\'">'
|
||||
+ '{{ taxline }}'
|
||||
+ '</small>'
|
||||
+ '</div>'),
|
||||
@@ -422,10 +422,6 @@ Vue.component('pricebox', {
|
||||
return '<span class="pretix-widget-pricebox-currency">' + this.$root.currency + "</span> " + this.display_price;
|
||||
}
|
||||
},
|
||||
show_taxline: function () {
|
||||
// rate can either be "0.00" or "0" => parseFloat to check
|
||||
return Number.parseFloat(this.price.rate) && Number.parseFloat(this.price.gross);
|
||||
},
|
||||
taxline: function () {
|
||||
if (this.$root.display_net_prices) {
|
||||
if (this.price.includes_mixed_tax_rate) {
|
||||
|
||||
@@ -86,8 +86,7 @@ const taxline = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
// rate can either be "0.00" or "0" => parseFloat to check
|
||||
const showTaxline = computed(() => Number.parseFloat(props.price.rate) && Number.parseFloat(props.price.gross))
|
||||
const showTaxline = computed(() => props.price.rate !== '0' && props.price.gross !== '0.00')
|
||||
</script>
|
||||
<template lang="pug">
|
||||
.pretix-widget-pricebox
|
||||
|
||||
@@ -299,30 +299,6 @@ def get_test_order_review_pending():
|
||||
'method': 'GET'}]}
|
||||
|
||||
|
||||
def get_test_empty_captures():
|
||||
return {'id': '806440346Y391300T',
|
||||
'intent': 'CAPTURE',
|
||||
'status': 'COMPLETED',
|
||||
'purchase_units': [{'reference_id': 'default',
|
||||
'amount': {'currency_code': 'EUR', 'value': '43.59'},
|
||||
'payee': {'email_address': 'dummy-facilitator@dummy.dummy',
|
||||
'merchant_id': 'G6R2B9YXADKWW'},
|
||||
'description': 'Order JWJGC for PayPal v2',
|
||||
'custom_id': 'Order PAYPALV2-JWJGC',
|
||||
'soft_descriptor': 'MARTINFACIL',
|
||||
'payments': {'captures': []}
|
||||
}],
|
||||
'payer': {'name': {'given_name': 'test', 'surname': 'buyer'},
|
||||
'email_address': 'dummy@dummy.dummy',
|
||||
'payer_id': 'Q739JNKWH67HE',
|
||||
'address': {'country_code': 'DE'}},
|
||||
'create_time': '2022-04-28T11:59:59Z',
|
||||
'update_time': '2022-04-28T12:00:22Z',
|
||||
'links': [{'href': 'https://api.sandbox.paypal.com/v2/checkout/orders/806440346Y391300T',
|
||||
'rel': 'self',
|
||||
'method': 'GET'}]}
|
||||
|
||||
|
||||
class Object():
|
||||
pass
|
||||
|
||||
@@ -480,100 +456,6 @@ def test_webhook_all_good(env, client, monkeypatch):
|
||||
assert order.status == Order.STATUS_PAID
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_webhook_empty_captures(env, client, monkeypatch):
|
||||
order = env[1]
|
||||
with scopes_disabled():
|
||||
p = order.payments.first()
|
||||
p.state = OrderPayment.PAYMENT_STATE_PENDING
|
||||
p.save()
|
||||
order.status = Order.STATUS_PENDING
|
||||
order.save()
|
||||
|
||||
pp_order = Result(get_test_empty_captures())
|
||||
monkeypatch.setattr("paypalcheckoutsdk.orders.OrdersGetRequest", lambda *args: pp_order)
|
||||
monkeypatch.setattr("pretix.plugins.paypal2.payment.PaypalMethod.init_api", init_api)
|
||||
|
||||
with scopes_disabled():
|
||||
ReferencedPayPalObject.objects.create(order=order, payment=order.payments.first(),
|
||||
reference="806440346Y391300T")
|
||||
|
||||
client.post('/_paypal/webhook/', json.dumps(
|
||||
{
|
||||
"id": "WH-4T867178D0574904F-7TT11736YU643990P",
|
||||
"create_time": "2022-04-28T12:00:37.077Z",
|
||||
"resource_type": "checkout-order",
|
||||
"event_type": "CHECKOUT.ORDER.COMPLETED",
|
||||
"summary": "Checkout Order Completed",
|
||||
"resource": {
|
||||
"update_time": "2022-04-28T12:00:22Z",
|
||||
"create_time": "2022-04-28T11:59:59Z",
|
||||
"purchase_units": [
|
||||
{
|
||||
"reference_id": "default",
|
||||
"amount": {
|
||||
"currency_code": "EUR",
|
||||
"value": "43.59"
|
||||
},
|
||||
"payee": {
|
||||
"email_address": "dummy-facilitator@dummy.dummy",
|
||||
"merchant_id": "G6R2B9YXADKWW"
|
||||
},
|
||||
"description": "Order JWJGC for PayPal v2",
|
||||
"custom_id": "Order PAYPALV2-JWJGC",
|
||||
"soft_descriptor": "MARTINFACIL",
|
||||
"payments": {
|
||||
"captures": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/806440346Y391300T",
|
||||
"rel": "self",
|
||||
"method": "GET"
|
||||
}
|
||||
],
|
||||
"id": "806440346Y391300T",
|
||||
"intent": "CAPTURE",
|
||||
"payer": {
|
||||
"name": {
|
||||
"given_name": "test",
|
||||
"surname": "buyer"
|
||||
},
|
||||
"email_address": "dummy@dummy.dummy",
|
||||
"payer_id": "Q739JNKWH67HE",
|
||||
"address": {
|
||||
"country_code": "DE"
|
||||
}
|
||||
},
|
||||
"status": "COMPLETED"
|
||||
},
|
||||
"status": "SUCCESS",
|
||||
"links": [
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-4T867178D0574904F-7TT11736YU643990P",
|
||||
"rel": "self",
|
||||
"method": "GET",
|
||||
"encType": "application/json"
|
||||
},
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-4T867178D0574904F-7TT11736YU643990P/resend",
|
||||
"rel": "resend",
|
||||
"method": "POST",
|
||||
"encType": "application/json"
|
||||
}
|
||||
],
|
||||
"event_version": "1.0",
|
||||
"resource_version": "2.0"
|
||||
}
|
||||
), content_type='application_json')
|
||||
|
||||
order = env[1]
|
||||
order.refresh_from_db()
|
||||
assert order.status == Order.STATUS_PENDING
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_webhook_mark_paid(env, client, monkeypatch):
|
||||
order = env[1]
|
||||
|
||||
@@ -31,9 +31,6 @@ export default defineConfig({
|
||||
// Allow serving source files from sibling plugin directories
|
||||
allow: ['src', ...pluginDirs],
|
||||
},
|
||||
cors: {
|
||||
origin: /^https?:\/\/(?:(?:[^:]+\.)?localhost|127\.0\.0\.1|\[::1\]|[^:]+\.pretix\.(dev|work))(?::\d+)?$/
|
||||
},
|
||||
},
|
||||
build: {
|
||||
manifest: true,
|
||||
|
||||
Reference in New Issue
Block a user