Login

Fix for the bad behaviour of GenericForeignKey field

Author:
pinkeen
Posted:
January 3, 2011
Language:
Python
Version:
1.2
Score:
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)

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 3 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 3 months, 2 weeks ago
  3. Serializer factory with Django Rest Framework by julio 10 months, 2 weeks ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 11 months ago
  5. Help text hyperlinks by sa2812 12 months ago

Comments

Please login first before commenting.