Django

Code

Show
Ignore:
Timestamp:
07/19/08 08:30:47 (6 months ago)
Author:
jbronn
Message:

gis: Merged revisions 7921,7926-7928,7938-7941,7945-7947,7949-7950,7952,7955-7956,7961,7964-7968,7970-7978 via svnmerge from trunk.

This includes the newforms-admin branch, and thus is backwards-incompatible. The geographic admin is _not_ in this changeset, and is forthcoming.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/branches/gis

    • Property svnmerge-integrated changed from /django/trunk:1-7917 to /django/trunk:1-7978
  • django/branches/gis/tests/modeltests/fixtures/models.py

    r6394 r7979  
    5959# Database flushing does not work on MySQL with the default storage engine 
    6060# because it requires transaction support. 
    61 if settings.DATABASE_ENGINE not in ('mysql', 'mysql_old')
     61if settings.DATABASE_ENGINE != 'mysql'
    6262    __test__['API_TESTS'] += \ 
    6363""" 
  • django/branches/gis/tests/modeltests/invalid_models/models.py

    r6920 r7979  
    66 
    77from django.db import models 
    8  
     8model_errors = "" 
    99class FieldErrors(models.Model): 
    1010    charfield = models.CharField() 
    1111    decimalfield = models.DecimalField() 
    1212    filefield = models.FileField() 
    13     prepopulate = models.CharField(max_length=10, prepopulate_from='bad') 
    1413    choices = models.CharField(max_length=10, choices='bad') 
    1514    choices2 = models.CharField(max_length=10, choices=[(1,2,3),(1,2,3)]) 
     
    117116invalid_models.fielderrors: "decimalfield": DecimalFields require a "max_digits" attribute. 
    118117invalid_models.fielderrors: "filefield": FileFields require an "upload_to" attribute. 
    119 invalid_models.fielderrors: "prepopulate": prepopulate_from should be a list or tuple. 
    120118invalid_models.fielderrors: "choices": "choices" should be iterable (e.g., a tuple or list). 
    121119invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-tuples. 
  • django/branches/gis/tests/modeltests/lookup/models.py

    r7482 r7979  
    381381 
    382382 
    383 if settings.DATABASE_ENGINE not in ('mysql', 'mysql_old')
     383if settings.DATABASE_ENGINE != 'mysql'
    384384    __test__['API_TESTS'] += r""" 
    385385# grouping and backreferences 
  • django/branches/gis/tests/modeltests/many_to_one/models.py

    r7642 r7979  
    156156 
    157157# And should work fine with the unicode that comes out of 
    158 # newforms.Form.cleaned_data 
     158# forms.Form.cleaned_data 
    159159>>> Article.objects.filter(reporter__first_name__exact='John').extra(where=["many_to_one_reporter.last_name='%s'" % u'Smith']) 
    160160[<Article: John's second story>, <Article: This is a test>] 
  • django/branches/gis/tests/modeltests/model_forms/models.py

    r7918 r7979  
    8080 
    8181__test__ = {'API_TESTS': """ 
    82 >>> from django import newforms as forms 
    83 >>> from django.newforms.models import ModelForm 
     82>>> from django import forms 
     83>>> from django.forms.models import ModelForm 
    8484>>> from django.core.files.uploadedfile import SimpleUploadedFile 
    8585 
     
    114114 
    115115>>> CategoryForm.base_fields['url'].__class__ 
    116 <class 'django.newforms.fields.BooleanField'> 
     116<class 'django.forms.fields.BooleanField'> 
    117117 
    118118 
     
    212212# Old form_for_x tests ####################################################### 
    213213 
    214 >>> from django.newforms import ModelForm, CharField 
     214>>> from django.forms import ModelForm, CharField 
    215215>>> import datetime 
    216216 
     
    606606# ModelChoiceField ############################################################ 
    607607 
    608 >>> from django.newforms import ModelChoiceField, ModelMultipleChoiceField 
     608>>> from django.forms import ModelChoiceField, ModelMultipleChoiceField 
    609609 
    610610>>> f = ModelChoiceField(Category.objects.all()) 
     
    993993>>> instance.delete() 
    994994 
     995# Media on a ModelForm ######################################################## 
     996 
     997# Similar to a regular Form class you can define custom media to be used on 
     998# the ModelForm. 
     999 
     1000>>> class ModelFormWithMedia(ModelForm): 
     1001...     class Media: 
     1002...         js = ('/some/form/javascript',) 
     1003...         css = { 
     1004...             'all': ('/some/form/css',) 
     1005...         } 
     1006...     class Meta: 
     1007...         model = PhoneNumber 
     1008>>> f = ModelFormWithMedia() 
     1009>>> print f.media 
     1010<link href="/some/form/css" type="text/css" media="all" rel="stylesheet" /> 
     1011<script type="text/javascript" src="/some/form/javascript"></script> 
     1012 
    9951013"""} 
  • django/branches/gis/tests/modeltests/test_client/views.py

    r7918 r7979  
    55from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound 
    66from django.contrib.auth.decorators import login_required, permission_required 
    7 from django.newforms.forms import Form 
    8 from django.newforms import fields 
     7from django.forms.forms import Form 
     8from django.forms import fields 
    99from django.shortcuts import render_to_response 
    1010 
  • django/branches/gis/tests/modeltests/transactions/models.py

    r7482 r7979  
    2626building_docs = getattr(settings, 'BUILDING_DOCS', False) 
    2727 
    28 if building_docs or settings.DATABASE_ENGINE not in ('mysql', 'mysql_old')
     28if building_docs or settings.DATABASE_ENGINE != 'mysql'
    2929    __test__['API_TESTS'] += """ 
    3030# the default behavior is to autocommit after each save() action 
  • django/branches/gis/tests/regressiontests/admin_scripts/tests.py

    r7918 r7979  
    5454        # Build the command line 
    5555        cmd = 'python "%s"' % script 
    56         cmd += ''.join(' %s' % arg for arg in args
     56        cmd += ''.join([' %s' % arg for arg in args]
    5757         
    5858        # Remember the old environment 
     
    109109    def assertOutput(self, stream, msg): 
    110110        "Utility assertion: assert that the given message exists in the output" 
    111         self.assertTrue(msg in stream, "'%s' does not match actual output text '%s'" % (msg, stream)) 
     111        self.failUnless(msg in stream, "'%s' does not match actual output text '%s'" % (msg, stream)) 
    112112 
    113113########################################################################## 
  • django/branches/gis/tests/regressiontests/forms/error_messages.py

    r7836 r7979  
    11# -*- coding: utf-8 -*- 
    22tests = r""" 
    3 >>> from django.newforms import * 
     3>>> from django.forms import * 
    44>>> from django.core.files.uploadedfile import SimpleUploadedFile 
    55 
  • django/branches/gis/tests/regressiontests/forms/extra.py

    r7354 r7979  
    11# -*- coding: utf-8 -*- 
    22tests = r""" 
    3 >>> from django.newforms import * 
     3>>> from django.forms import * 
    44>>> from django.utils.encoding import force_unicode 
    55>>> import datetime 
     
    1515############### 
    1616 
    17 The newforms library comes with some extra, higher-level Field and Widget 
     17The forms library comes with some extra, higher-level Field and Widget 
    1818classes that demonstrate some of the library's abilities. 
    1919 
    2020# SelectDateWidget ############################################################ 
    2121 
    22 >>> from django.newforms.extras import SelectDateWidget 
     22>>> from django.forms.extras import SelectDateWidget 
    2323>>> w = SelectDateWidget(years=('2007','2008','2009','2010','2011','2012','2013','2014','2015','2016')) 
    2424>>> print w.render('mydate', '') 
     
    425425####################################### 
    426426 
    427 >>> from django.newforms.util import ErrorList 
     427>>> from django.forms.util import ErrorList 
    428428>>> class DivErrorList(ErrorList): 
    429429...     def __unicode__(self): 
  • django/branches/gis/tests/regressiontests/forms/fields.py

    r7918 r7979  
    11# -*- coding: utf-8 -*- 
    22tests = r""" 
    3 >>> from django.newforms import * 
    4 >>> from django.newforms.widgets import RadioFieldRenderer 
     3>>> from django.forms import * 
     4>>> from django.forms.widgets import RadioFieldRenderer 
    55>>> from django.core.files.uploadedfile import SimpleUploadedFile 
    66>>> import datetime 
     
    1818 
    1919Each Field class does some sort of validation. Each Field has a clean() method, 
    20 which either raises django.newforms.ValidationError or returns the "clean" 
     20which either raises django.forms.ValidationError or returns the "clean" 
    2121data -- usually a Unicode object, but, in some rare cases, a list. 
    2222 
     
    981981# ChoiceField ################################################################# 
    982982 
    983 >>> f = ChoiceField(choices=[('1', '1'), ('2', '2')]) 
     983>>> f = ChoiceField(choices=[('1', 'One'), ('2', 'Two')]) 
    984984>>> f.clean('') 
    985985Traceback (most recent call last): 
     
    997997Traceback (most recent call last): 
    998998... 
    999 ValidationError: [u'Select a valid choice. That choice is not one of the available choices.'] 
    1000  
    1001 >>> f = ChoiceField(choices=[('1', '1'), ('2', '2')], required=False) 
     999ValidationError: [u'Select a valid choice. 3 is not one of the available choices.'] 
     1000 
     1001>>> f = ChoiceField(choices=[('1', 'One'), ('2', 'Two')], required=False) 
    10021002>>> f.clean('') 
    10031003u'' 
     
    10111011Traceback (most recent call last): 
    10121012... 
    1013 ValidationError: [u'Select a valid choice. That choice is not one of the available choices.'] 
     1013ValidationError: [u'Select a valid choice. 3 is not one of the available choices.'] 
    10141014 
    10151015>>> f = ChoiceField(choices=[('J', 'John'), ('P', 'Paul')]) 
     
    10191019Traceback (most recent call last): 
    10201020... 
    1021 ValidationError: [u'Select a valid choice. That choice is not one of the available choices.'] 
     1021ValidationError: [u'Select a valid choice. John is not one of the available choices.'] 
     1022 
     1023>>> f = ChoiceField(choices=[('Numbers', (('1', 'One'), ('2', 'Two'))), ('Letters', (('3','A'),('4','B'))), ('5','Other')]) 
     1024>>> f.clean(1) 
     1025u'1' 
     1026>>> f.clean('1') 
     1027u'1' 
     1028>>> f.clean(3) 
     1029u'3' 
     1030>>> f.clean('3') 
     1031u'3' 
     1032>>> f.clean(5) 
     1033u'5' 
     1034>>> f.clean('5') 
     1035u'5' 
     1036>>> f.clean('6') 
     1037Traceback (most recent call last): 
     1038... 
     1039ValidationError: [u'Select a valid choice. 6 is not one of the available choices.'] 
    10221040 
    10231041# NullBooleanField ############################################################ 
     
    10371055# MultipleChoiceField ######################################################### 
    10381056 
    1039 >>> f = MultipleChoiceField(choices=[('1', '1'), ('2', '2')]) 
     1057>>> f = MultipleChoiceField(choices=[('1', 'One'), ('2', 'Two')]) 
    10401058>>> f.clean('') 
    10411059Traceback (most recent call last): 
     
    10731091ValidationError: [u'Select a valid choice. 3 is not one of the available choices.'] 
    10741092 
    1075 >>> f = MultipleChoiceField(choices=[('1', '1'), ('2', '2')], required=False) 
     1093>>> f = MultipleChoiceField(choices=[('1', 'One'), ('2', 'Two')], required=False) 
    10761094>>> f.clean('') 
    10771095[] 
     
    11011119ValidationError: [u'Select a valid choice. 3 is not one of the available choices.'] 
    11021120 
     1121>>> f = MultipleChoiceField(choices=[('Numbers', (('1', 'One'), ('2', 'Two'))), ('Letters', (('3','A'),('4','B'))), ('5','Other')]) 
     1122>>> f.clean([1]) 
     1123[u'1'] 
     1124>>> f.clean(['1']) 
     1125[u'1'] 
     1126>>> f.clean([1, 5]) 
     1127[u'1', u'5'] 
     1128>>> f.clean([1, '5']) 
     1129[u'1', u'5'] 
     1130>>> f.clean(['1', 5]) 
     1131[u'1', u'5'] 
     1132>>> f.clean(['1', '5']) 
     1133[u'1', u'5'] 
     1134>>> f.clean(['6']) 
     1135Traceback (most recent call last): 
     1136... 
     1137ValidationError: [u'Select a valid choice. 6 is not one of the available choices.'] 
     1138>>> f.clean(['1','6']) 
     1139Traceback (most recent call last): 
     1140... 
     1141ValidationError: [u'Select a valid choice. 6 is not one of the available choices.'] 
     1142 
     1143 
    11031144# ComboField ################################################################## 
    11041145 
     
    11541195... 
    11551196>>> import os 
    1156 >>> from django import newforms as forms 
     1197>>> from django import forms 
    11571198>>> path = forms.__file__ 
    11581199>>> path = os.path.dirname(path) + '/' 
    11591200>>> fix_os_paths(path) 
    1160 '.../django/newforms/' 
     1201'.../django/forms/' 
    11611202>>> f = forms.FilePathField(path=path) 
    11621203>>> f.choices.sort() 
    11631204>>> fix_os_paths(f.choices) 
    1164 [('.../django/newforms/__init__.py', '__init__.py'), ('.../django/newforms/__init__.pyc', '__init__.pyc'), ('.../django/newforms/fields.py', 'fields.py'), ('.../django/newforms/fields.pyc', 'fields.pyc'), ('.../django/newforms/forms.py', 'forms.py'), ('.../django/newforms/forms.pyc', 'forms.pyc'), ('.../django/newforms/models.py', 'models.py'), ('.../django/newforms/models.pyc', 'models.pyc'), ('.../django/newforms/util.py', 'util.py'), ('.../django/newforms/util.pyc', 'util.pyc'), ('.../django/newforms/widgets.py', 'widgets.py'), ('.../django/newforms/widgets.pyc', 'widgets.pyc')] 
     1205[('.../django/forms/__init__.py', '__init__.py'), ('.../django/forms/__init__.pyc', '__init__.pyc'), ('.../django/forms/fields.py', 'fields.py'), ('.../django/forms/fields.pyc', 'fields.pyc'), ('.../django/forms/forms.py', 'forms.py'), ('.../django/forms/forms.pyc', 'forms.pyc'), ('.../django/forms/models.py', 'models.py'), ('.../django/forms/models.pyc', 'models.pyc'), ('.../django/forms/util.py', 'util.py'), ('.../django/forms/util.pyc', 'util.pyc'), ('.../django/forms/widgets.py', 'widgets.py'), ('.../django/forms/widgets.pyc', 'widgets.pyc')] 
    11651206>>> f.clean('fields.py') 
    11661207Traceback (most recent call last): 
    11671208... 
    1168 ValidationError: [u'Select a valid choice. That choice is not one of the available choices.'] 
     1209ValidationError: [u'Select a valid choice. fields.py is not one of the available choices.'] 
    11691210>>> fix_os_paths(f.clean(path + 'fields.py')) 
    1170 u'.../django/newforms/fields.py' 
     1211u'.../django/forms/fields.py' 
    11711212>>> f = forms.FilePathField(path=path, match='^.*?\.py$') 
    11721213>>> f.choices.sort() 
    11731214>>> fix_os_paths(f.choices) 
    1174 [('.../django/newforms/__init__.py', '__init__.py'), ('.../django/newforms/fields.py', 'fields.py'), ('.../django/newforms/forms.py', 'forms.py'), ('.../django/newforms/models.py', 'models.py'), ('.../django/newforms/util.py', 'util.py'), ('.../django/newforms/widgets.py', 'widgets.py')] 
     1215[('.../django/forms/__init__.py', '__init__.py'), ('.../django/forms/fields.py', 'fields.py'), ('.../django/forms/forms.py', 'forms.py'), ('.../django/forms/models.py', 'models.py'), ('.../django/forms/util.py', 'util.py'), ('.../django/forms/widgets.py', 'widgets.py')] 
    11751216>>> f = forms.FilePathField(path=path, recursive=True, match='^.*?\.py$') 
    11761217>>> f.choices.sort() 
    11771218>>> fix_os_paths(f.choices) 
    1178 [('.../django/newforms/__init__.py', '__init__.py'), ('.../django/newforms/extras/__init__.py', 'extras/__init__.py'), ('.../django/newforms/extras/widgets.py', 'extras/widgets.py'), ('.../django/newforms/fields.py', 'fields.py'), ('.../django/newforms/forms.py', 'forms.py'), ('.../django/newforms/models.py', 'models.py'), ('.../django/newforms/util.py', 'util.py'), ('.../django/newforms/widgets.py', 'widgets.py')] 
     1219[('.../django/forms/__init__.py', '__init__.py'), ('.../django/forms/extras/__init__.py', 'extras/__init__.py'), ('.../django/forms/extras/widgets.py', 'extras/widgets.py'), ('.../django/forms/fields.py', 'fields.py'), ('.../django/forms/forms.py', 'forms.py'), ('.../django/forms/models.py', 'models.py'), ('.../django/forms/util.py', 'util.py'), ('.../django/forms/widgets.py', 'widgets.py')] 
    11791220 
    11801221# SplitDateTimeField ########################################################## 
  • django/branches/gis/tests/regressiontests/forms/forms.py

    r7836 r7979  
    11# -*- coding: utf-8 -*- 
    22tests = r""" 
    3 >>> from django.newforms import * 
     3>>> from django.forms import * 
    44>>> from django.core.files.uploadedfile import SimpleUploadedFile 
    55>>> import datetime 
     
    16681668<input type="submit" /> 
    16691669</form> 
     1670 
     1671 
     1672# The empty_permitted attribute ############################################## 
     1673 
     1674Sometimes (pretty much in formsets) we want to allow a form to pass validation 
     1675if it is completely empty. We can accomplish this by using the empty_permitted 
     1676agrument to a form constructor. 
     1677 
     1678>>> class SongForm(Form): 
     1679...     artist = CharField() 
     1680...     name = CharField() 
     1681 
     1682First let's show what happens id empty_permitted=False (the default): 
     1683 
     1684>>> data = {'artist': '', 'song': ''} 
     1685 
     1686>>> form = SongForm(data, empty_permitted=False) 
     1687>>> form.is_valid() 
     1688False 
     1689>>> form.errors 
     1690{'name': [u'This field is required.'], 'artist': [u'This field is required.']} 
     1691>>> form.cleaned_data 
     1692Traceback (most recent call last): 
     1693... 
     1694AttributeError: 'SongForm' object has no attribute 'cleaned_data' 
     1695 
     1696 
     1697Now let's show what happens when empty_permitted=True and the form is empty. 
     1698 
     1699>>> form = SongForm(data, empty_permitted=True) 
     1700>>> form.is_valid() 
     1701True 
     1702>>> form.errors 
     1703{} 
     1704>>> form.cleaned_data 
     1705{} 
     1706 
     1707But if we fill in data for one of the fields, the form is no longer empty and 
     1708the whole thing must pass validation. 
     1709 
     1710>>> data = {'artist': 'The Doors', 'song': ''} 
     1711>>> form = SongForm(data, empty_permitted=False) 
     1712>>> form.is_valid() 
     1713False 
     1714>>> form.errors 
     1715{'name': [u'This field is required.']} 
     1716>>> form.cleaned_data 
     1717Traceback (most recent call last): 
     1718... 
     1719AttributeError: 'SongForm' object has no attribute 'cleaned_data' 
     1720 
     1721If a field is not given in the data then None is returned for its data. Lets 
     1722make sure that when checking for empty_permitted that None is treated 
     1723accordingly. 
     1724 
     1725>>> data = {'artist': None, 'song': ''} 
     1726>>> form = SongForm(data, empty_permitted=True) 
     1727>>> form.is_valid() 
     1728True 
     1729 
     1730However, we *really* need to be sure we are checking for None as any data in 
     1731initial that returns False on a boolean call needs to be treated literally. 
     1732 
     1733>>> class PriceForm(Form): 
     1734...     amount = FloatField() 
     1735...     qty = IntegerField() 
     1736 
     1737>>> data = {'amount': '0.0', 'qty': ''} 
     1738>>> form = PriceForm(data, initial={'amount': 0.0}, empty_permitted=True) 
     1739>>> form.is_valid() 
     1740True 
     1741 
    16701742""" 
  • django/branches/gis/tests/regressiontests/forms/models.py

    r6815 r7979  
    1616 
    1717__test__ = {'API_TESTS': """ 
    18 >>> from django.newforms import form_for_model, form_for_instance 
     18>>> from django.forms import form_for_model, form_for_instance 
    1919 
    2020# Boundary conditions on a PostitiveIntegerField ######################### 
  • django/branches/gis/tests/regressiontests/forms/regressions.py

    r7768 r7979  
    44tests = r""" 
    55It should be possible to re-use attribute dictionaries (#3810) 
    6 >>> from django.newforms import * 
     6>>> from django.forms import * 
    77>>> extra_attrs = {'class': 'special'} 
    88>>> class TestForm(Form): 
  • django/branches/gis/tests/regressiontests/forms/tests.py

    r6920 r7979  
    2727from util import tests as util_tests 
    2828from widgets import tests as widgets_tests 
     29from formsets import tests as formset_tests 
     30from media import media_tests 
    2931 
    3032__test__ = { 
     
    5456    'localflavor_za_tests': localflavor_za_tests, 
    5557    'regression_tests': regression_tests, 
     58    'formset_tests': formset_tests, 
     59    'media_tests': media_tests, 
    5660    'util_tests': util_tests, 
    5761    'widgets_tests': widgets_tests, 
  • django/branches/gis/tests/regressiontests/forms/util.py

    r7354 r7979  
    11# coding: utf-8 
    22""" 
    3 Tests for newforms/util.py module. 
     3Tests for forms/util.py module. 
    44""" 
    55 
    66tests = r""" 
    7 >>> from django.newforms.util import * 
     7>>> from django.forms.util import * 
    88>>> from django.utils.translation import ugettext_lazy 
    99 
     
    1212########### 
    1313 
    14 >>> from django.newforms.util import flatatt 
     14>>> from django.forms.util import flatatt 
    1515>>> flatatt({'id': "header"}) 
    1616u' id="header"' 
  • django/branches/gis/tests/regressiontests/forms/widgets.py

    r7768 r7979  
    11# -*- coding: utf-8 -*- 
    22tests = r""" 
    3 >>> from django.newforms import * 
    4 >>> from django.newforms.widgets import RadioFieldRenderer 
     3>>> from django.forms import * 
     4>>> from django.forms.widgets import RadioFieldRenderer 
    55>>> from django.utils.safestring import mark_safe 
    66>>> import datetime 
     
    203203u'<input type="file" class="fun" name="email" />' 
    204204 
     205Test for the behavior of _has_changed for FileInput. The value of data will 
     206more than likely come from request.FILES. The value of initial data will 
     207likely be a filename stored in the database. Since its value is of no use to 
     208a FileInput it is ignored. 
     209 
     210>>> w = FileInput() 
     211 
     212# No file was uploaded and no initial data. 
     213>>> w._has_changed(u'', None) 
     214False 
     215 
     216# A file was uploaded and no initial data. 
     217>>> w._has_changed(u'', {'filename': 'resume.txt', 'content': 'My resume'}) 
     218True 
     219 
     220# A file was not uploaded, but there is initial data 
     221>>> w._has_changed(u'resume.txt', None) 
     222False 
     223 
     224# A file was uploaded and there is initial data (file identity is not dealt 
     225# with here) 
     226>>> w._has_changed('resume.txt', {'filename': 'resume.txt', 'content': 'My resume'}) 
     227True 
     228 
    205229# Textarea Widget ############################################################# 
    206230 
     
    293317False 
    294318 
     319>>> w._has_changed(None, None) 
     320False 
     321>>> w._has_changed(None, u'') 
     322False 
     323>>> w._has_changed(u'', None) 
     324False 
     325>>> w._has_changed(u'', u'') 
     326False 
     327>>> w._has_changed(False, u'on') 
     328True 
     329>>> w._has_changed(True, u'on') 
     330False 
     331>>> w._has_changed(True, u'') 
     332True 
     333 
    295334# Select Widget ############################################################### 
    296335 
     
    420459</select> 
    421460 
     461Choices can be nested one level in order to create HTML optgroups: 
     462>>> w.choices=(('outer1', 'Outer 1'), ('Group "1"', (('inner1', 'Inner 1'), ('inner2', 'Inner 2')))) 
     463>>> print w.render('nestchoice', None) 
     464<select name="nestchoice"> 
     465<option value="outer1">Outer 1</option> 
     466<optgroup label="Group &quot;1&quot;"> 
     467<option value="inner1">Inner 1</option> 
     468<option value="inner2">Inner 2</option> 
     469</optgroup> 
     470</select> 
     471 
     472>>> print w.render('nestchoice', 'outer1') 
     473<select name="nestchoice"> 
     474<option value="outer1" selected="selected">Outer 1</option> 
     475<optgroup label="Group &quot;1&quot;"> 
     476<option value="inner1">Inner 1</option> 
     477<option value="inner2">Inner 2</option> 
     478</optgroup> 
     479</select> 
     480 
     481>>> print w.render('nestchoice', 'inner1') 
     482<select name="nestchoice"> 
     483<option value="outer1">Outer 1</option> 
     484<optgroup label="Group &quot;1&quot;"> 
     485<option value="inner1" selected="selected">Inner 1</option> 
     486<option value="inner2">Inner 2</option> 
     487</optgroup> 
     488</select> 
     489 
    422490# NullBooleanSelect Widget #################################################### 
    423491 
     
    573641>>> w.render('nums', ['ŠĐĆŽćžšđ'], choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')]) 
    574642u'<select multiple="multiple" name="nums">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" selected="selected">\u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111</option>\n<option value="\u0107\u017e\u0161\u0111">abc\u0107\u017e\u0161\u0111</option>\n</select>' 
     643 
     644# Test the usage of _has_changed 
     645>>> w._has_changed(None, None) 
     646False 
     647>>> w._has_changed([], None) 
     648False 
     649>>> w._has_changed(None, [u'1']) 
     650True 
     651>>> w._has_changed([1, 2], [u'1', u'2']) 
     652False 
     653>>> w._has_changed([1, 2], [u'1']) 
     654True 
     655>>> w._has_changed([1, 2], [u'1', u'3']) 
     656True 
     657 
     658# Choices can be nested one level in order to create HTML optgroups: 
     659>>> w.choices = (('outer1', 'Outer 1'), ('Group "1"', (('inner1', 'Inner 1'), ('inner2', 'Inner 2')))) 
     660>>> print w.render('nestchoice', None) 
     661<select multiple="multiple" name="nestchoice"> 
     662<option value="outer1">Outer 1</option> 
     663<optgroup label="Group &quot;1&quot;"> 
     664<option value="inner1">Inner 1</option> 
     665<option value="inner2">Inner 2</option> 
     666</optgroup> 
     667</select> 
     668 
     669>>> print w.render('nestchoice', ['outer1']) 
     670<select multiple="multiple" name="nestchoice"> 
     671<option value="outer1" selected="selected">Outer 1</option> 
     672<optgroup label="Group &quot;1&quot;"> 
     673<option value="inner1">Inner 1</option> 
     674<option value="inner2">Inner 2</option> 
     675</optgroup> 
     676</select> 
     677 
     678>>> print w.render('nestchoice', ['inner1']) 
     679<select multiple="multiple" name="nestchoice"> 
     680<option value="outer1">Outer 1</option> 
     681<optgroup label="Group &quot;1&quot;"> 
     682<option value="inner1" selected="selected">Inner 1</option> 
     683<option value="inner2">Inner 2</option> 
     684</optgroup> 
     685</select> 
     686 
     687>>> print w.render('nestchoice', ['outer1', 'inner2']) 
     688<select multiple="multiple" name="nestchoice"> 
     689<option value="outer1" selected="selected">Outer 1</option> 
     690<optgroup label="Group &quot;1&quot;"> 
     691<option value="inner1">Inner 1</option> 
     692<option value="inner2" selected="selected">Inner 2</option> 
     693</optgroup> 
     694</select> 
    575695 
    576696# RadioSelect Widget ########################################################## 
     
    872992</ul> 
    873993 
     994# Test the usage of _has_changed 
     995>>> w._has_changed(None, None) 
     996False 
     997>>> w._has_changed([], None) 
     998False 
     999>>> w._has_changed(None, [u'1']) 
     1000True 
     1001>>> w._has_changed([1, 2], [u'1', u'2']) 
     1002False 
     1003>>> w._has_changed([1, 2], [u'1']) 
     1004True 
     1005>>> w._has_changed([1, 2], [u'1', u'3']) 
     1006True 
     1007 
    8741008# Unicode choices are correctly rendered as HTML 
    8751009>>> w.render('nums', ['ŠĐĆŽćžšđ'], choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')]) 
     
    8961030u'<input id="bar_0" type="text" class="big" value="john" name="name_0" /><br /><input id="bar_1" type="text" class="small" value="lennon" name="name_1" />' 
    8971031 
     1032>>> w = MyMultiWidget(widgets=(TextInput(), TextInput())) 
     1033 
     1034# test with no initial data 
     1035>>> w._has_changed(None, [u'john', u'lennon']) 
     1036True 
     1037 
     1038# test when the data is the same as initial 
     1039>>> w._has_changed(u'john__lennon', [u'john', u'lennon']) 
     1040False 
     1041 
     1042# test when the first widget's data has changed 
     1043>>> w._has_changed(u'john__lennon', [u'alfred', u'lennon']) 
     1044True 
     1045 
     1046# test when the last widget's data has changed. this ensures that it is not 
     1047# short circuiting while testing the widgets. 
     1048>>> w._has_changed(u'john__lennon', [u'john', u'denver']) 
     1049True 
     1050 
    8981051# SplitDateTimeWidget ######################################################### 
    8991052 
     
    914