views:

394

answers:

4

Let's say that I have a system that has some pages that are public (both non-authenticated users and logged-in users can view) and others which only logged-in users can view.

I want the template to show slightly different content for each of these two classes of pages. The @login_required view decorator is always used on views which only logged-in users can view. However, my template would need to know whether this decorator is used on the view from which the template was invoked from.

Please keep in mind that I do not care whether the user is logged in or not for the public pages. What I care about is whether a page can be viewed by the general public, and the absence of a @login_required decorator will tell me that.

Can anyone throw me a hint on how the template would know whether a particular decorator is being used on the view from which the template invoked from?

+3  A: 

I would pass an extra context variable into the template.

So, the view that has @login_required would pass a variable like private: True and the other views would pass private: False

Van Gale
I ended up doing this in the short term. Perfectly effective, but not quite pythonic since I could derive this value from the absence of the @login_required decorator.
+1  A: 

Why does your template need to know this? If the @login_required decorator is used, the view itself prevents people who aren't logged in from ever reaching the page and therefore never seeing the template to begin with.

Soviut
Probably the template is referred to from multiple places, some entry points having @login_required, and some not.
Blair Conrad
Yeah, that's what I assumed.
Van Gale
Blaird is correct. The template is my base.html template which is the root parent of all other templates on my website.
A: 

Templates are hierarchical so why not have a @login_required version and a "no @login_required" version, both of which inherit from the same parent?

This would keep the templates a lot cleaner and easier to maintain.

Dave Webb
There is a subtle diff. with what I want to do. I wnat to check in my view whether a particular template is used, not implement the opposite template behavior.
+5  A: 

Yes, it is possible, but not terribly straightforward. The complicating factor is that Django's login_required decorator actually passes through 2 levels of indirection (one dynamic function and one other decorator), to end up at django.contrib.auth.decorators._CheckLogin, which is a class with a __call__ method.

Let's say you have a non-django, garden-variety decorated function that looks like this:

def my_decorator(func):
    def inner():
        return func()
    return inner

@my_decorator
def foo():
    print foo.func_name

# results in: inner

Checking to see if the function foo has been wrapped can be as simple as checking the function object's name. You can do this inside the function. The name will actually be the name of the last wrapper function. For more complicated cases, you can use the inspect module to walk up the outer frames from the current frame if you're looking for something in particular.

In the case of Django, however, the fact that the decorator is actually an instance of the _CheckLogin class means that the function is not really a function, and therefore has no func_name property: trying the above code will raise an Exception.

Looking at the source code for django.contrib.auth.decorators._CheckLogin, however, shows that the _CheckLogin instance will have a login_url property. This is a pretty straightforward thing to test for:

@login_required
def my_view(request):
    is_private = hasattr(my_view, 'login_url')

Because _CheckLogin is also used to implement the other auth decorators, this approach will also work for permission_required, etc. I've never actually had a need to use this, however, so I really can't comment on what you should look for if you have multiple decorators around a single view... an exercise left to the reader, I guess (inspect the frame stack?).

As unrequested editorial advice, however, I would say checking the function itself to see if it was wrapped like this strikes me as a bit fiddly. You can probably imagine all sorts of unpredictable behaviour waiting to happen when a new developer comes to the project as slaps on some other decorator. In fact, you're also exposed to changes in the django framework itself... a security risk waiting to happen.

I would recommend Van Gale's approach for that reason as something that is explicit, and therefore a much more robust implementation.

Jarret Hardie
Your first code block won't work as written. my_decorator annihilates the function passed to it, so putting a print statement in foo is useless.
David Berger
Thanks David, you're right... I was glossing over the first block to get to the main point without carefully reading what I had typed. Appreciate the edit.
Jarret Hardie
Thank you for the in-depth answer. Your ending comment is what I ended up doing, even though I think there may be some more "pythonic;' way of doing with without repeating myself. I guess in my case doing this the "right" way is way too complicated and not very robust.
I would still need a way to get a reference to the view function from within the template, but my guess is that could be done as well in some roundabout way using a custom processor in the RequestContext.