Login

Fix for the bad behaviour of GenericForeignKey field

Author:
pinkeen
Posted:
January 3, 2011
Language:
Python
Version:
1.2
Tags:
genericforeignkey contenttypes
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 <pinkeen@gmail.com> """

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. Improved Pickled Object Field by taavi223 5 years, 9 months ago
  2. Automatically slugify slug fields in your models by Aliquip 8 years, 2 months ago
  3. "Approved" field with timestamp by miracle2k 7 years, 10 months ago
  4. create_object and update_object for newforms by danjak 8 years, 2 months ago
  5. Binding signals to abstract models by andreterra 3 years ago

Comments

Please login first before commenting.