Fixed #21275 -- Fixed a serializer error when generating migrations for contrib.auth.
The migration serializer now looks for a deconstruct method on any object.
This commit is contained in:
parent
28b70425af
commit
e565e1332d
|
@ -362,7 +362,7 @@ class AbstractUser(AbstractBaseUser, PermissionsMixin):
|
||||||
help_text=_('Required. 30 characters or fewer. Letters, numbers and '
|
help_text=_('Required. 30 characters or fewer. Letters, numbers and '
|
||||||
'@/./+/-/_ characters'),
|
'@/./+/-/_ characters'),
|
||||||
validators=[
|
validators=[
|
||||||
validators.RegexValidator(re.compile('^[\w.@+-]+$'), _('Enter a valid username.'), 'invalid')
|
validators.RegexValidator(r'^[\w.@+-]+$', _('Enter a valid username.'), 'invalid')
|
||||||
])
|
])
|
||||||
first_name = models.CharField(_('first name'), max_length=30, blank=True)
|
first_name = models.CharField(_('first name'), max_length=30, blank=True)
|
||||||
last_name = models.CharField(_('last name'), max_length=30, blank=True)
|
last_name = models.CharField(_('last name'), max_length=30, blank=True)
|
||||||
|
|
|
@ -3,6 +3,7 @@ from __future__ import unicode_literals
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.utils.deconstruct import deconstructible
|
||||||
from django.utils.translation import ugettext_lazy as _, ungettext_lazy
|
from django.utils.translation import ugettext_lazy as _, ungettext_lazy
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
from django.utils.ipv6 import is_valid_ipv6_address
|
from django.utils.ipv6 import is_valid_ipv6_address
|
||||||
|
@ -14,6 +15,7 @@ from django.utils.six.moves.urllib.parse import urlsplit, urlunsplit
|
||||||
EMPTY_VALUES = (None, '', [], (), {})
|
EMPTY_VALUES = (None, '', [], (), {})
|
||||||
|
|
||||||
|
|
||||||
|
@deconstructible
|
||||||
class RegexValidator(object):
|
class RegexValidator(object):
|
||||||
regex = ''
|
regex = ''
|
||||||
message = _('Enter a valid value.')
|
message = _('Enter a valid value.')
|
||||||
|
@ -39,6 +41,7 @@ class RegexValidator(object):
|
||||||
raise ValidationError(self.message, code=self.code)
|
raise ValidationError(self.message, code=self.code)
|
||||||
|
|
||||||
|
|
||||||
|
@deconstructible
|
||||||
class URLValidator(RegexValidator):
|
class URLValidator(RegexValidator):
|
||||||
regex = re.compile(
|
regex = re.compile(
|
||||||
r'^(?:http|ftp)s?://' # http:// or https://
|
r'^(?:http|ftp)s?://' # http:// or https://
|
||||||
|
@ -77,6 +80,7 @@ def validate_integer(value):
|
||||||
raise ValidationError(_('Enter a valid integer.'), code='invalid')
|
raise ValidationError(_('Enter a valid integer.'), code='invalid')
|
||||||
|
|
||||||
|
|
||||||
|
@deconstructible
|
||||||
class EmailValidator(object):
|
class EmailValidator(object):
|
||||||
message = _('Enter a valid email address.')
|
message = _('Enter a valid email address.')
|
||||||
code = 'invalid'
|
code = 'invalid'
|
||||||
|
@ -173,6 +177,7 @@ comma_separated_int_list_re = re.compile('^[\d,]+$')
|
||||||
validate_comma_separated_integer_list = RegexValidator(comma_separated_int_list_re, _('Enter only digits separated by commas.'), 'invalid')
|
validate_comma_separated_integer_list = RegexValidator(comma_separated_int_list_re, _('Enter only digits separated by commas.'), 'invalid')
|
||||||
|
|
||||||
|
|
||||||
|
@deconstructible
|
||||||
class BaseValidator(object):
|
class BaseValidator(object):
|
||||||
compare = lambda self, a, b: a is not b
|
compare = lambda self, a, b: a is not b
|
||||||
clean = lambda self, x: x
|
clean = lambda self, x: x
|
||||||
|
@ -189,18 +194,21 @@ class BaseValidator(object):
|
||||||
raise ValidationError(self.message, code=self.code, params=params)
|
raise ValidationError(self.message, code=self.code, params=params)
|
||||||
|
|
||||||
|
|
||||||
|
@deconstructible
|
||||||
class MaxValueValidator(BaseValidator):
|
class MaxValueValidator(BaseValidator):
|
||||||
compare = lambda self, a, b: a > b
|
compare = lambda self, a, b: a > b
|
||||||
message = _('Ensure this value is less than or equal to %(limit_value)s.')
|
message = _('Ensure this value is less than or equal to %(limit_value)s.')
|
||||||
code = 'max_value'
|
code = 'max_value'
|
||||||
|
|
||||||
|
|
||||||
|
@deconstructible
|
||||||
class MinValueValidator(BaseValidator):
|
class MinValueValidator(BaseValidator):
|
||||||
compare = lambda self, a, b: a < b
|
compare = lambda self, a, b: a < b
|
||||||
message = _('Ensure this value is greater than or equal to %(limit_value)s.')
|
message = _('Ensure this value is greater than or equal to %(limit_value)s.')
|
||||||
code = 'min_value'
|
code = 'min_value'
|
||||||
|
|
||||||
|
|
||||||
|
@deconstructible
|
||||||
class MinLengthValidator(BaseValidator):
|
class MinLengthValidator(BaseValidator):
|
||||||
compare = lambda self, a, b: a < b
|
compare = lambda self, a, b: a < b
|
||||||
clean = lambda self, x: len(x)
|
clean = lambda self, x: len(x)
|
||||||
|
@ -211,6 +219,7 @@ class MinLengthValidator(BaseValidator):
|
||||||
code = 'min_length'
|
code = 'min_length'
|
||||||
|
|
||||||
|
|
||||||
|
@deconstructible
|
||||||
class MaxLengthValidator(BaseValidator):
|
class MaxLengthValidator(BaseValidator):
|
||||||
compare = lambda self, a, b: a > b
|
compare = lambda self, a, b: a > b
|
||||||
clean = lambda self, x: len(x)
|
clean = lambda self, x: len(x)
|
||||||
|
|
|
@ -146,6 +146,9 @@ class MigrationWriter(object):
|
||||||
elif isinstance(value, models.Field):
|
elif isinstance(value, models.Field):
|
||||||
attr_name, path, args, kwargs = value.deconstruct()
|
attr_name, path, args, kwargs = value.deconstruct()
|
||||||
return cls.serialize_deconstructed(path, args, kwargs)
|
return cls.serialize_deconstructed(path, args, kwargs)
|
||||||
|
# Anything that knows how to deconstruct itself.
|
||||||
|
elif hasattr(value, 'deconstruct'):
|
||||||
|
return cls.serialize_deconstructed(*value.deconstruct())
|
||||||
# Functions
|
# Functions
|
||||||
elif isinstance(value, (types.FunctionType, types.BuiltinFunctionType)):
|
elif isinstance(value, (types.FunctionType, types.BuiltinFunctionType)):
|
||||||
# @classmethod?
|
# @classmethod?
|
||||||
|
@ -153,8 +156,6 @@ class MigrationWriter(object):
|
||||||
klass = value.__self__
|
klass = value.__self__
|
||||||
module = klass.__module__
|
module = klass.__module__
|
||||||
return "%s.%s.%s" % (module, klass.__name__, value.__name__), set(["import %s" % module])
|
return "%s.%s.%s" % (module, klass.__name__, value.__name__), set(["import %s" % module])
|
||||||
elif hasattr(value, 'deconstruct'):
|
|
||||||
return cls.serialize_deconstructed(*value.deconstruct())
|
|
||||||
elif value.__name__ == '<lambda>':
|
elif value.__name__ == '<lambda>':
|
||||||
raise ValueError("Cannot serialize function: lambda")
|
raise ValueError("Cannot serialize function: lambda")
|
||||||
elif value.__module__ is None:
|
elif value.__module__ is None:
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
def deconstructible(*args, **kwargs):
|
||||||
|
"""
|
||||||
|
Class decorator that allow the decorated class to be serialized
|
||||||
|
by the migrations subsystem.
|
||||||
|
|
||||||
|
Accepts an optional kwarg `path` to specify the import path.
|
||||||
|
"""
|
||||||
|
path = kwargs.pop('path', None)
|
||||||
|
|
||||||
|
def decorator(klass):
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
# We capture the arguments to make returning them trivial
|
||||||
|
obj = super(klass, cls).__new__(cls)
|
||||||
|
obj._constructor_args = (args, kwargs)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def deconstruct(obj):
|
||||||
|
"""
|
||||||
|
Returns a 3-tuple of class import path, positional arguments,
|
||||||
|
and keyword arguments.
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
path or '%s.%s' % (obj.__class__.__module__, obj.__class__.__name__),
|
||||||
|
obj._constructor_args[0],
|
||||||
|
obj._constructor_args[1],
|
||||||
|
)
|
||||||
|
|
||||||
|
klass.__new__ = staticmethod(__new__)
|
||||||
|
klass.deconstruct = deconstruct
|
||||||
|
|
||||||
|
return klass
|
||||||
|
|
||||||
|
if not args:
|
||||||
|
return decorator
|
||||||
|
return decorator(*args, **kwargs)
|
|
@ -6,11 +6,13 @@ import copy
|
||||||
import datetime
|
import datetime
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from django.core.validators import RegexValidator, EmailValidator
|
||||||
from django.db import models, migrations
|
from django.db import models, migrations
|
||||||
from django.db.migrations.writer import MigrationWriter
|
from django.db.migrations.writer import MigrationWriter
|
||||||
from django.db.models.loading import cache
|
from django.db.models.loading import cache
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
from django.utils.deconstruct import deconstructible
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
@ -77,6 +79,18 @@ class WriterTests(TestCase):
|
||||||
self.assertSerializedEqual(datetime.datetime.today)
|
self.assertSerializedEqual(datetime.datetime.today)
|
||||||
self.assertSerializedEqual(datetime.date.today())
|
self.assertSerializedEqual(datetime.date.today())
|
||||||
self.assertSerializedEqual(datetime.date.today)
|
self.assertSerializedEqual(datetime.date.today)
|
||||||
|
# Classes
|
||||||
|
validator = RegexValidator(message="hello")
|
||||||
|
string, imports = MigrationWriter.serialize(validator)
|
||||||
|
self.assertEqual(string, "django.core.validators.RegexValidator(message=%s)" % repr("hello"))
|
||||||
|
self.serialize_round_trip(validator)
|
||||||
|
validator = EmailValidator(message="hello") # Test with a subclass.
|
||||||
|
string, imports = MigrationWriter.serialize(validator)
|
||||||
|
self.assertEqual(string, "django.core.validators.EmailValidator(message=%s)" % repr("hello"))
|
||||||
|
self.serialize_round_trip(validator)
|
||||||
|
validator = deconstructible(path="custom.EmailValidator")(EmailValidator)(message="hello")
|
||||||
|
string, imports = MigrationWriter.serialize(validator)
|
||||||
|
self.assertEqual(string, "custom.EmailValidator(message=%s)" % repr("hello"))
|
||||||
# Django fields
|
# Django fields
|
||||||
self.assertSerializedFieldEqual(models.CharField(max_length=255))
|
self.assertSerializedFieldEqual(models.CharField(max_length=255))
|
||||||
self.assertSerializedFieldEqual(models.TextField(null=True, blank=True))
|
self.assertSerializedFieldEqual(models.TextField(null=True, blank=True))
|
||||||
|
|
Loading…
Reference in New Issue