Detect whether code is being run in the context of migrate/makemigrations command
Asked Answered
T

3

10

I have a model with dynamic choices, and I would like to return an empty choice list if I can guarantee that the code is being run in the event of a django-admin.py migrate / makemigrations command to prevent it either creating or warning about useless choice changes.

Code:

from artist.models import Performance
from location.models import Location

def lazy_discover_foreign_id_choices():
    choices = []

    performances = Performance.objects.all()
    choices += {performance.id: str(performance) for performance in performances}.items()

    locations = Location.objects.all()
    choices += {location.id: str(location) for location in locations}.items()

    return choices
lazy_discover_foreign_id_choices = lazy(lazy_discover_foreign_id_choices, list)


class DiscoverEntry(Model):
    foreign_id = models.PositiveIntegerField('Foreign Reference', choices=lazy_discover_foreign_id_choices(), )

So I would think if I can detect the run context in lazy_discover_foreign_id_choices then I can choose to output an empty choice list. I was thinking about testing sys.argv and __main__.__name__ but I'm hoping there's possibly a more reliable way or an API?

Turfy answered 15/10, 2015 at 8:56 Comment(10)
How exactly are your choices dynamic? Could you post some code?Jotting
Sure thing, code addedTurfy
How do you import Performance and Location?Heathendom
Updated code to show imports.Turfy
For me there is another problem: those are not historical versions of these models. This might cause unexpected behavior in migrations, because those models you import are the newest versions, while in migrations, you get the current migration versions.Heathendom
@Heathendom That won't be a problem when an empty list is returned during makemigrations :)Jotting
@Heathendom that sounds complex, and fun! I'll give it a whirl if there's no other silver bullet answersTurfy
@Heathendom it is the makemigrations phase that is important here, the migration phase only executes the migration files created during it.Jotting
@Jotting Actually I would like it to apply to migrate command as well to avoid the warning that migrate will generate when it detects that makemigrations is needed. In that sense, I guess subclassing the migrate command as per your answer would also suffice.Turfy
Indeed that would be enoughJotting
J
6

A solution I can think of would be to subclass the Django makemigrations command to set a flag before actually performing the actual operation.

Example:

Put that code in <someapp>/management/commands/makemigrations.py, it will override Django's default makemigrations command.

from django.core.management.commands import makemigrations
from django.db import migrations


class Command(makemigrations.Command):
    def handle(self, *args, **kwargs):
        # Set the flag.
        migrations.MIGRATION_OPERATION_IN_PROGRESS = True

        # Execute the normal behaviour.
        super(Command, self).handle(*args, **kwargs)

Do the same for the migrate command.

And modify your dynamic choices function:

from django.db import migrations


def lazy_discover_foreign_id_choices():
    if getattr(migrations, 'MIGRATION_OPERATION_IN_PROGRESS', False):
        return []
    # Leave the rest as is.

It is very hacky but fairly easy to setup.

Jotting answered 15/10, 2015 at 11:12 Comment(7)
Thanks for the suggestion, but this doesn't work. Seems that the model field choices is executes before Command.handle()Turfy
I tried it on a project of mine and it seemed to work, I'll investigate.Jotting
@Turfy I confirm that it works fine on a clean install of Django 1.8.5, are you sure that it is the new command that gets executed and not the default one?Jotting
Sorry you're right, it is working for makemigrations. However the same concept does not seem to be working for migrate command.Turfy
The problem with migrate being that Command.handle() executes too late if I put an exception in both migrate.Command.handle() and lazy_discover_foreign_id_choices(). Not sure if this is a conclusive test though?Turfy
No, it is normal that lazy_discover_foreign_id_choices gets executed before, it will be re-executed after, try printing some text instead of raising an exception, you will see. Are you checking the right flag for the migrate command? You should either check both flags in lazy_discover_foreign_id_choices or setting the flag elsewhere.Jotting
For some reason I'm seeing success on both commands since your edit to use migrations.MIGRATION_OPERATION_IN_PROGRESS. Thanks!Turfy
P
21

Here is a fairly non hacky way to do this (since django already creates flags for us) :

import sys
def lazy_discover_foreign_id_choices():
    if ('makemigrations' in sys.argv or 'migrate' in sys.argv):
        return []
    # Leave the rest as is.

This should work for all cases.

Pelops answered 29/10, 2015 at 0:17 Comment(3)
Oh, this is a really nice way to do this.Fact
Wow, I just come with the same idea! It is a really simple though working approach.Ephialtes
Any way to isolate the migration phase of testing?Trill
J
6

A solution I can think of would be to subclass the Django makemigrations command to set a flag before actually performing the actual operation.

Example:

Put that code in <someapp>/management/commands/makemigrations.py, it will override Django's default makemigrations command.

from django.core.management.commands import makemigrations
from django.db import migrations


class Command(makemigrations.Command):
    def handle(self, *args, **kwargs):
        # Set the flag.
        migrations.MIGRATION_OPERATION_IN_PROGRESS = True

        # Execute the normal behaviour.
        super(Command, self).handle(*args, **kwargs)

Do the same for the migrate command.

And modify your dynamic choices function:

from django.db import migrations


def lazy_discover_foreign_id_choices():
    if getattr(migrations, 'MIGRATION_OPERATION_IN_PROGRESS', False):
        return []
    # Leave the rest as is.

It is very hacky but fairly easy to setup.

Jotting answered 15/10, 2015 at 11:12 Comment(7)
Thanks for the suggestion, but this doesn't work. Seems that the model field choices is executes before Command.handle()Turfy
I tried it on a project of mine and it seemed to work, I'll investigate.Jotting
@Turfy I confirm that it works fine on a clean install of Django 1.8.5, are you sure that it is the new command that gets executed and not the default one?Jotting
Sorry you're right, it is working for makemigrations. However the same concept does not seem to be working for migrate command.Turfy
The problem with migrate being that Command.handle() executes too late if I put an exception in both migrate.Command.handle() and lazy_discover_foreign_id_choices(). Not sure if this is a conclusive test though?Turfy
No, it is normal that lazy_discover_foreign_id_choices gets executed before, it will be re-executed after, try printing some text instead of raising an exception, you will see. Are you checking the right flag for the migrate command? You should either check both flags in lazy_discover_foreign_id_choices or setting the flag elsewhere.Jotting
For some reason I'm seeing success on both commands since your edit to use migrations.MIGRATION_OPERATION_IN_PROGRESS. Thanks!Turfy
H
0

Using the django.db.models.signals.pre_migrate should be enough to detect the migrate command. The drawback is that you cannot use it at configuration stage.

Holp answered 23/6, 2020 at 18:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.