diff options
| author | Joseph Kocherhans <joseph@jkocherhans.com> | 2010-01-05 03:56:19 +0000 |
|---|---|---|
| committer | Joseph Kocherhans <joseph@jkocherhans.com> | 2010-01-05 03:56:19 +0000 |
| commit | 471596fc1afcb9c6258d317c619eaf5fd394e797 (patch) | |
| tree | 193767161be3cc23dc2e6be5e4f16d8fd21a2925 /django/db/models | |
| parent | 4e89105d64bb9e04c409139a41e9c7aac263df4c (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.py | 179 | ||||
| -rw-r--r-- | django/db/models/fields/__init__.py | 243 | ||||
| -rw-r--r-- | django/db/models/fields/related.py | 33 |
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): |
