Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

[multi-db] Merged trunk to [4188]. Some tests still failing.

  • Loading branch information...
commit 694f44f600335c30189cbcee73c4d133881c54d2 1 parent 55d7f65
JP authored December 10, 2006
2  django/contrib/admin/templates/admin/search_form.html
@@ -7,7 +7,7 @@
7 7
 <input type="text" size="40" name="{{ search_var }}" value="{{ cl.query|escape }}" id="searchbar" />
8 8
 <input type="submit" value="{% trans 'Go' %}" />
9 9
 {% if show_result_count %}
10  
-    <span class="small quiet">{% blocktrans count cl.result_count as counter %}1 result{% plural %}{{ counter }} results{% endblocktrans %} (<a href="?">{% blocktrans with cl.full_result_count as full_result_count %}{{ full_result_count }} total{% endblocktrans %}</a>)</span>
  10
+    <span class="small quiet">{% blocktrans count cl.result_count as counter %}1 result{% plural %}{{ counter }} results{% endblocktrans %} (<a href="?{% if cl.is_popup %}pop=1{% endif %}">{% blocktrans with cl.full_result_count as full_result_count %}{{ full_result_count }} total{% endblocktrans %}</a>)</span>
11 11
 {% endif %}
12 12
 {% for pair in cl.params.items %}
13 13
     {% ifnotequal pair.0 search_var %}<input type="hidden" name="{{ pair.0|escape }}" value="{{ pair.1|escape }}"/>{% endifnotequal %}
14  django/contrib/admin/views/main.py
@@ -226,7 +226,7 @@ def index(request):
226 226
 def add_stage(request, app_label, model_name, show_delete=False, form_url='', post_url=None, post_url_continue='../%s/', object_id_override=None):
227 227
     model = models.get_model(app_label, model_name)
228 228
     if model is None:
229  
-        raise Http404, "App %r, model %r, not found" % (app_label, model_name)
  229
+        raise Http404("App %r, model %r, not found" % (app_label, model_name))
230 230
     opts = model._meta
231 231
 
232 232
     if not request.user.has_perm(app_label + '.' + opts.get_add_permission()):
@@ -302,7 +302,7 @@ def change_stage(request, app_label, model_name, object_id):
302 302
     model = models.get_model(app_label, model_name)
303 303
     object_id = unquote(object_id)
304 304
     if model is None:
305  
-        raise Http404, "App %r, model %r, not found" % (app_label, model_name)
  305
+        raise Http404("App %r, model %r, not found" % (app_label, model_name))
306 306
     opts = model._meta
307 307
 
308 308
     if not request.user.has_perm(app_label + '.' + opts.get_change_permission()):
@@ -313,8 +313,8 @@ def change_stage(request, app_label, model_name, object_id):
313 313
 
314 314
     try:
315 315
         manipulator = model.ChangeManipulator(object_id)
316  
-    except ObjectDoesNotExist:
317  
-        raise Http404
  316
+    except model.DoesNotExist:
  317
+        raise Http404('%s object with primary key %r does not exist' % (model_name, escape(object_id)))
318 318
 
319 319
     if request.POST:
320 320
         new_data = request.POST.copy()
@@ -490,7 +490,7 @@ def delete_stage(request, app_label, model_name, object_id):
490 490
     model = models.get_model(app_label, model_name)
491 491
     object_id = unquote(object_id)
492 492
     if model is None:
493  
-        raise Http404, "App %r, model %r, not found" % (app_label, model_name)
  493
+        raise Http404("App %r, model %r, not found" % (app_label, model_name))
494 494
     opts = model._meta
495 495
     if not request.user.has_perm(app_label + '.' + opts.get_delete_permission()):
496 496
         raise PermissionDenied
@@ -527,7 +527,7 @@ def history(request, app_label, model_name, object_id):
527 527
     model = models.get_model(app_label, model_name)
528 528
     object_id = unquote(object_id)
529 529
     if model is None:
530  
-        raise Http404, "App %r, model %r, not found" % (app_label, model_name)
  530
+        raise Http404("App %r, model %r, not found" % (app_label, model_name))
531 531
     action_list = LogEntry.objects.filter(object_id=object_id,
532 532
         content_type__id__exact=ContentType.objects.get_for_model(model).id).select_related().order_by('action_time')
533 533
     # If no history was found, see whether this object even exists.
@@ -743,7 +743,7 @@ def url_for_result(self, result):
743 743
 def change_list(request, app_label, model_name):
744 744
     model = models.get_model(app_label, model_name)
745 745
     if model is None:
746  
-        raise Http404, "App %r, model %r, not found" % (app_label, model_name)
  746
+        raise Http404("App %r, model %r, not found" % (app_label, model_name))
747 747
     if not request.user.has_perm(app_label + '.' + model._meta.get_change_permission()):
748 748
         raise PermissionDenied
749 749
     try:
11  django/contrib/contenttypes/management.py
@@ -3,9 +3,9 @@
3 3
 """
4 4
 
5 5
 from django.dispatch import dispatcher
6  
-from django.db.models import get_models, signals
  6
+from django.db.models import get_apps, get_models, signals
7 7
 
8  
-def create_contenttypes(app, created_models, verbosity):
  8
+def create_contenttypes(app, created_models, verbosity=2):
9 9
     from django.contrib.contenttypes.models import ContentType
10 10
     app_models = get_models(app)
11 11
     if not app_models:
@@ -22,4 +22,11 @@ def create_contenttypes(app, created_models, verbosity):
22 22
             if verbosity >= 2:
23 23
                 print "Adding content type '%s | %s'" % (ct.app_label, ct.model)
24 24
 
  25
+def create_all_contenttypes(verbosity=2):
  26
+    for app in get_apps():
  27
+        create_contenttypes(app, None, verbosity)
  28
+
25 29
 dispatcher.connect(create_contenttypes, signal=signals.post_syncdb)
  30
+
  31
+if __name__ == "__main__":
  32
+    create_all_contenttypes()
160  django/contrib/formtools/preview.py
... ...
@@ -0,0 +1,160 @@
  1
+"""
  2
+Formtools Preview application.
  3
+
  4
+This is an abstraction of the following workflow:
  5
+
  6
+    "Display an HTML form, force a preview, then do something with the submission."
  7
+
  8
+Given a django.newforms.Form object that you define, this takes care of the
  9
+following:
  10
+
  11
+    * Displays the form as HTML on a Web page.
  12
+    * Validates the form data once it's submitted via POST.
  13
+        * If it's valid, displays a preview page.
  14
