Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

schema-evolution: updated from trunk/HEAD (r5787)

git-svn-id: http://code.djangoproject.com/svn/django/branches/schema-evolution@5788 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 0f5a5a0594597dd10afe187e0ae8cecf11f5848b 1 parent fd77e42
Derek Anderson authored

Showing 42 changed files with 580 additions and 158 deletions. Show diff stats Hide diff stats

  1. 4  AUTHORS
  2. 19  django/contrib/auth/models.py
  3. 19  django/contrib/auth/tests.py
  4. 3  django/contrib/databrowse/plugins/fieldchoices.py
  5. 2  django/contrib/databrowse/templates/databrowse/calendar_day.html
  6. 2  django/contrib/databrowse/templates/databrowse/calendar_month.html
  7. 2  django/contrib/databrowse/templates/databrowse/choice_detail.html
  8. 2  django/contrib/databrowse/templates/databrowse/choice_list.html
  9. 2  django/contrib/databrowse/templates/databrowse/homepage.html
  10. 2  django/contrib/databrowse/templates/databrowse/model_detail.html
  11. 10  django/contrib/databrowse/templates/databrowse/object_detail.html
  12. 23  django/core/management.py
  13. 3  django/db/models/fields/__init__.py
  14. 4  django/db/models/manager.py
  15. 26  django/db/models/query.py
  16. 21  django/newforms/forms.py
  17. 33  django/newforms/widgets.py
  18. 60  django/shortcuts/__init__.py
  19. 19  django/template/loaders/app_directories.py
  20. 12  django/template/loaders/filesystem.py
  21. 2  django/test/client.py
  22. 114  django/test/simple.py
  23. 2  django/test/testcases.py
  24. 4  django/test/utils.py
  25. 23  django/utils/_os.py
  26. 7  django/utils/http.py
  27. 24  docs/authentication.txt
  28. 16  docs/contributing.txt
  29. 16  docs/db-api.txt
  30. 38  docs/model-api.txt
  31. 48  docs/testing.txt
  32. 27  tests/modeltests/get_object_or_404/models.py
  33. 24  tests/modeltests/lookup/models.py
  34. 12  tests/modeltests/test_client/models.py
  35. 3  tests/modeltests/test_client/views.py
  36. 18  tests/regressiontests/forms/tests.py
  37. 0  tests/regressiontests/model_fields/__init__.py
  38. 0  tests/regressiontests/model_fields/models.py
  39. 18  tests/regressiontests/model_fields/tests.py
  40. 48  tests/regressiontests/templates/tests.py
  41. 13  tests/regressiontests/test_client_regress/models.py
  42. 13  tests/runtests.py
4  AUTHORS
@@ -117,7 +117,7 @@ answer newbie questions, and generally made Django that much better:
117 117
     glin@seznam.cz
118 118
     martin.glueck@gmail.com
119 119
     GomoX <gomo@datafull.com>
120  
-    Mario Gonzalez <gonzalemario @t gmail.com>
  120
+    Mario Gonzalez <gonzalemario@gmail.com>
121 121
     Simon Greenhill <dev@simon.net.nz>
122 122
     Owen Griffiths
123 123
     Espen Grindhaug <http://grindhaug.org/>
@@ -214,6 +214,7 @@ answer newbie questions, and generally made Django that much better:
214 214
     plisk
215 215
     Daniel Poelzleithner <http://poelzi.org/>
216 216
     polpak@yahoo.com
  217
+    Johann Queuniet <johann.queuniet@adh.naellia.eu>
217 218
     J. Rademaker
218 219
     Michael Radziej <mir@noris.de>
219 220
     Ramiro Morales <rm0@gmx.net>
@@ -242,6 +243,7 @@ answer newbie questions, and generally made Django that much better:
242 243
     Thomas Steinacher <http://www.eggdrop.ch/>
243 244
     nowell strite
244 245
     Sundance
  246
+    SuperJared
245 247
     Radek Švarz <http://www.svarz.cz/translate/>
246 248
     Swaroop C H <http://www.swaroopch.info>
247 249
     Aaron Swartz <http://www.aaronsw.com/>
19  django/contrib/auth/models.py
@@ -7,6 +7,8 @@
7 7
 import datetime
8 8
 import urllib
9 9
 
  10
+UNUSABLE_PASSWORD = '!' # This will never be a valid hash
  11
+
10 12
 try:
11 13
     set
12 14
 except NameError:
@@ -83,11 +85,14 @@ def __unicode__(self):
83 85
         return self.name
84 86
 
85 87
 class UserManager(models.Manager):
86  
-    def create_user(self, username, email, password):
  88
+    def create_user(self, username, email, password=None):
87 89
         "Creates and saves a User with the given username, e-mail and password."
88 90
         now = datetime.datetime.now()
89 91
         user = self.model(None, username, '', '', email.strip().lower(), 'placeholder', False, True, False, now, now)
90  
-        user.set_password(password)
  92
+        if password:
  93
+            user.set_password(password)
  94
+        else:
  95
+            user.set_unusable_password()
91 96
         user.save()
92 97
         return user
93 98
 
@@ -179,6 +184,13 @@ def check_password(self, raw_password):
179 184
             return is_correct
180 185
         return check_password(raw_password, self.password)
181 186
 
  187
+    def set_unusable_password(self):
  188
+        # Sets a value that will never be a valid hash
  189
+        self.password = UNUSABLE_PASSWORD
  190
+
  191
+    def has_usable_password(self):
  192
+        return self.password != UNUSABLE_PASSWORD
  193
+
182 194
     def get_group_permissions(self):
183 195
         "Returns a list of permission strings that this user has through his/her groups."
184 196
         if not hasattr(self, '_group_perm_cache'):
@@ -268,7 +280,8 @@ def get_profile(self):
268 280
         return self._profile_cache
269 281
 
270 282
 class Message(models.Model):
271  
-    """The message system is a lightweight way to queue messages for given users. A message is associated with a User instance (so it is only applicable for registered users). There's no concept of expiration or timestamps. Messages are created by the Django admin after successful actions. For example, "The poll Foo was created successfully." is a message.
  283
+    """
  284
+    The message system is a lightweight way to queue messages for given users. A message is associated with a User instance (so it is only applicable for registered users). There's no concept of expiration or timestamps. Messages are created by the Django admin after successful actions. For example, "The poll Foo was created successfully." is a message.
272 285
     """
273 286
     user = models.ForeignKey(User)
274 287
     message = models.TextField(_('message'))
19  django/contrib/auth/tests.py
... ...
@@ -0,0 +1,19 @@
  1
+"""
  2
+>>> from models import User
  3
+>>> u = User.objects.create_user('testuser', 'test@example.com', 'testpw')
  4
+>>> u.has_usable_password()
  5
+True
  6
+>>> u.check_password('bad')
  7
