diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py index 0b6dbf2e6b..17213ea5b3 100644 --- a/django/contrib/admin/util.py +++ b/django/contrib/admin/util.py @@ -85,7 +85,7 @@ def get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current_ perms_needed.add(related.opts.verbose_name) # We don't care about populating deleted_objects now. continue - if related.field.rel.edit_inline or not has_admin: + if not has_admin: # Don't display link to edit, because it either has no # admin or is edited inline. nh(deleted_objects, current_depth, [u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), sub_obj), []]) @@ -101,7 +101,7 @@ def get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current_ has_related_objs = False for sub_obj in getattr(obj, rel_opts_name).all(): has_related_objs = True - if related.field.rel.edit_inline or not has_admin: + if not has_admin: # Don't display link to edit, because it either has no # admin or is edited inline. nh(deleted_objects, current_depth, [u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), sub_obj), []]) @@ -132,7 +132,7 @@ def get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current_ if has_related_objs: for sub_obj in rel_objs.all(): - if related.field.rel.edit_inline or not has_admin: + if not has_admin: # Don't display link to edit, because it either has no # admin or is edited inline. nh(deleted_objects, current_depth, [_('One or more %(fieldname)s in %(name)s: %(obj)s') % \ diff --git a/django/contrib/auth/management/commands/createsuperuser.py b/django/contrib/auth/management/commands/createsuperuser.py index 91e39f7235..aaedcab0f4 100644 --- a/django/contrib/auth/management/commands/createsuperuser.py +++ b/django/contrib/auth/management/commands/createsuperuser.py @@ -8,10 +8,19 @@ import re import sys from optparse import make_option from django.contrib.auth.models import User -from django.core import validators +from django.core import exceptions from django.core.management.base import BaseCommand, CommandError +from django.utils.translation import ugettext as _ RE_VALID_USERNAME = re.compile('\w+$') +EMAIL_RE = re.compile( + r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom + r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"' # quoted-string + r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain + +def is_valid_email(value): + if not EMAIL_RE.search(value): + raise exceptions.ValidationError(_('Enter a valid e-mail address.')) class Command(BaseCommand): option_list = BaseCommand.option_list + ( @@ -39,8 +48,8 @@ class Command(BaseCommand): if not RE_VALID_USERNAME.match(username): raise CommandError("Invalid username. Use only letters, digits, and underscores") try: - validators.isValidEmail(email, None) - except validators.ValidationError: + is_valid_email(email) + except exceptions.ValidationError: raise CommandError("Invalid email address.") password = '' @@ -94,8 +103,8 @@ class Command(BaseCommand): if not email: email = raw_input('E-mail address: ') try: - validators.isValidEmail(email, None) - except validators.ValidationError: + is_valid_email(email) + except exceptions.ValidationError: sys.stderr.write("Error: That e-mail address is invalid.\n") email = None else: diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py index 1bddbc2296..4d3189b08b 100644 --- a/django/contrib/auth/models.py +++ b/django/contrib/auth/models.py @@ -1,5 +1,4 @@ from django.contrib import auth -from django.core import validators from django.core.exceptions import ImproperlyConfigured from django.db import models from django.db.models.manager import EmptyManager diff --git a/django/contrib/comments/forms.py b/django/contrib/comments/forms.py index 4e7870501e..adb2741873 100644 --- a/django/contrib/comments/forms.py +++ b/django/contrib/comments/forms.py @@ -117,9 +117,6 @@ class CommentForm(forms.Form): """ comment = self.cleaned_data["comment"] if settings.COMMENTS_ALLOW_PROFANITIES == False: - # Logic adapted from django.core.validators; it's not clear if they - # should be used in newforms or will be deprecated along with the - # rest of oldforms bad_words = [w for w in settings.PROFANITIES_LIST if w in comment.lower()] if bad_words: plural = len(bad_words) > 1 diff --git a/django/contrib/comments/models.py b/django/contrib/comments/models.py index 7424ec041a..40243d000d 100644 --- a/django/contrib/comments/models.py +++ b/django/contrib/comments/models.py @@ -5,7 +5,7 @@ from django.contrib.contenttypes import generic from django.contrib.contenttypes.models import ContentType from django.contrib.sites.models import Site from django.db import models -from django.core import urlresolvers, validators +from django.core import urlresolvers from django.utils.translation import ugettext_lazy as _ from django.conf import settings diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py index 9b6c15a22a..045b1ebab7 100644 --- a/django/contrib/contenttypes/generic.py +++ b/django/contrib/contenttypes/generic.py @@ -2,18 +2,16 @@ Classes allowing "generic" relations through ContentType and object-id fields. """ -from django import oldforms from django.core.exceptions import ObjectDoesNotExist from django.db import connection from django.db.models import signals from django.db import models from django.db.models.fields.related import RelatedField, Field, ManyToManyRel from django.db.models.loading import get_model -from django.utils.functional import curry - from django.forms import ModelForm from django.forms.models import BaseModelFormSet, modelformset_factory, save_instance from django.contrib.admin.options import InlineModelAdmin, flatten_fieldsets +from django.utils.encoding import smart_unicode class GenericForeignKey(object): """ @@ -120,19 +118,12 @@ class GenericRelation(RelatedField, Field): kwargs['serialize'] = False Field.__init__(self, **kwargs) - def get_manipulator_field_objs(self): - choices = self.get_choices_default() - return [curry(oldforms.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)] - def get_choices_default(self): return Field.get_choices(self, include_blank=False) - def flatten_data(self, follow, obj = None): - new_data = {} - if obj: - instance_ids = [instance._get_pk_val() for instance in getattr(obj, self.name).all()] - new_data[self.name] = instance_ids - return new_data + def value_to_string(self, obj): + qs = getattr(obj, self.name).all() + return smart_unicode([instance._get_pk_val() for instance in qs]) def m2m_db_table(self): return self.rel.to._meta.db_table @@ -290,7 +281,6 @@ class GenericRel(ManyToManyRel): self.to = to self.related_name = related_name self.limit_choices_to = limit_choices_to or {} - self.edit_inline = False self.symmetrical = symmetrical self.multiple = True @@ -300,7 +290,7 @@ class BaseGenericInlineFormSet(BaseModelFormSet): """ ct_field_name = "content_type" ct_fk_field_name = "object_id" - + def __init__(self, data=None, files=None, instance=None, save_as_new=None): opts = self.model._meta self.instance = instance @@ -395,4 +385,3 @@ class GenericStackedInline(GenericInlineModelAdmin): class GenericTabularInline(GenericInlineModelAdmin): template = 'admin/edit_inline/tabular.html' - diff --git a/django/contrib/flatpages/models.py b/django/contrib/flatpages/models.py index 02005dd07d..85873ac7b1 100644 --- a/django/contrib/flatpages/models.py +++ b/django/contrib/flatpages/models.py @@ -1,4 +1,3 @@ -from django.core import validators from django.db import models from django.contrib.sites.models import Site from django.utils.translation import ugettext_lazy as _ diff --git a/django/contrib/localflavor/jp/forms.py b/django/contrib/localflavor/jp/forms.py index c86dbaf8da..1d765c8b17 100644 --- a/django/contrib/localflavor/jp/forms.py +++ b/django/contrib/localflavor/jp/forms.py @@ -2,7 +2,6 @@ JP-specific Form helpers """ -from django.core import validators from django.forms import ValidationError from django.utils.translation import ugettext_lazy as _ from django.forms.fields import RegexField, Select diff --git a/django/core/exceptions.py b/django/core/exceptions.py index e5df8caca8..1c21031739 100644 --- a/django/core/exceptions.py +++ b/django/core/exceptions.py @@ -32,3 +32,6 @@ class FieldError(Exception): """Some kind of problem with a model field.""" pass +class ValidationError(Exception): + """An error while validating data.""" + pass diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py index bfd785a6fe..22de2d70d0 100644 --- a/django/core/serializers/base.py +++ b/django/core/serializers/base.py @@ -57,12 +57,7 @@ class Serializer(object): """ Convert a field's value to a string. """ - if isinstance(field, models.DateTimeField): - d = datetime_safe.new_datetime(getattr(obj, field.name)) - value = d.strftime("%Y-%m-%d %H:%M:%S") - else: - value = field.flatten_data(follow=None, obj=obj).get(field.name, "") - return smart_unicode(value) + return smart_unicode(field.value_to_string(obj)) def start_serialization(self): """ diff --git a/django/core/validators.py b/django/core/validators.py deleted file mode 100644 index f94db40c1b..0000000000 --- a/django/core/validators.py +++ /dev/null @@ -1,598 +0,0 @@ -""" -A library of validators that return None and raise ValidationError when the -provided data isn't valid. - -Validators may be callable classes, and they may have an 'always_test' -attribute. If an 'always_test' attribute exists (regardless of value), the -validator will *always* be run, regardless of whether its associated -form field is required. -""" - -import urllib2 -import re -try: - from decimal import Decimal, DecimalException -except ImportError: - from django.utils._decimal import Decimal, DecimalException # Python 2.3 - -from django.conf import settings -from django.utils.translation import ugettext as _, ugettext_lazy, ungettext -from django.utils.functional import Promise, lazy -from django.utils.encoding import force_unicode, smart_str - -_datere = r'\d{4}-\d{1,2}-\d{1,2}' -_timere = r'(?:[01]?[0-9]|2[0-3]):[0-5][0-9](?::[0-5][0-9])?' -alnum_re = re.compile(r'^\w+$') -alnumurl_re = re.compile(r'^[-\w/]+$') -ansi_date_re = re.compile('^%s$' % _datere) -ansi_time_re = re.compile('^%s$' % _timere) -ansi_datetime_re = re.compile('^%s %s$' % (_datere, _timere)) -email_re = re.compile( - r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom - r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"' # quoted-string - r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain -integer_re = re.compile(r'^-?\d+$') -ip4_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}$') -phone_re = re.compile(r'^[A-PR-Y0-9]{3}-[A-PR-Y0-9]{3}-[A-PR-Y0-9]{4}$', re.IGNORECASE) -slug_re = re.compile(r'^[-\w]+$') -url_re = re.compile(r'^https?://\S+$') - -lazy_inter = lazy(lambda a,b: force_unicode(a) % b, unicode) - -class ValidationError(Exception): - def __init__(self, message): - "ValidationError can be passed a string or a list." - if isinstance(message, list): - self.messages = [force_unicode(msg) for msg in message] - else: - assert isinstance(message, (basestring, Promise)), ("%s should be a string" % repr(message)) - self.messages = [force_unicode(message)] - - def __str__(self): - # This is needed because, without a __str__(), printing an exception - # instance would result in this: - # AttributeError: ValidationError instance has no attribute 'args' - # See http://www.python.org/doc/current/tut/node10.html#handling - return str(self.messages) - -class CriticalValidationError(Exception): - def __init__(self, message): - "ValidationError can be passed a string or a list." - if isinstance(message, list): - self.messages = [force_unicode(msg) for msg in message] - else: - assert isinstance(message, (basestring, Promise)), ("'%s' should be a string" % message) - self.messages = [force_unicode(message)] - - def __str__(self): - return str(self.messages) - -def isAlphaNumeric(field_data, all_data): - if not alnum_re.search(field_data): - raise ValidationError, _("This value must contain only letters, numbers and underscores.") - -def isAlphaNumericURL(field_data, all_data): - if not alnumurl_re.search(field_data): - raise ValidationError, _("This value must contain only letters, numbers, underscores, dashes or slashes.") - -def isSlug(field_data, all_data): - if not slug_re.search(field_data): - raise ValidationError, _("This value must contain only letters, numbers, underscores or hyphens.") - -def isLowerCase(field_data, all_data): - if field_data.lower() != field_data: - raise ValidationError, _("Uppercase letters are not allowed here.") - -def isUpperCase(field_data, all_data): - if field_data.upper() != field_data: - raise ValidationError, _("Lowercase letters are not allowed here.") - -def isCommaSeparatedIntegerList(field_data, all_data): - for supposed_int in field_data.split(','): - try: - int(supposed_int) - except ValueError: - raise ValidationError, _("Enter only digits separated by commas.") - -def isCommaSeparatedEmailList(field_data, all_data): - """ - Checks that field_data is a string of e-mail addresses separated by commas. - Blank field_data values will not throw a validation error, and whitespace - is allowed around the commas. - """ - for supposed_email in field_data.split(','): - try: - isValidEmail(supposed_email.strip(), '') - except ValidationError: - raise ValidationError, _("Enter valid e-mail addresses separated by commas.") - -def isValidIPAddress4(field_data, all_data): - if not ip4_re.search(field_data): - raise ValidationError, _("Please enter a valid IP address.") - -def isNotEmpty(field_data, all_data): - if field_data.strip() == '': - raise ValidationError, _("Empty values are not allowed here.") - -def isOnlyDigits(field_data, all_data): - if not field_data.isdigit(): - raise ValidationError, _("Non-numeric characters aren't allowed here.") - -def isNotOnlyDigits(field_data, all_data): - if field_data.isdigit(): - raise ValidationError, _("This value can't be comprised solely of digits.") - -def isInteger(field_data, all_data): - # This differs from isOnlyDigits because this accepts the negative sign - if not integer_re.search(field_data): - raise ValidationError, _("Enter a whole number.") - -def isOnlyLetters(field_data, all_data): - if not field_data.isalpha(): - raise ValidationError, _("Only alphabetical characters are allowed here.") - -def _isValidDate(date_string): - """ - A helper function used by isValidANSIDate and isValidANSIDatetime to - check if the date is valid. The date string is assumed to already be in - YYYY-MM-DD format. - """ - from datetime import date - # Could use time.strptime here and catch errors, but datetime.date below - # produces much friendlier error messages. - year, month, day = map(int, date_string.split('-')) - try: - date(year, month, day) - except ValueError, e: - msg = _('Invalid date: %s') % _(str(e)) - raise ValidationError, msg - -def isValidANSIDate(field_data, all_data): - if not ansi_date_re.search(field_data): - raise ValidationError, _('Enter a valid date in YYYY-MM-DD format.') - _isValidDate(field_data) - -def isValidANSITime(field_data, all_data): - if not ansi_time_re.search(field_data): - raise ValidationError, _('Enter a valid time in HH:MM format.') - -def isValidANSIDatetime(field_data, all_data): - if not ansi_datetime_re.search(field_data): - raise ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM format.') - _isValidDate(field_data.split()[0]) - -def isValidEmail(field_data, all_data): - if not email_re.search(field_data): - raise ValidationError, _('Enter a valid e-mail address.') - -def isValidImage(field_data, all_data): - """ - Checks that the file-upload field data contains a valid image (GIF, JPG, - PNG, possibly others -- whatever the Python Imaging Library supports). - """ - from PIL import Image - from cStringIO import StringIO - try: - content = field_data.read() - except TypeError: - raise ValidationError, _("No file was submitted. Check the encoding type on the form.") - try: - # load() is the only method that can spot a truncated JPEG, - # but it cannot be called sanely after verify() - trial_image = Image.open(StringIO(content)) - trial_image.load() - # verify() is the only method that can spot a corrupt PNG, - # but it must be called immediately after the constructor - trial_image = Image.open(StringIO(content)) - trial_image.verify() - except Exception: # Python Imaging Library doesn't recognize it as an image - raise ValidationError, _("Upload a valid image. The file you uploaded was either not an image or a corrupted image.") - -def isValidImageURL(field_data, all_data): - uc = URLMimeTypeCheck(('image/jpeg', 'image/gif', 'image/png')) - try: - uc(field_data, all_data) - except URLMimeTypeCheck.InvalidContentType: - raise ValidationError, _("The URL %s does not point to a valid image.") % field_data - -def isValidPhone(field_data, all_data): - if not phone_re.search(field_data): - raise ValidationError, _('Phone numbers must be in XXX-XXX-XXXX format. "%s" is invalid.') % field_data - -def isValidQuicktimeVideoURL(field_data, all_data): - "Checks that the given URL is a video that can be played by QuickTime (qt, mpeg)" - uc = URLMimeTypeCheck(('video/quicktime', 'video/mpeg',)) - try: - uc(field_data, all_data) - except URLMimeTypeCheck.InvalidContentType: - raise ValidationError, _("The URL %s does not point to a valid QuickTime video.") % field_data - -def isValidURL(field_data, all_data): - if not url_re.search(field_data): - raise ValidationError, _("A valid URL is required.") - -def isValidHTML(field_data, all_data): - import urllib, urllib2 - try: - u = urllib2.urlopen('http://validator.w3.org/check', urllib.urlencode({'fragment': field_data, 'output': 'xml'})) - except: - # Validator or Internet connection is unavailable. Fail silently. - return - html_is_valid = (u.headers.get('x-w3c-validator-status', 'Invalid') == 'Valid') - if html_is_valid: - return - from xml.dom.minidom import parseString - error_messages = [e.firstChild.wholeText for e in parseString(u.read()).getElementsByTagName('messages')[0].getElementsByTagName('msg')] - raise ValidationError, _("Valid HTML is required. Specific errors are:\n%s") % "\n".join(error_messages) - -def isWellFormedXml(field_data, all_data): - from xml.dom.minidom import parseString - try: - parseString(field_data) - except Exception, e: # Naked except because we're not sure what will be thrown - raise ValidationError, _("Badly formed XML: %s") % str(e) - -def isWellFormedXmlFragment(field_data, all_data): - isWellFormedXml('%s' % field_data, all_data) - -def isExistingURL(field_data, all_data): - try: - headers = { - "Accept" : "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5", - "Accept-Language" : "en-us,en;q=0.5", - "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7", - "Connection" : "close", - "User-Agent": settings.URL_VALIDATOR_USER_AGENT - } - req = urllib2.Request(field_data,None, headers) - u = urllib2.urlopen(req) - except ValueError: - raise ValidationError, _("Invalid URL: %s") % field_data - except urllib2.HTTPError, e: - # 401s are valid; they just mean authorization is required. - # 301 and 302 are redirects; they just mean look somewhere else. - if str(e.code) not in ('401','301','302'): - raise ValidationError, _("The URL %s is a broken link.") % field_data - except: # urllib2.URLError, httplib.InvalidURL, etc. - raise ValidationError, _("The URL %s is a broken link.") % field_data - -def isValidUSState(field_data, all_data): - "Checks that the given string is a valid two-letter U.S. state abbreviation" - states = ['AA', 'AE', 'AK', 'AL', 'AP', 'AR', 'AS', 'AZ', 'CA', 'CO', 'CT', 'DC', 'DE', 'FL', 'FM', 'GA', 'GU', 'HI', 'IA', 'ID', 'IL', 'IN', 'KS', 'KY', 'LA', 'MA', 'MD', 'ME', 'MH', 'MI', 'MN', 'MO', 'MP', 'MS', 'MT', 'NC', 'ND', 'NE', 'NH', 'NJ', 'NM', 'NV', 'NY', 'OH', 'OK', 'OR', 'PA', 'PR', 'PW', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VA', 'VI', 'VT', 'WA', 'WI', 'WV', 'WY'] - if field_data.upper() not in states: - raise ValidationError, _("Enter a valid U.S. state abbreviation.") - -def hasNoProfanities(field_data, all_data): - """ - Checks that the given string has no profanities in it. This does a simple - check for whether each profanity exists within the string, so 'fuck' will - catch 'motherfucker' as well. Raises a ValidationError such as: - Watch your mouth! The words "f--k" and "s--t" are not allowed here. - """ - field_data = field_data.lower() # normalize - words_seen = [w for w in settings.PROFANITIES_LIST if w in field_data] - if words_seen: - from django.utils.text import get_text_list - plural = len(words_seen) - raise ValidationError, ungettext("Watch your mouth! The word %s is not allowed here.", - "Watch your mouth! The words %s are not allowed here.", plural) % \ - get_text_list(['"%s%s%s"' % (i[0], '-'*(len(i)-2), i[-1]) for i in words_seen], _('and')) - -class AlwaysMatchesOtherField(object): - def __init__(self, other_field_name, error_message=None): - self.other = other_field_name - self.error_message = error_message or lazy_inter(ugettext_lazy("This field must match the '%s' field."), self.other) - self.always_test = True - - def __call__(self, field_data, all_data): - if field_data != all_data[self.other]: - raise ValidationError, self.error_message - -class ValidateIfOtherFieldEquals(object): - def __init__(self, other_field, other_value, validator_list): - self.other_field, self.other_value = other_field, other_value - self.validator_list = validator_list - self.always_test = True - - def __call__(self, field_data, all_data): - if self.other_field in all_data and all_data[self.other_field] == self.other_value: - for v in self.validator_list: - v(field_data, all_data) - -class RequiredIfOtherFieldNotGiven(object): - def __init__(self, other_field_name, error_message=ugettext_lazy("Please enter something for at least one field.")): - self.other, self.error_message = other_field_name, error_message - self.always_test = True - - def __call__(self, field_data, all_data): - if not all_data.get(self.other, False) and not field_data: - raise ValidationError, self.error_message - -class RequiredIfOtherFieldsGiven(object): - def __init__(self, other_field_names, error_message=ugettext_lazy("Please enter both fields or leave them both empty.")): - self.other, self.error_message = other_field_names, error_message - self.always_test = True - - def __call__(self, field_data, all_data): - for field in self.other: - if all_data.get(field, False) and not field_data: - raise ValidationError, self.error_message - -class RequiredIfOtherFieldGiven(RequiredIfOtherFieldsGiven): - "Like RequiredIfOtherFieldsGiven, but takes a single field name instead of a list." - def __init__(self, other_field_name, error_message=ugettext_lazy("Please enter both fields or leave them both empty.")): - RequiredIfOtherFieldsGiven.__init__(self, [other_field_name], error_message) - -class RequiredIfOtherFieldEquals(object): - def __init__(self, other_field, other_value, error_message=None, other_label=None): - self.other_field = other_field - self.other_value = other_value - other_label = other_label or other_value - self.error_message = error_message or lazy_inter(ugettext_lazy("This field must be given if %(field)s is %(value)s"), { - 'field': other_field, 'value': other_label}) - self.always_test = True - - def __call__(self, field_data, all_data): - if self.other_field in all_data and all_data[self.other_field] == self.other_value and not field_data: - raise ValidationError(self.error_message) - -class RequiredIfOtherFieldDoesNotEqual(object): - def __init__(self, other_field, other_value, other_label=None, error_message=None): - self.other_field = other_field - self.other_value = other_value - other_label = other_label or other_value - self.error_message = error_message or lazy_inter(ugettext_lazy("This field must be given if %(field)s is not %(value)s"), { - 'field': other_field, 'value': other_label}) - self.always_test = True - - def __call__(self, field_data, all_data): - if self.other_field in all_data and all_data[self.other_field] != self.other_value and not field_data: - raise ValidationError(self.error_message) - -class IsLessThanOtherField(object): - def __init__(self, other_field_name, error_message): - self.other, self.error_message = other_field_name, error_message - - def __call__(self, field_data, all_data): - if field_data > all_data[self.other]: - raise ValidationError, self.error_message - -class UniqueAmongstFieldsWithPrefix(object): - def __init__(self, field_name, prefix, error_message): - self.field_name, self.prefix = field_name, prefix - self.error_message = error_message or ugettext_lazy("Duplicate values are not allowed.") - - def __call__(self, field_data, all_data): - for field_name, value in all_data.items(): - if field_name != self.field_name and value == field_data: - raise ValidationError, self.error_message - -class NumberIsInRange(object): - """ - Validator that tests if a value is in a range (inclusive). - """ - def __init__(self, lower=None, upper=None, error_message=''): - self.lower, self.upper = lower, upper - if not error_message: - if lower and upper: - self.error_message = _("This value must be between %(lower)s and %(upper)s.") % {'lower': lower, 'upper': upper} - elif lower: - self.error_message = _("This value must be at least %s.") % lower - elif upper: - self.error_message = _("This value must be no more than %s.") % upper - else: - self.error_message = error_message - - def __call__(self, field_data, all_data): - # Try to make the value numeric. If this fails, we assume another - # validator will catch the problem. - try: - val = float(field_data) - except ValueError: - return - - # Now validate - if self.lower and self.upper and (val < self.lower or val > self.upper): - raise ValidationError(self.error_message) - elif self.lower and val < self.lower: - raise ValidationError(self.error_message) - elif self.upper and val > self.upper: - raise ValidationError(self.error_message) - -class IsAPowerOf(object): - """ - Usage: If you create an instance of the IsPowerOf validator: - v = IsAPowerOf(2) - - The following calls will succeed: - v(4, None) - v(8, None) - v(16, None) - - But this call: - v(17, None) - will raise "django.core.validators.ValidationError: ['This value must be a power of 2.']" - """ - def __init__(self, power_of): - self.power_of = power_of - - def __call__(self, field_data, all_data): - from math import log - val = log(int(field_data)) / log(self.power_of) - if val != int(val): - raise ValidationError, _("This value must be a power of %s.") % self.power_of - -class IsValidDecimal(object): - def __init__(self, max_digits, decimal_places): - self.max_digits, self.decimal_places = max_digits, decimal_places - - def __call__(self, field_data, all_data): - try: - val = Decimal(field_data) - except DecimalException: - raise ValidationError, _("Please enter a valid decimal number.") - - pieces = str(val).lstrip("-").split('.') - decimals = (len(pieces) == 2) and len(pieces[1]) or 0 - digits = len(pieces[0]) - - if digits + decimals > self.max_digits: - raise ValidationError, ungettext("Please enter a valid decimal number with at most %s total digit.", - "Please enter a valid decimal number with at most %s total digits.", self.max_digits) % self.max_digits - if digits > (self.max_digits - self.decimal_places): - raise ValidationError, ungettext( "Please enter a valid decimal number with a whole part of at most %s digit.", - "Please enter a valid decimal number with a whole part of at most %s digits.", str(self.max_digits-self.decimal_places)) % str(self.max_digits-self.decimal_places) - if decimals > self.decimal_places: - raise ValidationError, ungettext("Please enter a valid decimal number with at most %s decimal place.", - "Please enter a valid decimal number with at most %s decimal places.", self.decimal_places) % self.decimal_places - -def isValidFloat(field_data, all_data): - data = smart_str(field_data) - try: - float(data) - except ValueError: - raise ValidationError, _("Please enter a valid floating point number.") - -class HasAllowableSize(object): - """ - Checks that the file-upload field data is a certain size. min_size and - max_size are measurements in bytes. - """ - def __init__(self, min_size=None, max_size=None, min_error_message=None, max_error_message=None): - self.min_size, self.max_size = min_size, max_size - self.min_error_message = min_error_message or lazy_inter(ugettext_lazy("Make sure your uploaded file is at least %s bytes big."), min_size) - self.max_error_message = max_error_message or lazy_inter(ugettext_lazy("Make sure your uploaded file is at most %s bytes big."), max_size) - - def __call__(self, field_data, all_data): - try: - content = field_data.read() - except TypeError: - raise ValidationError, ugettext_lazy("No file was submitted. Check the encoding type on the form.") - if self.min_size is not None and len(content) < self.min_size: - raise ValidationError, self.min_error_message - if self.max_size is not None and len(content) > self.max_size: - raise ValidationError, self.max_error_message - -class MatchesRegularExpression(object): - """ - Checks that the field matches the given regular-expression. The regex - should be in string format, not already compiled. - """ - def __init__(self, regexp, error_message=ugettext_lazy("The format for this field is wrong.")): - self.regexp = re.compile(regexp) - self.error_message = error_message - - def __call__(self, field_data, all_data): - if not self.regexp.search(field_data): - raise ValidationError(self.error_message) - -class AnyValidator(object): - """ - This validator tries all given validators. If any one of them succeeds, - validation passes. If none of them succeeds, the given message is thrown - as a validation error. The message is rather unspecific, so it's best to - specify one on instantiation. - """ - def __init__(self, validator_list=None, error_message=ugettext_lazy("This field is invalid.")): - if validator_list is None: validator_list = [] - self.validator_list = validator_list - self.error_message = error_message - for v in validator_list: - if hasattr(v, 'always_test'): - self.always_test = True - - def __call__(self, field_data, all_data): - for v in self.validator_list: - try: - v(field_data, all_data) - return - except ValidationError, e: - pass - raise ValidationError(self.error_message) - -class URLMimeTypeCheck(object): - "Checks that the provided URL points to a document with a listed mime type" - class CouldNotRetrieve(ValidationError): - pass - class InvalidContentType(ValidationError): - pass - - def __init__(self, mime_type_list): - self.mime_type_list = mime_type_list - - def __call__(self, field_data, all_data): - import urllib2 - try: - isValidURL(field_data, all_data) - except ValidationError: - raise - try: - info = urllib2.urlopen(field_data).info() - except (urllib2.HTTPError, urllib2.URLError): - raise URLMimeTypeCheck.CouldNotRetrieve, _("Could not retrieve anything from %s.") % field_data - content_type = info['content-type'] - if content_type not in self.mime_type_list: - raise URLMimeTypeCheck.InvalidContentType, _("The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'.") % { - 'url': field_data, 'contenttype': content_type} - -class RelaxNGCompact(object): - "Validate against a Relax NG compact schema" - def __init__(self, schema_path, additional_root_element=None): - self.schema_path = schema_path - self.additional_root_element = additional_root_element - - def __call__(self, field_data, all_data): - import os, tempfile - if self.additional_root_element: - field_data = '<%(are)s>%(data)s\n' % { - 'are': self.additional_root_element, - 'data': field_data - } - filename = tempfile.mktemp() # Insecure, but nothing else worked - fp = open(filename, 'w') - fp.write(field_data) - fp.close() - if not os.path.exists(settings.JING_PATH): - raise Exception, "%s not found!" % settings.JING_PATH - p = os.popen('%s -c %s %s' % (settings.JING_PATH, self.schema_path, filename)) - errors = [line.strip() for line in p.readlines()] - p.close() - os.unlink(filename) - display_errors = [] - lines = field_data.split('\n') - for error in errors: - ignored, line, level, message = error.split(':', 3) - # Scrape the Jing error messages to reword them more nicely. - m = re.search(r'Expected "(.*?)" to terminate element starting on line (\d+)', message) - if m: - display_errors.append(_('Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with "%(start)s".)') % \ - {'tag':m.group(1).replace('/', ''), 'line':m.group(2), 'start':lines[int(m.group(2)) - 1][:30]}) - continue - if message.strip() == 'text not allowed here': - display_errors.append(_('Some text starting on line %(line)s is not allowed in that context. (Line starts with "%(start)s".)') % \ - {'line':line, 'start':lines[int(line) - 1][:30]}) - continue - m = re.search(r'\s*attribute "(.*?)" not allowed at this point; ignored', message) - if m: - display_errors.append(_('"%(attr)s" on line %(line)s is an invalid attribute. (Line starts with "%(start)s".)') % \ - {'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]}) - continue - m = re.search(r'\s*unknown element "(.*?)"', message) - if m: - display_errors.append(_('"<%(tag)s>" on line %(line)s is an invalid tag. (Line starts with "%(start)s".)') % \ - {'tag':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]}) - continue - if message.strip() == 'required attributes missing': - display_errors.append(_('A tag on line %(line)s is missing one or more required attributes. (Line starts with "%(start)s".)') % \ - {'line':line, 'start':lines[int(line) - 1][:30]}) - continue - m = re.search(r'\s*bad value for attribute "(.*?)"', message) - if m: - display_errors.append(_('The "%(attr)s" attribute on line %(line)s has an invalid value. (Line starts with "%(start)s".)') % \ - {'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]}) - continue - # Failing all those checks, use the default error message. - display_error = 'Line %s: %s [%s]' % (line, message, level.strip()) - display_errors.append(display_error) - if len(display_errors) > 0: - raise ValidationError, display_errors diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py index cbd685547e..5413133306 100644 --- a/django/db/models/__init__.py +++ b/django/db/models/__init__.py @@ -1,6 +1,5 @@ from django.conf import settings from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured -from django.core import validators from django.db import connection from django.db.models.loading import get_apps, get_app, get_models, get_model, register_models from django.db.models.query import Q @@ -9,7 +8,7 @@ from django.db.models.base import Model from django.db.models.fields import * from django.db.models.fields.subclassing import SubfieldBase from django.db.models.fields.files import FileField, ImageField -from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel, TABULAR, STACKED +from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel from django.db.models import signals # Admin stages. diff --git a/django/db/models/base.py b/django/db/models/base.py index 115d82bd4f..66cfb15be7 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -8,9 +8,7 @@ try: except NameError: from sets import Set as set # Python 2.3 fallback. -import django.db.models.manipulators # Imported to register signal handler. -import django.db.models.manager # Ditto. -from django.core import validators +import django.db.models.manager # Imported to register signal handler. from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError from django.db.models.fields import AutoField from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField @@ -320,9 +318,7 @@ class Model(object): # First, try an UPDATE. If that doesn't update anything, do an INSERT. pk_val = self._get_pk_val(meta) - # Note: the comparison with '' is required for compatibility with - # oldforms-style model creation. - pk_set = pk_val is not None and smart_unicode(pk_val) != u'' + pk_set = pk_val is not None record_exists = True manager = cls._default_manager if pk_set: diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index b8d05671b1..5c21e7db73 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -1,6 +1,7 @@ import copy import datetime import os +import re import time try: import decimal @@ -12,10 +13,8 @@ from django.db.models import signals from django.db.models.query_utils import QueryWrapper from django.dispatch import dispatcher from django.conf import settings -from django.core import validators -from django import oldforms from django import forms -from django.core.exceptions import ObjectDoesNotExist +from django.core import exceptions from django.utils.datastructures import DictWrapper from django.utils.functional import curry from django.utils.itercompat import tee @@ -34,17 +33,6 @@ BLANK_CHOICE_NONE = [("", "None")] class FieldDoesNotExist(Exception): pass -def manipulator_validator_unique(f, opts, self, field_data, all_data): - "Validates that the value is unique for this field." - lookup_type = f.get_validator_unique_lookup_type() - try: - old_obj = self.manager.get(**{lookup_type: field_data}) - except ObjectDoesNotExist: - return - if getattr(self, 'original_object', None) and self.original_object._get_pk_val() == old_obj._get_pk_val(): - return - raise validators.ValidationError, _("%(optname)s with this %(fieldname)s already exists.") % {'optname': capfirst(opts.verbose_name), 'fieldname': f.verbose_name} - # A guide to Field parameters: # # * name: The name of the field specifed in the model. @@ -73,11 +61,10 @@ class Field(object): def __init__(self, verbose_name=None, name=None, primary_key=False, max_length=None, unique=False, blank=False, null=False, - db_index=False, core=False, rel=None, default=NOT_PROVIDED, - editable=True, serialize=True, unique_for_date=None, - unique_for_month=None, unique_for_year=None, validator_list=None, - choices=None, help_text='', db_column=None, db_tablespace=None, - auto_created=False): + 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): self.name = name self.verbose_name = verbose_name self.primary_key = primary_key @@ -87,10 +74,10 @@ class Field(object): # option whenever '' is a possible value. if self.empty_strings_allowed and connection.features.interprets_empty_strings_as_nulls: self.null = True - self.core, self.rel, self.default = core, rel, default + self.rel = rel + self.default = default self.editable = editable self.serialize = serialize - self.validator_list = validator_list or [] self.unique_for_date, self.unique_for_month = unique_for_date, unique_for_month self.unique_for_year = unique_for_year self._choices = choices or [] @@ -126,8 +113,8 @@ class Field(object): def to_python(self, value): """ Converts the input value into the expected Python data type, raising - validators.ValidationError if the data can't be converted. Returns the - converted value. Subclasses should override this. + django.core.exceptions.ValidationError if the data can't be converted. + Returns the converted value. Subclasses should override this. """ return value @@ -252,93 +239,9 @@ class Field(object): return None return "" - def get_manipulator_field_names(self, name_prefix): - """ - Returns a list of field names that this object adds to the manipulator. - """ - return [name_prefix + self.name] - - def prepare_field_objs_and_params(self, manipulator, name_prefix): - params = {'validator_list': self.validator_list[:]} - if self.max_length and not self.choices: # Don't give SelectFields a max_length parameter. - params['max_length'] = self.max_length - - if self.choices: - field_objs = [oldforms.SelectField] - - params['choices'] = self.get_flatchoices() - else: - field_objs = self.get_manipulator_field_objs() - return (field_objs, params) - - def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True): - """ - Returns a list of oldforms.FormField instances for this field. It - calculates the choices at runtime, not at compile time. - - name_prefix is a prefix to prepend to the "field_name" argument. - rel is a boolean specifying whether this field is in a related context. - """ - field_objs, params = self.prepare_field_objs_and_params(manipulator, name_prefix) - - # Add the "unique" validator(s). - for field_name_list in opts.unique_together: - if field_name_list[0] == self.name: - params['validator_list'].append(getattr(manipulator, 'isUnique%s' % '_'.join(field_name_list))) - - # Add the "unique for..." validator(s). - if self.unique_for_date: - params['validator_list'].append(getattr(manipulator, 'isUnique%sFor%s' % (self.name, self.unique_for_date))) - if self.unique_for_month: - params['validator_list'].append(getattr(manipulator, 'isUnique%sFor%s' % (self.name, self.unique_for_month))) - if self.unique_for_year: - params['validator_list'].append(getattr(manipulator, 'isUnique%sFor%s' % (self.name, self.unique_for_year))) - if self.unique and not rel: - params['validator_list'].append(curry(manipulator_validator_unique, self, opts, manipulator)) - - # Only add is_required=True if the field cannot be blank. Primary keys - # are a special case, and fields in a related context should set this - # as False, because they'll be caught by a separate validator -- - # RequiredIfOtherFieldGiven. - params['is_required'] = not self.blank and not self.primary_key and not rel - - # BooleanFields (CheckboxFields) are a special case. They don't take - # is_required. - if isinstance(self, BooleanField): - del params['is_required'] - - # If this field is in a related context, check whether any other fields - # in the related object have core=True. If so, add a validator -- - # RequiredIfOtherFieldsGiven -- to this FormField. - if rel and not self.blank and not isinstance(self, AutoField) and not isinstance(self, FileField): - # First, get the core fields, if any. - core_field_names = [] - for f in opts.fields: - if f.core and f != self: - core_field_names.extend(f.get_manipulator_field_names(name_prefix)) - # Now, if there are any, add the validator to this FormField. - if core_field_names: - params['validator_list'].append(validators.RequiredIfOtherFieldsGiven(core_field_names, ugettext_lazy("This field is required."))) - - # Finally, add the field_names. - field_names = self.get_manipulator_field_names(name_prefix) - return [man(field_name=field_names[i], **params) for i, man in enumerate(field_objs)] - def get_validator_unique_lookup_type(self): return '%s__exact' % self.name - def get_manipulator_new_data(self, new_data, rel=False): - """ - Given the full new_data dictionary (from the manipulator), returns this - field's data. - """ - if rel: - return new_data.get(self.name, [self.get_default()])[0] - val = new_data.get(self.name, self.get_default()) - if not self.empty_strings_allowed and val == '' and self.null: - val = None - return val - def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH): """Returns choices with a default blank choices included, for use as SelectField choices for this field.""" @@ -366,19 +269,12 @@ class Field(object): else: return self.get_default() - def flatten_data(self, follow, obj=None): + def value_to_string(self, obj): """ - Returns a dictionary mapping the field's manipulator field names to its - "flattened" string values for the admin view. obj is the instance to - extract the values from. + Returns a string value of this field from the passed obj. + This is used by the serialization framework. """ - return {self.attname: self._get_val_from_obj(obj)} - - def get_follow(self, override=None): - if override != None: - return override - else: - return self.editable + return smart_unicode(self._get_val_from_obj(obj)) def bind(self, fieldmapping, original, bound_field_class): return bound_field_class(self, fieldmapping, original) @@ -432,29 +328,14 @@ class AutoField(Field): try: return int(value) except (TypeError, ValueError): - raise validators.ValidationError, _("This value must be an integer.") + raise exceptions.ValidationError( + _("This value must be an integer.")) def get_db_prep_value(self, value): if value is None: return None return int(value) - def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True): - if not rel: - return [] # Don't add a FormField unless it's in a related context. - return Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel, follow) - - def get_manipulator_field_objs(self): - return [oldforms.HiddenField] - - def get_manipulator_new_data(self, new_data, rel=False): - # Never going to be called - # Not in main change pages - # ignored in related context - if not rel: - return None - return Field.get_manipulator_new_data(self, new_data, rel) - def contribute_to_class(self, cls, name): assert not cls._meta.has_auto_field, "A model can't have more than one AutoField." super(AutoField, self).contribute_to_class(cls, name) @@ -478,25 +359,20 @@ 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 validators.ValidationError, _("This value must be either True or False.") + raise exceptions.ValidationError( + _("This value must be either True or False.")) def get_db_prep_value(self, value): if value is None: return None return bool(value) - def get_manipulator_field_objs(self): - return [oldforms.CheckboxField] - def formfield(self, **kwargs): defaults = {'form_class': forms.BooleanField} defaults.update(kwargs) return super(BooleanField, self).formfield(**defaults) class CharField(Field): - def get_manipulator_field_objs(self): - return [oldforms.TextField] - def get_internal_type(self): return "CharField" @@ -507,7 +383,8 @@ class CharField(Field): if self.null: return value else: - raise validators.ValidationError, ugettext_lazy("This field cannot be null.") + raise exceptions.ValidationError( + ugettext_lazy("This field cannot be null.")) return smart_unicode(value) def formfield(self, **kwargs): @@ -517,8 +394,9 @@ class CharField(Field): # TODO: Maybe move this into contrib, because it's specialized. class CommaSeparatedIntegerField(CharField): - def get_manipulator_field_objs(self): - return [oldforms.CommaSeparatedIntegerField] + pass + +ansi_date_re = re.compile(r'^\d{4}-\d{1,2}-\d{1,2}$') class DateField(Field): empty_strings_allowed = False @@ -540,11 +418,20 @@ class DateField(Field): return value.date() if isinstance(value, datetime.date): return value - validators.isValidANSIDate(value, None) + + if not ansi_date_re.search(value): + raise exceptions.ValidationError( + _('Enter a valid date in YYYY-MM-DD format.')) + # 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 + # produces much friendlier error messages. + year, month, day = map(int, value.split('-')) try: - return datetime.date(*time.strptime(value, '%Y-%m-%d')[:3]) - except ValueError: - raise validators.ValidationError, _('Enter a valid date in YYYY-MM-DD format.') + return datetime.date(year, month, day) + except ValueError, e: + msg = _('Invalid date: %s') % _(str(e)) + raise exceptions.ValidationError(msg) def pre_save(self, model_instance, add): if self.auto_now or (self.auto_now_add and add): @@ -562,13 +449,6 @@ class DateField(Field): setattr(cls, 'get_previous_by_%s' % self.name, curry(cls._get_next_or_previous_by_FIELD, field=self, is_next=False)) - # Needed because of horrible auto_now[_add] behaviour wrt. editable - def get_follow(self, override=None): - if override != None: - return override - else: - return self.editable or self.auto_now or self.auto_now_add - def get_db_prep_lookup(self, lookup_type, value): # For "__month" and "__day" lookups, convert the value to a string so # the database backend always sees a consistent type. @@ -580,16 +460,13 @@ class DateField(Field): # Casts dates into the format expected by the backend return connection.ops.value_to_db_date(self.to_python(value)) - def get_manipulator_field_objs(self): - return [oldforms.DateField] - - def flatten_data(self, follow, obj=None): + def value_to_string(self, obj): val = self._get_val_from_obj(obj) if val is None: data = '' else: data = datetime_safe.new_date(val).strftime("%Y-%m-%d") - return {self.attname: data} + return data def formfield(self, **kwargs): defaults = {'form_class': forms.DateField} @@ -616,7 +493,8 @@ class DateTimeField(DateField): value, usecs = value.split('.') usecs = int(usecs) except ValueError: - raise validators.ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM[:ss[.uuuuuu]] format.') + raise exceptions.ValidationError( + _('Enter a valid date/time in YYYY-MM-DD HH:MM[:ss[.uuuuuu]] format.')) else: usecs = 0 kwargs = {'microsecond': usecs} @@ -633,40 +511,21 @@ class DateTimeField(DateField): return datetime.datetime(*time.strptime(value, '%Y-%m-%d')[:3], **kwargs) except ValueError: - raise validators.ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM[:ss[.uuuuuu]] format.') + raise exceptions.ValidationError( + _('Enter a valid date/time in YYYY-MM-DD HH:MM[:ss[.uuuuuu]] format.')) def get_db_prep_value(self, value): # Casts dates into the format expected by the backend return connection.ops.value_to_db_datetime(self.to_python(value)) - def get_manipulator_field_objs(self): - return [oldforms.DateField, oldforms.TimeField] - - def get_manipulator_field_names(self, name_prefix): - return [name_prefix + self.name + '_date', name_prefix + self.name + '_time'] - - def get_manipulator_new_data(self, new_data, rel=False): - date_field, time_field = self.get_manipulator_field_names('') - if rel: - d = new_data.get(date_field, [None])[0] - t = new_data.get(time_field, [None])[0] - else: - d = new_data.get(date_field, None) - t = new_data.get(time_field, None) - if d is not None and t is not None: - return datetime.datetime.combine(d, t) - return self.get_default() - - def flatten_data(self,follow, obj = None): + def value_to_string(self, obj): val = self._get_val_from_obj(obj) - date_field, time_field = self.get_manipulator_field_names('') if val is None: - date_data = time_data = '' + data = '' else: d = datetime_safe.new_datetime(val) - date_data = d.strftime('%Y-%m-%d') - time_data = d.strftime('%H:%M:%S') - return {date_field: date_data, time_field: time_data} + data = d.strftime('%Y-%m-%d %H:%M:%S') + return data def formfield(self, **kwargs): defaults = {'form_class': forms.DateTimeField} @@ -688,7 +547,7 @@ class DecimalField(Field): try: return decimal.Decimal(value) except decimal.InvalidOperation: - raise validators.ValidationError( + raise exceptions.ValidationError( _("This value must be a decimal number.")) def _format(self, value): @@ -715,9 +574,6 @@ class DecimalField(Field): return connection.ops.value_to_db_decimal(self.to_python(value), self.max_digits, self.decimal_places) - def get_manipulator_field_objs(self): - return [curry(oldforms.DecimalField, max_digits=self.max_digits, decimal_places=self.decimal_places)] - def formfield(self, **kwargs): defaults = { 'max_digits': self.max_digits, @@ -732,9 +588,6 @@ class EmailField(CharField): kwargs['max_length'] = kwargs.get('max_length', 75) CharField.__init__(self, *args, **kwargs) - def get_manipulator_field_objs(self): - return [oldforms.EmailField] - def formfield(self, **kwargs): defaults = {'form_class': forms.EmailField} defaults.update(kwargs) @@ -756,9 +609,6 @@ class FilePathField(Field): defaults.update(kwargs) return super(FilePathField, self).formfield(**defaults) - def get_manipulator_field_objs(self): - return [curry(oldforms.FilePathField, path=self.path, match=self.match, recursive=self.recursive)] - def get_internal_type(self): return "FilePathField" @@ -770,9 +620,6 @@ class FloatField(Field): return None return float(value) - def get_manipulator_field_objs(self): - return [oldforms.FloatField] - def get_internal_type(self): return "FloatField" @@ -788,9 +635,6 @@ class IntegerField(Field): return None return int(value) - def get_manipulator_field_objs(self): - return [oldforms.IntegerField] - def get_internal_type(self): return "IntegerField" @@ -800,8 +644,9 @@ class IntegerField(Field): try: return int(value) except (TypeError, ValueError): - raise validators.ValidationError, _("This value must be an integer.") - + raise exceptions.ValidationError( + _("This value must be an integer.")) + def formfield(self, **kwargs): defaults = {'form_class': forms.IntegerField} defaults.update(kwargs) @@ -813,9 +658,6 @@ class IPAddressField(Field): kwargs['max_length'] = 15 Field.__init__(self, *args, **kwargs) - def get_manipulator_field_objs(self): - return [oldforms.IPAddressField] - def get_internal_type(self): return "IPAddressField" @@ -838,16 +680,14 @@ 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 validators.ValidationError, _("This value must be either None, True or False.") + raise exceptions.ValidationError( + _("This value must be either None, True or False.")) def get_db_prep_value(self, value): if value is None: return None return bool(value) - def get_manipulator_field_objs(self): - return [oldforms.NullBooleanField] - def formfield(self, **kwargs): defaults = { 'form_class': forms.NullBooleanField, @@ -858,9 +698,6 @@ class NullBooleanField(Field): return super(NullBooleanField, self).formfield(**defaults) class PhoneNumberField(Field): - def get_manipulator_field_objs(self): - return [oldforms.PhoneNumberField] - def get_internal_type(self): return "PhoneNumberField" @@ -871,9 +708,6 @@ class PhoneNumberField(Field): return super(PhoneNumberField, self).formfield(**defaults) class PositiveIntegerField(IntegerField): - def get_manipulator_field_objs(self): - return [oldforms.PositiveIntegerField] - def get_internal_type(self): return "PositiveIntegerField" @@ -883,9 +717,6 @@ class PositiveIntegerField(IntegerField): return super(PositiveIntegerField, self).formfield(**defaults) class PositiveSmallIntegerField(IntegerField): - def get_manipulator_field_objs(self): - return [oldforms.PositiveSmallIntegerField] - def get_internal_type(self): return "PositiveSmallIntegerField" @@ -897,7 +728,6 @@ class PositiveSmallIntegerField(IntegerField): class SlugField(CharField): def __init__(self, *args, **kwargs): kwargs['max_length'] = kwargs.get('max_length', 50) - kwargs.setdefault('validator_list', []).append(validators.isSlug) # Set db_index=True unless it's been set manually. if 'db_index' not in kwargs: kwargs['db_index'] = True @@ -907,23 +737,15 @@ class SlugField(CharField): return "SlugField" def formfield(self, **kwargs): - defaults = {'form_class': forms.RegexField, 'regex': r'^[a-zA-Z0-9_-]+$', - 'error_messages': {'invalid': _(u"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens.")}, - } + defaults = {'form_class': forms.SlugField} defaults.update(kwargs) return super(SlugField, self).formfield(**defaults) class SmallIntegerField(IntegerField): - def get_manipulator_field_objs(self): - return [oldforms.SmallIntegerField] - def get_internal_type(self): return "SmallIntegerField" class TextField(Field): - def get_manipulator_field_objs(self): - return [oldforms.LargeTextField] - def get_internal_type(self): return "TextField" @@ -957,7 +779,8 @@ class TimeField(Field): value, usecs = value.split('.') usecs = int(usecs) except ValueError: - raise validators.ValidationError, _('Enter a valid time in HH:MM[:ss[.uuuuuu]] format.') + raise exceptions.ValidationError( + _('Enter a valid time in HH:MM[:ss[.uuuuuu]] format.')) else: usecs = 0 kwargs = {'microsecond': usecs} @@ -970,7 +793,8 @@ class TimeField(Field): return datetime.time(*time.strptime(value, '%H:%M')[3:5], **kwargs) except ValueError: - raise validators.ValidationError, _('Enter a valid time in HH:MM[:ss[.uuuuuu]] format.') + raise exceptions.ValidationError( + _('Enter a valid time in HH:MM[:ss[.uuuuuu]] format.')) def pre_save(self, model_instance, add): if self.auto_now or (self.auto_now_add and add): @@ -984,12 +808,13 @@ class TimeField(Field): # Casts times into the format expected by the backend return connection.ops.value_to_db_time(self.to_python(value)) - def get_manipulator_field_objs(self): - return [oldforms.TimeField] - - def flatten_data(self,follow, obj = None): + def value_to_string(self, obj): val = self._get_val_from_obj(obj) - return {self.attname: (val is not None and val.strftime("%H:%M:%S") or '')} + if val is None: + data = '' + else: + data = val.strftime("%H:%M:%S") + return data def formfield(self, **kwargs): defaults = {'form_class': forms.TimeField} @@ -999,23 +824,15 @@ class TimeField(Field): class URLField(CharField): def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs): kwargs['max_length'] = kwargs.get('max_length', 200) - if verify_exists: - kwargs.setdefault('validator_list', []).append(validators.isExistingURL) self.verify_exists = verify_exists CharField.__init__(self, verbose_name, name, **kwargs) - def get_manipulator_field_objs(self): - return [oldforms.URLField] - def formfield(self, **kwargs): defaults = {'form_class': forms.URLField, 'verify_exists': self.verify_exists} defaults.update(kwargs) return super(URLField, self).formfield(**defaults) class USStateField(Field): - def get_manipulator_field_objs(self): - return [oldforms.USStateField] - def get_internal_type(self): return "USStateField" @@ -1029,7 +846,3 @@ class XMLField(TextField): def __init__(self, verbose_name=None, name=None, schema_path=None, **kwargs): self.schema_path = schema_path Field.__init__(self, verbose_name, name, **kwargs) - - def get_manipulator_field_objs(self): - return [curry(oldforms.XMLLargeTextField, schema_path=self.schema_path)] - diff --git a/django/db/models/fields/files.py b/django/db/models/fields/files.py index 935dee6162..f83f0cf575 100644 --- a/django/db/models/fields/files.py +++ b/django/db/models/fields/files.py @@ -11,9 +11,7 @@ from django.utils.functional import curry from django.db.models import signals from django.utils.encoding import force_unicode, smart_str from django.utils.translation import ugettext_lazy, ugettext as _ -from django import oldforms from django import forms -from django.core import validators from django.db.models.loading import cache class FieldFile(File): @@ -126,7 +124,7 @@ class FileField(Field): attr_class = FieldFile def __init__(self, verbose_name=None, name=None, upload_to='', storage=None, **kwargs): - for arg in ('core', 'primary_key', 'unique'): + for arg in ('primary_key', 'unique'): if arg in kwargs: raise TypeError("'%s' is not a valid argument for %s." % (arg, self.__class__)) @@ -153,42 +151,6 @@ class FileField(Field): return None return unicode(value) - def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True): - field_list = Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel, follow) - if not self.blank: - if rel: - # This validator makes sure FileFields work in a related context. - class RequiredFileField(object): - def __init__(self, other_field_names, other_file_field_name): - self.other_field_names = other_field_names - self.other_file_field_name = other_file_field_name - self.always_test = True - def __call__(self, field_data, all_data): - if not all_data.get(self.other_file_field_name, False): - c = validators.RequiredIfOtherFieldsGiven(self.other_field_names, ugettext_lazy("This field is required.")) - c(field_data, all_data) - # First, get the core fields, if any. - core_field_names = [] - for f in opts.fields: - if f.core and f != self: - core_field_names.extend(f.get_manipulator_field_names(name_prefix)) - # Now, if there are any, add the validator to this FormField. - if core_field_names: - field_list[0].validator_list.append(RequiredFileField(core_field_names, field_list[1].field_name)) - else: - v = validators.RequiredIfOtherFieldNotGiven(field_list[1].field_name, ugettext_lazy("This field is required.")) - v.always_test = True - field_list[0].validator_list.append(v) - field_list[0].is_required = field_list[1].is_required = False - - # If the raw path is passed in, validate it's under the MEDIA_ROOT. - def isWithinMediaRoot(field_data, all_data): - f = os.path.abspath(os.path.join(settings.MEDIA_ROOT, field_data)) - if not f.startswith(os.path.abspath(os.path.normpath(settings.MEDIA_ROOT))): - raise validators.ValidationError(_("Enter a valid filename.")) - field_list[1].validator_list.append(isWithinMediaRoot) - return field_list - def contribute_to_class(self, cls, name): super(FileField, self).contribute_to_class(cls, name) setattr(cls, self.name, FileDescriptor(self)) @@ -206,14 +168,9 @@ class FileField(Field): # Otherwise, just close the file, so it doesn't tie up resources. file.close() - def get_manipulator_field_objs(self): - return [oldforms.FileUploadField, oldforms.HiddenField] - - def get_manipulator_field_names(self, name_prefix): - return [name_prefix + self.name + '_file', name_prefix + self.name] - - def save_file(self, new_data, new_object, original_object, change, rel, save=True): - upload_field_name = self.get_manipulator_field_names('')[0] + def save_file(self, new_data, new_object, original_object, change, rel, + save=True): + upload_field_name = self.name + '_file' if new_data.get(upload_field_name, False): if rel: file = new_data[upload_field_name][0] @@ -282,9 +239,6 @@ class ImageField(FileField): self.width_field, self.height_field = width_field, height_field FileField.__init__(self, verbose_name, name, **kwargs) - def get_manipulator_field_objs(self): - return [oldforms.ImageUploadField, oldforms.HiddenField] - def formfield(self, **kwargs): defaults = {'form_class': forms.ImageField} defaults.update(kwargs) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 7601911f0c..db606e5042 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -4,10 +4,10 @@ from django.db.models.fields import AutoField, Field, IntegerField, PositiveInte 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.functional import curry -from django.core import validators -from django import oldforms +from django.core import exceptions from django import forms try: @@ -15,9 +15,6 @@ try: except NameError: from sets import Set as set # Python 2.3 fallback -# Values for Relation.edit_inline. -TABULAR, STACKED = 1, 2 - RECURSIVE_RELATIONSHIP_CONSTANT = 'self' pending_lookups = {} @@ -83,14 +80,6 @@ def do_pending_lookups(sender, **kwargs): signals.class_prepared.connect(do_pending_lookups) -def manipulator_valid_rel_key(f, self, field_data, all_data): - "Validates that the value is a valid foreign key" - klass = f.rel.to - try: - klass._default_manager.get(**{f.rel.field_name: field_data}) - except klass.DoesNotExist: - raise validators.ValidationError, _("Please enter a valid %s.") % f.verbose_name - #HACK class RelatedField(object): def contribute_to_class(self, cls, name): @@ -580,18 +569,14 @@ class ReverseManyRelatedObjectsDescriptor(object): manager.add(*value) class ManyToOneRel(object): - def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None, - max_num_in_admin=None, num_extra_on_change=1, edit_inline=False, - related_name=None, limit_choices_to=None, lookup_overrides=None, - parent_link=False): + def __init__(self, to, field_name, related_name=None, + limit_choices_to=None, lookup_overrides=None, parent_link=False): try: to._meta except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT assert isinstance(to, basestring), "'to' must be either a model, a model name or the string %r" % RECURSIVE_RELATIONSHIP_CONSTANT self.to, self.field_name = to, field_name - self.num_in_admin, self.edit_inline = num_in_admin, edit_inline - self.min_num_in_admin, self.max_num_in_admin = min_num_in_admin, max_num_in_admin - self.num_extra_on_change, self.related_name = num_extra_on_change, related_name + self.related_name = related_name if limit_choices_to is None: limit_choices_to = {} self.limit_choices_to = limit_choices_to @@ -611,29 +596,21 @@ class ManyToOneRel(object): return data[0] class OneToOneRel(ManyToOneRel): - def __init__(self, to, field_name, num_in_admin=0, min_num_in_admin=None, - max_num_in_admin=None, num_extra_on_change=None, edit_inline=False, - related_name=None, limit_choices_to=None, lookup_overrides=None, - parent_link=False): - # NOTE: *_num_in_admin and num_extra_on_change are intentionally - # ignored here. We accept them as parameters only to match the calling - # signature of ManyToOneRel.__init__(). - super(OneToOneRel, self).__init__(to, field_name, num_in_admin, - edit_inline=edit_inline, related_name=related_name, - limit_choices_to=limit_choices_to, + def __init__(self, to, field_name, related_name=None, + limit_choices_to=None, lookup_overrides=None, parent_link=False): + super(OneToOneRel, self).__init__(to, field_name, + related_name=related_name, limit_choices_to=limit_choices_to, lookup_overrides=lookup_overrides, parent_link=parent_link) self.multiple = False class ManyToManyRel(object): - def __init__(self, to, num_in_admin=0, related_name=None, - limit_choices_to=None, symmetrical=True, through=None): + def __init__(self, to, related_name=None, limit_choices_to=None, + symmetrical=True, through=None): self.to = to - self.num_in_admin = num_in_admin self.related_name = related_name if limit_choices_to is None: limit_choices_to = {} self.limit_choices_to = limit_choices_to - self.edit_inline = False self.symmetrical = symmetrical self.multiple = True self.through = through @@ -651,11 +628,6 @@ class ForeignKey(RelatedField, Field): kwargs['verbose_name'] = kwargs.get('verbose_name', None) kwargs['rel'] = rel_class(to, to_field, - num_in_admin=kwargs.pop('num_in_admin', 3), - min_num_in_admin=kwargs.pop('min_num_in_admin', None), - max_num_in_admin=kwargs.pop('max_num_in_admin', None), - num_extra_on_change=kwargs.pop('num_extra_on_change', 1), - edit_inline=kwargs.pop('edit_inline', False), related_name=kwargs.pop('related_name', None), limit_choices_to=kwargs.pop('limit_choices_to', None), lookup_overrides=kwargs.pop('lookup_overrides', None), @@ -670,15 +642,6 @@ class ForeignKey(RelatedField, Field): def get_validator_unique_lookup_type(self): return '%s__%s__exact' % (self.name, self.rel.get_related_field().name) - def prepare_field_objs_and_params(self, manipulator, name_prefix): - params = {'validator_list': self.validator_list[:], 'member_name': name_prefix + self.attname} - if self.null: - field_objs = [oldforms.NullSelectField] - else: - field_objs = [oldforms.SelectField] - params['choices'] = self.get_choices_default() - return field_objs, params - def get_default(self): "Here we check if the default value is an object and return the to_field if so." field_default = super(ForeignKey, self).get_default() @@ -686,17 +649,13 @@ class ForeignKey(RelatedField, Field): return getattr(field_default, self.rel.get_related_field().attname) return field_default - def get_manipulator_field_objs(self): - rel_field = self.rel.get_related_field() - return [oldforms.IntegerField] - def get_db_prep_save(self, value): if value == '' or value == None: return None else: return self.rel.get_related_field().get_db_prep_save(value) - def flatten_data(self, follow, obj=None): + def value_to_string(self, obj): if not obj: # In required many-to-one fields with only one available choice, # select that one available choice. Note: For SelectFields @@ -705,8 +664,8 @@ class ForeignKey(RelatedField, Field): if not self.blank and self.choices: choice_list = self.get_choices_default() if len(choice_list) == 2: - return {self.attname: choice_list[1][0]} - return Field.flatten_data(self, follow, obj) + return smart_unicode(choice_list[1][0]) + return Field.value_to_string(self, obj) def contribute_to_class(self, cls, name): super(ForeignKey, self).contribute_to_class(cls, name) @@ -744,8 +703,6 @@ class OneToOneField(ForeignKey): """ def __init__(self, to, to_field=None, **kwargs): kwargs['unique'] = True - if 'num_in_admin' not in kwargs: - kwargs['num_in_admin'] = 0 super(OneToOneField, self).__init__(to, to_field, OneToOneRel, **kwargs) def contribute_to_related_class(self, cls, related): @@ -768,7 +725,6 @@ class ManyToManyField(RelatedField, Field): kwargs['verbose_name'] = kwargs.get('verbose_name', None) kwargs['rel'] = ManyToManyRel(to, - num_in_admin=kwargs.pop('num_in_admin', 0), related_name=kwargs.pop('related_name', None), limit_choices_to=kwargs.pop('limit_choices_to', None), symmetrical=kwargs.pop('symmetrical', True), @@ -786,10 +742,6 @@ class ManyToManyField(RelatedField, Field): msg = ugettext_lazy('Hold down "Control", or "Command" on a Mac, to select more than one.') self.help_text = string_concat(self.help_text, ' ', msg) - def get_manipulator_field_objs(self): - choices = self.get_choices_default() - return [curry(oldforms.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)] - def get_choices_default(self): return Field.get_choices(self, include_blank=False) @@ -863,25 +815,27 @@ class ManyToManyField(RelatedField, Field): objects = mod._default_manager.in_bulk(pks) if len(objects) != len(pks): badkeys = [k for k in pks if k not in objects] - raise validators.ValidationError, ungettext("Please enter valid %(self)s IDs. The value %(value)r is invalid.", - "Please enter valid %(self)s IDs. The values %(value)r are invalid.", len(badkeys)) % { + raise exceptions.ValidationError( + ungettext("Please enter valid %(self)s IDs. The value %(value)r is invalid.", + "Please enter valid %(self)s IDs. The values %(value)r are invalid.", + len(badkeys)) % { 'self': self.verbose_name, 'value': len(badkeys) == 1 and badkeys[0] or tuple(badkeys), - } + }) - def flatten_data(self, follow, obj = None): - new_data = {} + def value_to_string(self, obj): + data = '' if obj: - instance_ids = [instance._get_pk_val() for instance in getattr(obj, self.name).all()] - new_data[self.name] = instance_ids + qs = getattr(obj, self.name).all() + data = [instance._get_pk_val() for instance in qs] else: # In required many-to-many fields with only one available choice, # select that one available choice. - if not self.blank and not self.rel.edit_inline: + if not self.blank: choices_list = self.get_choices_default() if len(choices_list) == 1: - new_data[self.name] = [choices_list[0][0]] - return new_data + data = [choices_list[0][0]] + return smart_unicode(data) def contribute_to_class(self, cls, name): super(ManyToManyField, self).contribute_to_class(cls, name) diff --git a/django/db/models/manipulators.py b/django/db/models/manipulators.py deleted file mode 100644 index c657d0158b..0000000000 --- a/django/db/models/manipulators.py +++ /dev/null @@ -1,333 +0,0 @@ -from django.core.exceptions import ObjectDoesNotExist -from django import oldforms -from django.core import validators -from django.db.models.fields import AutoField -from django.db.models.fields.files import FileField -from django.db.models import signals -from django.utils.functional import curry -from django.utils.datastructures import DotExpandedDict -from django.utils.text import capfirst -from django.utils.encoding import smart_str -from django.utils.translation import ugettext as _ -from django.utils import datetime_safe - -def add_manipulators(sender, **kwargs): - cls = sender - cls.add_to_class('AddManipulator', AutomaticAddManipulator) - cls.add_to_class('ChangeManipulator', AutomaticChangeManipulator) - -signals.class_prepared.connect(add_manipulators) - -class ManipulatorDescriptor(object): - # This class provides the functionality that makes the default model - # manipulators (AddManipulator and ChangeManipulator) available via the - # model class. - def __init__(self, name, base): - self.man = None # Cache of the manipulator class. - self.name = name - self.base = base - - def __get__(self, instance, model=None): - if instance != None: - raise AttributeError, "Manipulator cannot be accessed via instance" - else: - if not self.man: - # Create a class that inherits from the "Manipulator" class - # given in the model class (if specified) and the automatic - # manipulator. - bases = [self.base] - if hasattr(model, 'Manipulator'): - bases = [model.Manipulator] + bases - self.man = type(self.name, tuple(bases), {}) - self.man._prepare(model) - return self.man - -class AutomaticManipulator(oldforms.Manipulator): - def _prepare(cls, model): - cls.model = model - cls.manager = model._default_manager - cls.opts = model._meta - for field_name_list in cls.opts.unique_together: - setattr(cls, 'isUnique%s' % '_'.join(field_name_list), curry(manipulator_validator_unique_together, field_name_list, cls.opts)) - for f in cls.opts.fields: - if f.unique_for_date: - setattr(cls, 'isUnique%sFor%s' % (f.name, f.unique_for_date), curry(manipulator_validator_unique_for_date, f, cls.opts.get_field(f.unique_for_date), cls.opts, 'date')) - if f.unique_for_month: - setattr(cls, 'isUnique%sFor%s' % (f.name, f.unique_for_month), curry(manipulator_validator_unique_for_date, f, cls.opts.get_field(f.unique_for_month), cls.opts, 'month')) - if f.unique_for_year: - setattr(cls, 'isUnique%sFor%s' % (f.name, f.unique_for_year), curry(manipulator_validator_unique_for_date, f, cls.opts.get_field(f.unique_for_year), cls.opts, 'year')) - _prepare = classmethod(_prepare) - - def contribute_to_class(cls, other_cls, name): - setattr(other_cls, name, ManipulatorDescriptor(name, cls)) - contribute_to_class = classmethod(contribute_to_class) - - def __init__(self, follow=None): - self.follow = self.opts.get_follow(follow) - self.fields = [] - - for f in self.opts.fields + self.opts.many_to_many: - if self.follow.get(f.name, False): - self.fields.extend(f.get_manipulator_fields(self.opts, self, self.change)) - - # Add fields for related objects. - for f in self.opts.get_all_related_objects(): - if self.follow.get(f.name, False): - fol = self.follow[f.name] - self.fields.extend(f.get_manipulator_fields(self.opts, self, self.change, fol)) - - # Add field for ordering. - if self.change and self.opts.get_ordered_objects(): - self.fields.append(oldforms.CommaSeparatedIntegerField(field_name="order_")) - - def save(self, new_data): - # TODO: big cleanup when core fields go -> use recursive manipulators. - params = {} - for f in self.opts.fields: - # Fields with auto_now_add should keep their original value in the change stage. - auto_now_add = self.change and getattr(f, 'auto_now_add', False) - if self.follow.get(f.name, None) and not auto_now_add: - param = f.get_manipulator_new_data(new_data) - else: - if self.change: - param = getattr(self.original_object, f.attname) - else: - param = f.get_default() - params[f.attname] = param - - if self.change: - params[self.opts.pk.attname] = self.obj_key - - # First, create the basic object itself. - new_object = self.model(**params) - - # Now that the object's been created, save any uploaded files. - for f in self.opts.fields: - if isinstance(f, FileField): - f.save_file(new_data, new_object, self.change and self.original_object or None, self.change, rel=False, save=False) - - # Now save the object - new_object.save() - - # Calculate which primary fields have changed. - if self.change: - self.fields_added, self.fields_changed, self.fields_deleted = [], [], [] - for f in self.opts.fields: - if not f.primary_key and smart_str(getattr(self.original_object, f.attname)) != smart_str(getattr(new_object, f.attname)): - self.fields_changed.append(f.verbose_name) - - # Save many-to-many objects. Example: Set sites for a poll. - for f in self.opts.many_to_many: - if self.follow.get(f.name, None): - if not f.rel.edit_inline: - new_vals = new_data.getlist(f.name) - # First, clear the existing values. - rel_manager = getattr(new_object, f.name) - rel_manager.clear() - # Then, set the new values. - for n in new_vals: - rel_manager.add(f.rel.to._default_manager.get(pk=n)) - # TODO: Add to 'fields_changed' - - expanded_data = DotExpandedDict(dict(new_data)) - # Save many-to-one objects. Example: Add the Choice objects for a Poll. - for related in self.opts.get_all_related_objects(): - # Create obj_list, which is a DotExpandedDict such as this: - # [('0', {'id': ['940'], 'choice': ['This is the first choice']}), - # ('1', {'id': ['941'], 'choice': ['This is the second choice']}), - # ('2', {'id': [''], 'choice': ['']})] - child_follow = self.follow.get(related.name, None) - - if child_follow: - obj_list = expanded_data.get(related.var_name, {}).items() - if not obj_list: - continue - - obj_list.sort(lambda x, y: cmp(int(x[0]), int(y[0]))) - - # For each related item... - for _, rel_new_data in obj_list: - - params = {} - - # Keep track of which core=True fields were provided. - # If all core fields were given, the related object will be saved. - # If none of the core fields were given, the object will be deleted. - # If some, but not all, of the fields were given, the validator would - # have caught that. - all_cores_given, all_cores_blank = True, True - - # Get a reference to the old object. We'll use it to compare the - # old to the new, to see which fields have changed. - old_rel_obj = None - if self.change: - if rel_new_data[related.opts.pk.name][0]: - try: - old_rel_obj = getattr(self.original_object, related.get_accessor_name()).get(**{'%s__exact' % related.opts.pk.name: rel_new_data[related.opts.pk.attname][0]}) - except ObjectDoesNotExist: - pass - - for f in related.opts.fields: - if f.core and not isinstance(f, FileField) and f.get_manipulator_new_data(rel_new_data, rel=True) in (None, ''): - all_cores_given = False - elif f.core and not isinstance(f, FileField) and f.get_manipulator_new_data(rel_new_data, rel=True) not in (None, ''): - all_cores_blank = False - # If this field isn't editable, give it the same value it had - # previously, according to the given ID. If the ID wasn't - # given, use a default value. FileFields are also a special - # case, because they'll be dealt with later. - - if f == related.field: - param = getattr(new_object, related.field.rel.get_related_field().attname) - elif (not self.change) and isinstance(f, AutoField): - param = None - elif self.change and (isinstance(f, FileField) or not child_follow.get(f.name, None)): - if old_rel_obj: - param = getattr(old_rel_obj, f.column) - else: - param = f.get_default() - else: - param = f.get_manipulator_new_data(rel_new_data, rel=True) - if param != None: - params[f.attname] = param - - # Create the related item. - new_rel_obj = related.model(**params) - - # If all the core fields were provided (non-empty), save the item. - if all_cores_given: - new_rel_obj.save() - - # Save any uploaded files. - for f in related.opts.fields: - if child_follow.get(f.name, None): - if isinstance(f, FileField) and rel_new_data.get(f.name, False): - f.save_file(rel_new_data, new_rel_obj, self.change and old_rel_obj or None, old_rel_obj is not None, rel=True) - - # Calculate whether any fields have changed. - if self.change: - if not old_rel_obj: # This object didn't exist before. - self.fields_added.append('%s "%s"' % (related.opts.verbose_name, new_rel_obj)) - else: - for f in related.opts.fields: - if not f.primary_key and f != related.field and smart_str(getattr(old_rel_obj, f.attname)) != smart_str(getattr(new_rel_obj, f.attname)): - self.fields_changed.append('%s for %s "%s"' % (f.verbose_name, related.opts.verbose_name, new_rel_obj)) - - # Save many-to-many objects. - for f in related.opts.many_to_many: - if child_follow.get(f.name, None) and not f.rel.edit_inline: - new_value = rel_new_data[f.attname] - setattr(new_rel_obj, f.name, f.rel.to.objects.filter(pk__in=new_value)) - if self.change: - self.fields_changed.append('%s for %s "%s"' % (f.verbose_name, related.opts.verbose_name, new_rel_obj)) - - # If, in the change stage, all of the core fields were blank and - # the primary key (ID) was provided, delete the item. - if self.change and all_cores_blank and old_rel_obj: - new_rel_obj.delete() - self.fields_deleted.append('%s "%s"' % (related.opts.verbose_name, old_rel_obj)) - - # Save the order, if applicable. - if self.change and self.opts.get_ordered_objects(): - order = new_data['order_'] and map(int, new_data['order_'].split(',')) or [] - for rel_opts in self.opts.get_ordered_objects(): - getattr(new_object, 'set_%s_order' % rel_opts.object_name.lower())(order) - return new_object - - def get_related_objects(self): - return self.opts.get_followed_related_objects(self.follow) - - def flatten_data(self): - new_data = {} - obj = self.change and self.original_object or None - for f in self.opts.get_data_holders(self.follow): - fol = self.follow.get(f.name) - new_data.update(f.flatten_data(fol, obj)) - return new_data - -class AutomaticAddManipulator(AutomaticManipulator): - change = False - -class AutomaticChangeManipulator(AutomaticManipulator): - change = True - def __init__(self, obj_key, follow=None): - self.obj_key = obj_key - try: - self.original_object = self.manager.get(pk=obj_key) - except ObjectDoesNotExist: - # If the object doesn't exist, this might be a manipulator for a - # one-to-one related object that hasn't created its subobject yet. - # For example, this might be a Restaurant for a Place that doesn't - # yet have restaurant information. - if self.opts.one_to_one_field: - # Sanity check -- Make sure the "parent" object exists. - # For example, make sure the Place exists for the Restaurant. - # Let the ObjectDoesNotExist exception propagate up. - limit_choices_to = self.opts.one_to_one_field.rel.limit_choices_to - lookup_kwargs = {'%s__exact' % self.opts.one_to_one_field.rel.field_name: obj_key} - self.opts.one_to_one_field.rel.to.get_model_module().complex_filter(limit_choices_to).get(**lookup_kwargs) - params = dict([(f.attname, f.get_default()) for f in self.opts.fields]) - params[self.opts.pk.attname] = obj_key - self.original_object = self.opts.get_model_module().Klass(**params) - else: - raise - super(AutomaticChangeManipulator, self).__init__(follow=follow) - -def manipulator_validator_unique_together(field_name_list, opts, self, field_data, all_data): - from django.db.models.fields.related import ManyToOneRel - from django.utils.text import get_text_list - field_list = [opts.get_field(field_name) for field_name in field_name_list] - if isinstance(field_list[0].rel, ManyToOneRel): - kwargs = {'%s__%s__iexact' % (field_name_list[0], field_list[0].rel.field_name): field_data} - else: - kwargs = {'%s__iexact' % field_name_list[0]: field_data} - for f in field_list[1:]: - # This is really not going to work for fields that have different - # form fields, e.g. DateTime. - # This validation needs to occur after html2python to be effective. - field_val = all_data.get(f.name, None) - if field_val is None: - # This will be caught by another validator, assuming the field - # doesn't have blank=True. - return - if isinstance(f.rel, ManyToOneRel): - kwargs['%s__pk' % f.name] = field_val - else: - kwargs['%s__iexact' % f.name] = field_val - try: - old_obj = self.manager.get(**kwargs) - except ObjectDoesNotExist: - return - if hasattr(self, 'original_object') and self.original_object._get_pk_val() == old_obj._get_pk_val(): - pass - else: - raise validators.ValidationError, _("%(object)s with this %(type)s already exists for the given %(field)s.") % \ - {'object': capfirst(opts.verbose_name), 'type': field_list[0].verbose_name, 'field': get_text_list([f.verbose_name for f in field_list[1:]], _('and'))} - -def manipulator_validator_unique_for_date(from_field, date_field, opts, lookup_type, self, field_data, all_data): - from django.db.models.fields.related import ManyToOneRel - date_str = all_data.get(date_field.get_manipulator_field_names('')[0], None) - date_val = oldforms.DateField.html2python(date_str) - if date_val is None: - return # Date was invalid. This will be caught by another validator. - lookup_kwargs = {'%s__year' % date_field.name: date_val.year} - if isinstance(from_field.rel, ManyToOneRel): - lookup_kwargs['%s__pk' % from_field.name] = field_data - else: - lookup_kwargs['%s__iexact' % from_field.name] = field_data - if lookup_type in ('month', 'date'): - lookup_kwargs['%s__month' % date_field.name] = date_val.month - if lookup_type == 'date': - lookup_kwargs['%s__day' % date_field.name] = date_val.day - try: - old_obj = self.manager.get(**lookup_kwargs) - except ObjectDoesNotExist: - return - else: - if hasattr(self, 'original_object') and self.original_object._get_pk_val() == old_obj._get_pk_val(): - pass - else: - format_string = (lookup_type == 'date') and '%B %d, %Y' or '%B %Y' - date_val = datetime_safe.new_datetime(date_val) - raise validators.ValidationError, "Please enter a different %s. The one you entered is already being used for %s." % \ - (from_field.verbose_name, date_val.strftime(format_string)) diff --git a/django/db/models/options.py b/django/db/models/options.py index ffea6d5082..3648cf0710 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -396,28 +396,6 @@ class Options(object): self._related_many_to_many_cache = cache return cache - def get_followed_related_objects(self, follow=None): - if follow == None: - follow = self.get_follow() - return [f for f in self.get_all_related_objects() if follow.get(f.name, None)] - - def get_data_holders(self, follow=None): - if follow == None: - follow = self.get_follow() - return [f for f in self.fields + self.many_to_many + self.get_all_related_objects() if follow.get(f.name, None)] - - def get_follow(self, override=None): - follow = {} - for f in self.fields + self.many_to_many + self.get_all_related_objects(): - if override and f.name in override: - child_override = override[f.name] - else: - child_override = None - fol = f.get_follow(child_override) - if fol != None: - follow[f.name] = fol - return follow - def get_base_chain(self, model): """ Returns a list of parent classes leading to 'model' (order from closet @@ -459,28 +437,3 @@ class Options(object): # objects.append(opts) self._ordered_objects = objects return self._ordered_objects - - def has_field_type(self, field_type, follow=None): - """ - Returns True if this object's admin form has at least one of the given - field_type (e.g. FileField). - """ - # TODO: follow - if not hasattr(self, '_field_types'): - self._field_types = {} - if field_type not in self._field_types: - try: - # First check self.fields. - for f in self.fields: - if isinstance(f, field_type): - raise StopIteration - # Failing that, check related fields. - for related in self.get_followed_related_objects(follow): - for f in related.opts.fields: - if isinstance(f, field_type): - raise StopIteration - except StopIteration: - self._field_types[field_type] = True - else: - self._field_types[field_type] = False - return self._field_types[field_type] diff --git a/django/db/models/related.py b/django/db/models/related.py index 2c1dc5c516..ff7c787a93 100644 --- a/django/db/models/related.py +++ b/django/db/models/related.py @@ -15,113 +15,17 @@ class RelatedObject(object): self.model = model self.opts = model._meta self.field = field - self.edit_inline = field.rel.edit_inline self.name = '%s:%s' % (self.opts.app_label, self.opts.module_name) self.var_name = self.opts.object_name.lower() - def flatten_data(self, follow, obj=None): - new_data = {} - rel_instances = self.get_list(obj) - for i, rel_instance in enumerate(rel_instances): - instance_data = {} - for f in self.opts.fields + self.opts.many_to_many: - # TODO: Fix for recursive manipulators. - fol = follow.get(f.name, None) - if fol: - field_data = f.flatten_data(fol, rel_instance) - for name, value in field_data.items(): - instance_data['%s.%d.%s' % (self.var_name, i, name)] = value - new_data.update(instance_data) - return new_data - - def extract_data(self, data): - """ - Pull out the data meant for inline objects of this class, - i.e. anything starting with our module name. - """ - return data # TODO - - def get_list(self, parent_instance=None): - "Get the list of this type of object from an instance of the parent class." - if parent_instance is not None: - attr = getattr(parent_instance, self.get_accessor_name()) - if self.field.rel.multiple: - # For many-to-many relationships, return a list of objects - # corresponding to the xxx_num_in_admin options of the field - objects = list(attr.all()) - - count = len(objects) + self.field.rel.num_extra_on_change - if self.field.rel.min_num_in_admin: - count = max(count, self.field.rel.min_num_in_admin) - if self.field.rel.max_num_in_admin: - count = min(count, self.field.rel.max_num_in_admin) - - change = count - len(objects) - if change > 0: - return objects + [None] * change - if change < 0: - return objects[:change] - else: # Just right - return objects - else: - # A one-to-one relationship, so just return the single related - # object - return [attr] - else: - if self.field.rel.min_num_in_admin: - return [None] * max(self.field.rel.num_in_admin, self.field.rel.min_num_in_admin) - else: - return [None] * self.field.rel.num_in_admin - def get_db_prep_lookup(self, lookup_type, value): # Defer to the actual field definition for db prep return self.field.get_db_prep_lookup(lookup_type, value) - + def editable_fields(self): "Get the fields in this class that should be edited inline." return [f for f in self.opts.fields + self.opts.many_to_many if f.editable and f != self.field] - def get_follow(self, override=None): - if isinstance(override, bool): - if override: - over = {} - else: - return None - else: - if override: - over = override.copy() - elif self.edit_inline: - over = {} - else: - return None - - over[self.field.name] = False - return self.opts.get_follow(over) - - def get_manipulator_fields(self, opts, manipulator, change, follow): - if self.field.rel.multiple: - if change: - attr = getattr(manipulator.original_object, self.get_accessor_name()) - count = attr.count() - count += self.field.rel.num_extra_on_change - else: - count = self.field.rel.num_in_admin - if self.field.rel.min_num_in_admin: - count = max(count, self.field.rel.min_num_in_admin) - if self.field.rel.max_num_in_admin: - count = min(count, self.field.rel.max_num_in_admin) - else: - count = 1 - - fields = [] - for i in range(count): - for f in self.opts.fields + self.opts.many_to_many: - if follow.get(f.name, False): - prefix = '%s.%d.' % (self.var_name, i) - fields.extend(f.get_manipulator_fields(self.opts, manipulator, change, - name_prefix=prefix, rel=True)) - return fields - def __repr__(self): return "" % (self.name, self.field.name) diff --git a/django/forms/fields.py b/django/forms/fields.py index ee9b8c62f1..afbf6146b0 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -38,7 +38,7 @@ __all__ = ( 'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField', 'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField', 'ComboField', 'MultiValueField', 'FloatField', 'DecimalField', - 'SplitDateTimeField', 'IPAddressField', 'FilePathField', + 'SplitDateTimeField', 'IPAddressField', 'FilePathField', 'SlugField', ) # These values, if given to to_python(), will trigger the self.required check. @@ -835,3 +835,14 @@ class IPAddressField(RegexField): def __init__(self, *args, **kwargs): super(IPAddressField, self).__init__(ipv4_re, *args, **kwargs) + +slug_re = re.compile(r'^[-\w]+$') + +class SlugField(RegexField): + default_error_messages = { + 'invalid': _(u"Enter a valid 'slug' consisting of letters, numbers," + u" underscores or hyphens."), + } + + def __init__(self, *args, **kwargs): + super(SlugField, self).__init__(slug_re, *args, **kwargs) diff --git a/django/oldforms/__init__.py b/django/oldforms/__init__.py deleted file mode 100644 index b5698ab807..0000000000 --- a/django/oldforms/__init__.py +++ /dev/null @@ -1,1056 +0,0 @@ -from django.core import validators -from django.core.exceptions import PermissionDenied -from django.utils.html import escape -from django.utils.safestring import mark_safe -from django.conf import settings -from django.utils.translation import ugettext, ungettext -from django.utils.encoding import smart_unicode, force_unicode - -FORM_FIELD_ID_PREFIX = 'id_' - -class EmptyValue(Exception): - "This is raised when empty data is provided" - pass - -class Manipulator(object): - # List of permission strings. User must have at least one to manipulate. - # None means everybody has permission. - required_permission = '' - - def __init__(self): - # List of FormField objects - self.fields = [] - - def __getitem__(self, field_name): - "Looks up field by field name; raises KeyError on failure" - for field in self.fields: - if field.field_name == field_name: - return field - raise KeyError, "Field %s not found\n%s" % (field_name, repr(self.fields)) - - def __delitem__(self, field_name): - "Deletes the field with the given field name; raises KeyError on failure" - for i, field in enumerate(self.fields): - if field.field_name == field_name: - del self.fields[i] - return - raise KeyError, "Field %s not found" % field_name - - def check_permissions(self, user): - """Confirms user has required permissions to use this manipulator; raises - PermissionDenied on failure.""" - if self.required_permission is None: - return - if user.has_perm(self.required_permission): - return - raise PermissionDenied - - def prepare(self, new_data): - """ - Makes any necessary preparations to new_data, in place, before data has - been validated. - """ - for field in self.fields: - field.prepare(new_data) - - def get_validation_errors(self, new_data): - "Returns dictionary mapping field_names to error-message lists" - errors = {} - self.prepare(new_data) - for field in self.fields: - errors.update(field.get_validation_errors(new_data)) - val_name = 'validate_%s' % field.field_name - if hasattr(self, val_name): - val = getattr(self, val_name) - try: - field.run_validator(new_data, val) - except (validators.ValidationError, validators.CriticalValidationError), e: - errors.setdefault(field.field_name, []).extend(e.messages) - -# if field.is_required and not new_data.get(field.field_name, False): -# errors.setdefault(field.field_name, []).append(ugettext_lazy('This field is required.')) -# continue -# try: -# validator_list = field.validator_list -# if hasattr(self, 'validate_%s' % field.field_name): -# validator_list.append(getattr(self, 'validate_%s' % field.field_name)) -# for validator in validator_list: -# if field.is_required or new_data.get(field.field_name, False) or hasattr(validator, 'always_test'): -# try: -# if hasattr(field, 'requires_data_list'): -# validator(new_data.getlist(field.field_name), new_data) -# else: -# validator(new_data.get(field.field_name, ''), new_data) -# except validators.ValidationError, e: -# errors.setdefault(field.field_name, []).extend(e.messages) -# # If a CriticalValidationError is raised, ignore any other ValidationErrors -# # for this particular field -# except validators.CriticalValidationError, e: -# errors.setdefault(field.field_name, []).extend(e.messages) - return errors - - def save(self, new_data): - "Saves the changes and returns the new object" - # changes is a dictionary-like object keyed by field_name - raise NotImplementedError - - def do_html2python(self, new_data): - """ - Convert the data from HTML data types to Python datatypes, changing the - object in place. This happens after validation but before storage. This - must happen after validation because html2python functions aren't - expected to deal with invalid input. - """ - for field in self.fields: - field.convert_post_data(new_data) - -class FormWrapper(object): - """ - A wrapper linking a Manipulator to the template system. - This allows dictionary-style lookups of formfields. It also handles feeding - prepopulated data and validation error messages to the formfield objects. - """ - def __init__(self, manipulator, data=None, error_dict=None, edit_inline=True): - self.manipulator = manipulator - if data is None: - data = {} - if error_dict is None: - error_dict = {} - self.data = data - self.error_dict = error_dict - self._inline_collections = None - self.edit_inline = edit_inline - - def __repr__(self): - return repr(self.__dict__) - - def __getitem__(self, key): - for field in self.manipulator.fields: - if field.field_name == key: - data = field.extract_data(self.data) - return FormFieldWrapper(field, data, self.error_dict.get(field.field_name, [])) - if self.edit_inline: - self.fill_inline_collections() - for inline_collection in self._inline_collections: - # The 'orig_name' comparison is for backwards compatibility - # with hand-crafted forms. - if inline_collection.name == key or (':' not in key and inline_collection.orig_name == key): - return inline_collection - raise KeyError, "Could not find Formfield or InlineObjectCollection named %r" % key - - def fill_inline_collections(self): - if not self._inline_collections: - ic = [] - related_objects = self.manipulator.get_related_objects() - for rel_obj in related_objects: - data = rel_obj.extract_data(self.data) - inline_collection = InlineObjectCollection(self.manipulator, rel_obj, data, self.error_dict) - ic.append(inline_collection) - self._inline_collections = ic - - def has_errors(self): - return self.error_dict != {} - - def _get_fields(self): - try: - return self._fields - except AttributeError: - self._fields = [self.__getitem__(field.field_name) for field in self.manipulator.fields] - return self._fields - - fields = property(_get_fields) - -class FormFieldWrapper(object): - "A bridge between the template system and an individual form field. Used by FormWrapper." - def __init__(self, formfield, data, error_list): - self.formfield, self.data, self.error_list = formfield, data, error_list - self.field_name = self.formfield.field_name # for convenience in templates - - def __str__(self): - "Renders the field" - return unicode(self).encode('utf-8') - - def __unicode__(self): - "Renders the field" - return force_unicode(self.formfield.render(self.data)) - - def __repr__(self): - return '' % self.formfield.field_name - - def field_list(self): - """ - Like __str__(), but returns a list. Use this when the field's render() - method returns a list. - """ - return self.formfield.render(self.data) - - def errors(self): - return self.error_list - - def html_error_list(self): - if self.errors(): - return mark_safe('' % '
  • '.join([escape(e) for e in self.errors()])) - else: - return mark_safe('') - - def get_id(self): - return self.formfield.get_id() - -class FormFieldCollection(FormFieldWrapper): - "A utility class that gives the template access to a dict of FormFieldWrappers" - def __init__(self, formfield_dict): - self.formfield_dict = formfield_dict - - def __str__(self): - return unicode(self).encode('utf-8') - - def __unicode__(self): - return unicode(self.formfield_dict) - - def __getitem__(self, template_key): - "Look up field by template key; raise KeyError on failure" - return self.formfield_dict[template_key] - - def __repr__(self): - return "" % self.formfield_dict - - def errors(self): - "Returns list of all errors in this collection's formfields" - errors = [] - for field in self.formfield_dict.values(): - if hasattr(field, 'errors'): - errors.extend(field.errors()) - return errors - - def has_errors(self): - return bool(len(self.errors())) - - def html_combined_error_list(self): - return mark_safe(''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')])) - -class InlineObjectCollection(object): - "An object that acts like a sparse list of form field collections." - def __init__(self, parent_manipulator, rel_obj, data, errors): - self.parent_manipulator = parent_manipulator - self.rel_obj = rel_obj - self.data = data - self.errors = errors - self._collections = None - self.name = rel_obj.name - # This is the name used prior to fixing #1839. Needs for backwards - # compatibility. - self.orig_name = rel_obj.opts.module_name - - def __len__(self): - self.fill() - return self._collections.__len__() - - def __getitem__(self, k): - self.fill() - return self._collections.__getitem__(k) - - def __setitem__(self, k, v): - self.fill() - return self._collections.__setitem__(k,v) - - def __delitem__(self, k): - self.fill() - return self._collections.__delitem__(k) - - def __iter__(self): - self.fill() - return iter(self._collections.values()) - - def items(self): - self.fill() - return self._collections.items() - - def fill(self): - if self._collections: - return - else: - var_name = self.rel_obj.opts.object_name.lower() - collections = {} - orig = None - if hasattr(self.parent_manipulator, 'original_object'): - orig = self.parent_manipulator.original_object - orig_list = self.rel_obj.get_list(orig) - - for i, instance in enumerate(orig_list): - collection = {'original': instance} - for f in self.rel_obj.editable_fields(): - for field_name in f.get_manipulator_field_names(''): - full_field_name = '%s.%d.%s' % (var_name, i, field_name) - field = self.parent_manipulator[full_field_name] - data = field.extract_data(self.data) - errors = self.errors.get(full_field_name, []) - collection[field_name] = FormFieldWrapper(field, data, errors) - collections[i] = FormFieldCollection(collection) - self._collections = collections - - -class FormField(object): - """Abstract class representing a form field. - - Classes that extend FormField should define the following attributes: - field_name - The field's name for use by programs. - validator_list - A list of validation tests (callback functions) that the data for - this field must pass in order to be added or changed. - is_required - A Boolean. Is it a required field? - Subclasses should also implement a render(data) method, which is responsible - for rending the form field in XHTML. - """ - - def __str__(self): - return unicode(self).encode('utf-8') - - def __unicode__(self): - return self.render(u'') - - def __repr__(self): - return 'FormField "%s"' % self.field_name - - def prepare(self, new_data): - "Hook for doing something to new_data (in place) before validation." - pass - - def html2python(data): - "Hook for converting an HTML datatype (e.g. 'on' for checkboxes) to a Python type" - return data - html2python = staticmethod(html2python) - - def render(self, data): - raise NotImplementedError - - def get_member_name(self): - if hasattr(self, 'member_name'): - return self.member_name - else: - return self.field_name - - def extract_data(self, data_dict): - if hasattr(self, 'requires_data_list') and hasattr(data_dict, 'getlist'): - data = data_dict.getlist(self.get_member_name()) - else: - data = data_dict.get(self.get_member_name(), None) - if data is None: - data = '' - return data - - def convert_post_data(self, new_data): - name = self.get_member_name() - if self.field_name in new_data: - d = new_data.getlist(self.field_name) - try: - converted_data = [self.__class__.html2python(data) for data in d] - except ValueError: - converted_data = d - new_data.setlist(name, converted_data) - else: - try: - #individual fields deal with None values themselves - new_data.setlist(name, [self.__class__.html2python(None)]) - except EmptyValue: - new_data.setlist(name, []) - - - def run_validator(self, new_data, validator): - if self.is_required or new_data.get(self.field_name, False) or hasattr(validator, 'always_test'): - if hasattr(self, 'requires_data_list'): - validator(new_data.getlist(self.field_name), new_data) - else: - validator(new_data.get(self.field_name, ''), new_data) - - def get_validation_errors(self, new_data): - errors = {} - if self.is_required and not new_data.get(self.field_name, False): - errors.setdefault(self.field_name, []).append(ugettext('This field is required.')) - return errors - try: - for validator in self.validator_list: - try: - self.run_validator(new_data, validator) - except validators.ValidationError, e: - errors.setdefault(self.field_name, []).extend(e.messages) - # If a CriticalValidationError is raised, ignore any other ValidationErrors - # for this particular field - except validators.CriticalValidationError, e: - errors.setdefault(self.field_name, []).extend(e.messages) - return errors - - def get_id(self): - "Returns the HTML 'id' attribute for this form field." - return FORM_FIELD_ID_PREFIX + self.field_name - -#################### -# GENERIC WIDGETS # -#################### - -class TextField(FormField): - input_type = "text" - def __init__(self, field_name, length=30, max_length=None, is_required=False, validator_list=None, member_name=None): - if validator_list is None: validator_list = [] - self.field_name = field_name - self.length, self.max_length = length, max_length - self.is_required = is_required - self.validator_list = [self.isValidLength, self.hasNoNewlines] + validator_list - if member_name != None: - self.member_name = member_name - - def isValidLength(self, data, form): - if data and self.max_length and len(smart_unicode(data)) > self.max_length: - raise validators.ValidationError, ungettext("Ensure your text is less than %s character.", - "Ensure your text is less than %s characters.", self.max_length) % self.max_length - - def hasNoNewlines(self, data, form): - if data and '\n' in data: - raise validators.ValidationError, ugettext("Line breaks are not allowed here.") - - def render(self, data): - if data is None: - data = u'' - max_length = u'' - if self.max_length: - max_length = u'maxlength="%s" ' % self.max_length - return mark_safe(u'' % \ - (self.input_type, self.get_id(), self.__class__.__name__, self.is_required and u' required' or '', - self.field_name, self.length, escape(data), max_length)) - - def html2python(data): - return data - html2python = staticmethod(html2python) - -class PasswordField(TextField): - input_type = "password" - -class LargeTextField(TextField): - def __init__(self, field_name, rows=10, cols=40, is_required=False, validator_list=None, max_length=None): - if validator_list is None: validator_list = [] - self.field_name = field_name - self.rows, self.cols, self.is_required = rows, cols, is_required - self.validator_list = validator_list[:] - if max_length: - self.validator_list.append(self.isValidLength) - self.max_length = max_length - - def render(self, data): - if data is None: - data = '' - return mark_safe(u'' % \ - (self.get_id(), self.__class__.__name__, self.is_required and u' required' or u'', - self.field_name, self.rows, self.cols, escape(data))) - -class HiddenField(FormField): - def __init__(self, field_name, is_required=False, validator_list=None, max_length=None): - if validator_list is None: validator_list = [] - self.field_name, self.is_required = field_name, is_required - self.validator_list = validator_list[:] - - def render(self, data): - return mark_safe(u'' % \ - (self.get_id(), self.field_name, escape(data))) - -class CheckboxField(FormField): - def __init__(self, field_name, checked_by_default=False, validator_list=None, is_required=False): - if validator_list is None: validator_list = [] - self.field_name = field_name - self.checked_by_default = checked_by_default - self.is_required = is_required - self.validator_list = validator_list[:] - - def render(self, data): - checked_html = '' - if data or (data is '' and self.checked_by_default): - checked_html = ' checked="checked"' - return mark_safe(u'' % \ - (self.get_id(), self.__class__.__name__, - self.field_name, checked_html)) - - def html2python(data): - "Convert value from browser ('on' or '') to a Python boolean" - if data == 'on': - return True - return False - html2python = staticmethod(html2python) - -class SelectField(FormField): - def __init__(self, field_name, choices=None, size=1, is_required=False, validator_list=None, member_name=None): - if validator_list is None: validator_list = [] - if choices is None: choices = [] - choices = [(k, smart_unicode(v, strings_only=True)) for k, v in choices] - self.field_name = field_name - # choices is a list of (value, human-readable key) tuples because order matters - self.choices, self.size, self.is_required = choices, size, is_required - self.validator_list = [self.isValidChoice] + validator_list - if member_name != None: - self.member_name = member_name - - def render(self, data): - output = [u'') - return mark_safe(u'\n'.join(output)) - - def isValidChoice(self, data, form): - str_data = smart_unicode(data) - str_choices = [smart_unicode(item[0]) for item in self.choices] - if str_data not in str_choices: - raise validators.ValidationError, ugettext("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data': str_data, 'choices': str_choices} - -class NullSelectField(SelectField): - "This SelectField converts blank fields to None" - def html2python(data): - if not data: - return None - return data - html2python = staticmethod(html2python) - -class RadioSelectField(FormField): - def __init__(self, field_name, choices=None, ul_class='', is_required=False, validator_list=None, member_name=None): - if validator_list is None: validator_list = [] - if choices is None: choices = [] - choices = [(k, smart_unicode(v)) for k, v in choices] - self.field_name = field_name - # choices is a list of (value, human-readable key) tuples because order matters - self.choices, self.is_required = choices, is_required - self.validator_list = [self.isValidChoice] + validator_list - self.ul_class = ul_class - if member_name != None: - self.member_name = member_name - - def render(self, data): - """ - Returns a special object, RadioFieldRenderer, that is iterable *and* - has a default unicode() rendered output. - - This allows for flexible use in templates. You can just use the default - rendering: - - {{ field_name }} - - ...which will output the radio buttons in an unordered list. - Or, you can manually traverse each radio option for special layout: - - {% for option in field_name.field_list %} - {{ option.field }} {{ option.label }}
    - {% endfor %} - """ - class RadioFieldRenderer: - def __init__(self, datalist, ul_class): - self.datalist, self.ul_class = datalist, ul_class - def __unicode__(self): - "Default unicode() output for this radio field -- a
      " - output = [u'' % (self.ul_class and u' class="%s"' % self.ul_class or u'')] - output.extend([u'
    • %s %s
    • ' % (d['field'], d['label']) for d in self.datalist]) - output.append(u'
    ') - return mark_safe(u''.join(output)) - def __iter__(self): - for d in self.datalist: - yield d - def __len__(self): - return len(self.datalist) - datalist = [] - str_data = smart_unicode(data) # normalize to string - for i, (value, display_name) in enumerate(self.choices): - selected_html = '' - if smart_unicode(value) == str_data: - selected_html = u' checked="checked"' - datalist.append({ - 'value': value, - 'name': display_name, - 'field': mark_safe(u'' % \ - (self.get_id() + u'_' + unicode(i), self.field_name, value, selected_html)), - 'label': mark_safe(u'' % \ - (self.get_id() + u'_' + unicode(i), display_name), - )}) - return RadioFieldRenderer(datalist, self.ul_class) - - def isValidChoice(self, data, form): - str_data = smart_unicode(data) - str_choices = [smart_unicode(item[0]) for item in self.choices] - if str_data not in str_choices: - raise validators.ValidationError, ugettext("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data':str_data, 'choices':str_choices} - -class NullBooleanField(SelectField): - "This SelectField provides 'Yes', 'No' and 'Unknown', mapping results to True, False or None" - def __init__(self, field_name, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - SelectField.__init__(self, field_name, choices=[('1', ugettext('Unknown')), ('2', ugettext('Yes')), ('3', ugettext('No'))], - is_required=is_required, validator_list=validator_list) - - def render(self, data): - if data is None: data = '1' - elif data == True: data = '2' - elif data == False: data = '3' - return SelectField.render(self, data) - - def html2python(data): - return {None: None, '1': None, '2': True, '3': False}[data] - html2python = staticmethod(html2python) - -class SelectMultipleField(SelectField): - requires_data_list = True - def render(self, data): - output = [u'') - return mark_safe(u'\n'.join(output)) - - def isValidChoice(self, field_data, all_data): - # data is something like ['1', '2', '3'] - str_choices = [smart_unicode(item[0]) for item in self.choices] - for val in map(smart_unicode, field_data): - if val not in str_choices: - raise validators.ValidationError, ugettext("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data':val, 'choices':str_choices} - - def html2python(data): - if data is None: - raise EmptyValue - return data - html2python = staticmethod(html2python) - -class CheckboxSelectMultipleField(SelectMultipleField): - """ - This has an identical interface to SelectMultipleField, except the rendered - widget is different. Instead of a es. - - Of course, that results in multiple form elements for the same "single" - field, so this class's prepare() method flattens the split data elements - back into the single list that validators, renderers and save() expect. - """ - requires_data_list = True - def __init__(self, field_name, choices=None, ul_class='', validator_list=None): - if validator_list is None: validator_list = [] - if choices is None: choices = [] - self.ul_class = ul_class - SelectMultipleField.__init__(self, field_name, choices, size=1, is_required=False, validator_list=validator_list) - - def prepare(self, new_data): - # new_data has "split" this field into several fields, so flatten it - # back into a single list. - data_list = [] - for value, readable_value in self.choices: - if new_data.get('%s%s' % (self.field_name, value), '') == 'on': - data_list.append(value) - new_data.setlist(self.field_name, data_list) - - def render(self, data): - output = [u'' % (self.ul_class and u' class="%s"' % self.ul_class or u'')] - str_data_list = map(smart_unicode, data) # normalize to strings - for value, choice in self.choices: - checked_html = u'' - if smart_unicode(value) in str_data_list: - checked_html = u' checked="checked"' - field_name = u'%s%s' % (self.field_name, value) - output.append(u'
  • ' % \ - (self.get_id() + escape(value), self.__class__.__name__, field_name, checked_html, - self.get_id() + escape(value), choice)) - output.append(u'') - return mark_safe(u'\n'.join(output)) - -#################### -# FILE UPLOADS # -#################### - -class FileUploadField(FormField): - def __init__(self, field_name, is_required=False, validator_list=None, max_length=None): - if validator_list is None: validator_list = [] - self.field_name, self.is_required = field_name, is_required - self.validator_list = [self.isNonEmptyFile] + validator_list - - def isNonEmptyFile(self, new_data, all_data): - if hasattr(new_data, 'upload_errors'): - upload_errors = new_data.upload_errors() - if upload_errors: - raise validators.CriticalValidationError, upload_errors - try: - file_size = new_data.size - except AttributeError: - file_size = len(new_data['content']) - if not file_size: - raise validators.CriticalValidationError, ugettext("The submitted file is empty.") - - def render(self, data): - return mark_safe(u'' % \ - (self.get_id(), self.__class__.__name__, self.field_name)) - - def prepare(self, new_data): - if hasattr(new_data, 'upload_errors'): - upload_errors = new_data.upload_errors() - new_data[self.field_name] = { '_file_upload_error': upload_errors } - - def html2python(data): - if data is None: - raise EmptyValue - return data - html2python = staticmethod(html2python) - -class ImageUploadField(FileUploadField): - "A FileUploadField that raises CriticalValidationError if the uploaded file isn't an image." - def __init__(self, *args, **kwargs): - FileUploadField.__init__(self, *args, **kwargs) - self.validator_list.insert(0, self.isValidImage) - - def isValidImage(self, field_data, all_data): - try: - validators.isValidImage(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - -#################### -# INTEGERS/FLOATS # -#################### - -class IntegerField(TextField): - def __init__(self, field_name, length=10, max_length=None, is_required=False, validator_list=None, member_name=None): - if validator_list is None: validator_list = [] - validator_list = [self.isInteger] + validator_list - if member_name is not None: - self.member_name = member_name - TextField.__init__(self, field_name, length, max_length, is_required, validator_list) - - def isInteger(self, field_data, all_data): - try: - validators.isInteger(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - - def html2python(data): - if data == '' or data is None: - return None - return int(data) - html2python = staticmethod(html2python) - -class SmallIntegerField(IntegerField): - def __init__(self, field_name, length=5, max_length=5, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isSmallInteger] + validator_list - IntegerField.__init__(self, field_name, length, max_length, is_required, validator_list) - - def isSmallInteger(self, field_data, all_data): - if not -32768 <= int(field_data) <= 32767: - raise validators.CriticalValidationError, ugettext("Enter a whole number between -32,768 and 32,767.") - -class PositiveIntegerField(IntegerField): - def __init__(self, field_name, length=10, max_length=None, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isPositive] + validator_list - IntegerField.__init__(self, field_name, length, max_length, is_required, validator_list) - - def isPositive(self, field_data, all_data): - if int(field_data) < 0: - raise validators.CriticalValidationError, ugettext("Enter a positive number.") - -class PositiveSmallIntegerField(IntegerField): - def __init__(self, field_name, length=5, max_length=None, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isPositiveSmall] + validator_list - IntegerField.__init__(self, field_name, length, max_length, is_required, validator_list) - - def isPositiveSmall(self, field_data, all_data): - if not 0 <= int(field_data) <= 32767: - raise validators.CriticalValidationError, ugettext("Enter a whole number between 0 and 32,767.") - -class FloatField(TextField): - def __init__(self, field_name, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [validators.isValidFloat] + validator_list - TextField.__init__(self, field_name, is_required=is_required, validator_list=validator_list) - - def html2python(data): - if data == '' or data is None: - return None - return float(data) - html2python = staticmethod(html2python) - -class DecimalField(TextField): - def __init__(self, field_name, max_digits, decimal_places, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - self.max_digits, self.decimal_places = max_digits, decimal_places - validator_list = [self.isValidDecimal] + validator_list - # Initialise the TextField, making sure it's large enough to fit the number with a - sign and a decimal point. - super(DecimalField, self).__init__(field_name, max_digits+2, max_digits+2, is_required, validator_list) - - def isValidDecimal(self, field_data, all_data): - v = validators.IsValidDecimal(self.max_digits, self.decimal_places) - try: - v(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - - def html2python(data): - if data == '' or data is None: - return None - try: - import decimal - except ImportError: - from django.utils import _decimal as decimal - try: - return decimal.Decimal(data) - except decimal.InvalidOperation, e: - raise ValueError, e - html2python = staticmethod(html2python) - -#################### -# DATES AND TIMES # -#################### - -class DatetimeField(TextField): - """A FormField that automatically converts its data to a datetime.datetime object. - The data should be in the format YYYY-MM-DD HH:MM:SS.""" - def __init__(self, field_name, length=30, max_length=None, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - self.field_name = field_name - self.length, self.max_length = length, max_length - self.is_required = is_required - self.validator_list = [validators.isValidANSIDatetime] + validator_list - - def html2python(data): - "Converts the field into a datetime.datetime object" - import datetime - try: - date, time = data.split() - y, m, d = date.split('-') - timebits = time.split(':') - h, mn = timebits[:2] - if len(timebits) > 2: - s = int(timebits[2]) - else: - s = 0 - return datetime.datetime(int(y), int(m), int(d), int(h), int(mn), s) - except ValueError: - return None - html2python = staticmethod(html2python) - -class DateField(TextField): - """A FormField that automatically converts its data to a datetime.date object. - The data should be in the format YYYY-MM-DD.""" - def __init__(self, field_name, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isValidDate] + validator_list - TextField.__init__(self, field_name, length=10, max_length=10, - is_required=is_required, validator_list=validator_list) - - def isValidDate(self, field_data, all_data): - try: - validators.isValidANSIDate(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - - def html2python(data): - "Converts the field into a datetime.date object" - import time, datetime - try: - time_tuple = time.strptime(data, '%Y-%m-%d') - return datetime.date(*time_tuple[0:3]) - except (ValueError, TypeError): - return None - html2python = staticmethod(html2python) - -class TimeField(TextField): - """A FormField that automatically converts its data to a datetime.time object. - The data should be in the format HH:MM:SS or HH:MM:SS.mmmmmm.""" - def __init__(self, field_name, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isValidTime] + validator_list - TextField.__init__(self, field_name, length=8, max_length=8, - is_required=is_required, validator_list=validator_list) - - def isValidTime(self, field_data, all_data): - try: - validators.isValidANSITime(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - - def html2python(data): - "Converts the field into a datetime.time object" - import time, datetime - try: - part_list = data.split('.') - try: - time_tuple = time.strptime(part_list[0], '%H:%M:%S') - except ValueError: # seconds weren't provided - time_tuple = time.strptime(part_list[0], '%H:%M') - t = datetime.time(*time_tuple[3:6]) - if (len(part_list) == 2): - t = t.replace(microsecond=int(part_list[1])) - return t - except (ValueError, TypeError, AttributeError): - return None - html2python = staticmethod(html2python) - -#################### -# INTERNET-RELATED # -#################### - -class EmailField(TextField): - "A convenience FormField for validating e-mail addresses" - def __init__(self, field_name, length=50, max_length=75, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isValidEmail] + validator_list - TextField.__init__(self, field_name, length, max_length=max_length, - is_required=is_required, validator_list=validator_list) - - def isValidEmail(self, field_data, all_data): - try: - validators.isValidEmail(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - -class URLField(TextField): - "A convenience FormField for validating URLs" - def __init__(self, field_name, length=50, max_length=200, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isValidURL] + validator_list - TextField.__init__(self, field_name, length=length, max_length=max_length, - is_required=is_required, validator_list=validator_list) - - def isValidURL(self, field_data, all_data): - try: - validators.isValidURL(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - -class IPAddressField(TextField): - def __init__(self, field_name, length=15, max_length=15, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isValidIPAddress] + validator_list - TextField.__init__(self, field_name, length=length, max_length=max_length, - is_required=is_required, validator_list=validator_list) - - def isValidIPAddress(self, field_data, all_data): - try: - validators.isValidIPAddress4(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - - def html2python(data): - return data or None - html2python = staticmethod(html2python) - -#################### -# MISCELLANEOUS # -#################### - -class FilePathField(SelectField): - "A SelectField whose choices are the files in a given directory." - def __init__(self, field_name, path, match=None, recursive=False, is_required=False, validator_list=None, max_length=None): - import os - from django.db.models import BLANK_CHOICE_DASH - if match is not None: - import re - match_re = re.compile(match) - choices = not is_required and BLANK_CHOICE_DASH[:] or [] - if recursive: - for root, dirs, files in os.walk(path): - for f in files: - if match is None or match_re.search(f): - f = os.path.join(root, f) - choices.append((f, f.replace(path, "", 1))) - else: - try: - for f in os.listdir(path): - full_file = os.path.join(path, f) - if os.path.isfile(full_file) and (match is None or match_re.search(f)): - choices.append((full_file, f)) - except OSError: - pass - SelectField.__init__(self, field_name, choices, 1, is_required, validator_list) - -class PhoneNumberField(TextField): - "A convenience FormField for validating phone numbers (e.g. '630-555-1234')" - def __init__(self, field_name, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isValidPhone] + validator_list - TextField.__init__(self, field_name, length=12, max_length=12, - is_required=is_required, validator_list=validator_list) - - def isValidPhone(self, field_data, all_data): - try: - validators.isValidPhone(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - -class USStateField(TextField): - "A convenience FormField for validating U.S. states (e.g. 'IL')" - def __init__(self, field_name, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isValidUSState] + validator_list - TextField.__init__(self, field_name, length=2, max_length=2, - is_required=is_required, validator_list=validator_list) - - def isValidUSState(self, field_data, all_data): - try: - validators.isValidUSState(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - - def html2python(data): - if data: - return data.upper() # Should always be stored in upper case - return data - html2python = staticmethod(html2python) - -class CommaSeparatedIntegerField(TextField): - "A convenience FormField for validating comma-separated integer fields" - def __init__(self, field_name, max_length=None, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isCommaSeparatedIntegerList] + validator_list - TextField.__init__(self, field_name, length=20, max_length=max_length, - is_required=is_required, validator_list=validator_list) - - def isCommaSeparatedIntegerList(self, field_data, all_data): - try: - validators.isCommaSeparatedIntegerList(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - - def render(self, data): - if data is None: - data = u'' - elif isinstance(data, (list, tuple)): - data = u','.join(data) - return super(CommaSeparatedIntegerField, self).render(data) - -class RawIdAdminField(CommaSeparatedIntegerField): - def html2python(data): - if data: - return data.split(',') - else: - return [] - html2python = staticmethod(html2python) - -class XMLLargeTextField(LargeTextField): - """ - A LargeTextField with an XML validator. The schema_path argument is the - full path to a Relax NG compact schema to validate against. - """ - def __init__(self, field_name, schema_path, **kwargs): - self.schema_path = schema_path - kwargs.setdefault('validator_list', []).insert(0, self.isValidXML) - LargeTextField.__init__(self, field_name, **kwargs) - - def isValidXML(self, field_data, all_data): - v = validators.RelaxNGCompact(self.schema_path) - try: - v(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages diff --git a/docs/howto/custom-model-fields.txt b/docs/howto/custom-model-fields.txt index d1b7dddc8b..6b164803d0 100644 --- a/docs/howto/custom-model-fields.txt +++ b/docs/howto/custom-model-fields.txt @@ -108,11 +108,11 @@ What does a field class do? All of Django's fields (and when we say *fields* in this document, we always mean model fields and not :ref:`form fields `) are subclasses of :class:`django.db.models.Field`. Most of the information that Django records -about a field is common to all fields -- name, help text, validator lists, -uniqueness and so forth. Storing all that information is handled by ``Field``. -We'll get into the precise details of what ``Field`` can do later on; for now, -suffice it to say that everything descends from ``Field`` and then customizes -key pieces of the class behavior. +about a field is common to all fields -- name, help text, uniqueness and so +forth. Storing all that information is handled by ``Field``. We'll get into the +precise details of what ``Field`` can do later on; for now, suffice it to say +that everything descends from ``Field`` and then customizes key pieces of the +class behavior. It's important to realize that a Django field class is not what is stored in your model attributes. The model attributes contain normal Python objects. The @@ -210,7 +210,6 @@ parameters: * :attr:`~django.db.models.Field.unique_for_date` * :attr:`~django.db.models.Field.unique_for_month` * :attr:`~django.db.models.Field.unique_for_year` - * :attr:`~django.db.models.Field.validator_list` * :attr:`~django.db.models.Field.choices` * :attr:`~django.db.models.Field.help_text` * :attr:`~django.db.models.Field.db_column` @@ -567,33 +566,19 @@ output in some other place, outside of Django. Converting field data for serialization ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. method:: flatten_data(self, follow, obj=None) - -.. admonition:: Subject to change - - Although implementing this method is necessary to allow field - serialization, the API might change in the future. - -Returns a dictionary, mapping the field's attribute name to a flattened string -version of the data. This method has some internal uses that aren't of interest -to use here (mostly having to do with forms). For our purposes, it's sufficient -to return a one item dictionary that maps the attribute name to a string. +.. method:: value_to_string(self, obj) This method is used by the serializers to convert the field into a string for -output. You can ignore the input parameters for serialization purposes, although -calling :meth:`Field._get_val_from_obj(obj) -` is the best way to get the value to -serialize. - -For example, since our ``HandField`` uses strings for its data storage anyway, -we can reuse some existing conversion code:: +output. Calling :meth:``Field._get_val_from_obj(obj)`` is the best way to get the +value to serialize. For example, since our ``HandField`` uses strings for its +data storage anyway, we can reuse some existing conversion code:: class HandField(models.Field): # ... - def flatten_data(self, follow, obj=None): + def value_to_string(self, obj): value = self._get_val_from_obj(obj) - return {self.attname: self.get_db_prep_value(value)} + return self.get_db_prep_value(value) Some general advice -------------------- diff --git a/docs/intro/whatsnext.txt b/docs/intro/whatsnext.txt index ae67220f58..c3cc962341 100644 --- a/docs/intro/whatsnext.txt +++ b/docs/intro/whatsnext.txt @@ -154,12 +154,12 @@ shell command: One low-tech way of taking advantage of the text documentation is by using the Unix ``grep`` utility to search for a phrase in all of the documentation. For -example, this will show you each mention of the phrase "edit_inline" in any +example, this will show you each mention of the phrase "max_length" in any Django document: .. code-block:: bash - $ grep edit_inline /path/to/django/docs/*.txt + $ grep max_length /path/to/django/docs/*.txt As HTML, locally ---------------- diff --git a/docs/obsolete/forms.txt b/docs/obsolete/forms.txt deleted file mode 100644 index 9bfd4a979e..0000000000 --- a/docs/obsolete/forms.txt +++ /dev/null @@ -1,692 +0,0 @@ -.. _obsolete-forms: - -=============================== -Forms, fields, and manipulators -=============================== - -Forwards-compatibility note -=========================== - -The legacy forms/manipulators system described in this document is going to be -replaced in the next Django release. If you're starting from scratch, we -strongly encourage you not to waste your time learning this. Instead, learn and -use the new :ref:`forms library `. - -Introduction -============ - -Once you've got a chance to play with Django's admin interface, you'll probably -wonder if the fantastic form validation framework it uses is available to user -code. It is, and this document explains how the framework works. - -We'll take a top-down approach to examining Django's form validation framework, -because much of the time you won't need to use the lower-level APIs. Throughout -this document, we'll be working with the following model, a "place" object:: - - from django.db import models - - PLACE_TYPES = ( - (1, 'Bar'), - (2, 'Restaurant'), - (3, 'Movie Theater'), - (4, 'Secret Hideout'), - ) - - class Place(models.Model): - name = models.CharField(max_length=100) - address = models.CharField(max_length=100, blank=True) - city = models.CharField(max_length=50, blank=True) - state = models.USStateField() - zip_code = models.CharField(max_length=5, blank=True) - place_type = models.IntegerField(choices=PLACE_TYPES) - - class Admin: - pass - - def __unicode__(self): - return self.name - -Defining the above class is enough to create an admin interface to a ``Place``, -but what if you want to allow public users to submit places? - -Automatic Manipulators -====================== - -The highest-level interface for object creation and modification is the -**automatic Manipulator** framework. An automatic manipulator is a utility -class tied to a given model that "knows" how to create or modify instances of -that model and how to validate data for the object. Automatic Manipulators come -in two flavors: ``AddManipulators`` and ``ChangeManipulators``. Functionally -they are quite similar, but the former knows how to create new instances of the -model, while the latter modifies existing instances. Both types of classes are -automatically created when you define a new class:: - - >>> from mysite.myapp.models import Place - >>> Place.AddManipulator - - >>> Place.ChangeManipulator - - -Using the ``AddManipulator`` ----------------------------- - -We'll start with the ``AddManipulator``. Here's a very simple view that takes -POSTed data from the browser and creates a new ``Place`` object:: - - from django.shortcuts import render_to_response - from django.http import Http404, HttpResponse, HttpResponseRedirect - from django import oldforms as forms - from mysite.myapp.models import Place - - def naive_create_place(request): - """A naive approach to creating places; don't actually use this!""" - # Create the AddManipulator. - manipulator = Place.AddManipulator() - - # Make a copy of the POSTed data so that do_html2python can - # modify it in place (request.POST is immutable). - new_data = request.POST.copy() - - # Convert the request data (which will all be strings) into the - # appropriate Python types for those fields. - manipulator.do_html2python(new_data) - - # Save the new object. - new_place = manipulator.save(new_data) - - # It worked! - return HttpResponse("Place created: %s" % new_place) - -The ``naive_create_place`` example works, but as you probably can tell, this -view has a number of problems: - - * No validation of any sort is performed. If, for example, the ``name`` field - isn't given in ``request.POST``, the save step will cause a database error - because that field is required. Ugly. - - * Even if you *do* perform validation, there's still no way to give that - information to the user in any sort of useful way. - - * You'll have to separately create a form (and view) that submits to this - page, which is a pain and is redundant. - -Let's dodge these problems momentarily to take a look at how you could create a -view with a form that submits to this flawed creation view:: - - def naive_create_place_form(request): - """Simplistic place form view; don't actually use anything like this!""" - # Create a FormWrapper object that the template can use. Ignore - # the last two arguments to FormWrapper for now. - form = forms.FormWrapper(Place.AddManipulator(), {}, {}) - return render_to_response('places/naive_create_form.html', {'form': form}) - -(This view, as well as all the following ones, has the same imports as in the -first example above.) - -The ``forms.FormWrapper`` object is a wrapper that templates can -easily deal with to create forms. Here's the ``naive_create_form.html`` -template:: - - {% extends "base.html" %} - - {% block content %} -

    Create a place:

    - -
    -

    {{ form.name }}

    -

    {{ form.address }}

    -

    {{ form.city }}

    -

    {{ form.state }}

    -

    {{ form.zip_code }}

    -

    {{ form.place_type }}

    - -
    - {% endblock %} - -Before we get back to the problems with these naive set of views, let's go over -some salient points of the above template: - - * Field "widgets" are handled for you: ``{{ form.field }}`` automatically - creates the "right" type of widget for the form, as you can see with the - ``place_type`` field above. - - * There isn't a way just to spit out the form. You'll still need to define - how the form gets laid out. This is a feature: Every form should be - designed differently. Django doesn't force you into any type of mold. - If you must use tables, use tables. If you're a semantic purist, you can - probably find better HTML than in the above template. - - * To avoid name conflicts, the ``id`` values of form elements take the - form "id_*fieldname*". - -By creating a creation form we've solved problem number 3 above, but we still -don't have any validation. Let's revise the validation issue by writing a new -creation view that takes validation into account:: - - def create_place_with_validation(request): - manipulator = Place.AddManipulator() - new_data = request.POST.copy() - - # Check for validation errors - errors = manipulator.get_validation_errors(new_data) - manipulator.do_html2python(new_data) - if errors: - return render_to_response('places/errors.html', {'errors': errors}) - else: - new_place = manipulator.save(new_data) - return HttpResponse("Place created: %s" % new_place) - -In this new version, errors will be found -- ``manipulator.get_validation_errors`` -handles all the validation for you -- and those errors can be nicely presented -on an error page (templated, of course):: - - {% extends "base.html" %} - - {% block content %} - -

    Please go back and correct the following error{{ errors|pluralize }}:

    -
      - {% for e in errors.items %} -
    • Field "{{ e.0 }}": {{ e.1|join:", " }}
    • - {% endfor %} -
    - - {% endblock %} - -Still, this has its own problems: - - * There's still the issue of creating a separate (redundant) view for the - submission form. - - * Errors, though nicely presented, are on a separate page, so the user will - have to use the "back" button to fix errors. That's ridiculous and unusable. - -The best way to deal with these issues is to collapse the two views -- the form -and the submission -- into a single view. This view will be responsible for -creating the form, validating POSTed data, and creating the new object (if the -data is valid). An added bonus of this approach is that errors and the form will -both be available on the same page, so errors with fields can be presented in -context. - -.. admonition:: Philosophy: - - Finally, for the HTTP purists in the audience (and the authorship), this - nicely matches the "true" meanings of HTTP GET and HTTP POST: GET fetches - the form, and POST creates the new object. - -Below is the finished view:: - - def create_place(request): - manipulator = Place.AddManipulator() - - if request.method == 'POST': - # If data was POSTed, we're trying to create a new Place. - new_data = request.POST.copy() - - # Check for errors. - errors = manipulator.get_validation_errors(new_data) - manipulator.do_html2python(new_data) - - if not errors: - # No errors. This means we can save the data! - new_place = manipulator.save(new_data) - - # Redirect to the object's "edit" page. Always use a redirect - # after POST data, so that reloads don't accidentally create - # duplicate entries, and so users don't see the confusing - # "Repost POST data?" alert box in their browsers. - return HttpResponseRedirect("/places/edit/%i/" % new_place.id) - else: - # No POST, so we want a brand new form without any data or errors. - errors = new_data = {} - - # Create the FormWrapper, template, context, response. - form = forms.FormWrapper(manipulator, new_data, errors) - return render_to_response('places/create_form.html', {'form': form}) - -and here's the ``create_form`` template:: - - {% extends "base.html" %} - - {% block content %} -

    Create a place:

    - - {% if form.has_errors %} -

    Please correct the following error{{ form.error_dict|pluralize }}:

    - {% endif %} - -
    -

    - {{ form.name }} - {% if form.name.errors %}*** {{ form.name.errors|join:", " }}{% endif %} -

    -

    - {{ form.address }} - {% if form.address.errors %}*** {{ form.address.errors|join:", " }}{% endif %} -

    -

    - {{ form.city }} - {% if form.city.errors %}*** {{ form.city.errors|join:", " }}{% endif %} -

    -

    - {{ form.state }} - {% if form.state.errors %}*** {{ form.state.errors|join:", " }}{% endif %} -

    -

    - {{ form.zip_code }} - {% if form.zip_code.errors %}*** {{ form.zip_code.errors|join:", " }}{% endif %} -

    -

    - {{ form.place_type }} - {% if form.place_type.errors %}*** {{ form.place_type.errors|join:", " }}{% endif %} -

    - -
    - {% endblock %} - -The second two arguments to ``FormWrapper`` (``new_data`` and ``errors``) -deserve some mention. - -The first is any "default" data to be used as values for the fields. Pulling -the data from ``request.POST``, as is done above, makes sure that if there are -errors, the values the user put in aren't lost. If you try the above example, -you'll see this in action. - -The second argument is the error list retrieved from -``manipulator.get_validation_errors``. When passed into the ``FormWrapper``, -this gives each field an ``errors`` item (which is a list of error messages -associated with the field) as well as a ``html_error_list`` item, which is a -``
      `` of error messages. The above template uses these error items to -display a simple error message next to each field. The error list is saved as -an ``error_dict`` attribute of the ``FormWrapper`` object. - -Using the ``ChangeManipulator`` -------------------------------- - -The above has covered using the ``AddManipulator`` to create a new object. What -about editing an existing one? It's shockingly similar to creating a new one:: - - def edit_place(request, place_id): - # Get the place in question from the database and create a - # ChangeManipulator at the same time. - try: - manipulator = Place.ChangeManipulator(place_id) - except Place.DoesNotExist: - raise Http404 - - # Grab the Place object in question for future use. - place = manipulator.original_object - - if request.method == 'POST': - new_data = request.POST.copy() - errors = manipulator.get_validation_errors(new_data) - manipulator.do_html2python(new_data) - if not errors: - manipulator.save(new_data) - - # Do a post-after-redirect so that reload works, etc. - return HttpResponseRedirect("/places/edit/%i/" % place.id) - else: - errors = {} - # This makes sure the form accurate represents the fields of the place. - new_data = manipulator.flatten_data() - - form = forms.FormWrapper(manipulator, new_data, errors) - return render_to_response('places/edit_form.html', {'form': form, 'place': place}) - -The only real differences are: - - * We create a ``ChangeManipulator`` instead of an ``AddManipulator``. - The argument to a ``ChangeManipulator`` is the ID of the object - to be changed. As you can see, the initializer will raise an - ``ObjectDoesNotExist`` exception if the ID is invalid. - - * ``ChangeManipulator.original_object`` stores the instance of the - object being edited. - - * We set ``new_data`` based upon ``flatten_data()`` from the manipulator. - ``flatten_data()`` takes the data from the original object under - manipulation, and converts it into a data dictionary that can be used - to populate form elements with the existing values for the object. - - * The above example uses a different template, so create and edit can be - "skinned" differently if needed, but the form chunk itself is completely - identical to the one in the create form above. - -The astute programmer will notice the add and create functions are nearly -identical and could in fact be collapsed into a single view. This is left as an -exercise for said programmer. - -(However, the even-more-astute programmer will take heed of the note at the top -of this document and check out the :ref:`generic views ` -documentation if all she wishes to do is this type of simple create/update.) - -Custom forms and manipulators -============================= - -All the above is fine and dandy if you just want to use the automatically -created manipulators. But the coolness doesn't end there: You can easily create -your own custom manipulators for handling custom forms. - -Custom manipulators are pretty simple. Here's a manipulator that you might use -for a "contact" form on a website:: - - from django import oldforms as forms - - urgency_choices = ( - (1, "Extremely urgent"), - (2, "Urgent"), - (3, "Normal"), - (4, "Unimportant"), - ) - - class ContactManipulator(forms.Manipulator): - def __init__(self): - self.fields = ( - forms.EmailField(field_name="from", is_required=True), - forms.TextField(field_name="subject", length=30, max_length=200, is_required=True), - forms.SelectField(field_name="urgency", choices=urgency_choices), - forms.LargeTextField(field_name="contents", is_required=True), - ) - -A certain similarity to Django's models should be apparent. The only required -method of a custom manipulator is ``__init__`` which must define the fields -present in the manipulator. See the ``django.forms`` module for -all the form fields provided by Django. - -You use this custom manipulator exactly as you would use an auto-generated one. -Here's a simple function that might drive the above form:: - - def contact_form(request): - manipulator = ContactManipulator() - if request.method == 'POST': - new_data = request.POST.copy() - errors = manipulator.get_validation_errors(new_data) - manipulator.do_html2python(new_data) - if not errors: - - # Send e-mail using new_data here... - - return HttpResponseRedirect("/contact/thankyou/") - else: - errors = new_data = {} - form = forms.FormWrapper(manipulator, new_data, errors) - return render_to_response('contact_form.html', {'form': form}) - -Implementing ``flatten_data`` for custom manipulators ------------------------------------------------------- - -It is possible (although rarely needed) to replace the default automatically -created manipulators on a model with your own custom manipulators. If you do -this and you are intending to use those models in generic views, you should -also define a ``flatten_data`` method in any ``ChangeManipulator`` replacement. -This should act like the default ``flatten_data`` and return a dictionary -mapping field names to their values, like so:: - - def flatten_data(self): - obj = self.original_object - return dict( - from = obj.from, - subject = obj.subject, - ... - ) - -In this way, your new change manipulator will act exactly like the default -version. - -``FileField`` and ``ImageField`` special cases -============================================== - -Dealing with ``FileField`` and ``ImageField`` objects is a little more -complicated. - -First, you'll need to make sure that your ``
      `` element correctly defines -the ``enctype`` as ``"multipart/form-data"``, in order to upload files:: - - - -Next, you'll need to treat the field in the template slightly differently. A -``FileField`` or ``ImageField`` is represented by *two* HTML form elements. - -For example, given this field in a model:: - - photo = model.ImageField('/path/to/upload/location') - -You'd need to display two formfields in the template:: - -

      {{ form.photo }}{{ form.photo_file }}

      - -The first bit (``{{ form.photo }}``) displays the currently-selected file, -while the second (``{{ form.photo_file }}``) actually contains the file upload -form field. Thus, at the validation layer you need to check the ``photo_file`` -key. - -Finally, in your view, make sure to access ``request.FILES``, rather than -``request.POST``, for the uploaded files. This is necessary because -``request.POST`` does not contain file-upload data. - -For example, following the ``new_data`` convention, you might do something like -this:: - - new_data = request.POST.copy() - new_data.update(request.FILES) - -Validators -========== - -One useful feature of manipulators is the automatic validation. Validation is -done using a simple validation API: A validator is a callable that raises a -``ValidationError`` if there's something wrong with the data. -``django.core.validators`` defines a host of validator functions (see below), -but defining your own couldn't be easier:: - - from django.core import validators - from django import oldforms as forms - - class ContactManipulator(forms.Manipulator): - def __init__(self): - self.fields = ( - # ... snip fields as above ... - forms.EmailField(field_name="to", validator_list=[self.isValidToAddress]) - ) - - def isValidToAddress(self, field_data, all_data): - if not field_data.endswith("@example.com"): - raise validators.ValidationError("You can only send messages to example.com e-mail addresses.") - -Above, we've added a "to" field to the contact form, but required that the "to" -address end with "@example.com" by adding the ``isValidToAddress`` validator to -the field's ``validator_list``. - -The arguments to a validator function take a little explanation. ``field_data`` -is the value of the field in question, and ``all_data`` is a dictionary of all -the data being validated. - -.. admonition:: Note:: - - At the point validators are called all data will still be - strings (as ``do_html2python`` hasn't been called yet). - -Also, because consistency in user interfaces is important, we strongly urge you -to put punctuation at the end of your validation messages. - -When are validators called? ---------------------------- - -After a form has been submitted, Django validates each field in turn. First, -if the field is required, Django checks that it is present and non-empty. Then, -if that test passes *and the form submission contained data* for that field, all -the validators for that field are called in turn. The emphasized portion in the -last sentence is important: if a form field is not submitted (because it -contains no data -- which is normal HTML behavior), the validators are not -run against the field. - -This feature is particularly important for models using -``models.BooleanField`` or custom manipulators using things like -``forms.CheckBoxField``. If the checkbox is not selected, it will not -contribute to the form submission. - -If you would like your validator to run *always*, regardless of whether its -attached field contains any data, set the ``always_test`` attribute on the -validator function. For example:: - - def my_custom_validator(field_data, all_data): - # ... - my_custom_validator.always_test = True - -This validator will always be executed for any field it is attached to. - -Ready-made validators ---------------------- - -Writing your own validator is not difficult, but there are some situations -that come up over and over again. Django comes with a number of validators -that can be used directly in your code. All of these functions and classes -reside in ``django/core/validators.py``. - -The following validators should all be self-explanatory. Each one provides a -check for the given property: - - * isAlphaNumeric - * isAlphaNumericURL - * isSlug - * isLowerCase - * isUpperCase - * isCommaSeparatedIntegerList - * isCommaSeparatedEmailList - * isValidIPAddress4 - * isNotEmpty - * isOnlyDigits - * isNotOnlyDigits - * isInteger - * isOnlyLetters - * isValidANSIDate - * isValidANSITime - * isValidEmail - * isValidFloat - * isValidImage - * isValidImageURL - * isValidPhone - * isValidQuicktimeVideoURL - * isValidURL - * isValidHTML - * isWellFormedXml - * isWellFormedXmlFragment - * isExistingURL - * isValidUSState - * hasNoProfanities - -There are also a group of validators that are slightly more flexible. For -these validators, you create a validator instance, passing in the parameters -described below. The returned object is a callable that can be used as a -validator. - -For example:: - - from django.core import validators - from django import oldforms as forms - - power_validator = validators.IsAPowerOf(2) - - class InstallationManipulator(forms.Manipulator) - def __init__(self): - self.fields = ( - ... - forms.IntegerField(field_name = "size", validator_list=[power_validator]) - ) - -Here, ``validators.IsAPowerOf(...)`` returned something that could be used as -a validator (in this case, a check that a number was a power of 2). - -Each of the standard validators that take parameters have an optional final -argument (``error_message``) that is the message returned when validation -fails. If no message is passed in, a default message is used. - -``AlwaysMatchesOtherField`` - Takes a field name and the current field is valid if and only if its value - matches the contents of the other field. - -``ValidateIfOtherFieldEquals`` - Takes three parameters: ``other_field``, ``other_value`` and - ``validator_list``, in that order. If ``other_field`` has a value of - ``other_value``, then the validators in ``validator_list`` are all run - against the current field. - -``RequiredIfOtherFieldGiven`` - Takes a field name of the current field is only required if the other - field has a value. - -``RequiredIfOtherFieldsGiven`` - Similar to ``RequiredIfOtherFieldGiven``, except that it takes a list of - field names and if any one of the supplied fields has a value provided, - the current field being validated is required. - -``RequiredIfOtherFieldNotGiven`` - Takes the name of the other field and this field is only required if the - other field has no value. - -``RequiredIfOtherFieldEquals`` and ``RequiredIfOtherFieldDoesNotEqual`` - Each of these validator classes takes a field name and a value (in that - order). If the given field does (or does not have, in the latter case) the - given value, then the current field being validated is required. - - An optional ``other_label`` argument can be passed which, if given, is used - in error messages instead of the value. This allows more user friendly error - messages if the value itself is not descriptive enough. - - Note that because validators are called before any ``do_html2python()`` - functions, the value being compared against is a string. So - ``RequiredIfOtherFieldEquals('choice', '1')`` is correct, whilst - ``RequiredIfOtherFieldEquals('choice', 1)`` will never result in the - equality test succeeding. - -``IsLessThanOtherField`` - Takes a field name and validates that the current field being validated - has a value that is less than (or equal to) the other field's value. - Again, comparisons are done using strings, so be cautious about using - this function to compare data that should be treated as another type. The - string "123" is less than the string "2", for example. If you don't want - string comparison here, you will need to write your own validator. - -``NumberIsInRange`` - Takes two boundary numbers, ``lower`` and ``upper``, and checks that the - field is greater than ``lower`` (if given) and less than ``upper`` (if - given). - - Both checks are inclusive. That is, ``NumberIsInRange(10, 20)`` will allow - values of both 10 and 20. This validator only checks numeric values - (e.g., float and integer values). - -``IsAPowerOf`` - Takes an integer argument and when called as a validator, checks that the - field being validated is a power of the integer. - -``IsValidDecimal`` - Takes a maximum number of digits and number of decimal places (in that - order) and validates whether the field is a decimal with no more than the - maximum number of digits and decimal places. - -``MatchesRegularExpression`` - Takes a regular expression (a string) as a parameter and validates the - field value against it. - -``AnyValidator`` - Takes a list of validators as a parameter. At validation time, if the - field successfully validates against any one of the validators, it passes - validation. The validators are tested in the order specified in the - original list. - -``URLMimeTypeCheck`` - Used to validate URL fields. Takes a list of MIME types (such as - ``text/plain``) at creation time. At validation time, it verifies that the - field is indeed a URL and then tries to retrieve the content at the URL. - Validation succeeds if the content could be retrieved and it has a content - type from the list used to create the validator. - -``RelaxNGCompact`` - Used to validate an XML document against a Relax NG compact schema. Takes a - file path to the location of the schema and an optional root element (which - is wrapped around the XML fragment before validation, if supplied). At - validation time, the XML fragment is validated against the schema using the - executable specified in the ``JING_PATH`` setting (see the :ref:`settings - ` document for more details). diff --git a/docs/obsolete/newforms-migration.txt b/docs/obsolete/newforms-migration.txt deleted file mode 100644 index da56b5704e..0000000000 --- a/docs/obsolete/newforms-migration.txt +++ /dev/null @@ -1,48 +0,0 @@ -.. _howto-newforms-migration: - -Migrating from "oldforms" to "newforms" -======================================= - -:mod:`django.newforms` is new in Django's 0.96 release, but, as it won't be new -forever. We plan to rename it to ``django.forms`` in next official release. The -current ``django.forms`` package will be available as ``django.oldforms`` until -Django 1.0, when we plan to remove it for good. - -If you're using "old" forms -- and if you started using Django after 0.96 you're -probably not -- you need to read this document and understand this migration -plan. - - * The old forms framework (the current ``django.forms``) has been copied to - ``django.oldforms``. Thus, you can start upgrading your code *now*, - rather than waiting for the future backwards-incompatible change, by - changing your import statements like this:: - - from django import forms # old - from django import oldforms as forms # new - - * In the next Django release, we will move the current ``django.newforms`` - to ``django.forms``. This will be a backwards-incompatible change, and - anybody who is still using the old version of ``django.forms`` at that - time will need to change their import statements, as described in the - previous bullet. - - * We will remove ``django.oldforms`` in Django 1.0. It will continue to be - available from older tags in our SVN repository, but it will not be - consider part of Django, and will not be supported.. - -With this in mind, we recommend you use the following import statement when -using ``django.newforms``:: - - from django import newforms as forms - -This way, your code can refer to the ``forms`` module, and when -``django.newforms`` is renamed to ``django.forms``, you'll only have to change -your ``import`` statements. - -If you prefer "``import *``" syntax, you can do the following:: - - from django.newforms import * - -This will import all fields, widgets, form classes and other various utilities -into your local namespace. Some people find this convenient; others find it -too messy. The choice is yours. diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 8ea4b2385c..7654300617 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -33,11 +33,6 @@ exception or returns the clean value:: ... ValidationError: [u'Enter a valid e-mail address.'] -If you've used Django's old forms/validation framework, take care in noticing -this ``ValidationError`` is different than the previous ``ValidationError``. -This one lives at ``django.forms.ValidationError`` rather than -``django.core.validators.ValidationError``. - Core field arguments -------------------- diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 7faa07d4f7..909bfeff1f 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -145,23 +145,6 @@ hacking :attr:`~Field.choices` to be dynamic, you're probably better off using a proper database table with a :class:`ForeignKey`. :attr:`~Field.choices` is meant for static data that doesn't change much, if ever. -``core`` --------- - -.. attribute:: Field.core - -For objects that are edited inline to a related object. - -In the Django admin, if all "core" fields in an inline-edited object are -cleared, the object will be deleted. - -It is an error to have an inline-editable relation without at least one -``core=True`` field. - -Please note that each field marked "core" is treated as a required field by the -Django admin site. Essentially, this means you should put ``core=True`` on all -required fields in your related object that is being edited inline. - ``db_column`` ------------- @@ -287,15 +270,6 @@ respect to the month. Like :attr:`~Field.unique_for_date` and :attr:`~Field.unique_for_month`. -``validator_list`` ------------------- - -.. attribute:: Field.validator_list - -A list of extra validators to apply to the field. Each should be a callable that -takes the parameters ``field_data, all_data`` and raises -:exc:`django.core.validators.ValidationError` for errors. - .. _model-field-types: Field types @@ -913,5 +887,4 @@ that control how the relationship functions. The semantics of one-to-one relationships will be changing soon, so we don't recommend you use them. If that doesn't scare you away, however, -:class:`OneToOneField` takes the same options that :class:`ForeignKey` does, -except for the various :attr:`~ForeignKey.edit_inline`-related options. +:class:`OneToOneField` takes the same options that :class:`ForeignKey` does. diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index f93bd0fbf5..d161b3f7f5 100644 --- a/docs/topics/forms/modelforms.txt +++ b/docs/topics/forms/modelforms.txt @@ -67,8 +67,7 @@ the full list of conversions: (from ``django.contrib.localflavor.us``) ``PositiveIntegerField`` ``IntegerField`` ``PositiveSmallIntegerField`` ``IntegerField`` - ``SlugField`` ``RegexField`` accepting only letters, - numbers, underscores and hyphens + ``SlugField`` ``SlugField`` ``SmallIntegerField`` ``IntegerField`` ``TextField`` ``CharField`` with ``widget=Textarea`` ``TimeField`` ``TimeField`` diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index c8c06d91c9..42fa1fb9c3 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -899,8 +899,7 @@ applications: rendered on the form. ``form`` is the name the ``Form`` instance was given in the template - context. Note that this works only for ``forms.Form`` instances, not - ``oldforms.Form`` instances. + context. ``field`` is the name of the field on the form to check. If ``field`` has a value of ``None``, non-field errors (errors you can access via diff --git a/tests/modeltests/field_subclassing/models.py b/tests/modeltests/field_subclassing/models.py index baf07a072f..c776146de5 100644 --- a/tests/modeltests/field_subclassing/models.py +++ b/tests/modeltests/field_subclassing/models.py @@ -53,9 +53,6 @@ class SmallField(models.Field): return [] raise FieldError('Invalid lookup type: %r' % lookup_type) - def flatten_data(self, follow, obj=None): - return {self.attname: force_unicode(self._get_val_from_obj(obj))} - class MyModel(models.Model): name = models.CharField(max_length=10) data = SmallField('small field') diff --git a/tests/modeltests/invalid_models/models.py b/tests/modeltests/invalid_models/models.py index abd72b1293..28172089ab 100644 --- a/tests/modeltests/invalid_models/models.py +++ b/tests/modeltests/invalid_models/models.py @@ -23,13 +23,13 @@ class Target(models.Model): clash1_set = models.CharField(max_length=10) class Clash1(models.Model): - src_safe = models.CharField(max_length=10, core=True) + src_safe = models.CharField(max_length=10) foreign = models.ForeignKey(Target) m2m = models.ManyToManyField(Target) class Clash2(models.Model): - src_safe = models.CharField(max_length=10, core=True) + src_safe = models.CharField(max_length=10) foreign_1 = models.ForeignKey(Target, related_name='id') foreign_2 = models.ForeignKey(Target, related_name='src_safe') @@ -46,7 +46,7 @@ class Target2(models.Model): clashm2m_set = models.ManyToManyField(Target) class Clash3(models.Model): - src_safe = models.CharField(max_length=10, core=True) + src_safe = models.CharField(max_length=10) foreign_1 = models.ForeignKey(Target2, related_name='foreign_tgt') foreign_2 = models.ForeignKey(Target2, related_name='m2m_tgt') @@ -61,7 +61,7 @@ class ClashM2M(models.Model): m2m = models.ManyToManyField(Target2) class SelfClashForeign(models.Model): - src_safe = models.CharField(max_length=10, core=True) + src_safe = models.CharField(max_length=10) selfclashforeign = models.CharField(max_length=10) selfclashforeign_set = models.ForeignKey("SelfClashForeign") diff --git a/tests/modeltests/manipulators/__init__.py b/tests/modeltests/manipulators/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/modeltests/manipulators/models.py b/tests/modeltests/manipulators/models.py deleted file mode 100644 index 0adee9ed39..0000000000 --- a/tests/modeltests/manipulators/models.py +++ /dev/null @@ -1,105 +0,0 @@ -# coding: utf-8 -""" -27. Default manipulators - -Each model gets an ``AddManipulator`` and ``ChangeManipulator`` by default. -""" - -from django.db import models - -class Musician(models.Model): - first_name = models.CharField(max_length=30) - last_name = models.CharField(max_length=30) - - def __unicode__(self): - return u"%s %s" % (self.first_name, self.last_name) - -class Album(models.Model): - name = models.CharField(max_length=100) - musician = models.ForeignKey(Musician) - release_date = models.DateField(blank=True, null=True) - - def __unicode__(self): - return self.name - -__test__ = {'API_TESTS':u""" ->>> from django.utils.datastructures import MultiValueDict - -# Create a Musician object via the default AddManipulator. ->>> man = Musician.AddManipulator() ->>> data = MultiValueDict({'first_name': ['Ella'], 'last_name': ['Fitzgerald']}) - ->>> man.get_validation_errors(data) -{} ->>> man.do_html2python(data) ->>> m1 = man.save(data) - -# Verify it worked. ->>> Musician.objects.all() -[] ->>> [m1] == list(Musician.objects.all()) -True - -# Attempt to add a Musician without a first_name. ->>> man.get_validation_errors(MultiValueDict({'last_name': ['Blakey']}))['first_name'] -[u'This field is required.'] - -# Attempt to add a Musician without a first_name and last_name. ->>> errors = man.get_validation_errors(MultiValueDict({})) ->>> errors['first_name'] -[u'This field is required.'] ->>> errors['last_name'] -[u'This field is required.'] - -# Attempt to create an Album without a name or musician. ->>> man = Album.AddManipulator() ->>> errors = man.get_validation_errors(MultiValueDict({})) ->>> errors['musician'] -[u'This field is required.'] ->>> errors['name'] -[u'This field is required.'] - -# Attempt to create an Album with an invalid musician. ->>> errors = man.get_validation_errors(MultiValueDict({'name': ['Sallies Fforth'], 'musician': ['foo']})) ->>> errors['musician'] -[u"Select a valid choice; 'foo' is not in [u'', u'1']."] - -# Attempt to create an Album with an invalid release_date. ->>> errors = man.get_validation_errors(MultiValueDict({'name': ['Sallies Fforth'], 'musician': ['1'], 'release_date': 'today'})) ->>> errors['release_date'] -[u'Enter a valid date in YYYY-MM-DD format.'] - -# Create an Album without a release_date (because it's optional). ->>> data = MultiValueDict({'name': ['Ella and Basie'], 'musician': ['1']}) ->>> man.get_validation_errors(data) -{} ->>> man.do_html2python(data) ->>> a1 = man.save(data) - -# Verify it worked. ->>> Album.objects.all() -[] ->>> Album.objects.get().musician - - -# Create an Album with a release_date. ->>> data = MultiValueDict({'name': ['Ultimate Ella'], 'musician': ['1'], 'release_date': ['2005-02-13']}) ->>> man.get_validation_errors(data) -{} ->>> man.do_html2python(data) ->>> a2 = man.save(data) - -# Verify it worked. ->>> Album.objects.order_by('name') -[, ] ->>> a2 = Album.objects.get(pk=2) ->>> a2 - ->>> a2.release_date -datetime.date(2005, 2, 13) - -# Test isValidFloat Unicode coercion ->>> from django.core.validators import isValidFloat, ValidationError ->>> try: isValidFloat(u"รค", None) -... except ValidationError: pass -"""} diff --git a/tests/modeltests/mutually_referential/models.py b/tests/modeltests/mutually_referential/models.py index 5176721f3d..2cbaa4b50b 100644 --- a/tests/modeltests/mutually_referential/models.py +++ b/tests/modeltests/mutually_referential/models.py @@ -7,7 +7,7 @@ Strings can be used instead of model literals to set up "lazy" relations. from django.db.models import * class Parent(Model): - name = CharField(max_length=100, core=True) + name = CharField(max_length=100) # Use a simple string for forward declarations. bestchild = ForeignKey("Child", null=True, related_name="favoured_by") diff --git a/tests/regressiontests/model_fields/tests.py b/tests/regressiontests/model_fields/tests.py index 5aedcd15fc..0e67c60ac9 100644 --- a/tests/regressiontests/model_fields/tests.py +++ b/tests/regressiontests/model_fields/tests.py @@ -18,7 +18,7 @@ True >>> f.to_python("abc") Traceback (most recent call last): ... -ValidationError: [u'This value must be a decimal number.'] +ValidationError: This value must be a decimal number. >>> f = DecimalField(max_digits=5, decimal_places=1) >>> x = f.to_python(2)