1
0
mirror of https://github.com/django/django.git synced 2025-07-05 18:29:11 +00:00

generic-auth: Merged to trunk [4254]

git-svn-id: http://code.djangoproject.com/svn/django/branches/generic-auth@4255 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Joseph Kocherhans 2006-12-29 00:25:26 +00:00
parent d87c354b65
commit a8ba8485de
47 changed files with 2920 additions and 1450 deletions

View File

@ -118,10 +118,12 @@ answer newbie questions, and generally made Django that much better:
Manuzhai Manuzhai
Petar Marić Petar Marić
mark@junklight.com mark@junklight.com
Yasushi Masuda <whosaysni@gmail.com>
mattycakes@gmail.com mattycakes@gmail.com
Jason McBrayer <http://www.carcosa.net/jason/> Jason McBrayer <http://www.carcosa.net/jason/>
mccutchen@gmail.com mccutchen@gmail.com
michael.mcewan@gmail.com michael.mcewan@gmail.com
mitakummaa@gmail.com
mmarshall mmarshall
Eric Moritz <http://eric.themoritzfamily.com/> Eric Moritz <http://eric.themoritzfamily.com/>
Robin Munn <http://www.geekforgod.com/> Robin Munn <http://www.geekforgod.com/>
@ -160,6 +162,7 @@ answer newbie questions, and generally made Django that much better:
Tom Insam Tom Insam
Joe Topjian <http://joe.terrarum.net/geek/code/python/django/> Joe Topjian <http://joe.terrarum.net/geek/code/python/django/>
Karen Tracey <graybark@bellsouth.net> Karen Tracey <graybark@bellsouth.net>
Makoto Tsuyuki <mtsuyuki@gmail.com>
Amit Upadhyay Amit Upadhyay
Geert Vanderkelen Geert Vanderkelen
Milton Waddams Milton Waddams

View File

@ -25,7 +25,7 @@ ADMINS = ()
INTERNAL_IPS = () INTERNAL_IPS = ()
# Local time zone for this installation. All choices can be found here: # 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' TIME_ZONE = 'America/Chicago'
# Language code for this installation. All choices can be found here: # Language code for this installation. All choices can be found here:

View File

@ -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. DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
# Local time zone for this installation. All choices can be found here: # 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' TIME_ZONE = 'America/Chicago'
# Language code for this installation. All choices can be found here: # Language code for this installation. All choices can be found here:

View File

@ -38,7 +38,10 @@
<div id="content" class="{% block coltype %}colM{% endblock %}"> <div id="content" class="{% block coltype %}colM{% endblock %}">
{% block pretitle %}{% endblock %} {% block pretitle %}{% endblock %}
{% block content_title %}{% if title %}<h1>{{ title|escape }}</h1>{% endif %}{% 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 %} {% block sidebar %}{% endblock %}
<br class="clear" /> <br class="clear" />
</div> </div>

View File

@ -16,11 +16,13 @@
</div> </div>
{% endif %}{% endblock %} {% endif %}{% endblock %}
{% block content %}<div id="content-main"> {% block content %}<div id="content-main">
{% block object-tools %}
{% if change %}{% if not is_popup %} {% if change %}{% if not is_popup %}
<ul class="object-tools"><li><a href="history/" class="historylink">{% trans "History" %}</a></li> <ul class="object-tools"><li><a href="history/" class="historylink">{% trans "History" %}</a></li>
{% if has_absolute_url %}<li><a href="../../../r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">{% trans "View on site" %}</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> </ul>
{% endif %}{% endif %} {% 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 %} <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> <div>
{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %} {% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}

View File

@ -7,9 +7,11 @@
{% block coltype %}flex{% endblock %} {% block coltype %}flex{% endblock %}
{% block content %} {% block content %}
<div id="content-main"> <div id="content-main">
{% block object-tools %}
{% if has_add_permission %} {% 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> <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 %} {% endif %}
{% endblock %}
<div class="module{% if cl.has_filters %} filtered{% endif %}" id="changelist"> <div class="module{% if cl.has_filters %} filtered{% endif %}" id="changelist">
{% block search %}{% search_form cl %}{% endblock %} {% block search %}{% search_form cl %}{% endblock %}
{% block date_hierarchy %}{% date_hierarchy cl %}{% endblock %} {% block date_hierarchy %}{% date_hierarchy cl %}{% endblock %}

View File

@ -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.forms import UserCreationForm
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied 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.shortcuts import render_to_response
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
@ -24,7 +24,7 @@ def user_add_stage(request):
return HttpResponseRedirect('../%s/' % new_user.id) return HttpResponseRedirect('../%s/' % new_user.id)
else: else:
errors = new_data = {} 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', { return render_to_response('admin/auth/user/add_form.html', {
'title': _('Add user'), 'title': _('Add user'),
'form': form, 'form': form,

View File

@ -1,4 +1,4 @@
from django import forms, template from django import oldforms, template
from django.conf import settings from django.conf import settings
from django.contrib.auth import has_permission from django.contrib.auth import has_permission
from django.contrib.admin.filterspecs import FilterSpec from django.contrib.admin.filterspecs import FilterSpec
@ -288,7 +288,7 @@ def add_stage(request, app_label, model_name, show_delete=False, form_url='', po
errors = {} errors = {}
# Populate the FormWrapper. # Populate the FormWrapper.
form = forms.FormWrapper(manipulator, new_data, errors) form = oldforms.FormWrapper(manipulator, new_data, errors)
c = template.RequestContext(request, { c = template.RequestContext(request, {
'title': _('Add %s') % opts.verbose_name, 'title': _('Add %s') % opts.verbose_name,
@ -379,7 +379,7 @@ def change_stage(request, app_label, model_name, object_id):
errors = {} errors = {}
# Populate the FormWrapper. # Populate the FormWrapper.
form = forms.FormWrapper(manipulator, new_data, errors) form = oldforms.FormWrapper(manipulator, new_data, errors)
form.original = manipulator.original_object form.original = manipulator.original_object
form.order_objects = [] form.order_objects = []

View File

@ -1,6 +1,6 @@
from django.contrib.admin.views.decorators import staff_member_required from django.contrib.admin.views.decorators import staff_member_required
from django.core import validators from django.core import validators
from django import template, forms from django import template, oldforms
from django.template import loader from django.template import loader
from django.shortcuts import render_to_response from django.shortcuts import render_to_response
from django.contrib.sites.models import Site 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.') request.user.message_set.create(message='The template is valid.')
return render_to_response('admin/template_validator.html', { return render_to_response('admin/template_validator.html', {
'title': 'Template validator', 'title': 'Template validator',
'form': forms.FormWrapper(manipulator, new_data, errors), 'form': oldforms.FormWrapper(manipulator, new_data, errors),
}, context_instance=template.RequestContext(request)) }, context_instance=template.RequestContext(request))
template_validator = staff_member_required(template_validator) template_validator = staff_member_required(template_validator)
class TemplateValidator(forms.Manipulator): class TemplateValidator(oldforms.Manipulator):
def __init__(self, settings_modules): def __init__(self, settings_modules):
self.settings_modules = settings_modules self.settings_modules = settings_modules
site_list = Site.objects.in_bulk(settings_modules.keys()).values() site_list = Site.objects.in_bulk(settings_modules.keys()).values()
self.fields = ( self.fields = (
forms.SelectField('site', is_required=True, choices=[(s.id, s.name) for s in site_list]), oldforms.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.LargeTextField('template', is_required=True, rows=25, validator_list=[self.isValidTemplate]),
) )
def isValidTemplate(self, field_data, all_data): def isValidTemplate(self, field_data, all_data):

View File

@ -3,16 +3,16 @@ from django.contrib.auth import authenticate
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.template import Context, loader from django.template import Context, loader
from django.core import validators 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." "A form that creates a user, with no privileges, from the given username and password."
def __init__(self): def __init__(self):
self.fields = ( 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]), validator_list=[validators.isAlphaNumeric, self.isValidUsername]),
forms.PasswordField(field_name='password1', length=30, maxlength=60, is_required=True), oldforms.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='password2', length=30, maxlength=60, is_required=True,
validator_list=[validators.AlwaysMatchesOtherField('password1', _("The two password fields didn't match."))]), validator_list=[validators.AlwaysMatchesOtherField('password1', _("The two password fields didn't match."))]),
) )
@ -27,7 +27,7 @@ class UserCreationForm(forms.Manipulator):
"Creates the user." "Creates the user."
return User.objects.create_user(new_data['username'], '', new_data['password1']) 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 Base class for authenticating users. Extend this to get a form that accepts
username/password logins. username/password logins.
@ -41,9 +41,9 @@ class AuthenticationForm(forms.Manipulator):
""" """
self.request = request self.request = request
self.fields = [ 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]), 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 self.user_cache = None
@ -68,11 +68,11 @@ class AuthenticationForm(forms.Manipulator):
def get_user(self): def get_user(self):
return self.user_cache return self.user_cache
class PasswordResetForm(forms.Manipulator): class PasswordResetForm(oldforms.Manipulator):
"A form that lets a user request a password reset" "A form that lets a user request a password reset"
def __init__(self): def __init__(self):
self.fields = ( 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]), 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]) 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." "A form that lets a user change his password."
def __init__(self, user): def __init__(self, user):
self.user = user self.user = user
self.fields = ( 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]), 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."))]), 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): def isValidOldPassword(self, new_data, all_data):

View File

@ -1,6 +1,6 @@
from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.forms import PasswordResetForm, PasswordChangeForm 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.shortcuts import render_to_response
from django.template import RequestContext from django.template import RequestContext
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
@ -26,7 +26,7 @@ def login(request, template_name='registration/login.html'):
errors = {} errors = {}
request.session.set_test_cookie() request.session.set_test_cookie()
return render_to_response(template_name, { 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, REDIRECT_FIELD_NAME: redirect_to,
'site_name': Site.objects.get_current().name, 'site_name': Site.objects.get_current().name,
}, context_instance=RequestContext(request)) }, context_instance=RequestContext(request))
@ -62,7 +62,7 @@ def password_reset(request, is_admin_site=False, template_name='registration/pas
else: else:
form.save(email_template_name=email_template_name) form.save(email_template_name=email_template_name)
return HttpResponseRedirect('%sdone/' % request.path) 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)) context_instance=RequestContext(request))
def password_reset_done(request, template_name='registration/password_reset_done.html'): 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: if not errors:
form.save(new_data) form.save(new_data)
return HttpResponseRedirect('%sdone/' % request.path) 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)) context_instance=RequestContext(request))
password_change = login_required(password_change) password_change = login_required(password_change)

View File

