This commit is contained in:
Víðir Valberg Guðmundsson 2023-06-21 13:27:50 +02:00
parent 08e200e1e9
commit 1457a3a408

View file

@ -53,8 +53,11 @@ Can we apply the same pattern to Django? Yes we can!
# Introducing django-view-decorator! # Introducing django-view-decorator!
## Basics `django-view-decorator` is my attempt to implement a decorator which can be used to apply this pattern to Django views.
The project is very much under development, and it is currently in the "research and development" phase. But it does work!
## Basics
First we setup our project URLconf to include URLs from `django-view-decorator`: First we setup our project URLconf to include URLs from `django-view-decorator`:
@ -137,11 +140,17 @@ If we want different names for each path we can simply apply the decorator multi
## Behind the scenes ## Behind the scenes
So how does this work? Pretty much the same way as the `django.contrib.admin` module. `django-view-decorator` works by having a registry in which all views and their URL information is stored. In fact the mechanism to do most of this work is the same as is used by `django.contrib.admin`. The `@view` decorator is quite similar to the well-known `@admin.register` decorator.
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. Here is a step-by-step for what is going on:
So the `@view` decorator is quite similar to the well-known `@admin.register` decorator. 1. When Django starts and the app registry is ready, the `ready` method of `django_view_decorator.apps.ViewDecoratorAppConf` gets run.
2. The `ready` method calls `autodiscover_modules` from `django.utils.module_loading`. This imports `views.py` files from all apps in `INSTALLED_APPS`. The admin does the same thing, it just imports `admin.py` files.
3. By importing a `views.py` file we run all `@view()` invocations
4. In `view` decorator we gather information provided as arguments to the decorator and store this in a registry which is located at `django_view_decorator.apps.ViewRegistry`.
5. We can now use `ViewRegistry.urlpatterns()` to get the `urlpatterns` for all registrered views.
There are of course some small "buts and ifs" sprinkled around, but by and large this is how the whole thing works.
## Namespaces and the power of factories ## Namespaces and the power of factories
@ -191,6 +200,36 @@ This opens up a quite nifty possibility of injecting URLs into a namespace from
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! 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!
### Namespaces for AppConfigs
In the process of writing this blog post and trying to figure out how all this could be implemented into Django, I came up with the idea to leverage the applications framework in Django to get a namespaced decorator for a given Django app.
:::python
# foos/apps.py
from django_view_decorator import AppConfig
class FoosAppConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "foos"
namespace = "foos" # defaults to name from above
base_path = "foos"
view = FoosAppConfig.get_view_decorator()
# foos/views.py
from .apps import view
@view(paths="foo/", name="foo")
def foo(request: HttpRequest) -> HttpResponse:
return HttpResponse("foo")
I'm quite keen on this idea and I feel this might be an entry into introducing this pattern into Django.
Newcomers would learn that to hook up views to URLs they register their views into the app which the view belongs to - very much like how Flask does it with `@app`.
## The path to Django core ## The path to Django core
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. 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.
@ -206,19 +245,11 @@ So this is what I'm doing.
3. Gather community interest - ongoing 3. Gather community interest - ongoing
4. Merge! 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? ## What do you think?
So now I'm throwing the ball to the Django community! So now I'm throwing the ball to the Django community!
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? What do you think? Should we just keep views and URLs separate or am I on to something? Does my solution have any major downsides which I have been blind to? Is there any missed opportunities in my implementation that would make it sing even more?
Come discuss on the Django forum in this dedicated thread: Come discuss on the Django forum in this dedicated thread: