Rename {pretix,external}_pk to {pretix,external}_id_field

This commit is contained in:
Mira Weller
2025-05-06 15:03:49 +02:00
parent 398176bab5
commit d84bbf636f
4 changed files with 100 additions and 97 deletions

View File

@@ -95,8 +95,8 @@ class OrderSyncLink(models.Model):
OrderPosition, on_delete=models.CASCADE, related_name="synced_objects", blank=True, null=True,
)
external_object_type = models.CharField(blank=False, null=False, max_length=128)
external_pk_name = models.CharField(blank=False, null=False, max_length=128)
external_pk_value = models.CharField(blank=False, null=False, max_length=128)
external_id_field = models.CharField(blank=False, null=False, max_length=128)
id_value = models.CharField(blank=False, null=False, max_length=128)
external_link_href = models.CharField(blank=True, null=True, max_length=255)
external_link_display_name = models.CharField(blank=True, null=True, max_length=255)
timestamp = models.DateTimeField(blank=False, null=False, auto_now_add=True)
@@ -147,7 +147,7 @@ class SyncConfigError(Exception):
self.full_message = full_message
StaticMapping = namedtuple('StaticMapping', ('pk', 'pretix_model', 'external_object_type', 'pretix_pk', 'external_pk', 'property_mapping'))
StaticMapping = namedtuple('StaticMapping', ('pk', 'pretix_model', 'external_object_type', 'pretix_id_field', 'external_id_field', 'property_mapping'))
class OutboundSyncProvider:
@@ -167,17 +167,17 @@ 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):
"""
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")
"""
if not hasattr(cls, 'identifier'):
raise TypeError('Call this method on a derived class that defines an "identifier" attribute.')
OrderSyncQueue.objects.create(
@@ -198,37 +198,37 @@ class OutboundSyncProvider:
info = cls.get_external_link_info(event, external_link_href, external_link_display_name)
return make_link(info, '{val}')
"""
Optionally override to configure a different retry backoff behavior
"""
def next_retry_date(self, sq):
"""
Optionally override to configure a different retry backoff behavior
"""
return now() + timedelta(hours=1)
"""
Optionally override this method to exclude certain orders from sync by returning False
"""
def order_valid_for_sync(self, order):
"""
Optionally override this method to exclude certain orders from sync by returning False
"""
return True
"""
Implementations must override this property to provide the data mappings as a list of objects.
They can return instances of the StaticMapping namedtuple defined above, or create their own
class (e.g. a Django model).
The returned objects must have at least the following properties:
- pk: unique identifier
- pretix_model: which pretix model to use as data source in this mapping. possible values are
the keys of sourcefields.AVAILABLE_MODELS
- external_object_type: destination object type in the target system. opaque string of maximum 128 characters.
- pretix_pk: which pretix data field should be used to identify the mapped object. any
DataFieldInfo.key returned by sourcefields.get_data_fields for the combination of
Event and pretix_model
- external_pk: destination identifier field in the target system
- property_mapping: mapping configuration as generated by PropertyMappingFormSet.to_property_mapping_json()
"""
@property
def mappings(self):
"""
Implementations must override this property to provide the data mappings as a list of objects.
They can return instances of the StaticMapping namedtuple defined above, or create their own
class (e.g. a Django model).
:return: The returned objects must have at least the following properties:
- `pk`: Unique identifier
- `pretix_model`: Which pretix model to use as data source in this mapping. Possible values are
the keys of ``sourcefields.AVAILABLE_MODELS``
- `external_object_type`: Destination object type in the target system. opaque string of maximum 128 characters.
- `pretix_id_field`: Which pretix data field should be used to identify the mapped object. Any ``DataFieldInfo.key``
returned by ``sourcefields.get_data_fields()`` for the combination of ``Event`` and ``pretix_model``.
- `external_id_field`: Destination identifier field in the target system.
- `property_mapping`: Mapping configuration as generated by ``PropertyMappingFormSet.to_property_mapping_json()``.
"""
raise NotImplementedError
def sync_queued_orders(self, queued_orders):
@@ -304,46 +304,52 @@ 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.
:param pk_field: Identifier field in the target system as provided in mapping.external_pk
:param pk_value: Identifier contents as retrieved from the property specified by mapping.pretix_pk of the model
specified by mapping.pretix_model
:param properties: All properties defined in mapping.property_mapping, as list of three-tuples
(external_field, value, overwrite)
:param inputs: All pretix model instances from which data can be retrieved for this mapping
:param mapping: The mapping object as returned by self.mappings
:param mapped_objects: Information about objects that were synced in the same sync run, by mapping definitions
*before* the current one in order of self.mappings.
Type is a dictionary {mapping.pk: [list of return values of this method]}
Useful to create associations between objects in the target system.
:return: {
"object_type": mapping.external_object_type,
"pk_field": pk_field,
"pk_value": pk_value,
"external_link_href": "https://external-system.example.com/backend/link/to/contact/123/",
"external_link_display_name": "Contact #123 - Jane Doe",
"...optionally further values you need in mapped_objects for association": 123456789,
}
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,
external_id_field,
id_value,
properties: list,
inputs: dict,
mapping,
mapped_objects: dict,
**kwargs,
):
"""
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.
:param external_id_field: Identifier field in the external system as provided in mapping.external_identifier
:param id_value: Identifier contents as retrieved from the property specified by mapping.pretix_identifier of the model
specified by mapping.pretix_model
:param properties: All properties defined in mapping.property_mapping, as list of three-tuples
(external_field, value, overwrite)
:param inputs: All pretix model instances from which data can be retrieved for this mapping
:param mapping: The mapping object as returned by self.mappings
:param mapped_objects: Information about objects that were synced in the same sync run, by mapping definitions
*before* the current one in order of self.mappings.
Type is a dictionary {mapping.pk: [list of return values of this method]}
Useful to create associations between objects in the target system.
Example code to create return value::
return {
# required:
"object_type": mapping.external_object_type,
"external_id_field": external_id_field,
"id_value": id_value,
# optional:
"external_link_href": "https://external-system.example.com/backend/link/to/contact/123/",
"external_link_display_name": "Contact #123 - Jane Doe",
"...optionally further values you need in mapped_objects for association": 123456789,
}
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.
"""
raise NotImplementedError()
def sync_object(
@@ -356,13 +362,13 @@ class OutboundSyncProvider:
properties = self.get_properties(inputs, mapping.property_mapping)
logger.debug("Properties: %r", properties)
pk_value = self.get_field_value(inputs, {"pretix_field": mapping.pretix_pk})
if not pk_value:
id_value = self.get_field_value(inputs, {"pretix_field": mapping.pretix_id_field})
if not id_value:
return None
info = self.sync_object_with_properties(
pk_field=mapping.external_pk,
pk_value=pk_value,
external_id_field=mapping.external_id_field,
id_value=id_value,
properties=properties,
inputs=inputs,
mapping=mapping,
@@ -371,8 +377,8 @@ class OutboundSyncProvider:
OrderSyncLink.objects.create(
order=inputs.get(ORDER), order_position=inputs.get(ORDER_POSITION), sync_provider=self.identifier,
external_object_type=info.get('object_type'),
external_pk_name=info.get('pk_field'),
external_pk_value=info.get('pk_value'),
external_id_field=info.get('external_id_field'),
id_value=info.get('id_value'),
external_link_href=info.get('external_link_href'),
external_link_display_name=info.get('external_link_display_name'),
)
@@ -411,15 +417,15 @@ class OutboundSyncProvider:
self.finalize_sync_order(order)
return mapped_objects
"""
Called after sync_object has been called successfully for all objects of a specific order. Can be used for saving
bulk information per order.
"""
def finalize_sync_order(self, order):
"""
Called after sync_object has been called successfully for all objects of a specific order. Can be used for saving
bulk information per order.
"""
pass
"""
Called after all orders of an event have been synced. Can be used for clean-up tasks (closing a session etc).
"""
def close(self):
"""
Called after all orders of an event have been synced. Can be used for clean-up tasks (closing a session etc).
"""
pass

View File

@@ -33,27 +33,27 @@ def assign_properties(
):
out = {}
for k, v, mode in new_values:
for field_name, v, mode in new_values:
if mode == MODE_OVERWRITE:
out[k] = v
out[field_name] = v
continue
elif mode == MODE_SET_IF_NEW and not is_new:
continue
if not v:
continue
current_value = old_values.get(k, out.get(k, ""))
current_value = old_values.get(field_name, out.get(field_name, ""))
if mode in (MODE_SET_IF_EMPTY, MODE_SET_IF_NEW):
if not current_value:
out[k] = v
out[field_name] = v
elif mode == MODE_APPEND_LIST:
_add_to_list(out, k, current_value, v, list_sep)
_add_to_list(out, field_name, current_value, v, list_sep)
else:
raise SyncConfigError(["Invalid update mode " + mode])
return out
def _add_to_list(out, key, current_value, new_item, list_sep):
def _add_to_list(out, field_name, current_value, new_item, list_sep):
new_item = str(new_item)
if list_sep is not None:
new_item = new_item.replace(list_sep, "")
@@ -64,4 +64,4 @@ def _add_to_list(out, key, current_value, new_item, list_sep):
new_list = current_value + [new_item]
if list_sep is not None:
new_list = list_sep.join(new_list)
out[key] = new_list
out[field_name] = new_list

View File

@@ -214,7 +214,7 @@ class SimpleOrderSync(OutboundSyncProvider):
StaticMapping(
pk=1,
pretix_model='Order', external_object_type='ticketorders',
pretix_pk='event_order_code', external_pk='ordernumber',
pretix_id_field='event_order_code', external_id_field='ordernumber',
property_mapping=json.dumps([
{
"pretix_field": "email",
@@ -309,7 +309,7 @@ def test_simple_order_sync(event):
StaticMappingWithAssociations = namedtuple('StaticMappingWithAssociations', (
'pk', 'pretix_model', 'external_object_type', 'pretix_pk', 'external_pk', 'property_mapping', 'association_mapping'
'pk', 'pretix_model', 'external_object_type', 'pretix_id_field', 'external_id_field', 'property_mapping', 'association_mapping'
))
AssociationMapping = namedtuple('AssociationMapping', (
'via_mapping_pk'
@@ -326,7 +326,7 @@ class OrderAndTicketAssociationSync(OutboundSyncProvider):
StaticMappingWithAssociations(
pk=1,
pretix_model='OrderPosition', external_object_type='tickets',
pretix_pk='ticket_id', external_pk='ticketnumber',
pretix_id_field='ticket_id', external_id_field='ticketnumber',
property_mapping=json.dumps([
{
"pretix_field": "ticket_price",
@@ -379,7 +379,7 @@ class OrderAndTicketAssociationSync(OutboundSyncProvider):
StaticMappingWithAssociations(
pk=2,
pretix_model='Order', external_object_type='ticketorders',
pretix_pk='event_order_code', external_pk='ordernumber',
pretix_id_field='event_order_code', external_id_field='ordernumber',
property_mapping=json.dumps([
{
"pretix_field": "email",