pythondjangodjango-models

Cannot override custom field __init__ and make migrations, throws error


I was attempting to create a custom model field and use it in a model as the docs demonstrate for BetterCharField shown here (you might have to scroll a bit down): https://docs.djangoproject.com/en/3.0/howto/custom-model-fields/#custom-database-types

My code in my models.py is as follows, almost verbatim from the docs example:

 from django.db import models

 # Create your models here.

 class BetterCharField(models.Field):
     def __init__(self, max_length, *args, **kwargs):
         self.max_length = max_length
         super().__init__(*args, **kwargs)

     def db_type(self, connection):
         return "char({})".format(self.max_length)

 class MyModel(models.Model):
     my_field = BetterCharField(25)

However, when trying to run python manage.py makemigrations with this models.py file, I get the following error every time:

Traceback (most recent call last):
  File "/Users/dark_knight/test/venv/lib/python3.7/site-packages/django/db/migrations/state.py", line 413, in from_model
    fields.append((name, field.clone()))
  File "/Users/dark_knight/test/venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py", line 512, in clone
    return self.__class__(*args, **kwargs)
TypeError: __init__() missing 1 required positional argument: 'max_length'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "manage.py", line 21, in <module>
    main()
  File "manage.py", line 17, in main
    execute_from_command_line(sys.argv)
  File "/Users/dark_knight/test/venv/lib/python3.7/site-packages/django/core/management/__init__.py", line 401, in execute_from_command_line
    utility.execute()
  File "/Users/dark_knight/test/venv/lib/python3.7/site-packages/django/core/management/__init__.py", line 395, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/Users/dark_knight/test/venv/lib/python3.7/site-packages/django/core/management/base.py", line 328, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/Users/dark_knight/test/venv/lib/python3.7/site-packages/django/core/management/base.py", line 369, in execute
    output = self.handle(*args, **options)
  File "/Users/dark_knight/test/venv/lib/python3.7/site-packages/django/core/management/base.py", line 83, in wrapped
    res = handle_func(*args, **kwargs)
  File "/Users/dark_knight/test/venv/lib/python3.7/site-packages/django/core/management/commands/makemigrations.py", line 142, in handle
    ProjectState.from_apps(apps),
  File "/Users/dark_knight/test/venv/lib/python3.7/site-packages/django/db/migrations/state.py", line 221, in from_apps
    model_state = ModelState.from_model(model)
  File "/Users/dark_knight/test/venv/lib/python3.7/site-packages/django/db/migrations/state.py", line 418, in from_model
    e,
TypeError: Couldn't reconstruct field my_field on polls.MyModel: __init__() missing 1 required positional argument: 'max_length'

Why is this failing? I had yet to create any migrations at all for this project, let alone the field and model given here. I have my positional argument as 25 given to BetterCharField when I initialize it in my models.py file. What am I missing? I had a much more complex use case, but I built this example in a brand new test django project/app straight from the documentation, and it still fails.


Solution

  • This is documented behaviour:

    You can’t modify the number of positional arguments in an already migrated custom field without raising a TypeError. The old migration will call the modified __init__ method with the old signature. So if you need a new argument, please create a keyword argument and add something like assert 'argument_name' in kwargs in the constructor.