web-dev-qa-db-fra.com

Django peut-il créer automatiquement un modèle un à un associé?

J'ai deux modèles dans différentes applications: modelA et modelB. Ils ont une relation personnelle. Django peut-il créer et enregistrer automatiquement ModelB lorsque ModelA est enregistré?

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)

Lorsque je sauvegarde un nouveau ModelA, je souhaite qu’une entrée soit automatiquement enregistrée dans ModelB. Comment puis-je faire ceci? Existe-t-il un moyen de spécifier cela dans ModelA? Ou n'est-ce pas possible, et il me suffirait de créer et de sauvegarder ModelB dans la vue?

Edité pour dire que les modèles sont dans différentes applications.

42
vagabond

Jetez un coup d'œil à AutoOneToOneField dans Django-ennoying . De la docs:

from annoying.fields import AutoOneToOneField

class MyProfile(models.Model):
    user = AutoOneToOneField(User, primary_key=True)
    home_page = models.URLField(max_length=255)
    icq = models.CharField(max_length=255)

(Django-ennoying est une super petite bibliothèque qui inclut des gemmes comme les fonctions render_to decorator et get_object_or_None et get_config)

40
John Paulett

Comme m000 l'a souligné, vos modèles existent dans différentes applications. Vous utilisez souvent des applications que vous n'avez pas écrites. Pour permettre les mises à jour, vous avez besoin d'un moyen découplé de création de modèles liés de manière logique. C'est la solution privilégiée à mon avis et nous l'utilisons dans un très grand projet.

En utilisant des signaux:

Dans votre models.py:

from Django.db.models import signals


def create_model_b(sender, instance, created, **kwargs):
    """Create ModelB for every new ModelA."""
    if created:
        ModelB.objects.create(thing=instance)

signals.post_save.connect(create_model_b, sender=ModelA, weak=False,
                          dispatch_uid='models.create_model_b')

Vous pouvez créer une application distincte pour contenir ce fichier models.py si les deux applications sont disponibles dans le commerce.

26
Dmitry

Le moyen le plus simple consiste à remplacer la méthode de sauvegarde de ModelA:

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

    def save(self, force_insert=False, force_update=False):
        is_new = self.id is None
        super(ModelA, self).save(force_insert, force_update)
        if is_new:
            ModelB.objects.create(thing=self)
14
Jarret Hardie

Je sais qu'il est un peu tard, mais j'ai proposé une solution plus propre et plus élégante. Considérez ce code:

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

    @classmethod
    def get_new(cls):
        return cls.objects.create().id



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

Bien sûr, vous pouvez aussi utiliser lambda, à condition de renvoyer l'identifiant entier de l'objet associé :)

2
realmaniek

Vous pouvez utiliser le post_save-hook qui est déclenché après la sauvegarde d'un enregistrement. Pour plus de documentation sur les signaux Django, voir ici . Sur cette page , vous trouverez un exemple sur la manière d’appliquer le crochet à votre modèle.

0
schneck

Créez simplement une fonction qui crée et retourne un ModelA vide et définissez l'argument nommé par défaut sur "thing" à cette fonction. 

0
user287544

Je pense que vous voulez utiliser l'héritage modèle de Django . Cela est utile si la déclaration suivante est vraie: ModelA est un ModelB (comme, le restaurant est un lieu).

Vous pouvez définir:

class ModelB(models.Model):
    field1 = models.CharField(...)

class ModelA(ModelB): field2 = models.CharField(...) </ pre>

Vous pouvez maintenant créer une instance de ModelA et définir field2 et field1. Si ce modèle est enregistré, il créera également une instance de ModelB qui obtiendra la valeur de field1 attribuée. Tout cela se fait de manière transparente en coulisse.

Bien que vous puissiez faire ce qui suit:

a1 = ModelA()
a1.field1 = "foo"
a1.field2 = "bar"
a1.save()
a2 = ModelA.objects.get(id=a1.id)
a2.field1 == "foo" # is True
a2.field2 == "bar" # is True
b1 = ModelB.objects.get(id=a1.id)
b1.field1 == "foo" # is True
# b1.field2 is not defined

0
Gregor Müllegger

J'ai rassemblé quelques réponses différentes (car aucune d'entre elles ne fonctionnait tout à fait à part pour moi) et je l'ai trouvée. Je pensais que c'était assez propre, alors je le partage.

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)

Il utilise Signal comme suggéré par @Dmitry. Et comme @ daniel-roseman a commenté dans la réponse de @ jarret-hardie, Django Admin essaie parfois de créer l'objet associé pour vous (si vous modifiez la valeur par défaut dans le formulaire en ligne), ce que j'ai rencontré, donc la vérification hasattr. Le conseil du décorateur Nice provient de la réponse de @ shadfc dans Créez une instance OneToOne lors de la création d'un modèle

0
jtlai