From d5690ba5a31ce47d43c97a422b291b2a35260f33 Mon Sep 17 00:00:00 2001 From: Dominik George <dominik.george@teckids.org> Date: Sun, 15 Nov 2020 23:15:30 +0100 Subject: [PATCH] Include related model fields in syncable_fields --- aleksis/core/mixins.py | 41 ++++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/aleksis/core/mixins.py b/aleksis/core/mixins.py index 2fcab29da..3f30af08d 100644 --- a/aleksis/core/mixins.py +++ b/aleksis/core/mixins.py @@ -270,13 +270,40 @@ class ExtensibleModel(models.Model, metaclass=_ExtensibleModelBase): to.property_(_virtual_related, related_name) @classmethod - def syncable_fields(cls) -> List[models.Field]: - """Collect all fields that can be synced on a model.""" - return [ - field - for field in cls._meta.fields - if (field.editable and not field.auto_created and not field.is_relation) - ] + def syncable_fields(cls, recursive: bool = True) -> List[models.Field]: + """Collect all fields that can be synced on a model. + + If recursive is True, it recurses into related models and generates virtual + proxy fields to access fields in related models.""" + fields = [] + for field in cls._meta.fields: + if field.is_relation and recursive: + # Recurse into related model to get its fields as well + for subfield in field.related_model.syncable_fields(): + # generate virtual field names for proxy access + name = f"_{field.name}__{subfield.name}" + verbose_name = f"{field.verbose_name} -> {subfield.verbose_name}" + + # Add proxy properties to handle access to related model + def getter(self): + if hasattr(self, field.name): + related = getattr(self, field.name) + return getattr(related, subfield.name) + # Related instane does not exist + return None + def setter(self, val): + if hasattr(self, field.name): + related = getattr(self, field.name) + else: + # Auto-create related instance (but do not save) + related = field.related_model() + setattr(related, subfield.name, val) + setattr(cls, name, property(getter, setter)) + + # Generate a fake field class with enough API to detect attribute names + fields.append(type("FakeRelatedProxyField", (), {"name": name, "verbose_name": verbose_name})) + elif field.editable and not field.auto_created: + fields.append(field) @classmethod def syncable_fields_choices(cls) -> Tuple[Tuple[str, str]]: -- GitLab