Null constraint violations when saving objects with ForeignKey relationships

1,374 views
Skip to first unread message

gmayer

unread,
Dec 26, 2009, 1:12:21 PM12/26/09
to Django users
Hi guys,

I'm new to Django but otherwise quite a seasoned python and sql
programmer. I'm baffled by django's foreign key id behaviour and as a
result stuck in my current coding project. Let me start immediately
with an example of what I'm trying to do. Assume two very simple
models:

class A(models.Model):
pass
class B(models.Model):
a = models.ForeignKey(A)

Now I need to create an instance of A and B without saving either of
them (I'm parsing a whack load of data and only want to commit objects
to the db once successfully parsed):

from test.models import A,B
instA = A()
instB = B(a=instA)

Now I'm done with my parsing and am happy to save them to my database:

instA.save() # all good
instB.save() # fails with IntegrityError: null value in column "a_id"
violates not-null constraint

On closer inspection the second line above fails because instB.a_id is
None, even though instB.a.id is defined. Why does django throw in a
duplicate, now stale instB.a_id field which, to make matters worse, is
used in generating the underlying SQL for instB.save() to result in
failure????

Perhaps I'm just to n00b to use the correct semantics which avoid the
above issues... excuse me if that's the case.

Gunther

Gunther Mayer

unread,
Dec 27, 2009, 2:52:23 PM12/27/09
to django...@googlegroups.com
Ok, not sure why I'm not getting replies, perhaps my problem is too
complex or I'm being too silly with my approach. Either way, I found a
work around by writing a little function to prepare my objects for
saving which ensures that django finds the fkey id's when I issue my save():

def prepare_object(obj): # helper method to work around django
restrictions of NULL foreign keys before saving
for key in obj.__dict__:
if key[0] == '_' or key[-3:] != '_id': # skip private
and ordinary fields
continue
fkey_name = key[:-3]
try:
exec 'obj.%s = obj.%s.id' % (key, fkey_name)
except:
pass # there are some normal attributes that end
in _id but have got nothing to do with fkeys (hence don't have such
attributes)

Perhaps it will spare another lone programmer a hair or two. If nobody
has any further comments on this I think I'll just file a bug on
django's trac, this issue really *shouldn't* be present.

Gunther.

Łukasz Balcerzak

unread,
Dec 27, 2009, 3:50:05 PM12/27/09
to django...@googlegroups.com
Hi there,

Well, just try to make sure you are passing already saved instance (having primary key set)
into related model which has foreign key to the first one.

Your approach:

> from test.models import A,B
> instA = A()
> instB = B(a=instA)
>
> Now I'm done with my parsing and am happy to save them to my database:
>
> instA.save() # all good
> instB.save() # fails with IntegrityError: null value in column "a_id"
> violates not-null constraint

Could easily be fixed:

> from test.models import A,B
> instA = A()

> #instB = B(a=instA) # We will do that later


>
> Now I'm done with my parsing and am happy to save them to my database:
>
> instA.save() # all good

> instB = B(a=instA) # Now is the time
> instB.save() # shouldn't raise any exception

This is common problem if you are trying to get use to ORM. I had many problems with
understanding Hibernate/JPA a while ago when coding in Java.

IntegrityError was risen because instA was cached at instB (you may check this by comparing instB.a.id and instB.__dict__), and changes in instA
haven't been pushed. It's common approach for Object-Relation Mappers.

It's probably tempting to prepare your objects first but preparing your data before
is even better. If there would be not too much confusion you can try to use dicts as data
storage first, then just pass it to the constructors of your models - just a note, I do
this quite frequently in some cases.

But if you are not convinced and you really have to prepare objects first you may simply
set instA to instB just before calling ``save`` method at instB. I would be:

> from test.models import A,B
> instA = A()

> instB = B() # prepare but don't set instA yet


>
> Now I'm done with my parsing and am happy to save them to my database:
>
> instA.save() # all good

> instB.a = instA # now we set instA, *after* it has proper pk already set
> instB.save() # shouldn't raise any exception


Hope it helps

> --
>
> You received this message because you are subscribed to the Google Groups "Django users" group.
> To post to this group, send email to django...@googlegroups.com.
> To unsubscribe from this group, send email to django-users...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/django-users?hl=en.
>
>

kvl...@gmail.com

unread,
Jun 14, 2013, 6:56:05 PM6/14/13
to django...@googlegroups.com, lukaszb...@gmail.com
Hi Lukasz..

Thanks it was helpful..i was looking for something like this.

-Karan
Reply all
Reply to author
Forward
0 new messages