Kaydet (Commit) e565e133 authored tarafından Loic Bistuer's avatar Loic Bistuer Kaydeden (comit) Tim Graham

Fixed #21275 -- Fixed a serializer error when generating migrations for contrib.auth.

The migration serializer now looks for a deconstruct method on any object.
üst 28b70425
...@@ -362,7 +362,7 @@ class AbstractUser(AbstractBaseUser, PermissionsMixin): ...@@ -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 ...@@ -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 ...@@ -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): ...@@ -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): ...@@ -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,]+$') ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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:
......
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 ...@@ -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): ...@@ -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))
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment