Removed oldforms, validators, and related code:

* Removed `Manipulator`, `AutomaticManipulator`, and related classes.
 * Removed oldforms specific bits from model fields:
   * Removed `validator_list` and `core` arguments from constructors.
   * Removed the methods:
     * `get_manipulator_field_names`
     * `get_manipulator_field_objs`
     * `get_manipulator_fields`
     * `get_manipulator_new_data`
     * `prepare_field_objs_and_params`
     * `get_follow`
   * Renamed `flatten_data` method to `value_to_string` for better alignment with its use by the serialization framework, which was the only remaining code using `flatten_data`.
 * Removed oldforms methods from `django.db.models.Options` class: `get_followed_related_objects`, `get_data_holders`, `get_follow`, and `has_field_type`.
 * Removed oldforms-admin specific options from `django.db.models.fields.related` classes: `num_in_admin`, `min_num_in_admin`, `max_num_in_admin`, `num_extra_on_change`, and `edit_inline`.
 * Serialization framework
   * `Serializer.get_string_value` now calls the model fields' renamed `value_to_string` methods.
   * Removed a special-casing of `models.DateTimeField` in `core.serializers.base.Serializer.get_string_value` that's handled by `django.db.models.fields.DateTimeField.value_to_string`.
 * Removed `django.core.validators`:
   * Moved `ValidationError` exception to `django.core.exceptions`.
   * For the couple places that were using validators, brought over the necessary code to maintain the same functionality.
 * Introduced a SlugField form field for validation and to compliment the SlugField model field (refs #8040).
 * Removed an oldforms-style model creation hack (refs #2160).

git-svn-id: http://code.djangoproject.com/svn/django/trunk@8616 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Gary Wilson Jr 2008-08-27 07:19:44 +00:00
parent a157576660
commit c2ba59fc1d
35 changed files with 158 additions and 3468 deletions

View File

@ -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') % \

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
@ -395,4 +385,3 @@ class GenericStackedInline(GenericInlineModelAdmin):
class GenericTabularInline(GenericInlineModelAdmin):
template = 'admin/edit_inline/tabular.html'

View File

@ -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 _

View File

@ -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

View File

@ -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

View File

@ -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):
"""

View File

@ -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('<root>%s</root>' % 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)s>' % {
'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

View File

@ -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.

View File

@ -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:

View File

@ -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,7 +644,8 @@ 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}
@ -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)]

View File

@ -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)

View File

@ -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)

View File

@ -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))

View File

@ -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]

View File

@ -15,64 +15,9 @@ 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)
@ -81,47 +26,6 @@ class RelatedObject(object):
"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 "<RelatedObject: %s related to %s>" % (self.name, self.field.name)

View File

@ -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)

File diff suppressed because it is too large Load Diff

View File

@ -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 <ref-forms-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)
<django.db.models.Field._get_val_from_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
--------------------

View File

@ -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
----------------

View File

@ -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 <topics-forms-index>`.
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
<class 'django.models.manipulators.AddManipulator'>
>>> Place.ChangeManipulator
<class 'django.models.manipulators.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 %}
<h1>Create a place:</h1>
<form method="post" action="../do_new/">
<p><label for="id_name">Name:</label> {{ form.name }}</p>
<p><label for="id_address">Address:</label> {{ form.address }}</p>
<p><label for="id_city">City:</label> {{ form.city }}</p>
<p><label for="id_state">State:</label> {{ form.state }}</p>
<p><label for="id_zip_code">Zip:</label> {{ form.zip_code }}</p>
<p><label for="id_place_type">Place type:</label> {{ form.place_type }}</p>
<input type="submit" />
</form>
{% 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 %}
<h1>Please go back and correct the following error{{ errors|pluralize }}:</h1>
<ul>
{% for e in errors.items %}
<li>Field "{{ e.0 }}": {{ e.1|join:", " }}</li>
{% endfor %}
</ul>
{% 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 %}
<h1>Create a place:</h1>
{% if form.has_errors %}
<h2>Please correct the following error{{ form.error_dict|pluralize }}:</h2>
{% endif %}
<form method="post" action=".">
<p>
<label for="id_name">Name:</label> {{ form.name }}
{% if form.name.errors %}*** {{ form.name.errors|join:", " }}{% endif %}
</p>
<p>
<label for="id_address">Address:</label> {{ form.address }}
{% if form.address.errors %}*** {{ form.address.errors|join:", " }}{% endif %}
</p>
<p>
<label for="id_city">City:</label> {{ form.city }}
{% if form.city.errors %}*** {{ form.city.errors|join:", " }}{% endif %}
</p>
<p>
<label for="id_state">State:</label> {{ form.state }}
{% if form.state.errors %}*** {{ form.state.errors|join:", " }}{% endif %}
</p>
<p>
<label for="id_zip_code">Zip:</label> {{ form.zip_code }}
{% if form.zip_code.errors %}*** {{ form.zip_code.errors|join:", " }}{% endif %}
</p>
<p>
<label for="id_place_type">Place type:</label> {{ form.place_type }}
{% if form.place_type.errors %}*** {{ form.place_type.errors|join:", " }}{% endif %}
</p>
<input type="submit" />
</form>
{% 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
``<ul>`` 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 <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 ``<form>`` element correctly defines
the ``enctype`` as ``"multipart/form-data"``, in order to upload files::
<form enctype="multipart/form-data" method="post" action="/foo/">
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::
<p><label for="id_photo">Photo:</label> {{ form.photo }}{{ form.photo_file }}</p>
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
<ref-settings>` document for more details).

View File

@ -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.

View File

@ -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
--------------------

View File

@ -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.

View File

@ -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``

View File

@ -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

View File

@ -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')

View File

@ -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")

View File

@ -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()
[<Musician: Ella Fitzgerald>]
>>> [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: Ella and Basie>]
>>> Album.objects.get().musician
<Musician: Ella Fitzgerald>
# 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')
[<Album: Ella and Basie>, <Album: Ultimate Ella>]
>>> a2 = Album.objects.get(pk=2)
>>> a2
<Album: Ultimate Ella>
>>> 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
"""}

View File

@ -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")

View File

@ -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)