设置外键属性的默认值


78

在模型中为外键字段设置默认值的最佳方法是什么?假设我有两个模型,StudentExam学生有exam_taken作为外键。理想情况下,如何设置默认值?这是我的努力记录

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

可以,但是有一种更好的方法。

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

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

但这会失败,并且在同步时表不存在错误。

任何帮助,将不胜感激。



@NitzanTomer它用于AdminModelField。以前看过。
Primal Pappachan 2012年

6
@TomIngram:default=get_exam()get_exam立即调用并永久存储该值,而default=get_exam存储稍后在每次使用该default属性时将调用的方法,以获取该时刻的值。它通常与datetime一起使用,即default=datetime.nownot default=datetime.now()
克里斯·普拉特

@TomIngram:我不是在争论一种方法的优点。我的意思只是说它是有效的,而作者似乎想要那样。
克里斯·普拉特

1
@TomIngram:不同之处在于,当您添加括号时,它不再是“可调用的”;这是一个静态值,只不过在其中放置一个整数而已。
克里斯·普拉特

Answers:


43

在两个示例中,您都在对默认实例的ID进行硬编码。如果这是不可避免的,我只需要设置一个常量即可。

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

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


我收到以下错误:TypeError: Additional arguments should be named <dialectname>_<argument>, got 'default' 代码:category_id = db.Column(db.Integer, db.ForeignKey("category.id", default=1), primary_key=True) 您能帮忙吗?
ChickenFeet

顺便说一句,有两个PK,因此密钥仍然是唯一的。
ChickenFeet

你好 好问题!但是,如何在没有默认“ ID”的情况下对其进行编码。我使用角色模型类,并将“ get_or_create默认角色”作为默认角色字段。我应该返回ID,但想返回对象
parfeniukink

15

我会稍稍修改@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')

12

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

<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')

3
我在Django 1.6中进行了尝试,但收到错误消息:“以int()为基数10的无效文字:'IT'。(我的字符串不同,但您明白了。)
塞思

3
不过,这很好用,而且看起来更像Python风格:default=lambda: Country.objects.filter(sigla='IT').first()
赛斯(Seth)

奇怪,我记得曾经用Django 1.6进行过测试。谷歌搜索我发现这是一个元组问题,请尝试:def get_by_natural_key(self, sigla): return (self.get(sigla=sigla),)或也使用default=('IT',)。我只是在猜测;)
Vault

“自然”在这里是一个有问题的概念。您似乎要说的是要强迫用户为每个项目选择一个五个字符的ID,但这在本质上并不比序列号更自然。
grvsmth

8

就我而言,我想将默认值设置为相关模型的任何现有实例。由于可能已删除ExamID为的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)

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


4

您可以使用以下模式:

class Other(models.Model):
    DEFAULT_PK=1
    name=models.CharField(max_length=1024)

class FooModel(models.Model):
    other=models.ForeignKey(Other, default=Other.DEFAULT_PK)

当然,您需要确保的表中有一行Other。您应该使用数据迁移来确保它存在。


4

正如@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)

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

请注意,我们会pk根据docs的建议返回a :

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

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


如果U对我来说还没有任何迁移,它将无法正常工作。我对User使用字段角色,在其中我使用get_default_user_rolecallback作为默认参数,如您的示例。所以如果我没有迁移我会继续no such table: users_role下去python manage.py makemigration。但是,如果我role field用户模型中发表评论,则run makemigrations-没问题。在我取消评论之后role fieldrun migrations再次没有问题。也许你可以这样阻止我?
parfeniukink

@parfeniukink:我很乐意为您提供帮助,但是在看不到您的实际代码的情况下很难分辨出问题所在。也许您可以创建更多细节的新问题?
djvg

哦,非常感谢,但我已经完成了任务)))
parfeniukink

0

我正在Django Admin中寻找解决方案,然后发现了这一点:

class YourAdmin(admin.ModelAdmin)

    def get_changeform_initial_data(self, request):
        return {'owner': request.user}

这也使我可以使用当前用户。

看到Django文档


0

我知道的最好方法是使用lambda

class TblSearchCase(models.Model):
    weights = models.ForeignKey('TblSearchWeights', models.DO_NOTHING, default=lambda: TblSearchWeights.objects.get(weight_name='value_you_want'))

因此您可以指定默认行。

default=lambda: TblSearchWeights.objects.get(weight_name='value_you_want')
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.