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:
parent
a157576660
commit
c2ba59fc1d
|
@ -85,7 +85,7 @@ def get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current_
|
||||||
perms_needed.add(related.opts.verbose_name)
|
perms_needed.add(related.opts.verbose_name)
|
||||||
# We don't care about populating deleted_objects now.
|
# We don't care about populating deleted_objects now.
|
||||||
continue
|
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
|
# Don't display link to edit, because it either has no
|
||||||
# admin or is edited inline.
|
# admin or is edited inline.
|
||||||
nh(deleted_objects, current_depth, [u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), sub_obj), []])
|
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
|
has_related_objs = False
|
||||||
for sub_obj in getattr(obj, rel_opts_name).all():
|
for sub_obj in getattr(obj, rel_opts_name).all():
|
||||||
has_related_objs = True
|
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
|
# Don't display link to edit, because it either has no
|
||||||
# admin or is edited inline.
|
# admin or is edited inline.
|
||||||
nh(deleted_objects, current_depth, [u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), sub_obj), []])
|
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:
|
if has_related_objs:
|
||||||
for sub_obj in rel_objs.all():
|
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
|
# Don't display link to edit, because it either has no
|
||||||
# admin or is edited inline.
|
# admin or is edited inline.
|
||||||
nh(deleted_objects, current_depth, [_('One or more %(fieldname)s in %(name)s: %(obj)s') % \
|
nh(deleted_objects, current_depth, [_('One or more %(fieldname)s in %(name)s: %(obj)s') % \
|
||||||
|
|
|
@ -8,10 +8,19 @@ import re
|
||||||
import sys
|
import sys
|
||||||
from optparse import make_option
|
from optparse import make_option
|
||||||
from django.contrib.auth.models import User
|
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.core.management.base import BaseCommand, CommandError
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
RE_VALID_USERNAME = re.compile('\w+$')
|
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):
|
class Command(BaseCommand):
|
||||||
option_list = BaseCommand.option_list + (
|
option_list = BaseCommand.option_list + (
|
||||||
|
@ -39,8 +48,8 @@ class Command(BaseCommand):
|
||||||
if not RE_VALID_USERNAME.match(username):
|
if not RE_VALID_USERNAME.match(username):
|
||||||
raise CommandError("Invalid username. Use only letters, digits, and underscores")
|
raise CommandError("Invalid username. Use only letters, digits, and underscores")
|
||||||
try:
|
try:
|
||||||
validators.isValidEmail(email, None)
|
is_valid_email(email)
|
||||||
except validators.ValidationError:
|
except exceptions.ValidationError:
|
||||||
raise CommandError("Invalid email address.")
|
raise CommandError("Invalid email address.")
|
||||||
|
|
||||||
password = ''
|
password = ''
|
||||||
|
@ -94,8 +103,8 @@ class Command(BaseCommand):
|
||||||
if not email:
|
if not email:
|
||||||
email = raw_input('E-mail address: ')
|
email = raw_input('E-mail address: ')
|
||||||
try:
|
try:
|
||||||
validators.isValidEmail(email, None)
|
is_valid_email(email)
|
||||||
except validators.ValidationError:
|
except exceptions.ValidationError:
|
||||||
sys.stderr.write("Error: That e-mail address is invalid.\n")
|
sys.stderr.write("Error: That e-mail address is invalid.\n")
|
||||||
email = None
|
email = None
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
from django.contrib import auth
|
from django.contrib import auth
|
||||||
from django.core import validators
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.manager import EmptyManager
|
from django.db.models.manager import EmptyManager
|
||||||
|
|
|
@ -117,9 +117,6 @@ class CommentForm(forms.Form):
|
||||||
"""
|
"""
|
||||||
comment = self.cleaned_data["comment"]
|
comment = self.cleaned_data["comment"]
|
||||||
if settings.COMMENTS_ALLOW_PROFANITIES == False:
|
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()]
|
bad_words = [w for w in settings.PROFANITIES_LIST if w in comment.lower()]
|
||||||
if bad_words:
|
if bad_words:
|
||||||
plural = len(bad_words) > 1
|
plural = len(bad_words) > 1
|
||||||
|
|
|
@ -5,7 +5,7 @@ from django.contrib.contenttypes import generic
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
from django.db import models
|
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.utils.translation import ugettext_lazy as _
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
|
@ -2,18 +2,16 @@
|
||||||
Classes allowing "generic" relations through ContentType and object-id fields.
|
Classes allowing "generic" relations through ContentType and object-id fields.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django import oldforms
|
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
from django.db.models import signals
|
from django.db.models import signals
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.fields.related import RelatedField, Field, ManyToManyRel
|
from django.db.models.fields.related import RelatedField, Field, ManyToManyRel
|
||||||
from django.db.models.loading import get_model
|
from django.db.models.loading import get_model
|
||||||
from django.utils.functional import curry
|
|
||||||
|
|
||||||
from django.forms import ModelForm
|
from django.forms import ModelForm
|
||||||
from django.forms.models import BaseModelFormSet, modelformset_factory, save_instance
|
from django.forms.models import BaseModelFormSet, modelformset_factory, save_instance
|
||||||
from django.contrib.admin.options import InlineModelAdmin, flatten_fieldsets
|
from django.contrib.admin.options import InlineModelAdmin, flatten_fieldsets
|
||||||
|
from django.utils.encoding import smart_unicode
|
||||||
|
|
||||||
class GenericForeignKey(object):
|
class GenericForeignKey(object):
|
||||||
"""
|
"""
|
||||||
|
@ -120,19 +118,12 @@ class GenericRelation(RelatedField, Field):
|
||||||
kwargs['serialize'] = False
|
kwargs['serialize'] = False
|
||||||
Field.__init__(self, **kwargs)
|
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):
|
def get_choices_default(self):
|
||||||
return Field.get_choices(self, include_blank=False)
|
return Field.get_choices(self, include_blank=False)
|
||||||
|
|
||||||
def flatten_data(self, follow, obj = None):
|
def value_to_string(self, obj):
|
||||||
new_data = {}
|
qs = getattr(obj, self.name).all()
|
||||||
if obj:
|
return smart_unicode([instance._get_pk_val() for instance in qs])
|
||||||
instance_ids = [instance._get_pk_val() for instance in getattr(obj, self.name).all()]
|
|
||||||
new_data[self.name] = instance_ids
|
|
||||||
return new_data
|
|
||||||
|
|
||||||
def m2m_db_table(self):
|
def m2m_db_table(self):
|
||||||
return self.rel.to._meta.db_table
|
return self.rel.to._meta.db_table
|
||||||
|
@ -290,7 +281,6 @@ class GenericRel(ManyToManyRel):
|
||||||
self.to = to
|
self.to = to
|
||||||
self.related_name = related_name
|
self.related_name = related_name
|
||||||
self.limit_choices_to = limit_choices_to or {}
|
self.limit_choices_to = limit_choices_to or {}
|
||||||
self.edit_inline = False
|
|
||||||
self.symmetrical = symmetrical
|
self.symmetrical = symmetrical
|
||||||
self.multiple = True
|
self.multiple = True
|
||||||
|
|
||||||
|
@ -395,4 +385,3 @@ class GenericStackedInline(GenericInlineModelAdmin):
|
||||||
|
|
||||||
class GenericTabularInline(GenericInlineModelAdmin):
|
class GenericTabularInline(GenericInlineModelAdmin):
|
||||||
template = 'admin/edit_inline/tabular.html'
|
template = 'admin/edit_inline/tabular.html'
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
from django.core import validators
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
JP-specific Form helpers
|
JP-specific Form helpers
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.core import validators
|
|
||||||
from django.forms import ValidationError
|
from django.forms import ValidationError
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.forms.fields import RegexField, Select
|
from django.forms.fields import RegexField, Select
|
||||||
|
|
|
@ -32,3 +32,6 @@ class FieldError(Exception):
|
||||||
"""Some kind of problem with a model field."""
|
"""Some kind of problem with a model field."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class ValidationError(Exception):
|
||||||
|
"""An error while validating data."""
|
||||||
|
pass
|
||||||
|
|
|
@ -57,12 +57,7 @@ class Serializer(object):
|
||||||
"""
|
"""
|
||||||
Convert a field's value to a string.
|
Convert a field's value to a string.
|
||||||
"""
|
"""
|
||||||
if isinstance(field, models.DateTimeField):
|
return smart_unicode(field.value_to_string(obj))
|
||||||
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)
|
|
||||||
|
|
||||||
def start_serialization(self):
|
def start_serialization(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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
|
|
|
@ -1,6 +1,5 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
|
from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
|
||||||
from django.core import validators
|
|
||||||
from django.db import connection
|
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.loading import get_apps, get_app, get_models, get_model, register_models
|
||||||
from django.db.models.query import Q
|
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 import *
|
||||||
from django.db.models.fields.subclassing import SubfieldBase
|
from django.db.models.fields.subclassing import SubfieldBase
|
||||||
from django.db.models.fields.files import FileField, ImageField
|
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
|
from django.db.models import signals
|
||||||
|
|
||||||
# Admin stages.
|
# Admin stages.
|
||||||
|
|
|
@ -8,9 +8,7 @@ try:
|
||||||
except NameError:
|
except NameError:
|
||||||
from sets import Set as set # Python 2.3 fallback.
|
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 # Imported to register signal handler.
|
||||||
import django.db.models.manager # Ditto.
|
|
||||||
from django.core import validators
|
|
||||||
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError
|
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError
|
||||||
from django.db.models.fields import AutoField
|
from django.db.models.fields import AutoField
|
||||||
from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField
|
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.
|
# First, try an UPDATE. If that doesn't update anything, do an INSERT.
|
||||||
pk_val = self._get_pk_val(meta)
|
pk_val = self._get_pk_val(meta)
|
||||||
# Note: the comparison with '' is required for compatibility with
|
pk_set = pk_val is not None
|
||||||
# oldforms-style model creation.
|
|
||||||
pk_set = pk_val is not None and smart_unicode(pk_val) != u''
|
|
||||||
record_exists = True
|
record_exists = True
|
||||||
manager = cls._default_manager
|
manager = cls._default_manager
|
||||||
if pk_set:
|
if pk_set:
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import copy
|
import copy
|
||||||
import datetime
|
import datetime
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import time
|
import time
|
||||||
try:
|
try:
|
||||||
import decimal
|
import decimal
|
||||||
|
@ -12,10 +13,8 @@ from django.db.models import signals
|
||||||
from django.db.models.query_utils import QueryWrapper
|
from django.db.models.query_utils import QueryWrapper
|
||||||
from django.dispatch import dispatcher
|
from django.dispatch import dispatcher
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core import validators
|
|
||||||
from django import oldforms
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core import exceptions
|
||||||
from django.utils.datastructures import DictWrapper
|
from django.utils.datastructures import DictWrapper
|
||||||
from django.utils.functional import curry
|
from django.utils.functional import curry
|
||||||
from django.utils.itercompat import tee
|
from django.utils.itercompat import tee
|
||||||
|
@ -34,17 +33,6 @@ BLANK_CHOICE_NONE = [("", "None")]
|
||||||
class FieldDoesNotExist(Exception):
|
class FieldDoesNotExist(Exception):
|
||||||
pass
|
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:
|
# A guide to Field parameters:
|
||||||
#
|
#
|
||||||
# * name: The name of the field specifed in the model.
|
# * 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,
|
def __init__(self, verbose_name=None, name=None, primary_key=False,
|
||||||
max_length=None, unique=False, blank=False, null=False,
|
max_length=None, unique=False, blank=False, null=False,
|
||||||
db_index=False, core=False, rel=None, default=NOT_PROVIDED,
|
db_index=False, rel=None, default=NOT_PROVIDED, editable=True,
|
||||||
editable=True, serialize=True, unique_for_date=None,
|
serialize=True, unique_for_date=None, unique_for_month=None,
|
||||||
unique_for_month=None, unique_for_year=None, validator_list=None,
|
unique_for_year=None, choices=None, help_text='', db_column=None,
|
||||||
choices=None, help_text='', db_column=None, db_tablespace=None,
|
db_tablespace=None, auto_created=False):
|
||||||
auto_created=False):
|
|
||||||
self.name = name
|
self.name = name
|
||||||
self.verbose_name = verbose_name
|
self.verbose_name = verbose_name
|
||||||
self.primary_key = primary_key
|
self.primary_key = primary_key
|
||||||
|
@ -87,10 +74,10 @@ class Field(object):
|
||||||
# option whenever '' is a possible value.
|
# option whenever '' is a possible value.
|
||||||
if self.empty_strings_allowed and connection.features.interprets_empty_strings_as_nulls:
|
if self.empty_strings_allowed and connection.features.interprets_empty_strings_as_nulls:
|
||||||
self.null = True
|
self.null = True
|
||||||
self.core, self.rel, self.default = core, rel, default
|
self.rel = rel
|
||||||
|
self.default = default
|
||||||
self.editable = editable
|
self.editable = editable
|
||||||
self.serialize = serialize
|
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_date, self.unique_for_month = unique_for_date, unique_for_month
|
||||||
self.unique_for_year = unique_for_year
|
self.unique_for_year = unique_for_year
|
||||||
self._choices = choices or []
|
self._choices = choices or []
|
||||||
|
@ -126,8 +113,8 @@ class Field(object):
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
"""
|
"""
|
||||||
Converts the input value into the expected Python data type, raising
|
Converts the input value into the expected Python data type, raising
|
||||||
validators.ValidationError if the data can't be converted. Returns the
|
django.core.exceptions.ValidationError if the data can't be converted.
|
||||||
converted value. Subclasses should override this.
|
Returns the converted value. Subclasses should override this.
|
||||||
"""
|
"""
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@ -252,93 +239,9 @@ class Field(object):
|
||||||
return None
|
return None
|
||||||
return ""
|
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):
|
def get_validator_unique_lookup_type(self):
|
||||||
return '%s__exact' % self.name
|
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):
|
def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH):
|
||||||
"""Returns choices with a default blank choices included, for use
|
"""Returns choices with a default blank choices included, for use
|
||||||
as SelectField choices for this field."""
|
as SelectField choices for this field."""
|
||||||
|
@ -366,19 +269,12 @@ class Field(object):
|
||||||
else:
|
else:
|
||||||
return self.get_default()
|
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
|
Returns a string value of this field from the passed obj.
|
||||||
"flattened" string values for the admin view. obj is the instance to
|
This is used by the serialization framework.
|
||||||
extract the values from.
|
|
||||||
"""
|
"""
|
||||||
return {self.attname: self._get_val_from_obj(obj)}
|
return smart_unicode(self._get_val_from_obj(obj))
|
||||||
|
|
||||||
def get_follow(self, override=None):
|
|
||||||
if override != None:
|
|
||||||
return override
|
|
||||||
else:
|
|
||||||
return self.editable
|
|
||||||
|
|
||||||
def bind(self, fieldmapping, original, bound_field_class):
|
def bind(self, fieldmapping, original, bound_field_class):
|
||||||
return bound_field_class(self, fieldmapping, original)
|
return bound_field_class(self, fieldmapping, original)
|
||||||
|
@ -432,29 +328,14 @@ class AutoField(Field):
|
||||||
try:
|
try:
|
||||||
return int(value)
|
return int(value)
|
||||||
except (TypeError, ValueError):
|
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):
|
def get_db_prep_value(self, value):
|
||||||
if value is None:
|
if value is None:
|
||||||
return None
|
return None
|
||||||
return int(value)
|
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):
|
def contribute_to_class(self, cls, name):
|
||||||
assert not cls._meta.has_auto_field, "A model can't have more than one AutoField."
|
assert not cls._meta.has_auto_field, "A model can't have more than one AutoField."
|
||||||
super(AutoField, self).contribute_to_class(cls, name)
|
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 (True, False): return value
|
||||||
if value in ('t', 'True', '1'): return True
|
if value in ('t', 'True', '1'): return True
|
||||||
if value in ('f', 'False', '0'): return False
|
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):
|
def get_db_prep_value(self, value):
|
||||||
if value is None:
|
if value is None:
|
||||||
return None
|
return None
|
||||||
return bool(value)
|
return bool(value)
|
||||||
|
|
||||||
def get_manipulator_field_objs(self):
|
|
||||||
return [oldforms.CheckboxField]
|
|
||||||
|
|
||||||
def formfield(self, **kwargs):
|
def formfield(self, **kwargs):
|
||||||
defaults = {'form_class': forms.BooleanField}
|
defaults = {'form_class': forms.BooleanField}
|
||||||
defaults.update(kwargs)
|
defaults.update(kwargs)
|
||||||
return super(BooleanField, self).formfield(**defaults)
|
return super(BooleanField, self).formfield(**defaults)
|
||||||
|
|
||||||
class CharField(Field):
|
class CharField(Field):
|
||||||
def get_manipulator_field_objs(self):
|
|
||||||
return [oldforms.TextField]
|
|
||||||
|
|
||||||
def get_internal_type(self):
|
def get_internal_type(self):
|
||||||
return "CharField"
|
return "CharField"
|
||||||
|
|
||||||
|
@ -507,7 +383,8 @@ class CharField(Field):
|
||||||
if self.null:
|
if self.null:
|
||||||
return value
|
return value
|
||||||
else:
|
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)
|
return smart_unicode(value)
|
||||||
|
|
||||||
def formfield(self, **kwargs):
|
def formfield(self, **kwargs):
|
||||||
|
@ -517,8 +394,9 @@ class CharField(Field):
|
||||||
|
|
||||||
# TODO: Maybe move this into contrib, because it's specialized.
|
# TODO: Maybe move this into contrib, because it's specialized.
|
||||||
class CommaSeparatedIntegerField(CharField):
|
class CommaSeparatedIntegerField(CharField):
|
||||||
def get_manipulator_field_objs(self):
|
pass
|
||||||
return [oldforms.CommaSeparatedIntegerField]
|
|
||||||
|
ansi_date_re = re.compile(r'^\d{4}-\d{1,2}-\d{1,2}$')
|
||||||
|
|
||||||
class DateField(Field):
|
class DateField(Field):
|
||||||
empty_strings_allowed = False
|
empty_strings_allowed = False
|
||||||
|
@ -540,11 +418,20 @@ class DateField(Field):
|
||||||
return value.date()
|
return value.date()
|
||||||
if isinstance(value, datetime.date):
|
if isinstance(value, datetime.date):
|
||||||
return value
|
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:
|
try:
|
||||||
return datetime.date(*time.strptime(value, '%Y-%m-%d')[:3])
|
return datetime.date(year, month, day)
|
||||||
except ValueError:
|
except ValueError, e:
|
||||||
raise validators.ValidationError, _('Enter a valid date in YYYY-MM-DD format.')
|
msg = _('Invalid date: %s') % _(str(e))
|
||||||
|
raise exceptions.ValidationError(msg)
|
||||||
|
|
||||||
def pre_save(self, model_instance, add):
|
def pre_save(self, model_instance, add):
|
||||||
if self.auto_now or (self.auto_now_add and 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,
|
setattr(cls, 'get_previous_by_%s' % self.name,
|
||||||
curry(cls._get_next_or_previous_by_FIELD, field=self, is_next=False))
|
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):
|
def get_db_prep_lookup(self, lookup_type, value):
|
||||||
# For "__month" and "__day" lookups, convert the value to a string so
|
# For "__month" and "__day" lookups, convert the value to a string so
|
||||||
# the database backend always sees a consistent type.
|
# the database backend always sees a consistent type.
|
||||||
|
@ -580,16 +460,13 @@ class DateField(Field):
|
||||||
# Casts dates into the format expected by the backend
|
# Casts dates into the format expected by the backend
|
||||||
return connection.ops.value_to_db_date(self.to_python(value))
|
return connection.ops.value_to_db_date(self.to_python(value))
|
||||||
|
|
||||||
def get_manipulator_field_objs(self):
|
def value_to_string(self, obj):
|
||||||
return [oldforms.DateField]
|
|
||||||
|
|
||||||
def flatten_data(self, follow, obj=None):
|
|
||||||
val = self._get_val_from_obj(obj)
|
val = self._get_val_from_obj(obj)
|
||||||
if val is None:
|
if val is None:
|
||||||
data = ''
|
data = ''
|
||||||
else:
|
else:
|
||||||
data = datetime_safe.new_date(val).strftime("%Y-%m-%d")
|
data = datetime_safe.new_date(val).strftime("%Y-%m-%d")
|
||||||
return {self.attname: data}
|
return data
|
||||||
|
|
||||||
def formfield(self, **kwargs):
|
def formfield(self, **kwargs):
|
||||||
defaults = {'form_class': forms.DateField}
|
defaults = {'form_class': forms.DateField}
|
||||||
|
@ -616,7 +493,8 @@ class DateTimeField(DateField):
|
||||||
value, usecs = value.split('.')
|
value, usecs = value.split('.')
|
||||||
usecs = int(usecs)
|
usecs = int(usecs)
|
||||||
except ValueError:
|
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:
|
else:
|
||||||
usecs = 0
|
usecs = 0
|
||||||
kwargs = {'microsecond': usecs}
|
kwargs = {'microsecond': usecs}
|
||||||
|
@ -633,40 +511,21 @@ class DateTimeField(DateField):
|
||||||
return datetime.datetime(*time.strptime(value, '%Y-%m-%d')[:3],
|
return datetime.datetime(*time.strptime(value, '%Y-%m-%d')[:3],
|
||||||
**kwargs)
|
**kwargs)
|
||||||
except ValueError:
|
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):
|
def get_db_prep_value(self, value):
|
||||||
# Casts dates into the format expected by the backend
|
# Casts dates into the format expected by the backend
|
||||||
return connection.ops.value_to_db_datetime(self.to_python(value))
|
return connection.ops.value_to_db_datetime(self.to_python(value))
|
||||||
|
|
||||||
def get_manipulator_field_objs(self):
|
def value_to_string(self, obj):
|
||||||
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):
|
|
||||||
val = self._get_val_from_obj(obj)
|
val = self._get_val_from_obj(obj)
|
||||||
date_field, time_field = self.get_manipulator_field_names('')
|
|
||||||
if val is None:
|
if val is None:
|
||||||
date_data = time_data = ''
|
data = ''
|
||||||
else:
|
else:
|
||||||
d = datetime_safe.new_datetime(val)
|
d = datetime_safe.new_datetime(val)
|
||||||
date_data = d.strftime('%Y-%m-%d')
|
data = d.strftime('%Y-%m-%d %H:%M:%S')
|
||||||
time_data = d.strftime('%H:%M:%S')
|
return data
|
||||||
return {date_field: date_data, time_field: time_data}
|
|
||||||
|
|
||||||
def formfield(self, **kwargs):
|
def formfield(self, **kwargs):
|
||||||
defaults = {'form_class': forms.DateTimeField}
|
defaults = {'form_class': forms.DateTimeField}
|
||||||
|
@ -688,7 +547,7 @@ class DecimalField(Field):
|
||||||
try:
|
try:
|
||||||
return decimal.Decimal(value)
|
return decimal.Decimal(value)
|
||||||
except decimal.InvalidOperation:
|
except decimal.InvalidOperation:
|
||||||
raise validators.ValidationError(
|
raise exceptions.ValidationError(
|
||||||
_("This value must be a decimal number."))
|
_("This value must be a decimal number."))
|
||||||
|
|
||||||
def _format(self, value):
|
def _format(self, value):
|
||||||
|
@ -715,9 +574,6 @@ class DecimalField(Field):
|
||||||
return connection.ops.value_to_db_decimal(self.to_python(value),
|
return connection.ops.value_to_db_decimal(self.to_python(value),
|
||||||
self.max_digits, self.decimal_places)
|
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):
|
def formfield(self, **kwargs):
|
||||||
defaults = {
|
defaults = {
|
||||||
'max_digits': self.max_digits,
|
'max_digits': self.max_digits,
|
||||||
|
@ -732,9 +588,6 @@ class EmailField(CharField):
|
||||||
kwargs['max_length'] = kwargs.get('max_length', 75)
|
kwargs['max_length'] = kwargs.get('max_length', 75)
|
||||||
CharField.__init__(self, *args, **kwargs)
|
CharField.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
def get_manipulator_field_objs(self):
|
|
||||||
return [oldforms.EmailField]
|
|
||||||
|
|
||||||
def formfield(self, **kwargs):
|
def formfield(self, **kwargs):
|
||||||
defaults = {'form_class': forms.EmailField}
|
defaults = {'form_class': forms.EmailField}
|
||||||
defaults.update(kwargs)
|
defaults.update(kwargs)
|
||||||
|
@ -756,9 +609,6 @@ class FilePathField(Field):
|
||||||
defaults.update(kwargs)
|
defaults.update(kwargs)
|
||||||
return super(FilePathField, self).formfield(**defaults)
|
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):
|
def get_internal_type(self):
|
||||||
return "FilePathField"
|
return "FilePathField"
|
||||||
|
|
||||||
|
@ -770,9 +620,6 @@ class FloatField(Field):
|
||||||
return None
|
return None
|
||||||
return float(value)
|
return float(value)
|
||||||
|
|
||||||
def get_manipulator_field_objs(self):
|
|
||||||
return [oldforms.FloatField]
|
|
||||||
|
|
||||||
def get_internal_type(self):
|
def get_internal_type(self):
|
||||||
return "FloatField"
|
return "FloatField"
|
||||||
|
|
||||||
|
@ -788,9 +635,6 @@ class IntegerField(Field):
|
||||||
return None
|
return None
|
||||||
return int(value)
|
return int(value)
|
||||||
|
|
||||||
def get_manipulator_field_objs(self):
|
|
||||||
return [oldforms.IntegerField]
|
|
||||||
|
|
||||||
def get_internal_type(self):
|
def get_internal_type(self):
|
||||||
return "IntegerField"
|
return "IntegerField"
|
||||||
|
|
||||||
|
@ -800,7 +644,8 @@ class IntegerField(Field):
|
||||||
try:
|
try:
|
||||||
return int(value)
|
return int(value)
|
||||||
except (TypeError, ValueError):
|
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):
|
def formfield(self, **kwargs):
|
||||||
defaults = {'form_class': forms.IntegerField}
|
defaults = {'form_class': forms.IntegerField}
|
||||||
|
@ -813,9 +658,6 @@ class IPAddressField(Field):
|
||||||
kwargs['max_length'] = 15
|
kwargs['max_length'] = 15
|
||||||
Field.__init__(self, *args, **kwargs)
|
Field.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
def get_manipulator_field_objs(self):
|
|
||||||
return [oldforms.IPAddressField]
|
|
||||||
|
|
||||||
def get_internal_type(self):
|
def get_internal_type(self):
|
||||||
return "IPAddressField"
|
return "IPAddressField"
|
||||||
|
|
||||||
|
@ -838,16 +680,14 @@ class NullBooleanField(Field):
|
||||||
if value in ('None'): return None
|
if value in ('None'): return None
|
||||||
if value in ('t', 'True', '1'): return True
|
if value in ('t', 'True', '1'): return True
|
||||||
if value in ('f', 'False', '0'): return False
|
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):
|
def get_db_prep_value(self, value):
|
||||||
if value is None:
|
if value is None:
|
||||||
return None
|
return None
|
||||||
return bool(value)
|
return bool(value)
|
||||||
|
|
||||||
def get_manipulator_field_objs(self):
|
|
||||||
return [oldforms.NullBooleanField]
|
|
||||||
|
|
||||||
def formfield(self, **kwargs):
|
def formfield(self, **kwargs):
|
||||||
defaults = {
|
defaults = {
|
||||||
'form_class': forms.NullBooleanField,
|
'form_class': forms.NullBooleanField,
|
||||||
|
@ -858,9 +698,6 @@ class NullBooleanField(Field):
|
||||||
return super(NullBooleanField, self).formfield(**defaults)
|
return super(NullBooleanField, self).formfield(**defaults)
|
||||||
|
|
||||||
class PhoneNumberField(Field):
|
class PhoneNumberField(Field):
|
||||||
def get_manipulator_field_objs(self):
|
|
||||||
return [oldforms.PhoneNumberField]
|
|
||||||
|
|
||||||
def get_internal_type(self):
|
def get_internal_type(self):
|
||||||
return "PhoneNumberField"
|
return "PhoneNumberField"
|
||||||
|
|
||||||
|
@ -871,9 +708,6 @@ class PhoneNumberField(Field):
|
||||||
return super(PhoneNumberField, self).formfield(**defaults)
|
return super(PhoneNumberField, self).formfield(**defaults)
|
||||||
|
|
||||||
class PositiveIntegerField(IntegerField):
|
class PositiveIntegerField(IntegerField):
|
||||||
def get_manipulator_field_objs(self):
|
|
||||||
return [oldforms.PositiveIntegerField]
|
|
||||||
|
|
||||||
def get_internal_type(self):
|
def get_internal_type(self):
|
||||||
return "PositiveIntegerField"
|
return "PositiveIntegerField"
|
||||||
|
|
||||||
|
@ -883,9 +717,6 @@ class PositiveIntegerField(IntegerField):
|
||||||
return super(PositiveIntegerField, self).formfield(**defaults)
|
return super(PositiveIntegerField, self).formfield(**defaults)
|
||||||
|
|
||||||
class PositiveSmallIntegerField(IntegerField):
|
class PositiveSmallIntegerField(IntegerField):
|
||||||
def get_manipulator_field_objs(self):
|
|
||||||
return [oldforms.PositiveSmallIntegerField]
|
|
||||||
|
|
||||||
def get_internal_type(self):
|
def get_internal_type(self):
|
||||||
return "PositiveSmallIntegerField"
|
return "PositiveSmallIntegerField"
|
||||||
|
|
||||||
|
@ -897,7 +728,6 @@ class PositiveSmallIntegerField(IntegerField):
|
||||||
class SlugField(CharField):
|
class SlugField(CharField):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
kwargs['max_length'] = kwargs.get('max_length', 50)
|
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.
|
# Set db_index=True unless it's been set manually.
|
||||||
if 'db_index' not in kwargs:
|
if 'db_index' not in kwargs:
|
||||||
kwargs['db_index'] = True
|
kwargs['db_index'] = True
|
||||||
|
@ -907,23 +737,15 @@ class SlugField(CharField):
|
||||||
return "SlugField"
|
return "SlugField"
|
||||||
|
|
||||||
def formfield(self, **kwargs):
|
def formfield(self, **kwargs):
|
||||||
defaults = {'form_class': forms.RegexField, 'regex': r'^[a-zA-Z0-9_-]+$',
|
defaults = {'form_class': forms.SlugField}
|
||||||
'error_messages': {'invalid': _(u"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens.")},
|
|
||||||
}
|
|
||||||
defaults.update(kwargs)
|
defaults.update(kwargs)
|
||||||
return super(SlugField, self).formfield(**defaults)
|
return super(SlugField, self).formfield(**defaults)
|
||||||
|
|
||||||
class SmallIntegerField(IntegerField):
|
class SmallIntegerField(IntegerField):
|
||||||
def get_manipulator_field_objs(self):
|
|
||||||
return [oldforms.SmallIntegerField]
|
|
||||||
|
|
||||||
def get_internal_type(self):
|
def get_internal_type(self):
|
||||||
return "SmallIntegerField"
|
return "SmallIntegerField"
|
||||||
|
|
||||||
class TextField(Field):
|
class TextField(Field):
|
||||||
def get_manipulator_field_objs(self):
|
|
||||||
return [oldforms.LargeTextField]
|
|
||||||
|
|
||||||
def get_internal_type(self):
|
def get_internal_type(self):
|
||||||
return "TextField"
|
return "TextField"
|
||||||
|
|
||||||
|
@ -957,7 +779,8 @@ class TimeField(Field):
|
||||||
value, usecs = value.split('.')
|
value, usecs = value.split('.')
|
||||||
usecs = int(usecs)
|
usecs = int(usecs)
|
||||||
except ValueError:
|
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:
|
else:
|
||||||
usecs = 0
|
usecs = 0
|
||||||
kwargs = {'microsecond': usecs}
|
kwargs = {'microsecond': usecs}
|
||||||
|
@ -970,7 +793,8 @@ class TimeField(Field):
|
||||||
return datetime.time(*time.strptime(value, '%H:%M')[3:5],
|
return datetime.time(*time.strptime(value, '%H:%M')[3:5],
|
||||||
**kwargs)
|
**kwargs)
|
||||||
except ValueError:
|
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):
|
def pre_save(self, model_instance, add):
|
||||||
if self.auto_now or (self.auto_now_add and 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
|
# Casts times into the format expected by the backend
|
||||||
return connection.ops.value_to_db_time(self.to_python(value))
|
return connection.ops.value_to_db_time(self.to_python(value))
|
||||||
|
|
||||||
def get_manipulator_field_objs(self):
|
def value_to_string(self, obj):
|
||||||
return [oldforms.TimeField]
|
|
||||||
|
|
||||||
def flatten_data(self,follow, obj = None):
|
|
||||||
val = self._get_val_from_obj(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):
|
def formfield(self, **kwargs):
|
||||||
defaults = {'form_class': forms.TimeField}
|
defaults = {'form_class': forms.TimeField}
|
||||||
|
@ -999,23 +824,15 @@ class TimeField(Field):
|
||||||
class URLField(CharField):
|
class URLField(CharField):
|
||||||
def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs):
|
def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs):
|
||||||
kwargs['max_length'] = kwargs.get('max_length', 200)
|
kwargs['max_length'] = kwargs.get('max_length', 200)
|
||||||
if verify_exists:
|
|
||||||
kwargs.setdefault('validator_list', []).append(validators.isExistingURL)
|
|
||||||
self.verify_exists = verify_exists
|
self.verify_exists = verify_exists
|
||||||
CharField.__init__(self, verbose_name, name, **kwargs)
|
CharField.__init__(self, verbose_name, name, **kwargs)
|
||||||
|
|
||||||
def get_manipulator_field_objs(self):
|
|
||||||
return [oldforms.URLField]
|
|
||||||
|
|
||||||
def formfield(self, **kwargs):
|
def formfield(self, **kwargs):
|
||||||
defaults = {'form_class': forms.URLField, 'verify_exists': self.verify_exists}
|
defaults = {'form_class': forms.URLField, 'verify_exists': self.verify_exists}
|
||||||
defaults.update(kwargs)
|
defaults.update(kwargs)
|
||||||
return super(URLField, self).formfield(**defaults)
|
return super(URLField, self).formfield(**defaults)
|
||||||
|
|
||||||
class USStateField(Field):
|
class USStateField(Field):
|
||||||
def get_manipulator_field_objs(self):
|
|
||||||
return [oldforms.USStateField]
|
|
||||||
|
|
||||||
def get_internal_type(self):
|
def get_internal_type(self):
|
||||||
return "USStateField"
|
return "USStateField"
|
||||||
|
|
||||||
|
@ -1029,7 +846,3 @@ class XMLField(TextField):
|
||||||
def __init__(self, verbose_name=None, name=None, schema_path=None, **kwargs):
|
def __init__(self, verbose_name=None, name=None, schema_path=None, **kwargs):
|
||||||
self.schema_path = schema_path
|
self.schema_path = schema_path
|
||||||
Field.__init__(self, verbose_name, name, **kwargs)
|
Field.__init__(self, verbose_name, name, **kwargs)
|
||||||
|
|
||||||
def get_manipulator_field_objs(self):
|
|
||||||
return [curry(oldforms.XMLLargeTextField, schema_path=self.schema_path)]
|
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,7 @@ from django.utils.functional import curry
|
||||||
from django.db.models import signals
|
from django.db.models import signals
|
||||||
from django.utils.encoding import force_unicode, smart_str
|
from django.utils.encoding import force_unicode, smart_str
|
||||||
from django.utils.translation import ugettext_lazy, ugettext as _
|
from django.utils.translation import ugettext_lazy, ugettext as _
|
||||||
from django import oldforms
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.core import validators
|
|
||||||
from django.db.models.loading import cache
|
from django.db.models.loading import cache
|
||||||
|
|
||||||
class FieldFile(File):
|
class FieldFile(File):
|
||||||
|
@ -126,7 +124,7 @@ class FileField(Field):
|
||||||
attr_class = FieldFile
|
attr_class = FieldFile
|
||||||
|
|
||||||
def __init__(self, verbose_name=None, name=None, upload_to='', storage=None, **kwargs):
|
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:
|
if arg in kwargs:
|
||||||
raise TypeError("'%s' is not a valid argument for %s." % (arg, self.__class__))
|
raise TypeError("'%s' is not a valid argument for %s." % (arg, self.__class__))
|
||||||
|
|
||||||
|
@ -153,42 +151,6 @@ class FileField(Field):
|
||||||
return None
|
return None
|
||||||
return unicode(value)
|
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):
|
def contribute_to_class(self, cls, name):
|
||||||
super(FileField, self).contribute_to_class(cls, name)
|
super(FileField, self).contribute_to_class(cls, name)
|
||||||
setattr(cls, self.name, FileDescriptor(self))
|
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.
|
# Otherwise, just close the file, so it doesn't tie up resources.
|
||||||
file.close()
|
file.close()
|
||||||
|
|
||||||
def get_manipulator_field_objs(self):
|
def save_file(self, new_data, new_object, original_object, change, rel,
|
||||||
return [oldforms.FileUploadField, oldforms.HiddenField]
|
save=True):
|
||||||
|
upload_field_name = self.name + '_file'
|
||||||
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]
|
|
||||||
if new_data.get(upload_field_name, False):
|
if new_data.get(upload_field_name, False):
|
||||||
if rel:
|
if rel:
|
||||||
file = new_data[upload_field_name][0]
|
file = new_data[upload_field_name][0]
|
||||||
|
@ -282,9 +239,6 @@ class ImageField(FileField):
|
||||||
self.width_field, self.height_field = width_field, height_field
|
self.width_field, self.height_field = width_field, height_field
|
||||||
FileField.__init__(self, verbose_name, name, **kwargs)
|
FileField.__init__(self, verbose_name, name, **kwargs)
|
||||||
|
|
||||||
def get_manipulator_field_objs(self):
|
|
||||||
return [oldforms.ImageUploadField, oldforms.HiddenField]
|
|
||||||
|
|
||||||
def formfield(self, **kwargs):
|
def formfield(self, **kwargs):
|
||||||
defaults = {'form_class': forms.ImageField}
|
defaults = {'form_class': forms.ImageField}
|
||||||
defaults.update(kwargs)
|
defaults.update(kwargs)
|
||||||
|
|
|
@ -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.related import RelatedObject
|
||||||
from django.db.models.query import QuerySet
|
from django.db.models.query import QuerySet
|
||||||
from django.db.models.query_utils import QueryWrapper
|
from django.db.models.query_utils import QueryWrapper
|
||||||
|
from django.utils.encoding import smart_unicode
|
||||||
from django.utils.translation import ugettext_lazy, string_concat, ungettext, ugettext as _
|
from django.utils.translation import ugettext_lazy, string_concat, ungettext, ugettext as _
|
||||||
from django.utils.functional import curry
|
from django.utils.functional import curry
|
||||||
from django.core import validators
|
from django.core import exceptions
|
||||||
from django import oldforms
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -15,9 +15,6 @@ try:
|
||||||
except NameError:
|
except NameError:
|
||||||
from sets import Set as set # Python 2.3 fallback
|
from sets import Set as set # Python 2.3 fallback
|
||||||
|
|
||||||
# Values for Relation.edit_inline.
|
|
||||||
TABULAR, STACKED = 1, 2
|
|
||||||
|
|
||||||
RECURSIVE_RELATIONSHIP_CONSTANT = 'self'
|
RECURSIVE_RELATIONSHIP_CONSTANT = 'self'
|
||||||
|
|
||||||
pending_lookups = {}
|
pending_lookups = {}
|
||||||
|
@ -83,14 +80,6 @@ def do_pending_lookups(sender, **kwargs):
|
||||||
|
|
||||||
signals.class_prepared.connect(do_pending_lookups)
|
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
|
#HACK
|
||||||
class RelatedField(object):
|
class RelatedField(object):
|
||||||
def contribute_to_class(self, cls, name):
|
def contribute_to_class(self, cls, name):
|
||||||
|
@ -580,18 +569,14 @@ class ReverseManyRelatedObjectsDescriptor(object):
|
||||||
manager.add(*value)
|
manager.add(*value)
|
||||||
|
|
||||||
class ManyToOneRel(object):
|
class ManyToOneRel(object):
|
||||||
def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None,
|
def __init__(self, to, field_name, related_name=None,
|
||||||
max_num_in_admin=None, num_extra_on_change=1, edit_inline=False,
|
limit_choices_to=None, lookup_overrides=None, parent_link=False):
|
||||||
related_name=None, limit_choices_to=None, lookup_overrides=None,
|
|
||||||
parent_link=False):
|
|
||||||
try:
|
try:
|
||||||
to._meta
|
to._meta
|
||||||
except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
|
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
|
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.to, self.field_name = to, field_name
|
||||||
self.num_in_admin, self.edit_inline = num_in_admin, edit_inline
|
self.related_name = related_name
|
||||||
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
|
|
||||||
if limit_choices_to is None:
|
if limit_choices_to is None:
|
||||||
limit_choices_to = {}
|
limit_choices_to = {}
|
||||||
self.limit_choices_to = limit_choices_to
|
self.limit_choices_to = limit_choices_to
|
||||||
|
@ -611,29 +596,21 @@ class ManyToOneRel(object):
|
||||||
return data[0]
|
return data[0]
|
||||||
|
|
||||||
class OneToOneRel(ManyToOneRel):
|
class OneToOneRel(ManyToOneRel):
|
||||||
def __init__(self, to, field_name, num_in_admin=0, min_num_in_admin=None,
|
def __init__(self, to, field_name, related_name=None,
|
||||||
max_num_in_admin=None, num_extra_on_change=None, edit_inline=False,
|
limit_choices_to=None, lookup_overrides=None, parent_link=False):
|
||||||
related_name=None, limit_choices_to=None, lookup_overrides=None,
|
super(OneToOneRel, self).__init__(to, field_name,
|
||||||
parent_link=False):
|
related_name=related_name, limit_choices_to=limit_choices_to,
|
||||||
# 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,
|
|
||||||
lookup_overrides=lookup_overrides, parent_link=parent_link)
|
lookup_overrides=lookup_overrides, parent_link=parent_link)
|
||||||
self.multiple = False
|
self.multiple = False
|
||||||
|
|
||||||
class ManyToManyRel(object):
|
class ManyToManyRel(object):
|
||||||
def __init__(self, to, num_in_admin=0, related_name=None,
|
def __init__(self, to, related_name=None, limit_choices_to=None,
|
||||||
limit_choices_to=None, symmetrical=True, through=None):
|
symmetrical=True, through=None):
|
||||||
self.to = to
|
self.to = to
|
||||||
self.num_in_admin = num_in_admin
|
|
||||||
self.related_name = related_name
|
self.related_name = related_name
|
||||||
if limit_choices_to is None:
|
if limit_choices_to is None:
|
||||||
limit_choices_to = {}
|
limit_choices_to = {}
|
||||||
self.limit_choices_to = limit_choices_to
|
self.limit_choices_to = limit_choices_to
|
||||||
self.edit_inline = False
|
|
||||||
self.symmetrical = symmetrical
|
self.symmetrical = symmetrical
|
||||||
self.multiple = True
|
self.multiple = True
|
||||||
self.through = through
|
self.through = through
|
||||||
|
@ -651,11 +628,6 @@ class ForeignKey(RelatedField, Field):
|
||||||
kwargs['verbose_name'] = kwargs.get('verbose_name', None)
|
kwargs['verbose_name'] = kwargs.get('verbose_name', None)
|
||||||
|
|
||||||
kwargs['rel'] = rel_class(to, to_field,
|
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),
|
related_name=kwargs.pop('related_name', None),
|
||||||
limit_choices_to=kwargs.pop('limit_choices_to', None),
|
limit_choices_to=kwargs.pop('limit_choices_to', None),
|
||||||
lookup_overrides=kwargs.pop('lookup_overrides', None),
|
lookup_overrides=kwargs.pop('lookup_overrides', None),
|
||||||
|
@ -670,15 +642,6 @@ class ForeignKey(RelatedField, Field):
|
||||||
def get_validator_unique_lookup_type(self):
|
def get_validator_unique_lookup_type(self):
|
||||||
return '%s__%s__exact' % (self.name, self.rel.get_related_field().name)
|
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):
|
def get_default(self):
|
||||||
"Here we check if the default value is an object and return the to_field if so."
|
"Here we check if the default value is an object and return the to_field if so."
|
||||||
field_default = super(ForeignKey, self).get_default()
|
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 getattr(field_default, self.rel.get_related_field().attname)
|
||||||
return field_default
|
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):
|
def get_db_prep_save(self, value):
|
||||||
if value == '' or value == None:
|
if value == '' or value == None:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
return self.rel.get_related_field().get_db_prep_save(value)
|
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:
|
if not obj:
|
||||||
# In required many-to-one fields with only one available choice,
|
# In required many-to-one fields with only one available choice,
|
||||||
# select that one available choice. Note: For SelectFields
|
# select that one available choice. Note: For SelectFields
|
||||||
|
@ -705,8 +664,8 @@ class ForeignKey(RelatedField, Field):
|
||||||
if not self.blank and self.choices:
|
if not self.blank and self.choices:
|
||||||
choice_list = self.get_choices_default()
|
choice_list = self.get_choices_default()
|
||||||
if len(choice_list) == 2:
|
if len(choice_list) == 2:
|
||||||
return {self.attname: choice_list[1][0]}
|
return smart_unicode(choice_list[1][0])
|
||||||
return Field.flatten_data(self, follow, obj)
|
return Field.value_to_string(self, obj)
|
||||||
|
|
||||||
def contribute_to_class(self, cls, name):
|
def contribute_to_class(self, cls, name):
|
||||||
super(ForeignKey, self).contribute_to_class(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):
|
def __init__(self, to, to_field=None, **kwargs):
|
||||||
kwargs['unique'] = True
|
kwargs['unique'] = True
|
||||||
if 'num_in_admin' not in kwargs:
|
|
||||||
kwargs['num_in_admin'] = 0
|
|
||||||
super(OneToOneField, self).__init__(to, to_field, OneToOneRel, **kwargs)
|
super(OneToOneField, self).__init__(to, to_field, OneToOneRel, **kwargs)
|
||||||
|
|
||||||
def contribute_to_related_class(self, cls, related):
|
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['verbose_name'] = kwargs.get('verbose_name', None)
|
||||||
kwargs['rel'] = ManyToManyRel(to,
|
kwargs['rel'] = ManyToManyRel(to,
|
||||||
num_in_admin=kwargs.pop('num_in_admin', 0),
|
|
||||||
related_name=kwargs.pop('related_name', None),
|
related_name=kwargs.pop('related_name', None),
|
||||||
limit_choices_to=kwargs.pop('limit_choices_to', None),
|
limit_choices_to=kwargs.pop('limit_choices_to', None),
|
||||||
symmetrical=kwargs.pop('symmetrical', True),
|
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.')
|
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)
|
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):
|
def get_choices_default(self):
|
||||||
return Field.get_choices(self, include_blank=False)
|
return Field.get_choices(self, include_blank=False)
|
||||||
|
|
||||||
|
@ -863,25 +815,27 @@ class ManyToManyField(RelatedField, Field):
|
||||||
objects = mod._default_manager.in_bulk(pks)
|
objects = mod._default_manager.in_bulk(pks)
|
||||||
if len(objects) != len(pks):
|
if len(objects) != len(pks):
|
||||||
badkeys = [k for k in pks if k not in objects]
|
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.",
|
raise exceptions.ValidationError(
|
||||||
"Please enter valid %(self)s IDs. The values %(value)r are invalid.", len(badkeys)) % {
|
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,
|
'self': self.verbose_name,
|
||||||
'value': len(badkeys) == 1 and badkeys[0] or tuple(badkeys),
|
'value': len(badkeys) == 1 and badkeys[0] or tuple(badkeys),
|
||||||
}
|
})
|
||||||
|
|
||||||
def flatten_data(self, follow, obj = None):
|
def value_to_string(self, obj):
|
||||||
new_data = {}
|
data = ''
|
||||||
if obj:
|
if obj:
|
||||||
instance_ids = [instance._get_pk_val() for instance in getattr(obj, self.name).all()]
|
qs = getattr(obj, self.name).all()
|
||||||
new_data[self.name] = instance_ids
|
data = [instance._get_pk_val() for instance in qs]
|
||||||
else:
|
else:
|
||||||
# In required many-to-many fields with only one available choice,
|
# In required many-to-many fields with only one available choice,
|
||||||
# select that 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()
|
choices_list = self.get_choices_default()
|
||||||
if len(choices_list) == 1:
|
if len(choices_list) == 1:
|
||||||
new_data[self.name] = [choices_list[0][0]]
|
data = [choices_list[0][0]]
|
||||||
return new_data
|
return smart_unicode(data)
|
||||||
|
|
||||||
def contribute_to_class(self, cls, name):
|
def contribute_to_class(self, cls, name):
|
||||||
super(ManyToManyField, self).contribute_to_class(cls, name)
|
super(ManyToManyField, self).contribute_to_class(cls, name)
|
||||||
|
|
|
@ -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))
|
|
|
@ -396,28 +396,6 @@ class Options(object):
|
||||||
self._related_many_to_many_cache = cache
|
self._related_many_to_many_cache = cache
|
||||||
return 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):
|
def get_base_chain(self, model):
|
||||||
"""
|
"""
|
||||||
Returns a list of parent classes leading to 'model' (order from closet
|
Returns a list of parent classes leading to 'model' (order from closet
|
||||||
|
@ -459,28 +437,3 @@ class Options(object):
|
||||||
# objects.append(opts)
|
# objects.append(opts)
|
||||||
self._ordered_objects = objects
|
self._ordered_objects = objects
|
||||||
return self._ordered_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]
|
|
||||||
|
|
|
@ -15,64 +15,9 @@ class RelatedObject(object):
|
||||||
self.model = model
|
self.model = model
|
||||||
self.opts = model._meta
|
self.opts = model._meta
|
||||||
self.field = field
|
self.field = field
|
||||||
self.edit_inline = field.rel.edit_inline
|
|
||||||
self.name = '%s:%s' % (self.opts.app_label, self.opts.module_name)
|
self.name = '%s:%s' % (self.opts.app_label, self.opts.module_name)
|
||||||
self.var_name = self.opts.object_name.lower()
|
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):
|
def get_db_prep_lookup(self, lookup_type, value):
|
||||||
# Defer to the actual field definition for db prep
|
# Defer to the actual field definition for db prep
|
||||||
return self.field.get_db_prep_lookup(lookup_type, value)
|
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."
|
"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]
|
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):
|
def __repr__(self):
|
||||||
return "<RelatedObject: %s related to %s>" % (self.name, self.field.name)
|
return "<RelatedObject: %s related to %s>" % (self.name, self.field.name)
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ __all__ = (
|
||||||
'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField',
|
'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField',
|
||||||
'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
|
'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
|
||||||
'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
|
'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
|
||||||
'SplitDateTimeField', 'IPAddressField', 'FilePathField',
|
'SplitDateTimeField', 'IPAddressField', 'FilePathField', 'SlugField',
|
||||||
)
|
)
|
||||||
|
|
||||||
# These values, if given to to_python(), will trigger the self.required check.
|
# 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):
|
def __init__(self, *args, **kwargs):
|
||||||
super(IPAddressField, self).__init__(ipv4_re, *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
|
@ -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
|
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
|
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
|
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,
|
about a field is common to all fields -- name, help text, uniqueness and so
|
||||||
uniqueness and so forth. Storing all that information is handled by ``Field``.
|
forth. Storing all that information is handled by ``Field``. We'll get into the
|
||||||
We'll get into the precise details of what ``Field`` can do later on; for now,
|
precise details of what ``Field`` can do later on; for now, suffice it to say
|
||||||
suffice it to say that everything descends from ``Field`` and then customizes
|
that everything descends from ``Field`` and then customizes key pieces of the
|
||||||
key pieces of the class behavior.
|
class behavior.
|
||||||
|
|
||||||
It's important to realize that a Django field class is not what is stored in
|
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
|
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_date`
|
||||||
* :attr:`~django.db.models.Field.unique_for_month`
|
* :attr:`~django.db.models.Field.unique_for_month`
|
||||||
* :attr:`~django.db.models.Field.unique_for_year`
|
* :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.choices`
|
||||||
* :attr:`~django.db.models.Field.help_text`
|
* :attr:`~django.db.models.Field.help_text`
|
||||||
* :attr:`~django.db.models.Field.db_column`
|
* :attr:`~django.db.models.Field.db_column`
|
||||||
|
@ -567,33 +566,19 @@ output in some other place, outside of Django.
|
||||||
Converting field data for serialization
|
Converting field data for serialization
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. method:: flatten_data(self, follow, obj=None)
|
.. method:: value_to_string(self, obj)
|
||||||
|
|
||||||
.. 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.
|
|
||||||
|
|
||||||
This method is used by the serializers to convert the field into a string for
|
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
|
output. Calling :meth:``Field._get_val_from_obj(obj)`` is the best way to get the
|
||||||
calling :meth:`Field._get_val_from_obj(obj)
|
value to serialize. For example, since our ``HandField`` uses strings for its
|
||||||
<django.db.models.Field._get_val_from_obj>` is the best way to get the value to
|
data storage anyway, we can reuse some existing conversion code::
|
||||||
serialize.
|
|
||||||
|
|
||||||
For example, since our ``HandField`` uses strings for its data storage anyway,
|
|
||||||
we can reuse some existing conversion code::
|
|
||||||
|
|
||||||
class HandField(models.Field):
|
class HandField(models.Field):
|
||||||
# ...
|
# ...
|
||||||
|
|
||||||
def flatten_data(self, follow, obj=None):
|
def value_to_string(self, obj):
|
||||||
value = self._get_val_from_obj(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
|
Some general advice
|
||||||
--------------------
|
--------------------
|
||||||
|
|
|
@ -154,12 +154,12 @@ shell command:
|
||||||
|
|
||||||
One low-tech way of taking advantage of the text documentation is by using the
|
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
|
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:
|
Django document:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
$ grep edit_inline /path/to/django/docs/*.txt
|
$ grep max_length /path/to/django/docs/*.txt
|
||||||
|
|
||||||
As HTML, locally
|
As HTML, locally
|
||||||
----------------
|
----------------
|
||||||
|
|
|
@ -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).
|
|
|
@ -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.
|
|
|
@ -33,11 +33,6 @@ exception or returns the clean value::
|
||||||
...
|
...
|
||||||
ValidationError: [u'Enter a valid e-mail address.']
|
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
|
Core field arguments
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
|
|
@ -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
|
proper database table with a :class:`ForeignKey`. :attr:`~Field.choices` is
|
||||||
meant for static data that doesn't change much, if ever.
|
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``
|
``db_column``
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
@ -287,15 +270,6 @@ respect to the month.
|
||||||
|
|
||||||
Like :attr:`~Field.unique_for_date` and :attr:`~Field.unique_for_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:
|
.. _model-field-types:
|
||||||
|
|
||||||
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
|
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,
|
recommend you use them. If that doesn't scare you away, however,
|
||||||
:class:`OneToOneField` takes the same options that :class:`ForeignKey` does,
|
:class:`OneToOneField` takes the same options that :class:`ForeignKey` does.
|
||||||
except for the various :attr:`~ForeignKey.edit_inline`-related options.
|
|
||||||
|
|
|
@ -67,8 +67,7 @@ the full list of conversions:
|
||||||
(from ``django.contrib.localflavor.us``)
|
(from ``django.contrib.localflavor.us``)
|
||||||
``PositiveIntegerField`` ``IntegerField``
|
``PositiveIntegerField`` ``IntegerField``
|
||||||
``PositiveSmallIntegerField`` ``IntegerField``
|
``PositiveSmallIntegerField`` ``IntegerField``
|
||||||
``SlugField`` ``RegexField`` accepting only letters,
|
``SlugField`` ``SlugField``
|
||||||
numbers, underscores and hyphens
|
|
||||||
``SmallIntegerField`` ``IntegerField``
|
``SmallIntegerField`` ``IntegerField``
|
||||||
``TextField`` ``CharField`` with ``widget=Textarea``
|
``TextField`` ``CharField`` with ``widget=Textarea``
|
||||||
``TimeField`` ``TimeField``
|
``TimeField`` ``TimeField``
|
||||||
|
|
|
@ -899,8 +899,7 @@ applications:
|
||||||
rendered on the form.
|
rendered on the form.
|
||||||
|
|
||||||
``form`` is the name the ``Form`` instance was given in the template
|
``form`` is the name the ``Form`` instance was given in the template
|
||||||
context. Note that this works only for ``forms.Form`` instances, not
|
context.
|
||||||
``oldforms.Form`` instances.
|
|
||||||
|
|
||||||
``field`` is the name of the field on the form to check. If ``field``
|
``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
|
has a value of ``None``, non-field errors (errors you can access via
|
||||||
|
|
|
@ -53,9 +53,6 @@ class SmallField(models.Field):
|
||||||
return []
|
return []
|
||||||
raise FieldError('Invalid lookup type: %r' % lookup_type)
|
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):
|
class MyModel(models.Model):
|
||||||
name = models.CharField(max_length=10)
|
name = models.CharField(max_length=10)
|
||||||
data = SmallField('small field')
|
data = SmallField('small field')
|
||||||
|
|
|
@ -23,13 +23,13 @@ class Target(models.Model):
|
||||||
clash1_set = models.CharField(max_length=10)
|
clash1_set = models.CharField(max_length=10)
|
||||||
|
|
||||||
class Clash1(models.Model):
|
class Clash1(models.Model):
|
||||||
src_safe = models.CharField(max_length=10, core=True)
|
src_safe = models.CharField(max_length=10)
|
||||||
|
|
||||||
foreign = models.ForeignKey(Target)
|
foreign = models.ForeignKey(Target)
|
||||||
m2m = models.ManyToManyField(Target)
|
m2m = models.ManyToManyField(Target)
|
||||||
|
|
||||||
class Clash2(models.Model):
|
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_1 = models.ForeignKey(Target, related_name='id')
|
||||||
foreign_2 = models.ForeignKey(Target, related_name='src_safe')
|
foreign_2 = models.ForeignKey(Target, related_name='src_safe')
|
||||||
|
@ -46,7 +46,7 @@ class Target2(models.Model):
|
||||||
clashm2m_set = models.ManyToManyField(Target)
|
clashm2m_set = models.ManyToManyField(Target)
|
||||||
|
|
||||||
class Clash3(models.Model):
|
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_1 = models.ForeignKey(Target2, related_name='foreign_tgt')
|
||||||
foreign_2 = models.ForeignKey(Target2, related_name='m2m_tgt')
|
foreign_2 = models.ForeignKey(Target2, related_name='m2m_tgt')
|
||||||
|
@ -61,7 +61,7 @@ class ClashM2M(models.Model):
|
||||||
m2m = models.ManyToManyField(Target2)
|
m2m = models.ManyToManyField(Target2)
|
||||||
|
|
||||||
class SelfClashForeign(models.Model):
|
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 = models.CharField(max_length=10)
|
||||||
|
|
||||||
selfclashforeign_set = models.ForeignKey("SelfClashForeign")
|
selfclashforeign_set = models.ForeignKey("SelfClashForeign")
|
||||||
|
|
|
@ -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
|
|
||||||
"""}
|
|
|
@ -7,7 +7,7 @@ Strings can be used instead of model literals to set up "lazy" relations.
|
||||||
from django.db.models import *
|
from django.db.models import *
|
||||||
|
|
||||||
class Parent(Model):
|
class Parent(Model):
|
||||||
name = CharField(max_length=100, core=True)
|
name = CharField(max_length=100)
|
||||||
|
|
||||||
# Use a simple string for forward declarations.
|
# Use a simple string for forward declarations.
|
||||||
bestchild = ForeignKey("Child", null=True, related_name="favoured_by")
|
bestchild = ForeignKey("Child", null=True, related_name="favoured_by")
|
||||||
|
|
|
@ -18,7 +18,7 @@ True
|
||||||
>>> f.to_python("abc")
|
>>> f.to_python("abc")
|
||||||
Traceback (most recent call last):
|
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)
|
>>> f = DecimalField(max_digits=5, decimal_places=1)
|
||||||
>>> x = f.to_python(2)
|
>>> x = f.to_python(2)
|
||||||
|
|
Loading…
Reference in New Issue