+False
  8
+>>> u.check_password('testpw')
  9
+True
  10
+>>> u.set_unusable_password()
  11
+>>> u.save()
  12
+>>> u.check_password('testpw')
  13
+False
  14
+>>> u.has_usable_password()
  15
+False
  16
+>>> u2 = User.objects.create_user('testuser2', 'test2@example.com')
  17
+>>> u2.has_usable_password()
  18
+False
  19
+"""
3  django/contrib/databrowse/plugins/fieldchoices.py
@@ -37,9 +37,10 @@ def model_index_html(self, request, model, site):
37 37
 
38 38
     def urls(self, plugin_name, easy_instance_field):
39 39
         if easy_instance_field.field in self.field_dict(easy_instance_field.model.model).values():
  40
+            field_value = smart_str(easy_instance_field.raw_value)
40 41
             return [u'%s%s/%s/%s/' % (easy_instance_field.model.url(),
41 42
                 plugin_name, easy_instance_field.field.name,
42  
-                urllib.quote(smart_str(easy_instance_field.raw_value)))]
  43
+                urllib.quote(field_value, safe=''))]
43 44
 
44 45
     def model_view(self, request, model_databrowse, url):
45 46
         self.model, self.site = model_databrowse.model, model_databrowse.site
2  django/contrib/databrowse/templates/databrowse/calendar_day.html
@@ -10,7 +10,7 @@
10 10
 
11 11
 <ul class="objectlist">
12 12
 {% for object in object_list %}
13  
-<li class="{% cycle odd,even %}"><a href="{{ object.url }}">{{ object }}</a></li>
  13
+<li class="{% cycle odd,even %}"><a href="{{ object.url }}">{{ object|escape }}</a></li>
14 14
 {% endfor %}
15 15
 </ul>
16 16
 
2  django/contrib/databrowse/templates/databrowse/calendar_month.html
@@ -10,7 +10,7 @@
10 10
 
11 11
 <ul class="objectlist">
12 12
 {% for object in object_list %}
13  
-<li class="{% cycle odd,even %}"><a href="{{ object.url }}">{{ object }}</a></li>
  13
+<li class="{% cycle odd,even %}"><a href="{{ object.url }}">{{ object|escape }}</a></li>
14 14
 {% endfor %}
15 15
 </ul>
16 16
 
2  django/contrib/databrowse/templates/databrowse/choice_detail.html
@@ -10,7 +10,7 @@
10 10
 
11 11
 <ul class="objectlist">
12 12
 {% for object in object_list %}
13  
-<li class="{% cycle odd,even %}"><a href="{{ object.url }}">{{ object }}</a></li>
  13
+<li class="{% cycle odd,even %}"><a href="{{ object.url }}">{{ object|escape }}</a></li>
14 14
 {% endfor %}
15 15
 </ul>
16 16
 
2  django/contrib/databrowse/templates/databrowse/choice_list.html
@@ -10,7 +10,7 @@
10 10
 
11 11
 <ul class="objectlist">
12 12
 {% for choice in field.choices %}
13  
-<li class="{% cycle odd,even %}"><a href="{{ choice.url }}">{{ choice.label }}</a></li>
  13
+<li class="{% cycle odd,even %}"><a href="{{ choice.url }}">{{ choice.label|escape }}</a></li>
14 14
 {% endfor %}
15 15
 </ul>
16 16
 
2  django/contrib/databrowse/templates/databrowse/homepage.html
@@ -11,7 +11,7 @@
11 11
 	  <h2><a href="{{ model.url }}">{{ model.verbose_name_plural|capfirst }}</a></h2>
12 12
 		<p>
13 13
 		{% for object in model.sample_objects %}
14  
-			<a href="{{ object.url }}">{{ object }}</a>, 
  14
+			<a href="{{ object.url }}">{{ object|escape }}</a>, 
15 15
 		{% endfor %}
16 16
 			<a class="more" href="{{ model.url }}">More &rarr;</a>
17 17
 		</p>
2  django/contrib/databrowse/templates/databrowse/model_detail.html
@@ -12,7 +12,7 @@
12 12
 
13 13
 <ul class="objectlist">
14 14
 {% for object in model.objects %}
15  
-    <li class="{% cycle odd,even %}"><a href="{{ object.url }}">{{ object }}</a></li>
  15
+    <li class="{% cycle odd,even %}"><a href="{{ object.url }}">{{ object|escape }}</a></li>
16 16
 {% endfor %}
17 17
 </ul>
18 18
 
10  django/contrib/databrowse/templates/databrowse/object_detail.html
@@ -4,9 +4,9 @@
4 4
 
5 5
 {% block content %}
6 6
 
7  
-<div id="breadcrumbs"><a href="{{ root_url }}">Home</a> / <a href="{{ object.model.url }}">{{ object.model.verbose_name_plural|capfirst }}</a> / {{ object }}</div>
  7
+<div id="breadcrumbs"><a href="{{ root_url }}">Home</a> / <a href="{{ object.model.url }}">{{ object.model.verbose_name_plural|capfirst }}</a> / {{ object|escape }}</div>
8 8
 
9  
-<h1>{{ object.model.verbose_name|capfirst }}: {{ object }}</h1>
  9
+<h1>{{ object.model.verbose_name|capfirst }}: {{ object|escape }}</h1>
10 10
 
11 11
 <table class="objectinfo">
12 12
 {% for field in object.fields %}
@@ -14,8 +14,8 @@
14 14
 <th>{{ field.field.verbose_name|capfirst }}</th>
15 15
 <td>
16 16
 {% if field.urls %}
17  
-{% for urlvalue in field.urls %}
18  
-{% if urlvalue.1 %}<a href="{{ urlvalue.1 }}">{% endif %}{{ urlvalue.0 }}{% if urlvalue.1 %}</a>{% endif %}{% if not forloop.last %}, {% endif %}
  17
+{% for value, url in field.urls %}
  18
+{% if url %}<a href="{{ url }}">{% endif %}{{ value|escape }}{% if url %}</a>{% endif %}{% if not forloop.last %}, {% endif %}
19 19
 {% endfor %}
20 20
 {% else %}None{% endif %}
21 21
 </td>
@@ -29,7 +29,7 @@
29 29
   {% if related_object.object_list %}
30 30
   <ul class="objectlist">
31 31
     {% for object in related_object.object_list %}
32  
-    <li class="{% cycle odd,even %}"><a href="{{ object.url }}">{{ object }}</a></li>
  32
+    <li class="{% cycle odd,even %}"><a href="{{ object.url }}">{{ object|escape }}</a></li>
33 33
     {% endfor %}
34 34
   </ul>
35 35
   {% else %}
23  django/core/management.py
@@ -1454,7 +1454,8 @@ def inner_run():
1454 1454
             except (AttributeError, KeyError):
1455 1455
                 error_text = str(e)
1456 1456
             sys.stderr.write(style.ERROR("Error: %s" % error_text) + '\n')
1457  
-            sys.exit(1)
  1457
+            # Need to use an OS exit because sys.exit doesn't work in a thread
  1458
+            os._exit(1)
1458 1459
         except KeyboardInterrupt:
1459 1460
             sys.exit(0)
1460 1461
     if use_reloader:
@@ -1548,16 +1549,11 @@ def runfcgi(args):
1548 1549
     runfastcgi(args)
1549 1550
 runfcgi.args = '[various KEY=val options, use `runfcgi help` for help]'
1550 1551
 
1551  
-def test(app_labels, verbosity=1):
  1552
+def test(test_labels, verbosity=1, interactive=True):
1552 1553
     "Runs the test suite for the specified applications"
1553 1554
     from django.conf import settings
1554 1555
     from django.db.models import get_app, get_apps
1555  
-
1556  
-    if len(app_labels) == 0:
1557  
-        app_list = get_apps()
1558  
-    else:
1559  
-        app_list = [get_app(app_label) for app_label in app_labels]
1560  
-
  1556
+    
1561 1557
     test_path = settings.TEST_RUNNER.split('.')
1562 1558
     # Allow for Python 2.5 relative paths
1563 1559
     if len(test_path) > 1:
@@ -1567,12 +1563,12 @@ def test(app_labels, verbosity=1):
1567 1563
     test_module = __import__(test_module_name, {}, {}, test_path[-1])
1568 1564
     test_runner = getattr(test_module, test_path[-1])
1569 1565
 
1570  
-    failures = test_runner(app_list, verbosity)
  1566
+    failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive)
1571 1567
     if failures:
1572 1568
         sys.exit(failures)
1573 1569
 
1574 1570
 test.help_doc = 'Runs the test suite for the specified applications, or the entire site if no apps are specified'
1575  
-test.args = '[--verbosity] ' + APP_ARGS
  1571
+test.args = '[--verbosity] [--noinput]' + APP_ARGS
1576 1572
 
1577 1573
 def load_data(fixture_labels, verbosity=1):
1578 1574
     "Installs the provided fixture file(s) as data in the database."
@@ -1849,7 +1845,12 @@ def execute_from_command_line(action_mapping=DEFAULT_ACTION_MAPPING, argv=None):
1849 1845
             action_mapping[action](args[1])
1850 1846
         except IndexError:
1851 1847
             parser.print_usage_and_exit()
1852  
-    elif action in ('test', 'loaddata'):
  1848
+    elif action == 'test':
  1849
+        try:
  1850
+            action_mapping[action](args[1:], int(options.verbosity), options.interactive)
  1851
+        except IndexError:
  1852
+            parser.print_usage_and_exit()
  1853
+    elif action == 'loaddata':
1853 1854
         try:
1854 1855
             action_mapping[action](args[1:], int(options.verbosity))
1855 1856
         except IndexError:
3  django/db/models/fields/__init__.py
@@ -621,7 +621,8 @@ def to_python(self, value):
621 621
         try:
622 622
             return decimal.Decimal(value)
623 623
         except decimal.InvalidOperation:
624  
-            raise validators.ValidationError, ugettext("This value must be a decimal number.")
  624
+            raise validators.ValidationError(
  625
+                _("This value must be a decimal number."))
625 626
 
626 627
     def _format(self, value):
627 628
         if isinstance(value, basestring):
4  django/db/models/manager.py
@@ -3,10 +3,6 @@
3 3
 from django.db.models import signals
4 4
 from django.db.models.fields import FieldDoesNotExist
5 5
 
6  
-# Size of each "chunk" for get_iterator calls.
7  
-# Larger values are slightly faster at the expense of more storage space.
8  
-GET_ITERATOR_CHUNK_SIZE = 100
9  
-
10 6
 def ensure_default_manager(sender):
11 7
     cls = sender
12 8
     if not hasattr(cls, '_default_manager'):
26  django/db/models/query.py
@@ -579,28 +579,36 @@ def iterator(self):
579 579
         except EmptyResultSet:
580 580
             raise StopIteration
581 581
 
582  
-        # self._fields is a list of field names to fetch.
  582
+        # self._select is a dictionary, and dictionaries' key order is
  583
+        # undefined, so we convert it to a list of tuples.
  584
+        extra_select = self._select.items()
  585
+
  586
+        # Construct two objects -- fields and field_names.
  587
+        # fields is a list of Field objects to fetch.
  588
+        # field_names is a list of field names, which will be the keys in the
  589
+        # resulting dictionaries.
583 590
         if self._fields:
584  
-            if not self._select:
  591
+            if not extra_select:
585 592
                 fields = [self.model._meta.get_field(f, many_to_many=False) for f in self._fields]
  593
+                field_names = self._fields
586 594
             else:
587 595
                 fields = []
  596
+                field_names = []
588 597
                 for f in self._fields:
589 598
                     if f in [field.name for field in self.model._meta.fields]:
590 599
                         fields.append(self.model._meta.get_field(f, many_to_many=False))
591  
-                    elif not self._select.has_key( f ):
592  
-                        raise FieldDoesNotExist, '%s has no field named %r' % ( self.model._meta.object_name, f )
593  
-
594  
-            field_names = self._fields
  600
+                        field_names.append(f)
  601
+                    elif not self._select.has_key(f):
  602
+                        raise FieldDoesNotExist('%s has no field named %r' % (self.model._meta.object_name, f))
595 603
         else: # Default to all fields.
596 604
             fields = self.model._meta.fields
597 605
             field_names = [f.attname for f in fields]
598 606
 
599 607
         columns = [f.column for f in fields]
600 608
         select = ['%s.%s' % (backend.quote_name(self.model._meta.db_table), backend.quote_name(c)) for c in columns]
601  
-        # Add any additional SELECTs.
602  
-        if self._select:
603  
-            select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), backend.quote_name(s[0])) for s in self._select.items()])
  609
+        if extra_select:
  610
+            select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), backend.quote_name(s[0])) for s in extra_select])
  611
+            field_names.extend([f[0] for f in extra_select])
604 612
 
605 613
         cursor = connection.cursor()
606 614
         cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
21  django/newforms/forms.py
@@ -232,16 +232,8 @@ def __init__(self, form, field, name):
232 232
         self.help_text = field.help_text or ''
233 233
 
234 234
     def __unicode__(self):
235  
-        "Renders this field as an HTML widget."
236  
-        # Use the 'widget' attribute on the field to determine which type
237  
-        # of HTML widget to use.
238  
-        value = self.as_widget(self.field.widget)
239  
-        if not isinstance(value, basestring):
240  
-            # Some Widget render() methods -- notably RadioSelect -- return a
241  
-            # "special" object rather than a string. Call __unicode__() on that
242  
-            # object to get its rendered value.
243  
-            value = unicode(value)
244  
-        return value
  235
+        """Renders this field as an HTML widget."""
  236
+        return self.as_widget()
245 237
 
246 238
     def _errors(self):
247 239
         """
