﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
26475	Using functools.partial in model field options causes creation of unnecessary migration on every 'makemigrations' call	un.def	nobody	"Consider this simple model:


{{{
from django.db import models

from functools import partial


def concat(*args):
    return ''.join(args)


class TestModel(models.Model):

    test_field = models.CharField(
        max_length=128,
        default=partial(concat, 'foo', 'bar')
    )
}}}

Every time you run `./manage.py makemigration` it leads to creation of migration:

{{{
$ ./manage.py makemigrations
Migrations for 'test_app':
  0008_auto_....py:
    - Alter field test_field on testmodel

$ ./manage.py makemigrations
Migrations for 'test_app':
  0009_auto_....py:
    - Alter field test_field on testmodel

$ ./manage.py makemigrations
Migrations for 'test_app':
  0010_auto_....py:
    - Alter field test_field on testmodel
}}}

All created migrations are equal.

Cause: when project state is rendered from previous migration file, `functools.partial` instance is created (`migrations.AlterField(... default=functools.partial(djtest_app.models.concat, *('foo', 'bar'), **{}), ...)`). After that each model field is deconstructed to dict and previous and current field states (i.e. dicts) are compared [https://github.com/django/django/blob/2cd2d188516475ddf256e6267cd82c495fb5c430/django/db/migrations/autodetector.py#L862]
But different instances of `functools.partial` with same arguments are not equal — I guess that `partial` doesn't implement own `__eq__`/`__ne__` methods, so comparison is made by object `id()` (memory address), and false field changes is detected.

Possible solution (workaround): we can use own `partial` subclass in migration files. Our subclass implements custom `__eq__`/`__ne__` magic methods:

{{{
class Partial(functools.partial):
    
    def __eq__(self, other):
        return (self.func == other.func and
                self.args == other.args and
                self.keywords == other.keywords)
                
    def __ne__(self, other):
        return not self.__eq__(other)
}}}
"	Bug	closed	Migrations	1.9	Normal	fixed		Markus Holtermann	Ready for checkin	1	0	0	0	0	0
