Introspect related model to determine name of attribute for reverse descriptor?

Given schema:

class User(models.Model):
    name = models.CharField(max_length=50)

class Post(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)

User objects will end up having a post_set attribute of type:


Which returns a RelatedManager based on Post’s default manager. Cool.

Q: But if you’re trying to introspect on a schema, how do you determine that the attribute name is “post_set”?

When I call User._meta.get_fields(), I see a field called “post”, not “post_set”. (Of type: django.db.models.fields.reverse_related.ManyToOneRel)

I’ve explored nearly all of ManyToOneRel’s API, and can’t find any attribute or method that will return the value “post_set”, only “post”.

Because post_set is a manager, post is the related field name. They are two different things used two different ways.

1 Like

Is there a way via introspection to determine the attribute name (“post_set”) containing that manager for a given ForeignKey?

The “_set” managers are attributes of the class itself, just like objects. If you print dir(User) you should see post_set as an attribute. I’m not aware of any specific API allowing you to specifically enumerate them (not that I’ve ever looked for one).

However, this is only true if the related_name isn’t set. If the related_name attribute is set then there wouldn’t be anything to find it directly. You’d need to iterate over the entire set of attributes in the class to find those that are instances of the proper type.

1 Like

I’m disappointed that field.attname doesn’t contain this information. Instead, it just contains the same value as field.column – which, in the case of User.post_set, is None for both (since it’s a reverse relation rather than a DB column).

I would think if any of Field’s attributes were to contain the attribute name used during model class definition, it would be the one called attname. :slight_smile:

Based on my rudimentary understanding of internals, this seems like it could have been implemented as part of the contribute_to_class function.

The point here is that it’s not a field, its a manager. It wouldn’t make sense to classify it in any way as a field, because it’s not a field and you don’t use it as one. Now, if there was an API to list all known managers associated with a model, I would expect to see it there.

" The point here is that it’s not a field" – yes, but neither is, yet get_fields() returns it.

I feel like you’re picking on my terminology instead of understanding what I’m getting at.

This is an incorrect statement. is a field from the perspective of the ORM. For example, you can use it in queries in filters. (See Model _meta API | Django documentation | Django).

I can validly write User.objects.filter(post__id=1) to return a queryset with the User object that is related to the Post with the id of 1. (Assuming your primary key field for that model is named id.)

I cannot do that with post_set. There is no query construction where I include post_set as the attribute parameter in a filter, get, etc.

That’s why I keep focusing on the difference between a field and a manager. They are different objects used in different ways in the ORM.

It may help if you provided more detail about what you’re trying to do with this information.

1 Like

I just found an interesting comment here:

However, it seems that note was written seven years ago.

I understand what managers are. I think the difference is in my mind field equates to column, but you’re saying that the determining factor in something being considered a field is it’s useability in the QuerySet API.

As for what I’m trying to do – in general, learn the ORM. However, the specific thread that lead me to this question was:

  1. I was re-reading the Query topic doc to better learn how to query across relations.
  2. I noticed they weren’t setting related_name, but then they queried using post instead of post_set, which I know would be the default related_name in the absence of setting it explicitly.
  3. That sent me down a Field introspection rabbit hole.

I’ve made a habit of explicitly setting my related_name (either to a value or “+”) because it seemed like good practice and I like choosing the best name for each situation.

I now realize that setting related_name was effectively setting related_query_name to the same value, achieving parity between the class attribute name used to mount the manager and the field name used to execute queries – thus making it easy to not notice that those are two separate concepts. Thus, it shocked me to see “post” instead of “post_set” used in the query.

I ran some experiments. I tried four different configurations:

class Post(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="foo")
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="foo", related_query_name="bar")
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_query_name="bar")

Here’s a table of the results of checking the name, related_name, and related_query_name on both fields generated by the relation:

                                        w/RN        w/RN+RQN        w/RQN
  - name                : "user"    : "user"    : "user"    : "user"
  - related_name        : -         : -         : -         : -
  - related_query_name  : ->"post"  : ->"foo"   : ->"bar"   : ->"bar"
  - name                : "post"    : "foo"     : "bar"     : "bar"
  - related_name        : None      : "foo"     : "foo"     : None
  - related_query_name  : None      : None      : "bar"     : "bar"


  • ForeignKey.related_name never exists
  • ForeignKEy.related_query_name is always a callable that returns what I’d expect
  • ManyToOneRel.related_name is None unless explicitly set in FK
  • ManyToOneRel.related_query_name is None unless explicitly set in FK, then it’s a str

I guess my gripes are:

  1. It’s weird how related_query_name is sometimes None, sometimes str, and sometimes callable. Makes it hard to write non-fragile introspection tools.
  2. The fact that the RN and RQN attributes only return values when explicitly set is fine, I guess, since you want to be able to tell if something is explicitly set, but I’m disappointed there’s no API to get the answer regardless of whether it was set implicitly or explicitly – there’s no API that will ever return “post_set” as an answer. (Unless you explicitly set the RN to “post_set”, obviously.)

I guess the fallback is to branch on the None value, and when None, provide my own transform that implements Django’s default behavior (taking lower-case model name and adding “_set” to it).

UPDATE You can at least use to get the field name on the other side (the name used for queries).

1 Like

Another summary I just wrote for myself:

Each relation has two endpoints, and each endpoint has two names:

  • a class attribute name (for accessing objects and managers)
  • a query name (for use with the QuerySet API)

When you add a relation field to a model via assignment (foo = models.ForeignKey(...)), both names on the local side are set to the name used in the assignment (“foo”).

On the reverse side…

  • the class attribute name is set by related_name if set, else defaults to f"{Model._meta.model_name}_set" (or just Model._meta.model_name for OneToOneField)
  • the query name is set by related_query_name if set, else related_name if set, else defaults to "Model._meta.model_name"

Is that a good summary?

As far as it goes, yes. (With perhaps the minor clarification that there might not be a reverse entry created, as in the case of related_name='+'.) Otherwise, I think this works.

1 Like