@@ -251,7 +243,14 @@ def _errors(self):
251 243
         return self.form.errors.get(self.name, ErrorList())
252 244
     errors = property(_errors)
253 245
 
254  
-    def as_widget(self, widget, attrs=None):
  246
+    def as_widget(self, widget=None, attrs=None):
  247
+        """
  248
+        Renders the field by rendering the passed widget, adding any HTML
  249
+        attributes passed as attrs.  If no widget is specified, then the
  250
+        field's default widget will be used.
  251
+        """
  252
+        if not widget:
  253
+            widget = self.field.widget
255 254
         attrs = attrs or {}
256 255
         auto_id = self.auto_id
257 256
         if auto_id and 'id' not in attrs and 'id' not in widget.attrs:
33  django/newforms/widgets.py
@@ -216,7 +216,11 @@ def value_from_datadict(self, data, name):
216 216
         return data.get(name, None)
217 217
 
218 218
 class RadioInput(StrAndUnicode):
219  
-    "An object used by RadioFieldRenderer that represents a single <input type='radio'>."
  219
+    """
  220
+    An object used by RadioFieldRenderer that represents a single
  221
+    <input type='radio'>.
  222
+    """
  223
+
220 224
     def __init__(self, name, value, attrs, choice, index):
221 225
         self.name, self.value = name, value
