Cerin
Cerin

Reputation: 64810

Maximum recursion depth trying to create Django migration

When I try to generate an initial migration for my Django app with:

python manage.py makemigrations myapp

I get a traceback like:

  File "/myproject/.env/local/lib/python2.7/site-packages/localflavor/us/models.py", line 21, in deconstruct
    name, path, args, kwargs = super(USStateField, self).deconstruct()
  File "/myproject/.env/local/lib/python2.7/site-packages/localflavor/us/models.py", line 21, in deconstruct
    name, path, args, kwargs = super(USStateField, self).deconstruct()
  File "/myproject/.env/local/lib/python2.7/site-packages/localflavor/us/models.py", line 21, in deconstruct
    name, path, args, kwargs = super(USStateField, self).deconstruct()
  File "/myproject/.env/local/lib/python2.7/site-packages/localflavor/us/models.py", line 21, in deconstruct
    name, path, args, kwargs = super(USStateField, self).deconstruct()
RuntimeError: maximum recursion depth exceeded while calling a Python object

The only odd thing I'm doing that's probably the cause is I'm trying to monkeypatch the USStateField from the localflavor package. Specifically, I need to set custom state choices from the field's state list, so I'm doing:

from localflavor.us import models as us_models

_USStateField = us_models.USStateField

class USStateField(_USStateField):

    description = _("U.S. state (two uppercase letters)")

    def __init__(self, *args, **kwargs):
        kwargs['choices'] = MY_STATE_CHOICES
        kwargs['max_length'] = 2
        super(_USStateField, self).__init__(*args, **kwargs)

us_models.USStateField = USStateField

This worked perfectly in Django 1.7, but now gives me this error in Django 1.10. Is there a better way to do this?

Upvotes: 0

Views: 889

Answers (1)

dahrens
dahrens

Reputation: 3959

I found little pieces of information on the topic "monkey patching django models and migrations" on github - but it does not look very promising.

I'm afraid that the behavior of the django migrations makes models not really compatible with the monkey patching approach. You encounter several side effects in different places (How to add custom migrations to external Django apps) when you need to deal with them.

If you rely directly on localflavor - what stops you from using inheritance instead of monkey patching the foreign dependencies? In the case of the custom field from localflavor.us you might just add fields.py to your custom app or to the project and use you're code there like that:

from localflavor.us import models as us_models

class USStateField(us_models.USStateField):

    description = _("U.S. state (two uppercase letters)")

    def __init__(self, *args, **kwargs):
        kwargs['choices'] = MY_STATE_CHOICES
        kwargs['max_length'] = 2
        super(USStateField, self).__init__(*args, **kwargs)

Afterwards you'll just use that one in your models from yourapp.fields import USStateField.

If you have other third party apps which rely on localfavor.us which makes this approach quite complicated, as you'll need to rewrite a bunch of models just for this little patch, I currently see two ways:

  • fork localflavor and provide it in requirements as direct git URL or via local pypi repository.
  • make a contribution to localflavor itself and make it possible to pass the options there - which avoids the monkey patching for this specific case.

On top it might be worth it to start a discussion on the django mailing list regarding monkey patching and migrations - but in the past opinions on the topic were not that positive.

UPDATE: without having a closer look at localflavor, but in this case it might by enough to just pass choices=MY_STATE_CHOICES to the field when using it - if you directly rely on the field. nevertheless the above addresses monkey patching and django migrations in general.

UPDATE2: I've talked to a co worker today who told me that apps.py might be a place where monkey patching works. The code there is executed before anything else happens - maybe you can savely monkey patch there. Would be interesting to know if it works.

UPDATE3: I recently had some time and as I was quite curious I tried the approach with apps.py myself. My co worker was completely right. When you move the monkey patching code over into apps.py migrations run without the error you descdribed.

This is what my apps.py looks like:

   from django.apps import AppConfig
   from localflavor.us import models as us_models

   _USStateField = us_models.USStateField

   MY_STATE_CHOICES = [('foo', 'BAR',)]

   class USStateField(_USStateField):

       description = "U.S. state (two uppercase letters)"

       def __init__(self, *args, **kwargs):
           kwargs['choices'] = MY_STATE_CHOICES
           kwargs['max_length'] = 2
           super(_USStateField, self).__init__(*args, **kwargs)

   us_models.USStateField = USStateField

   class FoobarConfig(AppConfig):
       name = 'foobar'

Upvotes: 1

Related Questions