+        * If it's not valid, redisplays the form with error messages.
  15
+    * At the preview page, if the preview confirmation button is pressed, calls
  16
+      a hook that you define -- a done() method.
  17
+
  18
+The framework enforces the required preview by passing a shared-secret hash to
  19
+the preview page. If somebody tweaks the form parameters on the preview page,
  20
+the form submission will fail the hash comparison test.
  21
+
  22
+Usage
  23
+=====
  24
+
  25
+Subclass FormPreview and define a done() method:
  26
+
  27
+    def done(self, request, clean_data):
  28
+        # ...
  29
+
  30
+This method takes an HttpRequest object and a dictionary of the form data after
  31
+it has been validated and cleaned. It should return an HttpResponseRedirect.
  32
+
  33
+Then, just instantiate your FormPreview subclass by passing it a Form class,
  34
+and pass that to your URLconf, like so:
  35
+
  36
+    (r'^post/$', MyFormPreview(MyForm)),
  37
+
  38
+The FormPreview class has a few other hooks. See the docstrings in the source
  39
+code below.
  40
+
  41
+The framework also uses two templates: 'formtools/preview.html' and
  42
+'formtools/form.html'. You can override these by setting 'preview_template' and
  43
+'form_template' attributes on your FormPreview subclass. See
  44
+django/contrib/formtools/templates for the default templates.
  45
+"""
  46
+
  47
+from django.conf import settings
  48
+from django.core.exceptions import ImproperlyConfigured
  49
+from django.http import Http404
  50
+from django.shortcuts import render_to_response
  51
+import cPickle as pickle
  52
+import md5
  53
+
  54
+AUTO_ID = 'formtools_%s' # Each form here uses this as its auto_id parameter.
  55
+
  56
+class FormPreview(object):
  57
+    preview_template = 'formtools/preview.html'
  58
+    form_template = 'formtools/form.html'
  59
+
  60
+    # METHODS SUBCLASSES SHOULDN'T OVERRIDE ###################################
  61
+
  62
+    def __init__(self, form):
  63
+        # form should be a Form class, not an instance.
  64
+        self.form, self.state = form, {}
  65
+
  66
+    def __call__(self, request, *args, **kwargs):
  67
+        stage = {'1': 'preview', '2': 'post'}.get(request.POST.get(self.unused_name('stage')), 'preview')
  68
+        self.parse_params(*args, **kwargs)
  69
+        try:
  70
+            method = getattr(self, stage + '_' + request.method.lower())
  71
+        except AttributeError:
  72
+            raise Http404
  73
+        return method(request)
  74
+
  75
+    def unused_name(self, name):
  76
+        """
  77
+        Given a first-choice name, adds an underscore to the name until it
  78
+        reaches a name that isn't claimed by any field in the form.
  79
+
  80
+        This is calculated rather than being hard-coded so that no field names
  81
+        are off-limits for use in the form.
  82
+        """
  83
+        while 1:
  84
+            try:
  85
+                f = self.form.fields[name]
  86
+            except KeyError:
  87
+                break # This field name isn't being used by the form.
  88
+            name += '_'
  89
+        return name
  90
+
  91
+    def preview_get(self, request):
  92
+        "Displays the form"
  93
+        f = self.form(auto_id=AUTO_ID)
  94
+        return render_to_response(self.form_template, {'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state})
  95
+
  96
+    def preview_post(self, request):
  97
+        "Validates the POST data. If valid, displays the preview page. Else, redisplays form."
  98
+        f = self.form(request.POST, auto_id=AUTO_ID)
  99
+        context = {'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state}
  100
+        if f.is_valid():
  101
+            context['hash_field'] = self.unused_name('hash')
  102
+            context['hash_value'] = self.security_hash(request, f)
  103
+            return render_to_response(self.preview_template, context)
  104
+        else:
  105
+            return render_to_response(self.form_template, context)
  106
+
  107
+    def post_post(self, request):
  108
+        "Validates the POST data. If valid, calls done(). Else, redisplays form."
  109
+        f = self.form(request.POST, auto_id=AUTO_ID)
  110
+        if f.is_valid():
  111
+            if self.security_hash(request, f) != request.POST.get(self.unused_name('hash')):
  112
+                return self.failed_hash(request) # Security hash failed.
  113
+            return self.done(request, f.clean_data)
  114
+        else:
  115
+            return render_to_response(self.form_template, {'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state})
  116
+
  117
+    # METHODS SUBCLASSES MIGHT OVERRIDE IF APPROPRIATE ########################
  118
+
  119
+    def parse_params(self, *args, **kwargs):
  120
+        """
  121
+        Given captured args and kwargs from the URLconf, saves something in
  122
+        self.state and/or raises Http404 if necessary.
  123
+
  124
+        For example, this URLconf captures a user_id variable:
  125
+
  126
+            (r'^contact/(?P<user_id>\d{1,6})/$', MyFormPreview(MyForm)),
  127
+
  128
+        In this case, the kwargs variable in parse_params would be
  129
+        {'user_id': 32} for a request to '/contact/32/'. You can use that
  130
+        user_id to make sure it's a valid user and/or save it for later, for
  131
+        use in done().
  132
+        """
  133
+        pass
  134
+
  135
+    def security_hash(self, request, form):
  136
+        """
  137
+        Calculates the security hash for the given Form instance.
  138
+
  139
+        This creates a list of the form field names/values in a deterministic
  140
+        order, pickles the result with the SECRET_KEY setting and takes an md5
  141
+        hash of that.
  142
+
  143
+        Subclasses may want to take into account request-specific information
  144
+        such as the IP address.
  145
