marue
marue

Reputation: 5726

In which order to sort my middleware path match requirements?

I created a middleware which allows me to use a list of dictionaries to specify some access rules for any of my views. Each of these dictionaries looks like this:

REQUIREMENTS=(
    {'viewname':'addtag',
     'permissions':'can_add_tags'},
    {'regex':re.compile(r'^somestart'),
    'user_check':lambda request:request.user.username=='sam'}
)

In my middleware i then try to find out which of those requirements match the current request. To do so, i filter the complete REQUIREMENTS, and in the filter function i use this code to check if the path matches:

def process_request(self,request):

    def path_matches(self,req):
        path_matches = False

        if  (req.has_key('url') and req['url'] == request.path ) or\
            (req.has_key('regex') and req['regex'].search(request.path)) or\
            (req.has_key('viewname') and resolve(request.path).url_name==req['viewname']):
            path_matches=True

        return path_matches

    requirements = filter(path_matches,REQUIREMENTS)
    # now use the returned requirements to determine if a user
    # matches the requirement and

My question now is: in which order should i use the checks? It's pretty clear that the check for the url is the fastest, so this has to be first. But then the question is if the regex search or django's url resolve function should follow first.

As i do not have any performance issues right now, this is more of an academic question. And if someone would have an all better solution to solve this, that would be even better.


edit:

To react to the given answers: what i'm trying to do is to create a possibility to restrict views of several external apps in a single file. So decorators are not an option, as long as i do not want to do something like this:

from ext_app1 import view1,view2
from ext_app2 import view3

@permission_required('can_do_stuff')
def view1_ext(*args,**kwargs):
    return view1(args,kwargs)

which would lead to rewriting the url specifications each time i change permissions. I want to avoid that. Additionally my solution allows for a user_check function to do a check on a user like this:

def check_user(user):
    if len(Item.objects.get(creator=user,datetime=today)) > 3:
        return False
    return True

That would be a simple way to, ie., restrict how many items a user can upload each day. (Ok, that would be possible with user_passes_test as well).

One more thing is that i sometimes want to check for a permission only if the request is a POST, or if the request contains a certain key:value pair (like 'action':'delete' should require the permission, while 'action':'change' should be allowed for anyone). This could be done with a custom decorator as well, but as soon as i would need a new check, i would need a new decorator.

Upvotes: 0

Views: 120

Answers (2)

caio
caio

Reputation: 1989

You might be looking for user_passes_test decorator.

You can decorate the views you need instead of using a middleware.

The code will looks like this:

from django.contrib.auth.decorators import user_passes_test

@user_passes_test(lambda user: user.has_perm('model.can_add_tags') \
                                and user.username == 'sam')
def my_view(request):
    ...  

Upvotes: 1

georgebrock
georgebrock

Reputation: 30183

If you're using Django's built in user authentication and permissions system (django.contrib.auth) then you should consider using the views decorators it provides instead of a middleware. These give you several advantages:

  • Code that changes together is in the same place, i.e. it's more likely that you'll need to change permissions for a specific view when you are changing the view's code than when you are changing other permissions.
  • You don't have to write much of the code yourself, which makes your project smaller and easier to maintain.

For simple situations you can use the login_required and permission_required decorators, and for a more complex condition the user_passes_test decorator allows you to check if the user passes any condition you care to specify.

The code looks something like this for a view function (example is lifted from the documentation):

from django.contrib.auth.decorators import permission_required

@permission_required('polls.can_vote')
def my_view(request):
    ...

If you're using class-based views then it looks a little different (again, this example is lifted from the documentation):

from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView

class ProtectedView(TemplateView):
    template_name = 'secret.html'

    @method_decorator(login_required)
    def dispatch(self, *args, **kwargs):
        return super(ProtectedView, self).dispatch(*args, **kwargs)

If you have a good reason not to use Django's permissions system, then you can still adopt a similar approach. The code for the django.contrib.auth decorators could easily be used as the basis of your own decorators.

Upvotes: 1

Related Questions