mirror of
https://github.com/pretix/pretix.git
synced 2026-05-08 15:44:02 +00:00
API: add api_meta to order
This commit is contained in:
committed by
GitHub
parent
9e61f7f978
commit
22e2143623
@@ -42,6 +42,8 @@ payment_date date **DEPRECATED AN
|
|||||||
payment_provider string **DEPRECATED AND INACCURATE** Payment provider used for this order
|
payment_provider string **DEPRECATED AND INACCURATE** Payment provider used for this order
|
||||||
total money (string) Total value of this order
|
total money (string) Total value of this order
|
||||||
comment string Internal comment on this order
|
comment string Internal comment on this order
|
||||||
|
api_meta object Meta data for that order. Only available through API, no guarantees
|
||||||
|
on the content structure. You can use this to save references to your system.
|
||||||
custom_followup_at date Internal date for a custom follow-up action
|
custom_followup_at date Internal date for a custom follow-up action
|
||||||
checkin_attention boolean If ``true``, the check-in app should show a warning
|
checkin_attention boolean If ``true``, the check-in app should show a warning
|
||||||
that this ticket requires special attention if a ticket
|
that this ticket requires special attention if a ticket
|
||||||
@@ -562,6 +564,7 @@ Fetching individual orders
|
|||||||
"fees": [],
|
"fees": [],
|
||||||
"total": "23.00",
|
"total": "23.00",
|
||||||
"comment": "",
|
"comment": "",
|
||||||
|
"api_meta": {},
|
||||||
"custom_followup_at": null,
|
"custom_followup_at": null,
|
||||||
"checkin_attention": false,
|
"checkin_attention": false,
|
||||||
"checkin_text": null,
|
"checkin_text": null,
|
||||||
@@ -742,6 +745,8 @@ Updating order fields
|
|||||||
|
|
||||||
* ``comment``
|
* ``comment``
|
||||||
|
|
||||||
|
* ``api_meta``
|
||||||
|
|
||||||
* ``custom_followup_at``
|
* ``custom_followup_at``
|
||||||
|
|
||||||
* ``invoice_address`` (you always need to supply the full object, or ``null`` to delete the current address)
|
* ``invoice_address`` (you always need to supply the full object, or ``null`` to delete the current address)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ Frontend
|
|||||||
|
|
||||||
|
|
||||||
.. automodule:: pretix.presale.signals
|
.. automodule:: pretix.presale.signals
|
||||||
:members: order_info, order_info_top, order_meta_from_request
|
:members: order_info, order_info_top, order_meta_from_request, order_api_meta_from_request
|
||||||
|
|
||||||
Request flow
|
Request flow
|
||||||
""""""""""""
|
""""""""""""
|
||||||
|
|||||||
@@ -726,7 +726,7 @@ class OrderSerializer(I18nAwareModelSerializer):
|
|||||||
'code', 'event', 'status', 'testmode', 'secret', 'email', 'phone', 'locale', 'datetime', 'expires', 'payment_date',
|
'code', 'event', 'status', 'testmode', 'secret', 'email', 'phone', 'locale', 'datetime', 'expires', 'payment_date',
|
||||||
'payment_provider', 'fees', 'total', 'comment', 'custom_followup_at', 'invoice_address', 'positions', 'downloads',
|
'payment_provider', 'fees', 'total', 'comment', 'custom_followup_at', 'invoice_address', 'positions', 'downloads',
|
||||||
'checkin_attention', 'checkin_text', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel',
|
'checkin_attention', 'checkin_text', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel',
|
||||||
'url', 'customer', 'valid_if_pending'
|
'url', 'customer', 'valid_if_pending', 'api_meta'
|
||||||
)
|
)
|
||||||
read_only_fields = (
|
read_only_fields = (
|
||||||
'code', 'status', 'testmode', 'secret', 'datetime', 'expires', 'payment_date',
|
'code', 'status', 'testmode', 'secret', 'datetime', 'expires', 'payment_date',
|
||||||
@@ -786,7 +786,7 @@ class OrderSerializer(I18nAwareModelSerializer):
|
|||||||
# Even though all fields that shouldn't be edited are marked as read_only in the serializer
|
# Even though all fields that shouldn't be edited are marked as read_only in the serializer
|
||||||
# (hopefully), we'll be extra careful here and be explicit about the model fields we update.
|
# (hopefully), we'll be extra careful here and be explicit about the model fields we update.
|
||||||
update_fields = ['comment', 'custom_followup_at', 'checkin_attention', 'checkin_text', 'email', 'locale',
|
update_fields = ['comment', 'custom_followup_at', 'checkin_attention', 'checkin_text', 'email', 'locale',
|
||||||
'phone', 'valid_if_pending']
|
'phone', 'valid_if_pending', 'api_meta']
|
||||||
|
|
||||||
if 'invoice_address' in validated_data:
|
if 'invoice_address' in validated_data:
|
||||||
iadata = validated_data.pop('invoice_address')
|
iadata = validated_data.pop('invoice_address')
|
||||||
@@ -1059,7 +1059,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
|
|||||||
fields = ('code', 'status', 'testmode', 'email', 'phone', 'locale', 'payment_provider', 'fees', 'comment', 'sales_channel',
|
fields = ('code', 'status', 'testmode', 'email', 'phone', 'locale', 'payment_provider', 'fees', 'comment', 'sales_channel',
|
||||||
'invoice_address', 'positions', 'checkin_attention', 'checkin_text', 'payment_info', 'payment_date',
|
'invoice_address', 'positions', 'checkin_attention', 'checkin_text', 'payment_info', 'payment_date',
|
||||||
'consume_carts', 'force', 'send_email', 'simulate', 'customer', 'custom_followup_at',
|
'consume_carts', 'force', 'send_email', 'simulate', 'customer', 'custom_followup_at',
|
||||||
'require_approval', 'valid_if_pending', 'expires')
|
'require_approval', 'valid_if_pending', 'expires', 'api_meta')
|
||||||
|
|
||||||
def validate_payment_provider(self, pp):
|
def validate_payment_provider(self, pp):
|
||||||
if pp is None:
|
if pp is None:
|
||||||
|
|||||||
18
src/pretix/base/migrations/0269_order_api_meta.py
Normal file
18
src/pretix/base/migrations/0269_order_api_meta.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.2.13 on 2024-07-17 14:03
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('pretixbase', '0268_remove_subevent_items_remove_subevent_variations_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='order',
|
||||||
|
name='api_meta',
|
||||||
|
field=models.JSONField(default=dict),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -299,6 +299,11 @@ class Order(LockModel, LoggedModel):
|
|||||||
verbose_name=_("Meta information"),
|
verbose_name=_("Meta information"),
|
||||||
null=True, blank=True
|
null=True, blank=True
|
||||||
)
|
)
|
||||||
|
api_meta = models.JSONField(
|
||||||
|
verbose_name=_("API meta information"),
|
||||||
|
null=False, blank=True,
|
||||||
|
default=dict
|
||||||
|
)
|
||||||
last_modified = models.DateTimeField(
|
last_modified = models.DateTimeField(
|
||||||
auto_now=True, db_index=False
|
auto_now=True, db_index=False
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -960,7 +960,7 @@ def _get_fees(positions: List[CartPosition], payment_requests: List[dict], addre
|
|||||||
def _create_order(event: Event, *, email: str, positions: List[CartPosition], now_dt: datetime,
|
def _create_order(event: Event, *, email: str, positions: List[CartPosition], now_dt: datetime,
|
||||||
payment_requests: List[dict], sales_channel: SalesChannel, locale: str=None,
|
payment_requests: List[dict], sales_channel: SalesChannel, locale: str=None,
|
||||||
address: InvoiceAddress=None, meta_info: dict=None, shown_total=None,
|
address: InvoiceAddress=None, meta_info: dict=None, shown_total=None,
|
||||||
customer=None, valid_if_pending=False):
|
customer=None, valid_if_pending=False, api_meta: dict=None):
|
||||||
payments = []
|
payments = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -985,6 +985,7 @@ def _create_order(event: Event, *, email: str, positions: List[CartPosition], no
|
|||||||
total=total,
|
total=total,
|
||||||
testmode=True if sales_channel.type_instance.testmode_supported and event.testmode else False,
|
testmode=True if sales_channel.type_instance.testmode_supported and event.testmode else False,
|
||||||
meta_info=json.dumps(meta_info or {}),
|
meta_info=json.dumps(meta_info or {}),
|
||||||
|
api_meta=api_meta or {},
|
||||||
require_approval=require_approval,
|
require_approval=require_approval,
|
||||||
sales_channel=sales_channel,
|
sales_channel=sales_channel,
|
||||||
customer=customer,
|
customer=customer,
|
||||||
@@ -1096,7 +1097,7 @@ def _order_placed_email_attendee(event: Event, order: Order, position: OrderPosi
|
|||||||
|
|
||||||
def _perform_order(event: Event, payment_requests: List[dict], position_ids: List[str],
|
def _perform_order(event: Event, payment_requests: List[dict], position_ids: List[str],
|
||||||
email: str, locale: str, address: int, meta_info: dict=None, sales_channel: str='web',
|
email: str, locale: str, address: int, meta_info: dict=None, sales_channel: str='web',
|
||||||
shown_total=None, customer=None):
|
shown_total=None, customer=None, api_meta: dict=None):
|
||||||
for p in payment_requests:
|
for p in payment_requests:
|
||||||
p['pprov'] = event.get_payment_providers(cached=True)[p['provider']]
|
p['pprov'] = event.get_payment_providers(cached=True)[p['provider']]
|
||||||
if not p['pprov']:
|
if not p['pprov']:
|
||||||
@@ -1200,7 +1201,8 @@ def _perform_order(event: Event, payment_requests: List[dict], position_ids: Lis
|
|||||||
sales_channel=sales_channel,
|
sales_channel=sales_channel,
|
||||||
shown_total=shown_total,
|
shown_total=shown_total,
|
||||||
customer=customer,
|
customer=customer,
|
||||||
valid_if_pending=valid_if_pending
|
valid_if_pending=valid_if_pending,
|
||||||
|
api_meta=api_meta,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -2873,12 +2875,13 @@ class OrderChangeManager:
|
|||||||
@app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(OrderError,))
|
@app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(OrderError,))
|
||||||
def perform_order(self, event: Event, payments: List[dict], positions: List[str],
|
def perform_order(self, event: Event, payments: List[dict], positions: List[str],
|
||||||
email: str=None, locale: str=None, address: int=None, meta_info: dict=None,
|
email: str=None, locale: str=None, address: int=None, meta_info: dict=None,
|
||||||
sales_channel: str='web', shown_total=None, customer=None, override_now_dt: datetime=None):
|
sales_channel: str='web', shown_total=None, customer=None, override_now_dt: datetime=None,
|
||||||
|
api_meta: dict=None):
|
||||||
with language(locale), time_machine_now_assigned(override_now_dt):
|
with language(locale), time_machine_now_assigned(override_now_dt):
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
return _perform_order(event, payments, positions, email, locale, address, meta_info,
|
return _perform_order(event, payments, positions, email, locale, address, meta_info,
|
||||||
sales_channel, shown_total, customer)
|
sales_channel, shown_total, customer, api_meta)
|
||||||
except LockTimeoutException:
|
except LockTimeoutException:
|
||||||
self.retry()
|
self.retry()
|
||||||
except (MaxRetriesExceededError, LockTimeoutException):
|
except (MaxRetriesExceededError, LockTimeoutException):
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ from pretix.presale.forms.customer import AuthenticationForm, RegistrationForm
|
|||||||
from pretix.presale.signals import (
|
from pretix.presale.signals import (
|
||||||
checkout_all_optional, checkout_confirm_messages, checkout_flow_steps,
|
checkout_all_optional, checkout_confirm_messages, checkout_flow_steps,
|
||||||
contact_form_fields, contact_form_fields_overrides,
|
contact_form_fields, contact_form_fields_overrides,
|
||||||
order_meta_from_request, question_form_fields,
|
order_api_meta_from_request, order_meta_from_request, question_form_fields,
|
||||||
question_form_fields_overrides,
|
question_form_fields_overrides,
|
||||||
)
|
)
|
||||||
from pretix.presale.utils import customer_login
|
from pretix.presale.utils import customer_login
|
||||||
@@ -1544,11 +1544,14 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
|
|||||||
str(m) for m in self.confirm_messages.values()
|
str(m) for m in self.confirm_messages.values()
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
api_meta = {}
|
||||||
unlock_hashes = request.session.get('pretix_unlock_hashes', [])
|
unlock_hashes = request.session.get('pretix_unlock_hashes', [])
|
||||||
if unlock_hashes:
|
if unlock_hashes:
|
||||||
meta_info['unlock_hashes'] = unlock_hashes
|
meta_info['unlock_hashes'] = unlock_hashes
|
||||||
for receiver, response in order_meta_from_request.send(sender=request.event, request=request):
|
for receiver, response in order_meta_from_request.send(sender=request.event, request=request):
|
||||||
meta_info.update(response)
|
meta_info.update(response)
|
||||||
|
for receiver, response in order_api_meta_from_request.send(sender=request.event, request=request):
|
||||||
|
api_meta.update(response)
|
||||||
|
|
||||||
return self.do(
|
return self.do(
|
||||||
self.request.event.id,
|
self.request.event.id,
|
||||||
@@ -1562,6 +1565,7 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
|
|||||||
shown_total=self.cart_session.get('shown_total'),
|
shown_total=self.cart_session.get('shown_total'),
|
||||||
customer=self.cart_session.get('customer'),
|
customer=self.cart_session.get('customer'),
|
||||||
override_now_dt=time_machine_now(default=None),
|
override_now_dt=time_machine_now(default=None),
|
||||||
|
api_meta=api_meta,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_success_message(self, value):
|
def get_success_message(self, value):
|
||||||
|
|||||||
@@ -170,6 +170,17 @@ You will receive the request triggering the order creation as the ``request`` ke
|
|||||||
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
|
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
order_api_meta_from_request = EventPluginSignal()
|
||||||
|
"""
|
||||||
|
Arguments: ``request``
|
||||||
|
|
||||||
|
This signal is sent before an order is created through the pretixpresale frontend. It allows you
|
||||||
|
to return a dictionary that will be merged in the api_meta attribute of the order.
|
||||||
|
You will receive the request triggering the order creation as the ``request`` keyword argument.
|
||||||
|
|
||||||
|
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
|
||||||
|
"""
|
||||||
|
|
||||||
checkout_confirm_page_content = EventPluginSignal()
|
checkout_confirm_page_content = EventPluginSignal()
|
||||||
"""
|
"""
|
||||||
Arguments: ``request``
|
Arguments: ``request``
|
||||||
|
|||||||
@@ -255,6 +255,9 @@ def test_order_update_allowed_fields(token_client, organizer, event, order):
|
|||||||
organizer.slug, event.slug, order.code
|
organizer.slug, event.slug, order.code
|
||||||
), format='json', data={
|
), format='json', data={
|
||||||
'comment': 'Here is a comment',
|
'comment': 'Here is a comment',
|
||||||
|
'api_meta': {
|
||||||
|
'test': 1
|
||||||
|
},
|
||||||
'valid_if_pending': True,
|
'valid_if_pending': True,
|
||||||
'custom_followup_at': '2021-06-12',
|
'custom_followup_at': '2021-06-12',
|
||||||
'checkin_attention': True,
|
'checkin_attention': True,
|
||||||
@@ -280,6 +283,9 @@ def test_order_update_allowed_fields(token_client, organizer, event, order):
|
|||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
order.refresh_from_db()
|
order.refresh_from_db()
|
||||||
assert order.comment == 'Here is a comment'
|
assert order.comment == 'Here is a comment'
|
||||||
|
assert order.api_meta == {
|
||||||
|
'test': 1
|
||||||
|
}
|
||||||
assert order.custom_followup_at.isoformat() == '2021-06-12'
|
assert order.custom_followup_at.isoformat() == '2021-06-12'
|
||||||
assert order.checkin_attention
|
assert order.checkin_attention
|
||||||
assert order.checkin_text == 'foobar'
|
assert order.checkin_text == 'foobar'
|
||||||
|
|||||||
@@ -232,6 +232,9 @@ def test_order_create(token_client, organizer, event, item, quota, question):
|
|||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
customer = organizer.customers.create()
|
customer = organizer.customers.create()
|
||||||
res['customer'] = customer.identifier
|
res['customer'] = customer.identifier
|
||||||
|
res['api_meta'] = {
|
||||||
|
'test': 1
|
||||||
|
}
|
||||||
resp = token_client.post(
|
resp = token_client.post(
|
||||||
'/api/v1/organizers/{}/events/{}/orders/'.format(
|
'/api/v1/organizers/{}/events/{}/orders/'.format(
|
||||||
organizer.slug, event.slug
|
organizer.slug, event.slug
|
||||||
@@ -251,6 +254,9 @@ def test_order_create(token_client, organizer, event, item, quota, question):
|
|||||||
assert o.valid_if_pending
|
assert o.valid_if_pending
|
||||||
assert o.expires > now()
|
assert o.expires > now()
|
||||||
assert not o.testmode
|
assert not o.testmode
|
||||||
|
assert o.api_meta == {
|
||||||
|
'test': 1
|
||||||
|
}
|
||||||
|
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
p = o.payments.first()
|
p = o.payments.first()
|
||||||
@@ -421,6 +427,7 @@ def test_order_create_simulate(token_client, organizer, event, item, quota, ques
|
|||||||
],
|
],
|
||||||
'total': '21.75',
|
'total': '21.75',
|
||||||
'comment': '',
|
'comment': '',
|
||||||
|
'api_meta': {},
|
||||||
"custom_followup_at": None,
|
"custom_followup_at": None,
|
||||||
'invoice_address': {
|
'invoice_address': {
|
||||||
'is_business': False,
|
'is_business': False,
|
||||||
|
|||||||
@@ -291,6 +291,7 @@ TEST_ORDER_RES = {
|
|||||||
"payment_provider": "banktransfer",
|
"payment_provider": "banktransfer",
|
||||||
"total": "23.00",
|
"total": "23.00",
|
||||||
"comment": "",
|
"comment": "",
|
||||||
|
"api_meta": {},
|
||||||
"custom_followup_at": None,
|
"custom_followup_at": None,
|
||||||
"checkin_attention": False,
|
"checkin_attention": False,
|
||||||
"checkin_text": None,
|
"checkin_text": None,
|
||||||
|
|||||||
Reference in New Issue
Block a user