Django

Code

Ticket #1840 (closed: duplicate)

Opened 3 years ago

Last modified 1 year ago

django's decorators don't assign necessary attributes to the wrapper

Reported by: russblau@imapmail.org Assigned to: nobody
Milestone: Component: Core framework
Version: SVN Keywords:
Cc: gary.wilson@gmail.com, myer0052@gmail.com, ben@tangozebra.com Triage Stage: Design decision needed
Has patch: 1 Needs documentation: 0
Needs tests: 1 Patch needs improvement: 0

Description

If a view function is wrapped with a Python decorator, the admin interface will show that decorator in place of the actual view (in admin/doc/views). For example:

/admin/doc/views/
View function: django.contrib.admin.views.decorators._checklogin

The actual view function is 'django.contrib.admin.views.doc.view_index', but it's wrapped with the staff_member_required() decorator, which in turn returns the _checklogin function object.

Attachments

admin-doc-views.diff (2.7 kB) - added by dummy@habmalnefrage.de on 08/13/06 11:26:26.
this maybe a solution
RaviKumar2.doc (54.5 kB) - added by anonymous on 10/20/06 03:52:44.
admindocdecoratorfix.diff (1.0 kB) - added by dan.ferrante at gmail on 02/08/07 13:01:08.
SVN patch for this ticket that changes decorator code to report the decorated function to admin.doc views

Change History

05/11/06 16:09:34 changed by lukeplant

06/02/06 00:13:26 changed by adrian

  • priority changed from normal to low.
  • severity changed from normal to minor.

I'm not sure what we can do to fix this...We'd have to somehow peek into the function to see whether it was being decorated or not. Either that, or each view decorator would have to mark itself as a decorator somehow, and the admin view-doc system would have to keep an eye out for that special mark. Hmmmm. Any other ideas?

06/02/06 19:40:11 changed by lukeplant

You could make the decorators set the name of the function they return to be the name of function they take:

def mydecorator(myfunc):
    def wrapper(*args, **kwargs):
        # do something here, then...
        return myfunc(*args, **kwargs)
    wrapper.__name__ = myfunc.__name__
    wrapper.__doc__ = myfunc.__doc__
    return wrapper

Then everything would be lovely. Of course, you'd have to do that on every decorator. Alternatively, you could use the module I posted a link to above, which does that automatically, and a lot more (including making 'wrapped' have the same function signature as 'myfunc', so you can introspect it nicely).

06/02/06 20:03:43 changed by lukeplant

