From 1457a3a4089a6815ae913c22d8c13985b612c030 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=AD=C3=B0ir=20Valberg=20Gu=C3=B0mundsson?= Date: Wed, 21 Jun 2023 13:27:50 +0200 Subject: [PATCH] WIP. --- content/django-view-decorator.md | 57 ++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/content/django-view-decorator.md b/content/django-view-decorator.md index f7f3dd7..e598e71 100644 --- a/content/django-view-decorator.md +++ b/content/django-view-decorator.md @@ -53,8 +53,11 @@ Can we apply the same pattern to Django? Yes we can! # 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`: @@ -137,11 +140,17 @@ If we want different names for each path we can simply apply the decorator multi ## 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 @@ -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! +### 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 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 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? 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: