I have a model with a generic relation (call it A), when creating an instance of this object I pass an instance of another model (call it B) as the initializer of the content_object field (via kwargs of the constructor).
If I don't save B before creating A then when saving A the content_object_id is saved to the db as NULL. If I save B before passing it to the constructor of A then everything's allright.
It's not logical. I assumed that the ID of the related object (B) is fetched when doing A.save() and it should throw some kind of an exception if B isn't saved yet but it just fails silently. I don't like the current solution (saving B beforhand) because we don't know yet if I will be always willing to keep the object, not just scrap it, and there are performance considerations - what if I will add some another data and save it once more shortly after.
class BaseNodeData(models.Model):
...
extnodedata_content_type = models.ForeignKey(ContentType, null=True)
extnodedata_object_id = models.PositiveIntegerField(null=True)
extnodedata = generic.GenericForeignKey(ct_field='extnodedata_content_type', fk_field='extnodedata_object_id')
class MarkupNodeData(models.Model):
raw_content = models.TextField()
Suppose we do:
markup = MarkupNodeData(raw_content='...')
base = BaseNodeData(..., extnodedata=markup)
markup.save()
base.save()
# both records are inserted to the DB but base is stored with extnodedata_object_id=NULL
markup = MarkupNodeData(raw_content='...')
base = BaseNodeData(..., extnodedata=markup)
base.save()
markup.save()
# no exception is thrown and everything is the same as above
markup = MarkupNodeData(raw_content='...')
markup.save()
base = BaseNodeData(..., extnodedata=markup)
base.save()
# this works as expected
Of course I can do it this way, but it doesn't change anything:
base = BaseNodeData(...)
base.extnodedata = markup
My question is - is this a bug in django which I should report or maybe I'm doing something wrong. Docs on GenericRelations aren't exactly verbose.
Thank you for your answers. I decided to take some more time to investigate the django sources and came up with a solution myself. I subclassed the GenericForeignKey. The code should be self-explanatory.
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 save it after its PK gets saved.
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)
I think that this should be considered a bug in django, so I will report it and see how it turns out.