Opened 10 years ago

Closed 10 years ago

Last modified 8 years ago

#22741 closed Bug (needsinfo)

Migrate fails with `TypeError` when using a callable default for a `ForeignKey`

Reported by: Rik Owned by: nobody
Component: Database layer (models, ORM) Version: dev
Severity: Normal Keywords: migrations foreignkey
Cc: Simon Charette Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by Tim Graham)

When using a lambda function as a default value for a ForeignKey field, makemigrations will fail with the exception:

    ValueError: Cannot serialize function: lambda

This is the code:

    from django.db import models


    class House(models.Model):
        address = models.CharField(max_length=100)


    class Person(models.Model):
        house = models.ForeignKey(House, default=lambda: House.objects.all()[0])
        name = models.CharField(max_length=100)

I tried to change the lambda to a defined function like this:

    from django.db import models


    class House(models.Model):
        address = models.CharField(max_length=100)


    def first_house():
        House.objects.all()[0]


    class Person(models.Model):
        house = models.ForeignKey(House, default=first_house)
        name = models.CharField(max_length=100)

In this case, makemigrations works, but migrate will now fail, with the exception:

    TypeError: int() argument must be a string or a number, not 'House'

I'm testing this in Python 3 btw.

Change History (4)

comment:1 by Simon Charette, 10 years ago

Cc: Simon Charette added
Resolution: needsinfo
Status: newclosed
Summary: Makemigrations fails when using lambda as default for ForeignKey fieldMigrate fails with `TypeError` when using a callable default for a `ForeignKey`

Please use the preview options before submitting a ticket -- code blocks should be wrapped in {{{ }}} in order to be displayed properly.

Unfortunately the migration framework doesn't support lambdas deconstruction, see #21037 and #22351 for details.

Since you seems to have another issue setting default=first_house we'd need the full TypeError traceback to determine if it's actually a Django bug.

The fact that your first_house function doesn't return anything (copy-pasta from the lambda?) is a bit suspicious here.

comment:2 by Tim Graham, 9 years ago

Description: modified (diff)

comment:3 by Reinout van Rees, 8 years ago

I'm seeing something that looks like the same issue. I *do* have a traceback :-) It is django 1.8.14 (I'm updating an ancient project):