222 226
         self.attrs = attrs
@@ -239,7 +243,10 @@ def tag(self):
239 243
         return u'<input%s />' % flatatt(final_attrs)
240 244
 
241 245
 class RadioFieldRenderer(StrAndUnicode):
242  
-    "An object used by RadioSelect to enable customization of radio widgets."
  246
+    """
  247
+    An object used by RadioSelect to enable customization of radio widgets.
  248
+    """
  249
+
243 250
     def __init__(self, name, value, attrs, choices):
244 251
         self.name, self.value, self.attrs = name, value, attrs
245 252
         self.choices = choices
@@ -253,16 +260,30 @@ def __getitem__(self, idx):
253 260
         return RadioInput(self.name, self.value, self.attrs.copy(), choice, idx)
254 261
 
255 262
     def __unicode__(self):
256  
-        "Outputs a <ul> for this set of radio fields."
  263
+        return self.render()
  264
+
  265
+    def render(self):
  266
+        """Outputs a <ul> for this set of radio fields."""
257 267
         return u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>' % force_unicode(w) for w in self])
258 268
 
259 269
 class RadioSelect(Select):
260  
-    def render(self, name, value, attrs=None, choices=()):
261  
-        "Returns a RadioFieldRenderer instance rather than a Unicode string."
  270
+
  271
+    def __init__(self, *args, **kwargs):
  272
+        self.renderer = kwargs.pop('renderer', None)
  273
+        if not self.renderer:
  274
+            self.renderer = RadioFieldRenderer
  275
+        super(RadioSelect, self).__init__(*args, **kwargs)
  276
+
  277
+    def get_renderer(self, name, value, attrs=None, choices=()):
  278
+        """Returns an instance of the renderer."""
262 279
         if value is None: value = ''
263 280
         str_value = force_unicode(value) # Normalize to string.
264 281
         final_attrs = self.build_attrs(attrs)
265  
-        return RadioFieldRenderer(name, str_value, final_attrs, list(chain(self.choices, choices)))
  282
+        choices = list(chain(self.choices, choices))
  283
+        return self.renderer(name, str_value, final_attrs, choices)
  284
+
  285
+    def render(self, name, value, attrs=None, choices=()):
  286
+        return self.get_renderer(name, value, attrs, choices).render()
266 287
 
267 288
     def id_for_label(self, id_):
268 289
         # RadioSelect is represented by multiple <input type="radio"> fields,
60  django/shortcuts/__init__.py
... ...
@@ -1,32 +1,62 @@
1  
-# This module collects helper functions and classes that "span" multiple levels
2  
-# of MVC. In other words, these functions/classes introduce controlled coupling
3  
-# for convenience's sake.
  1
+"""
  2
+This module collects helper functions and classes that "span" multiple levels
  3
+of MVC. In other words, these functions/classes introduce controlled coupling
  4
+for convenience's sake.
  5
+"""
4 6
 
5 7
 from django.template import loader
6 8
 from django.http import HttpResponse, Http404
7 9
 from django.db.models.manager import Manager
  10
+from django.db.models.query import QuerySet
8 11
 
9 12
 def render_to_response(*args, **kwargs):
  13
+    """
  14
+    Returns a HttpResponse whose content is filled with the result of calling
  15
+    django.template.loader.render_to_string() with the passed arguments.
  16
+    """
10 17
     return HttpResponse(loader.render_to_string(*args, **kwargs))
11 18
 load_and_render = render_to_response # For backwards compatibility.
12 19
 
13  
-def get_object_or_404(klass, *args, **kwargs):
14  
-    if isinstance(klass, Manager):
  20
+def _get_queryset(klass):
  21
+    """
  22
+    Returns a QuerySet from a Model, Manager, or QuerySet. Created to make
  23
+    get_object_or_404 and get_list_or_404 more DRY.
  24
+    """
  25
+    if isinstance(klass, QuerySet):
  26
+        return klass
  27
+    elif isinstance(klass, Manager):
15 28
         manager = klass
16  
-        klass = manager.model
17 29
     else:
18 30
         manager = klass._default_manager
  31
+    return manager.all()
  32
+
  33
+def get_object_or_404(klass, *args, **kwargs):
  34
+    """
  35
+    Uses get() to return an object, or raises a Http404 exception if the object
  36
+    does not exist.
  37
+
  38
+    klass may be a Model, Manager, or QuerySet object. All other passed
  39
+    arguments and keyword arguments are used in the get() query.
  40
+
  41
+    Note: Like with get(), an AssertionError will be raised if more than one
  42
+    object is found.
  43
+    """
  44
+    queryset = _get_queryset(klass)
19 45
     try:
20  
-        return manager.get(*args, **kwargs)
21  
-    except klass.DoesNotExist:
22  
-        raise Http404('No %s matches the given query.' % klass._meta.object_name)
  46
+        return queryset.get(*args, **kwargs)
  47
+    except queryset.model.DoesNotExist:
  48
+        raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)
23 49
 
24 50
 def get_list_or_404(klass, *args, **kwargs):
25  
-    if isinstance(klass, Manager):
26  
-        manager = klass
27  
-    else:
28  
-        manager = klass._default_manager
29  
-    obj_list = list(manager.filter(*args, **kwargs))
  51