+        """
  146
+        data = [(bf.name, bf.data) for bf in form] + [settings.SECRET_KEY]
  147
+        # Use HIGHEST_PROTOCOL because it's the most efficient. It requires
  148
+        # Python 2.3, but Django requires 2.3 anyway, so that's OK.
  149
+        pickled = pickle.dumps(data, protocol=pickle.HIGHEST_PROTOCOL)
  150
+        return md5.new(pickled).hexdigest()
  151
+
  152
+    def failed_hash(self, request):
  153
+        "Returns an HttpResponse in the case of an invalid security hash."
  154
+        return self.preview_post(request)
  155
+
  156
+    # METHODS SUBCLASSES MUST OVERRIDE ########################################
  157
+
  158
+    def done(self, request, clean_data):
  159
+        "Does something with the clean_data and returns an HttpResponseRedirect."
  160
+        raise NotImplementedError('You must define a done() method on your %s subclass.' % self.__class__.__name__)
15  django/contrib/formtools/templates/formtools/form.html
... ...
@@ -0,0 +1,15 @@
  1
+{% extends "base.html" %}
  2
+
  3
+{% block content %}
  4
+
  5
+{% if form.errors %}<h1>Please correct the following errors</h1>{% else %}<h1>Submit</h1>{% endif %}
  6
+
  7
+<form action="" method="post">
  8
+<table>
  9
+{{ form }}
  10
+</table>
  11
+<input type="hidden" name="{{ stage_field }}" value="1" />
  12
+<p><input type="submit" value="Submit" /></p>
  13
+</form>
  14
+
  15
+{% endblock %}
36  django/contrib/formtools/templates/formtools/preview.html
... ...
@@ -0,0 +1,36 @@
  1
+{% extends "base.html" %}
  2
+
  3
+{% block content %}
  4
+
  5
+<h1>Preview your submission</h1>
  6
+
  7
+<table>
  8
+{% for field in form %}
  9
+<tr>
  10
+<th>{{ field.verbose_name }}:</th>
  11
+<td>{{ field.data|escape }}</td>
  12
+</tr>
  13
+{% endfor %}
  14
+</table>
  15
+
  16
+<p>Security hash: {{ hash_value }}</p>
  17
+
  18
+<form action="" method="post">
  19
+{% for field in form %}{{ field.as_hidden }}
  20
+{% endfor %}
  21
+<input type="hidden" name="{{ stage_field }}" value="2" />
  22
+<input type="hidden" name="{{ hash_field }}" value="{{ hash_value }}" />
  23
+<p><input type="submit" value="Submit" /></p>
  24
+</form>
  25
+
  26
+<h1>Or edit it again</h1>
  27
+
  28
+<form action="" method="post">
  29
+<table>
  30
+{{ form }}
  31
+</table>
  32
+<input type="hidden" name="{{ stage_field }}" value="1" />
  33
+<p><input type="submit" value="Submit changes" /></p>
  34
+</form>
  35
+
  36
+{% endblock %}
2  django/contrib/sitemaps/__init__.py
@@ -29,7 +29,7 @@ def ping_google(sitemap_url=None, ping_url=PING_URL):
29 29
 
30 30
     from django.contrib.sites.models import Site
31 31
     current_site = Site.objects.get_current()
32  
-    url = "%s%s" % (current_site.domain, sitemap)
  32
+    url = "%s%s" % (current_site.domain, sitemap_url)
33 33
     params = urllib.urlencode({'sitemap':url})
34 34
     urllib.urlopen("%s?%s" % (ping_url, params))
35 35
 
2  django/core/servers/fastcgi.py
@@ -118,6 +118,8 @@ def runfastcgi(argset=[], **kwargs):
118 118
     else:
119 119
         return fastcgi_help("ERROR: Implementation must be one of prefork or thread.")
120 120
 
  121
+    wsgi_opts['debug'] = False # Turn off flup tracebacks
  122
+
121 123
     # Prep up and go
122 124
     from django.core.handlers.wsgi import WSGIHandler
123 125
 
91  django/newforms/fields.py
@@ -2,8 +2,9 @@
2 2
 Field classes
3 3
 """
4 4
 
5  
-from util import ValidationError, DEFAULT_ENCODING, smart_unicode
6  
-from widgets import TextInput, CheckboxInput, Select, SelectMultiple
  5
+from django.utils.translation import gettext
  6
+from util import ValidationError, smart_unicode
  7
+from widgets import TextInput, PasswordInput, CheckboxInput, Select, SelectMultiple
7 8
 import datetime
8 9
 import re
9 10
 import time
@@ -31,11 +32,17 @@ class Field(object):
31 32
     # Tracks each time a Field instance is created. Used to retain order.
32 33
     creation_counter = 0
33 34
 
34  
-    def __init__(self, required=True, widget=None):
35  
-        self.required = required
  35
+    def __init__(self, required=True, widget=None, label=None):
  36
+        self.required, self.label = required, label
36 37
         widget = widget or self.widget
37 38
         if isinstance(widget, type):
38 39
             widget = widget()
  40
+
  41
+        # Hook into self.widget_attrs() for any Field-specific HTML attributes.
  42
+        extra_attrs = self.widget_attrs(widget)
  43
+        if extra_attrs:
  44
+            widget.attrs.update(extra_attrs)
  45
+
39 46
         self.widget = widget
40 47
 
41 48
         # Increase the creation counter, and save our local copy.
@@ -50,13 +57,21 @@ def clean(self, value):
50 57
         Raises ValidationError for any errors.
51 58
         """
52 59
         if self.required and value in EMPTY_VALUES:
53  
-            raise ValidationError(u'This field is required.')
  60
+            raise ValidationError(gettext(u'This field is required.'))
54 61
         return value
55 62
 
  63
+    def widget_attrs(self, widget):
  64
+        """
  65
+        Given a Widget instance (*not* a Widget class), returns a dictionary of
  66
+        any HTML attributes that should be added to the Widget, based on this
  67
+        Field.
  68
+        """
  69
+        return {}
  70
+
56 71
 class CharField(Field):
57  
-    def __init__(self, max_length=None, min_length=None, required=True, widget=None):
58  
-        Field.__init__(self, required, widget)
  72
+    def __init__(self, max_length=None, min_length=None, required=True, widget=None, label=None):
59 73
         self.max_length, self.min_length = max_length, min_length
  74
+        Field.__init__(self, required, widget, label)
60 75
 
61 76
     def clean(self, value):
62 77
         "Validates max_length and min_length. Returns a Unicode object."
@@ -64,11 +79,15 @@ def clean(self, value):
64 79
         if value in EMPTY_VALUES: value = u''
65 80
         value = smart_unicode(value)
66 81
         if self.max_length is not None and len(value) > self.max_length:
67  
-            raise ValidationError(u'Ensure this value has at most %d characters.' % self.max_length)
  82
+            raise ValidationError(gettext(u'Ensure this value has at most %d characters.') % self.max_length)
68 83
         if self.min_length is not None and len(value) < self.min_length:
69  
-            raise ValidationError(u'Ensure this value has at least %d characters.' % self.min_length)
  84
+            raise ValidationError(gettext(u'Ensure this value has at least %d characters.') % self.min_length)
70 85
         return value
71 86
 
  87
+    def widget_attrs(self, widget):
  88
+        if self.max_length is not None and isinstance(widget, (TextInput, PasswordInput)):
  89
+            return {'maxlength': str(self.max_length)}
  90
+
72 91
 class IntegerField(Field):
73 92
     def clean(self, value):
74 93
         """
