Makemigration creating duplicate migrations

Hi, I added a custom validator to some fields in one of my models following the documentation. The desired validation is working correctly, but I’m noticing that migrations for fields in this model are getting re-generated (duplicated) each time I run makemigrations.

in validators.py

from functools import wraps
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _

def validate_range(low=0, high=None):
    @wraps(validate_range)
    def inner(value):
        if value is not None and value < low or high is not None and value > high:
            raise ValidationError(
                _('value must be >= %(low)s' if high is None
                  else 'value must be >= %(low)s and <= %(high)s'),
                params={'low': low, 'high': high},
            )
    return inner

It’s getting used for a number of FloatFields and SmallIntFields in models.py

    ...
    eligible_persons_for_cap = models.SmallIntegerField(
        default=0, validators=[validate_range(high=3)],
        verbose_name="# persons for cap",
        help_text="Number of eligible 'persons' for FSA payment caps.")

    other_nongrain_income = models.FloatField(
        default=0, validators=[validate_range(high=999999)],)

    assumed_basis_for_new = models.FloatField(
        default=0, validators=[validate_range(low=-2, high=2)],
        help_text="Assumed basis for non-contracted bushels.")
    ...

In migration 0015 I see

          migrations.AlterField(
              model_name='farmyear',
              name='eligible_persons_for_cap',
              field=models.SmallIntegerField(default=0, help_text="Number of eligible 'persons' for FSA payment caps.", validators=[main.validators.validate_range], verbose_name='# persons for cap'),
          ),
          migrations.AlterField(
              model_name='farmyear',
              name='other_nongrain_income',
              field=models.FloatField(default=0, validators=[main.validators.validate_range]),
          ),

In migration 0016 I see again

        migrations.AlterField(
            model_name='farmyear',
            name='eligible_persons_for_cap',
            field=models.SmallIntegerField(default=0, help_text="Number of eligible 'persons' for FSA payment caps.", validators=[main.validators.validate_range], verbose_name='# persons for cap'),
        ),
        migrations.AlterField(
            model_name='farmyear',
            name='other_nongrain_income',
            field=models.FloatField(default=0, validators=[main.validators.validate_range]),
        ),

Neither makemigrations nor migrate reports any error, but migration 0016 is identical to migration 0015, with every field using the custom validator viewed by the migration code as having been ‘altered’.
Here ‘mpy’ is an alias for python3 manage.py

(env) dow@dow-HP-Laptop:~/python/ifbtdir$ mpy makemigrations
Migrations for 'main':
  main/migrations/0016_alter_farmbudgetcrop_building_depr_and_more.py
    - Alter field building_depr on farmbudgetcrop
    - Alter field building_repair_and_rent on farmbudgetcrop
    - Alter field county_yield on farmbudgetcrop
    - Alter field drying on farmbudgetcrop
    - Alter field farm_yield on farmbudgetcrop
    - Alter field fertilizers on farmbudgetcrop
    - Alter field fuel_and_oil on farmbudgetcrop
    - Alter field insurance on farmbudgetcrop
    - Alter field interest_nonland on farmbudgetcrop
    - Alter field labor_and_mgmt on farmbudgetcrop
    - Alter field light_vehicle on farmbudgetcrop
    - Alter field machine_depr on farmbudgetcrop
    - Alter field machine_hire_lease on farmbudgetcrop
    - Alter field machine_repair on farmbudgetcrop
    - Alter field misc_overhead_costs on farmbudgetcrop
    - Alter field other_direct_costs on farmbudgetcrop
    - Alter field other_gov_pmts on farmbudgetcrop
    - Alter field other_overhead_costs on farmbudgetcrop
    - Alter field other_revenue on farmbudgetcrop
    - Alter field pesticides on farmbudgetcrop
    - Alter field rented_land_costs on farmbudgetcrop
    - Alter field seed on farmbudgetcrop
    - Alter field storage on farmbudgetcrop
    - Alter field utilities on farmbudgetcrop
    - Alter field yield_variability on farmbudgetcrop
    - Alter field adj_yield on farmcrop
    - Alter field base_coverage_level on farmcrop
    - Alter field planted_acres on farmcrop
    - Alter field prot_factor on farmcrop
    - Alter field rate_yield on farmcrop
    - Alter field rma_cty_expected_yield on farmcrop
    - Alter field ta_aph_yield on farmcrop
    - Alter field annual_land_int_expense on farmyear
    - Alter field annual_land_principal_pmt on farmyear
    - Alter field cash_rented_acres on farmyear
    - Alter field cropland_acres_owned on farmyear
    - Alter field cropland_acres_rented on farmyear
    - Alter field eligible_persons_for_cap on farmyear
    - Alter field land_repairs on farmyear
    - Alter field other_nongrain_expense on farmyear
    - Alter field other_nongrain_income on farmyear
    - Alter field price_factor on farmyear
    - Alter field property_taxes on farmyear
    - Alter field var_rent_cap_floor_frac on farmyear
    - Alter field yield_factor on farmyear
    - Alter field arcco_base_acres on fsacrop
    - Alter field plc_base_acres on fsacrop
    - Alter field plc_yield on fsacrop
    - Alter field assumed_basis_for_new on marketcrop
    - Alter field avg_contract_price on marketcrop
    - Alter field avg_locked_basis on marketcrop
    - Alter field basis_bu_locked on marketcrop
    - Alter field contracted_bu on marketcrop
(env) dow@dow-HP-Laptop:~/python/ifbtdir$ mpy migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, ext, main, sessions
Running migrations:
  Applying main.0016_alter_farmbudgetcrop_building_depr_and_more... OK

If I run this migration, Django reports success, but then if I run makemigrations again with no changes to any files, a new migration 0017 is created identical to 0016 instead of the expected behavior ‘no changes to model’. How can I prevent this from continuing to occur?

Thanks!
Dow

I would guess that this has something to do with the use of a decorator on the validator affecting the ability of the ORM to identify when something has been changed.

If I had to debug this, I would remove the decorated structure from the validator and create the validator directly as described in the docs you referenced, if only to identify this as being the cause of the behavior being seen.

(Note that the docs at Validators | Django documentation | Django describe the use of a class with the __call__ method to create “more sophisticated” validators and suggests you look at the RegexValidator as a pattern to follow.)

Thanks, Ken!

I appreciate the debugging tip! Initially, I did not have the decorator. I added it after getting a “Could not find function validator” error when running makemigrations. This stack overflow post recommended to add @wraps, which did solve that issue. I didn’t notice the duplication issue for a few days.

In this case, it was simple enough to replace my custom validator with two built-in validators.

from django.core.validators import (
    MinValueValidator as MinVal, MaxValueValidator as MaxVal)
...
    other_nongrain_income = models.FloatField(
        default=0, validators=[MinVal(0), MaxVal(999999)],)

Once that was done, the migration duplication issue did not occur again.