+    """
  52
+    Uses filter() to return a list of objects, or raise a Http404 exception if
  53
+    the list is empty.
  54
+
  55
+    klass may be a Model, Manager, or QuerySet object. All other passed
  56
+    arguments and keyword arguments are used in the filter() query.
  57
+    """
  58
+    queryset = _get_queryset(klass)
  59
+    obj_list = list(queryset.filter(*args, **kwargs))
30 60
     if not obj_list:
31  
-        raise Http404('No %s matches the given query.' % manager.model._meta.object_name)
  61
+        raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)
32 62
     return obj_list
19  django/template/loaders/app_directories.py
... ...
@@ -1,9 +1,14 @@
1  
-# Wrapper for loading templates from "template" directories in installed app packages.
  1
+"""
  2
+Wrapper for loading templates from "template" directories in INSTALLED_APPS
  3
+packages.
  4
+"""
  5
+
  6
+import os
2 7
 
3 8
 from django.conf import settings
4 9
 from django.core.exceptions import ImproperlyConfigured
5 10
 from django.template import TemplateDoesNotExist
6  
-import os
  11
+from django.utils._os import safe_join
7 12
 
8 13
 # At compile time, cache the directories to search.
9 14
 app_template_dirs = []
@@ -28,8 +33,14 @@
28 33
 app_template_dirs = tuple(app_template_dirs)
29 34
 
30 35
 def get_template_sources(template_name, template_dirs=None):
31  
-    for template_dir in app_template_dirs:
32  
-        yield os.path.join(template_dir, template_name)
  36
+    if not template_dirs:
  37
+        template_dirs = app_template_dirs
  38
+    for template_dir in template_dirs:
  39
+        try:
  40
+            yield safe_join(template_dir, template_name)
  41
+        except ValueError:
  42
+            # The joined path was located outside of template_dir.
  43
+            pass
33 44
 
34 45
 def load_template_source(template_name, template_dirs=None):
35 46
     for filepath in get_template_sources(template_name, template_dirs):
12  django/template/loaders/filesystem.py
... ...
@@ -1,14 +1,20 @@
1  
-# Wrapper for loading templates from the filesystem.
  1
+"""
  2
+Wrapper for loading templates from the filesystem.
  3
+"""
2 4
 
3 5
 from django.conf import settings
4 6
 from django.template import TemplateDoesNotExist
5  
-import os
  7
+from django.utils._os import safe_join
6 8
 
7 9
 def get_template_sources(template_name, template_dirs=None):
8 10
     if not template_dirs:
9 11
         template_dirs = settings.TEMPLATE_DIRS
10 12
     for template_dir in template_dirs:
11  
-        yield os.path.join(template_dir, template_name)
  13
+        try:
  14
+            yield safe_join(template_dir, template_name)
  15
+        except ValueError:
  16
+            # The joined path was located outside of template_dir.
  17
+            pass
12 18
 
13 19
 def load_template_source(template_name, template_dirs=None):
14 20
     tried = []
2  django/test/client.py
@@ -195,7 +195,7 @@ def get(self, path, data={}, **extra):
195 195
             'CONTENT_LENGTH':  None,
196 196
             'CONTENT_TYPE':    'text/html; charset=utf-8',
197 197
             'PATH_INFO':       path,
198  
-            'QUERY_STRING':    urlencode(data),
  198
+            'QUERY_STRING':    urlencode(data, doseq=True),
199 199
             'REQUEST_METHOD': 'GET',
200 200
         }
201 201
         r.update(extra)
114  django/test/simple.py
... ...
@@ -1,5 +1,6 @@
1 1
 import unittest
2 2
 from django.conf import settings
  3
+from django.db.models import get_app, get_apps
3 4
 from django.test import _doctest as doctest
4 5
 from django.test.utils import setup_test_environment, teardown_test_environment
5 6
 from django.test.utils import create_test_db, destroy_test_db
@@ -10,6 +11,31 @@
10 11
     
11 12
 doctestOutputChecker = OutputChecker()
12 13
 
  14
+def get_tests(app_module):
  15
+    try:
  16
+        app_path = app_module.__name__.split('.')[:-1]
  17
+        test_module = __import__('.'.join(app_path + [TEST_MODULE]), {}, {}, TEST_MODULE)
  18
+    except ImportError, e:
  19
+        # Couldn't import tests.py. Was it due to a missing file, or
  20
+        # due to an import error in a tests.py that actually exists?
  21
+        import os.path
  22
+        from imp import find_module
  23
+        try:
  24
+            mod = find_module(TEST_MODULE, [os.path.dirname(app_module.__file__)])
  25
+        except ImportError:
  26
+            # 'tests' module doesn't exist. Move on.
  27
+            test_module = None
  28
+        else:
  29
+            # The module exists, so there must be an import error in the 
  30
+            # test module itself. We don't need the module; so if the
  31
+            # module was a single file module (i.e., tests.py), close the file
  32
+            # handle returned by find_module. Otherwise, the test module
  33
+            # is a directory, and there is nothing to close.
  34
+            if mod[0]:
  35
+                mod[0].close()
  36
+            raise
  37
+    return test_module
  38
+    
13 39
 def build_suite(app_module):
14 40
     "Create a complete Django test suite for the provided application module"
15 41
     suite = unittest.TestSuite()
@@ -30,10 +56,8 @@ def build_suite(app_module):
30 56
     
31 57
     # Check to see if a separate 'tests' module exists parallel to the 
32 58
     # models module
33  
-    try:
34  
-        app_path = app_module.__name__.split('.')[:-1]
35  
-        test_module = __import__('.'.join(app_path + [TEST_MODULE]), {}, {}, TEST_MODULE)
36  
-        
  59
+    test_module = get_tests(app_module)
  60
+    if test_module:
37 61
         # Load unit and doctests in the tests.py module. If module has
38 62
         # a suite() method, use it. Otherwise build the test suite ourselves.
39 63
         if hasattr(test_module, 'suite'):
@@ -47,34 +71,50 @@ def build_suite(app_module):
47 71
             except ValueError:
48 72
                 # No doc tests in tests.py
49 73
                 pass
50  
-    except ImportError, e:
51  
-        # Couldn't import tests.py. Was it due to a missing file, or
52  
-        # due to an import error in a tests.py that actually exists?
53  
-        import os.path
54  
-        from imp import find_module
55  
-        try:
56  
-            mod = find_module(TEST_MODULE, [os.path.dirname(app_module.__file__)])
57  
-        except ImportError:
58  
-            # 'tests' module doesn't exist. Move on.
59  
-            pass
60  
-        else:
61  
-            # The module exists, so there must be an import error in the 
62  
-            # test module itself. We don't need the module; so if the
63  
-            # module was a single file module (i.e., tests.py), close the file
64  
-            # handle returned by find_module. Otherwise, the test module
65  
-            # is a directory, and there is nothing to close.
66  
-            if mod[0]:
67  
-                mod[0].close()
68  
-            raise
69  
-            
70 74
     return suite
71 75
 
72  
-def run_tests(module_list, verbosity=1, extra_tests=[]):
  76
+def build_test(label):
  77
+    """Construct a test case a test with the specified label. Label should 
  78
+    be of the form model.TestClass or model.TestClass.test_method. Returns
  79
+    an instantiated test or test suite corresponding to the label provided.
  80
+        
  81
+    """
  82
+    parts = label.split('.')
  83
+    if len(parts) < 2 or len(parts) > 3:
  84
+        raise ValueError("Test label '%s' should be of the form app.TestCase or app.TestCase.test_method" % label)
  85
+    
  86
+    app_module = get_app(parts[0])
  87
+    TestClass = getattr(app_module, parts[1], None)
  88
+
  89
+    # Couldn't find the test class in models.py; look in tests.py
  90
+    if TestClass is None:
  91
+        test_module = get_tests(app_module)
  92
+        if test_module:
  93
+            TestClass = getattr(test_module, parts[1], None)
  94
+
  95
+    if len(parts) == 2: # label is app.TestClass
  96
+        try:
  97
+            return unittest.TestLoader().loadTestsFromTestCase(TestClass)
  98
+        except TypeError:
  99
+            raise ValueError("Test label '%s' does not refer to a test class" % label)            
  100
+    else: # label is app.TestClass.test_method
  101
+        return TestClass(parts[2])
  102
+
  103
+def run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[]):
73 104
     """
74  
-    Run the unit tests for all the modules in the provided list.
75  
-    This testrunner will search each of the modules in the provided list,
76  
-    looking for doctests and unittests in models.py or tests.py within
77  
-    the module. A list of 'extra' tests may also be provided; these tests
  105
+    Run the unit tests for all the test labels in the provided list.
  106
+    Labels must be of the form:
  107
+     - app.TestClass.test_method
  108
+        Run a single specific test method
  109
+     - app.TestClass
  110
+        Run all the test methods in a given class
  111
+     - app
  112
+        Search for doctests and unittests in the named application.
  113
+
  114
+    When looking for tests, the test runner will look in the models and
  115
+    tests modules for the application.
  116
+    
  117
+    A list of 'extra' tests may also be provided; these tests
78 118
     will be added to the test suite.
79 119
     
80 120
     Returns the number of tests that failed.
@@ -83,15 +123,23 @@ def run_tests(module_list, verbosity=1, extra_tests=[]):
83 123
     
84 124
     settings.DEBUG = False    
85 125
     suite = unittest.TestSuite()
86  
-     
87  
-    for module in module_list:
88  
-        suite.addTest(build_suite(module))
  126
+    
  127
+    if test_labels:
  128
+        for label in test_labels:
  129
+            if '.' in label:
  130
+                suite.addTest(build_test(label))
  131
+            else:
  132
+                app = get_app(label)
  133
+                suite.addTest(build_suite(app))
  134
+    else:
  135
+        for app in get_apps():
  136
+            suite.addTest(build_suite(app))
89 137
     
90 138
     for test in extra_tests:
91 139
         suite.addTest(test)
92 140
 
93 141
     old_name = settings.DATABASE_NAME
94  
-    create_test_db(verbosity)
  142
+    create_test_db(verbosity, autoclobber=not interactive)
95 143
     result = unittest.TextTestRunner(verbosity=verbosity).run(suite)
96 144
     destroy_test_db(old_name, verbosity)
97 145
     
2  django/test/testcases.py
@@ -84,7 +84,7 @@ def assertContains(self, response, text, count=None, status_code=200):
84 84
             "Couldn't retrieve page: Response code was %d (expected %d)'" % 
85 85
                 (response.status_code, status_code))
86 86
         real_count = response.content.count(text)
87  
-        if count:
  87
+        if count is not None:
88 88
             self.assertEqual(real_count, count,
89 89
                 "Found %d instances of '%s' in response (expected %d)" % (real_count, text, count))
90 90
         else:
4  django/test/utils.py
@@ -144,6 +144,10 @@ def create_test_db(verbosity=1, autoclobber=False):
144 144
 
145 145
     management.syncdb(verbosity, interactive=False)
146 146
 
  147
+    if settings.CACHE_BACKEND.startswith('db://'):
  148
+        cache_name = settings.CACHE_BACKEND[len('db://'):]
  149
+        management.createcachetable(cache_name)
  150
+
147 151
     # Get a cursor (even though we don't need one yet). This has
148 152
     # the side effect of initializing the test database.
149 153
     cursor = connection.cursor()
23  django/utils/_os.py
... ...
@@ -0,0 +1,23 @@
  1
+from os.path import join, normcase, abspath, sep
  2
+
  3
+def safe_join(base, *paths):
  4
+    """
  5
+    Joins one or more path components to the base path component intelligently.
  6
+    Returns a normalized, absolute version of the final path.
  7
+
  8
+    The final path must be located inside of the base path component (otherwise
  9
+    a ValueError is raised).
  10
+    """
  11
+    # We need to use normcase to ensure we don't false-negative on case
  12
+    # insensitive operating systems (like Windows).
  13
+    final_path = normcase(abspath(join(base, *paths)))
  14
+    base_path = normcase(abspath(base))
  15
+    base_path_len = len(base_path)
  16
+    # Ensure final_path starts with base_path and that the next character after
  17
+    # the final path is os.sep (or nothing, in which case final_path must be
  18
+    # equal to base_path).
  19
+    if not final_path.startswith(base_path) \
  20
+       or final_path[base_path_len:base_path_len+1] not in ('', sep):
  21
+        raise ValueError('the joined path is located outside of the base path'
  22
+                         ' component')
  23
+    return final_path
7  django/utils/http.py
@@ -30,6 +30,9 @@ def urlencode(query, doseq=0):
30 30
     """
31 31
     if hasattr(query, 'items'):
32 32
         query = query.items()
33  
-    return urllib.urlencode([(smart_str(k), smart_str(v)) for k,
34  
-        v in query], doseq)
  33
+    return urllib.urlencode(
  34
+        [(smart_str(k),
  35
+         isinstance(v, (list,tuple)) and [smart_str(i) for i in v] or smart_str(v))
  36
+            for k, v in query],
  37
+        doseq)
35 38
 
24  docs/authentication.txt
@@ -99,7 +99,7 @@ custom methods:
99 99
       should prefer using ``is_authenticated()`` to this method.
100 100
 
101 101
     * ``is_authenticated()`` -- Always returns ``True``. This is a way to
102  
-      tell if the user has been authenticated. This does not imply any 
  102
+      tell if the user has been authenticated. This does not imply any
103 103
       permissions, and doesn't check if the user is active - it only indicates
