Opened 8 years ago

Closed 8 years ago

#6445 closed (fixed)

models.ForeignKey should accept instances as default value

Reported by: eikke@… Owned by: PhiR
Component: Core (Other) Version: master
Severity: Keywords: foreignkey default
Cc: Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: UI/UX:


It looks like the 'default' parameter of a models.ForeignKey field requires one to pass an id, passing an instance doesn't work. This is rather contra-intuitive as setting a ForeignKey field of an instance of a model to an instance of the foreign model is supported.

Here's a sample:

class Foo(models.Model):
    a = models.CharField(max_length=10)

def get_foo():
    return Foo.objects.get(id=1)

class Bar(models.Model):
    b = models.CharField(max_length=10)
    a = models.ForeignKey(Foo, default=get_foo)

In [8]: f = Foo(a='abc')

In [9]:

# Forgot to do a print here, but == 1

In [10]: b = Bar(b='def')

In [11]: b.a = f

# Works fine, as expected
In [12]:

In [13]: b.a
Out[13]: <Foo: Foo object>

In [14]: c = Bar(b='ghi')

# Using 'default', callable returns f, doesn't work, unlike expected
In [15]:                        
pysqlite2.dbapi2.InterfaceError                            Traceback (most recent call last)

/home/nicolas/Projects/django/app/<ipython console> 

/home/nicolas/Projects/django/django-trunk/django/db/models/ in save(self, raw)
    259                 cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % \
    260                     (qn(self._meta.db_table), ','.join(field_names),
--> 261                     ','.join(placeholders)), db_values)
    262             else:
    263                 # Create a new record with defaults for everything.

/home/nicolas/Projects/django/django-trunk/django/db/backends/ in execute(self, sql, params)
     16         start = time()
     17         try:
---> 18             return self.cursor.execute(sql, params)
     19         finally:
     20             stop = time()

/home/nicolas/Projects/django/django-trunk/django/db/backends/sqlite3/ in execute(self, query, params)
    131     def execute(self, query, params=()):
    132         query = self.convert_query(query, len(params))
--> 133         return Database.Cursor.execute(self, query, params)
    135     def executemany(self, query, param_list):

InterfaceError: Error binding parameter 1 - probably unsupported type.

Running this same query in a web view shows 'params' is [id, <Foo instance>], where the second expected value should be the id of the used Foo instance.

Attachments (1)

6645.diff (2.0 KB) - added by PhiR 8 years ago.
fix + tests + doc

Download all attachments as: .zip

Change History (5)

comment:1 Changed 8 years ago by Nicolas Trangez <eikke@…>

  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset

Forgot something: this does work, but it's a rather ugly workaround:

a = models.ForeignKey(Foo, default=lambda: get_foo().id)

comment:2 Changed 8 years ago by PhiR

  • Has patch set
  • Owner changed from nobody to PhiR
  • Status changed from new to assigned
  • Triage Stage changed from Unreviewed to Design decision needed

My patch fixes the issue by overriding get_default in ForeignKey to handle that case. I threw some tests in for good measure.

Changed 8 years ago by PhiR

fix + tests + doc

comment:3 Changed 8 years ago by jacob

  • Triage Stage changed from Design decision needed to Ready for checkin

comment:4 Changed 8 years ago by mtredinnick

  • Resolution set to fixed
  • Status changed from assigned to closed

(In [7331]) Fixed #6445 -- Allow model instances to be used as a default for ForeignKeys
(via a callable). Also updates the documentation of the "default" attribute to
indicate a callable can be used. Thanks, Philipe Raoult.

Note: See TracTickets for help on using tickets.
Back to Top