@@ -81,7 +100,7 @@ def clean(self, value):
81 100
         try:
82 101
             return int(value)
83 102
         except (ValueError, TypeError):
84  
-            raise ValidationError(u'Enter a whole number.')
  103
+            raise ValidationError(gettext(u'Enter a whole number.'))
85 104
 
86 105
 DEFAULT_DATE_INPUT_FORMATS = (
87 106
     '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
@@ -92,8 +111,8 @@ def clean(self, value):
92 111
 )
93 112
 
94 113
 class DateField(Field):
95  
-    def __init__(self, input_formats=None, required=True, widget=None):
96  
-        Field.__init__(self, required, widget)
  114
+    def __init__(self, input_formats=None, required=True, widget=None, label=None):
  115
+        Field.__init__(self, required, widget, label)
97 116
         self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS
98 117
 
99 118
     def clean(self, value):
@@ -113,7 +132,7 @@ def clean(self, value):
113 132
                 return datetime.date(*time.strptime(value, format)[:3])
114 133
             except ValueError:
115 134
                 continue
116  
-        raise ValidationError(u'Enter a valid date.')
  135
+        raise ValidationError(gettext(u'Enter a valid date.'))
117 136
 
118 137
 DEFAULT_DATETIME_INPUT_FORMATS = (
119 138
     '%Y-%m-%d %H:%M:%S',     # '2006-10-25 14:30:59'
@@ -128,8 +147,8 @@ def clean(self, value):
128 147
 )
129 148
 
130 149
 class DateTimeField(Field):
131  
-    def __init__(self, input_formats=None, required=True, widget=None):
132  
-        Field.__init__(self, required, widget)
  150
+    def __init__(self, input_formats=None, required=True, widget=None, label=None):
  151
+        Field.__init__(self, required, widget, label)
133 152
         self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS
134 153
 
135 154
     def clean(self, value):
@@ -149,20 +168,20 @@ def clean(self, value):
149 168
                 return datetime.datetime(*time.strptime(value, format)[:6])
150 169
             except ValueError:
151 170
                 continue
152  
-        raise ValidationError(u'Enter a valid date/time.')
  171
+        raise ValidationError(gettext(u'Enter a valid date/time.'))
153 172
 
154 173
 class RegexField(Field):
155  
-    def __init__(self, regex, error_message=None, required=True, widget=None):
  174
+    def __init__(self, regex, error_message=None, required=True, widget=None, label=None):
156 175
         """
157 176
         regex can be either a string or a compiled regular expression object.
158 177
         error_message is an optional error message to use, if
159 178
         'Enter a valid value' is too generic for you.
160 179
         """
161  
-        Field.__init__(self, required, widget)
  180
+        Field.__init__(self, required, widget, label)
162 181
         if isinstance(regex, basestring):
163 182
             regex = re.compile(regex)
164 183
         self.regex = regex
165  
-        self.error_message = error_message or u'Enter a valid value.'
  184
+        self.error_message = error_message or gettext(u'Enter a valid value.')
166 185
 
167 186
     def clean(self, value):
168 187
         """
@@ -184,8 +203,8 @@ def clean(self, value):
184 203
     r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE)  # domain
185 204
 
186 205
 class EmailField(RegexField):
187  
-    def __init__(self, required=True, widget=None):
188  
-        RegexField.__init__(self, email_re, u'Enter a valid e-mail address.', required, widget)
  206
+    def __init__(self, required=True, widget=None, label=None):
  207
+        RegexField.__init__(self, email_re, gettext(u'Enter a valid e-mail address.'), required, widget, label)
189 208
 
190 209
 url_re = re.compile(
191 210
     r'^https?://' # http:// or https://
@@ -201,9 +220,9 @@ def __init__(self, required=True, widget=None):
201 220
     URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)'
202 221
 
203 222
 class URLField(RegexField):
204  
-    def __init__(self, required=True, verify_exists=False, widget=None,
  223
+    def __init__(self, required=True, verify_exists=False, widget=None, label=None,
205 224
             validator_user_agent=URL_VALIDATOR_USER_AGENT):
206  
-        RegexField.__init__(self, url_re, u'Enter a valid URL.', required, widget)
  225
+        RegexField.__init__(self, url_re, gettext(u'Enter a valid URL.'), required, widget, label)
207 226
         self.verify_exists = verify_exists
208 227
         self.user_agent = validator_user_agent
209 228
 
@@ -223,9 +242,9 @@ def clean(self, value):
223 242
                 req = urllib2.Request(value, None, headers)
224 243
                 u = urllib2.urlopen(req)
225 244
             except ValueError:
226  
-                raise ValidationError(u'Enter a valid URL.')
  245
+                raise ValidationError(gettext(u'Enter a valid URL.'))
227 246
             except: # urllib2.URLError, httplib.InvalidURL, etc.
228  
-                raise ValidationError(u'This URL appears to be a broken link.')
  247
+                raise ValidationError(gettext(u'This URL appears to be a broken link.'))
229 248
         return value
230 249
 
231 250
 class BooleanField(Field):
@@ -237,10 +256,10 @@ def clean(self, value):
237 256
         return bool(value)
238 257
 
239 258
 class ChoiceField(Field):
240  
-    def __init__(self, choices=(), required=True, widget=Select):
  259
+    def __init__(self, choices=(), required=True, widget=Select, label=None):
241 260
         if isinstance(widget, type):
242 261
             widget = widget(choices=choices)
243  
-        Field.__init__(self, required, widget)
  262
+        Field.__init__(self, required, widget, label)
244 263
         self.choices = choices
245 264
 
246 265
     def clean(self, value):
@@ -254,37 +273,37 @@ def clean(self, value):
254 273
             return value
255 274
         valid_values = set([str(k) for k, v in self.choices])
256 275
         if value not in valid_values:
257  
-            raise ValidationError(u'Select a valid choice. %s is not one of the available choices.' % value)
  276
+            raise ValidationError(gettext(u'Select a valid choice. %s is not one of the available choices.') % value)
258 277
         return value
259 278
 
260 279
 class MultipleChoiceField(ChoiceField):
261  
-    def __init__(self, choices=(), required=True, widget=SelectMultiple):
262  
-        ChoiceField.__init__(self, choices, required, widget)
  280
+    def __init__(self, choices=(), required=True, widget=SelectMultiple, label=None):
  281
+        ChoiceField.__init__(self, choices, required, widget, label)
263 282
 
264 283
     def clean(self, value):
265 284
         """