104 104
       that the user has provided a valid username and password.
105 105
 
@@ -114,6 +114,18 @@ custom methods:
114 114
       string is the correct password for the user. (This takes care of the
115 115
       password hashing in making the comparison.)
116 116
 
  117
+    * ``set_unusable_password()`` -- **New in Django development version.**
  118
+      Marks the user as having no password set.  This isn't the same as having
  119
+      a blank string for a password. ``check_password()`` for this user will
  120
+      never return ``True``. Doesn't save the ``User`` object.
  121
+
  122
+      You may need this if authentication for your application takes place
  123
+      against an existing external source such as an LDAP directory.
  124
+
  125
+    * ``has_usable_password()`` -- **New in Django development version.**
  126
+      Returns ``False`` if ``set_unusable_password()`` has been called for this
  127
+      user.
  128
+
117 129
     * ``get_group_permissions()`` -- Returns a list of permission strings that
118 130
       the user has, through his/her groups.
119 131
 
@@ -126,7 +138,7 @@ custom methods:
126 138
 
127 139
     * ``has_perms(perm_list)`` -- Returns ``True`` if the user has each of the
128 140
       specified permissions, where each perm is in the format
129  
-      ``"package.codename"``. If the user is inactive, this method will 
  141
+      ``"package.codename"``. If the user is inactive, this method will
130 142
       always return ``False``.
131 143
 
132 144
     * ``has_module_perms(package_name)`` -- Returns ``True`` if the user has
@@ -152,9 +164,11 @@ Manager functions
152 164
 
153 165
 The ``User`` model has a custom manager that has the following helper functions:
154 166
 
155  
-    * ``create_user(username, email, password)`` -- Creates, saves and returns
156  
-      a ``User``. The ``username``, ``email`` and ``password`` are set as
157  
-      given, and the ``User`` gets ``is_active=True``.
  167
+    * ``create_user(username, email, password=None)`` -- Creates, saves and
  168
+      returns a ``User``. The ``username``, ``email`` and ``password`` are set
  169
+      as given, and the ``User`` gets ``is_active=True``.
  170
+
  171
+      If no password is provided, ``set_unusable_password()`` will be called.
158 172
 
159 173
       See _`Creating users` for example usage.
160 174
 
16  docs/contributing.txt
@@ -279,6 +279,22 @@ Please follow these coding standards when writing code for inclusion in Django:
279 279
     * Mark all strings for internationalization; see the `i18n documentation`_
280 280
       for details.
281 281
 
  282
+    * In docstrings, use "action words," like so::
  283
+
  284
+          def foo():
  285
+              """
  286
+              Calculates something and returns the result.
  287
+              """
  288
+              pass
  289
+
  290
+      Here's an example of what not to do::
  291
+
  292
+          def foo():
  293
+              """
  294
+              Calculate something and return the result.
  295
+              """
  296
+              pass
  297
+
282 298
     * Please don't put your name in the code you contribute. Our policy is to
283 299
       keep contributors' names in the ``AUTHORS`` file distributed with Django
284 300
       -- not scattered throughout the codebase itself. Feel free to include a
16  docs/db-api.txt
@@ -20,7 +20,7 @@ a weblog application::
20 20
 
21 21
     class Author(models.Model):
22 22
         name = models.CharField(maxlength=50)
23  
-        email = models.URLField()
  23
+        email = models.EmailField()
24 24
 
25 25
         def __unicode__(self):
26 26
             return self.name
@@ -1891,8 +1891,8 @@ get_object_or_404()
1891 1891
 One common idiom to use ``get()`` and raise ``Http404`` if the
1892 1892
 object doesn't exist. This idiom is captured by ``get_object_or_404()``.
1893 1893
 This function takes a Django model as its first argument and an
1894  
-arbitrary number of keyword arguments, which it passes to the manager's
1895  
-``get()`` function. It raises ``Http404`` if the object doesn't
  1894
+arbitrary number of keyword arguments, which it passes to the default
  1895
+manager's ``get()`` function. It raises ``Http404`` if the object doesn't
1896 1896
 exist. For example::
1897 1897
 
1898 1898
     # Get the Entry with a primary key of 3
@@ -1901,7 +1901,7 @@ exist. For example::
1901 1901
 When you provide a model to this shortcut function, the default manager
1902 1902
 is used to execute the underlying ``get()`` query. If you don't want to
1903 1903
 use the default manager, or if you want to search a list of related objects,
1904  
-you can provide ``get_object_or_404()`` with a manager object instead.
  1904
+you can provide ``get_object_or_404()`` with a ``Manager`` object instead.
1905 1905
 For example::
1906 1906
 
1907 1907
     # Get the author of blog instance e with a name of 'Fred'
@@ -1911,6 +1911,14 @@ For example::
1911 1911
     # entry with a primary key of 3
1912 1912
     e = get_object_or_404(Entry.recent_entries, pk=3)
1913 1913
 
  1914
+**New in Django development version:** The first argument to
  1915
+``get_object_or_404()`` can be a ``QuerySet`` object. This is useful in cases
  1916
+where you've defined a custom manager method. For example::
  1917
+
  1918
+    # Use a QuerySet returned from a 'published' method of a custom manager
  1919
+    # in the search for an entry with primary key of 5
  1920
+    e = get_object_or_404(Entry.objects.published(), pk=5)
  1921
+
1914 1922
 get_list_or_404()
1915 1923
 -----------------
1916 1924
 
38  docs/model-api.txt
@@ -1052,6 +1052,44 @@ create your tables. It's not called at any other time, so it can afford to
1052 1052
 execute slightly complex code, such as the ``DATABASE_ENGINE`` check in the
1053 1053
 above example.
1054 1054
 
  1055
+Some database column types accept parameters, such as ``CHAR(25)``, where the
  1056
+parameter ``25`` represents the maximum column length. In cases like these,
  1057
+it's more flexible if the parameter is specified in the model rather than being
  1058
+hard-coded in the ``db_type()`` method. For example, it wouldn't make much
  1059
+sense to have a ``CharMaxlength25Field``, shown here::
  1060
+
  1061
+    # This is a silly example of hard-coded parameters.
  1062
+    class CharMaxlength25Field(models.Field):
  1063
+        def db_type(self):
  1064
+            return 'char(25)'
  1065
+
  1066
+    # In the model:
  1067
+    class MyModel(models.Model):
  1068
+        # ...
  1069
+        my_field = CharMaxlength25Field()
  1070
+
  1071
+The better way of doing this would be to make the parameter specifiable at run
  1072
+time -- i.e., when the class is instantiated. To do that, just implement
  1073
+``__init__()``, like so::
  1074
+
  1075
+    # This is a much more flexible example.
  1076
+    class BetterCharField(models.Field):
  1077
+        def __init__(self, maxlength, *args, **kwargs):
  1078