Ah, except that it doesn't work. If you include module as well, then you get the whole name correct, but for some reason the documentation doesn't show up. And the 'decorator' module I linked seems to work even less (I'm sure I found that module from a reputable source like Ian Bicking or someone). I'll investigate it a bit.

06/02/06 20:36:21 changed by lukeplant

OK, the first part of my last comment was nonsense -- the method described does work (I was getting mixed up between difference view functions).

As for using the 'decorator' module, there is a very easy one line fix to apply (he just forgot to do the wrapper.__module__ = myfunc.__module__ bit for some reason), then it works for the documentation. However, I'm getting bizarro messages when actually trying to *use* the views.

06/02/06 23:30:24 changed by mattimustang@gmail.com

This is how i'm doing it in one of my decorators that adds extra context to a generic view:

class GenericViewContextAdder(object):    
    """
    Add extra context to a generic view function.
    Example usage:
        from django.views.generic import list_detail
        from nong.common.views import GenericViewContextAdder
        foo_list = GenericViewContextAdder(list_detail.object_list, 'title', bar=lambda original: mymodels.get_object(name__exact=original))
    """
    def __init__(self, method_to_wrap, *arguments_to_steal,
        **arguments_to_transform):
        self.method_to_wrap = method_to_wrap
        self.arguments_to_steal = arguments_to_steal
        self.arguments_to_transform = arguments_to_transform
        doc = ""
        for l in self.method_to_wrap.__doc__.splitlines():
            doc += l.replace('    ', '', 1)
            doc += '\n'
        doc = doc.replace('Context:', '\nContext:\n\n', 1)
        doc = doc.replace('Templates:', '\nTemplates:\n\n', 1)
        self.method_to_wrap.__doc__ = 'This view is a wrapper around the ' \
            + doc
        self.method_to_wrap.__doc__ += '\nExtra context:\n\n'
        for stolen_argument in self.arguments_to_steal:
            self.method_to_wrap.__doc__ += '\n\t%s\n' % stolen_argument
        for stolen_argument, transformation in self.arguments_to_transform:
            self.method_to_wrap.__doc__ += '\n\t%s=%s\n' % \
                (stolen_argument, transformation)

    def __call__(self, *args, **kwargs):
        extra_context = {}
        for stolen_argument in self.arguments_to_steal:
            extra_context[stolen_argument] = kwargs.pop(stolen_argument)
        for stolen_argument, transformation in self.arguments_to_transform:
            original_value = kwargs.pop(stolen_argument)
            transformed_value = transformation(original_value)
            extra_context[stolen_argument] = transformed_value
        kwargs.setdefault('extra_context', {}).update(extra_context)
        return self.method_to_wrap(*args, **kwargs)

    @property
    def __name__(self):
        return self.method_to_wrap.__name__

    @property
    def __module__(self):
        return self.method_to_wrap.__module__

This works for me.

06/06/06 22:57:10 changed by adrian

Luke -- which bizarro messages are you getting when you try to use the views? I've added the __doc__, __name__ and __module__ assignments in my local copy of django.utils.decorators.decorator_from_middleware, and the problem is fixed with no side effects.

06/10/06 06:12:24 changed by lukeplant

Adrian -- the __doc__, __name__ and __module__ assignments should be fine. I was using the 'decorator' module, which does that for you and also creates 'signature preserving' decorators, so you can use getargspec etc on them. However, I've just discovered that I was using it incorrectly, and using it correctly works very well.

The advantages of using the module are:

  • You don't have to repeat the __doc__, __name__ etc assignments (although I guess you could put them in a utility function)
  • You preserve the signature of the original functions, so you can introspect them.

The disadvantages are:

  • Adding more code to Django (120 lines, which includes doctests)
  • The changes you have to make are not quite as trivial as I thought before -- I thought it was just adding staff_member_required = decorator(staff_member_required . In reality, you have to change the decorator function like this:
def staff_member_required(view_func):
    def _wrapped(request, *args **kwargs):
        # Do stuff with request
        return view_func(request, *args, **kwargs)

to

@decorator.decorator
def staff_member_required(view_func, request, *args, **kwargs):
    # Do stuff with request
    return view_func(request, *args, **kwargs)

The transformation is very straightforward in each case -- write a 'caller' (which takes a function and the arguments to pass to it) instead of a typical decorator, and then use 'decorator' to make it into a very well-behaved decorator. In some sense it is a simplification -- you have a simple higher order function instead of higher order function plus closure. On the other hand, it is a slight obfuscation, since staff_member_required() looks like it takes several arguments, but it ends up as a callable that takes a single argument.

Overall I think I'm in favour of using it -- it would also solve the problem that Gary Wilson just mentioned in django-devs about not being able to use decorators with filters.

07/04/06 22:44:15 changed by hi-world cup

  • cc set to hi-world, cup.
  • keywords set to rthml tab space editor js.
  • summary changed from admin/doc/views shows decorator instead of actual view function to hi-world cup.

07/04/06 22:57:46 changed by adrian

  • cc deleted.
  • summary changed from hi-world cup to admin/doc/views shows decorator instead of actual view function.

08/13/06 11:25:49 changed by anonymous

  • version set to SVN.
  • summary changed from admin/doc/views shows decorator instead of actual view function to [patch] admin/doc/views shows decorator instead of actual view function.

08/13/06 11:26:26 changed by dummy@habmalnefrage.de

  • attachment admin-doc-views.diff added.

this maybe a solution

09/26/06 20:27:18 changed by Gary Wilson <gary.wilson@gmail.com>

We could require python2.5 and use the update_wrapper and wraps functions of the new functools module :)

Or, if we don't want to require 2.5, try to import the function(s) from the functools module, and if that fails then use a fake function/decorator that does nothing. Only people using 2.5 would see the benefit, but on the bright side it would require less code and would be the future.

09/26/06 20:34:56 changed by Gary Wilson <gary.wilson@gmail.com>

  • cc set to gary.wilson@gmail.com.
  • keywords deleted.
  • component changed from Admin interface to Core framework.
  • summary changed from [patch] admin/doc/views shows decorator instead of actual view function to django's decorators don't assign necessary attributes to the wrapper.

There are more decorators than just in the admin interface. Also removing patch from title since it's invalid.

09/26/06 20:38:06 changed by Gary Wilson <gary.wilson@gmail.com>

Here is the description of the update_wrapper function:

Update a wrapper function to look like the wrapped function. The optional arguments are tuples to specify which attributes of the original function are assigned directly to the matching attributes on the wrapper function and which attributes of the wrapper function are updated with the corresponding attributes from the original function. The default values for these arguments are the module level constants WRAPPER_ASSIGNMENTS (which assigns to the wrapper function's name, module and documentation string) and WRAPPER_UPDATES (which updates the wrapper function's instance dictionary).

The main intended use for this function is in decorator functions which wrap the decorated function and return the wrapper. If the wrapper function is not updated, the metadata of the returned function will reflect the wrapper definition rather than the original function definition, which is typically less than helpful.

10/20/06 03:52:44 changed by anonymous

  • attachment RaviKumar2.doc added.

01/17/07 21:48:25 changed by Robert Myers <myer0052@gmail.com>

  • cc changed from gary.wilson@gmail.com to gary.wilson@gmail.com, myer0052@gmail.com.

02/01/07 17:15:34 changed by Michael Radziej <mir@noris.de>

  • stage changed from Unreviewed to Design decision needed.

(follow-up: ↓ 18 ) 02/08/07 12:59:49 changed by dan.ferrante at gmail

  • has_patch set to 1.
  • needs_tests set to 1.

Hi. I made a really simple patch that is horribly simple and works fine AFAIK. All it does is add the following code, within the decorator functions in django.contrib.auth.decorators and django.contrib.admin.decorators:

_checklogin.__name__ = view_func.__name__
_checklogin.__module__ = view_func.__module__

This provides the appropriate variables to the admin.view_index view to make everything work swimmingly, and shouldn't mess with anything else.

02/08/07 13:01:08 changed by dan.ferrante at gmail

  • attachment admindocdecoratorfix.diff added.

SVN patch for this ticket that changes decorator code to report the decorated function to admin.doc views

(in reply to: ↑ 17 ) 02/28/07 00:18:18 changed by ubernostrum

Replying to dan.ferrante at gmail:

__name__ is read-only in Python 2.3, so this would break compatibility.

07/20/07 11:17:04 changed by anonymous

  • cc changed from gary.wilson@gmail.com, myer0052@gmail.com to gary.wilson@gmail.com, myer0052@gmail.com, ben@tangozebra.com.

11/20/07 00:45:34 changed by gwilson

  • status changed from new to closed.
  • resolution set to duplicate.

Marking this a dup of #5701 since we have a patch there.


Add/Change #1840 (django's decorators don't assign necessary attributes to the wrapper)




Change Properties
Action