266 285
         Validates that the input is a list or tuple.
267 286
         """
268 287
         if self.required and not value:
269  
-            raise ValidationError(u'This field is required.')
  288
+            raise ValidationError(gettext(u'This field is required.'))
270 289
         elif not self.required and not value:
271 290
             return []
272 291
         if not isinstance(value, (list, tuple)):
273  
-            raise ValidationError(u'Enter a list of values.')
  292
+            raise ValidationError(gettext(u'Enter a list of values.'))
274 293
         new_value = []
275 294
         for val in value:
276 295
             val = smart_unicode(val)
277 296
             new_value.append(val)
278 297
         # Validate that each value in the value list is in self.choices.
279  
-        valid_values = set([k for k, v in self.choices])
  298
+        valid_values = set([smart_unicode(k) for k, v in self.choices])
280 299
         for val in new_value:
281 300
             if val not in valid_values:
282  
-                raise ValidationError(u'Select a valid choice. %s is not one of the available choices.' % val)
  301
+                raise ValidationError(gettext(u'Select a valid choice. %s is not one of the available choices.') % val)
283 302
         return new_value
284 303
 
285 304
 class ComboField(Field):
286  
-    def __init__(self, fields=(), required=True, widget=None):
287  
-        Field.__init__(self, required, widget)
  305
+    def __init__(self, fields=(), required=True, widget=None, label=None):
  306
+        Field.__init__(self, required, widget, label)
288 307
         # Set 'required' to False on the individual fields, because the
289 308
         # required validation will be handled by ComboField, not by those
290 309
         # individual fields.
103  django/newforms/forms.py
@@ -6,7 +6,7 @@
6 6
 from django.utils.html import escape
7 7
 from fields import Field
8 8
 from widgets import TextInput, Textarea, HiddenInput
9  
-from util import ErrorDict, ErrorList, ValidationError
  9
+from util import StrAndUnicode, ErrorDict, ErrorList, ValidationError
10 10
 
11 11
 NON_FIELD_ERRORS = '__all__'
12 12
 
@@ -32,7 +32,7 @@ def __new__(cls, name, bases, attrs):
32 32
         attrs['fields'] = SortedDictFromList(fields)
33 33
         return type.__new__(cls, name, bases, attrs)
34 34
 
35  
-class Form(object):
  35
+class Form(StrAndUnicode):
36 36
     "A collection of Fields, plus their associated data."
37 37
     __metaclass__ = DeclarativeFieldsMetaclass
38 38
 
@@ -43,7 +43,7 @@ def __init__(self, data=None, auto_id=False): # TODO: prefix stuff
43 43
         self.clean_data = None # Stores the data after clean() has been called.
44 44
         self.__errors = None # Stores the errors after clean() has been called.
45 45
 
46  
-    def __str__(self):
  46
+    def __unicode__(self):
47 47
         return self.as_table()
48 48
 
49 49
     def __iter__(self):
@@ -72,41 +72,44 @@ def is_valid(self):
72 72
         """
73 73
         return not self.ignore_errors and not bool(self.errors)
74 74
 
75  
-    def as_table(self):
76  
-        "Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
77  
-        output = []
78  
-        if self.errors.get(NON_FIELD_ERRORS):
79  
-            # Errors not corresponding to a particular field are displayed at the top.
80  
-            output.append(u'<tr><td colspan="2">%s</td></tr>' % self.non_field_errors())
  75
+    def _html_output(self, normal_row, error_row, row_ender, errors_on_separate_row):
  76
+        "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()."
  77
+        top_errors = self.non_field_errors() # Errors that should be displayed above all fields.
  78
+        output, hidden_fields = [], []
81 79
         for name, field in self.fields.items():
82 80
             bf = BoundField(self, field, name)
  81
+            bf_errors = bf.errors # Cache in local variable.
83 82
             if bf.is_hidden:
84  
-                if bf.errors:
85  
-                    new_errors = ErrorList(['(Hidden field %s) %s' % (name, e) for e in bf.errors])
86  
-                    output.append(u'<tr><td colspan="2">%s</td></tr>' % new_errors)
87  
-                output.append(str(bf))
  83
+                if bf_errors:
  84
+                    top_errors.extend(['(Hidden field %s) %s' % (name, e) for e in bf_errors])
  85
+                hidden_fields.append(unicode(bf))
88 86
             else:
89  
-                if bf.errors:
90  
-                    output.append(u'<tr><td colspan="2">%s</td></tr>' % bf.errors)
91  
-                output.append(u'<tr><td>%s</td><td>%s</td></tr>' % (bf.label_tag(escape(bf.verbose_name+':')), bf))
  87
+                if errors_on_separate_row and bf_errors:
  88
+                    output.append(error_row % bf_errors)
  89
+                output.append(normal_row % {'errors': bf_errors, 'label': bf.label_tag(escape(bf.label+':')), 'field': bf})
  90
+        if top_errors:
  91
+            output.insert(0, error_row % top_errors)
  92
+        if hidden_fields: # Insert any hidden fields in the last row.
  93
+            str_hidden = u''.join(hidden_fields)
  94
+            if output:
  95
+                last_row = output[-1]
  96
+                # Chop off the trailing row_ender (e.g. '</td></tr>') and insert the hidden fields.
  97
+                output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender
  98
+            else: # If there aren't any rows in the output, just append the hidden fields.
  99
+                output.append(str_hidden)
92 100
         return u'\n'.join(output)
93 101
 
  102
+    def as_table(self):
  103
+        "Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
  104
+        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)
  105
+
94 106
     def as_ul(self):
95 107
         "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
96  
-        output = []
97  
-        if self.errors.get(NON_FIELD_ERRORS):
98  
-            # Errors not corresponding to a particular field are displayed at the top.
99  
-            output.append(u'<li>%s</li>' % self.non_field_errors())
100  
-        for name, field in self.fields.items():
101  
-            bf = BoundField(self, field, name)
102  
-            if bf.is_hidden:
103  
-                if bf.errors:
104  
-                    new_errors = ErrorList(['(Hidden field %s) %s' % (name, e) for e in bf.errors])
105  
-                    output.append(u'<li>%s</li>' % new_errors)
106  
-                output.append(str(bf))
107  
-            else:
108  
-                output.append(u'<li>%s%s %s</li>' % (bf.errors, bf.label_tag(escape(bf.verbose_name+':')), bf))
109  
-        return u'\n'.join(output)
  108
+        return self._html_output(u'<li>%(errors)s%(label)s %(field)s</li>', u'<li>%s</li>', '</li>', False)
  109
+
  110
+    def as_p(self):
  111
+        "Returns this form rendered as HTML <p>s."
  112
+        return self._html_output(u'<p>%(label)s %(field)s</p>', u'<p>%s</p>', '</p>', True)
110 113
 
111 114
     def non_field_errors(self):
112 115
         """
@@ -155,18 +158,19 @@ def clean(self):
155 158
         """
156 159
         return self.clean_data
157 160
 
158  
-class BoundField(object):
  161
+class BoundField(StrAndUnicode):
159 162
     "A Field plus data"
160 163
     def __init__(self, form, field, name):
161  
-        self._form = form
162  
-        self._field = field
163  
-        self._name = name
  164
+        self.form = form
  165
+        self.field = field
  166
+        self.name = name
  167
+        self.label = self.field.label or pretty_name(name)
164 168
 
165  
-    def __str__(self):
  169
+    def __unicode__(self):
166 170
         "Renders this field as an HTML widget."
167 171
         # Use the 'widget' attribute on the field to determine which type
168 172
         # of HTML widget to use.
169  
-        value = self.as_widget(self._field.widget)
  173
+        value = self.as_widget(self.field.widget)
170 174
         if not isinstance(value, basestring):
171 175
             # Some Widget render() methods -- notably RadioSelect -- return a
172 176
             # "special" object rather than a string. Call the __str__() on that
@@ -179,10 +183,7 @@ def _errors(self):
179 183
         Returns an ErrorList for this field. Returns an empty ErrorList
180 184
         if there are none.
181 185
         """
182  
-        try:
183  
-            return self._form.errors[self._name]
184  
-        except KeyError:
185  
-            return ErrorList()
  186
+        return self.form.errors.get(self.name, ErrorList())
186 187
     errors = property(_errors)
187 188
 
188 189
     def as_widget(self, widget, attrs=None):
@@ -190,7 +191,7 @@ def as_widget(self, widget, attrs=None):
190 191
         auto_id = self.auto_id
191 192
         if auto_id and not attrs.has_key('id') and not widget.attrs.has_key('id'):
192 193
             attrs['id'] = auto_id
193  
-        return widget.render(self._name, self.data, attrs=attrs)
  194
+        return widget.render(self.name, self.data, attrs=attrs)
194 195
 
195 196
     def as_text(self, attrs=None):
196 197
         """
@@ -210,21 +211,17 @@ def as_hidden(self, attrs=None):
210 211
 
211 212
     def _data(self):
212 213
         "Returns the data for this BoundField, or None if it wasn't given."
213  
-        return self._form.data.get(self._name, None)
  214
+        return self.form.data.get(self.name, None)
214 215
     data = property(_data)
215 216
 
216  
-    def _verbose_name(self):
217  
-        return pretty_name(self._name)
218  
-    verbose_name = property(_verbose_name)
219  
-
220 217
     def label_tag(self, contents=None):
221 218
         """
222 219
         Wraps the given contents in a <label>, if the field has an ID attribute.
223 220
         Does not HTML-escape the contents. If contents aren't given, uses the
224  
-        field's HTML-escaped verbose_name.
  221
+        field's HTML-escaped label.
225 222
         """
226  
-        contents = contents or escape(self.verbose_name)
227  
-        widget = self._field.widget
  223
+        contents = contents or escape(self.label)
  224
+        widget = self.field.widget
228 225
         id_ = widget.attrs.get('id') or self.auto_id
229 226
         if id_:
230 227
             contents = '<label for="%s">%s</label>' % (widget.id_for_label(id_), contents)
@@ -232,7 +229,7 @@ def label_tag(self, contents=None):
232 229
 
233 230
     def _is_hidden(self):
234 231
         "Returns True if this BoundField's widget is hidden."
235  
-        return self._field.widget.is_hidden
  232
+        return self.field.widget.is_hidden
236 233
     is_hidden = property(_is_hidden)
237 234
 
238 235
     def _auto_id(self):
@@ -240,10 +237,10 @@ def _auto_id(self):
240 237
         Calculates and returns the ID attribute for this BoundField, if the
241 238
         associated Form has specified auto_id. Returns an empty string otherwise.
242 239
         """
243  
-        auto_id = self._form.auto_id
  240
+        auto_id = self.form.auto_id
244 241
         if auto_id and '%s' in str(auto_id):
245  
-            return str(auto_id) % self._name
  242
+            return str(auto_id) % self.name
246 243
         elif auto_id:
247  
-            return self._name
  244
+            return self.name
248 245
         return ''
249 246
     auto_id = property(_auto_id)
15  django/newforms/util.py
... ...
@@ -1,13 +1,22 @@
1  
-# Default encoding for input byte strings.
2  
-DEFAULT_ENCODING = 'utf-8' # TODO: First look at django.conf.settings, then fall back to this.
  1
+from django.conf import settings
3 2
 
4 3
 def smart_unicode(s):
5 4
     if not isinstance(s, basestring):
6 5
         s = unicode(str(s))
7 6
     elif not isinstance(s, unicode):
8  
-        s = unicode(s, DEFAULT_ENCODING)
  7
+        s = unicode(s, settings.DEFAULT_CHARSET)
9 8
     return s
10 9
 
  10
+class StrAndUnicode(object):
  11
+    """
  12
+    A class whose __str__ returns its __unicode__ as a bytestring
  13
+    according to settings.DEFAULT_CHARSET.
  14
+
  15
+    Useful as a mix-in.
  16
+    """
  17
+    def __str__(self):
  18
+        return self.__unicode__().encode(settings.DEFAULT_CHARSET)
  19
+
11 20
 class ErrorDict(dict):
12 21
     """
13 22
     A collection of errors that knows how to display itself in various formats.
10  django/newforms/widgets.py
@@ -8,7 +8,7 @@
8 8
     'Select', 'SelectMultiple', 'RadioSelect', 'CheckboxSelectMultiple',
9 9
 )
10 10
 
11  
-from util import smart_unicode
  11
+from util import StrAndUnicode, smart_unicode
12 12
 from django.utils.html import escape
13 13
 from itertools import chain
14 14
 
@@ -146,7 +146,7 @@ def render(self, name, value, attrs=None, choices=()):
146 146
         output.append(u'</select>')
147 147
         return u'\n'.join(output)
148 148
 
149  
-class RadioInput(object):
  149
+class RadioInput(StrAndUnicode):
150 150
     "An object used by RadioFieldRenderer that represents a single <input type='radio'>."
