summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorAdrian Holovaty <adrian@holovaty.com>2006-12-27 02:49:28 +0000
committerAdrian Holovaty <adrian@holovaty.com>2006-12-27 02:49:28 +0000
commit0cf7bc439129c66df8d64601e885f83b256b4f25 (patch)
treea7fd3cd4df5e862578544778d1b720ded59b7274 /django
parentc4673e4fb68237f96652d7372bec0283c5446c27 (diff)
per-object-permissions: Merged to trunk [4241]
git-svn-id: http://code.djangoproject.com/svn/django/branches/per-object-permissions@4242 bcc190cf-cafb-0310-a4f2-bffc1f526a37
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/templates/admin/base.html5
-rw-r--r--django/contrib/admin/templates/admin/change_form.html2
-rw-r--r--django/contrib/admin/templates/admin/change_list.html2
-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/csrf/middleware.py2
-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/core/handlers/base.py11
-rw-r--r--django/core/handlers/wsgi.py9
-rw-r--r--django/core/servers/fastcgi.py2
-rw-r--r--django/db/backends/postgresql/base.py2
-rw-r--r--django/db/models/fields/__init__.py105
-rw-r--r--django/db/models/fields/generic.py4
-rw-r--r--django/db/models/fields/related.py95
-rw-r--r--django/db/models/manipulators.py8
-rw-r--r--django/forms/__init__.py1009
-rw-r--r--django/http/__init__.py2
-rw-r--r--django/newforms/__init__.py15
-rw-r--r--django/newforms/extras/__init__.py1
-rw-r--r--django/newforms/extras/widgets.py59
-rw-r--r--django/newforms/fields.py158
-rw-r--r--django/newforms/forms.py204
-rw-r--r--django/newforms/models.py38
-rw-r--r--django/newforms/util.py15
-rw-r--r--django/newforms/widgets.py116
-rw-r--r--django/oldforms/__init__.py1008
-rw-r--r--django/template/__init__.py6
-rw-r--r--django/utils/dateformat.py4
-rw-r--r--django/utils/text.py31
-rw-r--r--django/views/generic/create_update.py6
-rw-r--r--django/views/generic/list_detail.py2
44 files changed, 1937 insertions, 1318 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/templates/admin/base.html b/django/contrib/admin/templates/admin/base.html
index b63604b268..d3e8c96b91 100644
--- a/django/contrib/admin/templates/admin/base.html
+++ b/django/contrib/admin/templates/admin/base.html
@@ -38,7 +38,10 @@
<div id="content" class="{% block coltype %}colM{% endblock %}">
{% block pretitle %}{% endblock %}
{% block content_title %}{% if title %}<h1>{{ title|escape }}</h1>{% endif %}{% endblock %}
- {% block content %}{{ content }}{% endblock %}
+ {% block content %}
+ {% block object-tools %}{% endblock %}
+ {{ content }}
+ {% endblock %}
{% block sidebar %}{% endblock %}
<br class="clear" />
</div>
diff --git a/django/contrib/admin/templates/admin/change_form.html b/django/contrib/admin/templates/admin/change_form.html
index 02d05f9ad5..71a4df03e1 100644
--- a/django/contrib/admin/templates/admin/change_form.html
+++ b/django/contrib/admin/templates/admin/change_form.html
@@ -16,12 +16,14 @@
</div>
{% endif %}{% endblock %}
{% block content %}<div id="content-main">
+{% block object-tools %}
{% if change %}{% if not is_popup %}
<ul class="object-tools"><li><a href="history/" class="historylink">{% trans "History" %}</a></li>
{% if object_has_row_level_permissions and has_row_level_permissions %}<li><a href="row_level_permissions/" class="rowlevelpermissions">{% trans "Edit Row Level Permissions" %}</a></li>{% endif %}
{% if has_absolute_url %}<li><a href="../../../r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%}
</ul>
{% endif %}{% endif %}
+{% endblock %}
<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% block form_top %}{% endblock %}
<div>
{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
diff --git a/django/contrib/admin/templates/admin/change_list.html b/django/contrib/admin/templates/admin/change_list.html
index bd2304bd52..f50a73c934 100644
--- a/django/contrib/admin/templates/admin/change_list.html
+++ b/django/contrib/admin/templates/admin/change_list.html
@@ -7,9 +7,11 @@
{% block coltype %}flex{% endblock %}
{% block content %}
<div id="content-main">
+{% block object-tools %}
{% if has_add_permission %}
<ul class="object-tools"><li><a href="add/{% if is_popup %}?_popup=1{% endif %}" class="addlink">{% blocktrans with cl.opts.verbose_name|escape as name %}Add {{ name }}{% endblocktrans %}</a></li></ul>
{% endif %}
+{% endblock %}
<div class="module{% if cl.has_filters %} filtered{% endif %}" id="changelist">
{% block search %}{% search_form cl %}{% endblock %}
{% block date_hierarchy %}{% date_hierarchy cl %}{% endblock %}
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 06e469e5d2..52bf3bcde8 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 e0cfb169e0..aa8b35bb96 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
@@ -227,7 +227,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()):
@@ -299,7 +299,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,
@@ -318,13 +318,13 @@ 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
try:
manipulator = model.ChangeManipulator(object_id)
except ObjectDoesNotExist:
- raise Http404
+ raise Http404('%s object with primary key %r does not exist' % (model_name, escape(object_id)))
if not request.user.has_perm(app_label + '.' + opts.get_change_permission(), object=manipulator.original_object):
raise PermissionDenied
@@ -332,8 +332,6 @@ def change_stage(request, app_label, model_name, object_id):
if request.POST and request.POST.has_key("_saveasnew"):
return add_stage(request, app_label, model_name, form_url='../../add/')
-
-
if request.POST:
new_data = request.POST.copy()
@@ -400,7 +398,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 = []
@@ -519,7 +517,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
obj = get_object_or_404(model, pk=object_id)
@@ -558,7 +556,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.
@@ -788,7 +786,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.contains_permission(app_label + '.' + model._meta.get_change_permission(), model):
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/csrf/middleware.py b/django/contrib/csrf/middleware.py
index f6f78867dc..93a9484ca6 100644
--- a/django/contrib/csrf/middleware.py
+++ b/django/contrib/csrf/middleware.py
@@ -11,7 +11,7 @@ import md5
import re
import itertools
-_ERROR_MSG = "<h1>403 Forbidden</h1><p>Cross Site Request Forgery detected. Request aborted.</p>"
+_ERROR_MSG = '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><body><h1>403 Forbidden</h1><p>Cross Site Request Forgery detected. Request aborted.</p></body></html>'
_POST_FORM_RE = \
re.compile(r'(<form\W[^>]*\bmethod=(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE)
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/core/handlers/base.py b/django/core/handlers/base.py
index c1403ea4fa..ca48b301d4 100644
--- a/django/core/handlers/base.py
+++ b/django/core/handlers/base.py
@@ -60,7 +60,10 @@ class BaseHandler(object):
if response:
return response
- resolver = urlresolvers.RegexURLResolver(r'^/', settings.ROOT_URLCONF)
+ # Get urlconf from request object, if available. Otherwise use default.
+ urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF)
+
+ resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
try:
callback, callback_args, callback_kwargs = resolver.resolve(request.path)
@@ -84,7 +87,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 7dc1a4a5eb..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,8 +157,11 @@ class WSGIRequest(http.HttpRequest):
return self._raw_post_data
except AttributeError:
buf = StringIO()
- # CONTENT_LENGTH might be absent if POST doesn't have content at all (lighttpd)
- content_length = int(self.environ.get('CONTENT_LENGTH', 0))
+ 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/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py
index 1e48e9c3fa..e44bc0b560 100644
--- a/django/db/backends/postgresql/base.py
+++ b/django/db/backends/postgresql/base.py
@@ -118,7 +118,7 @@ def get_pk_default_value():
try:
Database.register_type(Database.new_type((1082,), "DATE", util.typecast_date))
except AttributeError:
- raise Exception, "You appear to be using psycopg version 2, which isn't supported yet, because it's still in beta. Use psycopg version 1 instead: http://initd.org/projects/psycopg1"
+ raise Exception, "You appear to be using psycopg version 2. Set your DATABASE_ENGINE to 'postgresql_psycopg2' instead of 'postgresql'."
Database.register_type(Database.new_type((1083,1266), "TIME", util.typecast_time))
Database.register_type(Database.new_type((1114,1184), "TIMESTAMP", util.typecast_timestamp))
Database.register_type(Database.new_type((16,), "BOOLEAN", util.typecast_boolean))
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index bc1eaf0a6a..fe317ac24f 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -2,7 +2,8 @@ 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 import newforms as forms
from django.core.exceptions import ObjectDoesNotExist
from django.utils.functional import curry
from django.utils.itercompat import tee
@@ -206,10 +207,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:
@@ -218,7 +219,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.
@@ -333,6 +334,11 @@ class Field(object):
return self._choices
choices = property(_get_choices)
+ def formfield(self):
+ "Returns a django.newforms.Field instance for this database Field."
+ # TODO: This is just a temporary default during development.
+ return forms.CharField(required=not self.blank, label=capfirst(self.verbose_name))
+
class AutoField(Field):
empty_strings_allowed = False
def __init__(self, *args, **kwargs):
@@ -354,7 +360,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
@@ -369,6 +375,9 @@ class AutoField(Field):
super(AutoField, self).contribute_to_class(cls, name)
cls._meta.has_auto_field = True
+ def formfield(self):
+ return None
+
class BooleanField(Field):
def __init__(self, *args, **kwargs):
kwargs['blank'] = True
@@ -381,11 +390,14 @@ 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]
+
+ def formfield(self):
+ return forms.BooleanField(required=not self.blank, label=capfirst(self.verbose_name))
class CharField(Field):
def get_manipulator_field_objs(self):
- return [forms.TextField]
+ return [oldforms.TextField]
def to_python(self, value):
if isinstance(value, basestring):
@@ -397,10 +409,13 @@ class CharField(Field):
raise validators.ValidationError, gettext_lazy("This field cannot be null.")
return str(value)
+ def formfield(self):
+ return forms.CharField(max_length=self.maxlength, required=not self.blank, label=capfirst(self.verbose_name))
+
# 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
@@ -457,19 +472,20 @@ class DateField(Field):
def get_db_prep_save(self, value):
# Casts dates into string format for entry into database.
- if isinstance(value, datetime.datetime):
- value = value.date().strftime('%Y-%m-%d')
- elif isinstance(value, datetime.date):
+ if value is not None:
value = value.strftime('%Y-%m-%d')
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)
return {self.attname: (val is not None and val.strftime("%Y-%m-%d") or '')}
+ def formfield(self):
+ return forms.DateField(required=not self.blank, label=capfirst(self.verbose_name))
+
class DateTimeField(DateField):
def to_python(self, value):
if isinstance(value, datetime.datetime):
@@ -489,19 +505,12 @@ 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 will throw a warning if microseconds are given, because it
# doesn't support microseconds.
if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'):
value = value.replace(microsecond=0)
value = str(value)
- elif isinstance(value, datetime.date):
- # MySQL will throw a warning if microseconds are given, because it
- # doesn't support microseconds.
- if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'):
- value = datetime.datetime(value.year, value.month, value.day, microsecond=0)
- value = str(value)
-
return Field.get_db_prep_save(self, value)
def get_db_prep_lookup(self, lookup_type, value):
@@ -512,7 +521,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']
@@ -535,6 +544,9 @@ class DateTimeField(DateField):
return {date_field: (val is not None and val.strftime("%Y-%m-%d") or ''),
time_field: (val is not None and val.strftime("%H:%M:%S") or '')}
+ def formfield(self):
+ return forms.DateTimeField(required=not self.blank, label=capfirst(self.verbose_name))
+
class EmailField(CharField):
def __init__(self, *args, **kwargs):
kwargs['maxlength'] = 75
@@ -544,11 +556,14 @@ 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)
+ def formfield(self):
+ return forms.EmailField(required=not self.blank, label=capfirst(self.verbose_name))
+
class FileField(Field):
def __init__(self, verbose_name=None, name=None, upload_to='', **kwargs):
self.upload_to = upload_to
@@ -608,7 +623,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]
@@ -636,7 +651,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
@@ -645,7 +660,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):
@@ -653,7 +668,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)
@@ -679,7 +694,10 @@ class ImageField(FileField):
class IntegerField(Field):
empty_strings_allowed = False
def get_manipulator_field_objs(self):
- return [forms.IntegerField]
+ return [oldforms.IntegerField]
+
+ def formfield(self):
+ return forms.IntegerField(required=not self.blank, label=capfirst(self.verbose_name))
class IPAddressField(Field):
def __init__(self, *args, **kwargs):
@@ -687,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)
@@ -698,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):
@@ -725,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
@@ -769,24 +787,31 @@ 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)
return {self.attname: (val is not None and val.strftime("%H:%M:%S") or '')}
+ def formfield(self):
+ return forms.TimeField(required=not self.blank, label=capfirst(self.verbose_name))
+
class URLField(Field):
def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs):
if verify_exists:
kwargs.setdefault('validator_list', []).append(validators.isExistingURL)
+ self.verify_exists = verify_exists
Field.__init__(self, verbose_name, name, **kwargs)
def get_manipulator_field_objs(self):
- return [forms.URLField]
+ return [oldforms.URLField]
+
+ def formfield(self):
+ return forms.URLField(required=not self.blank, verify_exists=self.verify_exists, label=capfirst(self.verbose_name))
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):
@@ -797,7 +822,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
@@ -810,4 +835,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 bd9262d55a..797ef05be1 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
@@ -256,8 +256,7 @@ class ForeignRelatedObjectsDescriptor(object):
# Otherwise, just move the named objects into the set.
if self.related.field.null:
manager.clear()
- for obj in value:
- manager.add(obj)
+ manager.add(*value)
def create_many_related_manager(superclass):
"""Creates a manager that subclasses 'superclass' (which is a Manager)
@@ -318,25 +317,31 @@ def create_many_related_manager(superclass):
# *objs - objects to add
from django.db import connection
- # Add the newly created or already existing objects to the join table.
- # First find out which items are already added, to avoid adding them twice
- new_ids = set([obj._get_pk_val() for obj in objs])
- cursor = connection.cursor()
- cursor.execute("SELECT %s FROM %s WHERE %s = %%s AND %s IN (%s)" % \
- (target_col_name, self.join_table, source_col_name,
- target_col_name, ",".join(['%s'] * len(new_ids))),
- [self._pk_val] + list(new_ids))
- if cursor.rowcount is not None and cursor.rowcount != 0:
- existing_ids = set([row[0] for row in cursor.fetchmany(cursor.rowcount)])
- else:
- existing_ids = set()
+ # If there aren't any objects, there is nothing to do.
+ if objs:
+ # Check that all the objects are of the right type
+ for obj in objs:
+ if not isinstance(obj, self.model):
+ raise ValueError, "objects to add() must be %s instances" % self.model._meta.object_name
+ # Add the newly created or already existing objects to the join table.
+ # First find out which items are already added, to avoid adding them twice
+ new_ids = set([obj._get_pk_val() for obj in objs])
+ cursor = connection.cursor()
+ cursor.execute("SELECT %s FROM %s WHERE %s = %%s AND %s IN (%s)" % \
+ (target_col_name, self.join_table, source_col_name,
+ target_col_name, ",".join(['%s'] * len(new_ids))),
+ [self._pk_val] + list(new_ids))
+ if cursor.rowcount is not None and cursor.rowcount != 0:
+ existing_ids = set([row[0] for row in cursor.fetchmany(cursor.rowcount)])
+ else:
+ existing_ids = set()
- # Add the ones that aren't there already
- for obj_id in (new_ids - existing_ids):
- cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
- (self.join_table, source_col_name, target_col_name),
- [self._pk_val, obj_id])
- transaction.commit_unless_managed()
+ # Add the ones that aren't there already
+ for obj_id in (new_ids - existing_ids):
+ cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
+ (self.join_table, source_col_name, target_col_name),
+ [self._pk_val, obj_id])
+ transaction.commit_unless_managed()
def _remove_items(self, source_col_name, target_col_name, *objs):
# source_col_name: the PK colname in join_table for the source object
@@ -344,16 +349,20 @@ def create_many_related_manager(superclass):
# *objs - objects to remove
from django.db import connection
- for obj in objs:
- if not isinstance(obj, self.model):
- raise ValueError, "objects to remove() must be %s instances" % self.model._meta.object_name
- # Remove the specified objects from the join table
- cursor = connection.cursor()
- for obj in objs:
- cursor.execute("DELETE FROM %s WHERE %s = %%s AND %s = %%s" % \
- (self.join_table, source_col_name, target_col_name),
- [self._pk_val, obj._get_pk_val()])
- transaction.commit_unless_managed()
+ # If there aren't any objects, there is nothing to do.
+ if objs:
+ # Check that all the objects are of the right type
+ for obj in objs:
+ if not isinstance(obj, self.model):
+ raise ValueError, "objects to remove() must be %s instances" % self.model._meta.object_name
+ # Remove the specified objects from the join table
+ old_ids = set([obj._get_pk_val() for obj in objs])
+ cursor = connection.cursor()
+ cursor.execute("DELETE FROM %s WHERE %s = %%s AND %s IN (%s)" % \
+ (self.join_table, source_col_name,
+ target_col_name, ",".join(['%s'] * len(old_ids))),
+ [self._pk_val] + list(old_ids))
+ transaction.commit_unless_managed()
def _clear_items(self, source_col_name):
# source_col_name: the PK colname in join_table for the source object
@@ -405,8 +414,7 @@ class ManyRelatedObjectsDescriptor(object):
manager = self.__get__(instance)
manager.clear()
- for obj in value:
- manager.add(obj)
+ manager.add(*value)
class ReverseManyRelatedObjectsDescriptor(object):
# This class provides the functionality that makes the related-object
@@ -447,8 +455,7 @@ class ReverseManyRelatedObjectsDescriptor(object):
manager = self.__get__(instance)
manager.clear()
- for obj in value:
- manager.add(obj)
+ manager.add(*value)
class ForeignKey(RelatedField, Field):
empty_strings_allowed = False
@@ -493,13 +500,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 +515,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 +588,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 +629,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 866e12781c..e9dfa7037c 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.
@@ -313,7 +313,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/forms/__init__.py b/django/forms/__init__.py
index 0b9ac05edb..68d3d245a2 100644
--- a/django/forms/__init__.py
+++ b/django/forms/__init__.py
@@ -1,1008 +1 @@
-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
+from django.oldforms import *
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/newforms/__init__.py b/django/newforms/__init__.py
index 2a472d7b39..0d9c68f9e0 100644
--- a/django/newforms/__init__.py
+++ b/django/newforms/__init__.py
@@ -13,16 +13,5 @@ TODO:
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 forms import *
+from models import *
diff --git a/django/newforms/extras/__init__.py b/django/newforms/extras/__init__.py
new file mode 100644
index 0000000000..a7f6a9b3f6
--- /dev/null
+++ b/django/newforms/extras/__init__.py
@@ -0,0 +1 @@
+from widgets import *
diff --git a/django/newforms/extras/widgets.py b/django/newforms/extras/widgets.py
new file mode 100644
index 0000000000..1011934fb8
--- /dev/null
+++ b/django/newforms/extras/widgets.py
@@ -0,0 +1,59 @@
+"""
+Extra HTML Widget classes
+"""
+
+from django.newforms.widgets import Widget, Select
+from django.utils.dates import MONTHS
+import datetime
+
+__all__ = ('SelectDateWidget',)
+
+class SelectDateWidget(Widget):
+ """
+ A Widget that splits date input into three <select> boxes.
+
+ This also serves as an example of a Widget that has more than one HTML
+ element and hence implements value_from_datadict.
+ """
+ month_field = '%s_month'
+ day_field = '%s_day'
+ year_field = '%s_year'
+
+ def __init__(self, attrs=None, years=None):
+ # years is an optional list/tuple of years to use in the "year" select box.
+ self.attrs = attrs or {}
+ if years:
+ self.years = years
+ else:
+ this_year = datetime.date.today().year
+ self.years = range(this_year, this_year+10)
+
+ def render(self, name, value, attrs=None):
+ try:
+ value = datetime.date(*map(int, value.split('-')))
+ year_val, month_val, day_val = value.year, value.month, value.day
+ except (AttributeError, TypeError, ValueError):
+ year_val = month_val = day_val = None
+
+ output = []
+
+ month_choices = MONTHS.items()
+ month_choices.sort()
+ select_html = Select(choices=month_choices).render(self.month_field % name, month_val)
+ output.append(select_html)
+
+ day_choices = [(i, i) for i in range(1, 32)]
+ select_html = Select(choices=day_choices).render(self.day_field % name, day_val)
+ output.append(select_html)
+
+ year_choices = [(i, i) for i in self.years]
+ select_html = Select(choices=year_choices).render(self.year_field % name, year_val)
+ output.append(select_html)
+
+ return u'\n'.join(output)
+
+ def value_from_datadict(self, data, name):
+ y, m, d = data.get(self.year_field % name), data.get(self.month_field % name), data.get(self.day_field % name)
+ if y and m and d:
+ return '%s-%s-%s' % (y, m, d)
+ return None
diff --git a/django/newforms/fields.py b/django/newforms/fields.py
index 40fc18bd3e..5c6d46ddac 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',
@@ -31,11 +33,19 @@ class Field(object):
# Tracks each time a Field instance is created. Used to retain order.
creation_counter = 0
- def __init__(self, required=True, widget=None):
- self.required = required
+ def __init__(self, required=True, widget=None, label=None):
+ if label is not None:
+ label = smart_unicode(label)
+ 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.
@@ -50,36 +60,62 @@ 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."
Field.clean(self, value)
- if value in EMPTY_VALUES: value = u''
+ if value in EMPTY_VALUES:
+ value = u''
+ if not self.required:
+ return value
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 __init__(self, max_value=None, min_value=None, required=True, widget=None, label=None):
+ self.max_value, self.min_value = max_value, min_value
+ Field.__init__(self, required, widget, label)
+
def clean(self, value):
"""
Validates that int() can be called on the input. Returns the result
of int().
"""
super(IntegerField, self).clean(value)
+ if not self.required and value in EMPTY_VALUES:
+ return u''
try:
- return int(value)
+ value = int(value)
except (ValueError, TypeError):
- raise ValidationError(u'Enter a whole number.')
+ raise ValidationError(gettext(u'Enter a whole number.'))
+ if self.max_value is not None and value > self.max_value:
+ raise ValidationError(gettext(u'Ensure this value is less than or equal to %s.') % self.max_value)
+ if self.min_value is not None and value < self.min_value:
+ raise ValidationError(gettext(u'Ensure this value is greater than or equal to %s.') % self.min_value)
+ return value
DEFAULT_DATE_INPUT_FORMATS = (
'%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
@@ -90,8 +126,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):
@@ -111,7 +147,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'
@@ -126,8 +189,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):
@@ -147,20 +210,22 @@ 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, max_length=None, min_length=None, 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.max_length, self.min_length = max_length, min_length
+ self.error_message = error_message or gettext(u'Enter a valid value.')
def clean(self, value):
"""
@@ -170,6 +235,12 @@ 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 self.max_length is not None and len(value) > 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(gettext(u'Ensure this value has at least %d characters.') % self.min_length)
if not self.regex.search(value):
raise ValidationError(self.error_message)
return value
@@ -180,8 +251,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, max_length=None, min_length=None, required=True, widget=None, label=None):
+ RegexField.__init__(self, email_re, max_length, min_length, gettext(u'Enter a valid e-mail address.'), required, widget, label)
url_re = re.compile(
r'^https?://' # http:// or https://
@@ -197,9 +268,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, max_length=None, min_length=None, 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, max_length, min_length, gettext(u'Enter a valid URL.'), required, widget, label)
self.verify_exists = verify_exists
self.user_agent = validator_user_agent
@@ -219,9 +290,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):
@@ -233,10 +304,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):
@@ -246,37 +317,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 b8264fb691..201cce3868 100644
--- a/django/newforms/forms.py
+++ b/django/newforms/forms.py
@@ -2,10 +2,13 @@
Form classes
"""
-from django.utils.datastructures import SortedDict
+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
+
+__all__ = ('BaseForm', 'Form')
NON_FIELD_ERRORS = '__all__'
@@ -31,17 +34,20 @@ class DeclarativeFieldsMetaclass(type):
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):
@@ -56,60 +62,76 @@ 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)
+ label = bf.label and bf.label_tag(escape(bf.label + ':')) or ''
+ output.append(normal_row % {'errors': bf_errors, 'label': 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>%(errors)s%(field)s</td></tr>', u'<tr><td colspan="2">%s</td></tr>', '</td></tr>', False)
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):
"""
@@ -117,8 +139,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
@@ -138,40 +166,59 @@ 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)
+ if self.field.label is None:
+ self.label = pretty_name(name)
+ else:
+ self.label = self.field.label
- 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):
"""
@@ -183,15 +230,44 @@ 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."
+ return self.field.widget.value_from_datadict(self.form.data, self.html_name)
+ 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..6b111d7ee1
--- /dev/null
+++ b/django/newforms/models.py
@@ -0,0 +1,38 @@
+"""
+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 create(self, save=True):
+ "Creates and returns model instance according to self.clean_data."
+ if self.errors:
+ raise ValueError("The %s could not be created because the data didn't validate." % self._model._meta.object_name)
+ obj = self._model(**self.clean_data)
+ if save:
+ obj.save()
+ return obj
+
+def form_for_model(model, form=None):
+ """
+ Returns a Form class for the given Django model class.
+
+ Provide 'form' if you want to use a custom BaseForm subclass.
+ """
+ opts = model._meta
+ field_list = []
+ for f in opts.fields + opts.many_to_many:
+ formfield = f.formfield()
+ if formfield:
+ field_list.append((f.name, formfield))
+ fields = SortedDictFromList(field_list)
+ form = form or BaseForm
+ return type(opts.object_name + 'Form', (form,), {'fields': fields, '_model': model, 'create': create})
+
+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..996e353775 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
@@ -22,25 +23,54 @@ except NameError:
flatatt = lambda attrs: u''.join([u' %s="%s"' % (k, escape(v)) for k, v in attrs.items()])
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 {}
- def render(self, name, value):
+ def render(self, name, value, attrs=None):
+ """
+ Returns this Widget rendered as HTML, as a Unicode string.
+
+ The 'value' given is not guaranteed to be valid input, so subclass
+ implementations should program defensively.
+ """
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 +85,7 @@ class PasswordInput(Input):
class HiddenInput(Input):
input_type = 'hidden'
+ is_hidden = True
class FileInput(Input):
input_type = 'file'
@@ -67,9 +98,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):
@@ -91,7 +135,6 @@ class Select(Widget):
return u'\n'.join(output)
class SelectMultiple(Widget):
- requires_data_list = True
def __init__(self, attrs=None, choices=()):
# choices can be any iterable
self.attrs = attrs or {}
@@ -109,36 +152,48 @@ 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 __getitem__(self, idx):
+ choice = self.choices[idx] # Let the IndexError propogate
+ return RadioInput(self.name, self.value, self.attrs.copy(), choice, idx)
- 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 +202,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/utils/dateformat.py b/django/utils/dateformat.py
index 0890a81a81..00eb9fe617 100644
--- a/django/utils/dateformat.py
+++ b/django/utils/dateformat.py
@@ -11,7 +11,7 @@ Usage:
>>>
"""
-from django.utils.dates import MONTHS, MONTHS_AP, WEEKDAYS
+from django.utils.dates import MONTHS, MONTHS_3, MONTHS_AP, WEEKDAYS
from django.utils.tzinfo import LocalTimezone
from calendar import isleap, monthrange
import re, time
@@ -147,7 +147,7 @@ class DateFormat(TimeFormat):
def M(self):
"Month, textual, 3 letters; e.g. 'Jan'"
- return MONTHS[self.data.month][0:3]
+ return MONTHS_3[self.data.month].title()
def n(self):
"Month without leading zeros; i.e. '1' to '12'"
diff --git a/django/utils/text.py b/django/utils/text.py
index 9e7bb3b6c4..217f42491b 100644
--- a/django/utils/text.py
+++ b/django/utils/text.py
@@ -8,17 +8,28 @@ capfirst = lambda x: x and x[0].upper() + x[1:]
def wrap(text, width):
"""
A word-wrap function that preserves existing line breaks and most spaces in
- the text. Expects that existing line breaks are posix newlines (\n).
- See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061
+ the text. Expects that existing line breaks are posix newlines.
"""
- return reduce(lambda line, word, width=width: '%s%s%s' %
- (line,
- ' \n'[(len(line[line.rfind('\n')+1:])
- + len(word.split('\n',1)[0]
- ) >= width)],
- word),
- text.split(' ')
- )
+ def _generator():
+ it = iter(text.split(' '))
+ word = it.next()
+ yield word
+ pos = len(word) - word.rfind('\n') - 1
+ for word in it:
+ if "\n" in word:
+ lines = word.splitlines()
+ else:
+ lines = (word,)
+ pos += len(lines[0]) + 1
+ if pos > width:
+ yield '\n'
+ pos = len(lines[-1])
+ else:
+ yield ' '
+ if len(lines) > 1:
+ pos = len(lines[-1])
+ yield word
+ return "".join(_generator())
def truncate_words(s, num):
"Truncates a string after a certain number of words."
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: