设置外键属性的默认值

Pri*_*han 57 django default foreign-keys

为模型中的外键字段设置默认值的最佳方法是什么?假设我有两个模型,学生和考试,学生将考试作为外键.我如何理想地为它设置默认值?这是我的努力记录

class Student(models.Model):
   ....
   .....
   exam_taken = models.ForeignKey("Exam", default=1)
Run Code Online (Sandbox Code Playgroud)

工作,但有一个预感有一个更好的方法.

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

class Student(models.Model):
    ....
    .....
    exam_taken = models.ForeignKey("Exam", default=get_exam)
Run Code Online (Sandbox Code Playgroud)

这里开始,但在同步时失败并且表不存在错误.

任何帮助,将不胜感激.

Gar*_*eth 35

在这两个示例中,您都是对默认实例的id进行硬编码.如果这是不可避免的,我只会设定一个常数.

DEFAULT_EXAM_ID = 1
class Student(models.Model):
    ...
    exam_taken = models.ForeignKey("Exam", default=DEFAULT_EXAM_ID)
Run Code Online (Sandbox Code Playgroud)

更少的代码,并命名常量使其更具可读性.


vau*_*ult 11

我使用自然键采用更自然的方法:

<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')
Run Code Online (Sandbox Code Playgroud)

  • 我在Django 1.6中尝试了这个,但是我得到了错误,"int(无效的文字为int(),基数为10:'IT'.(我的字符串不同,但你明白了.) (2认同)
  • 但这很好,而且看起来更像Pythonic:`default = lambda:Country.objects.filter(sigla ='IT').first()` (2认同)

Tan*_*kom 11

大多数这些方法的问题是它们在模型中使用硬编码值或 lambda 方法,自 Django 版本 1.7 以来不再支持这些方法。

在我看来,这里最好的方法是使用哨兵方法,该方法也可以用于论证on_delete

所以,对于你的情况,我会这样做

# 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
                 )
Run Code Online (Sandbox Code Playgroud)

现在,当您刚刚添加该exam_taken字段时,该字段将使用有保证的现有值,而在删除考试时,学生本身不会被删除,并且具有指向值的外键deleted


Car*_*eil 9

我会在上面略微修改@vault的答案(这可能是一个新功能)。绝对希望使用自然名称来引用该字段。但是,除了覆盖ManagerI之外,我还只是使用以下to_field参数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')
Run Code Online (Sandbox Code Playgroud)


con*_*ode 6

在我的例子中,我想将默认设置为相关模型的任何现有实例.因为可能已删除Examwith id 1,所以我已完成以下操作:

class Student(models.Model):
    exam_taken = models.ForeignKey("Exam", blank=True)

    def save(self, *args, **kwargs):
        try:
            self.exam_taken
        except:
            self.exam_taken = Exam.objects.first()
        super().save(*args, **kwargs)
Run Code Online (Sandbox Code Playgroud)

如果exam_taken不存在,django.db.models.fields.related_descriptors.RelatedObjectDoesNotExist则在尝试访问它时将引发.


djv*_*jvg 6

正如@gareth 的回答中已经暗示的那样,硬编码默认id值可能并不总是最好的主意:

如果该id值在数据库中不存在,那么您就有麻烦了。即使该特定id值确实存在,相应的对象也可能会发生变化。在任何情况下,当使用硬编码id值时,您都必须求助于数据迁移或手动编辑现有数据库内容之类的方法。

为防止出现这种情况,您可以将get_or_create()unique字段(除了id)结合使用。

这是我将如何做到的:

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)
Run Code Online (Sandbox Code Playgroud)

这里一个Exam.title字段用于获取唯一对象,一个Exam.description字段说明了我们如何使用defaults参数 (for get_or_create) 来完全指定默认Exam对象。

请注意,我们pk按照文档的建议返回 a :

对于ForeignKey映射到模型实例的字段,默认值应该是它们引用的字段的值(pk除非to_field设置)而不是模型实例。

另请注意,default可调用对象在Model.__init__()( source )中进行评估。因此,如果您的默认值取决于同一模型的另一个字段,或者取决于请求上下文,或者取决于客户端表单的状态,那么您可能应该查看其他地方

  • @Elcast如果你仔细观察,示例中的[元组已解压](https://docs.python.org/3/tutorial/datastructs.html#tuples-and-sequences):`exam,created = cls.objects .get_or_create(...)`,因此第一项分配给`exam`,第二项分配给`created`。与[Django 文档中的示例](https://docs.djangoproject.com/en/4.1/ref/models/querysets/#get-or-create) 完全相同。 (2认同)