设置外键属性的默认值

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

但是这个失败了,因为同步时表不存在错误。

如果你能帮忙,我将不胜感激。

109758 次浏览

I use natural keys to adopt a more natural approach:

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

You could use this pattern:

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)

Of course you need to be sure that there is a row in the table of Other. You should use a datamigration to be sure it exists.

In my case, I wanted to set the default to any existing instance of the related model. Because it's possible that the Exam with id 1 has been deleted, I've done the following:

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)

If exam_taken doesn't exist, django.db.models.fields.related_descriptors.RelatedObjectDoesNotExist will be raised when a attempting to access it.

I would modify @vault's answer above slightly (this may be a new feature). It is definitely desirable to refer to the field by a natural name. However instead of overriding the Manager I would simply use the to_field param of 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')

I'm looking for the solution in Django Admin, then I found this:

class YourAdmin(admin.ModelAdmin)


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

this also allows me to use the current user.

see django docs

As already implied in @gareth's answer, hard-coding a default id value might not always be the best idea:

If the id value does not exist in the database, you're in trouble. Even if that specific id value does exist, the corresponding object may change. In any case, when using a hard-coded id value, you'd have to resort to things like data-migrations or manual editing of existing database content.

To prevent that, you could use get_or_create() in combination with a unique field (other than id).

Here's one way to do it:

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
)

Here an Exam.title field is used to get a unique object, and an Exam.description field illustrates how we can use the defaults argument (for get_or_create) to fully specify the default Exam object.

Note that we return a pk, as suggested by the docs:

For fields like ForeignKey that map to model instances, defaults should be the value of the field they reference (pk unless to_field is set) instead of model instances.

Also note that default callables are evaluated in Model.__init__() (source). So, if your default value depends on another field of the same model, or on the request context, or on the state of the client-side form, you should probably look elsewhere.

the best way I know is to use lambdas

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

so you can specify the default row..

default=lambda: TblSearchWeights.objects.get(weight_name='value_you_want')

The issue with most of these approaches are that they use HARD CODED values or lambda methods inside the Model which are not supported anymore since Django Version 1.7.

In my opinion, the best approach here is to use a sentinel method which can also be used for the on_delete argument.

So, in your case, I would do

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

Now, when you just added the exam_taken field uses a guaranteed existing value while also, when deleting the exam, the Student themself are not deleted and have a foreign key to a deleted value.