Traceback (most recent call last):
  File "bin/test", line 42, in <module>
    sys.exit(djangorecipe.binscripts.test('trs.testsettings', '', 'trs'))
  File "/code/eggs/djangorecipe-2.2.1-py3.5.egg/djangorecipe/binscripts.py", line 22, in test
    management.execute_from_command_line(sys.argv)
  File "/code/eggs/Django-1.8.14-py3.5.egg/django/core/management/__init__.py", line 354, in execute_from_command_line
    utility.execute()
  File "/code/eggs/Django-1.8.14-py3.5.egg/django/core/management/__init__.py", line 346, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/code/eggs/Django-1.8.14-py3.5.egg/django/core/management/commands/test.py", line 30, in run_from_argv
    super(Command, self).run_from_argv(argv)
  File "/code/eggs/Django-1.8.14-py3.5.egg/django/core/management/base.py", line 394, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/code/eggs/Django-1.8.14-py3.5.egg/django/core/management/commands/test.py", line 74, in execute
    super(Command, self).execute(*args, **options)
  File "/code/eggs/raven-5.7.2-py3.5.egg/raven/contrib/django/management/__init__.py", line 41, in new_execute
    return original_func(self, *args, **kwargs)
  File "/code/eggs/Django-1.8.14-py3.5.egg/django/core/management/base.py", line 445, in execute
    output = self.handle(*args, **options)
  File "/code/eggs/Django-1.8.14-py3.5.egg/django/core/management/commands/test.py", line 90, in handle
    failures = test_runner.run_tests(test_labels)
  File "/code/eggs/django_nose-1.4.4-py3.5.egg/django_nose/runner.py", line 303, in run_tests
    result = self.run_suite(nose_argv)
  File "/code/eggs/django_nose-1.4.4-py3.5.egg/django_nose/runner.py", line 241, in run_suite
    addplugins=plugins_to_add)
  File "/code/eggs/nose-1.3.7-py3.5.egg/nose/core.py", line 121, in __init__
    **extra_args)
  File "/usr/lib/python3.5/unittest/main.py", line 94, in __init__
    self.runTests()
  File "/code/eggs/nose-1.3.7-py3.5.egg/nose/core.py", line 207, in runTests
    result = self.testRunner.run(self.test)
  File "/code/eggs/nose-1.3.7-py3.5.egg/nose/core.py", line 50, in run
    wrapper = self.config.plugins.prepareTest(test)
  File "/code/eggs/nose-1.3.7-py3.5.egg/nose/plugins/manager.py", line 99, in __call__
    return self.call(*arg, **kw)
  File "/code/eggs/nose-1.3.7-py3.5.egg/nose/plugins/manager.py", line 167, in simple
    result = meth(*arg, **kw)
  File "/code/eggs/django_nose-1.4.4-py3.5.egg/django_nose/plugin.py", line 82, in prepareTest
    self.old_names = self.runner.setup_databases()
  File "/code/eggs/django_nose-1.4.4-py3.5.egg/django_nose/runner.py", line 490, in setup_databases
    return super(NoseTestSuiteRunner, self).setup_databases()
  File "/code/eggs/Django-1.8.14-py3.5.egg/django/test/runner.py", line 166, in setup_databases
    **kwargs
  File "/code/eggs/Django-1.8.14-py3.5.egg/django/test/runner.py", line 370, in setup_databases
    serialize=connection.settings_dict.get("TEST", {}).get("SERIALIZE", True),
  File "/code/eggs/Django-1.8.14-py3.5.egg/django/db/backends/base/creation.py", line 368, in create_test_db
    test_flush=not keepdb,
  File "/code/eggs/Django-1.8.14-py3.5.egg/django/core/management/__init__.py", line 120, in call_command
    return command.execute(*args, **defaults)
  File "/code/eggs/raven-5.7.2-py3.5.egg/raven/contrib/django/management/__init__.py", line 41, in new_execute
    return original_func(self, *args, **kwargs)
  File "/code/eggs/Django-1.8.14-py3.5.egg/django/core/management/base.py", line 445, in execute
    output = self.handle(*args, **options)
  File "/code/eggs/Django-1.8.14-py3.5.egg/django/core/management/commands/migrate.py", line 222, in handle
    executor.migrate(targets, plan, fake=fake, fake_initial=fake_initial)
  File "/code/eggs/Django-1.8.14-py3.5.egg/django/db/migrations/executor.py", line 110, in migrate
    self.apply_migration(states[migration], migration, fake=fake, fake_initial=fake_initial)
  File "/code/eggs/Django-1.8.14-py3.5.egg/django/db/migrations/executor.py", line 148, in apply_migration
    state = migration.apply(state, schema_editor)
  File "/code/eggs/Django-1.8.14-py3.5.egg/django/db/migrations/migration.py", line 115, in apply
    operation.database_forwards(self.app_label, schema_editor, old_state, project_state)
  File "/code/eggs/Django-1.8.14-py3.5.egg/django/db/migrations/operations/fields.py", line 62, in database_forwards
    field,
  File "/code/eggs/Django-1.8.14-py3.5.egg/django/db/backends/sqlite3/schema.py", line 179, in add_field
    self._remake_table(model, create_fields=[field])
  File "/code/eggs/Django-1.8.14-py3.5.egg/django/db/backends/sqlite3/schema.py", line 77, in _remake_table
    self.effective_default(field)
  File "/code/eggs/Django-1.8.14-py3.5.egg/django/db/backends/base/schema.py", line 211, in effective_default
    default = field.get_db_prep_save(default, self.connection)
  File "/code/eggs/Django-1.8.14-py3.5.egg/django/db/models/fields/related.py", line 1958, in get_db_prep_save
    return self.related_field.get_db_prep_save(value, connection=connection)
  File "/code/eggs/Django-1.8.14-py3.5.egg/django/db/models/fields/__init__.py", line 710, in get_db_prep_save
    prepared=False)
  File "/code/eggs/Django-1.8.14-py3.5.egg/django/db/models/fields/__init__.py", line 977, in get_db_prep_value
    value = self.get_prep_value(value)
  File "/code/eggs/Django-1.8.14-py3.5.egg/django/db/models/fields/__init__.py", line 985, in get_prep_value
    return int(value)
TypeError: int() argument must be a string, a bytes-like object or a number, not 'YearWeek'

The relevant field on my model looks like this:

    start = models.ForeignKey(
        'YearWeek',
        blank=True,
        null=True,
        default=this_year_week,
        related_name="starting_projects",
        verbose_name="startweek")

So... a callable as a default that returns an object instance (of the correct type), but it looks like the migration mechanism wants a foreign key ID instead of an actual instance.

Two possible things that might make it a corner case:

  • I see the error when I run my tests. It happens in the test setup. The complication/cornercase is that the callable tries to grab the instance from memcache. So the test might get an instance somehow without it actually having an ID in the database. Doesn't sound very logical, I'm just mentioning it :-)
  • It is an initial migration.

I haven't investigated further. I also don't know yet if the issue should be re-openend. I'm leaving it closed for now and I'll do some further digging.

comment:4 by Reinout van Rees, 8 years ago

Yes, it can stay closed.

See #23454, especially comment 5 by Tim: https://code.djangoproject.com/ticket/23454#comment:5

Basically: yes, it should be an ID. (I assume an instance was OK for south, which I used previously).

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