summaryrefslogtreecommitdiff
path: root/django/db/models
diff options
context:
space:
mode:
authorJoseph Kocherhans <joseph@jkocherhans.com>2010-01-05 03:56:19 +0000
committerJoseph Kocherhans <joseph@jkocherhans.com>2010-01-05 03:56:19 +0000
commit471596fc1afcb9c6258d317c619eaf5fd394e797 (patch)
tree193767161be3cc23dc2e6be5e4f16d8fd21a2925 /django/db/models
parent4e89105d64bb9e04c409139a41e9c7aac263df4c (diff)
Merged soc2009/model-validation to trunk. Thanks, Honza!
git-svn-id: http://code.djangoproject.com/svn/django/trunk@12098 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django/db/models')
-rw-r--r--django/db/models/base.py179
-rw-r--r--django/db/models/fields/__init__.py243
-rw-r--r--django/db/models/fields/related.py33
3 files changed, 379 insertions, 76 deletions
diff --git a/django/db/models/base.py b/django/db/models/base.py
index 3464ae6712..935933ae0e 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -3,7 +3,8 @@ import sys
import os
from itertools import izip
import django.db.models.manager # Imported to register signal handler.
-from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError
+from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError, ValidationError, NON_FIELD_ERRORS
+from django.core import validators
from django.db.models.fields import AutoField, FieldDoesNotExist
from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField
from django.db.models.query import delete_objects, Q
@@ -12,9 +13,11 @@ from django.db.models.options import Options
from django.db import connections, transaction, DatabaseError, DEFAULT_DB_ALIAS
from django.db.models import signals
from django.db.models.loading import register_models, get_model
+from django.utils.translation import ugettext_lazy as _
import django.utils.copycompat as copy
from django.utils.functional import curry
from django.utils.encoding import smart_str, force_unicode, smart_unicode
+from django.utils.text import get_text_list, capfirst
from django.conf import settings
class ModelBase(type):
@@ -639,6 +642,180 @@ class Model(object):
def prepare_database_save(self, unused):
return self.pk
+ def validate(self):
+ """
+ Hook for doing any extra model-wide validation after clean() has been
+ called on every field. Any ValidationError raised by this method will
+ not be associated with a particular field; it will have a special-case
+ association with the field defined by NON_FIELD_ERRORS.
+ """
+ self.validate_unique()
+
+ def validate_unique(self):
+ unique_checks, date_checks = self._get_unique_checks()
+
+ errors = self._perform_unique_checks(unique_checks)
+ date_errors = self._perform_date_checks(date_checks)
+
+ for k, v in date_errors.items():
+ errors.setdefault(k, []).extend(v)
+
+ if errors:
+ raise ValidationError(errors)
+
+ def _get_unique_checks(self):
+ from django.db.models.fields import FieldDoesNotExist, Field as ModelField
+
+ unique_checks = list(self._meta.unique_together)
+ # these are checks for the unique_for_<date/year/month>
+ date_checks = []
+
+ # Gather a list of checks for fields declared as unique and add them to
+ # the list of checks. Again, skip empty fields and any that did not validate.
+ for f in self._meta.fields:
+ name = f.name
+ if f.unique:
+ unique_checks.append((name,))
+ if f.unique_for_date:
+ date_checks.append(('date', name, f.unique_for_date))
+ if f.unique_for_year:
+ date_checks.append(('year', name, f.unique_for_year))
+ if f.unique_for_month:
+ date_checks.append(('month', name, f.unique_for_month))
+ return unique_checks, date_checks
+
+
+ def _perform_unique_checks(self, unique_checks):
+ errors = {}
+
+ for unique_check in unique_checks:
+ # Try to look up an existing object with the same values as this
+ # object's values for all the unique field.
+
+ lookup_kwargs = {}
+ for field_name in unique_check:
+ f = self._meta.get_field(field_name)
+ lookup_value = getattr(self, f.attname)
+ if f.null and lookup_value is None:
+ # no value, skip the lookup
+ continue
+ if f.primary_key and not getattr(self, '_adding', False):
+ # no need to check for unique primary key when editting
+ continue
+ lookup_kwargs[str(field_name)] = lookup_value
+
+ # some fields were skipped, no reason to do the check
+ if len(unique_check) != len(lookup_kwargs.keys()):
+ continue
+
+ qs = self.__class__._default_manager.filter(**lookup_kwargs)
+
+ # Exclude the current object from the query if we are editing an
+ # instance (as opposed to creating a new one)
+ if not getattr(self, '_adding', False) and self.pk is not None:
+ qs = qs.exclude(pk=self.pk)
+
+ # This cute trick with extra/values is the most efficient way to
+ # tell if a particular query returns any results.
+ if qs.extra(select={'a': 1}).values('a').order_by():
+ if len(unique_check) == 1:
+ key = unique_check[0]
+ else:
+ key = NON_FIELD_ERRORS
+ errors.setdefault(key, []).append(self.unique_error_message(unique_check))
+
+ return errors
+
+ def _perform_date_checks(self, date_checks):
+ errors = {}
+ for lookup_type, field, unique_for in date_checks:
+ lookup_kwargs = {}
+ # there's a ticket to add a date lookup, we can remove this special
+ # case if that makes it's way in
+ date = getattr(self, unique_for)
+ if lookup_type == 'date':
+ lookup_kwargs['%s__day' % unique_for] = date.day
+ lookup_kwargs['%s__month' % unique_for] = date.month
+ lookup_kwargs['%s__year' % unique_for] = date.year
+ else:
+ lookup_kwargs['%s__%s' % (unique_for, lookup_type)] = getattr(date, lookup_type)
+ lookup_kwargs[field] = getattr(self, field)
+
+ qs = self.__class__._default_manager.filter(**lookup_kwargs)
+ # Exclude the current object from the query if we are editing an
+ # instance (as opposed to creating a new one)
+ if not getattr(self, '_adding', False) and self.pk is not None:
+ qs = qs.exclude(pk=self.pk)
+
+ # This cute trick with extra/values is the most efficient way to
+ # tell if a particular query returns any results.
+ if qs.extra(select={'a': 1}).values('a').order_by():
+ errors.setdefault(field, []).append(
+ self.date_error_message(lookup_type, field, unique_for)
+ )
+ return errors
+
+ def date_error_message(self, lookup_type, field, unique_for):
+ opts = self._meta
+ return _(u"%(field_name)s must be unique for %(date_field)s %(lookup)s.") % {
+ 'field_name': unicode(capfirst(opts.get_field(field).verbose_name)),
+ 'date_field': unicode(capfirst(opts.get_field(unique_for).verbose_name)),
+ 'lookup': lookup_type,
+ }
+
+ def unique_error_message(self, unique_check):
+ opts = self._meta
+ model_name = capfirst(opts.verbose_name)
+
+ # A unique field
+ if len(unique_check) == 1:
+ field_name = unique_check[0]
+ field_label = capfirst(opts.get_field(field_name).verbose_name)
+ # Insert the error into the error dict, very sneaky
+ return _(u"%(model_name)s with this %(field_label)s already exists.") % {
+ 'model_name': unicode(model_name),
+ 'field_label': unicode(field_label)
+ }
+ # unique_together
+ else:
+ field_labels = map(lambda f: capfirst(opts.get_field(f).verbose_name), unique_check)
+ field_labels = get_text_list(field_labels, _('and'))
+ return _(u"%(model_name)s with this %(field_label)s already exists.") % {
+ 'model_name': unicode(model_name),
+ 'field_label': unicode(field_labels)
+ }
+
+ def full_validate(self, exclude=[]):
+ """
+ Cleans all fields and raises ValidationError containing message_dict
+ of all validation errors if any occur.
+ """
+ errors = {}
+ for f in self._meta.fields:
+ if f.name in exclude:
+ continue
+ try:
+ setattr(self, f.attname, f.clean(getattr(self, f.attname), self))
+ except ValidationError, e:
+ errors[f.name] = e.messages
+
+ # Form.clean() is run even if other validation fails, so do the
+ # same with Model.validate() for consistency.
+ try:
+ self.validate()
+ except ValidationError, e:
+ if hasattr(e, 'message_dict'):
+ if errors:
+ for k, v in e.message_dict.items():
+ errors.set_default(k, []).extend(v)
+ else:
+ errors = e.message_dict
+ else:
+ errors[NON_FIELD_ERRORS] = e.messages
+
+ if errors:
+ raise ValidationError(errors)
+
############################################
# HELPER FUNCTIONS (CURRIED MODEL METHODS) #
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index b70f320df3..25f44a4d1c 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -13,12 +13,12 @@ from django.db.models.query_utils import QueryWrapper
from django.dispatch import dispatcher
from django.conf import settings
from django import forms
-from django.core import exceptions
+from django.core import exceptions, validators
from django.utils.datastructures import DictWrapper
from django.utils.functional import curry
from django.utils.itercompat import tee
from django.utils.text import capfirst
-from django.utils.translation import ugettext_lazy, ugettext as _
+from django.utils.translation import ugettext_lazy as _, ugettext
from django.utils.encoding import smart_unicode, force_unicode, smart_str
from django.utils import datetime_safe
@@ -60,6 +60,12 @@ class Field(object):
# creates, creation_counter is used for all user-specified fields.
creation_counter = 0
auto_creation_counter = -1
+ default_validators = [] # Default set of validators
+ default_error_messages = {
+ 'invalid_choice': _(u'Value %r is not a valid choice.'),
+ 'null': _(u'This field cannot be null.'),
+ 'blank': _(u'This field cannot be blank.'),
+ }
# Generic field type description, usually overriden by subclasses
def _description(self):
@@ -73,7 +79,8 @@ class Field(object):
db_index=False, rel=None, default=NOT_PROVIDED, editable=True,
serialize=True, unique_for_date=None, unique_for_month=None,
unique_for_year=None, choices=None, help_text='', db_column=None,
- db_tablespace=None, auto_created=False):
+ db_tablespace=None, auto_created=False, validators=[],
+ error_messages=None):
self.name = name
self.verbose_name = verbose_name
self.primary_key = primary_key
@@ -106,6 +113,42 @@ class Field(object):
self.creation_counter = Field.creation_counter
Field.creation_counter += 1
+ self.validators = self.default_validators + validators
+
+ messages = {}
+ for c in reversed(self.__class__.__mro__):
+ messages.update(getattr(c, 'default_error_messages', {}))
+ messages.update(error_messages or {})
+ self.error_messages = messages
+
+ def __getstate__(self):
+ """
+ Pickling support.
+ """
+ from django.utils.functional import Promise
+ obj_dict = self.__dict__.copy()
+ items = []
+ translated_keys = []
+ for k, v in self.error_messages.items():
+ if isinstance(v, Promise):
+ args = getattr(v, '_proxy____args', None)
+ if args:
+ translated_keys.append(k)
+ v = args[0]
+ items.append((k,v))
+ obj_dict['_translated_keys'] = translated_keys
+ obj_dict['error_messages'] = dict(items)
+ return obj_dict
+
+ def __setstate__(self, obj_dict):
+ """
+ Unpickling support.
+ """
+ translated_keys = obj_dict.pop('_translated_keys')
+ self.__dict__.update(obj_dict)
+ for k in translated_keys:
+ self.error_messages[k] = _(self.error_messages[k])
+
def __cmp__(self, other):
# This is needed because bisect does not take a comparison function.
return cmp(self.creation_counter, other.creation_counter)
@@ -127,6 +170,54 @@ class Field(object):
"""
return value
+ def run_validators(self, value):
+ if value in validators.EMPTY_VALUES:
+ return
+
+ errors = []
+ for v in self.validators:
+ try:
+ v(value)
+ except exceptions.ValidationError, e:
+ if hasattr(e, 'code') and e.code in self.error_messages:
+ message = self.error_messages[e.code]
+ if e.params:
+ message = message % e.params
+ errors.append(message)
+ else:
+ errors.extend(e.messages)
+ if errors:
+ raise exceptions.ValidationError(errors)
+
+ def validate(self, value, model_instance):
+ """
+ Validates value and throws ValidationError. Subclasses should override
+ this to provide validation logic.
+ """
+ if not self.editable:
+ # Skip validation for non-editable fields.
+ return
+ if self._choices and value:
+ if not value in dict(self.choices):
+ raise exceptions.ValidationError(self.error_messages['invalid_choice'] % value)
+
+ if value is None and not self.null:
+ raise exceptions.ValidationError(self.error_messages['null'])
+
+ if not self.blank and value in validators.EMPTY_VALUES:
+ raise exceptions.ValidationError(self.error_messages['blank'])
+
+ def clean(self, value, model_instance):
+ """
+ Convert the value's type and run validation. Validation errors from to_python
+ and validate are propagated. The correct value is returned if no error is
+ raised.
+ """
+ value = self.to_python(value)
+ self.validate(value, model_instance)
+ self.run_validators(value)
+ return value
+
def db_type(self, connection):
"""
Returns the database column data type for this field, for the provided
@@ -377,9 +468,12 @@ class Field(object):
return getattr(obj, self.attname)
class AutoField(Field):
- description = ugettext_lazy("Integer")
+ description = _("Integer")
empty_strings_allowed = False
+ default_error_messages = {
+ 'invalid': _(u'This value must be an integer.'),
+ }
def __init__(self, *args, **kwargs):
assert kwargs.get('primary_key', False) is True, "%ss must have primary_key=True." % self.__class__.__name__
kwargs['blank'] = True
@@ -391,8 +485,10 @@ class AutoField(Field):
try:
return int(value)
except (TypeError, ValueError):
- raise exceptions.ValidationError(
- _("This value must be an integer."))
+ raise exceptions.ValidationError(self.error_messages['invalid'])
+
+ def validate(self, value, model_instance):
+ pass
def get_prep_value(self, value):
if value is None:
@@ -410,7 +506,10 @@ class AutoField(Field):
class BooleanField(Field):
empty_strings_allowed = False
- description = ugettext_lazy("Boolean (Either True or False)")
+ default_error_messages = {
+ 'invalid': _(u'This value must be either True or False.'),
+ }
+ description = _("Boolean (Either True or False)")
def __init__(self, *args, **kwargs):
kwargs['blank'] = True
if 'default' not in kwargs and not kwargs.get('null'):
@@ -424,8 +523,7 @@ class BooleanField(Field):
if value in (True, False): return value
if value in ('t', 'True', '1'): return True
if value in ('f', 'False', '0'): return False
- raise exceptions.ValidationError(
- _("This value must be either True or False."))
+ raise exceptions.ValidationError(self.error_messages['invalid'])
def get_prep_lookup(self, lookup_type, value):
# Special-case handling for filters coming from a web request (e.g. the
@@ -453,36 +551,35 @@ class BooleanField(Field):
return super(BooleanField, self).formfield(**defaults)
class CharField(Field):
- description = ugettext_lazy("String (up to %(max_length)s)")
+ description = _("String (up to %(max_length)s)")
+
+ def __init__(self, *args, **kwargs):
+ super(CharField, self).__init__(*args, **kwargs)
+ self.validators.append(validators.MaxLengthValidator(self.max_length))
def get_internal_type(self):
return "CharField"
def to_python(self, value):
- if isinstance(value, basestring):
+ if isinstance(value, basestring) or value is None:
return value
- if value is None:
- if self.null:
- return value
- else:
- raise exceptions.ValidationError(
- ugettext_lazy("This field cannot be null."))
return smart_unicode(value)
def formfield(self, **kwargs):
+ # Passing max_length to forms.CharField means that the value's length
+ # will be validated twice. This is considered acceptable since we want
+ # the value in the form field (to pass into widget for example).
defaults = {'max_length': self.max_length}
defaults.update(kwargs)
return super(CharField, self).formfield(**defaults)
# TODO: Maybe move this into contrib, because it's specialized.
class CommaSeparatedIntegerField(CharField):
- description = ugettext_lazy("Comma-separated integers")
+ default_validators = [validators.validate_comma_separated_integer_list]
+ description = _("Comma-separated integers")
def formfield(self, **kwargs):
defaults = {
- 'form_class': forms.RegexField,
- 'regex': '^[\d,]+$',
- 'max_length': self.max_length,
'error_messages': {
'invalid': _(u'Enter only digits separated by commas.'),
}
@@ -493,9 +590,13 @@ class CommaSeparatedIntegerField(CharField):
ansi_date_re = re.compile(r'^\d{4}-\d{1,2}-\d{1,2}$')
class DateField(Field):
- description = ugettext_lazy("Date (without time)")
+ description = _("Date (without time)")
empty_strings_allowed = False
+ default_error_messages = {
+ 'invalid': _('Enter a valid date in YYYY-MM-DD format.'),
+ 'invalid_date': _('Invalid date: %s'),
+ }
def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs):
self.auto_now, self.auto_now_add = auto_now, auto_now_add
#HACKs : auto_now_add/auto_now should be done as a default or a pre_save.
@@ -516,8 +617,7 @@ class DateField(Field):
return value
if not ansi_date_re.search(value):
- raise exceptions.ValidationError(
- _('Enter a valid date in YYYY-MM-DD format.'))
+ raise exceptions.ValidationError(self.error_messages['invalid'])
# Now that we have the date string in YYYY-MM-DD format, check to make
# sure it's a valid date.
# We could use time.strptime here and catch errors, but datetime.date
@@ -526,7 +626,7 @@ class DateField(Field):
try:
return datetime.date(year, month, day)
except ValueError, e:
- msg = _('Invalid date: %s') % _(str(e))
+ msg = self.error_messages['invalid_date'] % _(str(e))
raise exceptions.ValidationError(msg)
def pre_save(self, model_instance, add):
@@ -575,7 +675,10 @@ class DateField(Field):
return super(DateField, self).formfield(**defaults)
class DateTimeField(DateField):
- description = ugettext_lazy("Date (with time)")
+ default_error_messages = {
+ 'invalid': _(u'Enter a valid date/time in YYYY-MM-DD HH:MM[:ss[.uuuuuu]] format.'),
+ }
+ description = _("Date (with time)")
def get_internal_type(self):
return "DateTimeField"
@@ -596,8 +699,7 @@ class DateTimeField(DateField):
value, usecs = value.split('.')
usecs = int(usecs)
except ValueError:
- raise exceptions.ValidationError(
- _('Enter a valid date/time in YYYY-MM-DD HH:MM[:ss[.uuuuuu]] format.'))
+ raise exceptions.ValidationError(self.error_messages['invalid'])
else:
usecs = 0
kwargs = {'microsecond': usecs}
@@ -614,8 +716,7 @@ class DateTimeField(DateField):
return datetime.datetime(*time.strptime(value, '%Y-%m-%d')[:3],
**kwargs)
except ValueError:
- raise exceptions.ValidationError(
- _('Enter a valid date/time in YYYY-MM-DD HH:MM[:ss[.uuuuuu]] format.'))
+ raise exceptions.ValidationError(self.error_messages['invalid'])
def get_prep_value(self, value):
return self.to_python(value)
@@ -642,7 +743,11 @@ class DateTimeField(DateField):
class DecimalField(Field):
empty_strings_allowed = False
- description = ugettext_lazy("Decimal number")
+ default_error_messages = {
+ 'invalid': _(u'This value must be a decimal number.'),
+ }
+ description = _("Decimal number")
+
def __init__(self, verbose_name=None, name=None, max_digits=None, decimal_places=None, **kwargs):
self.max_digits, self.decimal_places = max_digits, decimal_places
Field.__init__(self, verbose_name, name, **kwargs)
@@ -656,8 +761,7 @@ class DecimalField(Field):
try:
return decimal.Decimal(value)
except decimal.InvalidOperation:
- raise exceptions.ValidationError(
- _("This value must be a decimal number."))
+ raise exceptions.ValidationError(self.error_messages['invalid'])
def _format(self, value):
if isinstance(value, basestring) or value is None:
@@ -696,18 +800,15 @@ class DecimalField(Field):
return super(DecimalField, self).formfield(**defaults)
class EmailField(CharField):
- description = ugettext_lazy("E-mail address")
+ default_validators = [validators.validate_email]
+ description = _("E-mail address")
+
def __init__(self, *args, **kwargs):
kwargs['max_length'] = kwargs.get('max_length', 75)
CharField.__init__(self, *args, **kwargs)
- def formfield(self, **kwargs):
- defaults = {'form_class': forms.EmailField}
- defaults.update(kwargs)
- return super(EmailField, self).formfield(**defaults)
-
class FilePathField(Field):
- description = ugettext_lazy("File path")
+ description = _("File path")
def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs):
self.path, self.match, self.recursive = path, match, recursive
@@ -729,7 +830,10 @@ class FilePathField(Field):
class FloatField(Field):
empty_strings_allowed = False
- description = ugettext_lazy("Floating point number")
+ default_error_messages = {
+ 'invalid': _("This value must be a float."),
+ }
+ description = _("Floating point number")
def get_prep_value(self, value):
if value is None:
@@ -745,8 +849,7 @@ class FloatField(Field):
try:
return float(value)
except (TypeError, ValueError):
- raise exceptions.ValidationError(
- _("This value must be a float."))
+ raise exceptions.ValidationError(self.error_messages['invalid'])
def formfield(self, **kwargs):
defaults = {'form_class': forms.FloatField}
@@ -755,7 +858,10 @@ class FloatField(Field):
class IntegerField(Field):
empty_strings_allowed = False
- description = ugettext_lazy("Integer")
+ default_error_messages = {
+ 'invalid': _("This value must be a float."),
+ }
+ description = _("Integer")
def get_prep_value(self, value):
if value is None:
@@ -771,8 +877,7 @@ class IntegerField(Field):
try:
return int(value)
except (TypeError, ValueError):
- raise exceptions.ValidationError(
- _("This value must be an integer."))
+ raise exceptions.ValidationError(self.error_messages['invalid'])
def formfield(self, **kwargs):
defaults = {'form_class': forms.IntegerField}
@@ -781,7 +886,7 @@ class IntegerField(Field):
class BigIntegerField(IntegerField):
empty_strings_allowed = False
- description = ugettext_lazy("Big (8 byte) integer")
+ description = _("Big (8 byte) integer")
MAX_BIGINT = 9223372036854775807
def get_internal_type(self):
return "BigIntegerField"
@@ -794,7 +899,7 @@ class BigIntegerField(IntegerField):
class IPAddressField(Field):
empty_strings_allowed = False
- description = ugettext_lazy("IP address")
+ description = _("IP address")
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 15
Field.__init__(self, *args, **kwargs)
@@ -809,7 +914,11 @@ class IPAddressField(Field):
class NullBooleanField(Field):
empty_strings_allowed = False
- description = ugettext_lazy("Boolean (Either True, False or None)")
+ default_error_messages = {
+ 'invalid': _("This value must be either None, True or False."),
+ }
+ description = _("Boolean (Either True, False or None)")
+
def __init__(self, *args, **kwargs):
kwargs['null'] = True
Field.__init__(self, *args, **kwargs)
@@ -822,8 +931,7 @@ class NullBooleanField(Field):
if value in ('None',): return None
if value in ('t', 'True', '1'): return True
if value in ('f', 'False', '0'): return False
- raise exceptions.ValidationError(
- _("This value must be either None, True or False."))
+ raise exceptions.ValidationError(self.error_messages['invalid'])
def get_prep_lookup(self, lookup_type, value):
# Special-case handling for filters coming from a web request (e.g. the
@@ -849,7 +957,7 @@ class NullBooleanField(Field):
return super(NullBooleanField, self).formfield(**defaults)
class PositiveIntegerField(IntegerField):
- description = ugettext_lazy("Integer")
+ description = _("Integer")
def get_internal_type(self):
return "PositiveIntegerField"
@@ -860,7 +968,7 @@ class PositiveIntegerField(IntegerField):
return super(PositiveIntegerField, self).formfield(**defaults)
class PositiveSmallIntegerField(IntegerField):
- description = ugettext_lazy("Integer")
+ description = _("Integer")
def get_internal_type(self):
return "PositiveSmallIntegerField"
@@ -870,7 +978,7 @@ class PositiveSmallIntegerField(IntegerField):
return super(PositiveSmallIntegerField, self).formfield(**defaults)
class SlugField(CharField):
- description = ugettext_lazy("String (up to %(max_length)s)")
+ description = _("String (up to %(max_length)s)")
def __init__(self, *args, **kwargs):
kwargs['max_length'] = kwargs.get('max_length', 50)
# Set db_index=True unless it's been set manually.
@@ -887,13 +995,13 @@ class SlugField(CharField):
return super(SlugField, self).formfield(**defaults)
class SmallIntegerField(IntegerField):
- description = ugettext_lazy("Integer")
+ description = _("Integer")
def get_internal_type(self):
return "SmallIntegerField"
class TextField(Field):
- description = ugettext_lazy("Text")
+ description = _("Text")
def get_internal_type(self):
return "TextField"
@@ -904,9 +1012,12 @@ class TextField(Field):
return super(TextField, self).formfield(**defaults)
class TimeField(Field):
- description = ugettext_lazy("Time")
+ description = _("Time")
empty_strings_allowed = False
+ default_error_messages = {
+ 'invalid': _('Enter a valid time in HH:MM[:ss[.uuuuuu]] format.'),
+ }
def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs):
self.auto_now, self.auto_now_add = auto_now, auto_now_add
if auto_now or auto_now_add:
@@ -935,8 +1046,7 @@ class TimeField(Field):
value, usecs = value.split('.')
usecs = int(usecs)
except ValueError:
- raise exceptions.ValidationError(
- _('Enter a valid time in HH:MM[:ss[.uuuuuu]] format.'))
+ raise exceptions.ValidationError(self.error_messages['invalid'])
else:
usecs = 0
kwargs = {'microsecond': usecs}
@@ -949,8 +1059,7 @@ class TimeField(Field):
return datetime.time(*time.strptime(value, '%H:%M')[3:5],
**kwargs)
except ValueError:
- raise exceptions.ValidationError(
- _('Enter a valid time in HH:MM[:ss[.uuuuuu]] format.'))
+ raise exceptions.ValidationError(self.error_messages['invalid'])
def pre_save(self, model_instance, add):
if self.auto_now or (self.auto_now_add and add):
@@ -983,21 +1092,17 @@ class TimeField(Field):
return super(TimeField, self).formfield(**defaults)
class URLField(CharField):
- description = ugettext_lazy("URL")
+ description = _("URL")
def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs):
kwargs['max_length'] = kwargs.get('max_length', 200)
- self.verify_exists = verify_exists
CharField.__init__(self, verbose_name, name, **kwargs)
-
- def formfield(self, **kwargs):
- defaults = {'form_class': forms.URLField, 'verify_exists': self.verify_exists}
- defaults.update(kwargs)
- return super(URLField, self).formfield(**defaults)
+ self.validators.append(validators.URLValidator(verify_exists=verify_exists))
class XMLField(TextField):
- description = ugettext_lazy("XML text")
+ description = _("XML text")
def __init__(self, verbose_name=None, name=None, schema_path=None, **kwargs):
self.schema_path = schema_path
Field.__init__(self, verbose_name, name, **kwargs)
+
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 7cc9a03907..749bdcf39a 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -7,7 +7,7 @@ from django.db.models.related import RelatedObject
from django.db.models.query import QuerySet
from django.db.models.query_utils import QueryWrapper
from django.utils.encoding import smart_unicode
-from django.utils.translation import ugettext_lazy, string_concat, ungettext, ugettext as _
+from django.utils.translation import ugettext_lazy as _, string_concat, ungettext, ugettext
from django.utils.functional import curry
from django.core import exceptions
from django import forms
@@ -473,7 +473,7 @@ def create_many_related_manager(superclass, rel=False):
if not rel.through._meta.auto_created:
opts = through._meta
raise AttributeError, "Cannot use create() on a ManyToManyField which specifies an intermediary model. Use %s.%s's Manager instead." % (opts.app_label, opts.object_name)
- new_obj = super(ManyRelatedManager, self).using(self.instance._state.db).create(**kwargs)
+ new_obj = super(ManyRelatedManager, self).create(**kwargs)
self.add(new_obj)
return new_obj
create.alters_data = True
@@ -708,7 +708,10 @@ class ManyToManyRel(object):
class ForeignKey(RelatedField, Field):
empty_strings_allowed = False
- description = ugettext_lazy("Foreign Key (type determined by related field)")
+ default_error_messages = {
+ 'invalid': _('Model %(model)s with pk %(pk)r does not exist.')
+ }
+ description = _("Foreign Key (type determined by related field)")
def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs):
try:
to_name = to._meta.object_name.lower()
@@ -731,6 +734,18 @@ class ForeignKey(RelatedField, Field):
self.db_index = True
+ def validate(self, value, model_instance):
+ if self.rel.parent_link:
+ return
+ super(ForeignKey, self).validate(value, model_instance)
+ if not value:
+ return
+ try:
+ self.rel.to._default_manager.get(**{self.rel.field_name:value})
+ except self.rel.to.DoesNotExist, e:
+ raise exceptions.ValidationError(
+ self.error_messages['invalid'] % {'model': self.rel.to._meta.verbose_name, 'pk': value})
+
def get_attname(self):
return '%s_id' % self.name
@@ -812,7 +827,7 @@ class OneToOneField(ForeignKey):
always returns the object pointed to (since there will only ever be one),
rather than returning a list.
"""
- description = ugettext_lazy("One-to-one relationship")
+ description = _("One-to-one relationship")
def __init__(self, to, to_field=None, **kwargs):
kwargs['unique'] = True
super(OneToOneField, self).__init__(to, to_field, OneToOneRel, **kwargs)
@@ -826,6 +841,12 @@ class OneToOneField(ForeignKey):
return None
return super(OneToOneField, self).formfield(**kwargs)
+ def save_form_data(self, instance, data):
+ if isinstance(data, self.rel.to):
+ setattr(instance, self.name, data)
+ else:
+ setattr(instance, self.attname, data)
+
def create_many_to_many_intermediary_model(field, klass):
from django.db import models
managed = True
@@ -866,7 +887,7 @@ def create_many_to_many_intermediary_model(field, klass):
})
class ManyToManyField(RelatedField, Field):
- description = ugettext_lazy("Many-to-many relationship")
+ description = _("Many-to-many relationship")
def __init__(self, to, **kwargs):
try:
assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name)
@@ -886,7 +907,7 @@ class ManyToManyField(RelatedField, Field):
Field.__init__(self, **kwargs)
- msg = ugettext_lazy('Hold down "Control", or "Command" on a Mac, to select more than one.')
+ msg = _('Hold down "Control", or "Command" on a Mac, to select more than one.')
self.help_text = string_concat(self.help_text, ' ', msg)
def get_choices_default(self):