API: add api_meta to order

This commit is contained in:
Richard Schreiber
2024-07-18 10:01:03 +02:00
committed by GitHub
parent 9e61f7f978
commit 22e2143623
11 changed files with 70 additions and 10 deletions

View File

@@ -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)

View File

@@ -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
"""""""""""" """"""""""""

View File

@@ -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:

View 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),
),
]

View File

@@ -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
) )

View File

@@ -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):

View File

@@ -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):

View File

@@ -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``

View File

@@ -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'

View File

@@ -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,

View File

@@ -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,