151 151
     def __init__(self, name, value, attrs, choice, index):
152 152
         self.name, self.value = name, value
@@ -154,7 +154,7 @@ def __init__(self, name, value, attrs, choice, index):
154 154
         self.choice_value, self.choice_label = choice
155 155
         self.index = index
156 156
 
157  
-    def __str__(self):
  157
+    def __unicode__(self):
158 158
         return u'<label>%s %s</label>' % (self.tag(), self.choice_label)
159 159
 
160 160
     def is_checked(self):
@@ -168,7 +168,7 @@ def tag(self):
168 168
             final_attrs['checked'] = 'checked'
169 169
         return u'<input%s />' % flatatt(final_attrs)
170 170
 
171  
-class RadioFieldRenderer(object):
  171
+class RadioFieldRenderer(StrAndUnicode):
172 172
     "An object used by RadioSelect to enable customization of radio widgets."
173 173
     def __init__(self, name, value, attrs, choices):
174 174
         self.name, self.value, self.attrs = name, value, attrs
@@ -178,7 +178,7 @@ def __iter__(self):
178 178
         for i, choice in enumerate(self.choices):
179 179
             yield RadioInput(self.name, self.value, self.attrs.copy(), choice, i)
180 180
 
181  
-    def __str__(self):
  181
+    def __unicode__(self):
182 182
         "Outputs a <ul> for this set of radio fields."
183 183
         return u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>' % w for w in self])
184 184
 
6  django/template/__init__.py
@@ -742,7 +742,11 @@ def __repr__(self):
742 742
     def encode_output(self, output):
743 743
         # Check type so that we don't run str() on a Unicode object
744 744
         if not isinstance(output, basestring):
745  
-            return str(output)
  745
+            try:
  746
+                return str(output)
  747
+            except UnicodeEncodeError:
  748
+                # If __str__() returns a Unicode object, convert it to bytestring.
  749
+                return unicode(output).encode(settings.DEFAULT_CHARSET)
746 750
         elif isinstance(output, unicode):
747 751
             return output.encode(settings.DEFAULT_CHARSET)
748 752
         else:
17  docs/add_ons.txt
@@ -48,6 +48,23 @@ See the `csrf documentation`_.
48 48
 
49 49
 .. _csrf documentation: http://www.djangoproject.com/documentation/csrf/
50 50
 
  51
+formtools
  52
+=========
  53
+
  54
+**New in Django development version**
  55
+
  56
+A set of high-level abstractions for Django forms (django.newforms).
  57
+
  58
+django.contrib.formtools.preview
  59
+--------------------------------
  60
+
  61
+An abstraction of the following workflow:
  62
+
  63
+"Display an HTML form, force a preview, then do something with the submission."
  64
+
  65
+Full documentation for this feature does not yet exist, but you can read the
  66
+code and docstrings in ``django/contrib/formtools/preview.py`` for a start.
  67
+
51 68
 humanize
52 69
 ========
53 70
 
79  docs/newforms.txt
... ...
@@ -0,0 +1,79 @@
  1
+====================
  2
+The newforms library
  3
+====================
  4
+
  5
+``django.newforms`` is a new replacement for ``django.forms``, the old Django
  6
+form/manipulator/validation framework. This document explains how to use this
  7
+new form library.
  8
+
  9
+Migration plan
  10
+==============
  11
+
  12
+``django.newforms`` currently is only available in the Django development version
  13
+-- i.e., it's not available in the Django 0.95 release. For the next Django
  14
+release, our plan is to do the following:
  15
+
  16
+    * Move the current ``django.forms`` to ``django.oldforms``. This will allow
  17
+      for an eased migration of form code. You'll just have to change your
  18
+      import statements::
  19
+
  20
+          from django import forms             # old
  21
+          from django import oldforms as forms # new
  22
+
  23
+    * Move the current ``django.newforms`` to ``django.forms``.
  24
+
  25
+    * We will remove ``django.oldforms`` in the release *after* the next Django
  26
+      release -- the release that comes after the release in which we're
  27
+      creating ``django.oldforms``.
  28
+
  29
+With this in mind, we recommend you use the following import statement when
  30
+using ``django.newforms``::
  31
+
  32
+    from django import newforms as forms
  33
+
  34
+This way, your code can refer to the ``forms`` module, and when
  35
+``django.newforms`` is renamed to ``django.forms``, you'll only have to change
  36
+your ``import`` statements.
  37
+
  38
+If you prefer "``import *``" syntax, you can do the following::
  39
+
  40
+    from django.newforms import *
  41
+
  42
+This will import all fields, widgets, form classes and other various utilities
  43
+into your local namespace. Some people find this convenient; others find it
  44
+too messy. The choice is yours.
  45
+
  46
+Overview
  47
+========
  48
+
  49
+As the ``django.forms`` system before it, ``django.newforms`` is intended to
  50
+handle HTML form display, validation and redisplay. It's what you use if you
  51
+want to perform server-side validation for an HTML form.
  52
+
  53
+The library deals with these concepts:
  54
+
  55
+    * **Widget** -- A class that corresponds to an HTML form widget, e.g.
  56
+      ``<input type="text">`` or ``<textarea>``. This handles rendering of the
  57
+      widget as HTML.
  58
+
  59
+    * **Field** -- A class that is responsible for doing validation, e.g.
  60
+      an ``EmailField`` that makes sure its data is a valid e-mail address.
  61
+
  62
+    * **Form** -- A collection of fields that knows how to validate itself and
  63
+      display itself as HTML.
  64
+
  65
+
  66
+
  67
+Using forms with templates
  68
+==========================
  69
+
  70
+Using forms in views
  71
+====================
  72
+
  73
+More coming soon
  74
+================
  75
+
  76
+That's all the documentation for now. For more, see the file
  77
+http://code.djangoproject.com/browser/django/trunk/tests/regressiontests/forms/tests.py
  78
+-- the unit tests for ``django.newforms``. This can give you a good idea of
  79
