Django: list all reverse relations of a model
Asked Answered
R

5

23

I would like my django application to serve a list of any model's fields (this will help the GUI build itself).

Imagine the classes (ignore the fact that all field of Steps could be in Item, I have my reasons :-) )

class Item(models.Model):
    name = models.CharField(max_length=100)
    description = models.TextField()

class Steps(models.Model):
    item = models.OneToOneField('Item', related_name='steps')
    design = models.BooleanField(default=False)
    prototype = models.BooleanField(default=False)
    production = models.BooleanField(default=False)

Now, when I want to list a model's fields:

def get_fields(model):
    return model._meta.fields + model._meta.many_to_many

But I would also like to get the list of "related" one-to-one foreign keys to my models. In my case Item.steps would not be in that list.

I have found that model._meta.get_all_field_names does include all the related fields.

But when I call Item._meta.get_field_by_name('steps') it returns a tuple holding a RelatedObject, which does not tell me instantly whether this is a single relation or a one-to-many (I want to list only reversed one-to-one relations).

Also, I can use this bit of code:

from django.db.models.fields.related import SingleRelatedObjectDescriptor
reversed_f_keys = [attr for attr in Item.__dict__.values() \
                  if isinstance(attr, SingleRelatedObjectDescriptor)]

But I'm not very satisfied with this.

Any help, idea, tips are welcome!

Cheers

Rosa answered 22/10, 2013 at 7:57 Comment(1)
So yeah, I have a potential solution for my need, but I would be pleased to have a better one :)Rosa
S
31

This was changed (in 1.8 I think) and Olivier's answer doesn't work anymore. According to the docs, the new way is

[f for f in Item._meta.get_fields()
    if f.auto_created and not f.concrete]

This includes one-to-one, many-to-one, and many-to-many.

Striate answered 6/3, 2017 at 13:47 Comment(5)
Thanks for the heads up on the deprecation. It'll save me a headache when I upgrade to 1.10 :) (stuck with 1.6 for now though)Rosa
God I can't believe I've never thought of this before, so if it is auto_created and not concrete it will always be a reverse relationship?Rajah
There's a problem wit this solution, it also lists forward manytomany relationships that have a throughtable, I found a way to filter them but its very tediousRajah
The field attribute reference helps to determine the correct set of filters.Egidio
@Mojimi: Good question. A reverse relation appears on your model like magic, so it is auto_created. The actual database column holding the foreign keys is not part of your model (it is part of the related model), so it is not concrete. On the other hand, e.g. the id field is also auto_created but it is concrete, and a ForeignKey field is not auto_created but it is concrete. I wonder if e.g. f.is_relation and f.auto_created could also work.Egidio
R
3

I've found out that there are methods of Model._meta that can give me what I want.

my_model = get_model('app_name','model_name')
# Reverse foreign key relations
reverse_fks = my_model._meta.get_all_related_objects()
# Reverse M2M relations
reverse_m2ms = my_model._meta.get_all_related_many_to_many_objects()

By parsing the content of the relations, I can guess whether the "direct" field was a OneToOneField or whatever.

Rosa answered 16/6, 2015 at 13:8 Comment(0)
D
2

I was looking into this answer as a starting point to identify reversed relationships for a model instance.

So, I noticed that when you get all the fields using instance._meta.get_fields(), those that are direct relationships, which are 3 types (ForeignKey, ManyToMany, OneTone), their parent class (field.__class__.__bases__) is django.db.models.fields.related.ForeignKey.

However, those that are reverse relationships inherit from django.db.models.fields.reverse_related.ForeignObjectRel. And if you take a look at this class, it has:

auto_created = True
concrete = False

So you could identify those by the attributes mentioned in the top-rated answer or by asking isinstance(field, ForeignObjectRel.

Another thing I could notice is that those reverse relationships have a field attribute which points to the direct relationship generating that reverse relationship.

Additionally, in order to exclude the fields instantiating the through table, those have through and through_fields attributes

Delighted answered 21/4, 2022 at 21:8 Comment(0)
E
0

And what about this :

oneToOneFieldNames = [
    field_name 
    for field_name in Item._meta.get_all_field_names() 
    if isinstance(
        getattr(
            Item._meta.get_field_by_name(field_name)[0], 
            'field', 
            None
        ), 
        models.OneToOneField
    )
]

RelatedObject may have a Field attribute for relations. You just have to check if this is a OneToOne field and you can retrieve only what you want

Ephah answered 9/7, 2014 at 13:57 Comment(0)
C
0

if you are using Django Rest Framework, you could use something like that for your obj:

from rest_framework.utils import model_meta
info = model_meta.get_field_info(obj)
for field in obj.__class__.__dict__.keys():
    if field in info.relations and info.relations[field].to_many and info.relations[field].reverse:
        #print all reverse relations
        print(field)
Chaulmoogra answered 15/10, 2022 at 20:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.