Can Django automatically create a related one-to-one model?

Viewed 31335

I have two models in different apps: ModelA and ModelB. They have a one-to-one relationship. Is there a way django can automatically create and save ModelB when ModelA is saved?

class ModelA(models.Model):
    name = models.CharField(max_length=30)

class ModelB(models.Model):
    thing = models.OneToOneField(ModelA, primary_key=True)
    num_widgets = IntegerField(default=0)

When I save a new ModelA I want a entry for it to be saved automatically in ModelB. How can I do this? Is there a way to specify that in ModelA? Or is this not possible, and I would just need to create and save ModelB in the view?

Edited to say the models are in different apps.

9 Answers

I assembled a few different answers (because none of them worked straight out of the box for me) and came up with this. Thought it's pretty clean so I'm sharing it.

from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=ModelA)
def create_modelb(sender, instance, created, **kwargs):
    if created:
        if not hasattr(instance, 'modelb'):
            ModelB.objects.create(thing=instance)

It's using Signal as @Dmitry suggested. And as @daniel-roseman commented in @jarret-hardie's answer, Django Admin does try to create the related object for you sometimes (if you change the default value in the inline form), which I ran into, thus the hasattr check. The nice decorator tip is from @shadfc's answer in Create OneToOne instance on model creation

Just create a function that creates and returns an empty ModelA, and set the default named argument on "thing" to that function.

If you are using InlineForm in admin panel, than you can do like this.

But of course in other cases need to check too(like in DRF or manual model instance creation)

from django.contrib import admin
from django.forms.models import BaseInlineFormSet, ModelForm

class AddIfAddParentModelForm(ModelForm):
    def has_changed(self):
        has_changed = super().has_changed()

        if not self.instance.id:
            has_changed = True

        return has_changed

class CheckerInline(admin.StackedInline):
    """ Base class for checker inlines """
    extra = 0
    form = AddIfAddParentModelForm
Related