=== added directory 'tests/regressiontests/decorators'
=== added file 'tests/regressiontests/decorators/__init__.py'
=== added file 'tests/regressiontests/decorators/models.py'
--- tests/regressiontests/decorators/models.py	1970-01-01 00:00:00 +0000
+++ tests/regressiontests/decorators/models.py	2007-11-20 05:38:52 +0000
@@ -0,0 +1,2 @@
+# A models.py so that tests run.
+

=== added file 'tests/regressiontests/decorators/tests.py'
--- tests/regressiontests/decorators/tests.py	1970-01-01 00:00:00 +0000
+++ tests/regressiontests/decorators/tests.py	2007-12-01 23:21:29 +0000
@@ -0,0 +1,40 @@
+from unittest import TestCase
+from sys import version_info
+
+from django.views.decorators.http import require_http_methods
+from django.views.decorators.vary import vary_on_headers, vary_on_cookie
+from django.views.decorators.cache import cache_page, never_cache, cache_control
+from django.contrib.auth.decorators import user_passes_test
+from django.contrib.admin.views.decorators import staff_member_required
+
+def fully_decorated(request):
+    """Expected __doc__"""
+    return HttpResponse('<html><body>dummy</body></html>')
+fully_decorated.anything = "Expected __dict__"
+
+fully_decorated = require_http_methods(["GET"])(fully_decorated)
+
+fully_decorated = vary_on_headers('Accept-language')(fully_decorated)
+fully_decorated = vary_on_cookie(fully_decorated)
+
+fully_decorated = cache_page(60*15)(fully_decorated)
+fully_decorated = cache_control(private=True)(fully_decorated)
+fully_decorated = never_cache(fully_decorated)
+
+fully_decorated = user_passes_test(lambda u:True)(fully_decorated)
+fully_decorated = staff_member_required(fully_decorated)
+
+
+class DecoratorsTest(TestCase):
+
+    def test_attributes(self):
+        """
+        Tests that django decorators set certain attributes of the wrapped
+        function.
+        """
+        self.assertEquals(fully_decorated.__doc__, 'Expected __doc__')
+        self.assertEquals(fully_decorated.__dict__['anything'], 'Expected __dict__')
+        # Only run this test on Python 2.4 or later since __name__ can't be
+        # assigned to in earlier versions.
+        if version_info[0] >= 2 and version_info[1] >= 4:
+            self.assertEquals(fully_decorated.__name__, 'fully_decorated')

=== modified file 'django/contrib/admin/views/decorators.py'
--- django/contrib/admin/views/decorators.py	2007-11-18 19:06:47 +0000
+++ django/contrib/admin/views/decorators.py	2007-12-01 19:54:01 +0000
@@ -1,3 +1,11 @@
+import base64
+import md5
+import cPickle as pickle
+try:
+    from functools import wraps
+except ImportError:
+    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
+
 from django import http, template
 from django.conf import settings
 from django.contrib.auth.models import User
@@ -5,8 +13,6 @@
 from django.shortcuts import render_to_response
 from django.utils.translation import ugettext_lazy, ugettext as _
 from django.utils.safestring import mark_safe
-import base64, datetime, md5
-import cPickle as pickle
 
 ERROR_MESSAGE = ugettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.")
 LOGIN_FORM_KEY = 'this_is_the_login_form'
@@ -104,4 +110,4 @@
             else:
                 return _display_login_form(request, ERROR_MESSAGE)
 
-    return _checklogin
+    return wraps(view_func)(_checklogin)

=== modified file 'django/contrib/auth/decorators.py'
--- django/contrib/auth/decorators.py	2007-11-18 19:06:47 +0000
+++ django/contrib/auth/decorators.py	2007-12-01 19:54:17 +0000
@@ -1,3 +1,8 @@
+try:
+    from functools import wraps
+except ImportError:
+    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
+
 from django.contrib.auth import REDIRECT_FIELD_NAME
 from django.http import HttpResponseRedirect
 from django.utils.http import urlquote
