mirror of
https://github.com/django/django.git
synced 2025-11-07 07:15:35 +00:00
Fixed #3011 -- Added swappable auth.User models.
Thanks to the many people that contributed to the development and review of this patch, including (but not limited to) Jacob Kaplan-Moss, Anssi Kääriäinen, Ramiro Morales, Preston Holmes, Josh Ourisman, Thomas Sutton, and Roger Barnes, as well as the many, many people who have contributed to the design discussion around this ticket over many years. Squashed commit of the following: commitd84749a0f0Merge:531e7717c11b1aAuthor: Russell Keith-Magee <russell@keith-magee.com> Date: Wed Sep 26 18:37:04 2012 +0800 Merge remote-tracking branch 'django/master' into t3011 commit531e7715daMerge:29d1abb1f84b04Author: Russell Keith-Magee <russell@keith-magee.com> Date: Wed Sep 26 07:09:23 2012 +0800 Merged recent trunk changes. commit29d1abbe35Merge:8a527dd54c81a1Author: Russell Keith-Magee <russell@keith-magee.com> Date: Mon Sep 24 07:49:46 2012 +0800 Merge remote-tracking branch 'django/master' into t3011 commit8a527dda13Author: Russell Keith-Magee <russell@keith-magee.com> Date: Mon Sep 24 07:48:05 2012 +0800 Ensure sequences are reset correctly in the presence of swapped models. commite2b6e22f29Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 23 17:53:05 2012 +0800 Modifications to the handling and docs for auth forms. commit98aba856b5Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 23 15:28:57 2012 +0800 Improved error handling and docs for get_user_model() commit0229209c84Merge:6494bf98599f64Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 23 14:50:11 2012 +0800 Merged recent Django trunk changes. commit6494bf91f2Author: Russell Keith-Magee <russell@keith-magee.com> Date: Mon Sep 17 21:38:44 2012 +0800 Improved validation of swappable model settings. commit5a04cde342Author: Russell Keith-Magee <russell@keith-magee.com> Date: Mon Sep 17 07:15:14 2012 +0800 Removed some unused imports. commitffd535e413Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 16 20:31:28 2012 +0800 Corrected attribute access on for get_by_natural_key commit913e1ac84cAuthor: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 16 20:12:34 2012 +0800 Added test for proxy model safeguards on swappable models. commit280bf19e94Merge:dbb3900935a863Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 16 18:16:49 2012 +0800 Merge remote-tracking branch 'django/master' into t3011 commitdbb3900775Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 16 18:09:27 2012 +0800 Fixes for Python 3 compatibility. commitdfd72131d8Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 16 15:54:30 2012 +0800 Added protection against proxying swapped models. commitabcb027190Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 16 15:11:10 2012 +0800 Cleanup and documentation of AbstractUser base class. commita9491a8776Merge:fd8bb4e08bcb4aAuthor: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 16 14:46:49 2012 +0800 Merge commit '08bcb4aec1ed154cefc631b8510ee13e9af0c19d' into t3011 commitfd8bb4e3e4Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 16 14:20:14 2012 +0800 Documentation improvements coming from community review. commitb550a6d06dAuthor: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 16 13:52:47 2012 +0800 Refactored skipIfCustomUser into the contrib.auth tests. commit52a02f1110Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 16 13:46:10 2012 +0800 Refactored common 'get' pattern into manager method. commitb441a6bbc7Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 16 13:41:33 2012 +0800 Added note about backwards incompatible change to admin login messages. commit08bcb4aec1Author: Anssi Kääriäinen <akaariai@gmail.com> Date: Sat Sep 15 18:30:33 2012 +0300 Splitted User to AbstractUser and User commitd9f5e5addbAuthor: Anssi Kääriäinen <akaariai@gmail.com> Date: Sat Sep 15 18:30:02 2012 +0300 Reworked REQUIRED_FIELDS + create_user() interaction commit579f152e4aMerge:918497293e6733Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sat Sep 15 20:18:37 2012 +0800 Merge remote-tracking branch 'django/master' into t3011 commit918497218cAuthor: Russell Keith-Magee <russell@keith-magee.com> Date: Sat Sep 15 20:18:19 2012 +0800 Deprecate AUTH_PROFILE_MODULE and get_profile(). commit334cdfc1bbAuthor: Russell Keith-Magee <russell@keith-magee.com> Date: Sat Sep 15 20:00:12 2012 +0800 Added release notes for new swappable User feature. commit5d7bb22e8dAuthor: Russell Keith-Magee <russell@keith-magee.com> Date: Sat Sep 15 19:59:49 2012 +0800 Ensure swapped models can't be queried. commit57ac6e3d32Merge:f2ec915abfba3bAuthor: Russell Keith-Magee <russell@keith-magee.com> Date: Sat Sep 15 14:31:54 2012 +0800 Merge remote-tracking branch 'django/master' into t3011 commitf2ec915b20Merge:19526565e99a3dAuthor: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 9 08:29:51 2012 +0800 Merge remote-tracking branch 'django/master' into t3011 commit19526563b5Merge:2c5e833c4aa26aAuthor: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 9 08:22:26 2012 +0800 Merge recent changes from master. commit2c5e833a30Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 9 07:53:46 2012 +0800 Corrected admin_views tests following removal of the email fallback on admin logins. commit20d1892491Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 9 01:00:37 2012 +0800 Added conditional skips for all tests dependent on the default User model commit40ea8b8882Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sat Sep 8 23:47:02 2012 +0800 Added documentation for REQUIRED_FIELDS in custom auth. commite6aaf65970Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sat Sep 8 23:20:02 2012 +0800 Added first draft of custom User docs. Thanks to Greg Turner for the initial text. commit75118bd242Author: Thomas Sutton <me@thomas-sutton.id.au> Date: Mon Aug 20 11:17:26 2012 +0800 Admin app should not allow username discovery The admin app login form should not allow users to discover the username associated with an email address. commitd088b3af58Author: Thomas Sutton <me@thomas-sutton.id.au> Date: Mon Aug 20 10:32:13 2012 +0800 Admin app login form should use swapped user model commit7e82e83d67Merge:e29c01039aa890Author: Russell Keith-Magee <russell@keith-magee.com> Date: Fri Sep 7 23:45:03 2012 +0800 Merged master changes. commite29c010bebMerge:8e3fd7030bdf22Author: Russell Keith-Magee <russell@keith-magee.com> Date: Mon Aug 20 13:12:57 2012 +0800 Merge remote-tracking branch 'django/master' into t3011 commit8e3fd703d0Merge:507bb5026e0ba0Author: Russell Keith-Magee <russell@keith-magee.com> Date: Mon Aug 20 13:09:09 2012 +0800 Merged recent changes from trunk. commit507bb50a92Author: Russell Keith-Magee <russell@keith-magee.com> Date: Mon Jun 4 20:41:37 2012 +0800 Modified auth app so that login with alternate auth app is possible. commitdabe362836Author: Russell Keith-Magee <russell@keith-magee.com> Date: Mon Jun 4 20:10:51 2012 +0800 Modified auth management commands to handle custom user definitions. commit7cc0baf89dAuthor: Russell Keith-Magee <russell@keith-magee.com> Date: Mon Jun 4 14:17:28 2012 +0800 Added model Meta option for swappable models, and made auth.User a swappable model
This commit is contained in:
@@ -3,42 +3,54 @@ Global Django exception and warning classes.
|
||||
"""
|
||||
from functools import reduce
|
||||
|
||||
|
||||
class DjangoRuntimeWarning(RuntimeWarning):
|
||||
pass
|
||||
|
||||
|
||||
class ObjectDoesNotExist(Exception):
|
||||
"The requested object does not exist"
|
||||
silent_variable_failure = True
|
||||
|
||||
|
||||
class MultipleObjectsReturned(Exception):
|
||||
"The query returned multiple objects when only one was expected."
|
||||
pass
|
||||
|
||||
|
||||
class SuspiciousOperation(Exception):
|
||||
"The user did something suspicious"
|
||||
pass
|
||||
|
||||
|
||||
class PermissionDenied(Exception):
|
||||
"The user did not have permission to do that"
|
||||
pass
|
||||
|
||||
|
||||
class ViewDoesNotExist(Exception):
|
||||
"The requested view does not exist"
|
||||
pass
|
||||
|
||||
|
||||
class MiddlewareNotUsed(Exception):
|
||||
"This middleware is not used in this server configuration"
|
||||
pass
|
||||
|
||||
|
||||
class ImproperlyConfigured(Exception):
|
||||
"Django is somehow improperly configured"
|
||||
pass
|
||||
|
||||
|
||||
class FieldError(Exception):
|
||||
"""Some kind of problem with a model field."""
|
||||
pass
|
||||
|
||||
|
||||
NON_FIELD_ERRORS = '__all__'
|
||||
|
||||
|
||||
class ValidationError(Exception):
|
||||
"""An error while validating data."""
|
||||
def __init__(self, message, code=None, params=None):
|
||||
@@ -85,4 +97,3 @@ class ValidationError(Exception):
|
||||
else:
|
||||
error_dict[NON_FIELD_ERRORS] = self.messages
|
||||
return error_dict
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ from django.core.management.base import AppCommand
|
||||
from django.core.management.sql import sql_all
|
||||
from django.db import connections, DEFAULT_DB_ALIAS
|
||||
|
||||
|
||||
class Command(AppCommand):
|
||||
help = "Prints the CREATE TABLE, custom SQL and CREATE INDEX SQL statements for the given model module name(s)."
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ class Command(NoArgsCommand):
|
||||
if router.allow_syncdb(db, m)])
|
||||
for app in models.get_apps()
|
||||
]
|
||||
|
||||
def model_installed(model):
|
||||
opts = model._meta
|
||||
converter = connection.introspection.table_name_converter
|
||||
@@ -101,7 +102,6 @@ class Command(NoArgsCommand):
|
||||
cursor.execute(statement)
|
||||
tables.append(connection.introspection.table_name_converter(model._meta.db_table))
|
||||
|
||||
|
||||
transaction.commit_unless_managed(using=db)
|
||||
|
||||
# Send the post_syncdb signal, so individual apps can do whatever they need
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from django.core.management.base import NoArgsCommand
|
||||
|
||||
|
||||
class Command(NoArgsCommand):
|
||||
help = "Validates all installed models."
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ from django.core.management.base import CommandError
|
||||
from django.db import models
|
||||
from django.db.models import get_models
|
||||
|
||||
|
||||
def sql_create(app, style, connection):
|
||||
"Returns a list of the CREATE TABLE SQL statements for the given app."
|
||||
|
||||
@@ -55,6 +56,7 @@ def sql_create(app, style, connection):
|
||||
|
||||
return final_output
|
||||
|
||||
|
||||
def sql_delete(app, style, connection):
|
||||
"Returns a list of the DROP TABLE SQL statements for the given app."
|
||||
|
||||
@@ -83,7 +85,7 @@ def sql_delete(app, style, connection):
|
||||
opts = model._meta
|
||||
for f in opts.local_fields:
|
||||
if f.rel and f.rel.to not in to_delete:
|
||||
references_to_delete.setdefault(f.rel.to, []).append( (model, f) )
|
||||
references_to_delete.setdefault(f.rel.to, []).append((model, f))
|
||||
|
||||
to_delete.add(model)
|
||||
|
||||
@@ -97,7 +99,8 @@ def sql_delete(app, style, connection):
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
return output[::-1] # Reverse it, to deal with table dependencies.
|
||||
return output[::-1] # Reverse it, to deal with table dependencies.
|
||||
|
||||
|
||||
def sql_flush(style, connection, only_django=False, reset_sequences=True):
|
||||
"""
|
||||
@@ -114,6 +117,7 @@ def sql_flush(style, connection, only_django=False, reset_sequences=True):
|
||||
statements = connection.ops.sql_flush(style, tables, seqs)
|
||||
return statements
|
||||
|
||||
|
||||
def sql_custom(app, style, connection):
|
||||
"Returns a list of the custom table modifying SQL statements for the given app."
|
||||
output = []
|
||||
@@ -125,6 +129,7 @@ def sql_custom(app, style, connection):
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def sql_indexes(app, style, connection):
|
||||
"Returns a list of the CREATE INDEX SQL statements for all models in the given app."
|
||||
output = []
|
||||
@@ -132,10 +137,12 @@ def sql_indexes(app, style, connection):
|
||||
output.extend(connection.creation.sql_indexes_for_model(model, style))
|
||||
return output
|
||||
|
||||
|
||||
def sql_all(app, style, connection):
|
||||
"Returns a list of CREATE TABLE SQL, initial-data inserts, and CREATE INDEX SQL for the given module."
|
||||
return sql_create(app, style, connection) + sql_custom(app, style, connection) + sql_indexes(app, style, connection)
|
||||
|
||||
|
||||
def _split_statements(content):
|
||||
comment_re = re.compile(r"^((?:'[^']*'|[^'])*?)--.*$")
|
||||
statements = []
|
||||
@@ -150,6 +157,7 @@ def _split_statements(content):
|
||||
statement = ""
|
||||
return statements
|
||||
|
||||
|
||||
def custom_sql_for_model(model, style, connection):
|
||||
opts = model._meta
|
||||
app_dir = os.path.normpath(os.path.join(os.path.dirname(models.get_app(model._meta.app_label).__file__), 'sql'))
|
||||
|
||||
@@ -5,6 +5,7 @@ from django.utils.encoding import force_str
|
||||
from django.utils.itercompat import is_iterable
|
||||
from django.utils import six
|
||||
|
||||
|
||||
class ModelErrorCollection:
|
||||
def __init__(self, outfile=sys.stdout):
|
||||
self.errors = []
|
||||
@@ -15,6 +16,7 @@ class ModelErrorCollection:
|
||||
self.errors.append((context, error))
|
||||
self.outfile.write(self.style.ERROR(force_str("%s: %s\n" % (context, error))))
|
||||
|
||||
|
||||
def get_validation_errors(outfile, app=None):
|
||||
"""
|
||||
Validates all models that are part of the specified app. If no app name is provided,
|
||||
@@ -56,7 +58,7 @@ def get_validation_errors(outfile, app=None):
|
||||
e.add(opts, '"%s": CharFields require a "max_length" attribute that is a positive integer.' % f.name)
|
||||
if isinstance(f, models.DecimalField):
|
||||
decimalp_ok, mdigits_ok = False, False
|
||||
decimalp_msg ='"%s": DecimalFields require a "decimal_places" attribute that is a non-negative integer.'
|
||||
decimalp_msg = '"%s": DecimalFields require a "decimal_places" attribute that is a non-negative integer.'
|
||||
try:
|
||||
decimal_places = int(f.decimal_places)
|
||||
if decimal_places < 0:
|
||||
@@ -123,6 +125,10 @@ def get_validation_errors(outfile, app=None):
|
||||
if isinstance(f.rel.to, six.string_types):
|
||||
continue
|
||||
|
||||
# Make sure the model we're related hasn't been swapped out
|
||||
if f.rel.to._meta.swapped:
|
||||
e.add(opts, "'%s' defines a relation with the model '%s.%s', which has been swapped out. Update the relation to point at settings.%s." % (f.name, f.rel.to._meta.app_label, f.rel.to._meta.object_name, f.rel.to._meta.swappable))
|
||||
|
||||
# Make sure the related field specified by a ForeignKey is unique
|
||||
if not f.rel.to._meta.get_field(f.rel.field_name).unique:
|
||||
e.add(opts, "Field '%s' under model '%s' must have a unique=True constraint." % (f.rel.field_name, f.rel.to.__name__))
|
||||
@@ -165,6 +171,10 @@ def get_validation_errors(outfile, app=None):
|
||||
if isinstance(f.rel.to, six.string_types):
|
||||
continue
|
||||
|
||||
# Make sure the model we're related hasn't been swapped out
|
||||
if f.rel.to._meta.swapped:
|
||||
e.add(opts, "'%s' defines a relation with the model '%s.%s', which has been swapped out. Update the relation to point at settings.%s." % (f.name, f.rel.to._meta.app_label, f.rel.to._meta.object_name, f.rel.to._meta.swappable))
|
||||
|
||||
# Check that the field is not set to unique. ManyToManyFields do not support unique.
|
||||
if f.unique:
|
||||
e.add(opts, "ManyToManyFields cannot be unique. Remove the unique argument on '%s'." % f.name)
|
||||
@@ -176,7 +186,7 @@ def get_validation_errors(outfile, app=None):
|
||||
seen_from, seen_to, seen_self = False, False, 0
|
||||
for inter_field in f.rel.through._meta.fields:
|
||||
rel_to = getattr(inter_field.rel, 'to', None)
|
||||
if from_model == to_model: # relation to self
|
||||
if from_model == to_model: # relation to self
|
||||
if rel_to == from_model:
|
||||
seen_self += 1
|
||||
if seen_self > 2:
|
||||
@@ -275,10 +285,21 @@ def get_validation_errors(outfile, app=None):
|
||||
if r.get_accessor_name() == rel_query_name:
|
||||
e.add(opts, "Reverse query name for m2m field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
|
||||
|
||||
# Check swappable attribute.
|
||||
if opts.swapped:
|
||||
try:
|
||||
app_label, model_name = opts.swapped.split('.')
|
||||
except ValueError:
|
||||
e.add(opts, "%s is not of the form 'app_label.app_name'." % opts.swappable)
|
||||
continue
|
||||
if not models.get_model(app_label, model_name):
|
||||
e.add(opts, "Model has been swapped out for '%s' which has not been installed or is abstract." % opts.swapped)
|
||||
|
||||
# Check ordering attribute.
|
||||
if opts.ordering:
|
||||
for field_name in opts.ordering:
|
||||
if field_name == '?': continue
|
||||
if field_name == '?':
|
||||
continue
|
||||
if field_name.startswith('-'):
|
||||
field_name = field_name[1:]
|
||||
if opts.order_with_respect_to and field_name == '_order':
|
||||
|
||||
@@ -15,6 +15,7 @@ from django.utils import six
|
||||
# These values, if given to validate(), will trigger the self.required check.
|
||||
EMPTY_VALUES = (None, '', [], (), {})
|
||||
|
||||
|
||||
class RegexValidator(object):
|
||||
regex = ''
|
||||
message = _('Enter a valid value.')
|
||||
@@ -39,14 +40,15 @@ class RegexValidator(object):
|
||||
if not self.regex.search(force_text(value)):
|
||||
raise ValidationError(self.message, code=self.code)
|
||||
|
||||
|
||||
class URLValidator(RegexValidator):
|
||||
regex = re.compile(
|
||||
r'^(?:http|ftp)s?://' # http:// or https://
|
||||
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' #domain...
|
||||
r'localhost|' #localhost...
|
||||
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4
|
||||
r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6
|
||||
r'(?::\d+)?' # optional port
|
||||
r'^(?:http|ftp)s?://' # http:// or https://
|
||||
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain...
|
||||
r'localhost|' # localhost...
|
||||
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4
|
||||
r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6
|
||||
r'(?::\d+)?' # optional port
|
||||
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
|
||||
|
||||
def __call__(self, value):
|
||||
@@ -58,8 +60,8 @@ class URLValidator(RegexValidator):
|
||||
value = force_text(value)
|
||||
scheme, netloc, path, query, fragment = urlsplit(value)
|
||||
try:
|
||||
netloc = netloc.encode('idna').decode('ascii') # IDN -> ACE
|
||||
except UnicodeError: # invalid domain part
|
||||
netloc = netloc.encode('idna').decode('ascii') # IDN -> ACE
|
||||
except UnicodeError: # invalid domain part
|
||||
raise e
|
||||
url = urlunsplit((scheme, netloc, path, query, fragment))
|
||||
super(URLValidator, self).__call__(url)
|
||||
@@ -75,6 +77,7 @@ def validate_integer(value):
|
||||
except (ValueError, TypeError):
|
||||
raise ValidationError('')
|
||||
|
||||
|
||||
class EmailValidator(RegexValidator):
|
||||
|
||||
def __call__(self, value):
|
||||
@@ -106,10 +109,12 @@ validate_slug = RegexValidator(slug_re, _("Enter a valid 'slug' consisting of le
|
||||
ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$')
|
||||
validate_ipv4_address = RegexValidator(ipv4_re, _('Enter a valid IPv4 address.'), 'invalid')
|
||||
|
||||
|
||||
def validate_ipv6_address(value):
|
||||
if not is_valid_ipv6_address(value):
|
||||
raise ValidationError(_('Enter a valid IPv6 address.'), code='invalid')
|
||||
|
||||
|
||||
def validate_ipv46_address(value):
|
||||
try:
|
||||
validate_ipv4_address(value)
|
||||
@@ -125,6 +130,7 @@ ip_address_validator_map = {
|
||||
'ipv6': ([validate_ipv6_address], _('Enter a valid IPv6 address.')),
|
||||
}
|
||||
|
||||
|
||||
def ip_address_validators(protocol, unpack_ipv4):
|
||||
"""
|
||||
Depending on the given parameters returns the appropriate validators for
|
||||
@@ -147,7 +153,7 @@ validate_comma_separated_integer_list = RegexValidator(comma_separated_int_list_
|
||||
|
||||
class BaseValidator(object):
|
||||
compare = lambda self, a, b: a is not b
|
||||
clean = lambda self, x: x
|
||||
clean = lambda self, x: x
|
||||
message = _('Ensure this value is %(limit_value)s (it is %(show_value)s).')
|
||||
code = 'limit_value'
|
||||
|
||||
@@ -164,25 +170,28 @@ class BaseValidator(object):
|
||||
params=params,
|
||||
)
|
||||
|
||||
|
||||
class MaxValueValidator(BaseValidator):
|
||||
compare = lambda self, a, b: a > b
|
||||
message = _('Ensure this value is less than or equal to %(limit_value)s.')
|
||||
code = 'max_value'
|
||||
|
||||
|
||||
class MinValueValidator(BaseValidator):
|
||||
compare = lambda self, a, b: a < b
|
||||
message = _('Ensure this value is greater than or equal to %(limit_value)s.')
|
||||
code = 'min_value'
|
||||
|
||||
|
||||
class MinLengthValidator(BaseValidator):
|
||||
compare = lambda self, a, b: a < b
|
||||
clean = lambda self, x: len(x)
|
||||
clean = lambda self, x: len(x)
|
||||
message = _('Ensure this value has at least %(limit_value)d characters (it has %(show_value)d).')
|
||||
code = 'min_length'
|
||||
|
||||
|
||||
class MaxLengthValidator(BaseValidator):
|
||||
compare = lambda self, a, b: a > b
|
||||
clean = lambda self, x: len(x)
|
||||
clean = lambda self, x: len(x)
|
||||
message = _('Ensure this value has at most %(limit_value)d characters (it has %(show_value)d).')
|
||||
code = 'max_length'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user