Opened 7 years ago

Closed 7 years ago

Last modified 6 years ago

#28120 closed Bug (fixed)

CharField: if max_length=True (or False): makemigrations generates a migration and migrate fails

Reported by: Carles Pina Estany Owned by: Carles Pina Estany
Component: Database layer (models, ORM) Version: dev
Severity: Normal Keywords:
Cc: Дилян Палаузов Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

If a model has a field like:

    surname = models.CharField(max_length=True, null=True, blank=True)

python manage.py makemigrations writes a migration file but python manage.py migrate fails with an error:

carles@pinux:~/git/django-test/mysite$ python3 manage.py migrate
Operations to perform:
  Apply all migrations: admin, app01, auth, contenttypes, sessions
Running migrations:
  Applying app01.0002_auto_20170424_1440... OK
  Applying app01.0003_auto_20170424_1446...Traceback (most recent call last):
  File "/home/carles/git/django/django/db/backends/utils.py", line 60, in execute
    return self.cursor.execute(sql)
  File "/home/carles/git/django/django/db/backends/sqlite3/base.py", line 289, in execute
    return Database.Cursor.execute(self, query)
sqlite3.OperationalError: near "True": syntax error

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "manage.py", line 15, in <module>
    execute_from_command_line(sys.argv)
  File "/home/carles/git/django/django/core/management/__init__.py", line 354, in execute_from_command_line
    utility.execute()
  File "/home/carles/git/django/django/core/management/__init__.py", line 348, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/carles/git/django/django/core/management/base.py", line 280, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/home/carles/git/django/django/core/management/base.py", line 327, in execute
    output = self.handle(*args, **options)
  File "/home/carles/git/django/django/core/management/commands/migrate.py", line 200, in handle
    fake_initial=fake_initial,
  File "/home/carles/git/django/django/db/migrations/executor.py", line 113, in migrate
    state = self._migrate_all_forwards(state, plan, full_plan, fake=fake, fake_initial=fake_initial)
  File "/home/carles/git/django/django/db/migrations/executor.py", line 143, in _migrate_all_forwards
    state = self.apply_migration(state, migration, fake=fake, fake_initial=fake_initial)
  File "/home/carles/git/django/django/db/migrations/executor.py", line 240, in apply_migration
    state = migration.apply(state, schema_editor)
  File "/home/carles/git/django/django/db/migrations/migration.py", line 122, in apply
    operation.database_forwards(self.app_label, schema_editor, old_state, project_state)
  File "/home/carles/git/django/django/db/migrations/operations/fields.py", line 210, in database_forwards
    schema_editor.alter_field(from_model, from_field, to_field)
  File "/home/carles/git/django/django/db/backends/base/schema.py", line 490, in alter_field
    old_db_params, new_db_params, strict)
  File "/home/carles/git/django/django/db/backends/sqlite3/schema.py", line 258, in _alter_field
    self._remake_table(model, alter_field=(old_field, new_field))
  File "/home/carles/git/django/django/db/backends/sqlite3/schema.py", line 195, in _remake_table
    self.create_model(temp_model)
  File "/home/carles/git/django/django/db/backends/base/schema.py", line 290, in create_model
    self.execute(sql, params or None)
  File "/home/carles/git/django/django/db/backends/base/schema.py", line 109, in execute
    cursor.execute(sql, params)
  File "/home/carles/git/django/django/db/backends/utils.py", line 77, in execute
    return super().execute(sql, params)
  File "/home/carles/git/django/django/db/backends/utils.py", line 62, in execute
    return self.cursor.execute(sql, params)
  File "/home/carles/git/django/django/db/utils.py", line 90, in __exit__
    raise dj_exc_value.with_traceback(traceback)
  File "/home/carles/git/django/django/db/backends/utils.py", line 60, in execute
    return self.cursor.execute(sql)
  File "/home/carles/git/django/django/db/backends/sqlite3/base.py", line 289, in execute
    return Database.Cursor.execute(self, query)
django.db.utils.OperationalError: near "True": syntax error

Change History (10)

comment:1 by Carles Pina Estany, 7 years ago

Owner: changed from nobody to Carles Pina Estany
Status: newassigned

comment:2 by Simon Charette, 7 years ago

Triage Stage: UnreviewedReady for checkin
Version: 1.11master

comment:3 by Simon Charette <charettes@…>, 7 years ago

Resolution: fixed
Status: assignedclosed

In 9f2e8b5:

Fixed #28120 -- Checked that CharField.max_length is not a boolean.

comment:4 by Дилян Палаузов, 6 years ago

Cc: Дилян Палаузов added

What do max_length = True and max_length=False mean?

comment:5 by Tim Graham, 6 years ago

They are invalid values.

comment:6 by Simon Charette, 6 years ago

Дилян Палаузов this was required because issubclass(bool, int) is True.

Last edited 6 years ago by Simon Charette (previous) (diff)

comment:7 by Дилян Палаузов, 6 years ago

What about ensuring that max_length is an int, and not bool this way:

diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -1045,8 +1045,7 @@ class CharField(Field):
                     id='fields.E120',
                 )
             ]
-        elif (not isinstance(self.max_length, int) or isinstance(self.max_length, bool) or
-                self.max_length <= 0):
+        elif type(self.max_length) != int or self.max_length <= 0:
             return [
                 checks.Error(
                     "'max_length' must be a positive integer.",

There are more places in the code that check for not isinstance(..., int).

comment:8 by Simon Charette, 6 years ago

While uncommon that would prevent user defined int subclasses to work appropriately.

comment:9 by Дилян Палаузов, 6 years ago

Why do you want to allow subclasses of int to be passed as max_length, but bool, which is a subclass of int, shall be excluded?

comment:10 by Simon Charette, 6 years ago

This is all getting into bike shedding territory but I assume that if a user is advanced enough to subclass int and pass it to max_length they know what they are doing.

On the opposite passing a max_length=bool is highly likely to be a beginner mistake. The fact bool subclasses int is really just a relic of the past which I assume most Python users are not aware of. In my case at least I wasn't until tackling this ticket.

Last edited 6 years ago by Simon Charette (previous) (diff)
Note: See TracTickets for help on using tickets.
Back to Top