@ -1,5 +1,5 @@
from django.core import validators from django.core import validators
from django import forms from django import oldforms
from django.core.mail import mail_admins, mail_managers from django.core.mail import mail_admins, mail_managers
from django.http import Http404 from django.http import Http404
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
@ -28,37 +28,37 @@ class PublicCommentManipulator(AuthenticationForm):
else: else:
return [] return []
self.fields.extend([ 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]), 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, is_required=ratings_required and num_rating_choices > 0,
validator_list=get_validator_list(1), 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, is_required=ratings_required and num_rating_choices > 1,
validator_list=get_validator_list(2), 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, is_required=ratings_required and num_rating_choices > 2,
validator_list=get_validator_list(3), 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, is_required=ratings_required and num_rating_choices > 3,
validator_list=get_validator_list(4), 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, is_required=ratings_required and num_rating_choices > 4,
validator_list=get_validator_list(5), 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, is_required=ratings_required and num_rating_choices > 5,
validator_list=get_validator_list(6), 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, is_required=ratings_required and num_rating_choices > 6,
validator_list=get_validator_list(7), 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, is_required=ratings_required and num_rating_choices > 7,
validator_list=get_validator_list(8), 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()) mail_managers("Comment posted by sketchy user (%s)" % self.user_cache.username, c.get_as_text())
return c return c
class PublicFreeCommentManipulator(forms.Manipulator): class PublicFreeCommentManipulator(oldforms.Manipulator):
"Manipulator that handles public free (unregistered) comments" "Manipulator that handles public free (unregistered) comments"
def __init__(self): def __init__(self):
self.fields = ( 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]), 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]), validator_list=[self.hasNoProfanities]),
) )
@ -221,9 +221,9 @@ def post_comment(request):
from django.contrib.auth import login from django.contrib.auth import login
login(request, manipulator.get_user()) login(request, manipulator.get_user())
if errors or request.POST.has_key('preview'): 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): 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 self.rating_choices = rating_choices
def ratings(self): def ratings(self):
field_list = [self['rating%d' % (i+1)] for i in range(len(rating_choices))] 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) comment = errors and '' or manipulator.get_comment(new_data)
return render_to_response('comments/free_preview.html', { return render_to_response('comments/free_preview.html', {
'comment': comment, 'comment': comment,
'comment_form': forms.FormWrapper(manipulator, new_data, errors), 'comment_form': oldforms.FormWrapper(manipulator, new_data, errors),
'options': options, 'options': options,
'target': target, 'target': target,
'hash': security_hash, 'hash': security_hash,

View File

@ -11,7 +11,7 @@ import md5
import re import re
import itertools 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 = \ _POST_FORM_RE = \
re.compile(r'(<form\W[^>]*\bmethod=(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE) re.compile(r'(<form\W[^>]*\bmethod=(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE)

View File

@ -60,7 +60,10 @@ class BaseHandler(object):
if response: if response:
return 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: try:
callback, callback_args, callback_kwargs = resolver.resolve(request.path) callback, callback_args, callback_kwargs = resolver.resolve(request.path)

View File

@ -20,6 +20,38 @@ except ImportError:
# Import copy of _thread_local.py from Python 2.4 # Import copy of _thread_local.py from Python 2.4
from django.utils._threading_local import local from django.utils._threading_local import local
def smart_basestring(s, charset):
if isinstance(s, unicode):
return s.encode(charset)
return s
class UnicodeCursorWrapper(object):
"""
A thin wrapper around psycopg cursors that allows them to accept Unicode
strings as params.
This is necessary because psycopg doesn't apply any DB quoting to
parameters that are Unicode strings. If a param is Unicode, this will
convert it to a bytestring using DEFAULT_CHARSET before passing it to
psycopg.
"""
def __init__(self, cursor, charset):
self.cursor = cursor
self.charset = charset
def execute(self, sql, params=()):
return self.cursor.execute(sql, [smart_basestring(p, self.charset) for p in params])
def executemany(self, sql, param_list):
new_param_list = [[smart_basestring(p, self.charset) for p in params] for params in param_list]
return self.cursor.executemany(sql, new_param_list)
def __getattr__(self, attr):
if self.__dict__.has_key(attr):
return self.__dict__[attr]
else:
return getattr(self.cursor, attr)
class DatabaseWrapper(local): class DatabaseWrapper(local):
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.connection = None self.connection = None
@ -45,6 +77,7 @@ class DatabaseWrapper(local):
self.connection.set_isolation_level(1) # make transactions transparent to all cursors self.connection.set_isolation_level(1) # make transactions transparent to all cursors
cursor = self.connection.cursor() cursor = self.connection.cursor()
cursor.execute("SET TIME ZONE %s", [settings.TIME_ZONE]) cursor.execute("SET TIME ZONE %s", [settings.TIME_ZONE])
cursor = UnicodeCursorWrapper(cursor, settings.DEFAULT_CHARSET)
if settings.DEBUG: if settings.DEBUG:
return util.CursorDebugWrapper(cursor, self) return util.CursorDebugWrapper(cursor, self)
return cursor return cursor
@ -118,7 +151,7 @@ def get_pk_default_value():
try: try:
Database.register_type(Database.new_type((1082,), "DATE", util.typecast_date)) Database.register_type(Database.new_type((1082,), "DATE", util.typecast_date))
except AttributeError: 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((1083,1266), "TIME", util.typecast_time))
Database.register_type(Database.new_type((1114,1184), "TIMESTAMP", util.typecast_timestamp)) Database.register_type(Database.new_type((1114,1184), "TIMESTAMP", util.typecast_timestamp))
Database.register_type(Database.new_type((16,), "BOOLEAN", util.typecast_boolean)) Database.register_type(Database.new_type((16,), "BOOLEAN", util.typecast_boolean))

View File

@ -2,7 +2,8 @@ from django.db.models import signals
from django.dispatch import dispatcher from django.dispatch import dispatcher
from django.conf import settings from django.conf import settings
from django.core import validators from django.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.core.exceptions import ObjectDoesNotExist
from django.utils.functional import curry from django.utils.functional import curry
from django.utils.itercompat import tee from django.utils.itercompat import tee
@ -206,10 +207,10 @@ class Field(object):
if self.choices: if self.choices:
if self.radio_admin: if self.radio_admin:
field_objs = [forms.RadioSelectField] field_objs = [oldforms.RadioSelectField]
params['ul_class'] = get_ul_class(self.radio_admin) params['ul_class'] = get_ul_class(self.radio_admin)
else: else:
field_objs = [forms.SelectField] field_objs = [oldforms.SelectField]
params['choices'] = self.get_choices_default() params['choices'] = self.get_choices_default()
else: else:
@ -218,7 +219,7 @@ class Field(object):
def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True): 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. calculates the choices at runtime, not at compile time.
name_prefix is a prefix to prepend to the "field_name" argument. name_prefix is a prefix to prepend to the "field_name" argument.
@ -333,6 +334,15 @@ class Field(object):
return self._choices return self._choices
choices = property(_get_choices) choices = property(_get_choices)
def formfield(self, initial=None):
"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), initial=initial)
def value_from_object(self, obj):
"Returns the value of this field in the given model instance."
return getattr(obj, self.attname)
class AutoField(Field): class AutoField(Field):
empty_strings_allowed = False empty_strings_allowed = False
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -354,7 +364,7 @@ class AutoField(Field):
return Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel, follow) return Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel, follow)
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [forms.HiddenField] return [oldforms.HiddenField]
def get_manipulator_new_data(self, new_data, rel=False): def get_manipulator_new_data(self, new_data, rel=False):
# Never going to be called # Never going to be called
@ -369,6 +379,9 @@ class AutoField(Field):
super(AutoField, self).contribute_to_class(cls, name) super(AutoField, self).contribute_to_class(cls, name)
cls._meta.has_auto_field = True cls._meta.has_auto_field = True
def formfield(self, initial=None):
return None
class BooleanField(Field): class BooleanField(Field):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['blank'] = True kwargs['blank'] = True
@ -381,11 +394,14 @@ class BooleanField(Field):
raise validators.ValidationError, gettext("This value must be either True or False.") raise validators.ValidationError, gettext("This value must be either True or False.")
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [forms.CheckboxField] return [oldforms.CheckboxField]
def formfield(self, initial=None):
return forms.BooleanField(required=not self.blank, label=capfirst(self.verbose_name), initial=initial)
class CharField(Field): class CharField(Field):
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [forms.TextField] return [oldforms.TextField]
def to_python(self, value): def to_python(self, value):
if isinstance(value, basestring): if isinstance(value, basestring):
@ -397,10 +413,13 @@ class CharField(Field):
raise validators.ValidationError, gettext_lazy("This field cannot be null.") raise validators.ValidationError, gettext_lazy("This field cannot be null.")
return str(value) return str(value)
def formfield(self, initial=None):
return forms.CharField(max_length=self.maxlength, required=not self.blank, label=capfirst(self.verbose_name), initial=initial)
# TODO: Maybe move this into contrib, because it's specialized. # TODO: Maybe move this into contrib, because it's specialized.
class CommaSeparatedIntegerField(CharField): class CommaSeparatedIntegerField(CharField):
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [forms.CommaSeparatedIntegerField] return [oldforms.CommaSeparatedIntegerField]
class DateField(Field): class DateField(Field):
empty_strings_allowed = False empty_strings_allowed = False
@ -462,12 +481,15 @@ class DateField(Field):
return Field.get_db_prep_save(self, value) return Field.get_db_prep_save(self, value)
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [forms.DateField] return [oldforms.DateField]
def flatten_data(self, follow, obj = None): def flatten_data(self, follow, obj=None):
val = self._get_val_from_obj(obj) val = self._get_val_from_obj(obj)
return {self.attname: (val is not None and val.strftime("%Y-%m-%d") or '')} return {self.attname: (val is not None and val.strftime("%Y-%m-%d") or '')}
def formfield(self, initial=None):
return forms.DateField(required=not self.blank, label=capfirst(self.verbose_name), initial=initial)
class DateTimeField(DateField): class DateTimeField(DateField):
def to_python(self, value): def to_python(self, value):
if isinstance(value, datetime.datetime): if isinstance(value, datetime.datetime):
@ -503,7 +525,7 @@ class DateTimeField(DateField):
return Field.get_db_prep_lookup(self, lookup_type, value) return Field.get_db_prep_lookup(self, lookup_type, value)
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [forms.DateField, forms.TimeField] return [oldforms.DateField, oldforms.TimeField]
def get_manipulator_field_names(self, name_prefix): def get_manipulator_field_names(self, name_prefix):
return [name_prefix + self.name + '_date', name_prefix + self.name + '_time'] return [name_prefix + self.name + '_date', name_prefix + self.name + '_time']
@ -526,6 +548,9 @@ class DateTimeField(DateField):
return {date_field: (val is not None and val.strftime("%Y-%m-%d") or ''), 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 '')} time_field: (val is not None and val.strftime("%H:%M:%S") or '')}
def formfield(self, initial=None):
return forms.DateTimeField(required=not self.blank, label=capfirst(self.verbose_name), initial=initial)
class EmailField(CharField): class EmailField(CharField):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['maxlength'] = 75 kwargs['maxlength'] = 75
@ -535,11 +560,14 @@ class EmailField(CharField):
return "CharField" return "CharField"
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [forms.EmailField] return [oldforms.EmailField]
def validate(self, field_data, all_data): def validate(self, field_data, all_data):
validators.isValidEmail(field_data, all_data) validators.isValidEmail(field_data, all_data)
def formfield(self, initial=None):
return forms.EmailField(required=not self.blank, label=capfirst(self.verbose_name), initial=initial)
class FileField(Field): class FileField(Field):
def __init__(self, verbose_name=None, name=None, upload_to='', **kwargs): def __init__(self, verbose_name=None, name=None, upload_to='', **kwargs):
self.upload_to = upload_to self.upload_to = upload_to
@ -599,7 +627,7 @@ class FileField(Field):
os.remove(file_name) os.remove(file_name)
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [forms.FileUploadField, forms.HiddenField] return [oldforms.FileUploadField, oldforms.HiddenField]
def get_manipulator_field_names(self, name_prefix): def get_manipulator_field_names(self, name_prefix):
return [name_prefix + self.name + '_file', name_prefix + self.name] return [name_prefix + self.name + '_file', name_prefix + self.name]
@ -627,7 +655,7 @@ class FilePathField(Field):
Field.__init__(self, verbose_name, name, **kwargs) Field.__init__(self, verbose_name, name, **kwargs)
def get_manipulator_field_objs(self): 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): class FloatField(Field):
empty_strings_allowed = False empty_strings_allowed = False
@ -636,7 +664,7 @@ class FloatField(Field):
Field.__init__(self, verbose_name, name, **kwargs) Field.__init__(self, verbose_name, name, **kwargs)
def get_manipulator_field_objs(self): 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): class ImageField(FileField):
def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs): def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs):
@ -644,7 +672,7 @@ class ImageField(FileField):
FileField.__init__(self, verbose_name, name, **kwargs) FileField.__init__(self, verbose_name, name, **kwargs)
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [forms.ImageUploadField, forms.HiddenField] return [oldforms.ImageUploadField, oldforms.HiddenField]
def contribute_to_class(self, cls, name): def contribute_to_class(self, cls, name):
super(ImageField, self).contribute_to_class(cls, name) super(ImageField, self).contribute_to_class(cls, name)
@ -670,7 +698,10 @@ class ImageField(FileField):
class IntegerField(Field): class IntegerField(Field):
empty_strings_allowed = False empty_strings_allowed = False
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [forms.IntegerField] return [oldforms.IntegerField]
def formfield(self, initial=None):
return forms.IntegerField(required=not self.blank, label=capfirst(self.verbose_name), initial=initial)
class IPAddressField(Field): class IPAddressField(Field):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -678,7 +709,7 @@ class IPAddressField(Field):
Field.__init__(self, *args, **kwargs) Field.__init__(self, *args, **kwargs)
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [forms.IPAddressField] return [oldforms.IPAddressField]
def validate(self, field_data, all_data): def validate(self, field_data, all_data):
validators.isValidIPAddress4(field_data, None) validators.isValidIPAddress4(field_data, None)
@ -689,22 +720,22 @@ class NullBooleanField(Field):
Field.__init__(self, *args, **kwargs) Field.__init__(self, *args, **kwargs)
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [forms.NullBooleanField] return [oldforms.NullBooleanField]
class PhoneNumberField(IntegerField): class PhoneNumberField(IntegerField):
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [forms.PhoneNumberField] return [oldforms.PhoneNumberField]
def validate(self, field_data, all_data): def validate(self, field_data, all_data):
validators.isValidPhone(field_data, all_data) validators.isValidPhone(field_data, all_data)
class PositiveIntegerField(IntegerField): class PositiveIntegerField(IntegerField):
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [forms.PositiveIntegerField] return [oldforms.PositiveIntegerField]
class PositiveSmallIntegerField(IntegerField): class PositiveSmallIntegerField(IntegerField):
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [forms.PositiveSmallIntegerField] return [oldforms.PositiveSmallIntegerField]
class SlugField(Field): class SlugField(Field):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -716,15 +747,15 @@ class SlugField(Field):
Field.__init__(self, *args, **kwargs) Field.__init__(self, *args, **kwargs)
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [forms.TextField] return [oldforms.TextField]
class SmallIntegerField(IntegerField): class SmallIntegerField(IntegerField):
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [forms.SmallIntegerField] return [oldforms.SmallIntegerField]
class TextField(Field): class TextField(Field):
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [forms.LargeTextField] return [oldforms.LargeTextField]
class TimeField(Field): class TimeField(Field):
empty_strings_allowed = False empty_strings_allowed = False
@ -760,24 +791,31 @@ class TimeField(Field):
return Field.get_db_prep_save(self, value) return Field.get_db_prep_save(self, value)
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [forms.TimeField] return [oldforms.TimeField]
def flatten_data(self,follow, obj = None): def flatten_data(self,follow, obj = None):
val = self._get_val_from_obj(obj) val = self._get_val_from_obj(obj)
return {self.attname: (val is not None and val.strftime("%H:%M:%S") or '')} return {self.attname: (val is not None and val.strftime("%H:%M:%S") or '')}
def formfield(self, initial=None):
return forms.TimeField(required=not self.blank, label=capfirst(self.verbose_name), initial=initial)
class URLField(Field): class URLField(Field):
def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs): def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs):
if verify_exists: if verify_exists:
kwargs.setdefault('validator_list', []).append(validators.isExistingURL) kwargs.setdefault('validator_list', []).append(validators.isExistingURL)
self.verify_exists = verify_exists
Field.__init__(self, verbose_name, name, **kwargs) Field.__init__(self, verbose_name, name, **kwargs)
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [forms.URLField] return [oldforms.URLField]
def formfield(self, initial=None):
return forms.URLField(required=not self.blank, verify_exists=self.verify_exists, label=capfirst(self.verbose_name), initial=initial)
class USStateField(Field): class USStateField(Field):
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [forms.USStateField] return [oldforms.USStateField]
class XMLField(TextField): class XMLField(TextField):
def __init__(self, verbose_name=None, name=None, schema_path=None, **kwargs): def __init__(self, verbose_name=None, name=None, schema_path=None, **kwargs):
@ -788,7 +826,7 @@ class XMLField(TextField):
return "TextField" return "TextField"
def get_manipulator_field_objs(self): 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): class OrderingField(IntegerField):
empty_strings_allowed=False empty_strings_allowed=False
@ -801,4 +839,4 @@ class OrderingField(IntegerField):
return "IntegerField" return "IntegerField"
def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True): 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 File

