Package: django-file-router
Django File RouterFile and folder-based routes for Django views.Installationpip install django-file-routerThe problemImagine you are creating a Django project with some CRUD views for your objects. How many files do you need to allow users to create a new object?Create a form class informs.pyCreate a view function that imports the form inviews.pyCreate an HTML template that's referenced by the view somewhere in atemplatesdirectoryEditurls.pyto add your new view functionThat's a total of four files to accomplish something that to end users appears as a single action (add an object). On top of that you need to come up with a name for the form, the view, the template, and the url pattern even if they end up being some variation ofadd_:object class:.The solutionInspired by the popular JS frameworks like Next.js and Remix and the old-school convenience of PHP,django-file-routerallows developers to store all form, template, and view code in a single file; while also inferring the appropriate URL patterns from the directory structure of these views.In practice it looks like this:"""<form method="POST">{% csrf_token %}{{ form.as_p }}<input type="submit"></form>"""fromdjangoimportformsfromdjango.shortcutsimportredirectfromfile_routerimportrender_strfrommyapp.modelsimportMyModelclassAddMyModelForm(forms.ModelForm):classMeta:model=MyModelfields=("name","description")defview(request):form=AddMyModelForm(request.POSTorNone)ifrequest.method=="POST"andform.is_valid():obj=form.save()returnredirect(obj.get_absolute_url())returnrender_str(__doc__,request,{"form":form})There's very little magic in this file. The template is stored at the top as a regular Python docstring and is later passed to the special functionrender_strat the bottom of the file. This function is identical to Django'srendershortcut with the difference that the template code is passed directly as a string instead of a path. The only other hard requirement is that the file must expose a callable namedviewthat accepts the request and returns a response.You would store this code in a file likemyapp/views/mymodel/add.pyand add this to yoururls.py:fromfile_routerimportfile_patternsurlpatterns=[path("admin/",admin.site.urls),*file_patterns("myapp/views"),]With that single call tofile_patternsthe function will generate URL patterns for all your views automatically based on their folder structure insidemyapp/views. For the file we created earlier atmyapp/views/mymodel/add.pythis will result in a url of/mymodel/add. Then by simply creating more files and folders the URL patterns will be updated without any manual input.Here's an example folder structure for a complete CRUD workflow forMyModel:myapp
└── views
    └── mymodel
        ├── <id>
        │   ├── delete.py
        │   ├── edit.py
        │   └── __init__.py
        ├── add.py
        └── __init__.py

3 directories, 5 filesThis would generate the following URL patterns:/mymodel: list of all instances/mymodel/add: add a new instance/mymodel/<id>: view instance/mymodel/<id>/edit: edit instance/mymodel/<id>/delete: delete instanceEach file now holds all the pieces required to perform a given action and requires much less context switching.Notice that special placeholders like<id>are parsed as expected by Django'spathfunction, which means you can use path converters by including them in file and folder names such as<int:id>. For example, to get a single instance enforcing an integeridcreate a filemyapp/views/mymodel/<int:id>/__init__.pywith the code:"""<h1>{{ obj.name }}</h1>"""fromdjango.shortcutsimportget_object_or_404fromfile_routerimportrender_strfrommyapp.modelsimportMyModeldefview(request,id):obj=get_object_or_404(MyModel,id=id)returnrender_str(__doc__,request,{"obj":obj})More examples are available in thedemo folder.ConfigurationThefile_patternsfunction accepts the following arguments:ArgDescriptionappend_slashBoolean. IfTruewill add a trailing slash to the generated patterns.excludeString (glob). If set each file will be checked against this pattern and excluded from the pattern generation process altogether. Useful if you want to completely avoid importing certain files (like tests).FAQWhat about separation of concerns?I think that depends on how you define concerns. If you want to keep view, form, and template code separate then this approach goes against that, but in return you keep all code related to a particular user-facing functionality together, which is easier to reason about as this is how features are developed and maintained.What about syntax highlighting for the template code?Yes, currently template code is just a plain string, but I'm sure there's a way to create a custom language mode in IDEs that will highlight it as expected. I've seen it work in single file Vue components and GraphQL code in JS, so I know it's possible.What about named URL patterns?Every url pattern will also have an auto-generated url name. For example:URLURL name/mymodelmymodel/mymodel/addmymodel_add/mymodel/<id>mymodel_id/mymodel/<id>/editmymodel_id_edit/mymodel/<id>/deletemymodel_id_deleteAlternatively you can addurlandurlnameproperties to yourview:defview(request):...view.url="custom/url/path"view.urlname="my_custom_name"Are you serious?Yes, kinda? This seems like a net positive gain in productivity and also reduces cognitive load, plus it's a pattern that's been tried and tested for years. It's also very light and doesn't require too much magic. So I plan to use it where I can and see if others show interest.