Django получает, какие модели являются дочерними для данной модели

Я хочу знать, какие модели являются дочерними для той или иной модели. Как я знаю, если у нас есть ownerModel, которая является родителем childModel1 и check1Model:

import uuid
from django.db import models

class ownerModel(models.Model):
    ownerId = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False, blank=True)

class check1Model(models.Model):
    checkId = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False, blank=True)
    owner=models.ForeignKey(ownerModel,on_delete=models.CASCADE)

class childModel1(models.Model):
    childId = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False, blank=True)
    check2=models.ForeignKey(ownerModel,on_delete=models.CASCADE)

тогда мы можем получить, какие модели являются дочерними по отношению к ownerModel с помощью следующего кода:

class myView(views.APIView):
    def get(self, request, format=None):
        for f in ownerModel._meta.get_fields():
            if 'field' in f.__dict__.keys():
                print('***childModels***')
                print(f.__dict__)
                print()
        return Response({'0000'}, status=status.HTTP_200_OK)

Я имею в виду проверку того, находится ли ключ field в __dict__.keys() в элементах ownerModel._meta.get_fields()

ofc так, как здесь мы получаем расширенную информацию о детских моделях:

***childModels***
{'field': <django.db.models.fields.related.ForeignKey: owner>, 'model': <class 'Users.models.ownerModel'>, 'related_name': None, 'related_query_name': None, 'limit_choices_to': {}, 'parent_link': False, 'on_delete': <function 
CASCADE at 0x00000286550848B0>, 'symmetrical': False, 'multiple': True, 'field_name': 'ownerId', 'related_model': <class 'Users.models.check1Model'>, 'hidden': False}

***childModels***
{'field': <django.db.models.fields.related.ForeignKey: check2>, 'model': <class 'Users.models.ownerModel'>, 'related_name': None, 'related_query_name': None, 'limit_choices_to': {}, 'parent_link': False, 'on_delete': <function CASCADE at 0x00000286550848B0>, 'symmetrical': False, 'multiple': True, 'field_name': 'ownerId', 'related_model': <class 'Users.models.childModel1'>, 'hidden': False}

поэтому я нахожу эти 2 условия необходимыми для получения информации о детских моделях:

  1. в дочерних моделях убедитесь, что дочерние отношения установлены с помощью строки, как показано ниже:
models.ForeignKey(ownerModel,on_delete=models.CASCADE)
  1. как сказано "если ключ field находится в __dict__.keys() в элементах ownerModel._meta.get_fields()" для получения информации о детях.

но проблема в том, что в некоторых случаях я не могу получить информацию о детях из родительской модели. так:

  1. мне интересно, достаточно ли этих двух условий, чтобы выяснить, какие модели являются детьми модели, а какие нет?

  2. существуют ли другие подобные способы узнать, какие модели являются детьми модели?

вы можете найти дочерние элементы модели с помощью Mode._meta.related_objects

# Example Code
from django.utils.functional import classproperty

class SomeModel(models.Model):
    class Meta:
        abstract = True
    
    @classproperty
    def related_objects(cls):
        """
        Return list of related models within the same module
        ManyToManyRel not supported
        """
        related_objects = [
            rel
            for rel in cls._meta.related_objects
            if isinstance(rel, (models.ManyToOneRel, models.OneToOneRel))
        ]
        return [
            rel
            for rel in related_objects
            if rel.related_model._meta.app_label == cls._meta.app_label
        ]

Вы можете использовать этот класс в качестве mro для своих моделей.

Если вы хотите получить обратные связи с этой моделью, то лучше всего использовать ownerModel._meta.related_objects. Однако это часть частного API Django и официально не документируется/поддерживается. Вы можете найти документацию в исходном коде.

Возвращает все связанные объекты, указывающие на текущую модель. Связанные объекты могут иметь тип связи "один к одному", "один ко многим" или "многие ко многим".

Частный API, предназначенный только для использования самим Django; get_fields() в сочетании с фильтрацией свойств полей является публичным API для получения этого списка полей.

Вы также можете получить аналогичный результат, используя публичный API, отфильтровав get_fields():

related_objects = [
    f for f in ownerModel._meta.get_fields(include_hidden=True)
    if (not f.hidden and (f.one_to_one or f.one_to_many)) or f.many_to_many
]

Это решение дополнительно дает вам отношения многие-ко-многим, определенные в ownerModel, что, вероятно, ожидаемо, если вы вообще хотите иметь отношения многие-ко-многим. Чтобы исключить все отношения "многие ко многим", просто удалите их из условия:

related_objects = [
    f for f in ownerModel._meta.get_fields(include_hidden=True)
    if not f.hidden and (f.one_to_one or f.one_to_many)
]

Способ строго получить только обратные отношения (т.е. удалить отношения "многие к многим", определенные в ownerModel), может заключаться в проверке наличия ForeignObjectRel в иерархии поля.

from django.db.models.fields.reverse_related import ForeignObjectRel

related_objects = [
    f for f in ownerModel._meta.get_fields(include_hidden=True)
    if isinstance(f, ForeignObjectRel) and (not f.hidden or f.many_to_many)
]

Нет очевидной причины, по которой использование __dict__ не будет работать. Однако __dict__ не предназначен для такого использования. Это ненадежно, и нет причин использовать его вместо hasattr(), например.

Вот несколько примеров того, что может пойти не так с __dict__:

In [1]: class A:
   ...:     def __init__(self):
   ...:         self.foo = 'foobaz'
   ...:         self.bar = 'barbaz'
   ...: 

In [2]: a = A()

In [3]: a.__dict__
Out[3]: {'foo': 'foobaz', 'bar': 'barbaz'}

In [4]: class B(A):
   ...:     @property
   ...:     def bar(self):
   ...:         return 'barfoo'
   ...:     @bar.setter
   ...:     def bar(self, value):
   ...:         pass
   ...: 

In [5]: b = B()

In [6]: b.__dict__
Out[6]: {'foo': 'foobaz'}

In [7]: 'bar' in b.__dict__
Out[7]: False

In [8]: hasattr(b, 'bar')
Out[8]: True

In [9]: class C(A):
   ...:     __slots__ = ('foo', 'bar')
   ...: 

In [10]: c = C()

In [11]: c.foo
Out[11]: 'foobaz'

In [12]: c.bar
Out[12]: 'barbaz'

In [13]: c.__dict__
Out[13]: {}

In [14]: hasattr(c, 'foo')
Out[14]: True

Вместо этого вы можете использовать hasattr():

for f in ownerModel._meta.get_fields():
    if hasattr(f, 'field'):
        print(f, f.on_delete)
        print()

Однако, проверка наличия атрибута field не кажется надежным решением для определения того, является ли поле обратной связью или нет. Я бы скорее рекомендовал полагаться на атрибуты one_to_one и one_to_many, которые являются None для полей без отношения и True/False для полей с отношением:

for f in ownerModel._meta.get_fields():
    if f.one_to_one or f.one_to_many:
        print(f, f.on_delete)
        print()
Вернуться на верх