@ -2,7 +2,7 @@
Classes allowing "generic" relations through ContentType and object-id fields. 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.core.exceptions import ObjectDoesNotExist
from django.db import backend from django.db import backend
from django.db.models import signals from django.db.models import signals
@ -98,7 +98,7 @@ class GenericRelation(RelatedField, Field):
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
choices = self.get_choices_default() 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): def get_choices_default(self):
return Field.get_choices(self, include_blank=False) return Field.get_choices(self, include_blank=False)

View File

@ -2,10 +2,12 @@ from django.db import backend, transaction
from django.db.models import signals, get_model from django.db.models import signals, get_model
from django.db.models.fields import AutoField, Field, IntegerField, get_ul_class from django.db.models.fields import AutoField, Field, IntegerField, get_ul_class
from django.db.models.related import RelatedObject from django.db.models.related import RelatedObject
from django.utils.text import capfirst
from django.utils.translation import gettext_lazy, string_concat, ngettext from django.utils.translation import gettext_lazy, string_concat, ngettext
from django.utils.functional import curry from django.utils.functional import curry
from django.core import validators from django.core import validators
from django import forms from django import oldforms
from django import newforms as forms
from django.dispatch import dispatcher from django.dispatch import dispatcher
# For Python 2.3 # For Python 2.3
@ -256,8 +258,7 @@ class ForeignRelatedObjectsDescriptor(object):
# Otherwise, just move the named objects into the set. # Otherwise, just move the named objects into the set.
if self.related.field.null: if self.related.field.null:
manager.clear() manager.clear()
for obj in value: manager.add(*value)
manager.add(obj)
def create_many_related_manager(superclass): def create_many_related_manager(superclass):
"""Creates a manager that subclasses 'superclass' (which is a Manager) """Creates a manager that subclasses 'superclass' (which is a Manager)
@ -318,25 +319,31 @@ def create_many_related_manager(superclass):
# *objs - objects to add # *objs - objects to add
from django.db import connection from django.db import connection
# Add the newly created or already existing objects to the join table. # If there aren't any objects, there is nothing to do.
# First find out which items are already added, to avoid adding them twice if objs:
new_ids = set([obj._get_pk_val() for obj in objs]) # Check that all the objects are of the right type
cursor = connection.cursor() for obj in objs:
cursor.execute("SELECT %s FROM %s WHERE %s = %%s AND %s IN (%s)" % \ if not isinstance(obj, self.model):
(target_col_name, self.join_table, source_col_name, raise ValueError, "objects to add() must be %s instances" % self.model._meta.object_name
target_col_name, ",".join(['%s'] * len(new_ids))), # Add the newly created or already existing objects to the join table.
[self._pk_val] + list(new_ids)) # First find out which items are already added, to avoid adding them twice
if cursor.rowcount is not None and cursor.rowcount != 0: new_ids = set([obj._get_pk_val() for obj in objs])
existing_ids = set([row[0] for row in cursor.fetchmany(cursor.rowcount)]) cursor = connection.cursor()
else: cursor.execute("SELECT %s FROM %s WHERE %s = %%s AND %s IN (%s)" % \
existing_ids = set() (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 # Add the ones that aren't there already
for obj_id in (new_ids - existing_ids): for obj_id in (new_ids - existing_ids):
cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \ cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
(self.join_table, source_col_name, target_col_name), (self.join_table, source_col_name, target_col_name),
[self._pk_val, obj_id]) [self._pk_val, obj_id])
transaction.commit_unless_managed() transaction.commit_unless_managed()
def _remove_items(self, source_col_name, target_col_name, *objs): def _remove_items(self, source_col_name, target_col_name, *objs):
# source_col_name: the PK colname in join_table for the source object # source_col_name: the PK colname in join_table for the source object
@ -344,16 +351,20 @@ def create_many_related_manager(superclass):
# *objs - objects to remove # *objs - objects to remove
from django.db import connection from django.db import connection
for obj in objs: # If there aren't any objects, there is nothing to do.
if not isinstance(obj, self.model): if objs:
raise ValueError, "objects to remove() must be %s instances" % self.model._meta.object_name # Check that all the objects are of the right type
# Remove the specified objects from the join table for obj in objs:
cursor = connection.cursor() if not isinstance(obj, self.model):
for obj in objs: raise ValueError, "objects to remove() must be %s instances" % self.model._meta.object_name
cursor.execute("DELETE FROM %s WHERE %s = %%s AND %s = %%s" % \ # Remove the specified objects from the join table
(self.join_table, source_col_name, target_col_name), old_ids = set([obj._get_pk_val() for obj in objs])
[self._pk_val, obj._get_pk_val()]) cursor = connection.cursor()
transaction.commit_unless_managed() 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): def _clear_items(self, source_col_name):
# source_col_name: the PK colname in join_table for the source object # source_col_name: the PK colname in join_table for the source object
@ -405,8 +416,7 @@ class ManyRelatedObjectsDescriptor(object):
manager = self.__get__(instance) manager = self.__get__(instance)
manager.clear() manager.clear()
for obj in value: manager.add(*value)
manager.add(obj)
class ReverseManyRelatedObjectsDescriptor(object): class ReverseManyRelatedObjectsDescriptor(object):
# This class provides the functionality that makes the related-object # This class provides the functionality that makes the related-object
@ -447,8 +457,7 @@ class ReverseManyRelatedObjectsDescriptor(object):
manager = self.__get__(instance) manager = self.__get__(instance)
manager.clear() manager.clear()
for obj in value: manager.add(*value)
manager.add(obj)
class ForeignKey(RelatedField, Field): class ForeignKey(RelatedField, Field):
empty_strings_allowed = False empty_strings_allowed = False
@ -493,13 +502,13 @@ class ForeignKey(RelatedField, Field):
params['validator_list'].append(curry(manipulator_valid_rel_key, self, manipulator)) params['validator_list'].append(curry(manipulator_valid_rel_key, self, manipulator))
else: else:
if self.radio_admin: if self.radio_admin:
field_objs = [forms.RadioSelectField] field_objs = [oldforms.RadioSelectField]
params['ul_class'] = get_ul_class(self.radio_admin) params['ul_class'] = get_ul_class(self.radio_admin)
else: else:
if self.null: if self.null:
field_objs = [forms.NullSelectField] field_objs = [oldforms.NullSelectField]
else: else:
field_objs = [forms.SelectField] field_objs = [oldforms.SelectField]
params['choices'] = self.get_choices_default() params['choices'] = self.get_choices_default()
return field_objs, params return field_objs, params
@ -508,7 +517,7 @@ class ForeignKey(RelatedField, Field):
if self.rel.raw_id_admin and not isinstance(rel_field, AutoField): if self.rel.raw_id_admin and not isinstance(rel_field, AutoField):
return rel_field.get_manipulator_field_objs() return rel_field.get_manipulator_field_objs()
else: else:
return [forms.IntegerField] return [oldforms.IntegerField]
def get_db_prep_save(self, value): def get_db_prep_save(self, value):
if value == '' or value == None: if value == '' or value == None:
@ -539,6 +548,9 @@ class ForeignKey(RelatedField, Field):
def contribute_to_related_class(self, cls, related): def contribute_to_related_class(self, cls, related):
setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related)) setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related))
def formfield(self, initial=None):
return forms.ChoiceField(choices=self.get_choices_default(), required=not self.blank, label=capfirst(self.verbose_name), initial=initial)
class OneToOneField(RelatedField, IntegerField): class OneToOneField(RelatedField, IntegerField):
def __init__(self, to, to_field=None, **kwargs): def __init__(self, to, to_field=None, **kwargs):
try: try:
@ -581,13 +593,13 @@ class OneToOneField(RelatedField, IntegerField):
params['validator_list'].append(curry(manipulator_valid_rel_key, self, manipulator)) params['validator_list'].append(curry(manipulator_valid_rel_key, self, manipulator))
else: else:
if self.radio_admin: if self.radio_admin:
field_objs = [forms.RadioSelectField] field_objs = [oldforms.RadioSelectField]
params['ul_class'] = get_ul_class(self.radio_admin) params['ul_class'] = get_ul_class(self.radio_admin)
else: else:
if self.null: if self.null:
field_objs = [forms.NullSelectField] field_objs = [oldforms.NullSelectField]
else: else:
field_objs = [forms.SelectField] field_objs = [oldforms.SelectField]
params['choices'] = self.get_choices_default() params['choices'] = self.get_choices_default()
return field_objs, params return field_objs, params
@ -600,6 +612,9 @@ class OneToOneField(RelatedField, IntegerField):
if not cls._meta.one_to_one_field: if not cls._meta.one_to_one_field:
cls._meta.one_to_one_field = self cls._meta.one_to_one_field = self
def formfield(self, initial=None):
return forms.ChoiceField(choices=self.get_choices_default(), required=not self.blank, label=capfirst(self.verbose_name), initial=initial)
class ManyToManyField(RelatedField, Field): class ManyToManyField(RelatedField, Field):
def __init__(self, to, **kwargs): def __init__(self, to, **kwargs):
kwargs['verbose_name'] = kwargs.get('verbose_name', None) kwargs['verbose_name'] = kwargs.get('verbose_name', None)
@ -622,10 +637,10 @@ class ManyToManyField(RelatedField, Field):
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
if self.rel.raw_id_admin: if self.rel.raw_id_admin:
return [forms.RawIdAdminField] return [oldforms.RawIdAdminField]
else: else:
choices = self.get_choices_default() 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): def get_choices_default(self):
return Field.get_choices(self, include_blank=False) return Field.get_choices(self, include_blank=False)
@ -706,6 +721,13 @@ class ManyToManyField(RelatedField, Field):
def set_attributes_from_rel(self): def set_attributes_from_rel(self):
pass pass
def value_from_object(self, obj):
"Returns the value of this field in the given model instance."
return getattr(obj, self.attname).all()
def formfield(self, initial=None):
return forms.MultipleChoiceField(choices=self.get_choices_default(), required=not self.blank, label=capfirst(self.verbose_name), initial=initial)
class ManyToOneRel(object): class ManyToOneRel(object):
def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None, def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None,
max_num_in_admin=None, num_extra_on_change=1, edit_inline=False, max_num_in_admin=None, num_extra_on_change=1, edit_inline=False,

View File

