I want to create dynamic menus according to user permissions. As was already discussed here and by the the documentation itself, I know that I can achieve this in the templates using the following snippet:
{% if perms.polls.can_vote %}
<li>
Vote
</li>
{% endif %}
But the problem is that for security reasons I want to limit the access to the views too. The snippet that I found in the documentation is the following:
from django.contrib.auth.decorators import permission_required
def my_view(request):
# ...
my_view = permission_required('polls.can_vote', login_url='/loginpage/')(my_view)
Isn't this against DRY principle? Isn't there a way to define only in one place what is the permission needed for each url? Perhaps in urls.py?
EDIT: (See end of post for the original text of the answer with the initial, simple idea.)
After being kindly stricken with a cluebat (see the OP's comment below), I find I can see more to the problem than before. Sorry it took so long. Anyway:
Would this kind of template be alright for you?
{% for mi in dyn_menu_items %}
{% if mi.authorised %}
<a href={{ mi.url }}>{{ mi.title }}</a>
{% endif %}
{% endfor %}
To make this work on the Python side, you could use RequestContext in your views with a custom context processor setting the dyn_menu_items variable appropriately. In case some background information is required, the Advanced Templates chapter of the Django Book introduces RequestContext, shows how to use it with render_to_response (kinda important :-)) etc.
Also, I guess at this point it could be useful to put the view functions responsible for the locked-up sections of your site in a list somewhere:
_dyn_menu_items = [(url1, view1, title1, perm1), ...]
Then you could map a couple of functions, say prepare_pattern and prepare_menu_item across that list, having it work roughly like so:
def prepare_pattern(menu_item):
url1, view, title, perm = menu_item
pattern = PREPARE_URLCONF_ENTRY_SOMEHOW(...) # fill in as appropriate
return pattern
def prepare_menu_item(menu_item):
url, view, title, perm = menu_item
mi = PREPARE_THE_BIT_FOR_REQUESTCONTEXT(...) # as above
return mi
These could be combined into a single function, of course, but not everybody would find the result more readable... Anyway, the output of map(prepare_menu_item, _dyn_menu_items) would need to be a dictionary to be passed to your views by a helpful context processor (the figuring out of which, it being the slightly tedious bit here, I'll leave to you ;-)), whereas the output of map(prepare_pattern, _dyn_menu_items), let's call it dyn_menu_patterns, would be used in patterns('', *dyn_menu_patterns), to be used in your URLconf.
I hope this makes sense and is of some help...
THE PRE-EDIT ANSWER:
Based on your short description, I'm not sure what solution would be best for you... But if the permission_required snippet does what you want, just not DRY-ly enough, how about rolling your own wrapper:
def ask_to_login(perm, view):
return permission_required(perm, login_url='/loginpage/', view)
You could put this anywhere, including in URLconf. Then you could replace all mentions of '/loginpage/' with reference to a variable defined towards the top of your URLs file and you'd have yourself a solution with a single mention of the actual login URL, for one-place-only update of said URL should you have to move it around. :-)
Of course the views would still need to be wrapped explicitly; if that bothers you, you could try to make ask_to_login into a decorator for easy wrapping at the definition site. (But perhaps it's really best not to do it, lest you force yourself to dig your views from under the decorator in case you need them undecorated at some point in the future.)
I'm aware this question was asked a couple of weeks ago now, but you mentioned http://code.google.com/p/greatlemers-django-tools/ in one of your comments so I thought I'd chip in.
The project is still active (although it's slightly on the backburner at the moment) but I'm not sure if it is as DRY as you're after. You would still have to specify permissions twice, once in the model object for the menu item and once on the view. This isn't necessarily a bad thing however as the permissions that you define on the menu item may be slightly different from those on the view.
If you wanted to do everything in one place I'd probably suggest a combination of a utility function for use in urls.py that can add restrictions to a view whilst also storing said restriction somewhere for use with a special template tag. I'd imagine it may look something like this.
# Stored in a file named access_check_utils.py say.
from django.conf.urls.defaults import url
from django.core.urlresolvers import get_callable
from django.contrib.auth.decorators import permission_required
access_checked_urls = {}
def access_checked_url(regex, view, kwargs=None, name=None, prefix='', perms=None, login_url=None):
if perms is None:
perms = []
callback = None
if callable(view):
callback = view
elif isinstance(view, basestring):
if prefix:
view_path = "%s.%s" % (prefix, view)
else:
view_path = view
try:
callback = get_callable(view_path)
except:
callback = None
if callback is not None:
# Add all the permissions
for perm in perms:
callback = permission_required(perm, login_url=login_url)(callback)
if name is not None:
access_checked_urls[name] = perms
else:
callback = view
return url(regex, callback, kwargs=kwargs, name=name, prefix=prefix)
That should work for the pit needed in urls.py called the same way as you would with a normal url but with the added perms and login_url parameters (perms should be a list of all the relevant ones).
# In a templatetag folder somewhere
from django import template
from django.core.urlresolvers import
# This needs to point to the right place.
from access_check_utils import access_checked_urls
register = template.Library()
#register.inclusion_tag("access_checked_link.html", takes_context=True)
def access_checked_link(context, title, url, *args, **kwargs):
perms = access_checked_urls.get(url, [])
if not perms:
allowed = True
else:
allowed = context.request.user.has_perms(perms)
return { 'allowed': allowed,
'url': reverse(url, *args, **kwargs),
'title': title }
This would have an associated template file like:
{% if allowed %}{{ title }}{% endif %}
I've not tested this fully, but it should work (or at least be a good basis for something that should work). I'll probably even look to adding something like this into gdt_nav allowing it to check for these base permissions if they exist, and then checking for any extras added.
Hope this is of some help.
--
G
Related
Perhaps my question will seem somewhat simple and understandable, but even so.
In my project, I use standard pagination and sorting.
The problem is that they replace each other in the get request, for example I sort them and if I go to the second page then the set is not sorted. I understand the reason and it seems the answer lies on the top, but even so I could not find it.
Sorting:
Counter
Pagination example:
<a class="page-link" href="?page={{ history_application.previous_page_number }}">«</a>
The question is how to save the parameters that were transmitted earlier.
Try using a Django templatetag that updates the current request.GET parameters and adds new parameters using urllib.urlencode.
Create a templatetag:
# templatetags.app_tags
import urllib
#register.simple_tag(takes_context=True)
def url_add_query(context, **kwargs):
""" Updates the current path from existing GET parameters. """
request = context.get('request')
get = request.GET.copy()
get.update(kwargs)
return u'{path}?{params}'.format(path=request.path,
params=urllib.urlencode(get, 'utf-8'))
On your HTML template:
{% load app_tags %}
«
I have this models in Django:
News
Comments
Reactions
Relations are:
a News has various Comments
a Comment has various Reactions
The problem is the user (in request / session): the user may subscribe to a reaction, or a comment; he may be logged in or not. (it's a foo example, it doesn't have much sense)
I can't do in template:
{% for reaction in this_news.comments.reactions %}
{{ reaction.name }}
{% if reaction.user_subscribed %} #reaction.user_subscribed(request.user)...
You have subscribed this reaction!
{% endif %}
{% endfor %}
Problems are:
I can't call the method in the template with a parameter (see the comment above)
Models don't have access to request
Now i'm calling an init_user method in News Model, passing the request. Then i have the same method in Comment and Reaction model, and i have to set the user_subscribed property cycling the children of each model.
Isn't there a smarter way to do this?
EDIT: thanks to the Ignacio's hint about using custom tag i'm trying to do a generic mode to pass the user (avoiding the use of closures because i don't know how to use them atm):
def inject_user(parser, token):
try:
# split_contents() knows not to split quoted strings.
tag_name, method_injected, user = token.split_contents()
except ValueError:
raise template.TemplateSyntaxError("%r tag requires exactly three arguments" % token.contents.split()[0])
return InjectUserNode(method_injected, user)
class InjectUserNode(template.Node):
def __init__(self, method_injected, user):
self.method_injected = template.Variable(method_injected)
self.user = template.Variable(user)
def render(self, context):
try:
method_injected = self.method_injected.resolve(context)
user = self.user.resolve(context)
return method_injected(user)
except template.VariableDoesNotExist:
return ''
When i use it {% inject_user object.method_that_receives_a_user request.user %} i come to this error 'str' object is not callable in method_injected(user); how i can fix that?
Write custom template tags that take the user and set a context variable to indicate presence or absence of the criterion.
I've resolved it in a less elegant way, but it worked for me.
I've created a sort of singleton in my User defined class, with a property that i set in every view i need it.
The property is User.current.
Then, inside the models, where i need that i get the current user looking in User.current.
My Django app currently has URLs which are protected by 'permission_required()' functions.
This function is called in three different ways.
As a decorator in views.py, with hardcoded parameters.
As a plain function, with autogenerated parameter, in custom Class Based Generic Views.
As a function invoking views in urls.py, with hardcoded parameters.
I'm now adding a menu system to the app, and I need to make menu entries reflect whether the user has permission to request the URL of each menu entry. (Either by greying-out or hiding said entries.)
Is there a way of query the permissions required to a URL without requesting the URL?
The only solution I've thought of so far is to replace the decorator with a parameterless 'menu_permssion_required()' decorator and hardcode all of the permissions into a Python structure. This seems like a step backwards, as my custom Class Based Generic Views already autogenerate their required permissions.
Any suggestions on how to make a menu system which reflects URL permissions for the current user?
Here is an example of how to solve your problem:
First, Create a decorator wrapper to use instead of permission_required:
from django.contrib.auth.decorators import login_required, permission_required, user_passes_test
from django.core.exceptions import PermissionDenied
from functools import wraps
from django.utils.decorators import available_attrs
def require_perms(*perms):
def decorator(view_func):
view_func.permissions = perms
#wraps(view_func, assigned=available_attrs(view_func))
def _wrapped_view(request, *args, **kwargs):
for perm in perms:
return view_func(request, *args, **kwargs)
raise PermissionDenied()
return _wrapped_view
return decorator
Then, use it to decorate your views:
#require_perms('my_perm',)
def home(request):
.....
Then, add a tag to use for your menu items:
from django.core.urlresolvers import resolve
def check_menu_permissions(menu_path, user):
view = resolve(menu_path)
if hasattr(view.func, "permissions"):
permissions = view.func.permissions
for perm in permissions:
if user.has_perm(perm):
return True # Yep, the user can access this url
else:
return False # Nope, the user cannot access this url
return True # or False - depending on what is the default behavior
And finally, in your templates, when building the menu tree:
<button href="{{ some_path }} {% if not check_menu_permissions some_path request.user %}disabled="disabled"{% endif %} />
N.B. I've not tested the last part with the tag, but I hope you got the idea. The magic thing here is to add the permissions to the view_func in the decorator, and then you can access this using resolve(path). I'm not sure how this will behave in terms of performance, but after all that's just an idea.
EDIT: Just fixed a bug in the example..
Is there a way of query the permissions required to a URL without requesting the URL?
User.has_perm() and User.has_module_perms()
Any suggestions on how to make a menu system which reflects URL permissions for the current user?
I really like this question, because it concerns anyone that makes a website with django, so I find it really relevant. I've been through that myself and even coded a menu "system" in my first django project back in 2008. But since then I tried Pinax, and one of the (so many) things I learnt from their example projects is that it is completely unnecessary bloat.
So, I have no suggestion which I would support on how to make a menu "system" which respects the request user permissions.
I do have a suggestion on how to make a simple menu which respects the request user permissions, so that might not be completely unrelated.
Just make your menu in plain HTML, it's not like it's going to change so often that it has to be generated. That will also keep your Python code simpler.
Add to settings.TEMPLATE_CONTEXT_PROCESSORS: 'django.core.context_processors.PermWrapper'
Use the {{ perms }} proxy to User.has_perms.
Example:
{% if perms.auth %}
<li class="divider"></li>
{% if perms.auth.change_user %}
<li>
{% trans 'Users' %}
</li>
{% endif %}
{% if perms.auth.change_group %}
<li>
{% trans 'User groups' %}
</li>
{% endif %}
{% endif %}
{# etc, etc #}
That's how I keep navigation simple, stupid, and out of the way. But also I always include an autocomplete not far from the menus to allows the user to navigate to any detail page easily. So, that's all I know about navigation in django projects, I'm eager to read other answers !
I had a similar issue, but it went a little deeper. Instead of just permissions, I also wanted other tests based on the lidded in user (ie, is_staff, or user.units.count() > 1). Duplicating these in the view and the template seems prone to errors.
You can introspect a view object, and see all of the decorators wrapping it, and work out if they are checks (in my case: the first argument I'd u or user). If they all pass, then allow rendering the link.
Get all decorators wrapping a function describes the technique in a little more detail. You can find the app that wraps this up into a handy replacement for {% url %} at Django-menus.
{% include 'django.contrib.auth.views.login' %}
I don't want to write everything by hand.. I hate this really, django full of automatic stuff.
Goal is to include registration/login.html into base.html, so that I could have this form in every page
If I include only template itself (registration/login.html), problem appears that "form.login", I mean "form" var is not defined because this one comes from VIEW which called when you going to login url. So how can I call that view MANUALLY with include or at least to grab django.contrib.auth.views.login variables by my self in my own view and pass then to base.html?
P.s. It's not just about login form, I think there will be more situations like this
I have found better solution in #django irc.
They called inclusion tags
I'll give you my code, because I got lot's of problem learning new stuff in django =)
file: templatetags/form_login.py
from django import template
register = template.Library()
from django.contrib.auth.forms import AuthenticationForm
#register.inclusion_tag('registration/login.html')
def form_login():
return { 'form': AuthenticationForm() }
Now you can have your form anywhere, this will prerender template and THAT'S IT! no stupid context processors which requires to modify whole project settings.py, which is really sux if you writing stand alone little application..
If you need login-form on every page
Create a context processor:
def login_form_processor(request):
return {
'login_form': LoginForm(request.POST or None)
}
Add it to settings.CONTEXT_PROCESSORS.
Include the template for login form:
{% with login_form as form %}
{% include "registration/login.html" %}
{% endwith %}
You can also make you form lazy-loading, so form will not be created until it is used for the first time.
from django.utils improt functional
def login_form_processor(request):
create_login_form = lambda: LoginForm(request.POST or None)
return {
'login_form': functional.lazy(create_login_form, LoginForm)
}
But I guess you won't want the lazy-loading feature, because login-form is cheap to initialize.
Reusing views
Concerning the "grabbing variables" part from your question: you cannot grab variable from view. Django view is method which returns response object. You can not get variables from response. However some of views accept extra_context and other attributes. Those attributes allow you to configure those views in urls, or to wrap them with your own view, for example:
def my_login_view(request):
some_extra_data = get_some_data()
extra_context = {
'some_extra_var': some_extra_data
}
return login_view(request, extra_context=extra_context, template="my_template.html")
This is not exactly grabbing the variables from views, more like augmentation of existing views.
If you expect to have more situations like this, do less data-porcessing in views. Call some methods which checks for permissions. Collect some data from context-processors. Return rendered response. Now you can reuse the data in other views.
You can specify the action on the form html to point to the URL that accesses the corresponding view.
If you want a form, say called as login_form always populated in all templates, then put it in the context_processors.
Browsing the code for django.contrib.auth.views, you will see that the variables form, site and *site_name* are passed to the template.
Either you (1) provide your custom registration form or (2) you can just import django.contrib.auth.forms.AuthenticationForm in your view if you want to use it.
I want to highlight the current page in the navigation menu. Obviously I need to give the menu links a class like 'active' when you are on their page. This is a classic problem and I've seen many solutions proposed. My problem is I hate all of them and consider none of them to be very DRY. For example:
#register.simple_tag
def active(request, pattern):
import re
if re.search(pattern, request.path):
return 'active'
return ''
----
{% load tags %}
<div id="navigation">
<a class="{% active request "^/about/" %}" href="/about/">About</a>
<a class="{% active request "^/contact/" %}" href="/contact/">Contact</a>
<a class="{% active request "^/services/" %}" href="/services/">Services</a>
</div>
The tag takes your current request and a url expression and returns 'active' if you're currently on this page. Alternatively this can be done with named views rather than urls but the principle is the same.
My main issue with this is that my navigation will be called on 99% of my views and yet, in order to get the current request variable I still have parse a RequestContext to the template with something like this:
def contact(request):
# snip ...
return render_to_response(
'contact.html',
{ 'myvar' : myvar },
context_instance=RequestContext(request))
Why do I need to add this context_instance line to every single one of my views when probably all but one of them needs the request variable in order to get the current url/view to highlight the active link? This seems awfully wet, especially for a feature that must be in the great majority of django sites. I want the request to be included by default and be able to optionally suppress it. I can't find a way to do this in middleware as I can't intercept the template before its rendered after the view has returned it.
Any suggestions?
Your intention makes sense, you'll need RequestContext most of the time and only rarely it can be safely omitted for performance reasons. The solution is simple, instead of render_to_response use direct_to_template shortcut:
from django.views.generic.simple import direct_to_template
def contact(request):
# snip ...
return direct_to_template(request, 'contact.html', { 'myvar' : myvar })
... or render_to decorator from django-annoying:
from annoying.decorators import render_to
#render_to('template.html')
def foo(request):
bar = Bar.object.all()
return {'bar': bar}
You don't necessarily have to do anything to the markup of your navigation to give the current one a different style - there are declarative ways to do that using CSS.
See my answer here: Django: Is there a better way to bold the current page link for an example.
For future reference, one can use django-tabs for doing what OP wanted.