简体   繁体   中英

django model inheritance: How does model creation work with custom manager?

Recently I've faced an issue with Django, model inheritance and how the creation of model instances works. Suppose I have the following (basic) setup:


class InviteBaseManager(models.Manager):
    def create(self):
        new_code = # create some kind of unique code, not really relevant here.
        return super().create(code=new_code)

class InviteBase(models.Model):
   code = models.CharField(max_length=10, blank=False, null=False, unique=True)
   creationDate = models.DateTimeField(default=timezone.now())

   objects = InviteBaseManager()

class PartyInviteManager(models.Manager):
    def create(self, name):
        # method 1
        newInvite = InviteBase.objects.create()
        print(newInvite.code) # is definitly set, lets assume "ABCD" 
        # as expexted, the "InviteBase" table has one row with code "ABCD" and
        # default creationDate
        newPartyInvite = super().create(partyName=name, invite=newInvite)
        print(newPartyInvite.invite.code) # is EMPTY, prints nothing
        # In fact, when looking at the db, there is still only *one* row in the table "InviteBase",
        # with an *empty* code field and a default creationDate field.
        return newPartyInvite


        #method 2
        newPartyInvite = super().create(partyName=name)
        # creates the InviteBase instance implicitly, again, newPartyInvite.invite.code is empty.
        # fill newPartyInvite.invity.code manually.
        return newPartyInvite

class PartyInvite(InviteBase):
   #Isn't blank=False and null=False unnecessary? A child should probably not exist with no parent?
   invite = models.OneToOneField(InviteBase, parent_link=True, on_delete=models.CASCADE, null=False, blank=False)
   partyName = models.CharField(...)

   objects = PartyInviteManager()

So the question is: How can I pass an already existing instance of the base class inside the create method of my PartyInviteManager ? Even when using method 1, the existing instance that I pass along seems to be overwritten. A new one is created with the default value. Interestingly enough, this violates the constraints that code cannot be blank or NULL .

This behaviour seems a bit odd to me? Can someone point out what I am missing here?

To clarify: I know that I should usually use **kwargs in the create methods and that inheritance might not be the ideal use case here, but I'm just very curious about this behaviour. I know that this kind of model inheritance wont even create a pk for the child model (because holding a OneToOneField to the parent class effectively acts as a pk anyway), but why would it be impossible to pass a manually created instance as parent? Am I not allowed to use inheritance for my use-case?

I think I've found the correct way of doing this:

class InviteBaseManager(models.Manager):
    def create(self, **kwargs):
        kwargs['code'] = #createCode
        return super().create(**kwargs)

class InviteBase(models.Model):
   code = models.CharField(max_length=10, blank=False, null=False, unique=True)
   creationDate = models.DateTimeField(default=timezone.now())

   objects = InviteBaseManager()

class PartyInviteManager(InviteBaseManager):
    def create(self, name):
        return = super().create(partyName=name)

class PartyInvite(InviteBase):
   invite = models.OneToOneField(InviteBase, parent_link=True, on_delete=models.CASCADE, null=False, blank=False)
   partyName = models.CharField(...)

   objects = PartyInviteManager()

The PartyInviteManager now inherits from the InviteManager as well. When calling super.create() from the child manager, the base manager gets called, appends the code field and everything works as expected. I also found out, that if the PartyInviteManager does not inherit from the InviteBaseManager , the create method of InviteBaseManager does not get called when creating a new PartyInvite . This seems very odd to me.

Of course, the easier way would have been to create the code as default value via a function, like that:

def createCode():
   return "ABCD" # add fancy code creation magic here.

class InviteBase:
    code = models.CharField(max_length=128, blank=False, null=False, default=createCode)

But if, depending on the child class, the code needs additional information (such as invited members or whatever), this approach would not work anymore.

On a side note, interestingly enough the following code snippet:

invite = PartyInviteManager.objects.create(partyName='Birthday')
print(invite.invite.code)
print(invite.code)

produces the following output:

ABCD
ABCD

In the create method of the PartyInviteManager , one can directly use code=XXXX to pass along a string for the InviteBase model, invite_code on the other hand does not work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM