Skip to content
Snippets Groups Projects
Commit 75256e08 authored by Nik | Klampfradler's avatar Nik | Klampfradler
Browse files

Merge branch 'feature/extensible-model-fk' into 'master'

Allow adding a foreign key to Exensible Model

See merge request AlekSIS/official/AlekSIS!305
parents 5bae5747 9202d5f8
No related branches found
No related tags found
1 merge request!305Allow adding a foreign key to Exensible Model
Pipeline #2904 passed
......@@ -21,7 +21,7 @@ from django.views.generic.edit import ModelFormMixin
import reversion
from easyaudit.models import CRUDEvent
from guardian.admin import GuardedModelAdmin
from jsonstore.fields import JSONField, JSONFieldMixin
from jsonstore.fields import IntegerField, JSONField, JSONFieldMixin
from material.base import Layout, LayoutNode
from rules.contrib.admin import ObjectPermissionsModelAdmin
......@@ -103,7 +103,7 @@ class ExtensibleModel(models.Model, metaclass=_ExtensibleModelBase):
objects_all_sites = models.Manager()
extra_permissions = []
def get_absolute_url(self) -> str:
"""Get the URL o a view representing this model instance."""
pass
......@@ -209,6 +209,66 @@ class ExtensibleModel(models.Model, metaclass=_ExtensibleModelBase):
cls._safe_add(field, name)
@classmethod
def foreign_key(
cls,
field_name: str,
to: models.Model,
to_field: str = "pk",
to_field_type: JSONFieldMixin = IntegerField,
related_name: Optional[str] = None,
) -> None:
"""Add a virtual ForeignKey.
This works by storing the primary key (or any field passed in the to_field argument)
and adding a property that queries the desired model.
If the foreign model also is an ExtensibleModel, a reverse mapping is also added under
the related_name passed as argument, or this model's default related name.
"""
id_field_name = f"{field_name}_id"
if related_name is None:
related_name = cls.Meta.default_related_name
# Add field to hold key to foreign model
id_field = to_field_type()
cls.field(**{id_field_name: id_field})
@property
def _virtual_fk(self) -> Optional[models.Model]:
id_field_val = getattr(self, id_field_name)
if id_field_val:
try:
return to.objects.get(**{to_field: id_field_val})
except to.DoesNotExist:
# We found a stale foreign key
setattr(self, id_field_name, None)
self.save()
return None
else:
return None
@_virtual_fk.setter
def _virtual_fk(self, value: Optional[models.Model] = None) -> None:
if value is None:
id_field_val = None
else:
id_field_val = getattr(value, to_field)
setattr(self, id_field_name, id_field_val)
# Add property to wrap get/set on foreign model instance
cls._safe_add(_virtual_fk, field_name)
# Add related property on foreign model instance if it provides such an interface
if hasattr(to, "_safe_add"):
def _virtual_related(self) -> models.QuerySet:
id_field_val = getattr(self, to_field)
return cls.objects.filter(**{id_field_name: id_field_val})
to.property_(_virtual_related, related_name)
@classmethod
def syncable_fields(cls) -> List[models.Field]:
"""Collect all fields that can be synced on a model."""
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment