Opened 10 years ago
Last modified 8 years ago
#24182 new New feature
Document or improve limitations for doing queries in field defaults
Reported by: | arveitch | Owned by: | nobody |
---|---|---|---|
Component: | Migrations | Version: | dev |
Severity: | Normal | Keywords: | |
Cc: | Triage Stage: | Accepted | |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description (last modified by )
I've found a bug on all of 1.7; I've tested up to 1.7.3. The code works fine on 1.6 with syncdb. I think this is a reasonably common design pattern.
Here's the models.py:
import random from django.db import models def generateCode(prefix=''): while True: code = ''.join( # Omit I, O, 1 and 0 as they can cause confusion random.choice('ABCDEFGHJKLMNPQRSTUVWXYZ23456789') for i in range(7) ) code = prefix + code if not PromotionalCode.objects.filter(code=code).exists(): break return code class PromotionalCode(models.Model): code = models.CharField( default=generateCode, db_index=True, max_length=12, unique=True ) value_amount = models.DecimalField( default=0, max_digits=7, decimal_places=2 )
./manage.py migrate gives this traceback:
Applying example.0001_initial...Traceback (most recent call last): File "./manage.py", line 10, in <module> execute_from_command_line(sys.argv) File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/django/core/management/__init__.py", line 385, in execute_from_command_line utility.execute() File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/django/core/management/__init__.py", line 377, in execute self.fetch_command(subcommand).run_from_argv(self.argv) File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/django/core/management/base.py", line 288, in run_from_argv self.execute(*args, **options.__dict__) File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/django/core/management/base.py", line 338, in execute output = self.handle(*args, **options) File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/django/core/management/commands/migrate.py", line 161, in handle executor.migrate(targets, plan, fake=options.get("fake", False)) File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/django/db/migrations/executor.py", line 68, in migrate self.apply_migration(migration, fake=fake) File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/django/db/migrations/executor.py", line 102, in apply_migration migration.apply(project_state, schema_editor) File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/django/db/migrations/migration.py", line 108, in apply operation.database_forwards(self.app_label, schema_editor, project_state, new_state) File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/django/db/migrations/operations/models.py", line 36, in database_forwards schema_editor.create_model(model) File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/django/db/backends/schema.py", line 213, in create_model definition, extra_params = self.column_sql(model, field) File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/django/db/backends/schema.py", line 125, in column_sql default_value = self.effective_default(field) File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/django/db/backends/schema.py", line 175, in effective_default default = field.get_default() File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/django/db/models/fields/__init__.py", line 719, in get_default return self.default() File "/Users/andrew/Sites/testing/example/models.py", line 12, in generateCode if not PromotionalCode.objects.filter(code=code).exists(): File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/django/db/models/query.py", line 606, in exists return self.query.has_results(using=self.db) File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/django/db/models/sql/query.py", line 457, in has_results return compiler.has_results() File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 757, in has_results return bool(self.execute_sql(SINGLE)) File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 786, in execute_sql cursor.execute(sql, params) File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/django/db/backends/utils.py", line 81, in execute return super(CursorDebugWrapper, self).execute(sql, params) File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/django/db/backends/utils.py", line 65, in execute return self.cursor.execute(sql, params) File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/django/db/utils.py", line 94, in __exit__ six.reraise(dj_exc_type, dj_exc_value, traceback) File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/django/db/backends/utils.py", line 65, in execute return self.cursor.execute(sql, params) File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/django/db/backends/sqlite3/base.py", line 485, in execute return Database.Cursor.execute(self, query, params) django.db.utils.OperationalError: no such table: example_promotionalcode
If I comment out the PromotionalCode.objects.filter(code=code).exists() test then it works fine on 1.7
Change History (8)
comment:1 by , 10 years ago
Description: | modified (diff) |
---|
comment:2 by , 10 years ago
comment:3 by , 10 years ago
I think that's right. This is a regression, but I think it was an unavoidable one.
If this had been discovered before release, it certainly would have gone into the backwards-incompatible section of the release notes for 1.7. Does it make any sense to add it there now?
I also think we may need to consider whether there is any clean way, via supported API, to detect the table-doesn't-exist yet case in a default function like this one. Catching the exception is too broad, and asking people to do their own raw SQL query to find out is quite ugly. Since this is a backwards-incompatible regression I think if we can't fix it we would ideally provide a clean, documented new alternative technique.
comment:4 by , 10 years ago
Thanks - I do then have a solution for my particular case which is to query the PostgreSQL information schema using raw SQL to see if the table has been created yet. My application only runs on Postgres so that's fine for me.
Agree that a database agnostic utility to check if a table exists would be much nicer as a general solution.
comment:5 by , 10 years ago
You could use an undocumented (internal) API from the introspection to see if the database table exists:
In [1]: from django.db import connection In [2]: connection.introspection.get_table_list(connection.cursor()) Out[2]: ['django_migrations', 'app_a_a1', 'app_a_a2']
Apart from that, your migration(s) will suffer from #23932, regardless of whether you access the model or not.
- Add the field with
unique=False, null=True, default=NOT_PROVIDED
- Propagate existing rows
- Alter field to
unique=True, null=False, default=generateCode
I don't think this, backwards incompatible, behavior explicitly needs to be documented. Instead #23932 should be. That said, I'm inclined to close this ticket as duplicate.
comment:6 by , 10 years ago
Summary: | Bug with functional default and migrations → Document or improve limitations for doing queries in field defaults |
---|---|
Triage Stage: | Unreviewed → Accepted |
Type: | Bug → New feature |
Version: | 1.7 → master |
It doesn't seem to me that the docs we added for #23932 really addressed this.
follow-up: 8 comment:7 by , 10 years ago
I've just tried this code in Django 1.8.2 and it now works as expected.
Looks like this bug has been solved sometime between 1.7.3 and 1.8.2
comment:8 by , 8 years ago
Replying to arveitch:
I've just tried this code in Django 1.8.2 and it now works as expected.
Looks like this bug has been solved sometime between 1.7.3 and 1.8.2
I confirm this is still an issue on master.
Migrations call the default function so that if there are any existing rows, they can be populated with a value. I think you will need to adjust the
generateCode()
function to return a value in the case that thePromotionalCode
table doesn't exist. I'll leave this open for a second opinion in case I'm missing something.