+            self.maxlength = maxlength
  1079
+            super(BetterCharField, self).__init__(*args, **kwargs)
  1080
+
  1081
+        def db_type(self):
  1082
+            return 'char(%s)' % self.maxlength
  1083
+
  1084
+    # In the model:
  1085
+    class MyModel(models.Model):
  1086
+        # ...
  1087
+        my_field = BetterCharField(25)
  1088
+
  1089
+Note that if you implement ``__init__()`` on a ``Field`` subclass, it's
  1090
+important to call ``Field.__init__()`` -- i.e., the parent class'
  1091
+``__init__()`` method.
  1092
+
1055 1093
 Meta options
1056 1094
 ============
1057 1095
 
48  docs/testing.txt
@@ -450,6 +450,9 @@ look like::
450 450
         def setUp(self):
451 451
             # test definitions as before
452 452
 
  453
+        def testFluffyAnimals(self):
  454
+            # A test that uses the fixtures
  455
+
453 456
 At the start of each test case, before ``setUp()`` is run, Django will
454 457
 flush the database, returning the database the state it was in directly
455 458
 after ``syncdb`` was called. Then, all the named fixtures are installed.
@@ -483,8 +486,8 @@ that can be useful in testing the behavior of web sites.
483 486
 
484 487
 ``assertContains(response, text, count=None, status_code=200)``
485 488
     Assert that a response indicates that a page could be retrieved and
486  
-    produced the nominated status code, and that ``text`` in the content 
487  
-    of the response. If ``count`` is provided, ``text`` must occur exactly 
  489
+    produced the nominated status code, and that ``text`` in the content
  490
+    of the response. If ``count`` is provided, ``text`` must occur exactly
488 491
     ``count`` times in the response.
489 492
 
490 493
 ``assertFormError(response, form, field, errors)``
@@ -571,6 +574,18 @@ but you only want to run the animals unit tests, run::
571 574
 
572 575
     $ ./manage.py test animals
573 576
 
  577
+**New in Django development version:** If you use unit tests, you can be more
  578
+specific in the tests that are executed. To run a single test case in an
  579
+application (for example, the AnimalTestCase described previously), add the
  580
+name of the test case to the label on the command line::
  581
+
  582
+    $ ./manage.py test animals.AnimalTestCase
  583
+
  584
+**New in Django development version:** To run a single test method inside a
  585
+test case, add the name of the test method to the label::
  586
+
  587
+    $ ./manage.py test animals.AnimalTestCase.testFluffyAnimals
  588
+
574 589
 When you run your tests, you'll see a bunch of text flow by as the test
575 590
 database is created and models are initialized. This test database is
576 591
 created from scratch every time you run your tests.
@@ -662,17 +677,36 @@ framework that can be executed from Python code.
662 677
 Defining a test runner
663 678
 ----------------------
664 679
 By convention, a test runner should be called ``run_tests``; however, you
665  
-can call it anything you want. The only requirement is that it accept two
666  
-arguments:
  680
+can call it anything you want. The only requirement is that it has the
  681
+same arguments as the Django test runner:
667 682
 
668  
-``run_tests(module_list, verbosity=1)``
669  
-    The module list is the list of Python modules that contain the models to be
670  
-    tested. This is the same format returned by ``django.db.models.get_apps()``
  683
+``run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[])``
  684
+    
  685
+    **New in Django development version:** ``test_labels`` is a list of
  686
+    strings describing the tests to be run. A test label can take one of
  687
+    three forms:
  688
+
  689
+        * ``app.TestCase.test_method`` - Run a single test method in a test case
  690
+        * ``app.TestCase`` - Run all the test methods in a test case
  691
+        * ``app`` - Search for and run all tests in the named application.
  692
+
  693
+    If ``test_labels`` has a value of ``None``, the test runner should run
  694
+    search for tests in all the applications in ``INSTALLED_APPS``.
671 695
 
672 696
     Verbosity determines the amount of notification and debug information that
673 697
     will be printed to the console; ``0`` is no output, ``1`` is normal output,
674 698
     and ``2`` is verbose output.
675 699
 
  700
+    **New in Django development version:** If ``interactive`` is ``True``, the
  701
+    test suite may ask the user for instructions when the test suite is
  702
+    executed. An example of this behavior would be asking for permission to
  703
+    delete an existing test database. If ``interactive`` is ``False``, the
  704
+    test suite must be able to run without any manual intervention.
  705
+
  706
+    ``extra_tests`` is a list of extra ``TestCase`` instances to add to the
  707
+    suite that is executed by the test runner. These extra tests are run
  708
+    in addition to those discovered in the modules listed in ``module_list``.
  709
+
676 710
     This method should return the number of tests that failed.
677 711
 
678 712
 Testing utilities
27  tests/modeltests/get_object_or_404/models.py
@@ -3,11 +3,11 @@
3 3
 
4 4
 get_object_or_404 is a shortcut function to be used in view functions for
5 5
 performing a get() lookup and raising a Http404 exception if a DoesNotExist
6  
-exception was rasied during the get() call.
  6
+exception was raised during the get() call.
7 7
 
8 8
 get_list_or_404 is a shortcut function to be used in view functions for
9 9
 performing a filter() lookup and raising a Http404 exception if a DoesNotExist
10  
-exception was rasied during the filter() call.
  10
+exception was raised during the filter() call.
11 11
 """
12 12
 
13 13
 from django.db import models
@@ -69,11 +69,28 @@ def __unicode__(self):
69 69
 >>> get_object_or_404(Article.by_a_sir, title="Run away!")
70 70
 <Article: Run away!>
71 71
 
  72
+# QuerySets can be used too.
  73
+>>> get_object_or_404(Article.objects.all(), title__contains="Run")
  74
+<Article: Run away!>
  75
+
  76
+# Just as when using a get() lookup, you will get an error if more than one
  77
+# object is returned.
  78
+>>> get_object_or_404(Author.objects.all())
  79
+Traceback (most recent call last):
  80
+...
  81
+AssertionError: get() returned more than one Author -- it returned ...! Lookup parameters were {}
  82
+
  83
+# Using an EmptyQuerySet raises a Http404 error.
  84
+>>> get_object_or_404(Article.objects.none(), title__contains="Run")
  85
+Traceback (most recent call last):
  86
+...
  87
+Http404: No Article matches the given query.
  88
+
72 89
 # get_list_or_404 can be used to get lists of objects
73 90
 >>> get_list_or_404(a.article_set, title__icontains='Run')
74 91
 [<Article: Run away!>]
75 92
 
76  
-# Http404 is returned if the list is empty
  93
+# Http404 is returned if the list is empty.
77 94
 >>> get_list_or_404(a.article_set, title__icontains='Shrubbery')
78 95
 Traceback (most recent call last):
79 96
 ...