Is there a way to inspect field attributes on a related model during model loading?

Viewed 6

Suppose I have two models, the former owned by third party code:

class A(models.Model):
    x = models.CharField(max_length=100)


class B(models.Model):
    a = models.ForeignKey("other_app.A", ...)
    name = models.CharField(...)

Suppose further that I'd like B.name.max_length to be set based on B.a.x.max_length. You can imagine this to be useful for instance when denormalising fields for string representations. Should A be django.contrib.auth.get_user_model(), and B be a Profile, B.name might want to read f"Profile for {self.a.username}", thus setting the requirement for B.name.max_length == A.x.max_length + len("Profile for ").

I could head in and inspect what the .max_lengths on my dependencies are manually, sum them manually, and hardcode it, but that is manual work to duplicate data, which is likely to change. Should A.x.max_length increase without a change in B.name.max_length, suddenly certain new values in the database can cause 500s.

I could set B.name.max_length to "something sufficiently large", but that just kicks the can down the road. If my dependencies set a combined amount of 200, so I set mine to 255 to be safe, my dependents and theirs and theirs have an expanding problem.

Lastly, and the reason I'm asking this, I wish I could query the related model's field's attributes and set based on that, but I seemingly can't because of Django's insistence on magic with models (and forms but that's irrelevant).

class B(...):
    ...
    name = models.CharField(
        max_length=a.x.field.max_length + len(PREFIX),
        ...,
    )

# AttributeError: 'ForeignKey' object has no attribute 'x'

class B(...):
    ...
    name = models.CharField(
        max_length=a.related_model.x.field.max_length + len(PREFIX),
        ...,
    )

#  django.core.exceptions.AppRegistryNotReady: Models aren't loaded yet.

Is there any way to divine A.x.max_length from B during model loading?

I'm also quite ready to entertain the XY problem approach, so if that's the case the alternate question would be "what is the most elegant way to define CharField.max_length when the contents will come from another field?"

For anyone cracking open a shell to mess with this, the attribute whisks away to A.x.field.max_length after the Django metaclass magic involved during model loading. To play around in the moment, I recommend setting up the models as roughly described, then running python -i manage.py shell, and in the interpreter import pdb; pdb.pm().

1 Answers

Diving into the source code I've come up with the following roundabout way to get the information from the model.

from django.apps import apps

class B:
    ...
    name = models.CharField(max_length=(
        apps.get_model(
            a.remote_field.model,
            require_ready=False,
        ).x.field.max_length,
    ))

The main trick I found was the require_ready kwarg to .get_model(). Without it, I get the same Models aren't loaded yet. exception as from a.related_model, which makes me think there should be/is a better way to query ForeignKey().related_model before models are loaded. I'm leaving this answer unaccepted for a while pending anyone coming up with said better way.

Related