4

I need to have a dynamic URL prefix for all URLs in my app.

I'm familiar with doing a static prefix, such as url(r'^myprefix/app', include('app.urls')).

Instead, I need the myprefixto be dynamic, such as url(r'^(?P<prefix>\w+)/app', include('app.urls')).

That works, but here's the kicker. I don't want that prefix to be sent as a keyword argument to all of the views. I want to be able to capture it and use it in Middleware or something similar.


To give my specific use case, we have software (this Django project) used to manage various testing labs. The app needs knowledge of which lab it is operating on.

Currently I'm doing this with the following:

class LabMiddleware(object):
    def process_request(self, request):
        request.current_lab = 'lab1'  # Note that this is currently hard coded

The requirement states that the users be able to go to a URL such as http://host/<lab_name>/app where the lab_name would then get used in my LabMiddleware. Because of this, I obviously don't want to have to accept the lab_name in every single one of my views as it's cumbersome and overkill.


UPDATE:

Building on what Sohan gave in his answer, I ended up using a custom middleware class:

urls.py

url(r'^(?P<lab_name>\w+)/', include('apps.urls')),

apps/urls.py

url(r'^app1/', include('apps.app1.urls', namespace='app1')),

middleware.py

class LabMiddleware(object):
    def process_view(self, request, view_func, view_args, view_kwargs):
        lab_name = view_kwargs.get('lab_name')
        if lab_name:
            request.current_lab = Lab.objects.get(name=lab_name)
            # Remove the lab name from the kwargs before calling the view
            view_kwargs.pop('lab_name')
            return view_func(request, *view_args, **view_kwargs)

settings.py

MIDDLEWARE_CLASSES = (
    # Default Django middleware classes here...
    'raamp.middleware.LabMiddleware',
)

This allowed me to have the lab name in the URL and to add it to the request. Then by removing it from view_kwargs, it doesn't get passed on to the view function and everything works as I intended it.

Also note that the code I have above isn't the most optimized (e.g. I'm querying the database for every request). I stripped out the code I have for caching this as it's not important to showing how this problem was solved, but is worth mentioning that some improvements should be made to this code if you are using it in a production system.

3
  • IMO accepting another argument into all of your view functions is not overkill and only minimally cumbersome. I think your app will be easier to maintain and understand if the view arguments directly match the structure in routes.py Commented Dec 4, 2014 at 21:29
  • The problem is that for every single view function, a lab_name parameter would need to be included, then a function would need to be called in every single one to do something with that information. For a large application (which this currently is), this is cumbersome and error prone, especially with multiple developers. In the end, if I can't get it to work any other way, then this will be the implementation I will take but I'm trying to avoid it. Commented Dec 4, 2014 at 21:34
  • Ah I see -- actually a good solution could be to use a decorator instead. Your decorator can process and "swallow" the lab_name arg. Let me write up an example... Commented Dec 4, 2014 at 21:44

1 Answer 1

2

You can create a decorator that wraps each view function. The decorator can take care of any processing work you have on the lab name parameter, and every view won't need to see the lab_name param.

def process_lab_name(request, lab_name):
    request.current_lab = lab_name

def lab(view_func):
    def _decorator(request, lab_name, *args, **kwargs):
        # process the lab_name argument
        process_lab_name(request, lab_name)
        # when calling the view function, exclude the lab_name argument
        response = view_func(request, *args, **kwargs)
        return response
    return wraps(view_func)(_decorator)

@lab
def some_view(request):
    return render(...)

And your route will look like url(r'^(?P<lab_name>\w+)/app'

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for this. It actually lead me in a similar direction, but instead using a middleware class that defines process_view() to effectively do what you've shown here. I then remove lab_name from from kwargs before calling off to the view function. I'll update my post later with the particular solution that I went with, but I'm marking your answer as accepted since it does solve the problem. Thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.