Django: Set foreign key using integer?
Asked Answered
A

3

121

Is there a way to set foreign key relationship using the integer id of a model? This would be for optimization purposes.

For example, suppose I have an Employee model:

class Employee(models.Model):
  first_name = models.CharField(max_length=100)
  last_name = models.CharField(max_length=100)
  type = models.ForeignKey('EmployeeType')

and

EmployeeType(models.Model):
  type = models.CharField(max_length=100)

I want the flexibility of having unlimited employee types, but in the deployed application there will likely be only a single type so I'm wondering if there is a way to hardcode the id and set the relationship this way. This way I can avoid a db call to get the EmployeeType object first.

Ashelyashen answered 17/5, 2010 at 0:9 Comment(0)
H
241

Yep:

employee = Employee(first_name="Name", last_name="Name")
employee.type_id = 4
employee.save()

ForeignKey fields store their value in an attribute with _id at the end, which you can access directly to avoid visiting the database.

The _id version of a ForeignKey is a particularly useful aspect of Django, one that everyone should know and use from time to time when appropriate.

caveat: [ < Django 2.1 ]

@RuneKaagaard points out that employee.type is not accurate afterwards in recent Django versions, even after calling employee.save() (it holds its old value). Using it would of course defeat the purpose of the above optimisation, but I would prefer an accidental extra query to being incorrect. So be careful, only use this when you are finished working on your instance (eg employee).

Note: As @humcat points out below, the bug is fixed in Django 2.1

Hilliard answered 17/5, 2010 at 3:44 Comment(8)
model .save() uses the field attname (pre_save() returns the attname value). For ForeignKeys, attname is the name with _id suffix. Also, the foobar_id attribute of a model instance is automatically updated when you set foobar. But where is it officially documented?Fronnia
Using foreign key values directly: docs.djangoproject.com/en/1.8/topics/db/optimization/…Ponder
I tested this on Django 1.7 today, and it's not a good idea there. While it works, and the type field gets written to the database, if you access the type property afterwards it doesn't reflect the change. Said in code, this would fail assert(employe.type.id == 4).Olvan
@RuneKaagaard Wow, I didn't expect that. I expected Django to perform an SQL query when accessing the attribute and returning the relevant Type instance. But it didn't, it returned the value from when the Employee instance was created. I this this is a bug in Django, I would prefer inefficient to incorrect, I'll report it.Hilliard
@WillHardy Exactly, got bitten by that today, when logging a change in a FK field. I thinks Django means the _id shortcut to be readonly, and at least it should be documented as such.Olvan
my workaround to the problem @RuneKaagaard mentioned is to also invalidate the cached value of the related object. This is quite ugly because it touches the innards of the Django Model, but it works: if hasattr(employee, '_type_cache'): delattr(employee, '_type_cache')Christo
@AmichaiSchreiber I believe the next release of Django will have a fix for this problem: code.djangoproject.com/ticket/27710Hilliard
For anyone coming here in the future, this problem is fixed in Django 2.1.Servility
S
57

An alternative that uses create to create the object and save it to the database in one line:

employee = Employee.objects.create(first_name='first', last_name='last', type_id=4)
Sensationalism answered 20/5, 2013 at 20:58 Comment(1)
Does this call the constructor?Kop
C
1

Just to emphasize something on the accepted answer (based on my experience): When you want to add a value in the "id" field of the foreign key (hope that this is clear), you must add "_id" at the end of the name defined by you in that class.

In this example: employee = Employee(first_name="Name", last_name="Name", type_id=4)

Here "type" refers to "type" defined in the "Employee" class.

Note that if you have defined something like "foo_id", you still must add the "_id" suffix and the result will be "foo_id_id"

PS: I can't comment or do other actions (not enough reputation points)

Channing answered 1/3, 2023 at 16:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.