Is there a standard way of replacing urlpatterns with view decorators in django?
Asked Answered
A

3

8

I've developed my own, but it seems like it's a great enough thing that someone else probably thought of it first and did a better job ;)

The goal is to be able to write, in your myapp/views.py

router = Router(prefix=r'^myapp/')

@router.route(url=r'my/url/here', name="my-great-view")
def myview(request):
    return render_to_response("mytemplate.html", {})

And then in urls.py

urlpatterns += myapp.views.router.get_patterns()

And there are several other cool decorator things I've built in (@jsonview for taking a returned dictionary and making a json response, @use_template for taking a returned dictionary and passing it to the defined template as the context...)

It seems like this way makes everything a lot more localized an readable. When looking at a view function you don't have to search around to find what url it belongs to, or what it's "named".

I saw this one djangosnippet, but it's a lot more magic than I'd like, and it doesn't look like it's been generally adopted.

In the event that no one has put together a standard solution, should I clean mine up and submit a pull request to contrib?

  • here is what I currently have implemented: magic.py

Edit: if you want multiple urls for the same view:

@router.route(url="my-first-url", kwargs={'foo':'bar'})
@router.route(url="my-second=url", kwargs={'foo':'baz'})
def my_view(...): ...

And of course this doesn't have to be the only way to do it -- the normal urlpatterns way has some nice things about it two, but these two methods are not mutually exclusive.

Ambitendency answered 15/1, 2013 at 17:37 Comment(3)
I think the reason behind decoupling views and urls is that you could reuse views for different urls, your approach does not seem to do this.Johnsiejohnson
I second that. I often point a route such as create or update to the same view, passing in an optional parameter to identify the model instance.Kacey
Would just decorating it twice not achieve the same effect? (the decorator returns the original function unmodified - it only registers it with the Router object)Ambitendency
S
3

The regular configuration we have a main website urls.py . And the urls.py contains variable named urlpatterns.

so wo can push some url_pattern into it.

app/views.py

from django.urls import path as djpath

URLS = []
URLS_d = {}


def route(path=''):
    def wrapper(func):
        path_name = path or func.__name__
        URLS.append(
            djpath(path_name, func)
        )

        ### below is not important ####
        path_func = URLS_d.get(path_name, None)
        if path_func:
            print(path_func, '<>', func)
            raise Exception('THE same path')
        URLS_d[path_name] = func
        ### above is not important ####

    return wrapper

@route()
def index(req):
    return HttpResponse('hello')

website/urls.py

from app.views import URLS

urlpatterns.extend(URLS)
Soundless answered 8/10, 2019 at 8:54 Comment(2)
Please provide some explanation with your answer too.Concave
Sorry, i'm poor english. And this won't work if there is another decorator for the function. Someday i'll make a better way.Soundless
P
1

If you use class base view , you can use django-request-mapping,

A Simple Example
  • view.py
from django_request_mapping import request_mapping


@request_mapping("/user")
class UserView(View):

    @request_mapping("/login/", method="post")
    def login(self, request, *args, **kwargs):
        return HttpResponse("ok")

    @request_mapping("/signup/", method="post")
    def register(self, request, *args, **kwargs):
        return HttpResponse("ok")

    @request_mapping("/<int:user_id>/role/")
    def get_role(self, request, user_id):
       return HttpResponse("ok") 

    @request_mapping("/<int:pk/", method='delete')
    def delete(self, request, pk):
        User.objects.filter(pk=pk).delete()
        return HttpResponse("ok")


@request_mapping("/role")
class RoleView(View):
    # ...

  • urls.py
from django_request_mapping import UrlPattern
urlpatterns = UrlPattern()
urlpatterns.register(UserView)
urlpatterns.register(RoleView)

and request urls are:

post:  http://localhost:8000/user/login/
post:  http://localhost:8000/user/signup/
get:  http://localhost:8000/user/1/role/
delete: http://localhost:8000/user/1/
# ...
Priggish answered 29/3, 2020 at 4:17 Comment(0)
W
0

First, put this file "router.py" in your django project folder (presumably "website"):

# website/router.py
from django.urls import path as djpath

class Router:
    def __init__(self):
        #map from path to view
        self.map = {}

    def __call__(self, path, name=None):
        def route_collector(view):
            assert path not in self.map
            self.map[path] = view, name
            return view # no change
        return route_collector
    
    @property
    def urls(self):
        for path, (view, name) in self.map.items():
            if type(view) is type: #class based view
                view = view.as_view()
            yield djpath(path, view, name=name)

It uses the Router class for a decorator. To use, in your app/views.py:

### in app/views.py
from website.router import Router
route = Router()

# function based view
@route('', 'index')
def my_view(request): 
   return HttpResponse("You are home")

# class based view
@route('do/', 'action')
class ActionView(View): 
   def get(self, request, *args, **kwargs):
      return HttpResponse("To do list")

Finally, in your website/urls.py, add this:

### in website/urls.py
from app.views import route
urlpatterns.extend(route.urls)
Wanids answered 31/7 at 12:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.