1

I want to know what models are children of a model to retrieve their on_delete property. As I know, like below, if we have ownerModel which is parent of childModel1 and 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)

Then we can get what models are children of ownerModel with a code like this:

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)

I mean by checking if the field key is in __dict__.keys() in items of ownerModel._meta.get_fields().

Of course, here we get extended info about children models:

***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}

So I find these 2 conditions necessary to get child models info:

  1. In child models, making sure child relationship is set up with a line like below:
models.ForeignKey(ownerModel,on_delete=models.CASCADE)
  1. As said "if the field key is in __dict__.keys() in items of ownerModel._meta.get_fields()" to get children info.

But the problem is that in some cases I can't get the children info from parent model. So:

  1. It makes me wonder if these 2 conditions are enough to find out which models are children of a model?
  2. Are there other similar ways to get which models are children of a model?

By the way, I want to have on_delete also and having on_delete is the only reason I am using _meta.get_fields() over _meta.fields because _meta.fields doesn't provide on_delete property.

This is my code if you wanna have a look. Note that in full answer I also wanna know what has made problem. So in this case, that __dict__.keys() doesn't provide items which don't have field in their keys (doesn't provide child model details). Because generally those 2 conditions provide child model details. So later I can get child model details in all codes.

the problem is that even with for f in ownerModel._meta.get_fields(include_hidden=True) and without any further ifs doesnt retrieve lines including on_delete properties in this project. but in the other projects ownerModel._meta.get_fields() provides them. and I don't whats the cause that sometimes ownerModel._meta.get_fields() provides these relationships infos and other times doesnt.

Farhang Amaji
  • 742
  • 11
  • 24
  • What do you mean by "with `on_delete` property"? All and only `ForeignKey` and `OneToOneField` fields will have an `on_delete` property. Do you want to find all the models that depend on this model? Only foreign keys and one-to-one or also many-to-many? – Antoine Pinsard Nov 15 '22 at 18:16
  • yes I want to find the ' find all the models that depend on this model' and their `on_delete` property – Farhang Amaji Nov 15 '22 at 21:42

3 Answers3

1

you can find children of a model using Model._meta.related_objects

# Example Code
from django.utils.functional import classproperty
from django.db import models

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.related_model, rel.on_delete)
            for rel in related_objects
            if rel.related_model._meta.app_label == cls._meta.app_label
        ]

You can use this class as an mro for your models.

Zkh
  • 490
  • 1
  • 4
  • 16
  • I just added `class Meta` part and the `@classproperty` to one of my existing models and tried to get `model1.related_objects` and it returned `[]` empty list. btw I want to have `on_delete` also and having `on_delete` is the only reason Iam using `_meta.get_fields()` over `_meta.fields` because `_meta.fields` doesnt provide `on_delete` property. – Farhang Amaji Nov 07 '22 at 17:50
  • 1
    Please check your code, and i have updated my answer to include `on_delete`. – Zkh Nov 07 '22 at 23:49
  • @Mike_Jones thanks but I told u that I just added ur code to one of my classes and after that print `model1.related_objects` it returned `[]` (empty list) but supposed to return the children. – Farhang Amaji Nov 08 '22 at 00:49
  • Maybe this doesn't work for the same reason _meta.get_fields() doesn't work – Farhang Amaji Nov 08 '22 at 01:25
  • `Abstract` classes are meant to be inherited `class OwnerModel(SomeModel):` – Zkh Nov 08 '22 at 01:38
  • even this made no difference – Farhang Amaji Nov 08 '22 at 11:42
  • s24.picofile.com/file/8455316142/ChatTry6.rar.html this my code if u wanna take a look at. – Farhang Amaji Nov 08 '22 at 13:07
  • the problem is I have a code super similar to this code(past versions of this code) and it had no problem with `_meta.get_fields()` regarding showing child models and on_delete – Farhang Amaji Nov 08 '22 at 14:19
  • i don't know what to tell you, i have 30 models relying on this abstract class and they are all returning their children. try remove this condition `if rel.related_model._meta.app_label == cls._meta.app_label` from your code – Zkh Nov 08 '22 at 14:25
  • it made no difference – Farhang Amaji Nov 08 '22 at 14:30
  • Shoot ! then i really don't what to tell you ... – Zkh Nov 08 '22 at 14:39
  • thanks mike I also have no clue why these similar codes dont produce the same results – Farhang Amaji Nov 08 '22 at 14:52
0

If you want to retrieve the backward relationships to this model, you may use ownerModel._meta.related_objects. This is however part of Django's private API and isn't officially documented/supported. You can yet find documentation in the source code.

Return all related objects pointing to the current model. The related objects can come from a one-to-one, one-to-many, or many-to-many field relation type.

Private API intended only to be used by Django itself; get_fields() combined with filtering of field properties is the public API for obtaining this field list.

You can also get similar result using the public API by filtering 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
]

This solution additionally gives you the many-to-many relationships defined within ownerModel, which is likely expected if you want many-to-many at all. To exclude all the many-to-many relationships, just remove them from the condition:

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)
]

A way to strictly get the backwards relationships only (ie. removing the many-to-many and one-to-one relations defined within the ownerModel), could be to test the presence of ForeignObjectRel in the field's hierarchy.

from django.db.models 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)
]

If what you want is to display the value of the on_delete property, you can limit the results to one-to-one and one-to-many fields.

from django.db.models import ForeignObjectRel

related_objects = [
    f for f in ownerModel._meta.get_fields()
    if isinstance(f, ForeignObjectRel)
    and (f.one_to_one or f.one_to_many)
]
for f in related_objects:
    print(f, f.on_delete)
