2

My question is closely related to this question, yet subtly different.

Given the following Django models:

class Country(models.Model):
    code = models.CharField(primary_key=True)


class Person(models.Model):
    country = models.ForeignKey(Country, on_delete=models.CASCADE)

Note that I have specified a custom primary key field on Country called code. The Country class should have no id field or attribute. This is working fine.

And of course I want the Person class's foreign key to Country to use the code field. That is also working fine. So far so good.

However, I also really want the underlying column name in the Person table to be exactly country_code. NOT country_id. So I added the db_column field argument:

class Person(models.Model):
    country = models.ForeignKey(
        Country, 
        on_delete=models.CASCADE,
        db_column='country_code',
    )

Now the column name is correctly named country_code instead of country_id. So far so good.


Now. In my python code, I want to be able to access a country via a person in the normal way:

person = Person.objects.get(...)
country = person.country

BUT, I want to be able to access the country code pk via a person using country_code, not country_id, like so:

person = Person.objects.get(...)
country_code = person.country_code

And I also want to be able to create Person objects with the country_code argument, not country_id:

person = Person(country_code='foo')

And I also want to be able to query for Person objects with the country_code argument, not country_id:

people = Person.objects.filter(country_code='foo')

There should be no person.country_id attribute, field, or column. Only person.country and person.country_code. And yes, I want that field/attribute/column to be called exactly person.country_code, NOT person.country_id even if it holds the same value. That's the exact name I want in both Python and in the RDBMS.


So far, I have not been able to achieve this. I have tried tinkering with adding the to_field argument to the Person.country field declaration like so:

class Person(models.Model):
    country = models.ForeignKey(
        Country, 
        on_delete=models.CASCADE,
        to_field='code',
        db_column='country_code',
    )

This does not seem have the desired effect.

Is it possible to change a model's foreign key field attribute name (as well as the underlying column name) in Django?

This doesn't seem to me like like such a strange thing to do. Maybe I'm wrong. Is this possible? Is this just too hard/non-standard to be worth it? Am I asking for trouble?

Todd Ditchendorf
  • 11,217
  • 14
  • 69
  • 123

1 Answers1

3

customize a foreignkey subclass

class NamedForeignKey(models.ForeignKey):
    suffix_idname = 'id'
    def __init__(self, *args, **kwargs):
        suffix_idname = kwargs.pop('suffix_idname', None)
        if suffix_idname:
            self.suffix_idname = suffix_idname
        super().__init__(*args, **kwargs)

    def get_attname(self):
        return '%s_%s' % (self.name, self.suffix_idname)

class Person(models.Model):
    country = NamedForeignKey(
        Country, 
        on_delete=models.CASCADE,
        suffix_idname='code',
    )
Blackdoor
  • 922
  • 1
  • 6
  • 12