Get verbose name of foreign key from objects _meta dynamically in Django

Viewed 1285

For simplicity sake, with models like the following:

 class Plan(models.Model)
     name = models.CharField(max_length=255, verbose_name="Phone Plan Name")
     monthly_charge = models.FloatField()
 class Company(models.Model):
     name = models.CharField(max_length=255, verbose_name="Company Full Name")
     phone_plan = models.ForeignKey(Plan)
 class Client(models.Model):
     name = models.CharField(max_length=255, verbose_name="Client Full Name")
     company = models.ForeignKey(Company)

Given a string, I want to know if there is an easy way to retrieve the verbose name of a model attribute even if that string traverses through foreign keys.

I know that I can get the verbose name of a Client attribute by

Client._meta.get_field("name").verbose_name 

and this would result in "Client Full Name".

But what if I had the string "company__phone_plan__name", I cannot simply use

Client._meta.get_field("company__phone_plan__name").verbose_name

to arrive at "Phone Plan Name" as it yields an error.

These strings will be dynamic, so I am wondering what is the easiest way to arrive at the proper verbose name of an attribute, even if it traverses models?

This particular case is using Django 1.11

2 Answers

This is not so good answer but if you need what you want you can use this function:

def get_verbose_name(model, string):
    fields = string.split('__')

    for field_name in fields[:-1]:
        field = model._meta.get_field(field_name)

        if field.many_to_one:
            model = field.foreign_related_fields[0].model
        elif field.many_to_many or field.one_to_one or field.one_to_many:
            model = field.related_model
        else:
            raise ValueError('incorrect string')

    return model._meta.get_field(fields[-1]).verbose_name

This function gets model name and string and return verbose name

You can use it like so:

get_verbose_name(Client, 'company__phone_plan__name')

If you are working with instances of the models, then you could try this:

c = Client.objects.first()

# c._meta.get_field('company').verbose_name
# c.company._meta.get_field('phone_plan').verbose_name
c.company.phone_plan._meta.get_field('name').verbose_name

If you are working with classes only, then its a but more complex:

field_company = Client._meta.get_field('company')
field_phone_plan = field_company.rel.to._meta.get_field('phone_plan')
field_name = field_phone_plan.rel.to._meta.get_field('name')
field_name.verbose_name

# or one long line
s = Client._meta.get_field('company') \
    .rel.to._meta.get_field('phone_plan') \
    .rel.to._meta.get_field('name') \
    .verbose_name

EDIT: User @AndreyBerenda points out that the second option does not work for him in Django 2.1; I tested only in 1.11, which was specified in the question.

Related