Summary: Introducing django-view-decorator, a Django package which brings Locality of Behaviour to your Views and URLs
---
It seems that "The Location of Behaviour principle" (shortened as LoB) is gaining traction these days. This has given me the urge to try to influence the direction of Django to bring more LoB to the connection between views and URLs.
But first, what is "LoB"? The principle is coined by the author of HTMX in [a short, but great, essay](https://htmx.org/essays/locality-of-behaviour/). The principle states:
Carlton Gibson [mentions this](https://youtu.be/_3oGI4RC52s?t=315) in is talk at DjangoCon Europe 2023, and why this means that he often puts view code in the same file as his URLs.
But why this disconnect? Other frameworks, like Flask and FastAPI use a rather simple "pattern" using a decorator which puts the URL information where the view is defined:
Multiple URLs can point at the same view, and you might have noticed that the argument is the plural `paths`. This is because we can pass a list of paths which point at the same view. Like so:
Looking at the view we can grok that it is exposed on two paths, under the `foos` namespace, one which lists all `Foo` objects and one which given an integer gives us the detail for a single `Foo`. That's pretty powerful if you ask me!
So how does this work? Pretty much the same way as the `django.contrib.admin` module.
The `django_view_decorator.apps.ViewDecoratorAppConf.ready` method runs `django.utils.module_loading.autodiscover_modules` which looks for all `views.py` modules in installed apps and imports these. This means that when `@view` occurrences are loaded in, we can put the views and associated metadata into a registry which we then can ask to write our `urlpatterns` list for us.
So the `@view` decorator is quite similar to the well-known `@admin.register` decorator.
## Namespaces and the power of factories
In our more advanced example you might have noticed the `namespace="foos"`, which probably is going to quite tedious to repeat over and over again. One of the nice things about Django URLconfs is that we get namespacing by using the `include` function.
This is where the aptly named `namespaced_decorator_factory` comes into the picture. Let us look at an example:
:::python
# foo/views.py
from django_view_decorator import namespaced_decorator_factory
By calling `namespaced_decorator_factory` we get a specialised decorator for our namespace and we can even provide it with a path which will be prepended to all URLs registered using it.
This opens up a quite nifty possibility of injecting URLs into a namespace from anywhere. For example:
Now we can treat `custom_view` as if it was a part of the `app_1` namespace. Ie. `reverse("app_1:custom-view")` would give us `app_1/my-custom-view/`. Neat!
So, as I wrote initially, I have a mission to try to get this pattern into Django core. This is not going to be an easy feat.
To cite Carlton Gibson from the previously mentioned talk:
> Put it in a third party package. See if there is community interest. Then maybe it gets merged to the core if there is.
So this is what I'm doing.
1. Write the third party package - check!
2. Write a blog post - check!
3. Gather community interest - ongoing
4. Merge!
### How could this be manifested in Django?
I haven't started the work to write a PR to get this into Django just yet, but here are some of my initial thoughts on how this would work in Django.
First of I would probably locate the decorator at `django.views.view`. But it might event be a opt-in feature and live in `django.contrib.view_decorator` or something similary. Having it as a contrib package would make it "cleaner" to solve the "where do we put the auto discovery?" - it would be in `django.contrib.view_decorator.apps.ViewDecoratorAppConfig.ready`.
One idea I'm going to explore in `django-view-decorator` is to write an `AppConfig` subclass which uses `namespaced_decorator_factory` to generate a decorator for an app. This way there would be a way to get a decorator for a specific app using the "applications" framework in Django.
What do you think? Should we just keep seperating views and URLs into different files or am I on to something? Does my solution have any major downsides which I have been blind to?