@ -1,5 +1,5 @@
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django import forms from django import oldforms
from django.core import validators from django.core import validators
from django.db.models.fields import FileField, AutoField from django.db.models.fields import FileField, AutoField
from django.dispatch import dispatcher from django.dispatch import dispatcher
@ -40,7 +40,7 @@ class ManipulatorDescriptor(object):
self.man._prepare(model) self.man._prepare(model)
return self.man return self.man
class AutomaticManipulator(forms.Manipulator): class AutomaticManipulator(oldforms.Manipulator):
def _prepare(cls, model): def _prepare(cls, model):
cls.model = model cls.model = model
cls.manager = model._default_manager cls.manager = model._default_manager
@ -76,7 +76,7 @@ class AutomaticManipulator(forms.Manipulator):
# Add field for ordering. # Add field for ordering.
if self.change and self.opts.get_ordered_objects(): 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): def save(self, new_data):
# TODO: big cleanup when core fields go -> use recursive manipulators. # TODO: big cleanup when core fields go -> use recursive manipulators.
@ -308,7 +308,7 @@ def manipulator_validator_unique_together(field_name_list, opts, self, field_dat
def manipulator_validator_unique_for_date(from_field, date_field, opts, lookup_type, self, field_data, all_data): 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 from django.db.models.fields.related import ManyToOneRel
date_str = all_data.get(date_field.get_manipulator_field_names('')[0], None) 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: if date_val is None:
return # Date was invalid. This will be caught by another validator. return # Date was invalid. This will be caught by another validator.
lookup_kwargs = {'%s__year' % date_field.name: date_val.year} lookup_kwargs = {'%s__year' % date_field.name: date_val.year}

File diff suppressed because it is too large Load Diff

View File

@ -13,5 +13,5 @@ TODO:
from util import ValidationError from util import ValidationError
from widgets import * from widgets import *
from fields import * from fields import *
from forms import Form from forms import *
from models import * from models import *

View File

@ -0,0 +1 @@
from widgets import *

View File

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

View File

@ -4,7 +4,7 @@ Field classes
from django.utils.translation import gettext from django.utils.translation import gettext
from util import ValidationError, smart_unicode from util import ValidationError, smart_unicode
from widgets import TextInput, CheckboxInput, Select, SelectMultiple from widgets import TextInput, PasswordInput, CheckboxInput, Select, SelectMultiple
import datetime import datetime
import re import re
import time import time
@ -12,6 +12,7 @@ import time
__all__ = ( __all__ = (
'Field', 'CharField', 'IntegerField', 'Field', 'CharField', 'IntegerField',
'DEFAULT_DATE_INPUT_FORMATS', 'DateField', 'DEFAULT_DATE_INPUT_FORMATS', 'DateField',
'DEFAULT_TIME_INPUT_FORMATS', 'TimeField',
'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField', 'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField',
'RegexField', 'EmailField', 'URLField', 'BooleanField', 'RegexField', 'EmailField', 'URLField', 'BooleanField',
'ChoiceField', 'MultipleChoiceField', 'ChoiceField', 'MultipleChoiceField',
@ -32,11 +33,30 @@ class Field(object):
# Tracks each time a Field instance is created. Used to retain order. # Tracks each time a Field instance is created. Used to retain order.
creation_counter = 0 creation_counter = 0
def __init__(self, required=True, widget=None): def __init__(self, required=True, widget=None, label=None, initial=None):
self.required = required # required -- Boolean that specifies whether the field is required.
# True by default.
# widget -- A Widget class, or instance of a Widget class, that should be
# used for this Field when displaying it. Each Field has a default
# Widget that it'll use if you don't specify this. In most cases,
# the default widget is TextInput.
# label -- A verbose name for this field, for use in displaying this field in
# a form. By default, Django will use a "pretty" version of the form
# field name, if the Field is part of a Form.
# initial -- A value to use in this Field's initial display. This value is
# *not* used as a fallback if data isn't given.
if label is not None:
label = smart_unicode(label)
self.required, self.label, self.initial = required, label, initial
widget = widget or self.widget widget = widget or self.widget
if isinstance(widget, type): if isinstance(widget, type):
widget = widget() 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 self.widget = widget
# Increase the creation counter, and save our local copy. # Increase the creation counter, and save our local copy.
@ -54,15 +74,26 @@ class Field(object):
raise ValidationError(gettext(u'This field is required.')) raise ValidationError(gettext(u'This field is required.'))
return value 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): class CharField(Field):
def __init__(self, max_length=None, min_length=None, required=True, widget=None): def __init__(self, max_length=None, min_length=None, required=True, widget=None, label=None, initial=None):
Field.__init__(self, required, widget)
self.max_length, self.min_length = max_length, min_length self.max_length, self.min_length = max_length, min_length
Field.__init__(self, required, widget, label, initial)
def clean(self, value): def clean(self, value):
"Validates max_length and min_length. Returns a Unicode object." "Validates max_length and min_length. Returns a Unicode object."
Field.clean(self, value) 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) value = smart_unicode(value)
if self.max_length is not None and len(value) > self.max_length: 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) raise ValidationError(gettext(u'Ensure this value has at most %d characters.') % self.max_length)
@ -70,7 +101,15 @@ class CharField(Field):
raise ValidationError(gettext(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 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): class IntegerField(Field):
def __init__(self, max_value=None, min_value=None, required=True, widget=None, label=None, initial=None):
self.max_value, self.min_value = max_value, min_value
Field.__init__(self, required, widget, label, initial)
def clean(self, value): def clean(self, value):
""" """
Validates that int() can be called on the input. Returns the result Validates that int() can be called on the input. Returns the result
@ -80,9 +119,14 @@ class IntegerField(Field):
if not self.required and value in EMPTY_VALUES: if not self.required and value in EMPTY_VALUES:
return u'' return u''
try: try:
return int(value) value = int(value)
except (ValueError, TypeError): except (ValueError, TypeError):
raise ValidationError(gettext(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 = ( DEFAULT_DATE_INPUT_FORMATS = (
'%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06' '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
@ -93,8 +137,8 @@ DEFAULT_DATE_INPUT_FORMATS = (
) )
class DateField(Field): class DateField(Field):
def __init__(self, input_formats=None, required=True, widget=None): def __init__(self, input_formats=None, required=True, widget=None, label=None, initial=None):
Field.__init__(self, required, widget) Field.__init__(self, required, widget, label, initial)
self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS
def clean(self, value): def clean(self, value):
@ -116,6 +160,33 @@ class DateField(Field):
continue continue
raise ValidationError(gettext(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, initial=None):
Field.__init__(self, required, widget, label, initial)
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 = ( DEFAULT_DATETIME_INPUT_FORMATS = (
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%Y-%m-%d %H:%M', # '2006-10-25 14:30' '%Y-%m-%d %H:%M', # '2006-10-25 14:30'
@ -129,8 +200,8 @@ DEFAULT_DATETIME_INPUT_FORMATS = (
) )
class DateTimeField(Field): class DateTimeField(Field):
def __init__(self, input_formats=None, required=True, widget=None): def __init__(self, input_formats=None, required=True, widget=None, label=None, initial=None):
Field.__init__(self, required, widget) Field.__init__(self, required, widget, label, initial)
self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS
def clean(self, value): def clean(self, value):
@ -153,16 +224,18 @@ class DateTimeField(Field):
raise ValidationError(gettext(u'Enter a valid date/time.')) raise ValidationError(gettext(u'Enter a valid date/time.'))
class RegexField(Field): 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, initial=None):
""" """
regex can be either a string or a compiled regular expression object. regex can be either a string or a compiled regular expression object.
error_message is an optional error message to use, if error_message is an optional error message to use, if
'Enter a valid value' is too generic for you. 'Enter a valid value' is too generic for you.
""" """
Field.__init__(self, required, widget) Field.__init__(self, required, widget, label, initial)
if isinstance(regex, basestring): if isinstance(regex, basestring):
regex = re.compile(regex) regex = re.compile(regex)
self.regex = regex self.regex = regex
self.max_length, self.min_length = max_length, min_length
self.error_message = error_message or gettext(u'Enter a valid value.') self.error_message = error_message or gettext(u'Enter a valid value.')
def clean(self, value): def clean(self, value):
@ -175,6 +248,10 @@ class RegexField(Field):
value = smart_unicode(value) value = smart_unicode(value)
if not self.required and value == u'': if not self.required and value == u'':
return value 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): if not self.regex.search(value):
raise ValidationError(self.error_message) raise ValidationError(self.error_message)
return value return value
@ -185,8 +262,8 @@ email_re = re.compile(
r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain
class EmailField(RegexField): class EmailField(RegexField):
def __init__(self, required=True, widget=None): def __init__(self, max_length=None, min_length=None, required=True, widget=None, label=None, initial=None):
RegexField.__init__(self, email_re, gettext(u'Enter a valid e-mail address.'), required, widget) RegexField.__init__(self, email_re, max_length, min_length, gettext(u'Enter a valid e-mail address.'), required, widget, label, initial)
url_re = re.compile( url_re = re.compile(
r'^https?://' # http:// or https:// r'^https?://' # http:// or https://
@ -202,9 +279,9 @@ except ImportError:
URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)' URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)'
class URLField(RegexField): 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): initial=None, validator_user_agent=URL_VALIDATOR_USER_AGENT):
RegexField.__init__(self, url_re, gettext(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, initial)
self.verify_exists = verify_exists self.verify_exists = verify_exists
self.user_agent = validator_user_agent self.user_agent = validator_user_agent
@ -238,10 +315,10 @@ class BooleanField(Field):
return bool(value) return bool(value)
class ChoiceField(Field): class ChoiceField(Field):
def __init__(self, choices=(), required=True, widget=Select): def __init__(self, choices=(), required=True, widget=Select, label=None, initial=None):
if isinstance(widget, type): if isinstance(widget, type):
widget = widget(choices=choices) widget = widget(choices=choices)
Field.__init__(self, required, widget) Field.__init__(self, required, widget, label, initial)
self.choices = choices self.choices = choices
def clean(self, value): def clean(self, value):
@ -259,8 +336,8 @@ class ChoiceField(Field):
return value return value
class MultipleChoiceField(ChoiceField): class MultipleChoiceField(ChoiceField):
def __init__(self, choices=(), required=True, widget=SelectMultiple): def __init__(self, choices=(), required=True, widget=SelectMultiple, label=None, initial=None):
ChoiceField.__init__(self, choices, required, widget) ChoiceField.__init__(self, choices, required, widget, label, initial)
def clean(self, value): def clean(self, value):
""" """
@ -277,15 +354,15 @@ class MultipleChoiceField(ChoiceField):
val = smart_unicode(val) val = smart_unicode(val)
new_value.append(val) new_value.append(val)
# Validate that each value in the value list is in self.choices. # 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: for val in new_value:
if val not in valid_values: if val not in valid_values:
raise ValidationError(gettext(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 return new_value
class ComboField(Field): class ComboField(Field):
def __init__(self, fields=(), required=True, widget=None): def __init__(self, fields=(), required=True, widget=None, label=None, initial=None):
Field.__init__(self, required, widget) Field.__init__(self, required, widget, label, initial)
# Set 'required' to False on the individual fields, because the # Set 'required' to False on the individual fields, because the
# required validation will be handled by ComboField, not by those # required validation will be handled by ComboField, not by those
# individual fields. # individual fields.

View File

@ -2,12 +2,14 @@
Form classes Form classes
""" """
from django.utils.datastructures import SortedDict from django.utils.datastructures import SortedDict, MultiValueDict
from django.utils.html import escape from django.utils.html import escape
from fields import Field from fields import Field
from widgets import TextInput, Textarea, HiddenInput from widgets import TextInput, Textarea, HiddenInput
from util import StrAndUnicode, ErrorDict, ErrorList, ValidationError from util import StrAndUnicode, ErrorDict, ErrorList, ValidationError
__all__ = ('BaseForm', 'Form')
NON_FIELD_ERRORS = '__all__' NON_FIELD_ERRORS = '__all__'
def pretty_name(name): def pretty_name(name):
@ -32,14 +34,16 @@ class DeclarativeFieldsMetaclass(type):
attrs['fields'] = SortedDictFromList(fields) attrs['fields'] = SortedDictFromList(fields)
return type.__new__(cls, name, bases, attrs) return type.__new__(cls, name, bases, attrs)
class Form(StrAndUnicode): class BaseForm(StrAndUnicode):
"A collection of Fields, plus their associated data." # This is the main implementation of all the Form logic. Note that this
__metaclass__ = DeclarativeFieldsMetaclass # 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*
def __init__(self, data=None, auto_id=False): # TODO: prefix stuff # class, not to the Form class.
def __init__(self, data=None, auto_id='id_%s', prefix=None):
self.ignore_errors = data is None self.ignore_errors = data is None
self.data = data or {} self.data = data or {}
self.auto_id = auto_id self.auto_id = auto_id
self.prefix = prefix
self.clean_data = None # Stores the data after clean() has been called. self.clean_data = None # Stores the data after clean() has been called.
self.__errors = None # Stores the errors after clean() has been called. self.__errors = None # Stores the errors after clean() has been called.
@ -72,6 +76,15 @@ class Form(StrAndUnicode):
""" """
return not self.ignore_errors and not bool(self.errors) 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 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): 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()." "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. top_errors = self.non_field_errors() # Errors that should be displayed above all fields.
@ -86,7 +99,8 @@ class Form(StrAndUnicode):
else: else:
if errors_on_separate_row and bf_errors: if errors_on_separate_row and bf_errors:
output.append(error_row % bf_errors) output.append(error_row % bf_errors)
output.append(normal_row % {'errors': bf_errors, 'label': bf.label_tag(escape(bf.verbose_name+':')), 'field': bf}) 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: if top_errors:
output.insert(0, error_row % top_errors) output.insert(0, error_row % top_errors)
if hidden_fields: # Insert any hidden fields in the last row. if hidden_fields: # Insert any hidden fields in the last row.
@ -101,7 +115,7 @@ class Form(StrAndUnicode):
def as_table(self): def as_table(self):
"Returns this form rendered as HTML <tr>s -- excluding the <table></table>." "Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
return self._html_output(u'<tr><td>%(label)s</td><td>%(field)s</td></tr>', u'<tr><td colspan="2">%s</td></tr>', '</td></tr>', True) 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): def as_ul(self):
"Returns this form rendered as HTML <li>s -- excluding the <ul></ul>." "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
@ -132,7 +146,7 @@ class Form(StrAndUnicode):
# value_from_datadict() gets the data from the dictionary. # value_from_datadict() gets the data from the dictionary.
# Each widget type knows how to retrieve its own data, because some # Each widget type knows how to retrieve its own data, because some
# widgets split data over several HTML fields. # widgets split data over several HTML fields.
value = field.widget.value_from_datadict(self.data, name) value = field.widget.value_from_datadict(self.data, self.add_prefix(name))
try: try:
value = field.clean(value) value = field.clean(value)
self.clean_data[name] = value self.clean_data[name] = value
@ -158,12 +172,26 @@ class Form(StrAndUnicode):
""" """
return self.clean_data return self.clean_data
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): class BoundField(StrAndUnicode):
"A Field plus data" "A Field plus data"
def __init__(self, form, field, name): def __init__(self, form, field, name):
self.form = form self.form = form
self.field = field self.field = field
self.name = name 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 __unicode__(self): def __unicode__(self):
"Renders this field as an HTML widget." "Renders this field as an HTML widget."
@ -190,7 +218,11 @@ class BoundField(StrAndUnicode):
auto_id = self.auto_id auto_id = self.auto_id
if auto_id and not attrs.has_key('id') and not widget.attrs.has_key('id'): if auto_id and not attrs.has_key('id') and not widget.attrs.has_key('id'):
attrs['id'] = auto_id attrs['id'] = auto_id
return widget.render(self.name, self.data, attrs=attrs) if self.form.ignore_errors:
data = self.field.initial
else:
data = self.data
return widget.render(self.html_name, data, attrs=attrs)
def as_text(self, attrs=None): def as_text(self, attrs=None):
""" """
@ -209,21 +241,19 @@ class BoundField(StrAndUnicode):
return self.as_widget(HiddenInput(), attrs) return self.as_widget(HiddenInput(), attrs)
def _data(self): def _data(self):
"Returns the data for this BoundField, or None if it wasn't given." """
return self.form.data.get(self.name, None) 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) data = property(_data)
def _verbose_name(self):
return pretty_name(self.name)
verbose_name = property(_verbose_name)
def label_tag(self, contents=None): def label_tag(self, contents=None):
""" """
Wraps the given contents in a <label>, if the field has an ID attribute. 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 Does not HTML-escape the contents. If contents aren't given, uses the
field's HTML-escaped verbose_name. field's HTML-escaped label.
""" """
contents = contents or escape(self.verbose_name) contents = contents or escape(self.label)
widget = self.field.widget widget = self.field.widget
id_ = widget.attrs.get('id') or self.auto_id id_ = widget.attrs.get('id') or self.auto_id
if id_: if id_:
@ -242,8 +272,8 @@ class BoundField(StrAndUnicode):
""" """
auto_id = self.form.auto_id auto_id = self.form.auto_id
if auto_id and '%s' in str(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: elif auto_id:
return self.name return self.html_name
return '' return ''
auto_id = property(_auto_id) auto_id = property(_auto_id)

View File

@ -1,13 +1,75 @@
""" """
Helper functions for creating Forms from Django models and database field objects. Helper functions for creating Form classes from Django models
and database field objects.
""" """
__all__ = ('form_for_model', 'form_for_fields') from forms import BaseForm, DeclarativeFieldsMetaclass, SortedDictFromList
def form_for_model(model): __all__ = ('form_for_model', 'form_for_instance', 'form_for_fields')
"Returns a Form instance for the given Django model class."
raise NotImplementedError def create(self, save=True):
"""
Creates and returns model instance according to self.clean_data.
This method is created for any form_for_model Form.
"""
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 make_apply_changes(opts, instance):
"Returns the apply_changes() method for a form_for_instance Form."
from django.db import models
def apply_changes(self, save=True):
if self.errors:
raise ValueError("The %s could not be changed because the data didn't validate." % opts.object_name)
clean_data = self.clean_data
for f in opts.fields + opts.many_to_many:
if isinstance(f, models.AutoField):
continue
setattr(instance, f.attname, clean_data[f.name])
if save:
instance.save()
return instance
return apply_changes
def form_for_model(model, form=BaseForm):
"""
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)
return type(opts.object_name + 'Form', (form,), {'fields': fields, '_model': model, 'create': create})
def form_for_instance(instance, form=BaseForm):
"""
Returns a Form class for the given Django model instance.
Provide 'form' if you want to use a custom BaseForm subclass.
"""
model = instance.__class__
opts = model._meta
field_list = []
for f in opts.fields + opts.many_to_many:
current_value = f.value_from_object(instance)
formfield = f.formfield(initial=current_value)
if formfield:
field_list.append((f.name, formfield))
fields = SortedDictFromList(field_list)
return type(opts.object_name + 'InstanceForm', (form,),
{'fields': fields, '_model': model, 'apply_changes': make_apply_changes(opts, instance)})
def form_for_fields(field_list): def form_for_fields(field_list):
"Returns a Form instance for the given list of Django database field instances." "Returns a Form class for the given list of Django database field instances."
raise NotImplementedError fields = SortedDictFromList([(f.name, f.formfield()) for f in field_list])
return type('FormForFields', (BaseForm,), {'fields': fields})

View File

@ -9,6 +9,7 @@ __all__ = (
) )
from util import StrAndUnicode, smart_unicode from util import StrAndUnicode, smart_unicode
from django.utils.datastructures import MultiValueDict
from django.utils.html import escape from django.utils.html import escape
from itertools import chain from itertools import chain
@ -22,13 +23,18 @@ except NameError:
flatatt = lambda attrs: u''.join([u' %s="%s"' % (k, escape(v)) for k, v in attrs.items()]) flatatt = lambda attrs: u''.join([u' %s="%s"' % (k, escape(v)) for k, v in attrs.items()])
class Widget(object): 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">. is_hidden = False # Determines whether this corresponds to an <input type="hidden">.
def __init__(self, attrs=None): def __init__(self, attrs=None):
self.attrs = attrs or {} 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 raise NotImplementedError
def build_attrs(self, extra_attrs=None, **kwargs): def build_attrs(self, extra_attrs=None, **kwargs):
@ -64,6 +70,7 @@ class Input(Widget):
type='radio', which are special). type='radio', which are special).
""" """
input_type = None # Subclasses must define this. input_type = None # Subclasses must define this.
def render(self, name, value, attrs=None): def render(self, name, value, attrs=None):
if value is None: value = '' if value is None: value = ''
final_attrs = self.build_attrs(attrs, type=self.input_type, name=name) final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
@ -128,7 +135,6 @@ class Select(Widget):
return u'\n'.join(output) return u'\n'.join(output)
class SelectMultiple(Widget): class SelectMultiple(Widget):
requires_data_list = True
def __init__(self, attrs=None, choices=()): def __init__(self, attrs=None, choices=()):
# choices can be any iterable # choices can be any iterable
self.attrs = attrs or {} self.attrs = attrs or {}
@ -146,6 +152,11 @@ class SelectMultiple(Widget):
output.append(u'</select>') output.append(u'</select>')
return u'\n'.join(output) return u'\n'.join(output)
def value_from_datadict(self, data, name):
if isinstance(data, MultiValueDict):
return data.getlist(name)
return data.get(name, None)
class RadioInput(StrAndUnicode): class RadioInput(StrAndUnicode):
"An object used by RadioFieldRenderer that represents a single <input type='radio'>." "An object used by RadioFieldRenderer that represents a single <input type='radio'>."
def __init__(self, name, value, attrs, choice, index): def __init__(self, name, value, attrs, choice, index):
@ -178,6 +189,10 @@ class RadioFieldRenderer(StrAndUnicode):
for i, choice in enumerate(self.choices): for i, choice in enumerate(self.choices):
yield RadioInput(self.name, self.value, self.attrs.copy(), choice, i) 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 __unicode__(self): def __unicode__(self):
"Outputs a <ul> for this set of radio fields." "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]) return u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>' % w for w in self])

