111 votes

Définition de la valeur par défaut d'un attribut de clé étrangère

Quelle est la meilleure façon de définir une valeur par défaut pour un champ de clé étrangère dans un modèle ? Supposons que j'aie deux modèles, Student y Exam avec l'étudiant ayant exam_taken comme clé étrangère. Comment puis-je idéalement définir une valeur par défaut pour cette clé ? Voici un journal de mes efforts

class Student(models.Model):
   ....
   .....
   exam_taken = models.ForeignKey("Exam", default=1)

Cela fonctionne, mais j'ai l'impression qu'il y a une meilleure façon de procéder.

def get_exam():
    return Exam.objects.get(id=1)

class Student(models.Model):
    ....
    .....
    exam_taken = models.ForeignKey("Exam", default=get_exam)

Mais cette opération se solde par une erreur de tables inexistantes lors de la synchronisation.

Toute aide serait appréciée.

49voto

Gareth Lloyd Points 754

Dans vos deux exemples, vous codifiez en dur l'identifiant de l'instance par défaut. Si c'est inévitable, je mettrais simplement une constante.

DEFAULT_EXAM_ID = 1
class Student(models.Model):
    ...
    exam_taken = models.ForeignKey("Exam", default=DEFAULT_EXAM_ID)

Moins de code, et le fait de nommer les constantes rend le tout plus lisible.

31voto

Dennis Points 600

Comme l'a déjà laissé entendre le commentaire de @gareth répondre en codant en dur une valeur par défaut id n'est pas toujours la meilleure idée :

Si le id n'existe pas dans la base de données, vous avez un problème. Même si cette valeur id existe, l'objet correspondant peut changer. Dans tous les cas, lors de l'utilisation d'une valeur id vous devrez recourir à des solutions telles que la migration de données ou l'édition manuelle du contenu de la base de données existante.

Pour éviter cela, vous pouvez utiliser get_ou_create() en combinaison avec un unique (autre que id ).

Voici une façon de procéder :

from django.db import models

class Exam(models.Model):
    title = models.CharField(max_length=255, unique=True)
    description = models.CharField(max_length=255)

    @classmethod
    def get_default_pk(cls):
        exam, created = cls.objects.get_or_create(
            title='default exam', 
            defaults=dict(description='this is not an exam'),
        )
        return exam.pk

class Student(models.Model):
    exam_taken = models.ForeignKey(
        to=Exam, on_delete=models.CASCADE, default=Exam.get_default_pk
    )

Ici, un Exam.title est utilisé pour obtenir un objet unique, et un champ Exam.description illustre comment nous pouvons utiliser le champ defaults (pour les get_or_create ) pour spécifier complètement la valeur par défaut de la Exam objeto.

Notez que nous renvoyons un pk comme le suggère la documents :

Pour les champs tels que ForeignKey qui renvoient à des instances de modèle, les valeurs par défaut doivent être celles du champ auquel elles font référence ( pk sauf si to_field est défini) au lieu des instances de modèle.

Il convient également de noter que default sont évalués en Model.__init__() ( source ). Ainsi, si votre valeur par défaut dépend d'un autre champ du même modèle, ou du contexte de la requête, ou de l'état du formulaire côté client, vous devriez probablement rechercher ailleurs .

25voto

Carson McNeil Points 153

Je modifierais légèrement la réponse de @vault ci-dessus (il s'agit peut-être d'une nouvelle fonctionnalité). Il est tout à fait souhaitable de désigner le champ par un nom naturel. Cependant, au lieu de surcharger la fonction Manager Je me contenterais d'utiliser la fonction to_field paramètre de ForeignKey :

class Country(models.Model):
    sigla   = models.CharField(max_length=5, unique=True)

    def __unicode__(self):
        return u'%s' % self.sigla

class City(models.Model):
    nome   = models.CharField(max_length=64, unique=True)
    nation = models.ForeignKey(Country, to_field='sigla', default='IT')

14voto

vault Points 719

J'utilise des touches naturelles pour adopter une approche plus naturelle :

<app>/models.py

from django.db import models

class CountryManager(models.Manager):
    """Enable fixtures using self.sigla instead of `id`"""

    def get_by_natural_key(self, sigla):
        return self.get(sigla=sigla)

class Country(models.Model):
    objects = CountryManager()
    sigla   = models.CharField(max_length=5, unique=True)

    def __unicode__(self):
        return u'%s' % self.sigla

class City(models.Model):
    nome   = models.CharField(max_length=64, unique=True)
    nation = models.ForeignKey(Country, default='IT')

11voto

Tanckom Points 455

Le problème de la plupart de ces approches est qu'elles utilisent CODAGE DUR ou des méthodes lambda à l'intérieur du modèle, qui ne sont plus prises en charge depuis la version 1.7 de Django.

À mon avis, la meilleure approche consiste à utiliser un sentinelle qui peut également être utilisée pour la méthode on_delete argument.

Dans votre cas, je ferais donc

# Create or retrieve a placeholder
def get_sentinel_exam():
    return Exam.objects.get_or_create(name="deleted",grade="N/A")[0]

# Create an additional method to return only the id - default expects an id and not a Model object
def get_sentinel_exam_id():
    return get_sentinel_exam().id

class Exam(models.Model):
    ....
    # Making some madeup values
    name=models.CharField(max_length=200) # "English", "Chemistry",...
    year=models.CharField(max_length=200) # "2012", "2022",...

class Student(models.Model):
    ....
    .....
    exam_taken = models.ForeignKey("Exam",    
                       on_delete=models.SET(get_sentinel_exam),
                       default=get_sentinel_exam_id
                 )

Maintenant, lorsque vous venez d'ajouter le exam_taken utilise une valeur existante garantie et, lors de la suppression de l'examen, l'étudiant lui-même n'est pas supprimé et possède une clé étrangère vers un champ deleted valeur.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X