93

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.

Lord Elrond
  • 10,935
  • 6
  • 28
  • 60
Primal Pappachan
  • 24,967
  • 19
  • 66
  • 83
  • how about: http://stackoverflow.com/questions/937954/how-do-you-specify-a-default-for-a-django-foreignkey-model-or-adminmodel-field – Nitzan Tomer Feb 16 '12 at 13:55
  • @NitzanTomer It for a AdminModelField. Had seen it previously. – Primal Pappachan Feb 16 '12 at 14:04
  • 8
    @TomIngram: `default=get_exam()` will call `get_exam` immediately and store the value permanently, whereas `default=get_exam` stores the method which would later be called each time the `default` attribute is used, to get the value at that moment. It's often used with datetime, i.e. `default=datetime.now`, *not* `default=datetime.now()`. – Chris Pratt Feb 16 '12 at 15:33
  • @TomIngram: I'm not debating the merits of one approach over another. My point was only that it is valid, and the author seems to want it that way. – Chris Pratt Feb 16 '12 at 16:32
  • @ChrisPratt "This can be a value or a callable object. If callable it will be called every time a new object is created." so this means `default=get_exam()` and `default=get_exam` effectively is the same, unless you can re-call default? – T I Feb 16 '12 at 16:52
  • 1
    @TomIngram: The difference is that when you add the parenthesis, it's no longer a "callable"; it's a static value, no different that just putting an integer there. – Chris Pratt Feb 16 '12 at 20:23
  • @ChrisPratt hello I understand the difference etc, what I guess I'm struggling with is working out why. Giving it a bit of thought I'm thinking that as external states change perhaps the required default value will also and you would need to re-evaluate this value using the callback you passed, maybe I'm also over emphasising or misinterpreting `default` as to me it reads as something that should be relatively static, thanks it's been interesting and thought provoking for me at least :) – T I Feb 16 '12 at 21:03

9 Answers9

49

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.

Gareth
  • 1,420
  • 11
  • 14
  • I get the following error: `TypeError: Additional arguments should be named _, got 'default'` With code: `category_id = db.Column(db.Integer, db.ForeignKey("category.id", default=1), primary_key=True)` Can you please help? – ChickenFeet Mar 10 '17 at 08:05
  • BTW there are two PKs, so the key will still be unique. – ChickenFeet Mar 10 '17 at 08:08
  • Hi. Good issue! But how can I code it without "ID" in default. I use Role model class and I make "get_or_create default role" as default role field. I should return ID, but wanna return object – parfeniukink Jan 23 '20 at 22:37
18

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')
Carson McNeil
  • 680
  • 6
  • 21
13

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')
vault
  • 3,642
  • 1
  • 36
  • 46
  • 4
    I tried this in Django 1.6 but I get the error, "Invalid literal for int() with base 10: 'IT'. (My string is different, but you get the idea.) – Seth May 07 '14 at 18:30
  • 3
    This worked fine, though, and seems more Pythonic: `default=lambda: Country.objects.filter(sigla='IT').first()` – Seth May 07 '14 at 18:44
  • Strange, I remember to have tested this with django 1.6. Googling I found it's a tuple problem, try: `def get_by_natural_key(self, sigla): return (self.get(sigla=sigla),)` or also with `default=('IT',)`. I'm just guessing ;) – vault May 08 '14 at 10:37
  • "Natural" is a problematic concept here. What you seem to be saying is that you're forcing the user to choose a five-character ID for each item, but that is not inherently more natural than a serial number. – grvsmth Nov 26 '20 at 16:03
11

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 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.

djvg
  • 8,264
  • 3
  • 53
  • 83
  • It doesn't work if U have not any migrations yet for me. I use field role for User, where I use `get_default_user_role` callback as default parameter like in your example. So if I have no migrations I got `no such table: users_role` on `python manage.py makemigration`. But if I comment `role field` in *User* model and `run makemigrations` - it is no problems. After what I uncomment `role field` and `run migrations` again with no problems. Maybe U can halp me with that? – parfeniukink Oct 15 '20 at 20:59
  • @parfeniukink: I would be happy to help, but it is a bit difficult to tell what is going wrong without seeing your actual code. Perhaps you could create a new Question with some more details? – djvg Oct 16 '20 at 07:10
  • Oh, Thanks a lot, but I've finished this task ))) – parfeniukink Nov 04 '20 at 19:54
9

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.

connorbode
  • 2,897
  • 1
  • 27
  • 30
4

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.

guettli
  • 23,964
  • 63
  • 293
  • 556
4

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.

Tanckom
  • 1,102
  • 2
  • 14
  • 35
  • 1
    This worked perfectly — I had a website admin where there were overall site settings, and one of them was the default language. I don't know in advance which languages will be available, and this enabled me to provide a default of "undefined". – Andrew Swift Mar 02 '22 at 16:00
0

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

C.K.
  • 3,206
  • 22
  • 35
0

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')
DeyaEldeen
  • 9,127
  • 9
  • 40
  • 74