From b4dab2cd9c135322447fb61db1a777a85f82fe8f Mon Sep 17 00:00:00 2001 From: Andy Babic Date: Sun, 27 Oct 2024 06:32:47 +0000 Subject: [PATCH] Allow fields to be excluded from the model_from_serializable_data() process --- modelcluster/models.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/modelcluster/models.py b/modelcluster/models.py index 58e1294..8007242 100644 --- a/modelcluster/models.py +++ b/modelcluster/models.py @@ -58,8 +58,18 @@ def get_serializable_data_for_fields(model, exclude_fields=None): return obj -def model_from_serializable_data(model, data, check_fks=True, strict_fks=False): +def model_from_serializable_data(model, data, check_fks=True, strict_fks=False, exclude_fields=None): + """ + Return an instance of the given model, built from the serialised data + (`data`). + + :param check_fks: Optional. If set to False, disables checking of ForeignKey values. + :param strict_fks: Optional. If set to True, enables strict foreign key checks. + :param exclude_fields: Optional. An iterable of field names to exclude from the process. + """ + pk_field = model._meta.pk + exclude = set(exclude_fields or ()) kwargs = {} # If model is a child via multitable inheritance, we need to set ptr_id fields all the way up @@ -71,6 +81,9 @@ def model_from_serializable_data(model, data, check_fks=True, strict_fks=False): kwargs[pk_field.attname] = data['pk'] for field_name, field_value in data.items(): + if field_name in exclude: + continue + try: field = model._meta.get_field(field_name) except FieldDoesNotExist: @@ -80,11 +93,11 @@ def model_from_serializable_data(model, data, check_fks=True, strict_fks=False): if isinstance(field, ForeignObjectRel): continue - if field.remote_field and isinstance(field.remote_field, models.ManyToManyRel): + if field.remote_field and isinstance(field.remote_field, models.ManyToManyRel) and field_name not in exclude: related_objects = field.remote_field.model._default_manager.filter(pk__in=field_value) kwargs[field.attname] = list(related_objects) - elif field.remote_field and isinstance(field.remote_field, models.ManyToOneRel): + elif field.remote_field and isinstance(field.remote_field, models.ManyToOneRel) and field_name not in exclude: if field_value is None: kwargs[field.attname] = None else: @@ -107,6 +120,11 @@ def model_from_serializable_data(model, data, check_fks=True, strict_fks=False): else: raise Exception("can't currently handle on_delete types other than CASCADE, SET_NULL and DO_NOTHING") + elif field_name in exclude: + # In order to not break custom `__init__()` functionality that depends on + # field values being available, use Django's DEFERRED mechanism, allowing + # the canonical field value to be loaded from the database on access. + kwargs[field.name] = models.DEFERRED else: value = field.to_python(field_value)