- January 3, 2011
- contenttypes genericforeignkey
- 0 (after 0 ratings)
I don't know if you noticed but GenericForeignKey behaves badly in some situations. Particularly if you assign a not yet saved object (without pk) to this field then you save this object the fk_field does not get updated (even upon saving the model)- it's updated only upon assigning object to the field. So you have to save the related object prior to even assigning it to this field. It's get even more apparent when you have null=True on the fk/ct_fields, because then the null constrains won't stop you from saving an invalid object. By invalid I mean an object (based on a model with GFK field) which has ct_field set but fk_field=None.
Maybe this problem is irrelevant in most use case scenarios but this behaviour certainly isn't logical and can introduce silent bugs to your code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
""" Contact: Filip Sobalski <[email protected]> """ from django.contrib.contenttypes import generic from django.db.models import signals class ImprovedGenericForeignKey(generic.GenericForeignKey): """ Corrects the behaviour of GenericForeignKey so even if you firstly assign an object to this field and then save this object - its PK still gets saved in the fk_field. If you assign a not yet saved object to this field an exception is thrown upon saving the model. """ class IncompleteData(Exception): message = 'Object assigned to field "%s" doesn\'t have a PK (save it first)!' def __init__(self, field_name): self.field_name = field_name def __str__(self): return self.message % self.field_name def contribute_to_class(self, cls, name): signals.pre_save.connect(self.instance_pre_save, sender=cls, weak=False) super(ImprovedGenericForeignKey, self).contribute_to_class(cls, name) def instance_pre_save(self, sender, instance, **kwargs): """ Ensures that if GenericForeignKey has an object assigned that the fk_field stores the object's PK. """ """ If we already have pk set don't do anything... """ if getattr(instance, self.fk_field) is not None: return value = getattr(instance, self.name) """ If no objects is assigned then we leave it as it is. If null constraints are present they should take care of this, if not, well, it's not my fault;) """ if value is not None: fk = value._get_pk_val() if fk is None: raise self.IncompleteData(self.name) setattr(instance, self.fk_field, fk)