1008
django/oldforms/__init__.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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 django.utils.tzinfo import LocalTimezone
from calendar import isleap, monthrange from calendar import isleap, monthrange
import re, time import re, time
@ -147,7 +147,7 @@ class DateFormat(TimeFormat):
def M(self): def M(self):
"Month, textual, 3 letters; e.g. 'Jan'" "Month, textual, 3 letters; e.g. 'Jan'"
return MONTHS[self.data.month][0:3] return MONTHS_3[self.data.month].title()
def n(self): def n(self):
"Month without leading zeros; i.e. '1' to '12'" "Month without leading zeros; i.e. '1' to '12'"

View File

@ -8,17 +8,28 @@ capfirst = lambda x: x and x[0].upper() + x[1:]
def wrap(text, width): def wrap(text, width):
""" """
A word-wrap function that preserves existing line breaks and most spaces in A word-wrap function that preserves existing line breaks and most spaces in
the text. Expects that existing line breaks are posix newlines (\n). the text. Expects that existing line breaks are posix newlines.
See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061
""" """
return reduce(lambda line, word, width=width: '%s%s%s' % def _generator():
(line, it = iter(text.split(' '))
' \n'[(len(line[line.rfind('\n')+1:]) word = it.next()
+ len(word.split('\n',1)[0] yield word
) >= width)], pos = len(word) - word.rfind('\n') - 1
word), for word in it:
text.split(' ') 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): def truncate_words(s, num):
"Truncates a string after a certain number of words." "Truncates a string after a certain number of words."

View File

@ -1,6 +1,6 @@
from django.core.xheaders import populate_xheaders from django.core.xheaders import populate_xheaders
from django.template import loader from django.template import loader
from django import forms from django import oldforms
from django.db.models import FileField from django.db.models import FileField
from django.contrib.auth.views import redirect_to_login from django.contrib.auth.views import redirect_to_login
from django.template import RequestContext from django.template import RequestContext
@ -56,7 +56,7 @@ def create_object(request, model, template_name=None,
new_data = manipulator.flatten_data() new_data = manipulator.flatten_data()
# Create the FormWrapper, template, context, response # Create the FormWrapper, template, context, response
form = forms.FormWrapper(manipulator, new_data, errors) form = oldforms.FormWrapper(manipulator, new_data, errors)
if not template_name: if not template_name:
template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower()) template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower())
t = template_loader.get_template(template_name) 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. # This makes sure the form acurate represents the fields of the place.
new_data = manipulator.flatten_data() new_data = manipulator.flatten_data()
form = forms.FormWrapper(manipulator, new_data, errors) form = oldforms.FormWrapper(manipulator, new_data, errors)
if not template_name: if not template_name:
template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower()) template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower())
t = template_loader.get_template(template_name) t = template_loader.get_template(template_name)

View File

@ -22,6 +22,9 @@ of the community, so there are many ways you can help Django's development:
likely to be skeptical of large-scale suggestions without some code to likely to be skeptical of large-scale suggestions without some code to
back it up. back it up.
* Triage patches that have been submitted by other users. Please read
`Ticket triage`_ below, for details on the triage process.
That's all you need to know if you'd like to join the Django development That's all you need to know if you'd like to join the Django development
community. The rest of this document describes the details of how our community community. The rest of this document describes the details of how our community
works and how it handles bugs, mailing lists, and all the other minutiae of works and how it handles bugs, mailing lists, and all the other minutiae of
@ -44,8 +47,10 @@ particular:
* **Do** write complete, reproducible, specific bug reports. Include as * **Do** write complete, reproducible, specific bug reports. Include as
much information as you possibly can, complete with code snippets, test much information as you possibly can, complete with code snippets, test
cases, etc. A minimal example that illustrates the bug in a nice small cases, etc. This means including a clear, concise description of the
test case is the best possible bug report. problem, and a clear set of instructions for replicating the problem.
A minimal example that illustrates the bug in a nice small test case
is the best possible bug report.
* **Don't** use the ticket system to ask support questions. Use the * **Don't** use the ticket system to ask support questions. Use the
`django-users`_ list, or the `#django`_ IRC channel for that. `django-users`_ list, or the `#django`_ IRC channel for that.
@ -121,6 +126,50 @@ Patch style
it obvious that the ticket includes a patch, and it will add the ticket it obvious that the ticket includes a patch, and it will add the ticket
to the `list of tickets with patches`_. to the `list of tickets with patches`_.
* The code required to fix a problem or add a feature is an essential part
of a patch, but it is not the only part. A good patch should also include
a regression test to validate the behavior that has been fixed (and prevent
the problem from arising again).
* If the code associated with a patch adds a new feature, or modifies behavior
of an existing feature, the patch should also contain documentation.
Non-trivial patches
-------------------
A "non-trivial" patch is one that is more than a simple bug fix. It's a patch
that introduces Django functionality and makes some sort of design decision.
If you provide a non-trivial patch, include evidence that alternatives have
been discussed on `django-developers`_. If you're not sure whether your patch
should be considered non-trivial, just ask.
Ticket triage
=============
Unfortunately, not all bug reports in the `ticket tracker`_ provide all
the `required details`_. A number of tickets have patches, but those patches
don't meet all the requirements of a `good patch`_.
One way to help out is to *triage* bugs that have been reported by other users.
Pick an open ticket that is missing some details, and try to replicate the
problem. Fill in the missing pieces of the report. If the ticket doesn't have
a patch, create one.
Once you've completed all the missing details on the ticket and you have a
patch with all the required features, e-mail `django-developers`_. Indicate
that you have triaged a ticket, and recommend a course of action for dealing
with that ticket.
At first, this may require you to be persistent. If you find that your triaged
ticket still isn't getting attention, occasional polite requests for eyeballs
to look at your ticket may be necessary. However, as you earn a reputation for
quality triage work, you should find that it is easier to get the developers'
attention.
.. _required details: `Reporting bugs`_
.. _good patch: `Patch style`_
Submitting and maintaining translations Submitting and maintaining translations
======================================= =======================================
@ -338,21 +387,63 @@ trunk more than once.
Using branches Using branches
-------------- --------------
To test a given branch, you can simply check out the entire branch, like so:: To use a branch, you'll need to do two things:
* Get the branch's code through Subversion.
* Point your Python ``site-packages`` directory at the branch's version of
the ``django`` package rather than the version you already have
installed.
Getting the code from Subversion
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To get the latest version of a branch's code, check it out using Subversion::
svn co http://code.djangoproject.com/svn/django/branches/<branch>/ svn co http://code.djangoproject.com/svn/django/branches/<branch>/
Or, if you've got a working directory you'd like to switch to use a branch, ...where ``<branch>`` is the branch's name. See the `list of branch names`_.
you can use::
Alternatively, you can automatically convert an existing directory of the
Django source code as long as you've checked it out via Subversion. To do the
conversion, execute this command from within your ``django`` directory::
svn switch http://code.djangoproject.com/svn/django/branches/<branch>/ svn switch http://code.djangoproject.com/svn/django/branches/<branch>/
...in the root of your Django sandbox (the directory that contains ``django``,
``docs``, and ``tests``).
The advantage of using ``svn switch`` instead of ``svn co`` is that the The advantage of using ``svn switch`` instead of ``svn co`` is that the
``switch`` command retains any changes you might have made to your local copy ``switch`` command retains any changes you might have made to your local copy
of the code. It attempts to merge those changes into the "switched" code. of the code. It attempts to merge those changes into the "switched" code. The
disadvantage is that it may cause conflicts with your local changes if the
"switched" code has altered the same lines of code.
(Note that if you use ``svn switch``, you don't need to point Python at the new
version, as explained in the next section.)
.. _list of branch names: http://code.djangoproject.com/browser/django/branches
Pointing Python at the new Django version
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Once you've retrieved the branch's code, you'll need to change your Python
``site-packages`` directory so that it points to the branch version of the
``django`` directory. (The ``site-packages`` directory is somewhere such as
``/usr/lib/python2.4/site-packages`` or
``/usr/local/lib/python2.4/site-packages`` or ``C:\Python\site-packages``.)
The simplest way to do this is by renaming the old ``django`` directory to
``django.OLD`` and moving the trunk version of the code into the directory
and calling it ``django``.
Alternatively, you can use a symlink called ``django`` that points to the
location of the branch's ``django`` package. If you want to switch back, just
change the symlink to point to the old code.
If you're using Django 0.95 or earlier and installed it using
``python setup.py install``, you'll have a directory called something like
``Django-0.95-py2.4.egg`` instead of ``django``. In this case, edit the file
``setuptools.pth`` and remove the line that references the Django ``.egg``
file. Then copy the branch's version of the ``django`` directory into
``site-packages``.
Official releases Official releases
================= =================

View File