@@ -9,7 +14,7 @@
     that takes the user object and returns True if the user passes.
     """
     def decorate(view_func):
-        return _CheckLogin(view_func, test_func, login_url, redirect_field_name)
+        return wraps(view_func)(_CheckLogin(view_func, test_func, login_url, redirect_field_name))
     return decorate
 
 def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME):

=== modified file 'django/template/defaultfilters.py'
--- django/template/defaultfilters.py	2007-12-01 17:59:41 +0000
+++ django/template/defaultfilters.py	2007-12-01 19:54:56 +0000
@@ -2,6 +2,10 @@
 
 import re
 import random as random_module
+try:
+    from functools import wraps
+except ImportError:
+    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
 
 from django.template import Variable, Library
 from django.conf import settings
@@ -35,7 +39,7 @@
     for attr in ('is_safe', 'needs_autoescape'):
         if hasattr(func, attr):
             setattr(_dec, attr, getattr(func, attr))
-    return _dec
+    return wraps(func)(_dec)
 
 ###################
 # STRINGS         #

=== modified file 'django/utils/decorators.py'
--- django/utils/decorators.py	2007-10-10 05:38:02 +0000
+++ django/utils/decorators.py	2007-12-01 19:55:10 +0000
@@ -1,6 +1,10 @@
 "Functions that help with dynamically creating decorators for views."
 
 import types
+try:
+    from functools import wraps
+except ImportError:
+    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
 
 def decorator_from_middleware(middleware_class):
     """
@@ -53,5 +57,5 @@
                 if result is not None:
                     return result
             return response
-        return _wrapped_view
+        return wraps(view_func)(_wrapped_view)
     return _decorator_from_middleware

=== modified file 'django/utils/functional.py'
--- django/utils/functional.py	2007-11-18 19:06:47 +0000
+++ django/utils/functional.py	2007-12-01 21:25:05 +0000
@@ -1,8 +1,118 @@
+# License for code in this file that was taken from Python 2.5.
+
+# PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
+# --------------------------------------------
+#
+# 1. This LICENSE AGREEMENT is between the Python Software Foundation
+# ("PSF"), and the Individual or Organization ("Licensee") accessing and
+# otherwise using this software ("Python") in source or binary form and
+# its associated documentation.
+#
+# 2. Subject to the terms and conditions of this License Agreement, PSF
+# hereby grants Licensee a nonexclusive, royalty-free, world-wide
+# license to reproduce, analyze, test, perform and/or display publicly,
+# prepare derivative works, distribute, and otherwise use Python
+# alone or in any derivative version, provided, however, that PSF's
+# License Agreement and PSF's notice of copyright, i.e., "Copyright (c)
+# 2001, 2002, 2003, 2004, 2005, 2006, 2007 Python Software Foundation;
+# All Rights Reserved" are retained in Python alone or in any derivative
+# version prepared by Licensee.
+#
+# 3. In the event Licensee prepares a derivative work that is based on
+# or incorporates Python or any part thereof, and wants to make
+# the derivative work available to others as provided herein, then
+# Licensee hereby agrees to include in any such work a brief summary of
+# the changes made to Python.
+#
+# 4. PSF is making Python available to Licensee on an "AS IS"
+# basis.  PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+# IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
+# DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+# FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
+# INFRINGE ANY THIRD PARTY RIGHTS.
+#
+# 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+# FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+# A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
+# OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+#
+# 6. This License Agreement will automatically terminate upon a material
+# breach of its terms and conditions.
+#
+# 7. Nothing in this License Agreement shall be deemed to create any
+# relationship of agency, partnership, or joint venture between PSF and
+# Licensee.  This License Agreement does not grant permission to use PSF
+# trademarks or trade name in a trademark sense to endorse or promote
+# products or services of Licensee, or any third party.
+#
+# 8. By copying, installing or otherwise using Python, Licensee
+# agrees to be bound by the terms and conditions of this License
+# Agreement.
+
+
 def curry(_curried_func, *args, **kwargs):
     def _curried(*moreargs, **morekwargs):
         return _curried_func(*(args+moreargs), **dict(kwargs, **morekwargs))
     return _curried
 
