9

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?

DanH
  • 5,498
  • 4
  • 49
  • 72
  • 1
    How exactly are your choices dynamic? Could you post some code? – aumo Oct 15 '15 at 09:36
  • Sure thing, code added – DanH Oct 15 '15 at 10:33
  • How do you import `Performance` and `Location`? – Ivan Oct 15 '15 at 10:47
  • Updated code to show imports. – DanH Oct 15 '15 at 10:48
  • 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. – Ivan Oct 15 '15 at 10:51
  • @Ivan That won't be a problem when an empty list is returned during makemigrations :) – aumo Oct 15 '15 at 10:53
  • @Ivan that sounds complex, and fun! I'll give it a whirl if there's no other silver bullet answers – DanH Oct 15 '15 at 11:10
  • @Ivan it is the `makemigrations` phase that is important here, the migration phase only executes the migration files created during it. – aumo Oct 15 '15 at 11:15
  • @aumo 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. – DanH Oct 15 '15 at 12:16
  • Indeed that would be enough – aumo Oct 15 '15 at 13:08

3 Answers3

19

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.

Arctelix
  • 4,478
  • 3
  • 27
  • 38
5

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.

aumo
  • 5,344
  • 22
  • 25
  • Thanks for the suggestion, but this doesn't work. Seems that the model field choices is executes before `Command.handle()` – DanH Oct 15 '15 at 12:31
  • I tried it on a project of mine and it seemed to work, I'll investigate. – aumo Oct 15 '15 at 12:36
  • @DanH 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? – aumo Oct 15 '15 at 13:10
  • Sorry you're right, it is working for `makemigrations`. However the same concept does not seem to be working for `migrate` command. – DanH Oct 15 '15 at 13:31
  • 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? – DanH Oct 15 '15 at 13:34
  • 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. – aumo Oct 15 '15 at 13:35
  • For some reason I'm seeing success on both commands since your edit to use `migrations.MIGRATION_OPERATION_IN_PROGRESS`. Thanks! – DanH Oct 15 '15 at 13:51
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.

ajaest
  • 587
  • 4
  • 13