Add documentation

This commit is contained in:
Mira Weller
2025-07-29 17:24:23 +02:00
parent 9d93d49c2c
commit 8933ddf90d
3 changed files with 105 additions and 1 deletions
+61
View File
@@ -0,0 +1,61 @@
.. highlight:: python
:linenothreshold: 5
Data sync providers
===================
Pretix provides connectivity to many external services through plugins. A common requirement
is unidirectionally sending (order, customer, ticket, ...) data into external systems.
The transfer is usually triggered by signals provided by pretix core (e.g. order_created),
but performed asynchronously.
Such plugins should use the :class:`OutboundSyncProvider` API to utilize the queueing, retry and mapping mechanisms as well as the user interface for configuration and monitoring.
An :class:`OutboundSyncProvider` for registering event participants in a mailing list could start
like this, for example:
.. code-block:: python
class MyListSyncProvider(OutboundSyncProvider):
identifier = "my_list"
# ...
The plugin must register listeners in `signals.py` for all signals that should to trigger a sync and
within it has to call `MyListSyncProvider.enqueue_order` to enqueue the order for synchronization:
.. code-block:: python
@receiver(order_placed, dispatch_uid="mylist_order_placed")
def on_order_placed(sender, order, **kwargs):
MyListSyncProvider.enqueue_order(order, "order_placed")
Furthermore, most of these plugins need to transfer data from some pretix objects (e.g. orders)
into an external systems' data structures. Sometimes, there is only one reasonable way or the
plugin author makes an opinionated decision what information from which objects should be
transferred into which data structures in the external system.
Otherwise, you can use a `PropertyMappingFormSet` to let the user set up a mapping from pretix model fields
to external data fields. You could store the mapping information either in the Event settings, or in a separate
data model. Your implementation of OutboundSyncProvider.mappings needs to provide a list of Mappings, with at least
the properties defined in :class:`pretix.base.datasync.datasync.StaticMapping`.
.. code-block:: python
# class MyListSyncProvider, contd.
def mappings(self):
return [
StaticMapping(1, 'Order', 'Contact', 'email', 'email',
self.event.settings.mylist_order_mapping))
]
Currently, we support Orders and OrderPositions as data sources, with the data fields defined in
:func:`pretix.base.datasync.sourcefields.get_data_fields`.
+1
View File
@@ -18,5 +18,6 @@ Contents:
customview
cookieconsent
auth
datasync
general
quality
+43 -1
View File
@@ -144,8 +144,19 @@ class OutboundSyncProvider:
def display_name(cls):
return str(cls.identifier)
"""
Adds an order to the sync queue. May only be called on derived classes which define an "identifier" attribute.
Should be called in the appropriate signal receivers, e.g.:
@receiver(order_placed, dispatch_uid="mysync_order_placed")
def on_order_placed(sender, order, **kwargs):
MySyncProvider.enqueue_order(order, "order_placed")
"""
@classmethod
def enqueue_order(cls, order, triggered_by, not_before=None):
if not hasattr(cls, 'identifier'):
raise TypeError('Call this method on a derived class that defines an "identifier" attribute.')
OrderSyncQueue.objects.create(
order=order,
sync_provider=cls.identifier,
@@ -247,6 +258,30 @@ class OutboundSyncProvider:
for m in property_mapping
]
"""
This method is called for each object that needs to be created/updated in the external system -- which these are is
determined by the implementation of the `mapping` property.
TODO: describe the parameters
This method needs to be idempotent, i.e. calling it multiple times with the same input values should create
only a single object in the target system.
Subsequent calls with the same mapping and pk_value should update the existing object, instead of creating a new one.
In a SQL database, you might use an "INSERT OR UPDATE" or "UPSERT" statement; many REST APIs provide an equivalent API call.
"""
def sync_object_with_properties(
self,
pk_field,
pk_value,
properties: list,
inputs: dict,
mapping,
mapped_objects: dict,
**kwargs,
):
raise NotImplementedError()
def sync_object(
self,
inputs: dict,
@@ -261,7 +296,14 @@ class OutboundSyncProvider:
if not pk_value:
return None
info = self.sync_object_with_properties(inputs, mapping, mapped_objects, pk_value, properties)
info = self.sync_object_with_properties(
pk_field=mapping.external_pk,
pk_value=pk_value,
properties=properties,
inputs=inputs,
mapping=mapping,
mapped_objects=mapped_objects,
)
OrderSyncLink.objects.create(
order=inputs.get(ORDER), order_position=inputs.get(ORDER_POSITION), sync_provider=self.identifier,
external_object_type=info.get('object_type'),