Code

Opened 6 years ago

Closed 6 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:

Description

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:

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

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

In [9]: f.save()

# Forgot to do a print here, but f.id == 1

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

In [11]: b.a = f

# Works fine, as expected
In [12]: b.save()

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]: c.save()                        
---------------------------------------------------------------------------
pysqlite2.dbapi2.InterfaceError                            Traceback (most recent call last)

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

/home/nicolas/Projects/django/django-trunk/django/db/models/base.py 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/util.py 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/base.py 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)
    134 
    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 6 years ago.
fix + tests + doc

Download all attachments as: .zip

Change History (5)

comment:1 Changed 6 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 6 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 6 years ago by PhiR

fix + tests + doc

comment:3 Changed 6 years ago by jacob

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

comment:4 Changed 6 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.

Add Comment

Modify Ticket

Change Properties
<Author field>
Action
as closed
as The resolution will be set. Next status will be 'closed'
The resolution will be deleted. Next status will be 'new'
Author


E-mail address and user name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.