+### Begin from Python 2.5 functools.py ########################################
+
+# Changes made to this Python 2.5 code:
+#   * swapped ``partial`` for ``curry`` to maintain backwards-compatibility
+#     in Django.
+#   * Wrapped the setattr call in update_wrapper with a try-except block to
+#     make it compatible with Python 2.3, which doesn't allow assigning
+#     to __name__.
+
+# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007 Python Software Foundation.
+# All Rights Reserved.
+
+# update_wrapper() and wraps() are tools to help write
+# wrapper functions that can handle naive introspection
+
+WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')
+WRAPPER_UPDATES = ('__dict__',)
+def update_wrapper(wrapper,
+                   wrapped,
+                   assigned = WRAPPER_ASSIGNMENTS,
+                   updated = WRAPPER_UPDATES):
+    """Update a wrapper function to look like the wrapped function
+
+       wrapper is the function to be updated
+       wrapped is the original function
+       assigned is a tuple naming the attributes assigned directly
+       from the wrapped function to the wrapper function (defaults to
+       functools.WRAPPER_ASSIGNMENTS)
+       updated is a tuple naming the attributes off the wrapper that
+       are updated with the corresponding attribute from the wrapped
+       function (defaults to functools.WRAPPER_UPDATES)
+    """
+    for attr in assigned:
+        try:
+            setattr(wrapper, attr, getattr(wrapped, attr))
+        except TypeError: # Python 2.3 doesn't allow assigning to __name__.
+            pass
+    for attr in updated:
+        getattr(wrapper, attr).update(getattr(wrapped, attr))
+    # Return the wrapper so this can be used as a decorator via curry()
+    return wrapper
+
+def wraps(wrapped,
+          assigned = WRAPPER_ASSIGNMENTS,
+          updated = WRAPPER_UPDATES):
+    """Decorator factory to apply update_wrapper() to a wrapper function
+
+       Returns a decorator that invokes update_wrapper() with the decorated
+       function as the wrapper argument and the arguments to wraps() as the
+       remaining arguments. Default arguments are as for update_wrapper().
+       This is a convenience function to simplify applying curry() to
+       update_wrapper().
+    """
+    return curry(update_wrapper, wrapped=wrapped,
+                 assigned=assigned, updated=updated)
+
+### End from Python 2.5 functools.py ##########################################
+
 def memoize(func, cache, num_args):
     """
     Wrap a function so that results for any argument tuple are stored in

=== modified file 'django/views/decorators/cache.py'
--- django/views/decorators/cache.py	2006-11-14 04:53:49 +0000
+++ django/views/decorators/cache.py	2007-12-01 19:55:26 +0000
@@ -11,6 +11,11 @@
 account on caching -- just like the middleware does.
 """
 
+try:
+    from functools import wraps
+except ImportError:
+    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
+
 from django.utils.decorators import decorator_from_middleware
 from django.utils.cache import patch_cache_control, add_never_cache_headers
 from django.middleware.cache import CacheMiddleware
@@ -26,7 +31,7 @@
             patch_cache_control(response, **kwargs)
             return response
 
-        return _cache_controlled
+        return wraps(viewfunc)(_cache_controlled)
 
     return _cache_controller
 
@@ -39,4 +44,4 @@
         response = view_func(request, *args, **kwargs)
         add_never_cache_headers(response)
         return response
-    return _wrapped_view_func
+    return wraps(view_func)(_wrapped_view_func)

=== modified file 'django/views/decorators/http.py'
--- django/views/decorators/http.py	2006-11-14 04:53:49 +0000
+++ django/views/decorators/http.py	2007-12-01 19:55:39 +0000
@@ -2,6 +2,11 @@
 Decorators for views based on HTTP headers.
 """
 
+try:
+    from functools import wraps
+except ImportError:
+    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
+
 from django.utils.decorators import decorator_from_middleware
 from django.middleware.http import ConditionalGetMiddleware
 from django.http import HttpResponseNotAllowed
@@ -24,7 +29,7 @@
             if request.method not in request_method_list:
                 return HttpResponseNotAllowed(request_method_list)
             return func(request, *args, **kwargs)
-        return inner
+        return wraps(func)(inner)
     return decorator
 
 require_GET = require_http_methods(["GET"])

=== modified file 'django/views/decorators/vary.py'
--- django/views/decorators/vary.py	2006-11-14 04:53:49 +0000
+++ django/views/decorators/vary.py	2007-12-01 19:55:53 +0000
@@ -1,3 +1,8 @@
+try:
+    from functools import wraps
+except ImportError:
+    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
+
 from django.utils.cache import patch_vary_headers
 
 def vary_on_headers(*headers):
@@ -16,7 +21,7 @@
             response = func(*args, **kwargs)
             patch_vary_headers(response, headers)
             return response
-        return inner_func
+        return wraps(func)(inner_func)
     return decorator
 
 def vary_on_cookie(func):
@@ -32,4 +37,4 @@
         response = func(*args, **kwargs)
         patch_vary_headers(response, ('Cookie',))
         return response
-    return inner_func
+    return wraps(func)(inner_func)

