summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
Diffstat (limited to 'django')
-rw-r--r--django/conf/global_settings.py2
-rw-r--r--django/conf/project_template/settings.py2
-rw-r--r--django/conf/project_template/urls.py2
-rw-r--r--django/contrib/admin/media/js/admin/DateTimeShortcuts.js10
-rw-r--r--django/contrib/admin/media/js/core.js8
-rw-r--r--django/contrib/admin/templates/admin/search_form.html2
-rw-r--r--django/contrib/admin/views/auth.py4
-rw-r--r--django/contrib/admin/views/main.py20
-rw-r--r--django/contrib/admin/views/template.py10
-rw-r--r--django/contrib/auth/forms.py28
-rw-r--r--django/contrib/auth/views.py8
-rw-r--r--django/contrib/comments/views/comments.py32
-rw-r--r--django/contrib/contenttypes/management.py11
-rw-r--r--django/contrib/formtools/__init__.py0
-rw-r--r--django/contrib/formtools/preview.py160
-rw-r--r--django/contrib/formtools/templates/formtools/form.html15
-rw-r--r--django/contrib/formtools/templates/formtools/preview.html36
-rw-r--r--django/contrib/sitemaps/__init__.py2
-rw-r--r--django/contrib/sitemaps/templates/sitemap.xml2
-rw-r--r--django/contrib/sitemaps/templates/sitemap_index.xml2
-rw-r--r--django/core/handlers/base.py6
-rw-r--r--django/core/handlers/wsgi.py8
-rw-r--r--django/core/servers/fastcgi.py2
-rw-r--r--django/core/xheaders.py2
-rw-r--r--django/db/models/fields/__init__.py74
-rw-r--r--django/db/models/fields/generic.py4
-rw-r--r--django/db/models/fields/related.py20
-rw-r--r--django/db/models/manipulators.py8
-rw-r--r--django/http/__init__.py2
-rw-r--r--django/middleware/gzip.py1
-rw-r--r--django/newforms/__init__.py13
-rw-r--r--django/newforms/fields.py141
-rw-r--r--django/newforms/forms.py212
-rw-r--r--django/newforms/models.py19
-rw-r--r--django/newforms/util.py15
-rw-r--r--django/newforms/widgets.py102
-rw-r--r--django/oldforms/__init__.py1008
-rw-r--r--django/template/__init__.py6
-rw-r--r--django/views/generic/create_update.py6
-rw-r--r--django/views/generic/list_detail.py2
40 files changed, 1749 insertions, 258 deletions
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index 36fee9ec6d..245096590d 100644
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -25,7 +25,7 @@ ADMINS = ()
INTERNAL_IPS = ()
# Local time zone for this installation. All choices can be found here:
-# http://www.postgresql.org/docs/current/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
+# http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
TIME_ZONE = 'America/Chicago'
# Language code for this installation. All choices can be found here:
diff --git a/django/conf/project_template/settings.py b/django/conf/project_template/settings.py
index d6f34a28db..a44bc172f0 100644
--- a/django/conf/project_template/settings.py
+++ b/django/conf/project_template/settings.py
@@ -17,7 +17,7 @@ DATABASE_HOST = '' # Set to empty string for localhost. Not used wit
DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
# Local time zone for this installation. All choices can be found here:
-# http://www.postgresql.org/docs/current/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
+# http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
TIME_ZONE = 'America/Chicago'
# Language code for this installation. All choices can be found here:
diff --git a/django/conf/project_template/urls.py b/django/conf/project_template/urls.py
index 4483014173..402dd6536b 100644
--- a/django/conf/project_template/urls.py
+++ b/django/conf/project_template/urls.py
@@ -2,7 +2,7 @@ from django.conf.urls.defaults import *
urlpatterns = patterns('',
# Example:
- # (r'^{{ project_name }}/', include('{{ project_name }}.apps.foo.urls.foo')),
+ # (r'^{{ project_name }}/', include('{{ project_name }}.foo.urls')),
# Uncomment this for admin:
# (r'^admin/', include('django.contrib.admin.urls')),
diff --git a/django/contrib/admin/media/js/admin/DateTimeShortcuts.js b/django/contrib/admin/media/js/admin/DateTimeShortcuts.js
index 77c536b865..b1504fc819 100644
--- a/django/contrib/admin/media/js/admin/DateTimeShortcuts.js
+++ b/django/contrib/admin/media/js/admin/DateTimeShortcuts.js
@@ -44,7 +44,7 @@ var DateTimeShortcuts = {
var shortcuts_span = document.createElement('span');
inp.parentNode.insertBefore(shortcuts_span, inp.nextSibling);
var now_link = document.createElement('a');
- now_link.setAttribute('href', "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().getHourMinute());");
+ now_link.setAttribute('href', "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().getHourMinuteSecond());");
now_link.appendChild(document.createTextNode(gettext('Now')));
var clock_link = document.createElement('a');
clock_link.setAttribute('href', 'javascript:DateTimeShortcuts.openClock(' + num + ');');
@@ -80,10 +80,10 @@ var DateTimeShortcuts = {
quickElement('h2', clock_box, gettext('Choose a time'));
time_list = quickElement('ul', clock_box, '');
time_list.className = 'timelist';
- quickElement("a", quickElement("li", time_list, ""), gettext("Now"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().getHourMinute());")
- quickElement("a", quickElement("li", time_list, ""), gettext("Midnight"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", '00:00');")
- quickElement("a", quickElement("li", time_list, ""), gettext("6 a.m."), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", '06:00');")
- quickElement("a", quickElement("li", time_list, ""), gettext("Noon"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", '12:00');")
+ quickElement("a", quickElement("li", time_list, ""), gettext("Now"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().getHourMinuteSecond());")
+ quickElement("a", quickElement("li", time_list, ""), gettext("Midnight"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", '00:00:00');")
+ quickElement("a", quickElement("li", time_list, ""), gettext("6 a.m."), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", '06:00:00');")
+ quickElement("a", quickElement("li", time_list, ""), gettext("Noon"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", '12:00:00');")
cancel_p = quickElement('p', clock_box, '');
cancel_p.className = 'calendar-cancel';
diff --git a/django/contrib/admin/media/js/core.js b/django/contrib/admin/media/js/core.js
index d35bd29c1c..a17ac8a4d2 100644
--- a/django/contrib/admin/media/js/core.js
+++ b/django/contrib/admin/media/js/core.js
@@ -119,6 +119,10 @@ Date.prototype.getTwoDigitMinute = function() {
return (this.getMinutes() < 10) ? '0' + this.getMinutes() : this.getMinutes();
}
+Date.prototype.getTwoDigitSecond = function() {
+ return (this.getSeconds() < 10) ? '0' + this.getSeconds() : this.getSeconds();
+}
+
Date.prototype.getISODate = function() {
return this.getCorrectYear() + '-' + this.getTwoDigitMonth() + '-' + this.getTwoDigitDate();
}
@@ -127,6 +131,10 @@ Date.prototype.getHourMinute = function() {
return this.getTwoDigitHour() + ':' + this.getTwoDigitMinute();
}
+Date.prototype.getHourMinuteSecond = function() {
+ return this.getTwoDigitHour() + ':' + this.getTwoDigitMinute() + ':' + this.getTwoDigitSecond();
+}
+
// ----------------------------------------------------------------------------
// String object extensions
// ----------------------------------------------------------------------------
diff --git a/django/contrib/admin/templates/admin/search_form.html b/django/contrib/admin/templates/admin/search_form.html
index d9126c3ec5..445cca3089 100644
--- a/django/contrib/admin/templates/admin/search_form.html
+++ b/django/contrib/admin/templates/admin/search_form.html
@@ -7,7 +7,7 @@
<input type="text" size="40" name="{{ search_var }}" value="{{ cl.query|escape }}" id="searchbar" />
<input type="submit" value="{% trans 'Go' %}" />
{% if show_result_count %}
- <span class="small quiet">{% blocktrans count cl.result_count as counter %}1 result{% plural %}{{ counter }} results{% endblocktrans %} (<a href="?">{% blocktrans with cl.full_result_count as full_result_count %}{{ full_result_count }} total{% endblocktrans %}</a>)</span>
+ <span class="small quiet">{% blocktrans count cl.result_count as counter %}1 result{% plural %}{{ counter }} results{% endblocktrans %} (<a href="?{% if cl.is_popup %}pop=1{% endif %}">{% blocktrans with cl.full_result_count as full_result_count %}{{ full_result_count }} total{% endblocktrans %}</a>)</span>
{% endif %}
{% for pair in cl.params.items %}
{% ifnotequal pair.0 search_var %}<input type="hidden" name="{{ pair.0|escape }}" value="{{ pair.1|escape }}"/>{% endifnotequal %}
diff --git a/django/contrib/admin/views/auth.py b/django/contrib/admin/views/auth.py
index 03876bb4ac..abc02e96c4 100644
--- a/django/contrib/admin/views/auth.py
+++ b/django/contrib/admin/views/auth.py
@@ -2,7 +2,7 @@ from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied
-from django import forms, template
+from django import oldforms, template
from django.shortcuts import render_to_response
from django.http import HttpResponseRedirect
@@ -24,7 +24,7 @@ def user_add_stage(request):
return HttpResponseRedirect('../%s/' % new_user.id)
else:
errors = new_data = {}
- form = forms.FormWrapper(manipulator, new_data, errors)
+ form = oldforms.FormWrapper(manipulator, new_data, errors)
return render_to_response('admin/auth/user/add_form.html', {
'title': _('Add user'),
'form': form,
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
index 324841a669..548f8b9687 100644
--- a/django/contrib/admin/views/main.py
+++ b/django/contrib/admin/views/main.py
@@ -1,4 +1,4 @@
-from django import forms, template
+from django import oldforms, template
from django.conf import settings
from django.contrib.admin.filterspecs import FilterSpec
from django.contrib.admin.views.decorators import staff_member_required
@@ -226,7 +226,7 @@ index = staff_member_required(never_cache(index))
def add_stage(request, app_label, model_name, show_delete=False, form_url='', post_url=None, post_url_continue='../%s/', object_id_override=None):
model = models.get_model(app_label, model_name)
if model is None:
- raise Http404, "App %r, model %r, not found" % (app_label, model_name)
+ raise Http404("App %r, model %r, not found" % (app_label, model_name))
opts = model._meta
if not request.user.has_perm(app_label + '.' + opts.get_add_permission()):
@@ -283,7 +283,7 @@ def add_stage(request, app_label, model_name, show_delete=False, form_url='', po
errors = {}
# Populate the FormWrapper.
- form = forms.FormWrapper(manipulator, new_data, errors)
+ form = oldforms.FormWrapper(manipulator, new_data, errors)
c = template.RequestContext(request, {
'title': _('Add %s') % opts.verbose_name,
@@ -302,7 +302,7 @@ def change_stage(request, app_label, model_name, object_id):
model = models.get_model(app_label, model_name)
object_id = unquote(object_id)
if model is None:
- raise Http404, "App %r, model %r, not found" % (app_label, model_name)
+ raise Http404("App %r, model %r, not found" % (app_label, model_name))
opts = model._meta
if not request.user.has_perm(app_label + '.' + opts.get_change_permission()):
@@ -313,8 +313,8 @@ def change_stage(request, app_label, model_name, object_id):
try:
manipulator = model.ChangeManipulator(object_id)
- except ObjectDoesNotExist:
- raise Http404
+ except model.DoesNotExist:
+ raise Http404('%s object with primary key %r does not exist' % (model_name, escape(object_id)))
if request.POST:
new_data = request.POST.copy()
@@ -374,7 +374,7 @@ def change_stage(request, app_label, model_name, object_id):
errors = {}
# Populate the FormWrapper.
- form = forms.FormWrapper(manipulator, new_data, errors)
+ form = oldforms.FormWrapper(manipulator, new_data, errors)
form.original = manipulator.original_object
form.order_objects = []
@@ -490,7 +490,7 @@ def delete_stage(request, app_label, model_name, object_id):
model = models.get_model(app_label, model_name)
object_id = unquote(object_id)
if model is None:
- raise Http404, "App %r, model %r, not found" % (app_label, model_name)
+ raise Http404("App %r, model %r, not found" % (app_label, model_name))
opts = model._meta
if not request.user.has_perm(app_label + '.' + opts.get_delete_permission()):
raise PermissionDenied
@@ -527,7 +527,7 @@ def history(request, app_label, model_name, object_id):
model = models.get_model(app_label, model_name)
object_id = unquote(object_id)
if model is None:
- raise Http404, "App %r, model %r, not found" % (app_label, model_name)
+ raise Http404("App %r, model %r, not found" % (app_label, model_name))
action_list = LogEntry.objects.filter(object_id=object_id,
content_type__id__exact=ContentType.objects.get_for_model(model).id).select_related().order_by('action_time')
# If no history was found, see whether this object even exists.
@@ -743,7 +743,7 @@ class ChangeList(object):
def change_list(request, app_label, model_name):
model = models.get_model(app_label, model_name)
if model is None:
- raise Http404, "App %r, model %r, not found" % (app_label, model_name)
+ raise Http404("App %r, model %r, not found" % (app_label, model_name))
if not request.user.has_perm(app_label + '.' + model._meta.get_change_permission()):
raise PermissionDenied
try:
diff --git a/django/contrib/admin/views/template.py b/django/contrib/admin/views/template.py
index 93d110b045..a3b4538b10 100644
--- a/django/contrib/admin/views/template.py
+++ b/django/contrib/admin/views/template.py
@@ -1,6 +1,6 @@
from django.contrib.admin.views.decorators import staff_member_required
from django.core import validators
-from django import template, forms
+from django import template, oldforms
from django.template import loader
from django.shortcuts import render_to_response
from django.contrib.sites.models import Site
@@ -25,17 +25,17 @@ def template_validator(request):
request.user.message_set.create(message='The template is valid.')
return render_to_response('admin/template_validator.html', {
'title': 'Template validator',
- 'form': forms.FormWrapper(manipulator, new_data, errors),
+ 'form': oldforms.FormWrapper(manipulator, new_data, errors),
}, context_instance=template.RequestContext(request))
template_validator = staff_member_required(template_validator)
-class TemplateValidator(forms.Manipulator):
+class TemplateValidator(oldforms.Manipulator):
def __init__(self, settings_modules):
self.settings_modules = settings_modules
site_list = Site.objects.in_bulk(settings_modules.keys()).values()
self.fields = (
- forms.SelectField('site', is_required=True, choices=[(s.id, s.name) for s in site_list]),
- forms.LargeTextField('template', is_required=True, rows=25, validator_list=[self.isValidTemplate]),
+ oldforms.SelectField('site', is_required=True, choices=[(s.id, s.name) for s in site_list]),
+ oldforms.LargeTextField('template', is_required=True, rows=25, validator_list=[self.isValidTemplate]),
)
def isValidTemplate(self, field_data, all_data):
diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
index 24c69cb73e..aea52d1f2a 100644
--- a/django/contrib/auth/forms.py
+++ b/django/contrib/auth/forms.py
@@ -3,16 +3,16 @@ from django.contrib.auth import authenticate
from django.contrib.sites.models import Site
from django.template import Context, loader
from django.core import validators
-from django import forms
+from django import oldforms
-class UserCreationForm(forms.Manipulator):
+class UserCreationForm(oldforms.Manipulator):
"A form that creates a user, with no privileges, from the given username and password."
def __init__(self):
self.fields = (
- forms.TextField(field_name='username', length=30, maxlength=30, is_required=True,
+ oldforms.TextField(field_name='username', length=30, maxlength=30, is_required=True,
validator_list=[validators.isAlphaNumeric, self.isValidUsername]),
- forms.PasswordField(field_name='password1', length=30, maxlength=60, is_required=True),
- forms.PasswordField(field_name='password2', length=30, maxlength=60, is_required=True,
+ oldforms.PasswordField(field_name='password1', length=30, maxlength=60, is_required=True),
+ oldforms.PasswordField(field_name='password2', length=30, maxlength=60, is_required=True,
validator_list=[validators.AlwaysMatchesOtherField('password1', _("The two password fields didn't match."))]),
)
@@ -27,7 +27,7 @@ class UserCreationForm(forms.Manipulator):
"Creates the user."
return User.objects.create_user(new_data['username'], '', new_data['password1'])
-class AuthenticationForm(forms.Manipulator):
+class AuthenticationForm(oldforms.Manipulator):
"""
Base class for authenticating users. Extend this to get a form that accepts
username/password logins.
@@ -41,9 +41,9 @@ class AuthenticationForm(forms.Manipulator):
"""
self.request = request
self.fields = [
- forms.TextField(field_name="username", length=15, maxlength=30, is_required=True,
+ oldforms.TextField(field_name="username", length=15, maxlength=30, is_required=True,
validator_list=[self.isValidUser, self.hasCookiesEnabled]),
- forms.PasswordField(field_name="password", length=15, maxlength=30, is_required=True),
+ oldforms.PasswordField(field_name="password", length=15, maxlength=30, is_required=True),
]
self.user_cache = None
@@ -68,11 +68,11 @@ class AuthenticationForm(forms.Manipulator):
def get_user(self):
return self.user_cache
-class PasswordResetForm(forms.Manipulator):
+class PasswordResetForm(oldforms.Manipulator):
"A form that lets a user request a password reset"
def __init__(self):
self.fields = (
- forms.EmailField(field_name="email", length=40, is_required=True,
+ oldforms.EmailField(field_name="email", length=40, is_required=True,
validator_list=[self.isValidUserEmail]),
)
@@ -105,16 +105,16 @@ class PasswordResetForm(forms.Manipulator):
}
send_mail('Password reset on %s' % site_name, t.render(Context(c)), None, [self.user_cache.email])
-class PasswordChangeForm(forms.Manipulator):
+class PasswordChangeForm(oldforms.Manipulator):
"A form that lets a user change his password."
def __init__(self, user):
self.user = user
self.fields = (
- forms.PasswordField(field_name="old_password", length=30, maxlength=30, is_required=True,
+ oldforms.PasswordField(field_name="old_password", length=30, maxlength=30, is_required=True,
validator_list=[self.isValidOldPassword]),
- forms.PasswordField(field_name="new_password1", length=30, maxlength=30, is_required=True,
+ oldforms.PasswordField(field_name="new_password1", length=30, maxlength=30, is_required=True,
validator_list=[validators.AlwaysMatchesOtherField('new_password2', _("The two 'new password' fields didn't match."))]),
- forms.PasswordField(field_name="new_password2", length=30, maxlength=30, is_required=True),
+ oldforms.PasswordField(field_name="new_password2", length=30, maxlength=30, is_required=True),
)
def isValidOldPassword(self, new_data, all_data):
diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py
index 6882755787..fda17b91fb 100644
--- a/django/contrib/auth/views.py
+++ b/django/contrib/auth/views.py
@@ -1,6 +1,6 @@
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.forms import PasswordResetForm, PasswordChangeForm
-from django import forms
+from django import oldforms
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.contrib.sites.models import Site
@@ -26,7 +26,7 @@ def login(request, template_name='registration/login.html'):
errors = {}
request.session.set_test_cookie()
return render_to_response(template_name, {
- 'form': forms.FormWrapper(manipulator, request.POST, errors),
+ 'form': oldforms.FormWrapper(manipulator, request.POST, errors),
REDIRECT_FIELD_NAME: redirect_to,
'site_name': Site.objects.get_current().name,
}, context_instance=RequestContext(request))
@@ -62,7 +62,7 @@ def password_reset(request, is_admin_site=False, template_name='registration/pas
else:
form.save(email_template_name=email_template_name)
return HttpResponseRedirect('%sdone/' % request.path)
- return render_to_response(template_name, {'form': forms.FormWrapper(form, new_data, errors)},
+ return render_to_response(template_name, {'form': oldforms.FormWrapper(form, new_data, errors)},
context_instance=RequestContext(request))
def password_reset_done(request, template_name='registration/password_reset_done.html'):
@@ -77,7 +77,7 @@ def password_change(request, template_name='registration/password_change_form.ht
if not errors:
form.save(new_data)
return HttpResponseRedirect('%sdone/' % request.path)
- return render_to_response(template_name, {'form': forms.FormWrapper(form, new_data, errors)},
+ return render_to_response(template_name, {'form': oldforms.FormWrapper(form, new_data, errors)},
context_instance=RequestContext(request))
password_change = login_required(password_change)
diff --git a/django/contrib/comments/views/comments.py b/django/contrib/comments/views/comments.py
index 3640da90fe..12330afe41 100644
--- a/django/contrib/comments/views/comments.py
+++ b/django/contrib/comments/views/comments.py
@@ -1,5 +1,5 @@
from django.core import validators
-from django import forms
+from django import oldforms
from django.core.mail import mail_admins, mail_managers
from django.http import Http404
from django.core.exceptions import ObjectDoesNotExist
@@ -28,37 +28,37 @@ class PublicCommentManipulator(AuthenticationForm):
else:
return []
self.fields.extend([
- forms.LargeTextField(field_name="comment", maxlength=3000, is_required=True,
+ oldforms.LargeTextField(field_name="comment", maxlength=3000, is_required=True,
validator_list=[self.hasNoProfanities]),
- forms.RadioSelectField(field_name="rating1", choices=choices,
+ oldforms.RadioSelectField(field_name="rating1", choices=choices,
is_required=ratings_required and num_rating_choices > 0,
validator_list=get_validator_list(1),
),
- forms.RadioSelectField(field_name="rating2", choices=choices,
+ oldforms.RadioSelectField(field_name="rating2", choices=choices,
is_required=ratings_required and num_rating_choices > 1,
validator_list=get_validator_list(2),
),
- forms.RadioSelectField(field_name="rating3", choices=choices,
+ oldforms.RadioSelectField(field_name="rating3", choices=choices,
is_required=ratings_required and num_rating_choices > 2,
validator_list=get_validator_list(3),
),
- forms.RadioSelectField(field_name="rating4", choices=choices,
+ oldforms.RadioSelectField(field_name="rating4", choices=choices,
is_required=ratings_required and num_rating_choices > 3,
validator_list=get_validator_list(4),
),
- forms.RadioSelectField(field_name="rating5", choices=choices,
+ oldforms.RadioSelectField(field_name="rating5", choices=choices,
is_required=ratings_required and num_rating_choices > 4,
validator_list=get_validator_list(5),
),
- forms.RadioSelectField(field_name="rating6", choices=choices,
+ oldforms.RadioSelectField(field_name="rating6", choices=choices,
is_required=ratings_required and num_rating_choices > 5,
validator_list=get_validator_list(6),
),
- forms.RadioSelectField(field_name="rating7", choices=choices,
+ oldforms.RadioSelectField(field_name="rating7", choices=choices,
is_required=ratings_required and num_rating_choices > 6,
validator_list=get_validator_list(7),
),
- forms.RadioSelectField(field_name="rating8", choices=choices,
+ oldforms.RadioSelectField(field_name="rating8", choices=choices,
is_required=ratings_required and num_rating_choices > 7,
validator_list=get_validator_list(8),
),
@@ -117,13 +117,13 @@ class PublicCommentManipulator(AuthenticationForm):
mail_managers("Comment posted by sketchy user (%s)" % self.user_cache.username, c.get_as_text())
return c
-class PublicFreeCommentManipulator(forms.Manipulator):
+class PublicFreeCommentManipulator(oldforms.Manipulator):
"Manipulator that handles public free (unregistered) comments"
def __init__(self):
self.fields = (
- forms.TextField(field_name="person_name", maxlength=50, is_required=True,
+ oldforms.TextField(field_name="person_name", maxlength=50, is_required=True,
validator_list=[self.hasNoProfanities]),
- forms.LargeTextField(field_name="comment", maxlength=3000, is_required=True,
+ oldforms.LargeTextField(field_name="comment", maxlength=3000, is_required=True,
validator_list=[self.hasNoProfanities]),
)
@@ -221,9 +221,9 @@ def post_comment(request):
from django.contrib.auth import login
login(request, manipulator.get_user())
if errors or request.POST.has_key('preview'):
- class CommentFormWrapper(forms.FormWrapper):
+ class CommentFormWrapper(oldforms.FormWrapper):
def __init__(self, manipulator, new_data, errors, rating_choices):
- forms.FormWrapper.__init__(self, manipulator, new_data, errors)
+ oldforms.FormWrapper.__init__(self, manipulator, new_data, errors)
self.rating_choices = rating_choices
def ratings(self):
field_list = [self['rating%d' % (i+1)] for i in range(len(rating_choices))]
@@ -302,7 +302,7 @@ def post_free_comment(request):
comment = errors and '' or manipulator.get_comment(new_data)
return render_to_response('comments/free_preview.html', {
'comment': comment,
- 'comment_form': forms.FormWrapper(manipulator, new_data, errors),
+ 'comment_form': oldforms.FormWrapper(manipulator, new_data, errors),
'options': options,
'target': target,
'hash': security_hash,
diff --git a/django/contrib/contenttypes/management.py b/django/contrib/contenttypes/management.py
index de3a685477..f492f54303 100644
--- a/django/contrib/contenttypes/management.py
+++ b/django/contrib/contenttypes/management.py
@@ -3,9 +3,9 @@ Creates content types for all installed models.
"""
from django.dispatch import dispatcher
-from django.db.models import get_models, signals
+from django.db.models import get_apps, get_models, signals
-def create_contenttypes(app, created_models, verbosity):
+def create_contenttypes(app, created_models, verbosity=2):
from django.contrib.contenttypes.models import ContentType
app_models = get_models(app)
if not app_models:
@@ -22,4 +22,11 @@ def create_contenttypes(app, created_models, verbosity):
if verbosity >= 2:
print "Adding content type '%s | %s'" % (ct.app_label, ct.model)
+def create_all_contenttypes(verbosity=2):
+ for app in get_apps():
+ create_contenttypes(app, None, verbosity)
+
dispatcher.connect(create_contenttypes, signal=signals.post_syncdb)
+
+if __name__ == "__main__":
+ create_all_contenttypes()
diff --git a/django/contrib/formtools/__init__.py b/django/contrib/formtools/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/django/contrib/formtools/__init__.py
diff --git a/django/contrib/formtools/preview.py b/django/contrib/formtools/preview.py
new file mode 100644
index 0000000000..9a9371b5f8
--- /dev/null
+++ b/django/contrib/formtools/preview.py
@@ -0,0 +1,160 @@
+"""
+Formtools Preview application.
+
+This is an abstraction of the following workflow:
+
+ "Display an HTML form, force a preview, then do something with the submission."
+
+Given a django.newforms.Form object that you define, this takes care of the
+following:
+
+ * Displays the form as HTML on a Web page.
+ * Validates the form data once it's submitted via POST.
+ * If it's valid, displays a preview page.
+ * If it's not valid, redisplays the form with error messages.
+ * At the preview page, if the preview confirmation button is pressed, calls
+ a hook that you define -- a done() method.
+
+The framework enforces the required preview by passing a shared-secret hash to
+the preview page. If somebody tweaks the form parameters on the preview page,
+the form submission will fail the hash comparison test.
+
+Usage
+=====
+
+Subclass FormPreview and define a done() method:
+
+ def done(self, request, clean_data):
+ # ...
+
+This method takes an HttpRequest object and a dictionary of the form data after
+it has been validated and cleaned. It should return an HttpResponseRedirect.
+
+Then, just instantiate your FormPreview subclass by passing it a Form class,
+and pass that to your URLconf, like so:
+
+ (r'^post/$', MyFormPreview(MyForm)),
+
+The FormPreview class has a few other hooks. See the docstrings in the source
+code below.
+
+The framework also uses two templates: 'formtools/preview.html' and
+'formtools/form.html'. You can override these by setting 'preview_template' and
+'form_template' attributes on your FormPreview subclass. See
+django/contrib/formtools/templates for the default templates.
+"""
+
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.http import Http404
+from django.shortcuts import render_to_response
+import cPickle as pickle
+import md5
+
+AUTO_ID = 'formtools_%s' # Each form here uses this as its auto_id parameter.
+
+class FormPreview(object):
+ preview_template = 'formtools/preview.html'
+ form_template = 'formtools/form.html'
+
+ # METHODS SUBCLASSES SHOULDN'T OVERRIDE ###################################
+
+ def __init__(self, form):
+ # form should be a Form class, not an instance.
+ self.form, self.state = form, {}
+
+ def __call__(self, request, *args, **kwargs):
+ stage = {'1': 'preview', '2': 'post'}.get(request.POST.get(self.unused_name('stage')), 'preview')
+ self.parse_params(*args, **kwargs)
+ try:
+ method = getattr(self, stage + '_' + request.method.lower())
+ except AttributeError:
+ raise Http404
+ return method(request)
+
+ def unused_name(self, name):
+ """
+ Given a first-choice name, adds an underscore to the name until it
+ reaches a name that isn't claimed by any field in the form.
+
+ This is calculated rather than being hard-coded so that no field names
+ are off-limits for use in the form.
+ """
+ while 1:
+ try:
+ f = self.form.fields[name]
+ except KeyError:
+ break # This field name isn't being used by the form.
+ name += '_'
+ return name
+
+ def preview_get(self, request):
+ "Displays the form"
+ f = self.form(auto_id=AUTO_ID)
+ return render_to_response(self.form_template, {'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state})
+
+ def preview_post(self, request):
+ "Validates the POST data. If valid, displays the preview page. Else, redisplays form."
+ f = self.form(request.POST, auto_id=AUTO_ID)
+ context = {'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state}
+ if f.is_valid():
+ context['hash_field'] = self.unused_name('hash')
+ context['hash_value'] = self.security_hash(request, f)
+ return render_to_response(self.preview_template, context)
+ else:
+ return render_to_response(self.form_template, context)
+
+ def post_post(self, request):
+ "Validates the POST data. If valid, calls done(). Else, redisplays form."
+ f = self.form(request.POST, auto_id=AUTO_ID)
+ if f.is_valid():
+ if self.security_hash(request, f) != request.POST.get(self.unused_name('hash')):
+ return self.failed_hash(request) # Security hash failed.
+ return self.done(request, f.clean_data)
+ else:
+ return render_to_response(self.form_template, {'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state})
+
+ # METHODS SUBCLASSES MIGHT OVERRIDE IF APPROPRIATE ########################
+
+ def parse_params(self, *args, **kwargs):
+ """
+ Given captured args and kwargs from the URLconf, saves something in
+ self.state and/or raises Http404 if necessary.
+
+ For example, this URLconf captures a user_id variable:
+
+ (r'^contact/(?P<user_id>\d{1,6})/$', MyFormPreview(MyForm)),
+
+ In this case, the kwargs variable in parse_params would be
+ {'user_id': 32} for a request to '/contact/32/'. You can use that
+ user_id to make sure it's a valid user and/or save it for later, for
+ use in done().
+ """
+ pass
+
+ def security_hash(self, request, form):
+ """
+ Calculates the security hash for the given Form instance.
+
+ This creates a list of the form field names/values in a deterministic
+ order, pickles the result with the SECRET_KEY setting and takes an md5
+ hash of that.
+
+ Subclasses may want to take into account request-specific information
+ such as the IP address.
+ """
+ data = [(bf.name, bf.data) for bf in form] + [settings.SECRET_KEY]
+ # Use HIGHEST_PROTOCOL because it's the most efficient. It requires
+ # Python 2.3, but Django requires 2.3 anyway, so that's OK.
+ pickled = pickle.dumps(data, protocol=pickle.HIGHEST_PROTOCOL)
+ return md5.new(pickled).hexdigest()
+
+ def failed_hash(self, request):
+ "Returns an HttpResponse in the case of an invalid security hash."
+ return self.preview_post(request)
+
+ # METHODS SUBCLASSES MUST OVERRIDE ########################################
+
+ def done(self, request, clean_data):
+ "Does something with the clean_data and returns an HttpResponseRedirect."
+ raise NotImplementedError('You must define a done() method on your %s subclass.' % self.__class__.__name__)
diff --git a/django/contrib/formtools/templates/formtools/form.html b/django/contrib/formtools/templates/formtools/form.html
new file mode 100644
index 0000000000..90da8b2b2b
--- /dev/null
+++ b/django/contrib/formtools/templates/formtools/form.html
@@ -0,0 +1,15 @@
+{% extends "base.html" %}
+
+{% block content %}
+
+{% if form.errors %}<h1>Please correct the following errors</h1>{% else %}<h1>Submit</h1>{% endif %}
+
+<form action="" method="post">
+<table>
+{{ form }}
+</table>
+<input type="hidden" name="{{ stage_field }}" value="1" />
+<p><input type="submit" value="Submit" /></p>
+</form>
+
+{% endblock %}
diff --git a/django/contrib/formtools/templates/formtools/preview.html b/django/contrib/formtools/templates/formtools/preview.html
new file mode 100644
index 0000000000..c7955d46e1
--- /dev/null
+++ b/django/contrib/formtools/templates/formtools/preview.html
@@ -0,0 +1,36 @@
+{% extends "base.html" %}
+
+{% block content %}
+
+<h1>Preview your submission</h1>
+
+<table>
+{% for field in form %}
+<tr>
+<th>{{ field.verbose_name }}:</th>
+<td>{{ field.data|escape }}</td>
+</tr>
+{% endfor %}
+</table>
+
+<p>Security hash: {{ hash_value }}</p>
+
+<form action="" method="post">
+{% for field in form %}{{ field.as_hidden }}
+{% endfor %}
+<input type="hidden" name="{{ stage_field }}" value="2" />
+<input type="hidden" name="{{ hash_field }}" value="{{ hash_value }}" />
+<p><input type="submit" value="Submit" /></p>
+</form>
+
+<h1>Or edit it again</h1>
+
+<form action="" method="post">
+<table>
+{{ form }}
+</table>
+<input type="hidden" name="{{ stage_field }}" value="1" />
+<p><input type="submit" value="Submit changes" /></p>
+</form>
+
+{% endblock %}
diff --git a/django/contrib/sitemaps/__init__.py b/django/contrib/sitemaps/__init__.py
index 2c76e13c22..44ede4460a 100644
--- a/django/contrib/sitemaps/__init__.py
+++ b/django/contrib/sitemaps/__init__.py
@@ -29,7 +29,7 @@ def ping_google(sitemap_url=None, ping_url=PING_URL):
from django.contrib.sites.models import Site
current_site = Site.objects.get_current()
- url = "%s%s" % (current_site.domain, sitemap)
+ url = "%s%s" % (current_site.domain, sitemap_url)
params = urllib.urlencode({'sitemap':url})
urllib.urlopen("%s?%s" % (ping_url, params))
diff --git a/django/contrib/sitemaps/templates/sitemap.xml b/django/contrib/sitemaps/templates/sitemap.xml
index ad24c045d4..16d9a0bbe0 100644
--- a/django/contrib/sitemaps/templates/sitemap.xml
+++ b/django/contrib/sitemaps/templates/sitemap.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<urlset xmlns="http://www.google.com/schemas/sitemap/0.84">
+<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{% spaceless %}
{% for url in urlset %}
<url>
diff --git a/django/contrib/sitemaps/templates/sitemap_index.xml b/django/contrib/sitemaps/templates/sitemap_index.xml
index c89b192ecc..a2bcce85dc 100644
--- a/django/contrib/sitemaps/templates/sitemap_index.xml
+++ b/django/contrib/sitemaps/templates/sitemap_index.xml
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
-<sitemapindex xmlns="http://www.google.com/schemas/sitemap/0.84">
+<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{% for location in sitemaps %}<sitemap><loc>{{ location|escape }}</loc></sitemap>{% endfor %}
</sitemapindex>
diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py
index c1403ea4fa..85473a6353 100644
--- a/django/core/handlers/base.py
+++ b/django/core/handlers/base.py
@@ -84,7 +84,11 @@ class BaseHandler(object):
# Complain if the view returned None (a common error).
if response is None:
- raise ValueError, "The view %s.%s didn't return an HttpResponse object." % (callback.__module__, callback.func_name)
+ try:
+ view_name = callback.func_name # If it's a function
+ except AttributeError:
+ view_name = callback.__class__.__name__ + '.__call__' # If it's a class
+ raise ValueError, "The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name)
return response
except http.Http404, e:
diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py
index 2998bd31f6..71cfecd9a0 100644
--- a/django/core/handlers/wsgi.py
+++ b/django/core/handlers/wsgi.py
@@ -62,7 +62,7 @@ def safe_copyfileobj(fsrc, fdst, length=16*1024, size=0):
data in the body.
"""
if not size:
- return copyfileobj(fsrc, fdst, length)
+ return
while size > 0:
buf = fsrc.read(min(length, size))
if not buf:
@@ -157,7 +157,11 @@ class WSGIRequest(http.HttpRequest):
return self._raw_post_data
except AttributeError:
buf = StringIO()
- content_length = int(self.environ['CONTENT_LENGTH'])
+ try:
+ # CONTENT_LENGTH might be absent if POST doesn't have content at all (lighttpd)
+ content_length = int(self.environ.get('CONTENT_LENGTH', 0))
+ except ValueError: # if CONTENT_LENGTH was empty string or not an integer
+ content_length = 0
safe_copyfileobj(self.environ['wsgi.input'], buf, size=content_length)
self._raw_post_data = buf.getvalue()
buf.close()
diff --git a/django/core/servers/fastcgi.py b/django/core/servers/fastcgi.py
index fccb7bf087..649dd6942d 100644
--- a/django/core/servers/fastcgi.py
+++ b/django/core/servers/fastcgi.py
@@ -118,6 +118,8 @@ def runfastcgi(argset=[], **kwargs):
else:
return fastcgi_help("ERROR: Implementation must be one of prefork or thread.")
+ wsgi_opts['debug'] = False # Turn off flup tracebacks
+
# Prep up and go
from django.core.handlers.wsgi import WSGIHandler
diff --git a/django/core/xheaders.py b/django/core/xheaders.py
index 69f6115839..3beb930158 100644
--- a/django/core/xheaders.py
+++ b/django/core/xheaders.py
@@ -17,6 +17,6 @@ def populate_xheaders(request, response, model, object_id):
or if the request is from a logged in staff member.
"""
from django.conf import settings
- if request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS or (request.user.is_authenticated() and request.user.is_staff):
+ if request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS or (hasattr(request, 'user') and request.user.is_authenticated() and request.user.is_staff):
response['X-Object-Type'] = "%s.%s" % (model._meta.app_label, model._meta.object_name.lower())
response['X-Object-Id'] = str(object_id)
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index d59b697de2..d4dd4cdd47 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -2,7 +2,7 @@ from django.db.models import signals
from django.dispatch import dispatcher
from django.conf import settings
from django.core import validators
-from django import forms
+from django import oldforms
from django.core.exceptions import ObjectDoesNotExist
from django.utils.functional import curry
from django.utils.itercompat import tee
@@ -210,10 +210,10 @@ class Field(object):
if self.choices:
if self.radio_admin:
- field_objs = [forms.RadioSelectField]
+ field_objs = [oldforms.RadioSelectField]
params['ul_class'] = get_ul_class(self.radio_admin)
else:
- field_objs = [forms.SelectField]
+ field_objs = [oldforms.SelectField]
params['choices'] = self.get_choices_default()
else:
@@ -222,7 +222,7 @@ class Field(object):
def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True):
"""
- Returns a list of forms.FormField instances for this field. It
+ 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.
@@ -337,6 +337,12 @@ class Field(object):
return self._choices
choices = property(_get_choices)
+ def formfield(self):
+ "Returns a django.newforms.Field instance for this database Field."
+ from django.newforms import CharField
+ # TODO: This is just a temporary default during development.
+ return CharField(label=capfirst(self.verbose_name))
+
class AutoField(Field):
empty_strings_allowed = False
def __init__(self, *args, **kwargs):
@@ -358,7 +364,7 @@ class AutoField(Field):
return Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel, follow)
def get_manipulator_field_objs(self):
- return [forms.HiddenField]
+ return [oldforms.HiddenField]
def get_manipulator_new_data(self, new_data, rel=False):
# Never going to be called
@@ -385,11 +391,11 @@ class BooleanField(Field):
raise validators.ValidationError, gettext("This value must be either True or False.")
def get_manipulator_field_objs(self):
- return [forms.CheckboxField]
+ return [oldforms.CheckboxField]
class CharField(Field):
def get_manipulator_field_objs(self):
- return [forms.TextField]
+ return [oldforms.TextField]
def to_python(self, value):
if isinstance(value, basestring):
@@ -404,7 +410,7 @@ class CharField(Field):
# TODO: Maybe move this into contrib, because it's specialized.
class CommaSeparatedIntegerField(CharField):
def get_manipulator_field_objs(self):
- return [forms.CommaSeparatedIntegerField]
+ return [oldforms.CommaSeparatedIntegerField]
class DateField(Field):
empty_strings_allowed = False
@@ -472,7 +478,7 @@ class DateField(Field):
return Field.get_db_prep_save(self, value)
def get_manipulator_field_objs(self):
- return [forms.DateField]
+ return [oldforms.DateField]
def flatten_data(self, follow, obj = None):
val = self._get_val_from_obj(obj)
@@ -497,7 +503,7 @@ class DateTimeField(DateField):
def get_db_prep_save(self, value):
# Casts dates into string format for entry into database.
- if isinstance(value, datetime.datetime):
+ if value is not None:
# MySQL/Oracle will throw a warning if microseconds are given, because
# neither database supports microseconds.
if settings.DATABASE_ENGINE in ('mysql', 'oracle') and hasattr(value, 'microsecond'):
@@ -505,14 +511,6 @@ class DateTimeField(DateField):
# cx_Oracle wants the raw datetime instead of a string.
if settings.DATABASE_ENGINE != 'oracle':
value = str(value)
- elif isinstance(value, datetime.date):
- # MySQL/Oracle will throw a warning if microseconds are given, because
- # neither database supports microseconds.
- if settings.DATABASE_ENGINE in ('mysql', 'oracle') and hasattr(value, 'microsecond'):
- value = datetime.datetime(value.year, value.month, value.day, microsecond=0)
- # cx_Oracle wants the raw datetime instead of a string.
- if settings.DATABASE_ENGINE != 'oracle':
- value = str(value)
return Field.get_db_prep_save(self, value)
def get_db_prep_lookup(self, lookup_type, value):
@@ -527,7 +525,7 @@ class DateTimeField(DateField):
return Field.get_db_prep_lookup(self, lookup_type, value)
def get_manipulator_field_objs(self):
- return [forms.DateField, forms.TimeField]
+ return [oldforms.DateField, oldforms.TimeField]
def get_manipulator_field_names(self, name_prefix):
return [name_prefix + self.name + '_date', name_prefix + self.name + '_time']
@@ -564,7 +562,7 @@ class EmailField(CharField):
return "CharField"
def get_manipulator_field_objs(self):
- return [forms.EmailField]
+ return [oldforms.EmailField]
def validate(self, field_data, all_data):
validators.isValidEmail(field_data, all_data)
@@ -628,7 +626,7 @@ class FileField(Field):
os.remove(file_name)
def get_manipulator_field_objs(self):
- return [forms.FileUploadField, forms.HiddenField]
+ return [oldforms.FileUploadField, oldforms.HiddenField]
def get_manipulator_field_names(self, name_prefix):
return [name_prefix + self.name + '_file', name_prefix + self.name]
@@ -656,7 +654,7 @@ class FilePathField(Field):
Field.__init__(self, verbose_name, name, **kwargs)
def get_manipulator_field_objs(self):
- return [curry(forms.FilePathField, path=self.path, match=self.match, recursive=self.recursive)]
+ return [curry(oldforms.FilePathField, path=self.path, match=self.match, recursive=self.recursive)]
class FloatField(Field):
empty_strings_allowed = False
@@ -665,7 +663,7 @@ class FloatField(Field):
Field.__init__(self, verbose_name, name, **kwargs)
def get_manipulator_field_objs(self):
- return [curry(forms.FloatField, max_digits=self.max_digits, decimal_places=self.decimal_places)]
+ return [curry(oldforms.FloatField, max_digits=self.max_digits, decimal_places=self.decimal_places)]
class ImageField(FileField):
def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs):
@@ -673,7 +671,7 @@ class ImageField(FileField):
FileField.__init__(self, verbose_name, name, **kwargs)
def get_manipulator_field_objs(self):
- return [forms.ImageUploadField, forms.HiddenField]
+ return [oldforms.ImageUploadField, oldforms.HiddenField]
def contribute_to_class(self, cls, name):
super(ImageField, self).contribute_to_class(cls, name)
@@ -699,7 +697,7 @@ class ImageField(FileField):
class IntegerField(Field):
empty_strings_allowed = False
def get_manipulator_field_objs(self):
- return [forms.IntegerField]
+ return [oldforms.IntegerField]
class IPAddressField(Field):
def __init__(self, *args, **kwargs):
@@ -707,7 +705,7 @@ class IPAddressField(Field):
Field.__init__(self, *args, **kwargs)
def get_manipulator_field_objs(self):
- return [forms.IPAddressField]
+ return [oldforms.IPAddressField]
def validate(self, field_data, all_data):
validators.isValidIPAddress4(field_data, None)
@@ -718,22 +716,22 @@ class NullBooleanField(Field):
Field.__init__(self, *args, **kwargs)
def get_manipulator_field_objs(self):
- return [forms.NullBooleanField]
+ return [oldforms.NullBooleanField]
class PhoneNumberField(IntegerField):
def get_manipulator_field_objs(self):
- return [forms.PhoneNumberField]
+ return [oldforms.PhoneNumberField]
def validate(self, field_data, all_data):
validators.isValidPhone(field_data, all_data)
class PositiveIntegerField(IntegerField):
def get_manipulator_field_objs(self):
- return [forms.PositiveIntegerField]
+ return [oldforms.PositiveIntegerField]
class PositiveSmallIntegerField(IntegerField):
def get_manipulator_field_objs(self):
- return [forms.PositiveSmallIntegerField]
+ return [oldforms.PositiveSmallIntegerField]
class SlugField(Field):
def __init__(self, *args, **kwargs):
@@ -745,15 +743,15 @@ class SlugField(Field):
Field.__init__(self, *args, **kwargs)
def get_manipulator_field_objs(self):
- return [forms.TextField]
+ return [oldforms.TextField]
class SmallIntegerField(IntegerField):
def get_manipulator_field_objs(self):
- return [forms.SmallIntegerField]
+ return [oldforms.SmallIntegerField]
class TextField(Field):
def get_manipulator_field_objs(self):
- return [forms.LargeTextField]
+ return [oldforms.LargeTextField]
class TimeField(Field):
empty_strings_allowed = False
@@ -795,7 +793,7 @@ class TimeField(Field):
return Field.get_db_prep_save(self, value)
def get_manipulator_field_objs(self):
- return [forms.TimeField]
+ return [oldforms.TimeField]
def flatten_data(self,follow, obj = None):
val = self._get_val_from_obj(obj)
@@ -808,11 +806,11 @@ class URLField(Field):
Field.__init__(self, verbose_name, name, **kwargs)
def get_manipulator_field_objs(self):
- return [forms.URLField]
+ return [oldforms.URLField]
class USStateField(Field):
def get_manipulator_field_objs(self):
- return [forms.USStateField]
+ return [oldforms.USStateField]
class XMLField(TextField):
def __init__(self, verbose_name=None, name=None, schema_path=None, **kwargs):
@@ -823,7 +821,7 @@ class XMLField(TextField):
return "TextField"
def get_manipulator_field_objs(self):
- return [curry(forms.XMLLargeTextField, schema_path=self.schema_path)]
+ return [curry(oldforms.XMLLargeTextField, schema_path=self.schema_path)]
class OrderingField(IntegerField):
empty_strings_allowed=False
@@ -836,4 +834,4 @@ class OrderingField(IntegerField):
return "IntegerField"
def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True):
- return [forms.HiddenField(name_prefix + self.name)]
+ return [oldforms.HiddenField(name_prefix + self.name)]
diff --git a/django/db/models/fields/generic.py b/django/db/models/fields/generic.py
index 7d7651029c..1ad8346e42 100644
--- a/django/db/models/fields/generic.py
+++ b/django/db/models/fields/generic.py
@@ -2,7 +2,7 @@
Classes allowing "generic" relations through ContentType and object-id fields.
"""
-from django import forms
+from django import oldforms
from django.core.exceptions import ObjectDoesNotExist
from django.db import backend
from django.db.models import signals
@@ -98,7 +98,7 @@ class GenericRelation(RelatedField, Field):
def get_manipulator_field_objs(self):
choices = self.get_choices_default()
- return [curry(forms.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)]
+ return [curry(oldforms.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)]
def get_choices_default(self):
return Field.get_choices(self, include_blank=False)
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index ad78a01252..102e117631 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -5,7 +5,7 @@ from django.db.models.related import RelatedObject
from django.utils.translation import gettext_lazy, string_concat, ngettext
from django.utils.functional import curry
from django.core import validators
-from django import forms
+from django import oldforms
from django.dispatch import dispatcher
# For Python 2.3
@@ -493,13 +493,13 @@ class ForeignKey(RelatedField, Field):
params['validator_list'].append(curry(manipulator_valid_rel_key, self, manipulator))
else:
if self.radio_admin:
- field_objs = [forms.RadioSelectField]
+ field_objs = [oldforms.RadioSelectField]
params['ul_class'] = get_ul_class(self.radio_admin)
else:
if self.null:
- field_objs = [forms.NullSelectField]
+ field_objs = [oldforms.NullSelectField]
else:
- field_objs = [forms.SelectField]
+ field_objs = [oldforms.SelectField]
params['choices'] = self.get_choices_default()
return field_objs, params
@@ -508,7 +508,7 @@ class ForeignKey(RelatedField, Field):
if self.rel.raw_id_admin and not isinstance(rel_field, AutoField):
return rel_field.get_manipulator_field_objs()
else:
- return [forms.IntegerField]
+ return [oldforms.IntegerField]
def get_db_prep_save(self, value):
if value == '' or value == None:
@@ -581,13 +581,13 @@ class OneToOneField(RelatedField, IntegerField):
params['validator_list'].append(curry(manipulator_valid_rel_key, self, manipulator))
else:
if self.radio_admin:
- field_objs = [forms.RadioSelectField]
+ field_objs = [oldforms.RadioSelectField]
params['ul_class'] = get_ul_class(self.radio_admin)
else:
if self.null:
- field_objs = [forms.NullSelectField]
+ field_objs = [oldforms.NullSelectField]
else:
- field_objs = [forms.SelectField]
+ field_objs = [oldforms.SelectField]
params['choices'] = self.get_choices_default()
return field_objs, params
@@ -622,10 +622,10 @@ class ManyToManyField(RelatedField, Field):
def get_manipulator_field_objs(self):
if self.rel.raw_id_admin:
- return [forms.RawIdAdminField]
+ return [oldforms.RawIdAdminField]
else:
choices = self.get_choices_default()
- return [curry(forms.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)]
+ return [curry(oldforms.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)]
def get_choices_default(self):
return Field.get_choices(self, include_blank=False)
diff --git a/django/db/models/manipulators.py b/django/db/models/manipulators.py
index c61e82f813..aea3aa70a7 100644
--- a/django/db/models/manipulators.py
+++ b/django/db/models/manipulators.py
@@ -1,5 +1,5 @@
from django.core.exceptions import ObjectDoesNotExist
-from django import forms
+from django import oldforms
from django.core import validators
from django.db.models.fields import FileField, AutoField
from django.dispatch import dispatcher
@@ -40,7 +40,7 @@ class ManipulatorDescriptor(object):
self.man._prepare(model)
return self.man
-class AutomaticManipulator(forms.Manipulator):
+class AutomaticManipulator(oldforms.Manipulator):
def _prepare(cls, model):
cls.model = model
cls.manager = model._default_manager
@@ -76,7 +76,7 @@ class AutomaticManipulator(forms.Manipulator):
# Add field for ordering.
if self.change and self.opts.get_ordered_objects():
- self.fields.append(forms.CommaSeparatedIntegerField(field_name="order_"))
+ self.fields.append(oldforms.CommaSeparatedIntegerField(field_name="order_"))
def save(self, new_data):
# TODO: big cleanup when core fields go -> use recursive manipulators.
@@ -308,7 +308,7 @@ def manipulator_validator_unique_together(field_name_list, opts, self, field_dat
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 = forms.DateField.html2python(date_str)
+ 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}
diff --git a/django/http/__init__.py b/django/http/__init__.py
index bb0e973aae..48f10329fd 100644
--- a/django/http/__init__.py
+++ b/django/http/__init__.py
@@ -208,7 +208,7 @@ class HttpResponse(object):
if path is not None:
self.cookies[key]['path'] = path
if domain is not None:
- self.cookies[key]['domain'] = path
+ self.cookies[key]['domain'] = domain
self.cookies[key]['expires'] = 0
self.cookies[key]['max-age'] = 0
diff --git a/django/middleware/gzip.py b/django/middleware/gzip.py
index 7d860abdb1..a7c74481d0 100644
--- a/django/middleware/gzip.py
+++ b/django/middleware/gzip.py
@@ -25,4 +25,5 @@ class GZipMiddleware(object):
response.content = compress_string(response.content)
response['Content-Encoding'] = 'gzip'
+ response['Content-Length'] = str(len(response.content))
return response
diff --git a/django/newforms/__init__.py b/django/newforms/__init__.py
index 2a472d7b39..a445a21bfb 100644
--- a/django/newforms/__init__.py
+++ b/django/newforms/__init__.py
@@ -14,15 +14,4 @@ from util import ValidationError
from widgets import *
from fields import *
from forms import Form
-
-##########################
-# DATABASE API SHORTCUTS #
-##########################
-
-def form_for_model(model):
- "Returns a Form instance for the given Django model class."
- raise NotImplementedError
-
-def form_for_fields(field_list):
- "Returns a Form instance for the given list of Django database field instances."
- raise NotImplementedError
+from models import *
diff --git a/django/newforms/fields.py b/django/newforms/fields.py
index 179555fc77..24849bdbcf 100644
--- a/django/newforms/fields.py
+++ b/django/newforms/fields.py
@@ -2,8 +2,9 @@
Field classes
"""
-from util import ValidationError, DEFAULT_ENCODING, smart_unicode
-from widgets import TextInput, CheckboxInput, Select, SelectMultiple
+from django.utils.translation import gettext
+from util import ValidationError, smart_unicode
+from widgets import TextInput, PasswordInput, CheckboxInput, Select, SelectMultiple
import datetime
import re
import time
@@ -11,6 +12,7 @@ import time
__all__ = (
'Field', 'CharField', 'IntegerField',
'DEFAULT_DATE_INPUT_FORMATS', 'DateField',
+ 'DEFAULT_TIME_INPUT_FORMATS', 'TimeField',
'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField',
'RegexField', 'EmailField', 'URLField', 'BooleanField',
'ChoiceField', 'MultipleChoiceField',
@@ -28,13 +30,26 @@ except NameError:
class Field(object):
widget = TextInput # Default widget to use when rendering this type of Field.
- def __init__(self, required=True, widget=None):
- self.required = required
+ # Tracks each time a Field instance is created. Used to retain order.
+ creation_counter = 0
+
+ def __init__(self, required=True, widget=None, label=None):
+ self.required, self.label = required, label
widget = widget or self.widget
if isinstance(widget, type):
widget = widget()
+
+ # Hook into self.widget_attrs() for any Field-specific HTML attributes.
+ extra_attrs = self.widget_attrs(widget)
+ if extra_attrs:
+ widget.attrs.update(extra_attrs)
+
self.widget = widget
+ # Increase the creation counter, and save our local copy.
+ self.creation_counter = Field.creation_counter
+ Field.creation_counter += 1
+
def clean(self, value):
"""
Validates the given value and returns its "cleaned" value as an
@@ -43,13 +58,21 @@ class Field(object):
Raises ValidationError for any errors.
"""
if self.required and value in EMPTY_VALUES:
- raise ValidationError(u'This field is required.')
+ raise ValidationError(gettext(u'This field is required.'))
return value
+ def widget_attrs(self, widget):
+ """
+ Given a Widget instance (*not* a Widget class), returns a dictionary of
+ any HTML attributes that should be added to the Widget, based on this
+ Field.
+ """
+ return {}
+
class CharField(Field):
- def __init__(self, max_length=None, min_length=None, required=True, widget=None):
- Field.__init__(self, required, widget)
+ def __init__(self, max_length=None, min_length=None, required=True, widget=None, label=None):
self.max_length, self.min_length = max_length, min_length
+ Field.__init__(self, required, widget, label)
def clean(self, value):
"Validates max_length and min_length. Returns a Unicode object."
@@ -57,11 +80,15 @@ class CharField(Field):
if value in EMPTY_VALUES: value = u''
value = smart_unicode(value)
if self.max_length is not None and len(value) > self.max_length:
- raise ValidationError(u'Ensure this value has at most %d characters.' % self.max_length)
+ raise ValidationError(gettext(u'Ensure this value has at most %d characters.') % self.max_length)
if self.min_length is not None and len(value) < self.min_length:
- raise ValidationError(u'Ensure this value has at least %d characters.' % self.min_length)
+ raise ValidationError(gettext(u'Ensure this value has at least %d characters.') % self.min_length)
return value
+ def widget_attrs(self, widget):
+ if self.max_length is not None and isinstance(widget, (TextInput, PasswordInput)):
+ return {'maxlength': str(self.max_length)}
+
class IntegerField(Field):
def clean(self, value):
"""
@@ -69,10 +96,12 @@ class IntegerField(Field):
of int().
"""
super(IntegerField, self).clean(value)
+ if not self.required and value in EMPTY_VALUES:
+ return u''
try:
return int(value)
except (ValueError, TypeError):
- raise ValidationError(u'Enter a whole number.')
+ raise ValidationError(gettext(u'Enter a whole number.'))
DEFAULT_DATE_INPUT_FORMATS = (
'%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
@@ -83,8 +112,8 @@ DEFAULT_DATE_INPUT_FORMATS = (
)
class DateField(Field):
- def __init__(self, input_formats=None, required=True, widget=None):
- Field.__init__(self, required, widget)
+ def __init__(self, input_formats=None, required=True, widget=None, label=None):
+ Field.__init__(self, required, widget, label)
self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS
def clean(self, value):
@@ -104,7 +133,34 @@ class DateField(Field):
return datetime.date(*time.strptime(value, format)[:3])
except ValueError:
continue
- raise ValidationError(u'Enter a valid date.')
+ raise ValidationError(gettext(u'Enter a valid date.'))
+
+DEFAULT_TIME_INPUT_FORMATS = (
+ '%H:%M:%S', # '14:30:59'
+ '%H:%M', # '14:30'
+)
+
+class TimeField(Field):
+ def __init__(self, input_formats=None, required=True, widget=None, label=None):
+ Field.__init__(self, required, widget, label)
+ self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS
+
+ def clean(self, value):
+ """
+ Validates that the input can be converted to a time. Returns a Python
+ datetime.time object.
+ """
+ Field.clean(self, value)
+ if value in EMPTY_VALUES:
+ return None
+ if isinstance(value, datetime.time):
+ return value
+ for format in self.input_formats:
+ try:
+ return datetime.time(*time.strptime(value, format)[3:6])
+ except ValueError:
+ continue
+ raise ValidationError(gettext(u'Enter a valid time.'))
DEFAULT_DATETIME_INPUT_FORMATS = (
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
@@ -119,8 +175,8 @@ DEFAULT_DATETIME_INPUT_FORMATS = (
)
class DateTimeField(Field):
- def __init__(self, input_formats=None, required=True, widget=None):
- Field.__init__(self, required, widget)
+ def __init__(self, input_formats=None, required=True, widget=None, label=None):
+ Field.__init__(self, required, widget, label)
self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS
def clean(self, value):
@@ -140,20 +196,20 @@ class DateTimeField(Field):
return datetime.datetime(*time.strptime(value, format)[:6])
except ValueError:
continue
- raise ValidationError(u'Enter a valid date/time.')
+ raise ValidationError(gettext(u'Enter a valid date/time.'))
class RegexField(Field):
- def __init__(self, regex, error_message=None, required=True, widget=None):
+ def __init__(self, regex, error_message=None, required=True, widget=None, label=None):
"""
regex can be either a string or a compiled regular expression object.
error_message is an optional error message to use, if
'Enter a valid value' is too generic for you.
"""
- Field.__init__(self, required, widget)
+ Field.__init__(self, required, widget, label)
if isinstance(regex, basestring):
regex = re.compile(regex)
self.regex = regex
- self.error_message = error_message or u'Enter a valid value.'
+ self.error_message = error_message or gettext(u'Enter a valid value.')
def clean(self, value):
"""
@@ -163,6 +219,8 @@ class RegexField(Field):
Field.clean(self, value)
if value in EMPTY_VALUES: value = u''
value = smart_unicode(value)
+ if not self.required and value == u'':
+ return value
if not self.regex.search(value):
raise ValidationError(self.error_message)
return value
@@ -173,8 +231,8 @@ email_re = re.compile(
r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain
class EmailField(RegexField):
- def __init__(self, required=True, widget=None):
- RegexField.__init__(self, email_re, u'Enter a valid e-mail address.', required, widget)
+ def __init__(self, required=True, widget=None, label=None):
+ RegexField.__init__(self, email_re, gettext(u'Enter a valid e-mail address.'), required, widget, label)
url_re = re.compile(
r'^https?://' # http:// or https://
@@ -190,9 +248,9 @@ except ImportError:
URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)'
class URLField(RegexField):
- def __init__(self, required=True, verify_exists=False, widget=None,
+ def __init__(self, required=True, verify_exists=False, widget=None, label=None,
validator_user_agent=URL_VALIDATOR_USER_AGENT):
- RegexField.__init__(self, url_re, u'Enter a valid URL.', required, widget)
+ RegexField.__init__(self, url_re, gettext(u'Enter a valid URL.'), required, widget, label)
self.verify_exists = verify_exists
self.user_agent = validator_user_agent
@@ -212,9 +270,9 @@ class URLField(RegexField):
req = urllib2.Request(value, None, headers)
u = urllib2.urlopen(req)
except ValueError:
- raise ValidationError(u'Enter a valid URL.')
+ raise ValidationError(gettext(u'Enter a valid URL.'))
except: # urllib2.URLError, httplib.InvalidURL, etc.
- raise ValidationError(u'This URL appears to be a broken link.')
+ raise ValidationError(gettext(u'This URL appears to be a broken link.'))
return value
class BooleanField(Field):
@@ -226,10 +284,10 @@ class BooleanField(Field):
return bool(value)
class ChoiceField(Field):
- def __init__(self, choices=(), required=True, widget=Select):
+ def __init__(self, choices=(), required=True, widget=Select, label=None):
if isinstance(widget, type):
widget = widget(choices=choices)
- Field.__init__(self, required, widget)
+ Field.__init__(self, required, widget, label)
self.choices = choices
def clean(self, value):
@@ -239,37 +297,46 @@ class ChoiceField(Field):
value = Field.clean(self, value)
if value in EMPTY_VALUES: value = u''
value = smart_unicode(value)
+ if not self.required and value == u'':
+ return value
valid_values = set([str(k) for k, v in self.choices])
if value not in valid_values:
- raise ValidationError(u'Select a valid choice. %s is not one of the available choices.' % value)
+ raise ValidationError(gettext(u'Select a valid choice. %s is not one of the available choices.') % value)
return value
class MultipleChoiceField(ChoiceField):
- def __init__(self, choices=(), required=True, widget=SelectMultiple):
- ChoiceField.__init__(self, choices, required, widget)
+ def __init__(self, choices=(), required=True, widget=SelectMultiple, label=None):
+ ChoiceField.__init__(self, choices, required, widget, label)
def clean(self, value):
"""
Validates that the input is a list or tuple.
"""
- if not isinstance(value, (list, tuple)):
- raise ValidationError(u'Enter a list of values.')
if self.required and not value:
- raise ValidationError(u'This field is required.')
+ raise ValidationError(gettext(u'This field is required.'))
+ elif not self.required and not value:
+ return []
+ if not isinstance(value, (list, tuple)):
+ raise ValidationError(gettext(u'Enter a list of values.'))
new_value = []
for val in value:
val = smart_unicode(val)
new_value.append(val)
# Validate that each value in the value list is in self.choices.
- valid_values = set([k for k, v in self.choices])
+ valid_values = set([smart_unicode(k) for k, v in self.choices])
for val in new_value:
if val not in valid_values:
- raise ValidationError(u'Select a valid choice. %s is not one of the available choices.' % val)
+ raise ValidationError(gettext(u'Select a valid choice. %s is not one of the available choices.') % val)
return new_value
class ComboField(Field):
- def __init__(self, fields=(), required=True, widget=None):
- Field.__init__(self, required, widget)
+ def __init__(self, fields=(), required=True, widget=None, label=None):
+ Field.__init__(self, required, widget, label)
+ # Set 'required' to False on the individual fields, because the
+ # required validation will be handled by ComboField, not by those
+ # individual fields.
+ for f in fields:
+ f.required = False
self.fields = fields
def clean(self, value):
diff --git a/django/newforms/forms.py b/django/newforms/forms.py
index 2730bfe4e4..96948264e4 100644
--- a/django/newforms/forms.py
+++ b/django/newforms/forms.py
@@ -2,9 +2,11 @@
Form classes
"""
+from django.utils.datastructures import SortedDict, MultiValueDict
+from django.utils.html import escape
from fields import Field
-from widgets import TextInput, Textarea
-from util import ErrorDict, ErrorList, ValidationError
+from widgets import TextInput, Textarea, HiddenInput
+from util import StrAndUnicode, ErrorDict, ErrorList, ValidationError
NON_FIELD_ERRORS = '__all__'
@@ -13,23 +15,37 @@ def pretty_name(name):
name = name[0].upper() + name[1:]
return name.replace('_', ' ')
+class SortedDictFromList(SortedDict):
+ "A dictionary that keeps its keys in the order in which they're inserted."
+ # This is different than django.utils.datastructures.SortedDict, because
+ # this takes a list/tuple as the argument to __init__().
+ def __init__(self, data=None):
+ if data is None: data = []
+ self.keyOrder = [d[0] for d in data]
+ dict.__init__(self, dict(data))
+
class DeclarativeFieldsMetaclass(type):
"Metaclass that converts Field attributes to a dictionary called 'fields'."
def __new__(cls, name, bases, attrs):
- attrs['fields'] = dict([(name, attrs.pop(name)) for name, obj in attrs.items() if isinstance(obj, Field)])
+ fields = [(name, attrs.pop(name)) for name, obj in attrs.items() if isinstance(obj, Field)]
+ fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
+ attrs['fields'] = SortedDictFromList(fields)
return type.__new__(cls, name, bases, attrs)
-class Form(object):
- "A collection of Fields, plus their associated data."
- __metaclass__ = DeclarativeFieldsMetaclass
-
- def __init__(self, data=None, auto_id=False): # TODO: prefix stuff
+class BaseForm(StrAndUnicode):
+ # This is the main implementation of all the Form logic. Note that this
+ # class is different than Form. See the comments by the Form class for more
+ # information. Any improvements to the form API should be made to *this*
+ # class, not to the Form class.
+ def __init__(self, data=None, auto_id='id_%s', prefix=None):
+ self.ignore_errors = data is None
self.data = data or {}
self.auto_id = auto_id
+ self.prefix = prefix
self.clean_data = None # Stores the data after clean() has been called.
self.__errors = None # Stores the errors after clean() has been called.
- def __str__(self):
+ def __unicode__(self):
return self.as_table()
def __iter__(self):
@@ -44,60 +60,75 @@ class Form(object):
raise KeyError('Key %r not found in Form' % name)
return BoundField(self, field, name)
- def clean(self):
- if self.__errors is None:
- self.full_clean()
- return self.clean_data
-
- def errors(self):
+ def _errors(self):
"Returns an ErrorDict for self.data"
if self.__errors is None:
self.full_clean()
return self.__errors
+ errors = property(_errors)
def is_valid(self):
"""
- Returns True if the form has no errors. Otherwise, False. This exists
- solely for convenience, so client code can use positive logic rather
- than confusing negative logic ("if not form.errors()").
+ Returns True if the form has no errors. Otherwise, False. If errors are
+ being ignored, returns False.
+ """
+ return not self.ignore_errors and not bool(self.errors)
+
+ def add_prefix(self, field_name):
+ """
+ Returns the field name with a prefix appended, if this Form has a
+ prefix set.
+
+ Subclasses may wish to override.
"""
- return not bool(self.errors())
+ return self.prefix and ('%s-%s' % (self.prefix, field_name)) or field_name
+
+ def _html_output(self, normal_row, error_row, row_ender, errors_on_separate_row):
+ "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()."
+ top_errors = self.non_field_errors() # Errors that should be displayed above all fields.
+ output, hidden_fields = [], []
+ for name, field in self.fields.items():
+ bf = BoundField(self, field, name)
+ bf_errors = bf.errors # Cache in local variable.
+ if bf.is_hidden:
+ if bf_errors:
+ top_errors.extend(['(Hidden field %s) %s' % (name, e) for e in bf_errors])
+ hidden_fields.append(unicode(bf))
+ else:
+ if errors_on_separate_row and bf_errors:
+ output.append(error_row % bf_errors)
+ output.append(normal_row % {'errors': bf_errors, 'label': bf.label_tag(escape(bf.label+':')), 'field': bf})
+ if top_errors:
+ output.insert(0, error_row % top_errors)
+ if hidden_fields: # Insert any hidden fields in the last row.
+ str_hidden = u''.join(hidden_fields)
+ if output:
+ last_row = output[-1]
+ # Chop off the trailing row_ender (e.g. '</td></tr>') and insert the hidden fields.
+ output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender
+ else: # If there aren't any rows in the output, just append the hidden fields.
+ output.append(str_hidden)
+ return u'\n'.join(output)
def as_table(self):
"Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
- return u'\n'.join(['<tr><td>%s:</td><td>%s</td></tr>' % (pretty_name(name), BoundField(self, field, name)) for name, field in self.fields.items()])
+ return self._html_output(u'<tr><th>%(label)s</th><td>%(field)s</td></tr>', u'<tr><td colspan="2">%s</td></tr>', '</td></tr>', True)
def as_ul(self):
"Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
- return u'\n'.join(['<li>%s: %s</li>' % (pretty_name(name), BoundField(self, field, name)) for name, field in self.fields.items()])
+ return self._html_output(u'<li>%(errors)s%(label)s %(field)s</li>', u'<li>%s</li>', '</li>', False)
- def as_table_with_errors(self):
- "Returns this form rendered as HTML <tr>s, with errors."
- output = []
- if self.errors().get(NON_FIELD_ERRORS):
- # Errors not corresponding to a particular field are displayed at the top.
- output.append('<tr><td colspan="2"><ul>%s</ul></td></tr>' % '\n'.join(['<li>%s</li>' % e for e in self.errors()[NON_FIELD_ERRORS]]))
- for name, field in self.fields.items():
- bf = BoundField(self, field, name)
- if bf.errors:
- output.append('<tr><td colspan="2"><ul>%s</ul></td></tr>' % '\n'.join(['<li>%s</li>' % e for e in bf.errors]))
- output.append('<tr><td>%s:</td><td>%s</td></tr>' % (pretty_name(name), bf))
- return u'\n'.join(output)
+ def as_p(self):
+ "Returns this form rendered as HTML <p>s."
+ return self._html_output(u'<p>%(label)s %(field)s</p>', u'<p>%s</p>', '</p>', True)
- def as_ul_with_errors(self):
- "Returns this form rendered as HTML <li>s, with errors."
- output = []
- if self.errors().get(NON_FIELD_ERRORS):
- # Errors not corresponding to a particular field are displayed at the top.
- output.append('<li><ul>%s</ul></li>' % '\n'.join(['<li>%s</li>' % e for e in self.errors()[NON_FIELD_ERRORS]]))
- for name, field in self.fields.items():
- bf = BoundField(self, field, name)
- line = '<li>'
- if bf.errors:
- line += '<ul>%s</ul>' % '\n'.join(['<li>%s</li>' % e for e in bf.errors])
- line += '%s: %s</li>' % (pretty_name(name), bf)
- output.append(line)
- return u'\n'.join(output)
+ def non_field_errors(self):
+ """
+ Returns an ErrorList of errors that aren't associated with a particular
+ field -- i.e., from Form.clean(). Returns an empty ErrorList if there
+ are none.
+ """
+ return self.errors.get(NON_FIELD_ERRORS, ErrorList())
def full_clean(self):
"""
@@ -105,8 +136,14 @@ class Form(object):
"""
self.clean_data = {}
errors = ErrorDict()
+ if self.ignore_errors: # Stop further processing.
+ self.__errors = errors
+ return
for name, field in self.fields.items():
- value = self.data.get(name, None)
+ # value_from_datadict() gets the data from the dictionary.
+ # Each widget type knows how to retrieve its own data, because some
+ # widgets split data over several HTML fields.
+ value = field.widget.value_from_datadict(self.data, self.add_prefix(name))
try:
value = field.clean(value)
self.clean_data[name] = value
@@ -126,40 +163,56 @@ class Form(object):
def clean(self):
"""
Hook for doing any extra form-wide cleaning after Field.clean() been
- called on every field.
+ called on every field. Any ValidationError raised by this method will
+ not be associated with a particular field; it will have a special-case
+ association with the field named '__all__'.
"""
return self.clean_data
-class BoundField(object):
+class Form(BaseForm):
+ "A collection of Fields, plus their associated data."
+ # This is a separate class from BaseForm in order to abstract the way
+ # self.fields is specified. This class (Form) is the one that does the
+ # fancy metaclass stuff purely for the semantic sugar -- it allows one
+ # to define a form using declarative syntax.
+ # BaseForm itself has no way of designating self.fields.
+ __metaclass__ = DeclarativeFieldsMetaclass
+
+class BoundField(StrAndUnicode):
"A Field plus data"
def __init__(self, form, field, name):
- self._form = form
- self._field = field
- self._name = name
+ self.form = form
+ self.field = field
+ self.name = name
+ self.html_name = form.add_prefix(name)
+ self.label = self.field.label or pretty_name(name)
- def __str__(self):
+ def __unicode__(self):
"Renders this field as an HTML widget."
# Use the 'widget' attribute on the field to determine which type
# of HTML widget to use.
- return self.as_widget(self._field.widget)
+ value = self.as_widget(self.field.widget)
+ if not isinstance(value, basestring):
+ # Some Widget render() methods -- notably RadioSelect -- return a
+ # "special" object rather than a string. Call the __str__() on that
+ # object to get its rendered value.
+ value = value.__str__()
+ return value
def _errors(self):
"""
Returns an ErrorList for this field. Returns an empty ErrorList
if there are none.
"""
- try:
- return self._form.errors()[self._name]
- except KeyError:
- return ErrorList()
+ return self.form.errors.get(self.name, ErrorList())
errors = property(_errors)
def as_widget(self, widget, attrs=None):
attrs = attrs or {}
auto_id = self.auto_id
- if not attrs.has_key('id') and not widget.attrs.has_key('id') and auto_id:
+ if auto_id and not attrs.has_key('id') and not widget.attrs.has_key('id'):
attrs['id'] = auto_id
- return widget.render(self._name, self._form.data.get(self._name, None), attrs=attrs)
+ return widget.render(self.html_name, self.data, attrs=attrs)
def as_text(self, attrs=None):
"""
@@ -171,15 +224,46 @@ class BoundField(object):
"Returns a string of HTML for representing this as a <textarea>."
return self.as_widget(Textarea(), attrs)
+ def as_hidden(self, attrs=None):
+ """
+ Returns a string of HTML for representing this as an <input type="hidden">.
+ """
+ return self.as_widget(HiddenInput(), attrs)
+
+ def _data(self):
+ "Returns the data for this BoundField, or None if it wasn't given."
+ if self.field.widget.requires_data_list and isinstance(self.form.data, MultiValueDict):
+ return self.form.data.getlist(self.html_name)
+ return self.form.data.get(self.html_name, None)
+ data = property(_data)
+
+ def label_tag(self, contents=None):
+ """
+ Wraps the given contents in a <label>, if the field has an ID attribute.
+ Does not HTML-escape the contents. If contents aren't given, uses the
+ field's HTML-escaped label.
+ """
+ contents = contents or escape(self.label)
+ widget = self.field.widget
+ id_ = widget.attrs.get('id') or self.auto_id
+ if id_:
+ contents = '<label for="%s">%s</label>' % (widget.id_for_label(id_), contents)
+ return contents
+
+ def _is_hidden(self):
+ "Returns True if this BoundField's widget is hidden."
+ return self.field.widget.is_hidden
+ is_hidden = property(_is_hidden)
+
def _auto_id(self):
"""
Calculates and returns the ID attribute for this BoundField, if the
associated Form has specified auto_id. Returns an empty string otherwise.
"""
- auto_id = self._form.auto_id
+ auto_id = self.form.auto_id
if auto_id and '%s' in str(auto_id):
- return str(auto_id) % self._name
+ return str(auto_id) % self.html_name
elif auto_id:
- return self._name
+ return self.html_name
return ''
auto_id = property(_auto_id)
diff --git a/django/newforms/models.py b/django/newforms/models.py
new file mode 100644
index 0000000000..6c01969001
--- /dev/null
+++ b/django/newforms/models.py
@@ -0,0 +1,19 @@
+"""
+Helper functions for creating Form classes from Django models
+and database field objects.
+"""
+
+from forms import BaseForm, DeclarativeFieldsMetaclass, SortedDictFromList
+
+__all__ = ('form_for_model', 'form_for_fields')
+
+def form_for_model(model):
+ "Returns a Form class for the given Django model class."
+ opts = model._meta
+ fields = SortedDictFromList([(f.name, f.formfield()) for f in opts.fields + opts.many_to_many])
+ return type(opts.object_name + 'Form', (BaseForm,), {'fields': fields, '_model_opts': opts})
+
+def form_for_fields(field_list):
+ "Returns a Form class for the given list of Django database field instances."
+ fields = SortedDictFromList([(f.name, f.formfield()) for f in field_list])
+ return type('FormForFields', (BaseForm,), {'fields': fields})
diff --git a/django/newforms/util.py b/django/newforms/util.py
index a5cc4932ea..a78623a17b 100644
--- a/django/newforms/util.py
+++ b/django/newforms/util.py
@@ -1,13 +1,22 @@
-# Default encoding for input byte strings.
-DEFAULT_ENCODING = 'utf-8' # TODO: First look at django.conf.settings, then fall back to this.
+from django.conf import settings
def smart_unicode(s):
if not isinstance(s, basestring):
s = unicode(str(s))
elif not isinstance(s, unicode):
- s = unicode(s, DEFAULT_ENCODING)
+ s = unicode(s, settings.DEFAULT_CHARSET)
return s
+class StrAndUnicode(object):
+ """
+ A class whose __str__ returns its __unicode__ as a bytestring
+ according to settings.DEFAULT_CHARSET.
+
+ Useful as a mix-in.
+ """
+ def __str__(self):
+ return self.__unicode__().encode(settings.DEFAULT_CHARSET)
+
class ErrorDict(dict):
"""
A collection of errors that knows how to display itself in various formats.
diff --git a/django/newforms/widgets.py b/django/newforms/widgets.py
index 318c76e55d..ba11f35008 100644
--- a/django/newforms/widgets.py
+++ b/django/newforms/widgets.py
@@ -5,10 +5,11 @@ HTML Widget classes
__all__ = (
'Widget', 'TextInput', 'PasswordInput', 'HiddenInput', 'FileInput',
'Textarea', 'CheckboxInput',
- 'Select', 'SelectMultiple', 'RadioSelect',
+ 'Select', 'SelectMultiple', 'RadioSelect', 'CheckboxSelectMultiple',
)
-from util import smart_unicode
+from util import StrAndUnicode, smart_unicode
+from django.utils.datastructures import MultiValueDict
from django.utils.html import escape
from itertools import chain
@@ -23,6 +24,8 @@ flatatt = lambda attrs: u''.join([u' %s="%s"' % (k, escape(v)) for k, v in attrs
class Widget(object):
requires_data_list = False # Determines whether render()'s 'value' argument should be a list.
+ is_hidden = False # Determines whether this corresponds to an <input type="hidden">.
+
def __init__(self, attrs=None):
self.attrs = attrs or {}
@@ -30,17 +33,39 @@ class Widget(object):
raise NotImplementedError
def build_attrs(self, extra_attrs=None, **kwargs):
+ "Helper function for building an attribute dictionary."
attrs = dict(self.attrs, **kwargs)
if extra_attrs:
attrs.update(extra_attrs)
return attrs
+ def value_from_datadict(self, data, name):
+ """
+ Given a dictionary of data and this widget's name, returns the value
+ of this widget. Returns None if it's not provided.
+ """
+ return data.get(name, None)
+
+ def id_for_label(self, id_):
+ """
+ Returns the HTML ID attribute of this Widget for use by a <label>,
+ given the ID of the field. Returns None if no ID is available.
+
+ This hook is necessary because some widgets have multiple HTML
+ elements and, thus, multiple IDs. In that case, this method should
+ return an ID value that corresponds to the first ID in the widget's
+ tags.
+ """
+ return id_
+ id_for_label = classmethod(id_for_label)
+
class Input(Widget):
"""
Base class for all <input> widgets (except type='checkbox' and
type='radio', which are special).
"""
input_type = None # Subclasses must define this.
+
def render(self, name, value, attrs=None):
if value is None: value = ''
final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
@@ -55,6 +80,7 @@ class PasswordInput(Input):
class HiddenInput(Input):
input_type = 'hidden'
+ is_hidden = True
class FileInput(Input):
input_type = 'file'
@@ -67,9 +93,22 @@ class Textarea(Widget):
return u'<textarea%s>%s</textarea>' % (flatatt(final_attrs), escape(value))
class CheckboxInput(Widget):
+ def __init__(self, attrs=None, check_test=bool):
+ # check_test is a callable that takes a value and returns True
+ # if the checkbox should be checked for that value.
+ self.attrs = attrs or {}
+ self.check_test = check_test
+
def render(self, name, value, attrs=None):
final_attrs = self.build_attrs(attrs, type='checkbox', name=name)
- if value: final_attrs['checked'] = 'checked'
+ try:
+ result = self.check_test(value)
+ except: # Silently catch exceptions
+ result = False
+ if result:
+ final_attrs['checked'] = 'checked'
+ if value not in ('', True, False, None):
+ final_attrs['value'] = smart_unicode(value) # Only add the 'value' attribute if a value is non-empty.
return u'<input%s />' % flatatt(final_attrs)
class Select(Widget):
@@ -109,36 +148,44 @@ class SelectMultiple(Widget):
output.append(u'</select>')
return u'\n'.join(output)
-class RadioInput(object):
+ def value_from_datadict(self, data, name):
+ if isinstance(data, MultiValueDict):
+ return data.getlist(name)
+ return data.get(name, None)
+
+class RadioInput(StrAndUnicode):
"An object used by RadioFieldRenderer that represents a single <input type='radio'>."
- def __init__(self, name, value, attrs, choice):
+ def __init__(self, name, value, attrs, choice, index):
self.name, self.value = name, value
- self.attrs = attrs or {}
+ self.attrs = attrs
self.choice_value, self.choice_label = choice
+ self.index = index
- def __str__(self):
+ def __unicode__(self):
return u'<label>%s %s</label>' % (self.tag(), self.choice_label)
def is_checked(self):
return self.value == smart_unicode(self.choice_value)
def tag(self):
+ if self.attrs.has_key('id'):
+ self.attrs['id'] = '%s_%s' % (self.attrs['id'], self.index)
final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value)
if self.is_checked():
final_attrs['checked'] = 'checked'
return u'<input%s />' % flatatt(final_attrs)
-class RadioFieldRenderer(object):
+class RadioFieldRenderer(StrAndUnicode):
"An object used by RadioSelect to enable customization of radio widgets."
def __init__(self, name, value, attrs, choices):
self.name, self.value, self.attrs = name, value, attrs
self.choices = choices
def __iter__(self):
- for choice in self.choices:
- yield RadioInput(self.name, self.value, self.attrs, choice)
+ for i, choice in enumerate(self.choices):
+ yield RadioInput(self.name, self.value, self.attrs.copy(), choice, i)
- def __str__(self):
+ def __unicode__(self):
"Outputs a <ul> for this set of radio fields."
return u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>' % w for w in self])
@@ -147,7 +194,36 @@ class RadioSelect(Select):
"Returns a RadioFieldRenderer instance rather than a Unicode string."
if value is None: value = ''
str_value = smart_unicode(value) # Normalize to string.
+ attrs = attrs or {}
return RadioFieldRenderer(name, str_value, attrs, list(chain(self.choices, choices)))
-class CheckboxSelectMultiple(Widget):
- pass
+ def id_for_label(self, id_):
+ # RadioSelect is represented by multiple <input type="radio"> fields,
+ # each of which has a distinct ID. The IDs are made distinct by a "_X"
+ # suffix, where X is the zero-based index of the radio field. Thus,
+ # the label for a RadioSelect should reference the first one ('_0').
+ if id_:
+ id_ += '_0'
+ return id_
+ id_for_label = classmethod(id_for_label)
+
+class CheckboxSelectMultiple(SelectMultiple):
+ def render(self, name, value, attrs=None, choices=()):
+ if value is None: value = []
+ final_attrs = self.build_attrs(attrs, name=name)
+ output = [u'<ul>']
+ str_values = set([smart_unicode(v) for v in value]) # Normalize to strings.
+ cb = CheckboxInput(final_attrs, check_test=lambda value: value in str_values)
+ for option_value, option_label in chain(self.choices, choices):
+ option_value = smart_unicode(option_value)
+ rendered_cb = cb.render(name, option_value)
+ output.append(u'<li><label>%s %s</label></li>' % (rendered_cb, escape(smart_unicode(option_label))))
+ output.append(u'</ul>')
+ return u'\n'.join(output)
+
+ def id_for_label(self, id_):
+ # See the comment for RadioSelect.id_for_label()
+ if id_:
+ id_ += '_0'
+ return id_
+ id_for_label = classmethod(id_for_label)
diff --git a/django/oldforms/__init__.py b/django/oldforms/__init__.py
new file mode 100644
index 0000000000..0b9ac05edb
--- /dev/null
+++ b/django/oldforms/__init__.py
@@ -0,0 +1,1008 @@
+from django.core import validators
+from django.core.exceptions import PermissionDenied
+from django.utils.html import escape
+from django.conf import settings
+from django.utils.translation import gettext, ngettext
+
+FORM_FIELD_ID_PREFIX = 'id_'
+
+class EmptyValue(Exception):
+ "This is raised when empty data is provided"
+ pass
+
+class Manipulator(object):
+ # List of permission strings. User must have at least one to manipulate.
+ # None means everybody has permission.
+ required_permission = ''
+
+ def __init__(self):
+ # List of FormField objects
+ self.fields = []
+
+ def __getitem__(self, field_name):
+ "Looks up field by field name; raises KeyError on failure"
+ for field in self.fields:
+ if field.field_name == field_name:
+ return field
+ raise KeyError, "Field %s not found\n%s" % (field_name, repr(self.fields))
+
+ def __delitem__(self, field_name):
+ "Deletes the field with the given field name; raises KeyError on failure"
+ for i, field in enumerate(self.fields):
+ if field.field_name == field_name:
+ del self.fields[i]
+ return
+ raise KeyError, "Field %s not found" % field_name
+
+ def check_permissions(self, user):
+ """Confirms user has required permissions to use this manipulator; raises
+ PermissionDenied on failure."""
+ if self.required_permission is None:
+ return
+ if user.has_perm(self.required_permission):
+ return
+ raise PermissionDenied
+
+ def prepare(self, new_data):
+ """
+ Makes any necessary preparations to new_data, in place, before data has
+ been validated.
+ """
+ for field in self.fields:
+ field.prepare(new_data)
+
+ def get_validation_errors(self, new_data):
+ "Returns dictionary mapping field_names to error-message lists"
+ errors = {}
+ self.prepare(new_data)
+ for field in self.fields:
+ errors.update(field.get_validation_errors(new_data))
+ val_name = 'validate_%s' % field.field_name
+ if hasattr(self, val_name):
+ val = getattr(self, val_name)
+ try:
+ field.run_validator(new_data, val)
+ except (validators.ValidationError, validators.CriticalValidationError), e:
+ errors.setdefault(field.field_name, []).extend(e.messages)
+
+# if field.is_required and not new_data.get(field.field_name, False):
+# errors.setdefault(field.field_name, []).append(gettext_lazy('This field is required.'))
+# continue
+# try:
+# validator_list = field.validator_list
+# if hasattr(self, 'validate_%s' % field.field_name):
+# validator_list.append(getattr(self, 'validate_%s' % field.field_name))
+# for validator in validator_list:
+# if field.is_required or new_data.get(field.field_name, False) or hasattr(validator, 'always_test'):
+# try:
+# if hasattr(field, 'requires_data_list'):
+# validator(new_data.getlist(field.field_name), new_data)
+# else:
+# validator(new_data.get(field.field_name, ''), new_data)
+# except validators.ValidationError, e:
+# errors.setdefault(field.field_name, []).extend(e.messages)
+# # If a CriticalValidationError is raised, ignore any other ValidationErrors
+# # for this particular field
+# except validators.CriticalValidationError, e:
+# errors.setdefault(field.field_name, []).extend(e.messages)
+ return errors
+
+ def save(self, new_data):
+ "Saves the changes and returns the new object"
+ # changes is a dictionary-like object keyed by field_name
+ raise NotImplementedError
+
+ def do_html2python(self, new_data):
+ """
+ Convert the data from HTML data types to Python datatypes, changing the
+ object in place. This happens after validation but before storage. This
+ must happen after validation because html2python functions aren't
+ expected to deal with invalid input.
+ """
+ for field in self.fields:
+ field.convert_post_data(new_data)
+
+class FormWrapper(object):
+ """
+ A wrapper linking a Manipulator to the template system.
+ This allows dictionary-style lookups of formfields. It also handles feeding
+ prepopulated data and validation error messages to the formfield objects.
+ """
+ def __init__(self, manipulator, data=None, error_dict=None, edit_inline=True):
+ self.manipulator = manipulator
+ if data is None:
+ data = {}
+ if error_dict is None:
+ error_dict = {}
+ self.data = data
+ self.error_dict = error_dict
+ self._inline_collections = None
+ self.edit_inline = edit_inline
+
+ def __repr__(self):
+ return repr(self.__dict__)
+
+ def __getitem__(self, key):
+ for field in self.manipulator.fields:
+ if field.field_name == key:
+ data = field.extract_data(self.data)
+ return FormFieldWrapper(field, data, self.error_dict.get(field.field_name, []))
+ if self.edit_inline:
+ self.fill_inline_collections()
+ for inline_collection in self._inline_collections:
+ if inline_collection.name == key:
+ return inline_collection
+ raise KeyError, "Could not find Formfield or InlineObjectCollection named %r" % key
+
+ def fill_inline_collections(self):
+ if not self._inline_collections:
+ ic = []
+ related_objects = self.manipulator.get_related_objects()
+ for rel_obj in related_objects:
+ data = rel_obj.extract_data(self.data)
+ inline_collection = InlineObjectCollection(self.manipulator, rel_obj, data, self.error_dict)
+ ic.append(inline_collection)
+ self._inline_collections = ic
+
+ def has_errors(self):
+ return self.error_dict != {}
+
+ def _get_fields(self):
+ try:
+ return self._fields
+ except AttributeError:
+ self._fields = [self.__getitem__(field.field_name) for field in self.manipulator.fields]
+ return self._fields
+
+ fields = property(_get_fields)
+
+class FormFieldWrapper(object):
+ "A bridge between the template system and an individual form field. Used by FormWrapper."
+ def __init__(self, formfield, data, error_list):
+ self.formfield, self.data, self.error_list = formfield, data, error_list
+ self.field_name = self.formfield.field_name # for convenience in templates
+
+ def __str__(self):
+ "Renders the field"
+ return str(self.formfield.render(self.data))
+
+ def __repr__(self):
+ return '<FormFieldWrapper for "%s">' % self.formfield.field_name
+
+ def field_list(self):
+ """
+ Like __str__(), but returns a list. Use this when the field's render()
+ method returns a list.
+ """
+ return self.formfield.render(self.data)
+
+ def errors(self):
+ return self.error_list
+
+ def html_error_list(self):
+ if self.errors():
+ return '<ul class="errorlist"><li>%s</li></ul>' % '</li><li>'.join([escape(e) for e in self.errors()])
+ else:
+ return ''
+
+ def get_id(self):
+ return self.formfield.get_id()
+
+class FormFieldCollection(FormFieldWrapper):
+ "A utility class that gives the template access to a dict of FormFieldWrappers"
+ def __init__(self, formfield_dict):
+ self.formfield_dict = formfield_dict
+
+ def __str__(self):
+ return str(self.formfield_dict)
+
+ def __getitem__(self, template_key):
+ "Look up field by template key; raise KeyError on failure"
+ return self.formfield_dict[template_key]
+
+ def __repr__(self):
+ return "<FormFieldCollection: %s>" % self.formfield_dict
+
+ def errors(self):
+ "Returns list of all errors in this collection's formfields"
+ errors = []
+ for field in self.formfield_dict.values():
+ if hasattr(field, 'errors'):
+ errors.extend(field.errors())
+ return errors
+
+ def has_errors(self):
+ return bool(len(self.errors()))
+
+ def html_combined_error_list(self):
+ return ''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')])
+
+class InlineObjectCollection(object):
+ "An object that acts like a sparse list of form field collections."
+ def __init__(self, parent_manipulator, rel_obj, data, errors):
+ self.parent_manipulator = parent_manipulator
+ self.rel_obj = rel_obj
+ self.data = data
+ self.errors = errors
+ self._collections = None
+ self.name = rel_obj.name
+
+ def __len__(self):
+ self.fill()
+ return self._collections.__len__()
+
+ def __getitem__(self, k):
+ self.fill()
+ return self._collections.__getitem__(k)
+
+ def __setitem__(self, k, v):
+ self.fill()
+ return self._collections.__setitem__(k,v)
+
+ def __delitem__(self, k):
+ self.fill()
+ return self._collections.__delitem__(k)
+
+ def __iter__(self):
+ self.fill()
+ return iter(self._collections.values())
+
+ def items(self):
+ self.fill()
+ return self._collections.items()
+
+ def fill(self):
+ if self._collections:
+ return
+ else:
+ var_name = self.rel_obj.opts.object_name.lower()
+ collections = {}
+ orig = None
+ if hasattr(self.parent_manipulator, 'original_object'):
+ orig = self.parent_manipulator.original_object
+ orig_list = self.rel_obj.get_list(orig)
+
+ for i, instance in enumerate(orig_list):
+ collection = {'original': instance}
+ for f in self.rel_obj.editable_fields():
+ for field_name in f.get_manipulator_field_names(''):
+ full_field_name = '%s.%d.%s' % (var_name, i, field_name)
+ field = self.parent_manipulator[full_field_name]
+ data = field.extract_data(self.data)
+ errors = self.errors.get(full_field_name, [])
+ collection[field_name] = FormFieldWrapper(field, data, errors)
+ collections[i] = FormFieldCollection(collection)
+ self._collections = collections
+
+
+class FormField(object):
+ """Abstract class representing a form field.
+
+ Classes that extend FormField should define the following attributes:
+ field_name
+ The field's name for use by programs.
+ validator_list
+ A list of validation tests (callback functions) that the data for
+ this field must pass in order to be added or changed.
+ is_required
+ A Boolean. Is it a required field?
+ Subclasses should also implement a render(data) method, which is responsible
+ for rending the form field in XHTML.
+ """
+ def __str__(self):
+ return self.render('')
+
+ def __repr__(self):
+ return 'FormField "%s"' % self.field_name
+
+ def prepare(self, new_data):
+ "Hook for doing something to new_data (in place) before validation."
+ pass
+
+ def html2python(data):
+ "Hook for converting an HTML datatype (e.g. 'on' for checkboxes) to a Python type"
+ return data
+ html2python = staticmethod(html2python)
+
+ def render(self, data):
+ raise NotImplementedError
+
+ def get_member_name(self):
+ if hasattr(self, 'member_name'):
+ return self.member_name
+ else:
+ return self.field_name
+
+ def extract_data(self, data_dict):
+ if hasattr(self, 'requires_data_list') and hasattr(data_dict, 'getlist'):
+ data = data_dict.getlist(self.get_member_name())
+ else:
+ data = data_dict.get(self.get_member_name(), None)
+ if data is None:
+ data = ''
+ return data
+
+ def convert_post_data(self, new_data):
+ name = self.get_member_name()
+ if new_data.has_key(self.field_name):
+ d = new_data.getlist(self.field_name)
+ try:
+ converted_data = [self.__class__.html2python(data) for data in d]
+ except ValueError:
+ converted_data = d
+ new_data.setlist(name, converted_data)
+ else:
+ try:
+ #individual fields deal with None values themselves
+ new_data.setlist(name, [self.__class__.html2python(None)])
+ except EmptyValue:
+ new_data.setlist(name, [])
+
+
+ def run_validator(self, new_data, validator):
+ if self.is_required or new_data.get(self.field_name, False) or hasattr(validator, 'always_test'):
+ if hasattr(self, 'requires_data_list'):
+ validator(new_data.getlist(self.field_name), new_data)
+ else:
+ validator(new_data.get(self.field_name, ''), new_data)
+
+ def get_validation_errors(self, new_data):
+ errors = {}
+ if self.is_required and not new_data.get(self.field_name, False):
+ errors.setdefault(self.field_name, []).append(gettext('This field is required.'))
+ return errors
+ try:
+ for validator in self.validator_list:
+ try:
+ self.run_validator(new_data, validator)
+ except validators.ValidationError, e:
+ errors.setdefault(self.field_name, []).extend(e.messages)
+ # If a CriticalValidationError is raised, ignore any other ValidationErrors
+ # for this particular field
+ except validators.CriticalValidationError, e:
+ errors.setdefault(self.field_name, []).extend(e.messages)
+ return errors
+
+ def get_id(self):
+ "Returns the HTML 'id' attribute for this form field."
+ return FORM_FIELD_ID_PREFIX + self.field_name
+
+####################
+# GENERIC WIDGETS #
+####################
+
+class TextField(FormField):
+ input_type = "text"
+ def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=None, member_name=None):
+ if validator_list is None: validator_list = []
+ self.field_name = field_name
+ self.length, self.maxlength = length, maxlength
+ self.is_required = is_required
+ self.validator_list = [self.isValidLength, self.hasNoNewlines] + validator_list
+ if member_name != None:
+ self.member_name = member_name
+
+ def isValidLength(self, data, form):
+ if data and self.maxlength and len(data.decode(settings.DEFAULT_CHARSET)) > self.maxlength:
+ raise validators.ValidationError, ngettext("Ensure your text is less than %s character.",
+ "Ensure your text is less than %s characters.", self.maxlength) % self.maxlength
+
+ def hasNoNewlines(self, data, form):
+ if data and '\n' in data:
+ raise validators.ValidationError, gettext("Line breaks are not allowed here.")
+
+ def render(self, data):
+ if data is None:
+ data = ''
+ maxlength = ''
+ if self.maxlength:
+ maxlength = 'maxlength="%s" ' % self.maxlength
+ if isinstance(data, unicode):
+ data = data.encode(settings.DEFAULT_CHARSET)
+ return '<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \
+ (self.input_type, self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
+ self.field_name, self.length, escape(data), maxlength)
+
+ def html2python(data):
+ return data
+ html2python = staticmethod(html2python)
+
+class PasswordField(TextField):
+ input_type = "password"
+
+class LargeTextField(TextField):
+ def __init__(self, field_name, rows=10, cols=40, is_required=False, validator_list=None, maxlength=None):
+ if validator_list is None: validator_list = []
+ self.field_name = field_name
+ self.rows, self.cols, self.is_required = rows, cols, is_required
+ self.validator_list = validator_list[:]
+ if maxlength:
+ self.validator_list.append(self.isValidLength)
+ self.maxlength = maxlength
+
+ def render(self, data):
+ if data is None:
+ data = ''
+ if isinstance(data, unicode):
+ data = data.encode(settings.DEFAULT_CHARSET)
+ return '<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \
+ (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
+ self.field_name, self.rows, self.cols, escape(data))
+
+class HiddenField(FormField):
+ def __init__(self, field_name, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ self.field_name, self.is_required = field_name, is_required
+ self.validator_list = validator_list[:]
+
+ def render(self, data):
+ return '<input type="hidden" id="%s" name="%s" value="%s" />' % \
+ (self.get_id(), self.field_name, escape(data))
+
+class CheckboxField(FormField):
+ def __init__(self, field_name, checked_by_default=False, validator_list=None, is_required=False):
+ if validator_list is None: validator_list = []
+ self.field_name = field_name
+ self.checked_by_default = checked_by_default
+ self.is_required = is_required
+ self.validator_list = validator_list[:]
+
+ def render(self, data):
+ checked_html = ''
+ if data or (data is '' and self.checked_by_default):
+ checked_html = ' checked="checked"'
+ return '<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \
+ (self.get_id(), self.__class__.__name__,
+ self.field_name, checked_html)
+
+ def html2python(data):
+ "Convert value from browser ('on' or '') to a Python boolean"
+ if data == 'on':
+ return True
+ return False
+ html2python = staticmethod(html2python)
+
+class SelectField(FormField):
+ def __init__(self, field_name, choices=None, size=1, is_required=False, validator_list=None, member_name=None):
+ if validator_list is None: validator_list = []
+ if choices is None: choices = []
+ self.field_name = field_name
+ # choices is a list of (value, human-readable key) tuples because order matters
+ self.choices, self.size, self.is_required = choices, size, is_required
+ self.validator_list = [self.isValidChoice] + validator_list
+ if member_name != None:
+ self.member_name = member_name
+
+ def render(self, data):
+ output = ['<select id="%s" class="v%s%s" name="%s" size="%s">' % \
+ (self.get_id(), self.__class__.__name__,
+ self.is_required and ' required' or '', self.field_name, self.size)]
+ str_data = str(data) # normalize to string
+ for value, display_name in self.choices:
+ selected_html = ''
+ if str(value) == str_data:
+ selected_html = ' selected="selected"'
+ output.append(' <option value="%s"%s>%s</option>' % (escape(value), selected_html, escape(display_name)))
+ output.append(' </select>')
+ return '\n'.join(output)
+
+ def isValidChoice(self, data, form):
+ str_data = str(data)
+ str_choices = [str(item[0]) for item in self.choices]
+ if str_data not in str_choices:
+ raise validators.ValidationError, gettext("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data': str_data, 'choices': str_choices}
+
+class NullSelectField(SelectField):
+ "This SelectField converts blank fields to None"
+ def html2python(data):
+ if not data:
+ return None
+ return data
+ html2python = staticmethod(html2python)
+
+class RadioSelectField(FormField):
+ def __init__(self, field_name, choices=None, ul_class='', is_required=False, validator_list=None, member_name=None):
+ if validator_list is None: validator_list = []
+ if choices is None: choices = []
+ self.field_name = field_name
+ # choices is a list of (value, human-readable key) tuples because order matters
+ self.choices, self.is_required = choices, is_required
+ self.validator_list = [self.isValidChoice] + validator_list
+ self.ul_class = ul_class
+ if member_name != None:
+ self.member_name = member_name
+
+ def render(self, data):
+ """
+ Returns a special object, RadioFieldRenderer, that is iterable *and*
+ has a default str() rendered output.
+
+ This allows for flexible use in templates. You can just use the default
+ rendering:
+
+ {{ field_name }}
+
+ ...which will output the radio buttons in an unordered list.
+ Or, you can manually traverse each radio option for special layout:
+
+ {% for option in field_name.field_list %}
+ {{ option.field }} {{ option.label }}<br />
+ {% endfor %}
+ """
+ class RadioFieldRenderer:
+ def __init__(self, datalist, ul_class):
+ self.datalist, self.ul_class = datalist, ul_class
+ def __str__(self):
+ "Default str() output for this radio field -- a <ul>"
+ output = ['<ul%s>' % (self.ul_class and ' class="%s"' % self.ul_class or '')]
+ output.extend(['<li>%s %s</li>' % (d['field'], d['label']) for d in self.datalist])
+ output.append('</ul>')
+ return ''.join(output)
+ def __iter__(self):
+ for d in self.datalist:
+ yield d
+ def __len__(self):
+ return len(self.datalist)
+ datalist = []
+ str_data = str(data) # normalize to string
+ for i, (value, display_name) in enumerate(self.choices):
+ selected_html = ''
+ if str(value) == str_data:
+ selected_html = ' checked="checked"'
+ datalist.append({
+ 'value': value,
+ 'name': display_name,
+ 'field': '<input type="radio" id="%s" name="%s" value="%s"%s/>' % \
+ (self.get_id() + '_' + str(i), self.field_name, value, selected_html),
+ 'label': '<label for="%s">%s</label>' % \
+ (self.get_id() + '_' + str(i), display_name),
+ })
+ return RadioFieldRenderer(datalist, self.ul_class)
+
+ def isValidChoice(self, data, form):
+ str_data = str(data)
+ str_choices = [str(item[0]) for item in self.choices]
+ if str_data not in str_choices:
+ raise validators.ValidationError, gettext("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data':str_data, 'choices':str_choices}
+
+class NullBooleanField(SelectField):
+ "This SelectField provides 'Yes', 'No' and 'Unknown', mapping results to True, False or None"
+ def __init__(self, field_name, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ SelectField.__init__(self, field_name, choices=[('1', 'Unknown'), ('2', 'Yes'), ('3', 'No')],
+ is_required=is_required, validator_list=validator_list)
+
+ def render(self, data):
+ if data is None: data = '1'
+ elif data == True: data = '2'
+ elif data == False: data = '3'
+ return SelectField.render(self, data)
+
+ def html2python(data):
+ return {None: None, '1': None, '2': True, '3': False}[data]
+ html2python = staticmethod(html2python)
+
+class SelectMultipleField(SelectField):
+ requires_data_list = True
+ def render(self, data):
+ output = ['<select id="%s" class="v%s%s" name="%s" size="%s" multiple="multiple">' % \
+ (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
+ self.field_name, self.size)]
+ str_data_list = map(str, data) # normalize to strings
+ for value, choice in self.choices:
+ selected_html = ''
+ if str(value) in str_data_list:
+ selected_html = ' selected="selected"'
+ output.append(' <option value="%s"%s>%s</option>' % (escape(value), selected_html, escape(choice)))
+ output.append(' </select>')
+ return '\n'.join(output)
+
+ def isValidChoice(self, field_data, all_data):
+ # data is something like ['1', '2', '3']
+ str_choices = [str(item[0]) for item in self.choices]
+ for val in map(str, field_data):
+ if val not in str_choices:
+ raise validators.ValidationError, gettext("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data':val, 'choices':str_choices}
+
+ def html2python(data):
+ if data is None:
+ raise EmptyValue
+ return data
+ html2python = staticmethod(html2python)
+
+class CheckboxSelectMultipleField(SelectMultipleField):
+ """
+ This has an identical interface to SelectMultipleField, except the rendered
+ widget is different. Instead of a <select multiple>, this widget outputs a
+ <ul> of <input type="checkbox">es.
+
+ Of course, that results in multiple form elements for the same "single"
+ field, so this class's prepare() method flattens the split data elements
+ back into the single list that validators, renderers and save() expect.
+ """
+ requires_data_list = True
+ def __init__(self, field_name, choices=None, ul_class='', validator_list=None):
+ if validator_list is None: validator_list = []
+ if choices is None: choices = []
+ self.ul_class = ul_class
+ SelectMultipleField.__init__(self, field_name, choices, size=1, is_required=False, validator_list=validator_list)
+
+ def prepare(self, new_data):
+ # new_data has "split" this field into several fields, so flatten it
+ # back into a single list.
+ data_list = []
+ for value, readable_value in self.choices:
+ if new_data.get('%s%s' % (self.field_name, value), '') == 'on':
+ data_list.append(value)
+ new_data.setlist(self.field_name, data_list)
+
+ def render(self, data):
+ output = ['<ul%s>' % (self.ul_class and ' class="%s"' % self.ul_class or '')]
+ str_data_list = map(str, data) # normalize to strings
+ for value, choice in self.choices:
+ checked_html = ''
+ if str(value) in str_data_list:
+ checked_html = ' checked="checked"'
+ field_name = '%s%s' % (self.field_name, value)
+ output.append('<li><input type="checkbox" id="%s" class="v%s" name="%s"%s value="on" /> <label for="%s">%s</label></li>' % \
+ (self.get_id() + escape(value), self.__class__.__name__, field_name, checked_html,
+ self.get_id() + escape(value), choice))
+ output.append('</ul>')
+ return '\n'.join(output)
+
+####################
+# FILE UPLOADS #
+####################
+
+class FileUploadField(FormField):
+ def __init__(self, field_name, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ self.field_name, self.is_required = field_name, is_required
+ self.validator_list = [self.isNonEmptyFile] + validator_list
+
+ def isNonEmptyFile(self, field_data, all_data):
+ try:
+ content = field_data['content']
+ except TypeError:
+ raise validators.CriticalValidationError, gettext("No file was submitted. Check the encoding type on the form.")
+ if not content:
+ raise validators.CriticalValidationError, gettext("The submitted file is empty.")
+
+ def render(self, data):
+ return '<input type="file" id="%s" class="v%s" name="%s" />' % \
+ (self.get_id(), self.__class__.__name__, self.field_name)
+
+ def html2python(data):
+ if data is None:
+ raise EmptyValue
+ return data
+ html2python = staticmethod(html2python)
+
+class ImageUploadField(FileUploadField):
+ "A FileUploadField that raises CriticalValidationError if the uploaded file isn't an image."
+ def __init__(self, *args, **kwargs):
+ FileUploadField.__init__(self, *args, **kwargs)
+ self.validator_list.insert(0, self.isValidImage)
+
+ def isValidImage(self, field_data, all_data):
+ try:
+ validators.isValidImage(field_data, all_data)
+ except validators.ValidationError, e:
+ raise validators.CriticalValidationError, e.messages
+
+####################
+# INTEGERS/FLOATS #
+####################
+
+class IntegerField(TextField):
+ def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None, member_name=None):
+ if validator_list is None: validator_list = []
+ validator_list = [self.isInteger] + validator_list
+ if member_name is not None:
+ self.member_name = member_name
+ TextField.__init__(self, field_name, length, maxlength, is_required, validator_list)
+
+ def isInteger(self, field_data, all_data):
+ try:
+ validators.isInteger(field_data, all_data)
+ except validators.ValidationError, e:
+ raise validators.CriticalValidationError, e.messages
+
+ def html2python(data):
+ if data == '' or data is None:
+ return None
+ return int(data)
+ html2python = staticmethod(html2python)
+
+class SmallIntegerField(IntegerField):
+ def __init__(self, field_name, length=5, maxlength=5, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ validator_list = [self.isSmallInteger] + validator_list
+ IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list)
+
+ def isSmallInteger(self, field_data, all_data):
+ if not -32768 <= int(field_data) <= 32767:
+ raise validators.CriticalValidationError, gettext("Enter a whole number between -32,768 and 32,767.")
+
+class PositiveIntegerField(IntegerField):
+ def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ validator_list = [self.isPositive] + validator_list
+ IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list)
+
+ def isPositive(self, field_data, all_data):
+ if int(field_data) < 0:
+ raise validators.CriticalValidationError, gettext("Enter a positive number.")
+
+class PositiveSmallIntegerField(IntegerField):
+ def __init__(self, field_name, length=5, maxlength=None, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ validator_list = [self.isPositiveSmall] + validator_list
+ IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list)
+
+ def isPositiveSmall(self, field_data, all_data):
+ if not 0 <= int(field_data) <= 32767:
+ raise validators.CriticalValidationError, gettext("Enter a whole number between 0 and 32,767.")
+
+class FloatField(TextField):
+ def __init__(self, field_name, max_digits, decimal_places, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ self.max_digits, self.decimal_places = max_digits, decimal_places
+ validator_list = [self.isValidFloat] + validator_list
+ TextField.__init__(self, field_name, max_digits+2, max_digits+2, is_required, validator_list)
+
+ def isValidFloat(self, field_data, all_data):
+ v = validators.IsValidFloat(self.max_digits, self.decimal_places)
+ try:
+ v(field_data, all_data)
+ except validators.ValidationError, e:
+ raise validators.CriticalValidationError, e.messages
+
+ def html2python(data):
+ if data == '' or data is None:
+ return None
+ return float(data)
+ html2python = staticmethod(html2python)
+
+####################
+# DATES AND TIMES #
+####################
+
+class DatetimeField(TextField):
+ """A FormField that automatically converts its data to a datetime.datetime object.
+ The data should be in the format YYYY-MM-DD HH:MM:SS."""
+ def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ self.field_name = field_name
+ self.length, self.maxlength = length, maxlength
+ self.is_required = is_required
+ self.validator_list = [validators.isValidANSIDatetime] + validator_list
+
+ def html2python(data):
+ "Converts the field into a datetime.datetime object"
+ import datetime
+ try:
+ date, time = data.split()
+ y, m, d = date.split('-')
+ timebits = time.split(':')
+ h, mn = timebits[:2]
+ if len(timebits) > 2:
+ s = int(timebits[2])
+ else:
+ s = 0
+ return datetime.datetime(int(y), int(m), int(d), int(h), int(mn), s)
+ except ValueError:
+ return None
+ html2python = staticmethod(html2python)
+
+class DateField(TextField):
+ """A FormField that automatically converts its data to a datetime.date object.
+ The data should be in the format YYYY-MM-DD."""
+ def __init__(self, field_name, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ validator_list = [self.isValidDate] + validator_list
+ TextField.__init__(self, field_name, length=10, maxlength=10,
+ is_required=is_required, validator_list=validator_list)
+
+ def isValidDate(self, field_data, all_data):
+ try:
+ validators.isValidANSIDate(field_data, all_data)
+ except validators.ValidationError, e:
+ raise validators.CriticalValidationError, e.messages
+
+ def html2python(data):
+ "Converts the field into a datetime.date object"
+ import time, datetime
+ try:
+ time_tuple = time.strptime(data, '%Y-%m-%d')
+ return datetime.date(*time_tuple[0:3])
+ except (ValueError, TypeError):
+ return None
+ html2python = staticmethod(html2python)
+
+class TimeField(TextField):
+ """A FormField that automatically converts its data to a datetime.time object.
+ The data should be in the format HH:MM:SS or HH:MM:SS.mmmmmm."""
+ def __init__(self, field_name, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ validator_list = [self.isValidTime] + validator_list
+ TextField.__init__(self, field_name, length=8, maxlength=8,
+ is_required=is_required, validator_list=validator_list)
+
+ def isValidTime(self, field_data, all_data):
+ try:
+ validators.isValidANSITime(field_data, all_data)
+ except validators.ValidationError, e:
+ raise validators.CriticalValidationError, e.messages
+
+ def html2python(data):
+ "Converts the field into a datetime.time object"
+ import time, datetime
+ try:
+ part_list = data.split('.')
+ try:
+ time_tuple = time.strptime(part_list[0], '%H:%M:%S')
+ except ValueError: # seconds weren't provided
+ time_tuple = time.strptime(part_list[0], '%H:%M')
+ t = datetime.time(*time_tuple[3:6])
+ if (len(part_list) == 2):
+ t = t.replace(microsecond=int(part_list[1]))
+ return t
+ except (ValueError, TypeError, AttributeError):
+ return None
+ html2python = staticmethod(html2python)
+
+####################
+# INTERNET-RELATED #
+####################
+
+class EmailField(TextField):
+ "A convenience FormField for validating e-mail addresses"
+ def __init__(self, field_name, length=50, maxlength=75, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ validator_list = [self.isValidEmail] + validator_list
+ TextField.__init__(self, field_name, length, maxlength=maxlength,
+ is_required=is_required, validator_list=validator_list)
+
+ def isValidEmail(self, field_data, all_data):
+ try:
+ validators.isValidEmail(field_data, all_data)
+ except validators.ValidationError, e:
+ raise validators.CriticalValidationError, e.messages
+
+class URLField(TextField):
+ "A convenience FormField for validating URLs"
+ def __init__(self, field_name, length=50, maxlength=200, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ validator_list = [self.isValidURL] + validator_list
+ TextField.__init__(self, field_name, length=length, maxlength=maxlength,
+ is_required=is_required, validator_list=validator_list)
+
+ def isValidURL(self, field_data, all_data):
+ try:
+ validators.isValidURL(field_data, all_data)
+ except validators.ValidationError, e:
+ raise validators.CriticalValidationError, e.messages
+
+class IPAddressField(TextField):
+ def __init__(self, field_name, length=15, maxlength=15, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ validator_list = [self.isValidIPAddress] + validator_list
+ TextField.__init__(self, field_name, length=length, maxlength=maxlength,
+ is_required=is_required, validator_list=validator_list)
+
+ def isValidIPAddress(self, field_data, all_data):
+ try:
+ validators.isValidIPAddress4(field_data, all_data)
+ except validators.ValidationError, e:
+ raise validators.CriticalValidationError, e.messages
+
+ def html2python(data):
+ return data or None
+ html2python = staticmethod(html2python)
+
+####################
+# MISCELLANEOUS #
+####################
+
+class FilePathField(SelectField):
+ "A SelectField whose choices are the files in a given directory."
+ def __init__(self, field_name, path, match=None, recursive=False, is_required=False, validator_list=None):
+ import os
+ from django.db.models import BLANK_CHOICE_DASH
+ if match is not None:
+ import re
+ match_re = re.compile(match)
+ choices = not is_required and BLANK_CHOICE_DASH[:] or []
+ if recursive:
+ for root, dirs, files in os.walk(path):
+ for f in files:
+ if match is None or match_re.search(f):
+ choices.append((os.path.join(root, f), f))
+ else:
+ try:
+ for f in os.listdir(path):
+ full_file = os.path.join(path, f)
+ if os.path.isfile(full_file) and (match is None or match_re.search(f)):
+ choices.append((full_file, f))
+ except OSError:
+ pass
+ SelectField.__init__(self, field_name, choices, 1, is_required, validator_list)
+
+class PhoneNumberField(TextField):
+ "A convenience FormField for validating phone numbers (e.g. '630-555-1234')"
+ def __init__(self, field_name, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ validator_list = [self.isValidPhone] + validator_list
+ TextField.__init__(self, field_name, length=12, maxlength=12,
+ is_required=is_required, validator_list=validator_list)
+
+ def isValidPhone(self, field_data, all_data):
+ try:
+ validators.isValidPhone(field_data, all_data)
+ except validators.ValidationError, e:
+ raise validators.CriticalValidationError, e.messages
+
+class USStateField(TextField):
+ "A convenience FormField for validating U.S. states (e.g. 'IL')"
+ def __init__(self, field_name, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ validator_list = [self.isValidUSState] + validator_list
+ TextField.__init__(self, field_name, length=2, maxlength=2,
+ is_required=is_required, validator_list=validator_list)
+
+ def isValidUSState(self, field_data, all_data):
+ try:
+ validators.isValidUSState(field_data, all_data)
+ except validators.ValidationError, e:
+ raise validators.CriticalValidationError, e.messages
+
+ def html2python(data):
+ return data.upper() # Should always be stored in upper case
+ html2python = staticmethod(html2python)
+
+class CommaSeparatedIntegerField(TextField):
+ "A convenience FormField for validating comma-separated integer fields"
+ def __init__(self, field_name, maxlength=None, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ validator_list = [self.isCommaSeparatedIntegerList] + validator_list
+ TextField.__init__(self, field_name, length=20, maxlength=maxlength,
+ is_required=is_required, validator_list=validator_list)
+
+ def isCommaSeparatedIntegerList(self, field_data, all_data):
+ try:
+ validators.isCommaSeparatedIntegerList(field_data, all_data)
+ except validators.ValidationError, e:
+ raise validators.CriticalValidationError, e.messages
+
+ def render(self, data):
+ if data is None:
+ data = ''
+ elif isinstance(data, (list, tuple)):
+ data = ','.join(data)
+ return super(CommaSeparatedIntegerField, self).render(data)
+
+class RawIdAdminField(CommaSeparatedIntegerField):
+ def html2python(data):
+ if data:
+ return data.split(',')
+ else:
+ return []
+ html2python = staticmethod(html2python)
+
+class XMLLargeTextField(LargeTextField):
+ """
+ A LargeTextField with an XML validator. The schema_path argument is the
+ full path to a Relax NG compact schema to validate against.
+ """
+ def __init__(self, field_name, schema_path, **kwargs):
+ self.schema_path = schema_path
+ kwargs.setdefault('validator_list', []).insert(0, self.isValidXML)
+ LargeTextField.__init__(self, field_name, **kwargs)
+
+ def isValidXML(self, field_data, all_data):
+ v = validators.RelaxNGCompact(self.schema_path)
+ try:
+ v(field_data, all_data)
+ except validators.ValidationError, e:
+ raise validators.CriticalValidationError, e.messages
diff --git a/django/template/__init__.py b/django/template/__init__.py
index 5affafeba9..7718801684 100644
--- a/django/template/__init__.py
+++ b/django/template/__init__.py
@@ -742,7 +742,11 @@ class VariableNode(Node):
def encode_output(self, output):
# Check type so that we don't run str() on a Unicode object
if not isinstance(output, basestring):
- return str(output)
+ try:
+ return str(output)
+ except UnicodeEncodeError:
+ # If __str__() returns a Unicode object, convert it to bytestring.
+ return unicode(output).encode(settings.DEFAULT_CHARSET)
elif isinstance(output, unicode):
return output.encode(settings.DEFAULT_CHARSET)
else:
diff --git a/django/views/generic/create_update.py b/django/views/generic/create_update.py
index 3a03fa59e4..28987f7544 100644
--- a/django/views/generic/create_update.py
+++ b/django/views/generic/create_update.py
@@ -1,6 +1,6 @@
from django.core.xheaders import populate_xheaders
from django.template import loader
-from django import forms
+from django import oldforms
from django.db.models import FileField
from django.contrib.auth.views import redirect_to_login
from django.template import RequestContext
@@ -56,7 +56,7 @@ def create_object(request, model, template_name=None,
new_data = manipulator.flatten_data()
# Create the FormWrapper, template, context, response
- form = forms.FormWrapper(manipulator, new_data, errors)
+ form = oldforms.FormWrapper(manipulator, new_data, errors)
if not template_name:
template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower())
t = template_loader.get_template(template_name)
@@ -128,7 +128,7 @@ def update_object(request, model, object_id=None, slug=None,
# This makes sure the form acurate represents the fields of the place.
new_data = manipulator.flatten_data()
- form = forms.FormWrapper(manipulator, new_data, errors)
+ form = oldforms.FormWrapper(manipulator, new_data, errors)
if not template_name:
template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower())
t = template_loader.get_template(template_name)
diff --git a/django/views/generic/list_detail.py b/django/views/generic/list_detail.py
index bd0f17c56a..1836ce4a9f 100644
--- a/django/views/generic/list_detail.py
+++ b/django/views/generic/list_detail.py
@@ -84,7 +84,7 @@ def object_detail(request, queryset, object_id=None, slug=None,
context_processors=None, template_object_name='object',
mimetype=None):
"""
- Generic list of objects.
+ Generic detail of an object.
Templates: ``<app_label>/<model_name>_detail.html``
Context: