在Django中设置Foreign Key属性的默认值

5m1hhzi4  于 2023-05-19  发布在  Go
关注(0)|答案(9)|浏览(229)

为模型中的外键字段设置默认值的最佳方法是什么?假设我有两个模型,StudentExam,student使用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)

但这会失败,同步时出现表不存在错误。
任何帮助将不胜感激。

h9a6wy2h

h9a6wy2h1#

我会稍微修改@vault上面的答案(这可能是一个新功能)。用自然名称来指代该领域绝对是可取的。但是,我并没有覆盖Manager,而是简单地使用ForeignKeyto_field参数:

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

fjaof16o2#

正如@gareth的answer所暗示的那样,硬编码默认的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参数(对于get_or_create)来完全指定默认的Exam对象。
请注意,我们返回一个pk,如文档所建议的:
对于像ForeignKey这样Map到模型示例的字段,默认值应该是它们引用的字段的值(pk,除非设置了to_field),而不是模型示例。
还要注意,default可调用在Model.__init__()(源代码)中进行计算。因此,如果您的默认值依赖于同一模型的另一个字段,或者依赖于请求上下文,或者依赖于客户端表单的状态,您可能应该查看elsewhere

xmakbtuz

xmakbtuz3#

我使用自然键来采用更自然的方法:
<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')
umuewwlo

umuewwlo4#

在我的例子中,我希望将默认值设置为相关模型的任何 * 现有 * 示例。因为有可能id为1Exam已被删除,所以我执行了以下操作:

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

wyyhbhjk

wyyhbhjk5#

大多数方法的问题是它们在Model中使用了HARD CODED值或lambda方法,这些方法自Django 1.7版以来不再支持。
在我看来,这里最好的方法是使用sentinel方法,它也可以用于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
                 )

现在,当您刚刚添加exam_taken字段时,使用保证的现有值,同时在删除检查时,学生本身不会被删除,并且具有deleted值的外键。

rqmkfv5c

rqmkfv5c6#

你可以使用这个模式:

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表中有一行。您应该使用数据迁移来确保它存在。

fhg3lkii

fhg3lkii7#

您需要在get_exam()中使用get_or_create(),在models.ForeignKey()中使用on_delete(),如下所示。* 不要忘记将.id放在get_or_create(id=1)[0]之后,因为models.ForeignKey()中的默认值需要Exam对象的id,否则会出现错误,您可以看到我的答案解释get_or_create()

def get_exam():        # Here                # ↓ Don't forget
    return Exam.objects.get_or_create(id=1)[0].id

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

并且,您可以将get_exam()放入Student类中,如下所示:

class Student(models.Model):
    # ...
    def get_exam(): # Here
        return Exam.objects.get_or_create(id=1)[0].id

    exam_taken = models.ForeignKey(
        "Exam", 
        default=get_exam, 
        on_delete=models.CASCADE
    )

我建议在Student类中对get_exam()使用@classmethod,如下所示,以便其他类也可以通过类名使用get_exam()。* @classmethod可以做的事情比@staticmethod多,根据我的回答:

class Student(models.Model):
    # ...
    @classmethod # Here
    def get_exam(cls):
        return Exam.objects.get_or_create(id=1)[0].id

    exam_taken = models.ForeignKey(
        "Exam", 
        default=get_exam, 
        on_delete=models.CASCADE
    )

而不是get_exam,您可以将get_exam()分配给default,如下所示:

def get_exam():
    return Exam.objects.get_or_create(id=1)[0].id

class Student(models.Model):
    # ...
    exam_taken = models.ForeignKey(
        "Exam", # ↓ Here ↓
        default=get_exam(),
        on_delete=models.CASCADE
    )
class Student(models.Model):
    # ...
    def get_exam():
        return Exam.objects.get_or_create(id=1)[0].id

    exam_taken = models.ForeignKey(
        "Exam", # ↓ Here ↓
        default=get_exam(), 
        on_delete=models.CASCADE
    )
31moq8wy

31moq8wy8#

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

class YourAdmin(admin.ModelAdmin)

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

这也允许我使用当前用户。
查看Django文档

vwkv1x7d

vwkv1x7d9#

我所知道的最好的方法就是使用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')

相关问题