1👍
I was puzzled by Django’s behavior in the scenario you posited, so I put in a bunch of print
statements down in Django’s models\base.py file to figure out what was going on. It turns out that, in the inheritance example you set up, ModelC
effectively has more than one primary key. There’s even a comment down in the code itself that references this subtle behavior (see related Django ticket 17615).
The crux of the matter is that non-abstract model inheritance works through OneToOneField
relationships, which are a special variant of ForeignKey
. So ModelC
in your example ends up with two OneToOneField
relationships: one concrete key (modelb_ptr_id
) and one inherited key via ModelB (modela_ptr_id
). The id
attribute may also be considered a primary key, but I think it’s conceptually just an alias for modela_ptr_id
under the hood.
When you set the ModelC
instance pk value to 0, you are effectively clearing the "first" (closest binding) primary key. But there’s another primary key in play! The Django save()
routine calls an internal _save_parents() method which does a sync between the keys if they don’t match. It does this by recursively walking up the parent relationships, syncing items as it goes. This routine is where the ModelC
instance primary key gets "reset" to 1 in your initial example. The routine walks up ModelC
to ModelB
, sees that it has a parent (and that the parent value does not match the current value), syncs the two, and continues.
If I understand the code correctly, this routine is in place to ensure that all parents have been written to the database as necessary, so that there is no data loss as items get saved. This is subtle behavior, but something worth paying attention to in inheritance usage.
This was a very interesting question!
0👍
c=ModelC(info1="aaa",info2="bbb",info3="ccc")
# creates an instance
c.save()
# writes instance to db
c.pk=None
# I doubt u can nullify the auto-generated pk of an existing object, because a pk is not nullable
c.save()
# if I'm right nothing will happen here.
So c will always be the same object. If you want to clone it you need to generate a new object. Either with a constructor within ModelC:
def __init__(another_modelC_obj=null, self):
if another_modelC_obj:
# for every field in another_modelC_obj: do self.field = another_modelC_obj.field
super().__init__()
so you can go
c2=ModelC(c)
Or call it directly with:
c2=ModelC(c.info1, c.info2, c.info3)
Then c2 and c will be identical despite of their pk
- How to specify uniqueness for a tuple of field in a Django model
- AttributeError: 'RelatedManager' object has no attribute 'remove'
- 'WSGIRequest' object has no attribute 'session' while upgrading from django 1.3 to 1.9
- Django override the form HTML label template?
0👍
- You need to
save()
it again immediately after settingc.pk = None
.
Why?
Because the ModelC.id
field is an auto-generated and auto-incremented field.
When you call save()
, Django treats the instance as new, and the database will assign a unique primary key value.
# old id / pk
print(c.id)
# set it null
c.pk = None
# ask Django to save it again
c.save()
#get new id
print(c.id)
-
Be mindful of other many-to-many or foreign keys in your model, as these relationships will also be copied.
-
If this is a common requirement for your models, consider creating an abstract class for this purpose.
from django.db import models
class CloneableModel(models.Model):
class Meta:
abstract = True
def clone(self):
new_instance = self.__class__().get(id=self.id)
new_instance.id = None
new_instance.save()
return new_instance
class ModelC(CloneableModel):
...
- How do I restrict access to admin pages in Django?
- PyCharm does not resolve templates nor template tags nor statics in Django project
- Is there a way to generate pdf containing non-ascii symbols with pisa from django template?
- Why does Django South require a default value when removing a field?
- Django south fresh install –> error: unknown command 'schemamigration'