#29852: Infinite migrations when using SimpleLazyObject in field arguments
------------------------------+------------------------------------
     Reporter:  Javier Buzzi  |                    Owner:  nobody
         Type:  Bug           |                   Status:  new
    Component:  Migrations    |                  Version:  master
     Severity:  Normal        |               Resolution:
     Keywords:                |             Triage Stage:  Accepted
    Has patch:  0             |      Needs documentation:  0
  Needs tests:  0             |  Patch needs improvement:  0
Easy pickings:  0             |                    UI/UX:  0
------------------------------+------------------------------------
Changes (by Tim Graham):

 * type:  Uncategorized => Bug
 * stage:  Unreviewed => Accepted
 * component:  Uncategorized => Migrations


Old description:

> Reference: https://code.djangoproject.com/ticket/29772
>
> {{{
> from django.db import models
> from django.utils.functional import SimpleLazyObject
> from django.core.validators import MinValueValidator
> import datetime
>
> # Create your models here.
> class Thing(models.Model):
>     day =
> models.DateTimeField(validators=[MinValueValidator(SimpleLazyObject(datetime.datetime.now))])
> }}}
>
> This works great right up until you try running migrations:
> 1. First time it creates a `    - Create model Thing`
> 2. Then you run the same command again and again, and will always
> generate a new migration `  - Alter field day on thing`
>

> {{{
> class Migration(migrations.Migration):
>
>     dependencies = [
>         ('app', '0005_auto_20181015_2203'),
>     ]
>
>     operations = [
>         migrations.AlterField(
>             model_name='thing',
>             name='day',
> field=models.DateTimeField(validators=[django.core.validators.MinValueValidator(datetime.datetime(2018,
> 10, 15, 22, 3, 41, 390769))]),
>         ),
>     ]
> }}}
>

> The issue being that the `now()` is being evaluated and thus is always
> different.
>
> I got it 50% working with this diff:
>
> {{{
> diff --git a/django/db/migrations/serializer.py
> b/django/db/migrations/serializer.py
> index 911cf0f..ef2ad43 100644
> --- a/django/db/migrations/serializer.py
> +++ b/django/db/migrations/serializer.py
> @@ -12,7 +12,7 @@ import uuid
>  from django.db import models
>  from django.db.migrations.operations.base import Operation
>  from django.db.migrations.utils import COMPILED_REGEX_TYPE, RegexObject
> -from django.utils.functional import LazyObject, Promise
> +from django.utils.functional import LazyObject, Promise,
> SimpleLazyObject
>  from django.utils.timezone import utc
>  from django.utils.version import get_docs_version
>
> @@ -273,6 +273,8 @@ def serializer_factory(value):
>      from django.db.migrations.writer import SettingsReference
>      if isinstance(value, Promise):
>          value = str(value)
> +    elif isinstance(value, SimpleLazyObject):
> +        value = value._setupfunc
>      elif isinstance(value, LazyObject):
>          # The unwrapped value is returned as the first item of the
> arguments
>          # tuple.
> }}}
>

> Turns the migrations into:
>
> {{{
> class Migration(migrations.Migration):
>
>     dependencies = [
>         ('app', '0004_auto_20181015_2203'),
>     ]
>
>     operations = [
>         migrations.AlterField(
>             model_name='thing',
>             name='day',
> field=models.DateTimeField(validators=[django.core.validators.MinValueValidator(datetime.datetime.now)]),
>         ),
>     ]
> }}}
>
> While it's a great improvement, it still generates a new one every time.
> I'm a little over my head with this one, this code is very dense. I could
> keep looking at it tomorrow, but i need someone to point me in the right
> direction + where in the world do i put the tests for this thing??
> Thanks.

New description:

 {{{
 from django.db import models
 from django.utils.functional import SimpleLazyObject
 from django.core.validators import MinValueValidator
 import datetime

 # Create your models here.
 class Thing(models.Model):
     day =
 
models.DateTimeField(validators=[MinValueValidator(SimpleLazyObject(datetime.datetime.now))])
 }}}

 This works great right up until you try running migrations:
 1. First time it creates a `    - Create model Thing`
 2. Then you run the same command again and again, and will always generate
 a new migration `  - Alter field day on thing`


 {{{
 class Migration(migrations.Migration):

     dependencies = [
         ('app', '0005_auto_20181015_2203'),
     ]

     operations = [
         migrations.AlterField(
             model_name='thing',
             name='day',
 
field=models.DateTimeField(validators=[django.core.validators.MinValueValidator(datetime.datetime(2018,
 10, 15, 22, 3, 41, 390769))]),
         ),
     ]
 }}}


 The issue being that the `now()` is being evaluated and thus is always
 different.

 I got it 50% working with this diff:

 {{{
 diff --git a/django/db/migrations/serializer.py
 b/django/db/migrations/serializer.py
 index 911cf0f..ef2ad43 100644
 --- a/django/db/migrations/serializer.py
 +++ b/django/db/migrations/serializer.py
 @@ -12,7 +12,7 @@ import uuid
  from django.db import models
  from django.db.migrations.operations.base import Operation
  from django.db.migrations.utils import COMPILED_REGEX_TYPE, RegexObject
 -from django.utils.functional import LazyObject, Promise
 +from django.utils.functional import LazyObject, Promise, SimpleLazyObject
  from django.utils.timezone import utc
  from django.utils.version import get_docs_version

 @@ -273,6 +273,8 @@ def serializer_factory(value):
      from django.db.migrations.writer import SettingsReference
      if isinstance(value, Promise):
          value = str(value)
 +    elif isinstance(value, SimpleLazyObject):
 +        value = value._setupfunc
      elif isinstance(value, LazyObject):
          # The unwrapped value is returned as the first item of the
 arguments
          # tuple.
 }}}


 Turns the migrations into:

 {{{
 class Migration(migrations.Migration):

     dependencies = [
         ('app', '0004_auto_20181015_2203'),
     ]

     operations = [
         migrations.AlterField(
             model_name='thing',
             name='day',
 
field=models.DateTimeField(validators=[django.core.validators.MinValueValidator(datetime.datetime.now)]),
         ),
     ]
 }}}

 While it's a great improvement, it still generates a new one every time.
 I'm a little over my head with this one, this code is very dense. I could
 keep looking at it tomorrow, but i need someone to point me in the right
 direction + where in the world do i put the tests for this thing?? Thanks.

--

Comment:

 I think the problem is this:
 {{{
 >>> a = SimpleLazyObject(datetime.datetime.now)
 >>> b = SimpleLazyObject(datetime.datetime.now)
 >>> a == b
 }}}
 The two must be considered equal to prevent infinite migrations. See
 91f701f4fc324cd2feb7dbf151338a358ca0ea18 for a similar issue.

-- 
Ticket URL: <https://code.djangoproject.com/ticket/29852#comment:1>
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/069.3c41f1e38114765387ced2800e83141a%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to