Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

per-object-permissions: Merged to trunk [4241]

  • Loading branch information...
commit 0cf7bc439129c66df8d64601e885f83b256b4f25 1 parent c4673e4
@adrianholovaty adrianholovaty authored
Showing with 4,055 additions and 1,534 deletions.
  1. +4 −0 AUTHORS
  2. +1 −1  django/conf/global_settings.py
  3. +1 −1  django/conf/project_template/settings.py
  4. +1 −1  django/conf/project_template/urls.py
  5. +4 −1 django/contrib/admin/templates/admin/base.html
  6. +2 −0  django/contrib/admin/templates/admin/change_form.html
  7. +2 −0  django/contrib/admin/templates/admin/change_list.html
  8. +1 −1  django/contrib/admin/templates/admin/search_form.html
  9. +2 −2 django/contrib/admin/views/auth.py
  10. +9 −11 django/contrib/admin/views/main.py
  11. +5 −5 django/contrib/admin/views/template.py
  12. +14 −14 django/contrib/auth/forms.py
  13. +4 −4 django/contrib/auth/views.py
  14. +16 −16 django/contrib/comments/views/comments.py
  15. +9 −2 django/contrib/contenttypes/management.py
  16. +1 −1  django/contrib/csrf/middleware.py
  17. 0  django/contrib/formtools/__init__.py
  18. +160 −0 django/contrib/formtools/preview.py
  19. +15 −0 django/contrib/formtools/templates/formtools/form.html
  20. +36 −0 django/contrib/formtools/templates/formtools/preview.html
  21. +1 −1  django/contrib/sitemaps/__init__.py
  22. +9 −2 django/core/handlers/base.py
  23. +6 −3 django/core/handlers/wsgi.py
  24. +2 −0  django/core/servers/fastcgi.py
  25. +1 −1  django/db/backends/postgresql/base.py
  26. +65 −40 django/db/models/fields/__init__.py
  27. +2 −2 django/db/models/fields/generic.py
  28. +51 −44 django/db/models/fields/related.py
  29. +4 −4 django/db/models/manipulators.py
  30. +1 −1,008 django/forms/__init__.py
  31. +1 −1  django/http/__init__.py
  32. +2 −13 django/newforms/__init__.py
  33. +1 −0  django/newforms/extras/__init__.py
  34. +59 −0 django/newforms/extras/widgets.py
  35. +119 −39 django/newforms/fields.py
  36. +141 −65 django/newforms/forms.py
  37. +38 −0 django/newforms/models.py
  38. +12 −3 django/newforms/util.py
  39. +100 −16 django/newforms/widgets.py
  40. +1,008 −0 django/oldforms/__init__.py
  41. +5 −1 django/template/__init__.py
  42. +2 −2 django/utils/dateformat.py
  43. +21 −10 django/utils/text.py
  44. +3 −3 django/views/generic/create_update.py
  45. +1 −1  django/views/generic/list_detail.py
  46. +17 −0 docs/add_ons.txt
  47. +100 −9 docs/contributing.txt
  48. +19 −18 docs/csrf.txt
  49. +3 −3 docs/db-api.txt
  50. +26 −14 docs/forms.txt
  51. +2 −2 docs/generic_views.txt
  52. +300 −0 docs/newforms.txt
  53. +2 −2 docs/redirects.txt
  54. +1 −1  docs/sessions.txt
  55. +2 −2 docs/settings.txt
  56. +1 −1  docs/testing.txt
  57. +11 −4 setup.py
  58. +9 −6 tests/modeltests/basic/models.py
  59. +12 −0 tests/modeltests/many_to_many/models.py
  60. 0  tests/modeltests/model_forms/__init__.py
  61. +113 −0 tests/modeltests/model_forms/models.py
  62. +1,459 −132 tests/regressiontests/forms/tests.py
  63. +14 −4 tests/regressiontests/templates/tests.py
  64. +22 −17 tests/runtests.py