@ -1,9 +1,9 @@
===================================== =====================================
Cross Site Request Forgery Protection Cross Site Request Forgery protection
===================================== =====================================
The CsrfMiddleware class provides easy-to-use protection against The CsrfMiddleware class provides easy-to-use protection against
`Cross Site Request Forgeries`_. This type of attack occurs when a malicious `Cross Site Request Forgeries`_. This type of attack occurs when a malicious
web site creates a link or form button that is intended to perform some action web site creates a link or form button that is intended to perform some action
on your web site, using the credentials of a logged-in user who is tricked on your web site, using the credentials of a logged-in user who is tricked
into clicking on the link in their browser. into clicking on the link in their browser.
@ -12,12 +12,12 @@ The first defense against CSRF attacks is to ensure that GET requests
are side-effect free. POST requests can then be protected by adding this are side-effect free. POST requests can then be protected by adding this
middleware into your list of installed middleware. middleware into your list of installed middleware.
.. _Cross Site Request Forgeries: http://www.squarefree.com/securitytips/web-developers.html#CSRF .. _Cross Site Request Forgeries: http://www.squarefree.com/securitytips/web-developers.html#CSRF
How to use it How to use it
============= =============
Add the middleware ``'django.contrib.csrf.middleware.CsrfMiddleware'`` to
Add the middleware ``'django.contrib.csrf.middleware.CsrfMiddleware'`` to
your list of middleware classes, ``MIDDLEWARE_CLASSES``. It needs to process your list of middleware classes, ``MIDDLEWARE_CLASSES``. It needs to process
the response after the SessionMiddleware, so must come before it in the the response after the SessionMiddleware, so must come before it in the
list. It also must process the response before things like compression list. It also must process the response before things like compression
@ -25,16 +25,17 @@ happen to the response, so it must come after GZipMiddleware in the list.
How it works How it works
============ ============
CsrfMiddleware does two things: CsrfMiddleware does two things:
1. It modifies outgoing requests by adding a hidden form field to all 1. It modifies outgoing requests by adding a hidden form field to all
'POST' forms, with the name 'csrfmiddlewaretoken' and a value which is 'POST' forms, with the name 'csrfmiddlewaretoken' and a value which is
a hash of the session ID plus a secret. If there is no session ID set, a hash of the session ID plus a secret. If there is no session ID set,
this modification of the response isn't done, so there is very little this modification of the response isn't done, so there is very little
performance penalty for those requests that don't have a session. performance penalty for those requests that don't have a session.
2. On all incoming POST requests that have the session cookie set, it 2. On all incoming POST requests that have the session cookie set, it
checks that the 'csrfmiddlewaretoken' is present and correct. If it checks that the 'csrfmiddlewaretoken' is present and correct. If it
isn't, the user will get a 403 error. isn't, the user will get a 403 error.
This ensures that only forms that have originated from your web site This ensures that only forms that have originated from your web site
@ -43,26 +44,26 @@ can be used to POST data back.
It deliberately only targets HTTP POST requests (and the corresponding It deliberately only targets HTTP POST requests (and the corresponding
POST forms). GET requests ought never to have side effects (if you are POST forms). GET requests ought never to have side effects (if you are
using HTTP GET and POST correctly), and so a CSRF attack with a GET using HTTP GET and POST correctly), and so a CSRF attack with a GET
request will always be harmless. request will always be harmless.
POST requests that are not accompanied by a session cookie are not protected, POST requests that are not accompanied by a session cookie are not protected,
but they do not need to be protected, since the 'attacking' web site but they do not need to be protected, since the 'attacking' web site
could make these kind of requests anyway. could make these kind of requests anyway.
The Content-Type is checked before modifying the response, and only The Content-Type is checked before modifying the response, and only
pages that are served as 'text/html' or 'application/xml+xhtml' pages that are served as 'text/html' or 'application/xml+xhtml'
are modified. are modified.
Limitations Limitations
=========== ===========
CsrfMiddleware requires Django's session framework to work. If you have CsrfMiddleware requires Django's session framework to work. If you have
a custom authentication system that manually sets cookies and the like, a custom authentication system that manually sets cookies and the like,
it won't help you. it won't help you.
If your app creates HTML pages and forms in some unusual way, (e.g. If your app creates HTML pages and forms in some unusual way, (e.g.
it sends fragments of HTML in javascript document.write statements) it sends fragments of HTML in javascript document.write statements)
you might bypass the filter that adds the hidden field to the form, you might bypass the filter that adds the hidden field to the form,
in which case form submission will always fail. It may still be possible in which case form submission will always fail. It may still be possible
to use the middleware, provided you can find some way to get the to use the middleware, provided you can find some way to get the
CSRF token and ensure that is included when your form is submitted. CSRF token and ensure that is included when your form is submitted.

View File

@ -143,9 +143,9 @@ or ``UPDATE`` SQL statements. Specifically, when you call ``save()``, Django
follows this algorithm: follows this algorithm:
* If the object's primary key attribute is set to a value that evaluates to * If the object's primary key attribute is set to a value that evaluates to
``False`` (such as ``None`` or the empty string), Django executes a ``True`` (i.e., a value other than ``None`` or the empty string), Django
``SELECT`` query to determine whether a record with the given primary key executes a ``SELECT`` query to determine whether a record with the given
already exists. primary key already exists.
* If the record with the given primary key does already exist, Django * If the record with the given primary key does already exist, Django
executes an ``UPDATE`` query. executes an ``UPDATE`` query.
* If the object's primary key attribute is *not* set, or if it's set but a * If the object's primary key attribute is *not* set, or if it's set but a

View File

@ -2,15 +2,27 @@
Forms, fields, and manipulators Forms, fields, and manipulators
=============================== ===============================
Forwards-compatibility note
===========================
The legacy forms/manipulators system described in this document is going to be
replaced in the next Django release. If you're starting from scratch, we
strongly encourage you not to waste your time learning this. Instead, learn and
use the django.newforms system, which we have begun to document in the
`newforms documentation`_.
If you have legacy form/manipulator code, read the "Migration plan" section in
that document to understand how we're making the switch.
.. _newforms documentation: http://www.djangoproject.com/documentation/newforms/
Introduction
============
Once you've got a chance to play with Django's admin interface, you'll probably Once you've got a chance to play with Django's admin interface, you'll probably
wonder if the fantastic form validation framework it uses is available to user wonder if the fantastic form validation framework it uses is available to user
code. It is, and this document explains how the framework works. code. It is, and this document explains how the framework works.
.. admonition:: A note to the lazy
If all you want to do is present forms for a user to create and/or
update a given object, you may be able to use `generic views`_.
We'll take a top-down approach to examining Django's form validation framework, We'll take a top-down approach to examining Django's form validation framework,
because much of the time you won't need to use the lower-level APIs. Throughout because much of the time you won't need to use the lower-level APIs. Throughout
this document, we'll be working with the following model, a "place" object:: this document, we'll be working with the following model, a "place" object::
@ -41,17 +53,17 @@ this document, we'll be working with the following model, a "place" object::
Defining the above class is enough to create an admin interface to a ``Place``, Defining the above class is enough to create an admin interface to a ``Place``,
but what if you want to allow public users to submit places? but what if you want to allow public users to submit places?
Manipulators Automatic Manipulators
============ ======================
The highest-level interface for object creation and modification is the The highest-level interface for object creation and modification is the
**Manipulator** framework. A manipulator is a utility class tied to a given **automatic Manipulator** framework. An automatic manipulator is a utility
model that "knows" how to create or modify instances of that model and how to class tied to a given model that "knows" how to create or modify instances of
validate data for the object. Manipulators come in two flavors: that model and how to validate data for the object. Automatic Manipulators come
``AddManipulators`` and ``ChangeManipulators``. Functionally they are quite in two flavors: ``AddManipulators`` and ``ChangeManipulators``. Functionally
similar, but the former knows how to create new instances of the model, while they are quite similar, but the former knows how to create new instances of the
the latter modifies existing instances. Both types of classes are automatically model, while the latter modifies existing instances. Both types of classes are
created when you define a new class:: automatically created when you define a new class::
>>> from mysite.myapp.models import Place >>> from mysite.myapp.models import Place
>>> Place.AddManipulator >>> Place.AddManipulator

View File

@ -902,7 +902,7 @@ If ``template_name`` isn't specified, this view will use the template
In addition to ``extra_context``, the template's context will be: In addition to ``extra_context``, the template's context will be:
* ``form``: A ``django.forms.FormWrapper`` instance representing the form * ``form``: A ``django.oldforms.FormWrapper`` instance representing the form
for editing the object. This lets you refer to form fields easily in the for editing the object. This lets you refer to form fields easily in the
template system. template system.
@ -984,7 +984,7 @@ If ``template_name`` isn't specified, this view will use the template
In addition to ``extra_context``, the template's context will be: In addition to ``extra_context``, the template's context will be:
* ``form``: A ``django.forms.FormWrapper`` instance representing the form * ``form``: A ``django.oldforms.FormWrapper`` instance representing the form
for editing the object. This lets you refer to form fields easily in the for editing the object. This lets you refer to form fields easily in the
template system. template system.

View File

@ -22,7 +22,6 @@ what the name of the database is. Do that by editing these settings in your
* `DATABASE_ENGINE`_ * `DATABASE_ENGINE`_
* `DATABASE_USER`_ * `DATABASE_USER`_
* `DATABASE_PASSWORD`_ * `DATABASE_PASSWORD`_
* `DATABASE_NAME`_
* `DATABASE_HOST`_ * `DATABASE_HOST`_
* `DATABASE_PORT`_ * `DATABASE_PORT`_
@ -31,7 +30,6 @@ what the name of the database is. Do that by editing these settings in your
.. _DATABASE_ENGINE: http://www.djangoproject.com/documentation/settings/#database-engine .. _DATABASE_ENGINE: http://www.djangoproject.com/documentation/settings/#database-engine
.. _DATABASE_USER: http://www.djangoproject.com/documentation/settings/#database-user .. _DATABASE_USER: http://www.djangoproject.com/documentation/settings/#database-user
.. _DATABASE_PASSWORD: http://www.djangoproject.com/documentation/settings/#database-password .. _DATABASE_PASSWORD: http://www.djangoproject.com/documentation/settings/#database-password
.. _DATABASE_NAME: http://www.djangoproject.com/documentation/settings/#database-name
.. _DATABASE_HOST: http://www.djangoproject.com/documentation/settings/#database-host .. _DATABASE_HOST: http://www.djangoproject.com/documentation/settings/#database-host
.. _DATABASE_PORT: http://www.djangoproject.com/documentation/settings/#database-port .. _DATABASE_PORT: http://www.djangoproject.com/documentation/settings/#database-port

View File

