web-dev-qa-db-fra.com

Champ modèle Django basé sur l'autre produit

J'ai un modèle que je voudrais contenir un nom de sujets et leurs initiales. (Les données sont quelque peu anonymisées et suivies par des initiales.) 

En ce moment, j'ai écrit 

class Subject(models.Model):

    name = models.CharField("Name", max_length=30)
    def subject_initials(self):
        return ''.join(map(lambda x: '' if len(x)==0 else x[0],
                           self.name.split(' ')))
    # Next line is what I want to do (or something equivalent), but doesn't work with
    # NameError: name 'self' is not defined
    subject_init = models.CharField("Subject Initials", max_length=5, default=self.subject_initials)

Comme indiqué à la dernière ligne, je préférerais pouvoir stocker les initiales dans la base de données sous forme de champ (indépendant du nom), mais cette dernière est initialisée avec une valeur par défaut basée sur le champ du nom. Cependant, j'ai des problèmes car les modèles Django ne semblent pas avoir un «moi». 

Si je modifie la ligne en subject_init = models.CharField("Subject initials", max_length=2, default=subject_initials), je peux effectuer la synchronisation, mais je ne peux pas créer de nouveaux sujets.

Est-ce possible dans Django, ayant une fonction callable donnant une valeur par défaut à un champ en fonction de la valeur d'un autre champ?

(Pour les curieux, si je veux séparer les initiales de mon magasin séparément, c’est dans de rares cas où les noms étranges peuvent avoir des noms différents de ceux que je suis en train de suivre. Par exemple, une autre personne a décidé que les initiales du sujet 1, "John O'Mallory" "JM" plutôt que "JO" et souhaite le modifier en tant qu'administrateur.)

70
dr jimbob

Les modèles ont certainement un "moi"! Vous essayez simplement de définir un attribut d'une classe de modèle comme dépendant d'une instance de modèle; ce n'est pas possible, car l'instance n'existe pas (et ne peut pas) exister avant que vous définissiez la classe et ses attributs.

Pour obtenir l'effet souhaité, substituez la méthode save () de la classe de modèle. Apportez les modifications souhaitées à l'instance, puis appelez la méthode de la superclasse pour effectuer la sauvegarde. Voici un exemple rapide.

def save(self, *args, **kwargs):
    if not self.subject_init:
        self.subject_init = self.subject_initials()
    super(Subject, self).save(*args, **kwargs)

Ceci est couvert dans Méthode de modèle prioritaire dans la documentation.

70
Elf Sternberg

Je ne sais pas s'il existe un meilleur moyen de le faire, mais vous pouvez utiliser un gestionnaire de signal pour le signal pre_save :

from Django.db.models.signals import pre_save

def default_subject(sender, instance, using):
    if not instance.subject_init:
        instance.subject_init = instance.subject_initials()

pre_save.connect(default_subject, sender=Subject)
14
Gabi Purcaru

En utilisant les signaux Django , cela peut être fait assez tôt, en recevant le signal post_init du modèle.

from Django.db import models
import Django.dispatch

class LoremIpsum(models.Model):
    name = models.CharField(
        "Name",
        max_length=30,
    )
    subject_initials = models.CharField(
        "Subject Initials",
        max_length=5,
    )

@Django.dispatch.receiver(models.signals.post_init, sender=LoremIpsum)
def set_default_loremipsum_initials(sender, instance, *args, **kwargs):
    """
    Set the default value for `subject_initials` on the `instance`.

    :param sender: The `LoremIpsum` class that sent the signal.
    :param instance: The `LoremIpsum` instance that is being
        initialised.
    :return: None.
    """
    if not instance.subject_initials:
        instance.subject_initials = "".join(map(
                (lambda x: x[0] if x else ""),
                instance.name.split(" ")))

Le signal post_init est envoyé par la classe une fois qu'elle a initialisé l'instance. De cette façon, l'instance obtient une valeur pour name avant de vérifier si ses champs non nullables sont définis.

4
bignose

En guise d’autre implémentation de la réponse de Gabi Purcaru , vous pouvez également vous connecter au signal pre_save à l’aide du décorateur receiver :

from Django.db.models.signals import pre_save
from Django.dispatch import receiver


@receiver(pre_save, sender=Subject)
def default_subject(sender, instance, **kwargs):
    if not instance.subject_init:
        instance.subject_init = instance.subject_initials()

Cette fonction de récepteur utilise également les arguments de mot clé générique **kwargs que tous les gestionnaires de signaux doivent utiliser conformément à https://docs.djangoproject.com/en/2.0/topics/signals/#receiver-functions

1
Kurt Peek