+what's possible.
2  docs/settings.txt
@@ -837,7 +837,7 @@ Default: ``Django/<version> (http://www.djangoproject.com/)``
837 837
 The string to use as the ``User-Agent`` header when checking to see if URLs
838 838
 exist (see the ``verify_exists`` option on URLField_).
839 839
 
840  
-.. URLField: ../model_api/#urlfield
  840
+.. _URLField: ../model_api/#urlfield
841 841
 
842 842
 USE_ETAGS
843 843
 ---------
0  formtools/__init__.py b/django/contrib/formtools/__init__.py
No changes.
173  tests/regressiontests/forms/tests.py
@@ -636,6 +636,9 @@
636 636
               used for this Field when displaying it. Each Field has a default
637 637
               Widget that it'll use if you don't specify this. In most cases,
638 638
               the default widget is TextInput.
  639
+    label -- A verbose name for this field, for use in displaying this field in
  640
+             a form. By default, Django will use a "pretty" version of the form
  641
+             field name, if the Field is part of a Form.
639 642
 
640 643
 Other than that, the Field subclasses have class-specific options for
641 644
 __init__(). For example, CharField has a max_length option.
@@ -1335,7 +1338,7 @@
1335 1338
 <input type="text" name="last_name" value="Lennon" />
1336 1339
 <input type="text" name="birthday" value="1940-10-9" />
1337 1340
 >>> for boundfield in p:
1338  
-...     print boundfield.verbose_name, boundfield.data
  1341
+...     print boundfield.label, boundfield.data
1339 1342
 First name John
1340 1343
 Last name Lennon
1341 1344
 Birthday 1940-10-9
@@ -1368,6 +1371,13 @@
1368 1371
 <li><ul class="errorlist"><li>This field is required.</li></ul>First name: <input type="text" name="first_name" /></li>
1369 1372
 <li><ul class="errorlist"><li>This field is required.</li></ul>Last name: <input type="text" name="last_name" /></li>
1370 1373
 <li><ul class="errorlist"><li>This field is required.</li></ul>Birthday: <input type="text" name="birthday" /></li>
  1374
+>>> print p.as_p()
  1375
+<p><ul class="errorlist"><li>This field is required.</li></ul></p>
  1376
+<p>First name: <input type="text" name="first_name" /></p>
  1377
+<p><ul class="errorlist"><li>This field is required.</li></ul></p>
  1378
+<p>Last name: <input type="text" name="last_name" /></p>
  1379
+<p><ul class="errorlist"><li>This field is required.</li></ul></p>
  1380
+<p>Birthday: <input type="text" name="birthday" /></p>
1371 1381
 
1372 1382
 If you don't pass any values to the Form's __init__(), or if you pass None,
1373 1383
 the Form won't do any validation. Form.errors will be an empty dictionary *but*
@@ -1389,6 +1399,10 @@
1389 1399
 <li>First name: <input type="text" name="first_name" /></li>
1390 1400
 <li>Last name: <input type="text" name="last_name" /></li>
1391 1401
 <li>Birthday: <input type="text" name="birthday" /></li>
  1402
+>>> print p.as_p()
  1403
+<p>First name: <input type="text" name="first_name" /></p>
  1404
+<p>Last name: <input type="text" name="last_name" /></p>
  1405
+<p>Birthday: <input type="text" name="birthday" /></p>
1392 1406
 
1393 1407
 Unicode values are handled properly.
1394 1408
 >>> p = Person({'first_name': u'John', 'last_name': u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111', 'birthday': '1940-10-9'})
@@ -1396,6 +1410,8 @@
1396 1410
 u'<tr><td>First name:</td><td><input type="text" name="first_name" value="John" /></td></tr>\n<tr><td>Last name:</td><td><input type="text" name="last_name" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" /></td></tr>\n<tr><td>Birthday:</td><td><input type="text" name="birthday" value="1940-10-9" /></td></tr>'
1397 1411
 >>> p.as_ul()
1398 1412
 u'<li>First name: <input type="text" name="first_name" value="John" /></li>\n<li>Last name: <input type="text" name="last_name" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" /></li>\n<li>Birthday: <input type="text" name="birthday" value="1940-10-9" /></li>'
  1413
+>>> p.as_p()
  1414
+u'<p>First name: <input type="text" name="first_name" value="John" /></p>\n<p>Last name: <input type="text" name="last_name" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" /></p>\n<p>Birthday: <input type="text" name="birthday" value="1940-10-9" /></p>'
1399 1415
 
1400 1416
 >>> p = Person({'last_name': u'Lennon'})
1401 1417
 >>> p.errors
@@ -1432,14 +1448,18 @@
1432 1448
 into which the field's name will be inserted. It will also put a <label> around
1433 1449
 the human-readable labels for a field.
1434 1450
 >>> p = Person(auto_id='id_%s')
1435  
->>> print p.as_ul()
1436  
-<li><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></li>
1437  
-<li><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></li>
1438  
-<li><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></li>
1439 1451
 >>> print p.as_table()
1440 1452
 <tr><td><label for="id_first_name">First name:</label></td><td><input type="text" name="first_name" id="id_first_name" /></td></tr>
1441 1453
 <tr><td><label for="id_last_name">Last name:</label></td><td><input type="text" name="last_name" id="id_last_name" /></td></tr>
1442 1454
 <tr><td><label for="id_birthday">Birthday:</label></td><td><input type="text" name="birthday" id="id_birthday" /></td></tr>
  1455
+>>> print p.as_ul()
  1456
+<li><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></li>
  1457
+<li><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></li>
  1458
+<li><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></li>
  1459
+>>> print p.as_p()
  1460
+<p><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></p>
  1461
+<p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></p>
  1462
+<p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></p>
1443 1463
 
1444 1464
 If auto_id is any True value whose str() does not contain '%s', the "id"
1445 1465
 attribute will be the name of the field.
@@ -1596,6 +1616,12 @@
1596 1616
 <li><label><input type="radio" id="id_language_0" value="P" name="language" /> Python</label></li>
1597 1617
 <li><label><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li>
1598 1618
 </ul></li>
  1619
+>>> print f.as_p()
  1620
+<p><label for="id_name">Name:</label> <input type="text" name="name" id="id_name" /></p>
  1621
+<p><label for="id_language_0">Language:</label> <ul>
  1622
+<li><label><input type="radio" id="id_language_0" value="P" name="language" /> Python</label></li>
  1623
+<li><label><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li>
  1624
+</ul></p>
1599 1625
 
1600 1626
 MultipleChoiceField is a special case, as its data is required to be a list:
1601 1627
 >>> class SongForm(Form):
@@ -1713,7 +1739,7 @@
1713 1739
 >>> f = UserRegistration({})
1714 1740
 >>> print f.as_table()
1715 1741
 <tr><td colspan="2"><ul class="errorlist"><li>This field is required.</li></ul></td></tr>
1716  
-<tr><td>Username:</td><td><input type="text" name="username" /></td></tr>
  1742
+<tr><td>Username:</td><td><input type="text" name="username" maxlength="10" /></td></tr>
1717 1743
 <tr><td colspan="2">