@ -13,18 +13,23 @@ Migration plan
-- i.e., it's not available in the Django 0.95 release. For the next Django -- i.e., it's not available in the Django 0.95 release. For the next Django
release, our plan is to do the following: release, our plan is to do the following:
* Move the current ``django.forms`` to ``django.oldforms``. This will allow * As of revision [4208], we've copied the current ``django.forms`` to
for an eased migration of form code. You'll just have to change your ``django.oldforms``. This allows you to upgrade your code *now* rather
import statements:: than waiting for the backwards-incompatible change and rushing to fix
your code after the fact. Just change your import statements like this::
from django import forms # old from django import forms # old
from django import oldforms as forms # new from django import oldforms as forms # new
* Move the current ``django.newforms`` to ``django.forms``. * At an undecided future date, we will move the current ``django.newforms``
to ``django.forms``. This will be a backwards-incompatible change, and
anybody who is still using the old version of ``django.forms`` at that
time will need to change their import statements, as described in the
previous bullet.
* We will remove ``django.oldforms`` in the release *after* the next Django * We will remove ``django.oldforms`` in the release *after* the next Django
release -- the release that comes after the release in which we're release -- the release that comes after the release in which we're
creating ``django.oldforms``. creating the new ``django.forms``.
With this in mind, we recommend you use the following import statement when With this in mind, we recommend you use the following import statement when
using ``django.newforms``:: using ``django.newforms``::
@ -46,9 +51,14 @@ too messy. The choice is yours.
Overview Overview
======== ========
As the ``django.forms`` system before it, ``django.newforms`` is intended to As the ``django.forms`` ("manipulators") system before it, ``django.newforms``
handle HTML form display, validation and redisplay. It's what you use if you is intended to handle HTML form display, validation and redisplay. It's what
want to perform server-side validation for an HTML form. you use if you want to perform server-side validation for an HTML form.
For example, if your Web site has a contact form that visitors can use to
send you e-mail, you'd use this library to implement the display of the HTML
form fields, along with the form validation. Any time you need to use an HTML
``<form>``, you can use this library.
The library deals with these concepts: The library deals with these concepts:
@ -62,6 +72,223 @@ The library deals with these concepts:
* **Form** -- A collection of fields that knows how to validate itself and * **Form** -- A collection of fields that knows how to validate itself and
display itself as HTML. display itself as HTML.
Form objects
============
The primary way of using the ``newforms`` library is to create a form object.
Do this by subclassing ``django.newforms.Form`` and specifying the form's
fields, in a declarative style that you'll be familiar with if you've used
Django database models. In this section, we'll iteratively develop a form
object that you might to implement "contact me" functionality on your personal
Web site.
Start with this basic ``Form`` subclass, which we'll call ``ContactForm``::
from django import newforms as forms
class ContactForm(forms.Form):
subject = forms.CharField(max_length=100)
message = forms.CharField()
sender = forms.EmailField()
cc_myself = forms.BooleanField()
A form is composed of ``Field`` objects. In this case, our form has four
fields: ``subject``, ``message``, ``sender`` and ``cc_myself``. We'll explain
the different types of fields -- e.g., ``CharField`` and ``EmailField`` --
shortly.
Outputting forms as HTML
------------------------
The first thing we can do with this is output it as HTML. To do so, instantiate
it and ``print`` it::
>>> f = ContactForm()
>>> print f
<tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" /></td></tr>
<tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" /></td></tr>
<tr><th><label for="id_sender">Sender:</label></th><td><input type="text" name="sender" id="id_sender" /></td></tr>
<tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself" /></td></tr>
This default output is a two-column HTML table, with a ``<tr>`` for each field.
Notice the following:
* For flexibility, the output does *not* include the ``<table>`` and
``</table>`` tags, nor does it include the ``<form>`` and ``</form>``
tags or an ``<input type="submit">`` tag. It's your job to do that.
* Each field type has a default HTML representation. ``CharField`` and
``EmailField`` are represented by an ``<input type="text">``.
``BooleanField`` is represented by an ``<input type="checkbox">``. Note
these are merely sensible defaults; you can specify which HTML to use for
a given field by using ``widgets``, which we'll explain shortly.
* The HTML ``name`` for each tag is taken directly from its attribute name
in the ``ContactForm`` class.
* The text label for each field -- e.g. ``'Subject:'``, ``'Message:'`` and
``'CC myself:'`` is generated from the field name by converting all
underscores to spaces and upper-casing the first letter. Again, note
these are merely sensible defaults; you can also specify labels manually.
* Each text label is surrounded in an HTML ``<label>`` tag, which points
to the appropriate form field via its ``id``. Its ``id``, in turn, is
generated by prepending ``'id_'`` to the field name. The ``id``
attributes and ``<label>`` tags are included in the output by default, to
follow best practices, but you can change that behavior.
Although ``<table>`` output is the default output style when you ``print`` a
form, other output styles are available. Each style is available as a method on
a form object, and each rendering method returns a Unicode object.
``as_p()``
~~~~~~~~~~
``Form.as_p()`` renders the form as a series of ``<p>`` tags, with each ``<p>``
containing one field::
>>> f = ContactForm()
>>> f.as_p()
u'<p><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" /></p>\n<p><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" /></p>\n<p><label for="id_sender">Sender:</label> <input type="text" name="sender" id="id_sender" /></p>\n<p><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself" /></p>'
>>> print f.as_p()
<p><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" /></p>
<p><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" /></p>
<p><label for="id_sender">Sender:</label> <input type="text" name="sender" id="id_sender" /></p>
<p><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself" /></p>
``as_ul()``
~~~~~~~~~~~
``Form.as_ul()`` renders the form as a series of ``<li>`` tags, with each
``<li>`` containing one field. It does *not* include the ``<ul>`` or ``</ul>``,
so that you can specify any HTML attributes on the ``<ul>`` for flexibility::
>>> f = ContactForm()
>>> f.as_ul()
u'<li><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" /></li>\n<li><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" /></li>\n<li><label for="id_sender">Sender:</label> <input type="text" name="sender" id="id_sender" /></li>\n<li><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself" /></li>'
>>> print f.as_ul()
<li><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" /></li>
<li><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" /></li>
<li><label for="id_sender">Sender:</label> <input type="text" name="sender" id="id_sender" /></li>
<li><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself" /></li>
``as_table()``
~~~~~~~~~~~~~~
Finally, ``Form.as_table()`` outputs the form as an HTML ``<table>``. This is
exactly the same as ``print``. In fact, when you ``print`` a form object, it
calls its ``as_table()`` method behind the scenes::
>>> f = ContactForm()
>>> f.as_table()
u'<tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" /></td></tr>\n<tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" /></td></tr>\n<tr><th><label for="id_sender">Sender:</label></th><td><input type="text" name="sender" id="id_sender" /></td></tr>\n<tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself" /></td></tr>'
>>> print f.as_table()
<tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" /></td></tr>
<tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" /></td></tr>
<tr><th><label for="id_sender">Sender:</label></th><td><input type="text" name="sender" id="id_sender" /></td></tr>
<tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself" /></td></tr>
Configuring HTML ``<label>`` tags
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
An HTML ``<label>`` tag designates which label text is associated with which
form element. This small enhancement makes forms more usable and more accessible
to assistive devices. It's always a good idea to use ``<label>`` tags.
By default, the form rendering methods include HTML ``id`` attributes on the
form elements and corresponding ``<label>`` tags around the labels. The ``id``
attribute values are generated by prepending ``id_`` to the form field names.
This behavior is configurable, though, if you want to change the ``id``
convention or remove HTML ``id`` attributes and ``<label>`` tags entirely.
Use the ``auto_id`` argument to the ``Form`` constructor to control the label
and ``id`` behavior. This argument must be ``True``, ``False`` or a string.
If ``auto_id`` is ``False``, then the form output will not include ``<label>``
tags nor ``id`` attributes::
>>> f = ContactForm(auto_id=False)
>>> print f.as_table()
<tr><th>Subject:</th><td><input type="text" name="subject" maxlength="100" /></td></tr>
<tr><th>Message:</th><td><input type="text" name="message" /></td></tr>
<tr><th>Sender:</th><td><input type="text" name="sender" /></td></tr>
<tr><th>Cc myself:</th><td><input type="checkbox" name="cc_myself" /></td></tr>
>>> print f.as_ul()
<li>Subject: <input type="text" name="subject" maxlength="100" /></li>
<li>Message: <input type="text" name="message" /></li>
<li>Sender: <input type="text" name="sender" /></li>
<li>Cc myself: <input type="checkbox" name="cc_myself" /></li>
>>> print f.as_p()
<p>Subject: <input type="text" name="subject" maxlength="100" /></p>
<p>Message: <input type="text" name="message" /></p>
<p>Sender: <input type="text" name="sender" /></p>
<p>Cc myself: <input type="checkbox" name="cc_myself" /></p>
If ``auto_id`` is set to ``True``, then the form output *will* include
``<label>`` tags and will simply use the field name as its ``id`` for each form
field::
>>> f = ContactForm(auto_id=True)
>>> print f.as_table()
<tr><th><label for="subject">Subject:</label></th><td><input id="subject" type="text" name="subject" maxlength="100" /></td></tr>
<tr><th><label for="message">Message:</label></th><td><input type="text" name="message" id="message" /></td></tr>
<tr><th><label for="sender">Sender:</label></th><td><input type="text" name="sender" id="sender" /></td></tr>
<tr><th><label for="cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="cc_myself" /></td></tr>
>>> print f.as_ul()
<li><label for="subject">Subject:</label> <input id="subject" type="text" name="subject" maxlength="100" /></li>
<li><label for="message">Message:</label> <input type="text" name="message" id="message" /></li>
<li><label for="sender">Sender:</label> <input type="text" name="sender" id="sender" /></li>
<li><label for="cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="cc_myself" /></li>
>>> print f.as_p()
<p><label for="subject">Subject:</label> <input id="subject" type="text" name="subject" maxlength="100" /></p>
<p><label for="message">Message:</label> <input type="text" name="message" id="message" /></p>
<p><label for="sender">Sender:</label> <input type="text" name="sender" id="sender" /></p>
<p><label for="cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="cc_myself" /></p>
If ``auto_id`` is set to a string containing the format character ``'%s'``,
then the form output will include ``<label>`` tags, and will generate ``id``
attributes based on the format string. For example, for a format string
``'field_%s'``, a field named ``subject`` will get the ``id``
``'field_subject'``. Continuing our example::
>>> f = ContactForm(auto_id='id_for_%s')
>>> print f.as_table()
<tr><th><label for="id_for_subject">Subject:</label></th><td><input id="id_for_subject" type="text" name="subject" maxlength="100" /></td></tr>
<tr><th><label for="id_for_message">Message:</label></th><td><input type="text" name="message" id="id_for_message" /></td></tr>
<tr><th><label for="id_for_sender">Sender:</label></th><td><input type="text" name="sender" id="id_for_sender" /></td></tr>
<tr><th><label for="id_for_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_for_cc_myself" /></td></tr>
>>> print f.as_ul()
<li><label for="id_for_subject">Subject:</label> <input id="id_for_subject" type="text" name="subject" maxlength="100" /></li>
<li><label for="id_for_message">Message:</label> <input type="text" name="message" id="id_for_message" /></li>
<li><label for="id_for_sender">Sender:</label> <input type="text" name="sender" id="id_for_sender" /></li>
<li><label for="id_for_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_for_cc_myself" /></li>
>>> print f.as_p()
<p><label for="id_for_subject">Subject:</label> <input id="id_for_subject" type="text" name="subject" maxlength="100" /></p>
<p><label for="id_for_message">Message:</label> <input type="text" name="message" id="id_for_message" /></p>
<p><label for="id_for_sender">Sender:</label> <input type="text" name="sender" id="id_for_sender" /></p>
<p><label for="id_for_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_for_cc_myself" /></p>
If ``auto_id`` is set to any other true value -- such as a string that doesn't
include ``%s`` -- then the library will act as if ``auto_id`` is ``True``.
By default, ``auto_id`` is set to the string ``'id_%s'``.
Notes on field ordering
~~~~~~~~~~~~~~~~~~~~~~~
In the ``as_p()``, ``as_ul()`` and ``as_table()`` shortcuts, the fields are
displayed in the order in which you define them in your form class. For
example, in the ``ContactForm`` example, the fields are defined in the order
``subject``, ``message``, ``sender``, ``cc_myself``. To reorder the HTML
output, just change the order in which those fields are listed in the class.
Using forms to validate data
----------------------------
In addition to HTML form display, a ``Form`` class is responsible for
validating data.
More coming soon More coming soon
================ ================
@ -69,3 +296,9 @@ That's all the documentation for now. For more, see the file
http://code.djangoproject.com/browser/django/trunk/tests/regressiontests/forms/tests.py http://code.djangoproject.com/browser/django/trunk/tests/regressiontests/forms/tests.py
-- the unit tests for ``django.newforms``. This can give you a good idea of -- the unit tests for ``django.newforms``. This can give you a good idea of
what's possible. what's possible.
Using forms with templates
==========================
Using forms in views
====================

View File

@ -63,9 +63,9 @@ Via the Python API
------------------ ------------------
Redirects are represented by a standard `Django model`_, which lives in Redirects are represented by a standard `Django model`_, which lives in
`django/contrib/redirects/models/redirects.py`_. You can access redirect `django/contrib/redirects/models.py`_. You can access redirect
objects via the `Django database API`_. objects via the `Django database API`_.
.. _Django model: http://www.djangoproject.com/documentation/model_api/ .. _Django model: http://www.djangoproject.com/documentation/model_api/
.. _django/contrib/redirects/models/redirects.py: http://code.djangoproject.com/browser/django/trunk/django/contrib/redirects/models/redirects.py .. _django/contrib/redirects/models.py: http://code.djangoproject.com/browser/django/trunk/django/contrib/redirects/models.py
.. _Django database API: http://www.djangoproject.com/documentation/db_api/ .. _Django database API: http://www.djangoproject.com/documentation/db_api/

View File

@ -871,7 +871,7 @@ TIME_FORMAT and MONTH_DAY_FORMAT.
.. _cache docs: http://www.djangoproject.com/documentation/cache/ .. _cache docs: http://www.djangoproject.com/documentation/cache/
.. _middleware docs: http://www.djangoproject.com/documentation/middleware/ .. _middleware docs: http://www.djangoproject.com/documentation/middleware/
.. _session docs: http://www.djangoproject.com/documentation/sessions/ .. _session docs: http://www.djangoproject.com/documentation/sessions/
.. _See available choices: http://www.postgresql.org/docs/current/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE .. _See available choices: http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
.. _template documentation: http://www.djangoproject.com/documentation/templates_python/ .. _template documentation: http://www.djangoproject.com/documentation/templates_python/
Creating your own settings Creating your own settings

View File

