Django

Code

Changeset 8193

Show
Ignore:
Timestamp:
08/02/08 00:56:57 (4 months ago)
Author:
gwilson
Message:

Fixed #7919 -- md5 and sha modules are deprecated since Python 2.5, use hashlib module when available. Patch from Karen Tracey.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/trunk/django/contrib/admin/sites.py

    r8063 r8193  
     1import base64 
     2import cPickle as pickle 
     3import datetime 
     4import re 
     5 
    16from django import http, template 
    27from django.contrib.admin import ModelAdmin 
     
    1015from django.views.decorators.cache import never_cache 
    1116from django.conf import settings 
    12 import base64 
    13 import cPickle as pickle 
    14 import datetime 
    15 import md5 
    16 import re 
     17from django.utils.hashcompat import md5_constructor 
    1718 
    1819ERROR_MESSAGE = ugettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.") 
     
    3031    from django.conf import settings 
    3132    pickled = pickle.dumps(post_data) 
    32     pickled_md5 = md5.new(pickled + settings.SECRET_KEY).hexdigest() 
     33    pickled_md5 = md5_constructor(pickled + settings.SECRET_KEY).hexdigest() 
    3334    return base64.encodestring(pickled + pickled_md5) 
    3435 
     
    3738    encoded_data = base64.decodestring(encoded_data) 
    3839    pickled, tamper_check = encoded_data[:-32], encoded_data[-32:] 
    39     if md5.new(pickled + settings.SECRET_KEY).hexdigest() != tamper_check: 
     40    if md5_constructor(pickled + settings.SECRET_KEY).hexdigest() != tamper_check: 
    4041        from django.core.exceptions import SuspiciousOperation 
    4142        raise SuspiciousOperation, "User may have tampered with session cookie." 
     
    4950    that presents a full admin interface for the collection of registered models. 
    5051    """ 
    51      
     52 
    5253    index_template = None 
    5354    login_template = None 
    54      
     55 
    5556    def __init__(self): 
    5657        self._registry = {} # model_class class -> admin_class instance 
     
    118119 
    119120    def root(self, request, url): 
    120         """  
     121        """ 
    121122        Handles main URL routing for the admin app. 
    122123 
     
    125126        if request.method == 'GET' and not request.path.endswith('/'): 
    126127            return http.HttpResponseRedirect(request.path + '/') 
    127          
     128 
    128129        # Figure out the admin base URL path and stash it for later use 
    129130        self.root_path = re.sub(re.escape(url) + '$', '', request.path) 
    130          
     131 
    131132        url = url.rstrip('/') # Trim trailing slash, if it exists. 
    132133 
     
    134135        if url == 'logout': 
    135136            return self.logout(request) 
    136          
     137 
    137138        # Check permission to continue or display login form. 
    138139        if not self.has_permission(request): 
     
    155156            if match: 
    156157                return self.user_change_password(request, match.group(1)) 
    157                  
     158 
    158159            if '/' in url: 
    159160                return self.model_page(request, *url.split('/', 2)) 
     
    321322        for app in app_list: 
    322323            app['models'].sort(lambda x, y: cmp(x['name'], y['name'])) 
    323          
     324 
    324325        context = { 
    325326            'title': _('Site administration'), 
     
    328329        } 
    329330        context.update(extra_context or {}) 
    330         return render_to_response(self.index_template or 'admin/index.html', context,  
     331        return render_to_response(self.index_template or 'admin/index.html', context, 
    331332            context_instance=template.RequestContext(request) 
    332333        ) 
     
    343344        else: 
    344345            post_data = _encode_post_data({}) 
    345          
     346 
    346347        context = { 
    347348            'title': _('Log in'), 
  • django/trunk/django/contrib/admin/views/decorators.py

    r7967 r8193  
    11import base64 
    2 import md5 
    32import cPickle as pickle 
    43try: 
     
    1312from django.shortcuts import render_to_response 
    1413from django.utils.translation import ugettext_lazy, ugettext as _ 
     14from django.utils.hashcompat import md5_constructor 
    1515 
    1616ERROR_MESSAGE = ugettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.") 
     
    3636def _encode_post_data(post_data): 
    3737    pickled = pickle.dumps(post_data) 
    38     pickled_md5 = md5.new(pickled + settings.SECRET_KEY).hexdigest() 
     38    pickled_md5 = md5_constructor(pickled + settings.SECRET_KEY).hexdigest() 
    3939    return base64.encodestring(pickled + pickled_md5) 
    4040 
     
    4242    encoded_data = base64.decodestring(encoded_data) 
    4343    pickled, tamper_check = encoded_data[:-32], encoded_data[-32:] 
    44     if md5.new(pickled + settings.SECRET_KEY).hexdigest() != tamper_check: 
     44    if md5_constructor(pickled + settings.SECRET_KEY).hexdigest() != tamper_check: 
    4545        from django.core.exceptions import SuspiciousOperation 
    4646        raise SuspiciousOperation, "User may have tampered with session cookie." 
     
    8888                    message = _("Your e-mail address is not your username. Try '%s' instead.") % users[0].username 
    8989                else: 
    90                     # Either we cannot find the user, or if more than 1  
     90                    # Either we cannot find the user, or if more than 1 
    9191                    # we cannot guess which user is the correct one. 
    9292                    message = _("Usernames cannot contain the '@' character.") 
  • django/trunk/django/contrib/auth/tokens.py

    r8162 r8193  
    5151        # invalid as soon as it is used. 
    5252        # We limit the hash to 20 chars to keep URL short 
    53         import sha 
    54         hash = sha.new(settings.SECRET_KEY + unicode(user.id) +  
    55                        user.password + unicode(user.last_login) +  
    56                        unicode(timestamp)).hexdigest()[::2] 
     53        from django.utils.hashcompat import sha_constructor 
     54        hash = sha_constructor(settings.SECRET_KEY + unicode(user.id) + 
     55                               user.password + unicode(user.last_login) + 
     56                               unicode(timestamp)).hexdigest()[::2] 
    5757        return "%s-%s" % (ts_b36, hash) 
    5858 
    5959    def _num_days(self, dt): 
    60         return (dt - date(2001,1,1)).days  
     60        return (dt - date(2001,1,1)).days 
    6161 
    6262    def _today(self): 
    6363        # Used for mocking in tests 
    64         return date.today()         
     64        return date.today() 
    6565 
    6666default_token_generator = PasswordResetTokenGenerator() 
  • django/trunk/django/contrib/comments/models.py

    r7967 r8193  
    3030        validate that submitted form options have not been tampered-with. 
    3131        """ 
    32         import md5 
    33         return md5.new(options + photo_options + rating_options + target + settings.SECRET_KEY).hexdigest() 
     32        from django.utils.hashcompat import md5_constructor 
     33        return md5_constructor(options + photo_options + rating_options + target + settings.SECRET_KEY).hexdigest() 
    3434 
    3535    def get_rating_options(self, rating_string): 
  • django/trunk/django/contrib/csrf/middleware.py

    r6671 r8193  
    33 
    44This module provides a middleware that implements protection 
    5 against request forgeries from other sites.  
     5against request forgeries from other sites. 
     6""" 
    67 
    7 """ 
     8import re 
     9import itertools 
     10 
    811from django.conf import settings 
    912from django.http import HttpResponseForbidden 
     13from django.utils.hashcompat import md5_constructor 
    1014from django.utils.safestring import mark_safe 
    11 import md5 
    12 import re 
    13 import itertools 
    1415 
    1516_ERROR_MSG = mark_safe('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><body><h1>403 Forbidden</h1><p>Cross Site Request Forgery detected. Request aborted.</p></body></html>') 
     
    1718_POST_FORM_RE = \ 
    1819    re.compile(r'(<form\W[^>]*\bmethod=(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE) 
    19      
    20 _HTML_TYPES = ('text/html', 'application/xhtml+xml')     
     20 
     21_HTML_TYPES = ('text/html', 'application/xhtml+xml') 
    2122 
    2223def _make_token(session_id): 
    23     return md5.new(settings.SECRET_KEY + session_id).hexdigest() 
     24    return md5_constructor(settings.SECRET_KEY + session_id).hexdigest() 
    2425 
    2526class CsrfMiddleware(object): 
    2627    """Django middleware that adds protection against Cross Site 
    27     Request Forgeries by adding hidden form fields to POST forms and  
    28     checking requests for the correct value.   
    29      
    30     In the list of middlewares, SessionMiddleware is required, and must come  
    31     after this middleware.  CsrfMiddleWare must come after compression  
     28    Request Forgeries by adding hidden form fields to POST forms and 
     29    checking requests for the correct value. 
     30 
     31    In the list of middlewares, SessionMiddleware is required, and must come 
     32    after this middleware.  CsrfMiddleWare must come after compression 
    3233    middleware. 
    33     
    34     If a session ID cookie is present, it is hashed with the SECRET_KEY  
    35     setting to create an authentication token.  This token is added to all  
    36     outgoing POST forms and is expected on all incoming POST requests that  
     34 
     35    If a session ID cookie is present, it is hashed with the SECRET_KEY 
     36    setting to create an authentication token.  This token is added to all 
     37    outgoing POST forms and is expected on all incoming POST requests that 
    3738    have a session ID cookie. 
    38      
    39     If you are setting cookies directly, instead of using Django's session  
     39 
     40    If you are setting cookies directly, instead of using Django's session 
    4041    framework, this middleware will not work. 
    4142    """ 
    42      
     43 
    4344    def process_request(self, request): 
    4445        if request.method == 'POST': 
     
    5556            except KeyError: 
    5657                return HttpResponseForbidden(_ERROR_MSG) 
    57              
     58 
    5859            if request_csrf_token != csrf_token: 
    5960                return HttpResponseForbidden(_ERROR_MSG) 
    60                  
     61 
    6162        return None 
    6263 
     
    6768            csrf_token = _make_token(cookie.value) 
    6869        except KeyError: 
    69             # No outgoing cookie to set session, but  
     70            # No outgoing cookie to set session, but 
    7071            # a session might already exist. 
    7172            try: 
     
    7576                # no incoming or outgoing cookie 
    7677                pass 
    77              
     78 
    7879        if csrf_token is not None and \ 
    7980                response['Content-Type'].split(';')[0] in _HTML_TYPES: 
    80              
     81 
    8182            # ensure we don't add the 'id' attribute twice (HTML validity) 
    82             idattributes = itertools.chain(("id='csrfmiddlewaretoken'",),  
     83            idattributes = itertools.chain(("id='csrfmiddlewaretoken'",), 
    8384                                            itertools.repeat('')) 
    8485            def add_csrf_field(match): 
  • django/trunk/django/contrib/formtools/preview.py

    r7277 r8193  
    22Formtools Preview application. 
    33""" 
     4 
     5import cPickle as pickle 
    46 
    57from django.conf import settings 
     
    79from django.shortcuts import render_to_response 
    810from django.template.context import RequestContext 
    9 import cPickle as pickle 
    10 import md5 
     11from django.utils.hashcompat import md5_constructor 
    1112 
    1213AUTO_ID = 'formtools_%s' # Each form here uses this as its auto_id parameter. 
     
    110111        # Python 2.3, but Django requires 2.3 anyway, so that's OK. 
    111112        pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL) 
    112         return md5.new(pickled).hexdigest() 
     113        return md5_constructor(pickled).hexdigest() 
    113114 
    114115    def failed_hash(self, request): 
  • django/trunk/django/contrib/formtools/wizard.py

    r7971 r8193  
    55""" 
    66 
     7import cPickle as pickle 
     8 
    79from django import forms 
    810from django.conf import settings 
     
    1012from django.shortcuts import render_to_response 
    1113from django.template.context import RequestContext 
    12 import cPickle as pickle 
    13 import md5 
     14from django.utils.hashcompat import md5_constructor 
    1415 
    1516class FormWizard(object): 
     
    151152        # Python 2.3, but Django requires 2.3 anyway, so that's OK. 
    152153        pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL) 
    153         return md5.new(pickled).hexdigest() 
     154        return md5_constructor(pickled).hexdigest() 
    154155 
    155156    def determine_step(self, request, *args, **kwargs): 
  • django/trunk/django/contrib/sessions/backends/base.py

    r7725 r8193  
    11import base64 
    2 import md5 
    32import os 
    43import random 
     
    1312from django.conf import settings 
    1413from django.core.exceptions import SuspiciousOperation 
     14from django.utils.hashcompat import md5_constructor 
    1515 
    1616 
     
    7474        "Returns the given session dictionary pickled and encoded as a string." 
    7575        pickled = pickle.dumps(session_dict, pickle.HIGHEST_PROTOCOL) 
    76         pickled_md5 = md5.new(pickled + settings.SECRET_KEY).hexdigest() 
     76        pickled_md5 = md5_constructor(pickled + settings.SECRET_KEY).hexdigest() 
    7777        return base64.encodestring(pickled + pickled_md5) 
    7878 
     
    8080        encoded_data = base64.decodestring(session_data) 
    8181        pickled, tamper_check = encoded_data[:-32], encoded_data[-32:] 
    82         if md5.new(pickled + settings.SECRET_KEY).hexdigest() != tamper_check: 
     82        if md5_constructor(pickled + settings.SECRET_KEY).hexdigest() != tamper_check: 
    8383            raise SuspiciousOperation("User tampered with session cookie.") 
    8484        try: 
     
    118118            pid = 1 
    119119        while 1: 
    120             session_key = md5.new("%s%s%s%s" % (random.randint(0, sys.maxint - 1), 
    121                                   pid, time.time(), settings.SECRET_KEY)).hexdigest() 
     120            session_key = md5_constructor("%s%s%s%s" % (random.randint(0, sys.maxint - 1), 
     121                                          pid, time.time(), settings.SECRET_KEY)).hexdigest() 
    122122            if not self.exists(session_key): 
    123123                break 
  • django/trunk/django/contrib/sessions/models.py

    r7725 r8193  
    11import base64 
    2 import md5 
    32import cPickle as pickle 
    43 
     
    65from django.utils.translation import ugettext_lazy as _ 
    76from django.conf import settings 
     7from django.utils.hashcompat import md5_constructor 
    88 
    99 
     
    1414        """ 
    1515        pickled = pickle.dumps(session_dict) 
    16         pickled_md5 = md5.new(pickled + settings.SECRET_KEY).hexdigest() 
     16        pickled_md5 = md5_constructor(pickled + settings.SECRET_KEY).hexdigest() 
    1717        return base64.encodestring(pickled + pickled_md5) 
    1818 
     
    5757        encoded_data = base64.decodestring(self.session_data) 
    5858        pickled, tamper_check = encoded_data[:-32], encoded_data[-32:] 
    59         if md5.new(pickled + settings.SECRET_KEY).hexdigest() != tamper_check: 
     59        if md5_constructor(pickled + settings.SECRET_KEY).hexdigest() != tamper_check: 
    6060            from django.core.exceptions import SuspiciousOperation 
    6161            raise SuspiciousOperation, "User tampered with session cookie." 
  • django/trunk/django/core/cache/backends/filebased.py

    r6887 r8193  
    11"File-based cache backend" 
    22 
    3 import md5 
    4 import os, time 
     3import os 
     4import time 
    55try: 
    66    import cPickle as pickle 
    77except ImportError: 
    88    import pickle 
     9 
    910from django.core.cache.backends.base import BaseCache 
     11from django.utils.hashcompat import md5_constructor 
    1012 
    1113class CacheClass(BaseCache): 
    1214    def __init__(self, dir, params): 
    1315        BaseCache.__init__(self, params) 
    14          
     16 
    1517        max_entries = params.get('max_entries', 300) 
    1618        try: 
     
    1820        except (ValueError, TypeError): 
    1921            self._max_entries = 300 
    20              
     22 
    2123        cull_frequency = params.get('cull_frequency', 3) 
    2224        try: 
     
    2426        except (ValueError, TypeError): 
    2527            self._cull_frequency = 3 
    26              
     28 
    2729        self._dir = dir 
    2830        if not os.path.exists(self._dir): 
     
    3234        if self.has_key(key): 
    3335            return None 
    34          
     36 
    3537        self.set(key, value, timeout) 
    3638 
     
    5355        fname = self._key_to_file(key) 
    5456        dirname = os.path.dirname(fname) 
    55          
     57 
    5658        if timeout is None: 
    5759            timeout = self.default_timeout 
    58              
     60 
    5961        self._cull() 
    60          
     62 
    6163        try: 
    6264            if not os.path.exists(dirname): 
     
    104106        if int(self._num_entries) < self._max_entries: 
    105107            return 
    106          
     108 
    107109        try: 
    108110            filelist = os.listdir(self._dir) 
    109111        except (IOError, OSError): 
    110112            return 
    111          
     113 
    112114        if self._cull_frequency == 0: 
    113115            doomed = filelist 
     
    134136        bits of the path into directory prefixes to be nice to filesystems 
    135137        that have problems with large numbers of files in a directory. 
    136          
     138 
    137139        Thus, a cache key of "foo" gets turnned into a file named 
    138140        ``{cache-dir}ac/bd/18db4cc2f85cedef654fccc4a4d8``. 
    139141        """ 
    140         path = md5.new(key.encode('utf-8')).hexdigest() 
     142        path = md5_constructor(key.encode('utf-8')).hexdigest() 
    141143        path = os.path.join(path[:2], path[2:4], path[4:]) 
    142144        return os.path.join(self._dir, path) 
     
    148150        return count 
    149151    _num_entries = property(_get_num_entries) 
    150  
  • django/trunk/django/db/backends/util.py

    r8131 r8193  
    11import datetime 
    2 import md5 
    32from time import time 
     3 
     4from django.utils.hashcompat import md5_constructor 
    45 
    56try: 
     
    115116        return name 
    116117 
    117     hash = md5.md5(name).hexdigest()[:4] 
     118    hash = md5_constructor(name).hexdigest()[:4] 
    118119 
    119120    return '%s%s' % (name[:length-4], hash) 
  • django/trunk/django/middleware/common.py

    r7659 r8193  
    1 import md5 
    21import re 
    32 
     
    76from django.utils.http import urlquote 
    87from django.core import urlresolvers 
     8from django.utils.hashcompat import md5_constructor 
    99 
    1010class CommonMiddleware(object): 
     
    2222              appending a slash at the end. If this new URL is found in 
    2323              urlpatterns, then an HTTP-redirect is returned to this new URL; 
    24               otherwise the initial URL is processed as usual.  
     24              otherwise the initial URL is processed as usual. 
    2525 
    2626        - ETags: If the USE_ETAGS setting is set, ETags will be calculated from 
     
    109109                etag = response['ETag'] 
    110110            else: 
    111                 etag = '"%s"' % md5.new(response.content).hexdigest() 
     111                etag = '"%s"' % md5_constructor(response.content).hexdigest() 
    112112            if response.status_code >= 200 and response.status_code < 300 and request.META.get('HTTP_IF_NONE_MATCH') == etag: 
    113113                cookies = response.cookies 
  • django/trunk/django/utils/cache.py

    r7659 r8193  
    1818""" 
    1919 
    20 import md5 
    2120import re 
    2221import time 
     
    3029from django.utils.encoding import smart_str, iri_to_uri 
    3130from django.utils.http import http_date 
     31from django.utils.hashcompat import md5_constructor 
    3232 
    3333cc_delim_re = re.compile(r'\s*,\s*') 
     
    105105        cache_timeout = 0 # Can't have max-age negative 
    106106    if not response.has_header('ETag'): 
    107         response['ETag'] = '"%s"' % md5.new(response.content).hexdigest() 
     107        response['ETag'] = '"%s"' % md5_constructor(response.content).hexdigest() 
    108108    if not response.has_header('Last-Modified'): 
    109109        response['Last-Modified'] = http_date() 
     
    139139def _generate_cache_key(request, headerlist, key_prefix): 
    140140    """Returns a cache key from the headers given in the header list.""" 
    141     ctx = md5.new() 
     141    ctx = md5_constructor() 
    142142    for header in headerlist: 
    143143        value = request.META.get(header, None) 
  • django/trunk/tests/regressiontests/cache/tests.py

    r8085 r8193  
    44# Uses whatever cache backend is set in the test settings file. 
    55 
     6import os 
     7import shutil 
     8import tempfile 
    69import time 
    710import unittest 
     11 
    812from django.core.cache import cache 
     13from django.core.cache.backends.filebased import CacheClass as FileCache 
     14from django.http import HttpResponse 
    915from django.utils.cache import patch_vary_headers 
    10 from django.http import HttpResponse 
     16from django.utils.hashcompat import md5_constructor 
    1117 
    1218# functions/classes for complex data type tests 
     
    2834        cache.add("addkey1", "newvalue") 
    2935        self.assertEqual(cache.get("addkey1"), "value") 
    30          
     36 
    3137    def test_non_existent(self): 
    3238        # get with non-existent keys 
     
    8187        cache.set('expire3', 'very quickly', 1) 
    8288 
    83         time.sleep(2)         
     89        time.sleep(2) 
    8490        self.assertEqual(cache.get("expire1"), None) 
    85          
     91 
    8692        cache.add("expire2", "newvalue") 
    8793        self.assertEqual(cache.get("expire2"), "newvalue") 
     
    99105            self.assertEqual(cache.get(key), value) 
    100106 
    101 import os 
    102 import md5 
    103 import shutil 
    104 import tempfile 
    105 from django.core.cache.backends.filebased import CacheClass as FileCache 
    106107 
    107108class FileBasedCacheTests(unittest.TestCase): 
     
    113114        os.mkdir(self.dirname) 
    114115        self.cache = FileCache(self.dirname, {}) 
    115          
     116 
    116117    def tearDown(self): 
    117118        shutil.rmtree(self.dirname) 
    118          
     119 
    119120    def test_hashing(self): 
    120121        """Test that keys are hashed into subdirectories correctly""" 
    121122        self.cache.set("foo", "bar") 
    122         keyhash = md5.new("foo").hexdigest() 
     123        keyhash = md5_constructor("foo").hexdigest() 
    123124        keypath = os.path.join(self.dirname, keyhash[:2], keyhash[2:4], keyhash[4:]) 
    124125        self.assert_(os.path.exists(keypath)) 
    125          
     126 
    126127    def test_subdirectory_removal(self): 
    127128        """ 
     
    129130        """ 
    130131        self.cache.set("foo", "bar") 
    131         keyhash = md5.new("foo").hexdigest() 
     132        keyhash = md5_constructor("foo").hexdigest() 
    132133        keypath = os.path.join(self.dirname, keyhash[:2], keyhash[2:4], keyhash[4:]) 
    133134        self.assert_(os.path.exists(keypath)) 
     
    140141class CacheUtils(unittest.TestCase): 
    141142    """TestCase for django.utils.cache functions.""" 
    142      
     143 
    143144    def test_patch_vary_headers(self): 
    144         headers = (  
     145        headers = ( 
    145146            # Initial vary, new headers, resulting vary. 
    146147            (None, ('Accept-Encoding',), 'Accept-Encoding'), 
  • django/trunk/tests/regressiontests/file_uploads/tests.py

    r8096 r8193  
    11import os 
    22import errno 
    3 import sha 
    43import shutil 
    54import unittest 
     
    98from django.test import TestCase, client 
    109from django.utils import simplejson 
     10from django.utils.hashcompat import sha_constructor 
    1111 
    1212from models import FileModel, UPLOAD_ROOT, UPLOAD_TO 
     
    4646        for key in post_data.keys(): 
    4747            try: 
    48                 post_data[key + '_hash'] = sha.new(post_data[key].read()).hexdigest() 
     48                post_data[key + '_hash'] = sha_constructor(post_data[key].read()).hexdigest() 
    4949                post_data[key].seek(0) 
    5050            except AttributeError: 
    51                 post_data[key + '_hash'] = sha.new(post_data[key]).hexdigest() 
     51                post_data[key + '_hash'] = sha_constructor(post_data[key]).hexdigest() 
    5252 
    5353        response = self.client.post('/file_uploads/verify/', post_data) 
  • django/trunk/tests/regressiontests/test_client_regress/models.py

    r8082 r8193  
    11""" 
    22Regression tests for the Test Client, especially the customized assertions. 
    3  
    43""" 
     4 
    55from django.test import Client, TestCase 
    66from django.core.urlresolvers import reverse 
    77from django.core.exceptions import SuspiciousOperation 
    8 import os 
    9 import sha 
    108 
    119class AssertContainsTests(TestCase): 
     
    2523        except AssertionError, e: 
    2624            self.assertEquals(str(e), "Response should not contain 'once'") 
    27              
     25 
    2826        try: 
    2927            self.assertContains(response, 'never', 1) 
     
    288286class ExceptionTests(TestCase): 
    289287    fixtures = ['testdata.json'] 
    290      
     288 
    291289    def test_exception_cleared(self): 
    292290        "#5836 - A stale user exception isn't re-raised by the test client." 
     
    301299 
    302300        # At this point, an exception has been raised, and should be cleared. 
    303          
     301 
    304302        # This next operation should be successful; if it isn't we have a problem. 
    305303        login = self.client.login(username='staff', password='password')