Quickstart¶
After Installation, you can use django-composite-foreignkey
in your models.
the django-composite-foreignkey give you two fields : CompositeForeignKey and CompositeOneToOneField. each one has the same behavior: it don’t create a field on the database, but use a/some existings ones.
Example simple composite ForeignKey models¶
CompositeForeignKey¶
CompositeOneToOneField¶
these 2 models is linked by either a CompositeForeignKey or a CompositeOneToOneField on Contact.customer. there is no customer_id field, but it use instead the shared fields company and customer_id. CompositeForeignKey support a advanced mapping which allow the fields from both models to no beeing named identicaly.
in the prevous exemple, the folowing fields is linked :
Contact | Customer |
---|---|
company_code | company |
customer_code | company_code |
where a «normal» ForeignKey shoud be :
Contact | Customer |
---|---|
customer_id | pk |
Note
you can provide the to_fields attribute as a set instead of a dict if ALL fields is a simple linke to the related model and no special value is required.
to_fields={"company", "customer_id"}
is equivalent to
to_fields={"company": "company", "customer_id": "customer_id"}
Extra | Customer |
---|---|
company | |
customer_id |
Example advanced composite ForeignKey models¶
in this exemple, the Address Model can be used by either Supplier OR Customer. the linked fields is for Customer :
Customer | Address |
---|---|
company | company |
customer_id | customer_id |
RawFieldValue(“C”) | type_tiers |
The model Address have a field named «type_tiers» that allow to dinstinguish if the «tiers_id» is for a Supplier or a Customer. si the Customer model will always have an address with «S» in the «type_tiers» field. so be it via the RawFieldValue which tel exactly that : don’t search on the table, the value is always «C».
for convenience, a oposit version of RawFieldValue exists and mean «search on the table field X». it is LocalFieldValue(“X”).
so the class Supplier could be wrote:
We also can refer by CompositeForeignKey in more flexible way using FunctionBasedFieldValue instead of RawFieldValue:
from django.conf import global_settings
from django.utils import translation
from compositefk.fields import CompositeForeignKey
class Supplier(models.Model):
company = models.IntegerField()
supplier_id = models.IntegerField()
class SupplierTranslations(models.Model):
master = models.ForeignKey(
Supplier,
on_delete=CASCADE,
related_name='translations',
null=True,
)
language_code = models.CharField(max_length=255, choices=global_settings.LANGUAGES)
name = models.CharField(max_length=255)
title = models.CharField(max_length=255)
class Meta:
unique_together = ('language_code', 'master')
active_translations = CompositeForeignKey(
SupplierTranslations,
on_delete=DO_NOTHING,
to_fields={
'master_id': 'id',
'language_code': FunctionBasedFieldValue(translation.get_language)
})
active_translations.contribute_to_class(Supplier, 'active_translations')
in this example, the Supplier Model joins with SupplierTranslations in current active language and supplier_instance.active_translations.name will return different names depend on which language was activated by translation.activate(..):
translation.activate('en')
print Supplier.objects.get(id=1).active_translations.name
translation.activate('your_language_code')
print Supplier.objects.get(id=1).active_translations.name
- output should be:
- ‘en_language_name’
- ‘your_language_name’
Treate specific values as None¶
sometimes, some database is broken and some values should be treated as None to make sur no query will be made. ie if company code is «-1» instead of None, the query shall not seach for related model with company = -1 since this is an old aplicative exception.
you just have one thing to do that : null_if_equal
in this exemple, if company is -1, OR customer_id is -1 too, no query will be made and custome.address will be equal to None. it is the same behavior as if a normal foreignkey address had address_id = None.
Note
you must allow null value to permit that (which will not have any impact on database).
Note
these cases should not be possible on database that use ForeignKey constraint. but with some legacy database that won’t, this feathure is mandatory to bypass the headarch comming with broken logic on special values.
Set Specific attribute to None¶
Sometimes, all fields used in the composite relation is not only used for this one. in our Contact class, the company can be used in other fields. you can use the arguments nullable_fields to give the list of fields to set to null in case you wante to remove the link. since if one of the composite field is resolved to None, the field will return None.
so Contact.customer = None is equal to Contact.customer_code = None if nullable_fields=[“customer_code”]
nullable_fields can be a dict, which provide the value to put instead of None of each updated fields, which can synergize well with null_if_equal
Test application¶
The test application provides a number of useful examples.
https://github.com/onysos/django-composite-foreignkey/tree/master/testapp/