@ -25,9 +25,12 @@ for dirpath, dirnames, filenames in os.walk(django_dir):
else: else:
data_files.append((dirpath, [os.path.join(dirpath, f) for f in filenames])) data_files.append((dirpath, [os.path.join(dirpath, f) for f in filenames]))
# Dynamically calculate the version based on django.VERSION.
version = "%d.%d-%s" % (__import__('django').VERSION)
setup( setup(
name = "Django", name = "Django",
version = "0.95", version = version,
url = 'http://www.djangoproject.com/', url = 'http://www.djangoproject.com/',
author = 'Lawrence Journal-World', author = 'Lawrence Journal-World',
author_email = 'holovaty@gmail.com', author_email = 'holovaty@gmail.com',

View File

@ -10,6 +10,9 @@ class Article(models.Model):
headline = models.CharField(maxlength=100, default='Default headline') headline = models.CharField(maxlength=100, default='Default headline')
pub_date = models.DateTimeField() pub_date = models.DateTimeField()
class Meta:
ordering = ('pub_date','headline')
def __str__(self): def __str__(self):
return self.headline return self.headline
@ -245,7 +248,7 @@ datetime.datetime(2005, 7, 28, 0, 0)
# Slices (without step) are lazy: # Slices (without step) are lazy:
>>> Article.objects.all()[0:5].filter() >>> Article.objects.all()[0:5].filter()
[<Article: Area woman programs in Python>, <Article: Second article>, <Article: Third article>, <Article: Fourth article>, <Article: Article 6>] [<Article: Area woman programs in Python>, <Article: Second article>, <Article: Third article>, <Article: Article 6>, <Article: Default headline>]
# Slicing again works: # Slicing again works:
>>> Article.objects.all()[0:5][0:2] >>> Article.objects.all()[0:5][0:2]
@ -253,17 +256,17 @@ datetime.datetime(2005, 7, 28, 0, 0)
>>> Article.objects.all()[0:5][:2] >>> Article.objects.all()[0:5][:2]
[<Article: Area woman programs in Python>, <Article: Second article>] [<Article: Area woman programs in Python>, <Article: Second article>]
>>> Article.objects.all()[0:5][4:] >>> Article.objects.all()[0:5][4:]
[<Article: Article 6>] [<Article: Default headline>]
>>> Article.objects.all()[0:5][5:] >>> Article.objects.all()[0:5][5:]
[] []
# Some more tests! # Some more tests!
>>> Article.objects.all()[2:][0:2] >>> Article.objects.all()[2:][0:2]
[<Article: Third article>, <Article: Fourth article>] [<Article: Third article>, <Article: Article 6>]
>>> Article.objects.all()[2:][:2] >>> Article.objects.all()[2:][:2]
[<Article: Third article>, <Article: Fourth article>] [<Article: Third article>, <Article: Article 6>]
>>> Article.objects.all()[2:][2:3] >>> Article.objects.all()[2:][2:3]
[<Article: Article 6>] [<Article: Default headline>]
# Note that you can't use 'offset' without 'limit' (on some dbs), so this doesn't work: # Note that you can't use 'offset' without 'limit' (on some dbs), so this doesn't work:
>>> Article.objects.all()[2:] >>> Article.objects.all()[2:]
@ -312,7 +315,7 @@ AttributeError: Manager isn't accessible via Article instances
# Bulk delete test: How many objects before and after the delete? # Bulk delete test: How many objects before and after the delete?
>>> Article.objects.all() >>> Article.objects.all()
[<Article: Area woman programs in Python>, <Article: Second article>, <Article: Third article>, <Article: Fourth article>, <Article: Article 6>, <Article: Default headline>, <Article: Article 7>, <Article: Updated article 8>] [<Article: Area woman programs in Python>, <Article: Second article>, <Article: Third article>, <Article: Article 6>, <Article: Default headline>, <Article: Fourth article>, <Article: Article 7>, <Article: Updated article 8>]
>>> Article.objects.filter(id__lte=4).delete() >>> Article.objects.filter(id__lte=4).delete()
>>> Article.objects.all() >>> Article.objects.all()
[<Article: Article 6>, <Article: Default headline>, <Article: Article 7>, <Article: Updated article 8>] [<Article: Article 6>, <Article: Default headline>, <Article: Article 7>, <Article: Updated article 8>]

View File

@ -231,4 +231,16 @@ __test__ = {'API_TESTS':"""
>>> p1.article_set.all() >>> p1.article_set.all()
[<Article: NASA uses Python>] [<Article: NASA uses Python>]
# An alternate to calling clear() is to assign the empty set
>>> p1.article_set = []
>>> p1.article_set.all()
[]
>>> a2.publications = [p1, new_publication]
>>> a2.publications.all()
[<Publication: Highlights for Children>, <Publication: The Python Journal>]
>>> a2.publications = []
>>> a2.publications.all()
[]
"""} """}

View File

View File

@ -0,0 +1,184 @@
"""
34. Generating HTML forms from models
Django provides shortcuts for creating Form objects from a model class.
The function django.newforms.form_for_model() takes a model class and returns
a Form that is tied to the model. This Form works just like any other Form,
with one additional method: create(). The create() method creates an instance
of the model and returns that newly created instance. It saves the instance to
the database if create(save=True), which is default. If you pass
create(save=False), then you'll get the object without saving it.
"""
from django.db import models
class Category(models.Model):
name = models.CharField(maxlength=20)
url = models.CharField('The URL', maxlength=40)
def __str__(self):
return self.name
class Writer(models.Model):
name = models.CharField(maxlength=50)
def __str__(self):
return self.name
class Article(models.Model):
headline = models.CharField(maxlength=50)
pub_date = models.DateTimeField()
writer = models.ForeignKey(Writer)
categories = models.ManyToManyField(Category, blank=True)
def __str__(self):
return self.headline
__test__ = {'API_TESTS': """
>>> from django.newforms import form_for_model, form_for_instance, BaseForm
>>> import datetime
>>> Category.objects.all()
[]
>>> CategoryForm = form_for_model(Category)
>>> f = CategoryForm()
>>> print f
<tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" maxlength="20" /></td></tr>
<tr><th><label for="id_url">The URL:</label></th><td><input id="id_url" type="text" name="url" maxlength="40" /></td></tr>
>>> print f.as_ul()
<li><label for="id_name">Name:</label> <input id="id_name" type="text" name="name" maxlength="20" /></li>
<li><label for="id_url">The URL:</label> <input id="id_url" type="text" name="url" maxlength="40" /></li>
>>> print f['name']
<input id="id_name" type="text" name="name" maxlength="20" />
>>> f = CategoryForm(auto_id=False)
>>> print f.as_ul()
<li>Name: <input type="text" name="name" maxlength="20" /></li>
<li>The URL: <input type="text" name="url" maxlength="40" /></li>
>>> f = CategoryForm({'name': 'Entertainment', 'url': 'entertainment'})
>>> f.errors
{}
>>> f.clean_data
{'url': u'entertainment', 'name': u'Entertainment'}
>>> obj = f.create()
>>> obj
<Category: Entertainment>
>>> Category.objects.all()
[<Category: Entertainment>]
>>> f = CategoryForm({'name': "It's a test", 'url': 'test'})
>>> f.errors
{}
>>> f.clean_data
{'url': u'test', 'name': u"It's a test"}
>>> obj = f.create()
>>> obj
<Category: It's a test>
>>> Category.objects.all()
[<Category: Entertainment>, <Category: It's a test>]
>>> f = CategoryForm({'name': 'Third test', 'url': 'third'})
>>> f.errors
{}
>>> f.clean_data
{'url': u'third', 'name': u'Third test'}
>>> obj = f.create(save=False)
>>> obj
<Category: Third test>
>>> Category.objects.all()
[<Category: Entertainment>, <Category: It's a test>]
>>> obj.save()
>>> Category.objects.all()
[<Category: Entertainment>, <Category: It's a test>, <Category: Third test>]
>>> f = CategoryForm({'name': '', 'url': 'foo'})
>>> f.errors
{'name': [u'This field is required.']}
>>> f.clean_data
>>> f.create()
Traceback (most recent call last):
...
ValueError: The Category could not be created because the data didn't validate.
>>> f = CategoryForm({'name': '', 'url': 'foo'})
>>> f.create()
Traceback (most recent call last):
...
ValueError: The Category could not be created because the data didn't validate.
Create a couple of Writers.
>>> w = Writer(name='Mike Royko')
>>> w.save()
>>> w = Writer(name='Bob Woodward')
>>> w.save()
ManyToManyFields are represented by a MultipleChoiceField, and ForeignKeys are
represented by a ChoiceField.
>>> ArticleForm = form_for_model(Article)
>>> f = ArticleForm(auto_id=False)
>>> print f
<tr><th>Headline:</th><td><input type="text" name="headline" maxlength="50" /></td></tr>
<tr><th>Pub date:</th><td><input type="text" name="pub_date" /></td></tr>
<tr><th>Writer:</th><td><select name="writer">
<option value="" selected="selected">---------</option>
<option value="1">Mike Royko</option>
<option value="2">Bob Woodward</option>
</select></td></tr>
<tr><th>Categories:</th><td><select multiple="multiple" name="categories">
<option value="1">Entertainment</option>
<option value="2">It&#39;s a test</option>
<option value="3">Third test</option>
</select></td></tr>
You can pass a custom Form class to form_for_model. Make sure it's a
subclass of BaseForm, not Form.
>>> class CustomForm(BaseForm):
... def say_hello(self):
... print 'hello'
>>> CategoryForm = form_for_model(Category, form=CustomForm)
>>> f = CategoryForm()
>>> f.say_hello()
hello
Use form_for_instance to create a Form from a model instance. There are two
differences between this Form and one created via form_for_model. First, the
object's current values are inserted as 'initial' data in each Field. Second,
the Form gets an apply_changes() method instead of a create() method.
>>> w = Writer.objects.get(name='Mike Royko')
>>> RoykoForm = form_for_instance(w)
>>> f = RoykoForm(auto_id=False)
>>> print f
<tr><th>Name:</th><td><input type="text" name="name" value="Mike Royko" maxlength="50" /></td></tr>
>>> art = Article(headline='Test article', pub_date=datetime.date(1988, 1, 4), writer=w)
>>> art.save()
>>> art.id
1
>>> TestArticleForm = form_for_instance(art)
>>> f = TestArticleForm(auto_id=False)
>>> print f.as_ul()
<li>Headline: <input type="text" name="headline" value="Test article" maxlength="50" /></li>
<li>Pub date: <input type="text" name="pub_date" value="1988-01-04" /></li>
<li>Writer: <select name="writer">
<option value="">---------</option>
<option value="1" selected="selected">Mike Royko</option>
<option value="2">Bob Woodward</option>
</select></li>
<li>Categories: <select multiple="multiple" name="categories">
<option value="1">Entertainment</option>
<option value="2">It&#39;s a test</option>
<option value="3">Third test</option>
</select></li>
>>> f = TestArticleForm({'headline': u'New headline', 'pub_date': u'1988-01-04', 'writer': u'1'})
>>> f.is_valid()
True
>>> new_art = f.apply_changes()
>>> new_art.id
1
>>> new_art = Article.objects.get(id=1)
>>> new_art.headline
'New headline'
"""}

File diff suppressed because it is too large Load Diff

View File

@ -40,12 +40,12 @@ def get_invalid_models():
if f.startswith('invalid'): if f.startswith('invalid'):
models.append((loc, f)) models.append((loc, f))
return models return models
class InvalidModelTestCase(unittest.TestCase): class InvalidModelTestCase(unittest.TestCase):
def __init__(self, model_label): def __init__(self, model_label):
unittest.TestCase.__init__(self) unittest.TestCase.__init__(self)
self.model_label = model_label self.model_label = model_label
def runTest(self): def runTest(self):
from django.core import management from django.core import management
from django.db.models.loading import load_app from django.db.models.loading import load_app
@ -55,7 +55,7 @@ class InvalidModelTestCase(unittest.TestCase):
module = load_app(self.model_label) module = load_app(self.model_label)
except Exception, e: except Exception, e:
self.fail('Unable to load invalid model module') self.fail('Unable to load invalid model module')
s = StringIO() s = StringIO()
count = management.get_validation_errors(s, module) count = management.get_validation_errors(s, module)
s.seek(0) s.seek(0)
@ -71,39 +71,43 @@ class InvalidModelTestCase(unittest.TestCase):
def django_tests(verbosity, tests_to_run): def django_tests(verbosity, tests_to_run):
from django.conf import settings from django.conf import settings
from django.db.models.loading import get_apps, load_app
old_installed_apps = settings.INSTALLED_APPS old_installed_apps = settings.INSTALLED_APPS
old_test_database_name = settings.TEST_DATABASE_NAME old_test_database_name = settings.TEST_DATABASE_NAME
old_root_urlconf = settings.ROOT_URLCONF old_root_urlconf = settings.ROOT_URLCONF
old_template_dirs = settings.TEMPLATE_DIRS old_template_dirs = settings.TEMPLATE_DIRS
old_use_i18n = settings.USE_I18N
# Redirect some settings for the duration of these tests # Redirect some settings for the duration of these tests
settings.TEST_DATABASE_NAME = TEST_DATABASE_NAME settings.TEST_DATABASE_NAME = TEST_DATABASE_NAME
settings.INSTALLED_APPS = ALWAYS_INSTALLED_APPS settings.INSTALLED_APPS = ALWAYS_INSTALLED_APPS
settings.ROOT_URLCONF = 'urls' settings.ROOT_URLCONF = 'urls'
settings.TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), TEST_TEMPLATE_DIR),) settings.TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), TEST_TEMPLATE_DIR),)
settings.USE_I18N = True
# load all the ALWAYS_INSTALLED_APPS
# Load all the ALWAYS_INSTALLED_APPS.
# (This import statement is intentionally delayed until after we
# access settings because of the USE_I18N dependency.)
from django.db.models.loading import get_apps, load_app
get_apps() get_apps()
# Load all the test model apps # Load all the test model apps
test_models = [] test_models = []
for model_dir, model_name in get_test_models(): for model_dir, model_name in get_test_models():
model_label = '.'.join([model_dir, model_name]) model_label = '.'.join([model_dir, model_name])
try: try:
# if the model was named on the command line, or # if the model was named on the command line, or
# no models were named (i.e., run all), import # no models were named (i.e., run all), import
# this model and add it to the list to test. # this model and add it to the list to test.
if not tests_to_run or model_name in tests_to_run: if not tests_to_run or model_name in tests_to_run:
if verbosity >= 1: if verbosity >= 1:
print "Importing model %s" % model_name print "Importing model %s" % model_name
mod = load_app(model_label) mod = load_app(model_label)
settings.INSTALLED_APPS.append(model_label) settings.INSTALLED_APPS.append(model_label)
test_models.append(mod) test_models.append(mod)
except Exception, e: except Exception, e:
sys.stderr.write("Error while importing %s:" % model_name + ''.join(traceback.format_exception(*sys.exc_info())[1:])) sys.stderr.write("Error while importing %s:" % model_name + ''.join(traceback.format_exception(*sys.exc_info())[1:]))
continue continue
# Add tests for invalid models # Add tests for invalid models
extra_tests = [] extra_tests = []
@ -111,28 +115,29 @@ def django_tests(verbosity, tests_to_run):
model_label = '.'.join([model_dir, model_name]) model_label = '.'.join([model_dir, model_name])
if not tests_to_run or model_name in tests_to_run: if not tests_to_run or model_name in tests_to_run:
extra_tests.append(InvalidModelTestCase(model_label)) extra_tests.append(InvalidModelTestCase(model_label))
# Run the test suite, including the extra validation tests. # Run the test suite, including the extra validation tests.
from django.test.simple import run_tests from django.test.simple import run_tests
run_tests(test_models, verbosity, extra_tests=extra_tests) run_tests(test_models, verbosity, extra_tests=extra_tests)
# Restore the old settings # Restore the old settings
settings.INSTALLED_APPS = old_installed_apps settings.INSTALLED_APPS = old_installed_apps
settings.TESTS_DATABASE_NAME = old_test_database_name settings.TESTS_DATABASE_NAME = old_test_database_name
settings.ROOT_URLCONF = old_root_urlconf settings.ROOT_URLCONF = old_root_urlconf
settings.TEMPLATE_DIRS = old_template_dirs settings.TEMPLATE_DIRS = old_template_dirs
settings.USE_I18N = old_use_i18n
if __name__ == "__main__": if __name__ == "__main__":
from optparse import OptionParser from optparse import OptionParser
usage = "%prog [options] [model model model ...]" usage = "%prog [options] [model model model ...]"
parser = OptionParser(usage=usage) parser = OptionParser(usage=usage)
parser.add_option('-v','--verbosity', action='store', dest='verbosity', default='0', parser.add_option('-v','--verbosity', action='store', dest='verbosity', default='0',
type='choice', choices=['0', '1', '2'], type='choice', choices=['0', '1', '2'],
help='Verbosity level; 0=minimal output, 1=normal output, 2=all output') help='Verbosity level; 0=minimal output, 1=normal output, 2=all output')
parser.add_option('--settings', parser.add_option('--settings',
help='Python path to settings module, e.g. "myproject.settings". If this isn\'t provided, the DJANGO_SETTINGS_MODULE environment variable will be used.') help='Python path to settings module, e.g. "myproject.settings". If this isn\'t provided, the DJANGO_SETTINGS_MODULE environment variable will be used.')
options, args = parser.parse_args() options, args = parser.parse_args()
if options.settings: if options.settings:
os.environ['DJANGO_SETTINGS_MODULE'] = options.settings os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
django_tests(int(options.verbosity), args) django_tests(int(options.verbosity), args)