#Useless migrations

14 messages · Page 1 of 1 (latest)

faint scaffold
#

Hello everyone,
the makemigrations command keeps on creating updates for fields that have not changed, in particular choice fields. I suppose I did something wrong somewhere but I'm unsure how to track this.
It happened a while ago and I did not mind, but it starts to clutter my migration files with a lot of useless information, for example here, none of the fields have actually changed. Does anyone have an idea where to look for answers ?

# Generated by Django 5.0.6 on 2025-05-04 06:08

from django.db import migrations, models

class Migration(migrations.Migration):

    dependencies = [
        ('website', '0122_alter_compte_statut_and_more'),
    ]

    operations = [
        migrations.AlterField(
            model_name='circuit',
            name='type',
            field=models.CharField(choices=[('ECL', 'éclairage & prises co'), ('PC', 'prises fixes'), ('PSP', 'circuit spécialisé')], max_length=3),
        ),
        migrations.AlterField(
            model_name='compte',
            name='statut',
            field=models.CharField(choices=[('CH', 'chaud'), ('FR', 'froid'), ('PR', 'perdu'), ('AR', 'archivé'), ('PP', 'prospect'), ('CC', 'conclu')], default='PP', max_length=2),
        ),
        migrations.AlterField(
            model_name='compte',
            name='tranche_effectif_etablissement',
            field=models.CharField(blank=True, choices=[('02', '3 à 5 salariés'), ('53', '10 000 salariés et plus'), ('32', '250 à 499 salariés'), ('NN', 'Etablissement non-employeur ou présumé non-employeur'), ('42', '1 000 à 1 999 salariés'), ('01', '1 ou 2 salariés'), ('41', '500 à 999 salariés'), ('21', '50 à 99 salariés'), ('12', '20 à 49 salariés'), ('31', '200 à 249 salariés'), ('11', '10 à 19 salariés'), ('00', '0 salarié'), ('03', '6 à 9 salariés'), ('52', '5 000 à 9 999 salariés'), ('22', '100 à 199 salariés'), ('51', '2 000 à 4 999 salariés')], max_length=4, null=True),
        ),
    ]
fleet tree
#

You will get new migrations if you change the choices for a field, depending on how they're defined, even if you don't change the field at all. What does the field definition look like for those fields?

faint scaffold
#

I defined the choices in a static way, see below example for the model 'Compte' and fields 'type', 'statut' and 'tranche_effectif_etablissement'. Is it not the right way ?
I had some other choice fields generated dynamically from a CSV import because they contain a lot of entries, but they don't seem to pose a problem, if anything I would have suspected those first 😅

class Compte(GenericEntity):

    objects = CompteManager()

    # types
    PARTICULIER = 'PC'
    ENTREPRISE = 'EN'
    ADMINISTRATION = 'AD'
    CHOIX_TYPE = {
        (PARTICULIER, 'particulier'),
        (ENTREPRISE, 'entreprise'),
        (ADMINISTRATION, 'administration'),
    }
    # statuts
    PROSPECT = 'PP'  # new Compte instances that need to be evaluated
    CHAUD = 'CH'  # there is sales potential
    FROID = 'FR'  # there was sales potential but not now
    PERDU = 'PR'  # there was sales potential but we lost
    CONCLU = 'CC'  # sales concluded
    ARCHIVE = 'AR'  # there is no sales potential
    CHOIX_STATUT = {
        (PROSPECT, 'prospect'),
        (CHAUD, 'chaud'),
        (FROID, 'froid'),
        (PERDU, 'perdu'),
        (CONCLU, 'conclu'),
        (ARCHIVE, 'archivé')
    }
    # categories juridiques
    CHOIX_CATEGORIES_JURIDIQUES = Sirene.get_categories_juridiques_choices()
   
#
# type d'activité
    CHOIX_ACTIVITE = Sirene.get_NAF_choices(niveau=5)

    # tranche effectif
    CHOIX_TRANCHE_EFFECTIF = {
        ('NN', 'Etablissement non-employeur ou présumé non-employeur'),
        ('00', '0 salarié'),
        ('01', '1 ou 2 salariés'),
        ('02', '3 à 5 salariés'),
        ('03', '6 à 9 salariés'),
        ('11', '10 à 19 salariés'),
        ('12', '20 à 49 salariés'),
        ('21', '50 à 99 salariés'),
        ('22', '100 à 199 salariés'),
        ('31', '200 à 249 salariés'),
        ('32', '250 à 499 salariés'),
        ('41', '500 à 999 salariés'),
        ('42', '1 000 à 1 999 salariés'),
        ('51', '2 000 à 4 999 salariés'),
        ('52', '5 000 à 9 999 salariés'),
        ('53', '10 000 salariés et plus'),
    }

    # generic fields
    uid = models.CharField(max_length=50, null=True)
    parent = models.ForeignKey('Compte', null=True, blank=True, on_delete=models.SET_NULL)
    type = models.CharField(max_length=2, choices=CHOIX_TYPE)
    statut = models.CharField(max_length=2, choices=CHOIX_STATUT, default=PROSPECT)
