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)
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 3 weeks ago
- Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 6 months ago
Comments
Please login first before commenting.