#26475: Using functools.partial in model field options causes creation of
unnecessary migration on every 'makemigrations' call
----------------------------+--------------------
     Reporter:  un-def      |      Owner:  nobody
         Type:  Bug         |     Status:  new
    Component:  Migrations  |    Version:  1.9
     Severity:  Normal      |   Keywords:
 Triage Stage:  Unreviewed  |  Has patch:  0
Easy pickings:  0           |      UI/UX:  0
----------------------------+--------------------
 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)
 }}}

--
Ticket URL: <https://code.djangoproject.com/ticket/26475>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/049.e7f481edc0f24e40c3bb0c2baf100b50%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to