Setting default value for Foreign Key attribute
What is the best way to set a default value for a foreign key field in a model? Suppose I have two models, Student
and Exam
with student having exam_taken
as foreign key. How would I ideally set a default value for it? Here's a log of my effort
class Student(models.Model):
....
.....
exam_taken = models.ForeignKey("Exam", default=1)
Works, but have a hunch there's a better way.
def get_exam():
return Exam.objects.get(id=1)
class Student(models.Model):
....
.....
exam_taken = models.ForeignKey("Exam", default=get_exam)
But this fails with tables does not exist error while syncing.
Any help would be appreciated.
Solution 1:
In both of your examples, you're hard-coding the id of the default instance. If that's inevitable, I'd just set a constant.
DEFAULT_EXAM_ID = 1
class Student(models.Model):
...
exam_taken = models.ForeignKey("Exam", default=DEFAULT_EXAM_ID)
Less code, and naming the constant makes it more readable.
Solution 2:
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')
Solution 3:
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')
Solution 4:
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.
Solution 5:
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 how I would 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
unlessto_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.