ForeignKey/OneToOneField should support user-defined id attribute name
|Reported by:||dstora||Owned by:||nobody|
|Component:||Database layer (models, ORM)||Version:|
|Severity:||Normal||Keywords:||foreign key ForeignKey OneToOneField id|
|Has patch:||yes||Needs documentation:||yes|
|Needs tests:||yes||Patch needs improvement:||no|
Currently, when defining a ForeignKey/OneToOneField field XXX, Django automatically defines a XXX_id attribute to store the id value.
However, it is sometimes desirable to decide of the name as XXX_id may not be the most appropriate.
Let's take the following example:
- I initially have a table "Payment" with a "ccy_code" field designating a 3-letter currency code.
My model definition looks like:
from django.db import model class Payment(models.Model): # ... ccy_code = models.CharField(max_length=3)
In my python code I refer to the currency code as follows:
p = Payment() #... print p.ccy_code
- Later, I decide to actually create a "Currency" table to hold some information about currencies.
And I also decide to define a foreign key constraint from "Payment" to "Currency".
My model now looks like:
from django.db import model class Currency(models.Model): ccy_code = models.CharField(max_length=3, primary_key=True) #... class Payment(models.Model): # ... ccy = models.ForeignKey(Currency, to_field="ccy_code", db_column="ccy_code")
The problem here is that my existing Python code is broken, because "ccy_code" is not defined anymore for "Payment".
Django has instead define a "ccy_id" attribute.
Based on the principle that defining things like foreign keys should only add functionality (and not remove any) it seems quite important to me that one can choose the id attribute name.
This can be achieved by adding an optional keyword argument to ForeignKey/OneToOneField constructors (here I decided to call it "id_attr_name").
The implementation of this feature is pretty small and fully backward-compatible.
Here is the SVN diff against trunk:
Index: related.py =================================================================== --- related.py (revision 10924) +++ related.py (working copy) @@ -660,6 +660,7 @@ class ForeignKey(RelatedField, Field): empty_strings_allowed = False def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs): + self.__id_attr_name = kwargs.pop('id_attr_name', None) try: to_name = to._meta.object_name.lower() except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT @@ -679,7 +680,10 @@ self.db_index = True def get_attname(self): - return '%s_id' % self.name + if self.__id_attr_name: + return self.__id_attr_name + else: + return '%s_id' % self.name def get_validator_unique_lookup_type(self): return '%s__%s__exact' % (self.name, self.rel.get_related_field().name)
In my example, I could then define my model like:
from django.db import model class Currency(models.Model): ccy_code = models.CharField(max_length=3, primary_key=True) #... class Payment(models.Model): # ... ccy = models.ForeignKey(Currency, to_field="ccy_code", db_column="ccy_code", id_attr_name="ccy_code")
Change History (9)
Changed 7 years ago by dstora
comment:1 Changed 7 years ago by Alex
- Needs documentation unset
- Needs tests unset
- Patch needs improvement unset
- Triage Stage changed from Unreviewed to Accepted
comment:2 Changed 7 years ago by SmileyChris
- Needs documentation set
- Needs tests set
- Triage Stage changed from Accepted to Design decision needed
- Version 1.0 deleted
comment:3 Changed 7 years ago by SmileyChris
- Triage Stage changed from Design decision needed to Accepted