View
4 AUTHORS
@@ -118,10 +118,12 @@ answer newbie questions, and generally made Django that much better:
Manuzhai
Petar Marić
mark@junklight.com
+ Yasushi Masuda <whosaysni@gmail.com>
mattycakes@gmail.com
Jason McBrayer <http://www.carcosa.net/jason/>
mccutchen@gmail.com
michael.mcewan@gmail.com
+ mitakummaa@gmail.com
mmarshall
Eric Moritz <http://eric.themoritzfamily.com/>
Robin Munn <http://www.geekforgod.com/>
@@ -151,6 +153,7 @@ answer newbie questions, and generally made Django that much better:
SmileyChris <smileychris@gmail.com>
sopel
Thomas Steinacher <tom@eggdrop.ch>
+ nowell strite
Radek Švarz <http://www.svarz.cz/translate/>
Swaroop C H <http://www.swaroopch.info>
Aaron Swartz <http://www.aaronsw.com/>
@@ -159,6 +162,7 @@ answer newbie questions, and generally made Django that much better:
Tom Insam
Joe Topjian <http://joe.terrarum.net/geek/code/python/django/>
Karen Tracey <graybark@bellsouth.net>
+ Makoto Tsuyuki <mtsuyuki@gmail.com>
Amit Upadhyay
Geert Vanderkelen
Milton Waddams
View
2  django/conf/global_settings.py
@@ -25,7 +25,7 @@
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:
View
2  django/conf/project_template/settings.py
@@ -17,7 +17,7 @@
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:
View
2  django/conf/project_template/urls.py
@@ -2,7 +2,7 @@
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')),
View
5 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>
View
2  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 %}
View
2  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 %}
View
2  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 %}
View
4 django/contrib/admin/views/auth.py
@@ -2,7 +2,7 @@
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,
View
20 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 @@ def index(request):
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 @@ def url_for_result(self, result):
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:
View
10 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):
View
28 django/contrib/auth/forms.py
@@ -3,16 +3,16 @@
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 @@ def save(self, new_data):
"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 @@ def __init__(self, request=None):
"""
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 @@ def get_user_id(self):
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 @@ def save(self, domain_override=None, email_template_name='registration/password_
}
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):
View
8 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)
View
32 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 @@ def get_validator_list(rating_num):
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 @@ def save(self, new_data):
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,
View
11 django/contrib/contenttypes/management.py
@@ -3,9 +3,9 @@
"""
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()
View
2  django/contrib/csrf/middleware.py
@@ -11,7 +11,7 @@
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)
View
0  django/contrib/formtools/__init__.py
No changes.
View
160 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__)
View
15 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 %}
View
36 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 %}
View
2  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))
View
11 django/core/handlers/base.py
@@ -60,7 +60,10 @@ def get_response(self, request):
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 @@ def get_response(self, request):
# 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:
View
9 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 @@ def _get_raw_post_data(self):
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()
View
2  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
View
2  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))
View
105 django/db/models/fields/__init__.py
@@ -2,7 +2,8 @@
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 @@ def prepare_field_objs_and_params(self, manipulator, name_prefix):
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 @@ def prepare_field_objs_and_params(self, manipulator, name_prefix):
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 @@ def _get_choices(self):
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 @@ def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=
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 @@ def contribute_to_class(self, cls, name):
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 @@ def to_python(self, value):
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 @@ def to_python(self, value):
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 @@ def get_follow(self, override=None):
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 @@ def to_python(self, value):
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 @@ def get_db_prep_lookup(self, lookup_type, value):
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 @@ def flatten_data(self,follow, obj = None):
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 @@ def get_internal_type(self):
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 @@ def delete_file(self, instance):
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 @@ def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=
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 @@ def __init__(self, verbose_name=None, name=None, max_digits=None, decimal_places
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 @@ def __init__(self, verbose_name=None, name=None, width_field=None, height_field=
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 @@ def save_file(self, new_data, new_object, original_object, change, rel):
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 @@ def __init__(self, *args, **kwargs):
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 @@ def __init__(self, *args, **kwargs):
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 @@ def __init__(self, *args, **kwargs):
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 @@ def get_db_prep_save(self, value):
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 @@ def get_internal_type(self):
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 @@ def get_internal_type(self):
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)]
View
4 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 @@ def __init__(self, to, **kwargs):
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)
View
95 django/db/models/fields/related.py
@@ -5,7 +5,7 @@
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 @@ def __set__(self, instance, value):
# 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 _add_items(self, source_col_name, target_col_name, *objs):
# *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 _remove_items(self, source_col_name, target_col_name, *objs):
# *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 @@ def __set__(self, instance, value):
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 @@ def __set__(self, instance, value):
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 @@ def prepare_field_objs_and_params(self, manipulator, name_prefix):
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 @@ def get_manipulator_field_objs(self):
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 @@ def prepare_field_objs_and_params(self, manipulator, name_prefix):
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 @@ def __init__(self, to, **kwargs):
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)
View
8 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 @@ def __get__(self, instance, model=None):
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 @@ def __init__(self, follow=None):
# 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}
View
1,009 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 = []