Opened 9 years ago
Closed 9 years ago
#25280 closed Bug (fixed)
Infinite migrations created when using regex based validators
Reported by: | Sayid Munawar | Owned by: | Markus Holtermann |
---|---|---|---|
Component: | Migrations | Version: | dev |
Severity: | Release blocker | Keywords: | migrations, validators, slug |
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
Using django.core.validators.validate_slug in a model will make the migration files on that model increment infinitely, allthough no changes has made in the model.
from django.core.validators import validate_slug class Organization(models.Model): slug = models.SlugField(unique=True, validators=[validate_slug])
manage.py makemigrations
Migrations for 'organizations': 0009_auto_20150815_2151.py: - Alter field slug on organization Migrations for 'organizations': 0010_auto_20150815_2151.py: - Alter field slug on organization Migrations for 'organizations': 0011_auto_20150815_2158.py: - Alter field slug on organization Migrations for 'organizations': 0012_auto_20150815_2158.py: - Alter field slug on organization
A quick fix is to workaround this issue is just copy paste the code in django.core.validators:
from django.core.validators import RegexValidator orgslug_validator = [ RegexValidator(regex=r'^[-a-zA-Z0-9_]+\Z', message=_("Enter a valid 'slug' consisting of letters, " "numbers, underscores or hyphens."), code='invalid_slug') ] class Organization(models.Model): slug = models.SlugField(unique=True, validators=orgslug_validator)
Attachments (1)
Change History (12)
comment:1 by , 9 years ago
Type: | Uncategorized → Bug |
---|
comment:2 by , 9 years ago
Severity: | Normal → Release blocker |
---|---|
Summary: | django 1.9 using validators.validate_slug will recreate migrations over and over → Infinite migrations created when using function based validators |
Triage Stage: | Unreviewed → Accepted |
comment:3 by , 9 years ago
Owner: | changed from | to
---|---|
Status: | new → assigned |
comment:4 by , 9 years ago
Before the commit that introduced the regression. deep_deconstruct()
returns:
[<django.core.validators.RegexValidator object at 0x7fb6ca4cd780>]
Now it returns this for historical models:
[('django.core.validators.RegexValidator', [re.compile('^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\Z'), 'Enter a valid IPv4 address.', 'invalid']
... or this when processing the actual models:
('django.core.validators.RegexValidator', [re.compile('^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\Z'), <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f3a97c44b00>, 'invalid']
The lazy error message is not resolved, so it's treated as unequal to the string in the historical model.
by , 9 years ago
Attachment: | 25280-test.diff added |
---|
comment:5 by , 9 years ago
I attached my attempt at a regression test, unfortunately it passes so it doesn't seem to capture the issue.
comment:6 by , 9 years ago
Owner: | changed from | to
---|
comment:7 by , 9 years ago
Has patch: | set |
---|
OK, I this is just an idea, and the current tests on master work, but I don't have a test that doesn't work on master yet ;)
-
django/db/migrations/autodetector.py
diff --git a/django/db/migrations/autodetector.py b/django/db/migrations/autodetector.py index 41830f0..1509fa7 100644
a b from django.db.migrations.migration import Migration 11 11 from django.db.migrations.operations.models import AlterModelOptions 12 12 from django.db.migrations.optimizer import MigrationOptimizer 13 13 from django.db.migrations.questioner import MigrationQuestioner 14 from django.db.migrations.utils import COMPILED_REGEX_TYPE, RegexObject 14 15 from django.utils import six 15 16 16 17 from .topological_sort import stable_topological_sort … … class MigrationAutodetector(object): 62 63 key: self.deep_deconstruct(value) 63 64 for key, value in obj.items() 64 65 } 66 elif isinstance(obj, COMPILED_REGEX_TYPE): 67 return RegexObject(obj.pattern, obj.flags) 65 68 elif isinstance(obj, type): 66 69 # If this is a type that implements 'deconstruct' as an instance method, 67 70 # avoid treating this as being deconstructible itself - see #22951 -
new file django/db/migrations/utils.py
diff --git a/django/db/migrations/utils.py b/django/db/migrations/utils.py new file mode 100644 index 0000000..412db15
- + 1 import re 2 3 COMPILED_REGEX_TYPE = type(re.compile('')) 4 5 6 class RegexObject(object): 7 def __init__(self, pattern, flags): 8 self.pattern = pattern 9 self.flags = flags 10 11 def __eq__(self, other): 12 return self.pattern == other.pattern and self.flags == other.flags -
django/db/migrations/writer.py
diff --git a/django/db/migrations/writer.py b/django/db/migrations/writer.py index 44a4017..fb6a162 100644
a b from django.apps import apps 14 14 from django.db import migrations, models 15 15 from django.db.migrations.loader import MigrationLoader 16 16 from django.db.migrations.operations.base import Operation 17 from django.db.migrations.utils import COMPILED_REGEX_TYPE, RegexObject 17 18 from django.utils import datetime_safe, six 18 19 from django.utils._os import upath 19 20 from django.utils.encoding import force_text … … from django.utils.module_loading import module_dir 23 24 from django.utils.timezone import utc 24 25 from django.utils.version import get_docs_version 25 26 26 COMPILED_REGEX_TYPE = type(re.compile(''))27 28 27 29 28 class SettingsReference(str): 30 29 """ … … class MigrationWriter(object): 506 505 format = "(%s)" if len(strings) != 1 else "(%s,)" 507 506 return format % (", ".join(strings)), imports 508 507 # Compiled regex 509 elif isinstance(value, COMPILED_REGEX_TYPE):508 elif isinstance(value, (COMPILED_REGEX_TYPE, RegexObject)): 510 509 imports = {"import re"} 511 510 regex_pattern, pattern_imports = cls.serialize(value.pattern) 512 511 regex_flags, flag_imports = cls.serialize(value.flags)
comment:8 by , 9 years ago
using MarkusH patch, i can confirm that my case is resolved
$ ./manage.py makemigrations No changes detected $ ./manage.py makemigrations No changes detected $ ./manage.py makemigrations No changes detected
comment:9 by , 9 years ago
Needs documentation: | set |
---|
Here's a PR including tests: https://github.com/django/django/pull/5173
Before backporting it to 1.8 I'd like to get it into 1.9 alpha and see if it actually works in the field as I don't really like the approach.
comment:10 by , 9 years ago
Needs documentation: | unset |
---|---|
Summary: | Infinite migrations created when using function based validators → Infinite migrations created when using regex based validators |
Triage Stage: | Accepted → Ready for checkin |
The issue doesn't affect 1.8.
Bisected to ff8a02ae0b33ee52050e22909d432a3aa060d683.