#
# generic fields
    uid = models.CharField(max_length=50, null=True)
    parent = models.ForeignKey('Compte', null=True, blank=True, on_delete=models.SET_NULL)
    type = models.CharField(max_length=2, choices=CHOIX_TYPE)
    statut = models.CharField(max_length=2, choices=CHOIX_STATUT, default=PROSPECT)
    mail = models.CharField(max_length=100, null=True)
    mail_perso = models.CharField(max_length=100, null=True)
    tel_fix = models.CharField(max_length=50, null=True)
    tel_mob = models.CharField(max_length=50, null=True)
    site_web = models.CharField(max_length=200, null=True, blank=True)
    note = models.CharField(max_length=2000, null=True, blank=True)

    # fields related to SIRENE database
    sirene_etablissement = models.ForeignKey('sirene.SireneEtablissementRecord', null=True, blank=True, on_delete=models.SET_NULL)
    sirene_unite_legale = models.ForeignKey('sirene.SireneUniteLegaleRecord', null=True, blank=True, on_delete=models.SET_NULL)
    nom_legal = models.CharField(max_length=200, null=False)
    nom_usuel = models.CharField(max_length=50, null=True)
    date_creation = models.DateField(null=True, blank=True)
    date_dernier_traitement_etablissement = models.DateTimeField(null=True, blank=True)  # last update in SIRENE
    categorie_juridique_unite_legale = models.IntegerField(null=True, blank=True, choices=CHOIX_CATEGORIES_JURIDIQUES)
    activite_principale_unite_legale = models.CharField(max_length=50, null=True, blank=True, choices=CHOIX_ACTIVITE)  # code NAF
    tranche_effectif_etablissement = models.CharField(max_length=4, null=True, blank=True, choices=CHOIX_TRANCHE_EFFECTIF)
    siret = models.CharField(max_length=50, null=True, blank=True)
    adresse = models.CharField(max_length=100, null=True)
    ville = models.CharField(max_length=50, null=True)
    cp = models.CharField(max_length=20, null=True)
    pays = models.CharField(max_length=20, null=True)

    vcf_filepath = models.CharField(max_length=200, null=True)
fleet tree
#

So any time you change those, you'll get a new migration created, as Django sees it as a change to the field. You can either live with that, or change the choices to come from a callable, mentioned in the docs — see the section that says "choices can also be defined as a callable that expects no arguments and returns any of the formats described above."

faint scaffold
#

OK - if I keep the choice list identical, no migration should be triggered right ? This is my issue at the moment, I do not modify the choice list, but still have a migration popping.

fleet tree
#

Yeah, if nothing changes, it shouldn't. If you're saying it does, what differences are there between the each new migration file that's created?

surreal tiger
faint scaffold
#

Thanks for the pointers folks !
I realised that i can run makemigrations / migrate in sequence without changing any bit of code, it will always find a migration for these 4 choice fields.

I tried a few things, the fastest for me was replacing the dict by a list in the choices definition, and it solved the issue. I suppose that with a dict, python shuffles around the choices available somehow and Django thinks they changed? Anyway thanks a lot for your support , you rock !

#

FINALLY

dcrm@bf1tfre5294:~/web$ python manage.py makemigrations
No changes detected
frosty glade
# faint scaffold Thanks for the pointers folks ! I realised that i can run makemigrations / migr...

you have actually not defined your choices as a dict, but as a set:

CHOIX_STATUT_SET = {
    ('PROSPECT', 'prospect'),
    ('CHAUD', 'chaud'),
    ('FROID', 'froid'),
    ('PERDU', 'perdu'),
    ('CONCLU', 'conclu'),
    ('ARCHIVE', 'archivé')
}

CHOIX_STATUT_DICT = {
    'PROSPECT': 'prospect',
    'CHAUD': 'chaud',
    'FROID': 'froid',
    'PERDU': 'perdu',
    'CONCLU': 'conclu',
    'ARCHIVE': 'archivé'
}

see https://realpython.com/python-sets/#creating-sets-through-literals

sets are unordered and that's likely why Django detects varying orders as a change. that set syntax can be tough to spot and used by accident, been there!

faint scaffold
#

Thanks a lot !! it’s a relief to know the actual reason for this, and also an opportunity to remember about sets 🤣 I almost never use them, that’s probably a shame

surreal tiger
#

Sorry to be offtopic, but yes, sets are awesome! Here's a quick overview from the Hettinger who developed the API, it's testing focused but relevant generally: https://youtu.be/jSIsyMd2-RY?si=oz2TX7KCoOOYR5oM&t=837

Pro tips for writing great unit tests - PyCon Italia 2022

There is an art to condensing test concepts into readable, fast, clear predicates.

  • We look at many examples and show how they can be improve
  • Master the use of any() and all() with generator expressions.
  • Expression set() relations to express big ideas clearly.
  • Cover the problem s...
▶ Play video