Antoine Pinsard
  • 33,148
  • 8
  • 67
  • 87
  • the problem is that even with `for f in ownerModel._meta.get_fields(include_hidden=True)` and without any further `if`s there are no lines including `on_delete` properties in this project. but in the other projects `ownerModel._meta.get_fields()` provides them. and I don't whats the cause that sometimes `ownerModel._meta.get_fields()` provides these relationships infos and other times doesnt. – Farhang Amaji Nov 17 '22 at 13:47
  • and the problem is not related to ability take some key from the `__dict__` or `hasattr()` – Farhang Amaji Nov 17 '22 at 13:52
0

Update

After further investigation in the comments, it turned out that the issue was that models were not defined in a models submodule. As stated in Django docs:

Once you have defined your models, you need to tell Django you’re going to use those models. Do this by editing your settings file and changing the INSTALLED_APPS setting to add the name of the module that contains your models.py.

This requirement can also be confirmed in Django source code that handles models detection:

MODELS_MODULE_NAME = "models"


class AppConfig:

    ...

    def import_models(self):
        # Dictionary of models for this app, primarily maintained in the
        # 'all_models' attribute of the Apps this AppConfig is attached to.
        self.models = self.apps.all_models[self.label]

        if module_has_submodule(self.module, MODELS_MODULE_NAME):
            models_module_name = "%s.%s" % (self.name, MODELS_MODULE_NAME)
            self.models_module = import_module(models_module_name)

Original answer

There is no obvious reason why using __dict__ wouldn't work. However, __dict__ is not really meant for this kind of usage. This is not reliable and there is no reason to use it over hasattr() for instance.

Here are a few examples of what could go wrong with __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'}
# B redefined 'bar' as a property, which removed it from __dict__

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]: {}
# C defined 'foo' and 'bar' as slots, which removed them from __dict__

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

You could instead use hasattr():

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

However, checking on the presence of a field attribute doesn't seem to be a reliable solution to determine whether the field is a backward relation or not. I would rather recommend to rely on one_to_one and one_to_many attributes which are None for non-relation fields, and True/False for relation fields:

for f in ownerModel._meta.get_fields():
    if (f.one_to_one or f.one_to_many) and hasattr(f, 'on_delete'):
        print(f, f.on_delete)
        print()

I also included hasattr(f, 'on_delete') in the condition, to exclude OneToOneField relations defined within ownerModel. It would probably be cleaner to test isinstance(f, ForeignObjectRel) though:

from django.db.models import ForeignObjectRel

for f in ownerModel._meta.get_fields():
    if isinstance(f, ForeignObjectRel) and (f.one_to_one or f.one_to_many):
        print(f, f.on_delete)
        print()
Antoine Pinsard
  • 33,148
  • 8
  • 67
  • 87
  • hi AntoinePinsard 1st where is ur last answer? 2nd with `if f.one_to_one or f.one_to_many:` there is no `on_delete` info – Farhang Amaji Nov 17 '22 at 12:36
  • I deleted my first answer as I believe it didn't address your issue properly. I can undelete it if you think some information were interesting. As of the issue with `on_delete`, I just noticed that it fails in some specific case. I believe adding the conditon `hasattr(f, 'on_delete')` should be sufficient. Can you confirm? – Antoine Pinsard Nov 17 '22 at 13:04
  • the main problem is that `ownerModel._meta.get_fields()` doesnt have the lines containing the `{'field': , 'model': , 'related_name': None, 'related_query_name': None, 'limit_choices_to': {}, 'parent_link': False, 'on_delete': , 'symmetrical': False, 'multiple': True, 'field_name': 'ownerId', 'related_model': , 'hidden': False}` – Farhang Amaji Nov 17 '22 at 13:37
  • What do you mean? Why wouldn't it have these lines? What does `f.__dict__` show? I just checked with one of my models: https://bpa.st/RJZA – Antoine Pinsard Nov 17 '22 at 14:01
  • let just say that there is an inconsistency in django `._meta.get_fields()`. I know `ownerModel._meta.get_fields()` should provide lines that have relationships infos. and in other projects I have used it. but in my new project doesnt work. and I dont know the reason – Farhang Amaji Nov 17 '22 at 14:06
  • I also have provided my codes in highlighted `mycode` link in the question. – Farhang Amaji Nov 17 '22 at 14:09
  • What does `ownerModel._meta.get_fields()` return then? We need to figure out what's going wrong, not try to find an alternative way to get what it should return. – Antoine Pinsard Nov 17 '22 at 14:13
  • for ie in `PvChatModel._meta.get_fields()` which is supposed to have `PvChatMember` as a child just returns https://onecompiler.com/python/3yp8kbus6 (not its just a text not a code) – Farhang Amaji Nov 17 '22 at 14:19
  • Your models aren't defined in a `models` submodule of your apps. I don't believe Django can detect them properly. See [Using models](https://docs.djangoproject.com/en/stable/topics/db/models/#using-models) and [how Django detects models](https://github.com/django/django/blob/9c19aff/django/apps/config.py#L262-L269). – Antoine Pinsard Nov 17 '22 at 14:30
  • I think I also had tried to put them back in `models.py` and didnt work. I would check another time. – Farhang Amaji Nov 17 '22 at 14:34
  • 1
    thank you AntoinePinsard I tried putting models in `models.py` it again and solved the problem. so please make the final answer. thank you again – Farhang Amaji Nov 17 '22 at 15:13