Skip to content

Commit

Permalink
Make LegacyGenericforeignKey work correctly on Django > 1.8.x
Browse files Browse the repository at this point in the history
  • Loading branch information
hmpf committed Jan 28, 2025
1 parent 24384fa commit 2de7923
Showing 1 changed file with 31 additions and 25 deletions.
56 changes: 31 additions & 25 deletions python/nav/models/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from django import forms
from django.db import models
from django.db.models import signals
from django.db.models.fields.mixins import FieldCacheMixin
from django.core import exceptions
from django.db.models import Q
from django.apps import apps
Expand Down Expand Up @@ -146,7 +147,7 @@ def formfield(self, **kwargs):
# this interfaces with Django model protocols, which generates unnecessary
# pylint violations:
# pylint: disable=W0201,W0212
class LegacyGenericForeignKey(object):
class LegacyGenericForeignKey(FieldCacheMixin):
"""Generic foreign key for legacy NAV database.
Some legacy tables in NAV have generic foreign keys that look very much
Expand All @@ -155,16 +156,25 @@ class LegacyGenericForeignKey(object):
"""

# Field flags
auto_created = False
concrete = False
editable = False
hidden = False

is_relation = True
many_to_many = False
many_to_one = True
one_to_one = False
related_model = None
remote_field = None

def __init__(self, model_name_field, model_fk_field, for_concrete_model=True):
self.mn_field = model_name_field
self.fk_field = model_fk_field
self.is_relation = True
self.many_to_many = False
self.one_to_many = True
self.related_model = None
self.auto_created = False
self.for_concrete_model = for_concrete_model
self.editable = False
self.for_concrete_model = for_concrete_model

def __str__(self):
modelname = getattr(self, 'mn_field')
Expand All @@ -175,14 +185,16 @@ def contribute_to_class(self, cls, name):
"""Add things to the model class using this descriptor"""
self.name = name
self.model = cls
self.cache_attr = "_%s_cache" % name
cls._meta.private_fields.append(self)

if not cls._meta.abstract:
signals.pre_init.connect(self.instance_pre_init, sender=cls)

setattr(cls, name, self)

def get_cache_name(self):
return self.name

def instance_pre_init(self, signal, sender, args, kwargs, **_kwargs):
"""
Handles initializing an object with the generic FK instead of
Expand All @@ -197,28 +209,22 @@ def instance_pre_init(self, signal, sender, args, kwargs, **_kwargs):
kwargs[self.mn_field] = None
kwargs[self.fk_field] = None

def is_cached(self, instance):
return hasattr(instance, self.cache_attr)

def __get__(self, instance, instance_type=None):
if instance is None:
return self

try:
return getattr(instance, self.cache_attr)
except AttributeError:
rel_obj = None
rel_obj = self.get_cached_value(instance, default=None)

field = self.model._meta.get_field(self.mn_field)
table_name = getattr(instance, field.get_attname(), None)
rel_model = self.get_model_class(table_name)
if rel_model:
try:
rel_obj = rel_model.objects.get(id=getattr(instance, self.fk_field))
except exceptions.ObjectDoesNotExist:
pass
setattr(instance, self.cache_attr, rel_obj)
return rel_obj
field = self.model._meta.get_field(self.mn_field)
table_name = getattr(instance, field.get_attname(), None)
rel_model = self.get_model_class(table_name)
if rel_model:
try:
rel_obj = rel_model.objects.get(id=getattr(instance, self.fk_field))
except exceptions.ObjectDoesNotExist:
pass
self.set_cached_value(instance, rel_obj)
return rel_obj

def __set__(self, instance, value):
if instance is None:
Expand All @@ -232,7 +238,7 @@ def __set__(self, instance, value):

setattr(instance, self.mn_field, table_name)
setattr(instance, self.fk_field, fkey)
setattr(instance, self.cache_attr, value)
self.set_cached_value(instance, value)

@staticmethod
def get_model_name(obj) -> str:
Expand Down

0 comments on commit 2de7923

Please sign in to comment.