{
    "archive_path": "archive/1731999737.051356",
    "base_url": "david.guillot.me/en/posts/tech/proposal-for-a-django-project-template",
    "basename": "",
    "bookmarked_date": "2024-11-19 07:02",
    "canonical": {
        "archive_org_path": "https://web.archive.org/web/david.guillot.me/en/posts/tech/proposal-for-a-django-project-template",
        "dom_path": "output.html",
        "favicon_path": "favicon.ico",
        "git_path": "git/",
        "google_favicon_path": "https://www.google.com/s2/favicons?domain=david.guillot.me",
        "headers_path": "headers.json",
        "htmltotext_path": "htmltotext.txt",
        "index_path": "index.html",
        "media_path": "media/",
        "mercury_path": "mercury/content.html",
        "pdf_path": "output.pdf",
        "readability_path": "readability/content.html",
        "screenshot_path": "screenshot.png",
        "singlefile_path": "singlefile.html",
        "warc_path": "warc/",
        "wget_path": null
    },
    "domain": "david.guillot.me",
    "downloaded_at": "2024-11-19T07:02:18.251493+00:00",
    "downloaded_datestr": "2024-11-19 07:02",
    "extension": "",
    "hash": "7BNKZWCBV2B2RSSMG6MZ",
    "history": {
        "archive_org": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--head",
                    "--max-time",
                    "60",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://web.archive.org/save/https://david.guillot.me/en/posts/tech/proposal-for-a-django-project-template/"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2024-11-19T07:03:21.121469+00:00",
                "index_texts": null,
                "output": "https://web.archive.org/web/20241119070309/https://david.guillot.me/en/posts/tech/proposal-for-a-django-project-template/",
                "pwd": "/data/archive/1731999737.051356",
                "schema": "ArchiveResult",
                "start_ts": "2024-11-19T07:02:55.607022+00:00",
                "status": "succeeded"
            }
        ],
        "dom": [
            {
                "cmd": [
                    "/usr/bin/chromium-browser",
                    "--virtual-time-budget=15000",
                    "--disable-features=DarkMode",
                    "--run-all-compositor-stages-before-draw",
                    "--hide-scrollbars",
                    "--autoplay-policy=no-user-gesture-required",
                    "--no-first-run",
                    "--use-fake-ui-for-media-stream",
                    "--use-fake-device-for-media-stream",
                    "--simulate-outdated-no-au='Tue, 31 Dec 2099 23:59:59 GMT'",
                    "--headless=new",
                    "--no-sandbox",
                    "--no-zygote",
                    "--disable-dev-shm-usage",
                    "--disable-software-rasterizer",
                    "--disable-sync",
                    "--window-size=1440,2000",
                    "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "--user-data-dir=/data/personas/Default/chrome_profile",
                    "--profile-directory=Default",
                    "--dump-dom",
                    "https://david.guillot.me/en/posts/tech/proposal-for-a-django-project-template/"
                ],
                "cmd_version": "130.0.6723",
                "end_ts": "2024-11-19T07:02:29.997917+00:00",
                "index_texts": null,
                "output": "output.html",
                "pwd": "/data/archive/1731999737.051356",
                "schema": "ArchiveResult",
                "start_ts": "2024-11-19T07:02:24.833607+00:00",
                "status": "succeeded"
            }
        ],
        "favicon": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--max-time",
                    "60",
                    "--output",
                    "favicon.ico",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://www.google.com/s2/favicons?domain=david.guillot.me"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2024-11-19T07:02:19.129997+00:00",
                "index_texts": null,
                "output": "favicon.ico",
                "pwd": "/data/archive/1731999737.051356",
                "schema": "ArchiveResult",
                "start_ts": "2024-11-19T07:02:18.716271+00:00",
                "status": "succeeded"
            }
        ],
        "git": [],
        "headers": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--head",
                    "--max-time",
                    "60",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://david.guillot.me/en/posts/tech/proposal-for-a-django-project-template/"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2024-11-19T07:02:19.749052+00:00",
                "index_texts": null,
                "output": "headers.json",
                "pwd": "/data/archive/1731999737.051356",
                "schema": "ArchiveResult",
                "start_ts": "2024-11-19T07:02:19.162590+00:00",
                "status": "succeeded"
            }
        ],
        "htmltotext": [
            {
                "cmd": [
                    "(internal) archivebox.extractors.htmltotext",
                    "./{singlefile,dom}.html"
                ],
                "cmd_version": "0.8.5rc51",
                "end_ts": "2024-11-19T07:02:48.094580+00:00",
                "index_texts": [
                    "Proposal for a Django project template | David Guillot (https://david.guillot.me/en/posts/tech/proposal-for-a-django-project-template/) (/assets/css/stylesheet.9939535537f3e4e0a491d37f063b6942ce37c7eac8187b16591ff88e211a5794.css) (https://david.guillot.me/favicon.ico) (https://david.guillot.me/favicon-16x16.png) (https://david.guillot.me/favicon-32x32.png) (https://david.guillot.me/apple-touch-icon.png) (https://david.guillot.me/safari-pinned-tab.svg) (https://david.guillot.me/fr/posts/tech/proposition-de-modele-de-projet-django/) (https://david.guillot.me/en/posts/tech/proposal-for-a-django-project-template/)   (https://david.guillot.me/en/) (David Guillot (Alt + H)) David Guillot ((Alt + T))              | (https://david.guillot.me/fr/) (Fran\u00e7ais) Fr     (https://david.guillot.me/en/posts/tech/) (Technology) Technology   (https://david.guillot.me/en/posts/thoughts/) (Thoughts) Thoughts      Proposal for a Django project template  (2024-10-09 00:00:00 +0000 UTC) October 9, 2024 \u00b7\u00a013 min\u00a0\u00b7\u00a0David Guillot\u00a0|\u00a0Translations: (https://david.guillot.me/fr/posts/tech/proposition-de-modele-de-projet-django/) Fr     ((Alt + C)) Table of Contents  \ud83e\uddd0 Why?  \ud83d\udea4 TL;DR  \u2692\ufe0f A quick word on tooling \ud83d\udc0d Python dependency management: uv, what else?  \u2699\ufe0f Task runner: just use just    \ud83c\udfd7\ufe0f Project structure  \ud83e\uddf1 Configuration \ud83c\udfb7 Django settings  \ud83d\uddc3\ufe0f Environment variables and secrets \ud83d\udd10 Secrets encryption  \ud83e\udd1d Collaboration  \ud83d\ude80 Deployment and decryption  \ud83d\udcbb Working on local devel environment      \ud83d\udc85 \u201cFront-end\u201d Web UI  \u2753 So what now? \ud83d\udcdc Legal notice        \ud83e\uddd0 Why?#  Because after 7 years working on the same big Django codebase, when I wanted to start a new project from scratch (a small demo for another blog post that\u2019s yet to come \ud83d\ude05), I had a hard time Because none of the Django project templates I found on the web felt right to me (either too opinionated, or too weak on some aspects) Because tooling has evolved greatly recently  One of the reasons Django is awesome is because it\u2019s unopinionated: it lets you make your own choices. But sometimes it\u2019s intimidating. The idea here is to be opinionated on what\u2019s around Django (Python tooling, structure, environment, UI tooling) but let you make your own implementation choices when it comes to your application. \ud83d\udea4 TL;DR#  This blog post introduces (https://codeberg.org/David-Guillot/django-project-template) a Django project template I\u2019ve been working on in the past few weeks. You can find it here and try it by yourself (and you can also find (https://codeberg.org/David-Guillot/django-project-example) an example project created using the template ), but as it\u2019s a bit unusual, you may want to read this. \u2692\ufe0f A quick word on tooling#  \ud83d\udc0d Python dependency management: uv , what else?#  I\u2019ve been a long-time (https://pip-tools.readthedocs.io/en/stable/) pip-tools + native (https://docs.python.org/3/library/venv.html) venv (+ sometimes (https://github.com/pyenv/pyenv) pyenv ) user. When (https://pipenv.pypa.io/en/latest/) pipenv , then (https://python-poetry.org/) poetry , then (https://pdm-project.org/latest/) pdm came, some people tried to tell me that I should switch. I was never convinced. I was always doubtful about an all-in-one tool pretending to replace my custom-tailored assembly of well established specialized tools, I didn\u2019t see enough value. But then came (https://docs.astral.sh/uv/) uv  . It made me realize that no matter my theoretical doubts, if a tool a just 100x faster, I had to embrace it. So don\u2019t be surprised to find this Django project template using uv . \u2699\ufe0f Task runner: just use just #  I\u2019ve been a long-time Makefile user. But I was frustrated by the .PHONY thing, and by the fact that some behaviors seemed weird to me (because I was hacking a build tool to make a task runner out of it). (https://just.systems/) just  is what I needed from the beginning, and it doesn\u2019t just avoid Makefile \u2019s caveats, it also offers other advantages. So I\u2019ve used it in this template. \ud83c\udfd7\ufe0f Project structure#  The default project structure proposed by the official Django tutorials always felt odd to me. At the end of (https://docs.djangoproject.com/en/5.1/intro/tutorial01/) step 1 , you end up with: .\n\u251c\u2500\u2500 mysite\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 manage.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 mysite\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 asgi.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 settings.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 urls.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 wsgi.py\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 polls\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 admin.py\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 apps.py\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 migrations\n\u2502\u00a0\u00a0     \u2502\u00a0\u00a0 \u2514\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 models.py\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 tests.py\n\u2502\u00a0\u00a0     \u2514\u2500\u2500 views.py\n\u2514\u2500\u2500 pyproject.toml  copy  What I don\u2019t like about it (ordered by -criticity ): Having my Django apps nested under my Django project: not only it doesn\u2019t help me thinking about my apps as potentially reusable (ensuring separation of concerns), but also it creates a situation where the configuration folder (mysite ) will be lost in the middle of apps folders. I would rather have an apps folder where I would put all my apps, wouldn\u2019t you? Having a redundant mysite naming (one for my Git root, one for my Django project): not only it feels weird to type mysite/mysite when I need to edit my site configuration, but also the Django project itself will mostly contain configuration, so I would rather name it conf  Not having manage.py at my Git root Not having wsgi.py /asgi.py at the same level as manage.py , while their job is essentially the same (bootstrap the application and load the configuration, just for different interfaces)  It may seem like minor concerns, but when you work with a codebase on a daily basis, some of them can feel annoying. Here is the proposed structure: .\n\u251c\u2500\u2500 apps\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 polls\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 admin.py\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 apps.py\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 migrations\n\u2502\u00a0\u00a0     \u2502\u00a0\u00a0 \u2514\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 models.py\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 tests.py\n\u2502\u00a0\u00a0     \u2514\u2500\u2500 views.py\n\u251c\u2500\u2500 conf\n\u2502   \u251c\u2500\u2500 settings.py\n\u2502   \u2514\u2500\u2500 urls.py\n\u251c\u2500\u2500 asgi.py\n\u251c\u2500\u2500 manage.py\n\u251c\u2500\u2500 pyproject.toml\n\u2514\u2500\u2500 wsgi.py  copy  With this, anyone can immediately tell that my project is a collection of apps assembled with some configuration, and anyone immediately knows where to find anything. Fortunately, Django is awesome : native commands startproject and startapp take arguments! I just had to write some recipes to get what I wanted. The only thing that I couldn\u2019t get to work is having asgi.py and wsgi.py at the same level as manage.py , but that was my least critical issue. \ud83d\udca1 In case you wonder how it\u2019s done: As said in the message of (https://codeberg.org/David-Guillot/django-project-template/commit/394fd46ec6b9d71393ae4efcef45dcb70b61a244) the commit creating the template structure, the command run was uv run manage.py startproject conf .  The just recipe to start a Django app in this structure can be seen (https://codeberg.org/David-Guillot/django-project-template/src/commit/a5178f7c24217077a5f1236684b599e19e916315/justfile#L46-L50) here    \ud83e\uddf1 Configuration#  This is one of the main pain points I\u2019ve seen in web development in general, and using vanilla Django doesn\u2019t help as much as it should/could, at least when the project grows. We have our conf/settings.py file, it\u2019s fine. But what happens when we add many third-party apps with their own settings, and when we write many home-made apps, as it\u2019s usually done as soon as Django is used beyond a simple blog? (and please, use Django for way more than a simple blog, it\u2019s awesome! \u2728). Here are the key issues: How do we split our settings to keep them readable? Is it gonna be (https://github.com/wemake-services/django-split-settings) django-split-settings ? Do we need a third-party lib for that? How do we separate environments (devel/testing/staging/prod)? Is it gonna be (https://github.com/jazzband/django-configurations) django-configurations ? Do we need a third-party lib for that? How do we even manage environment variables? Is it gonna be .env files? If so, what about secrets? And how are we going to load envvars into Django? Is it gonna be (https://github.com/sloria/environs) environs ? (https://github.com/theskumar/python-dotenv) python-dotenv ? I thought loading envvars into an application was the environments job, not the applications\u2026  But what if I told you you can solve all this without any Python dependency? \ud83c\udfb7 Django settings#  \ud83d\udde8\ufe0f A place for everything and everything in its place  Consider this structure: .\n\u251c\u2500\u2500 apps\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 \u2026\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 \u2026\n\u251c\u2500\u2500 conf\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 \u2026\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 settings\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 apps\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 myapp.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 base.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 default.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 devel.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 testing.py\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 \u2026  copy  Here is how it works: settings/base.py is basically the settings.py that startproject command generated default.py imports everything from base.py , and everything from every Python module found in settings/apps ; it\u2019s called default because everything it contains is imported by __init__.py ; this makes conf.settings a perfectly valid Django settings module, allowing default manage.py /wsgi.py /asgi.py to work out-of-the-box Whenever you use the just startapp myapp recipe, a myapp.py is created within settings/apps  Whenever you install a third-party app, you can put its settings in settings/apps/THE_APP.py in order to find them easily later   devel.py and testing.py import everything from default.py and exist in order to override things for development or testing only ; it\u2019s usually small files with a few overrides  With this simple Python package/module/import structure, leveraging nothing else than Pythons awesomeness, I\u2019ve found myself able to easily manage settings of dozens of Django apps (both third-party and home-made) and multiple environments (devel/testing/staging/prod) without any friction: Each settings file stays thin, even base.py  Each apps settings file can insert stuff into MIDDLEWARE by simply from ..base import MIDDLEWARE  Environment-specific overrides can be found immediately  \ud83e\udd14 You might think that this settings structure is too much for many Django projects. And you would be right! This Django project template doesn\u2019t target small Django applications: it proposes solutions that have proven to be effective when working on a Django project that went big (~15 third-party apps + ~15 home-made apps).  \ud83d\uddc3\ufe0f Environment variables and secrets#  Environment variables (envvars) are the right way to load environment-specific settings into an application, we know that for some time now. The .env non-standard has become a de-facto standard, but it comes with its own challenges: How to load envvars from the .env file into our application\u2019s environment? What about version-control? Do we distribute a generic .env.dist file that each developer has to copy? What about the production .env file? As the .env files are physical files, how do we manage secrets?  In my experience this has been a giant pain ; not because it\u2019s impossible to solve, but because the solutions I\u2019ve seen working always felt horribly over-complicated to my taste. And everybody seems to be fine with it. To be clear: I don\u2019t want two separate mechanisms to manage my envvars, according to their need of secrecy I don\u2019t want to ask myself \u201cIs this a secret?\u201d every time I add an envvar And hell I don\u2019t want to be forced to depend on a cloud providers \u201cvault\u201d feature, even if the cost is near-zero  \ud83d\udde8\ufe0f (slamming the table) There must be a better way!  And there is! Behold\u2026 (https://getsops.io/) SOPS  . Erm, yeah I know, I know, it\u2019s not exactly new. But I haven\u2019t met a Django project that makes use of it yet, so I gave it a try. In this Django project template, every environment gets a version-controlled .env file containing all its envvars, including the secrets ! See how easy it is to edit an encrypted .env file using (https://plugins.jetbrains.com/plugin/21317-simple-sops-edit) this small PyCharm plugin : (sops.gif) (Screencast of editing a .env file in PyCharm with a plugin called \u201cSimple SOPS Edit\u201d. When opening the file, the .env keys appear unencrypted, but their values are encrypted. The plugin shows a banner saying \u201cSOPS file detected\u201d and offers to either view it unencrypted or edit it. Once the file is edited, closing it saves the encrypted version, which can be viewed unencrypted on-demand.) (Click to enlarge ; it's a link so you'll have to hit the Back button)   I also chose to put all .env files in a dedicated directory named\u2026 envs , with a dedicated subdirectory for all devel environments of your team. After project initialization (just init david ), you get: .\n\u251c\u2500\u2500 apps\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 \u2026\n\u251c\u2500\u2500 conf\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 \u2026\n\u251c\u2500\u2500 envs\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 devel\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 david.enc.env\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 production.enc.env\n\u2514\u2500\u2500 \u2026  copy  Files with extension .enc.env can safely be version-controlled, as they are encrypted by SOPS. \ud83d\udca1 You can see what SOPS-encrypted .env files look like in the example project based on the template (https://codeberg.org/David-Guillot/django-project-example/commit/b990f422958cc0695ca9acbf66422212f36c3937) here   \ud83d\udd10 Secrets encryption#  In order to be fully independent, I chose (https://github.com/FiloSottile/age) age ((http://ipa-reader.xyz/?text=a%C9%A1e%CC%9E&voice=Joanna) pronounced [a\u0261e\u031e]  ) , the encryption tool that comes with SOPS . Future versions of this project template may include the ability of choosing your encryption method. \ud83e\udd1d Collaboration#  As SOPS keeps .env files keys human-readable (only values are encrypted), you can see when one of your teammates adds a new envvar somewhere (but you can\u2019t see the value if it\u2019s in their own .env file). I chose to generate two distinct encryption key pairs: One for you, i.e. for your own .env file One for the team, i.e. for shared .env files, such as production  Both public keys live in envvars defined in your .env file. The secret keys live in a file within your $HOME , so you need to find a way to share the shared secret keys with your teammates. And probably you should make backups \ud83d\ude2c. \ud83d\ude80 Deployment and decryption#  SOPS comes with a handy exec-env command (see (https://getsops.io/docs/#passing-secrets-to-other-processes) the docs ) that decrypts an encrypted .env file and exports every envvar found in it before starting as a child process the command you want it to execute. Perfect for working with Gunicorn ! Obviously the tricky part is to provision the production secret key to your production infrastructure. Methods will vary greatly, but you have an example with Flux (https://major.io/p/encrypted-gitops-secrets-with-flux-and-age/#decryption-in-flux) there . \ud83d\udcbb Working on local devel environment#  Obviously on your local environment you will keep an unencrypted version of your .env file (which is Git-ignored in the template), and it will be automatically loaded if you start Django using the following command: just runserver     copy  Yes! just loads .env files, and I wrote the recipe to leverage uv in order to start the right Python, with the right virtualenv. Everything works out-of-the-box! You can find a list of handful commands in (https://codeberg.org/David-Guillot/django-project-template#useful-recipes) the template\u2019s README . \ud83d\udca1 Introduction of SOPS and secrets management to the project template can be seen (https://codeberg.org/David-Guillot/django-project-template/commit/b21cc3ffad183c5286e784035b948da6b8e6d781) here .  \ud83d\udc85 \u201cFront-end\u201d Web UI#  Now that is an area of web development where I feel the Django ecosystem could do better. We are stuck in a place where: Django is awesome! Its templating system is both powerful and easy to use The built-in staticfiles app makes a wonderful job at collecting JS/CSS/image files from every installed app and put them into a unified storage ready to be served. It can even avoid the cache hell with the cache-busting feature of the ManifestStaticFilesStorage !   But for modern web UIs, static files are not enough: The CSS that CSS specialists want to write is not the one they want to send to the browsers: they want it minified, they want to use nested selectors that are not yet supported, maybe even SCSS mixins and third-party libs! The same is true for Javascript, but at a much higher severity: Javascript specialists want to write ES modules and classes, they want to use import for using third-party code, they want linting, maybe even typing! And guess what: they are right ! We simply cannot deliver static jQuery plugins like it\u2019s 2010: web UI elements of a website cannot be considered second-class citizens!    Between 2015 and 2022, many teams have worked around this by reducing Django to an API \u201cback-end\u201d, and writing a Javascript-first \u201cfront-end\u201d with horrific maintenance costs. That was, in many cases, an absurd choice. Fortunately, some people are getting convinced that the web platform should (and can) follow the hypermedia principles, and more and more websites are sending HTML over the wire again. \u2753 So how do we make web UI stuff first-class citizens with Django? (/en/posts/tech/following-up-mother-of-all-htmx-demos/#but-how-does-that-integrate-with-django-and-its-old-fashioned-static-folder) I introduced this in my follow-up of the \u201cMother of all Htmx Demos\u201d , but not in a detailed way. Today I\u2019m going a step further: it\u2019s integrated in my proposed Django project template. Here is what it offers: CSS and Javascript dependency management with standard package.json for use with npm : never import CSS/JS from CDN again, and keep your dependencies up-to-date! Fast build/transpilation/lint/bundling/minification with (https://esbuild.github.io/) esbuild  Write your CSS and JS code as you want in each Django apps static_src directory, it gets compiled to the static directory, so that Django\u2019s staticfiles awesomeness can shine (you can use {% static %} templatetag, compiled files get collected) ManifestStaticFilesStorage enabled by default A Django app named ui where you can centralize your foundation UI stuff like base styles, components, UI kit, etc.  Basically the idea is to get the best of both worlds, in order to make it realistic to build a modern website or webapp with Django, and make web UI specialists want to work with Django! \ud83d\udca1 How it works is visible (https://codeberg.org/David-Guillot/django-project-template/commit/3133834b4bce87685ac08dceb4727d8e24be8f0f) here .  \ud83d\udca1 A typical integration of the famous Bootstrap UI framework can be seen in the example project based on the template, (https://codeberg.org/David-Guillot/django-project-example/commit/6d4f306fe142be08e2219f866c4051da01b0ae9b) here . Yes it\u2019s a bit more verbose than just downloading Bootstrap from a CDN, but it\u2019s the cost of getting things done the right way.  \u2753 So what now?#  This project template is at very early stage. Some things are missing, others are imperfect. But I hope you get the idea, and I hope that the idea makes sense to you. Feedback is more than welcome, because: If it\u2019s considered useless, I\u2019ll stop working on it If it\u2019s considered useful but flawed, I may take some time to improve it If it\u2019s considered useful but just imperfect/incomplete, I will take some time to fix bugs and add features  \ud83d\udcdc Legal notice#  The repository is released under GPLv3 license, which means: You are allowed to use this template for any kind of project, unconditionally If you modify this template or include it as part of a larger project template, you have to release your changes and your derived work under GPLv3       \u00a9 2024 David Guillot, licensed under (https://creativecommons.org/licenses/by-sa/4.0/) CC BY-SA 4.0 \u00b7 Source (https://codeberg.org/David-Guillot/blog) on Codeberg  \u00b7 Powered by (https://gohugo.io/) Hugo & (https://github.com/adityatelange/hugo-PaperMod/) PaperMod   (Go to Top (Alt + G))      "
                ],
                "output": "htmltotext.txt",
                "pwd": "/data/archive/1731999737.051356",
                "schema": "ArchiveResult",
                "start_ts": "2024-11-19T07:02:48.058836+00:00",
                "status": "succeeded"
            }
        ],
        "media": [
            {
                "cmd": [
                    "/usr/local/bin/yt-dlp",
                    "--restrict-filenames",
                    "--trim-filenames",
                    "128",
                    "--write-description",
                    "--write-info-json",
                    "--write-annotations",
                    "--write-thumbnail",
                    "--no-call-home",
                    "--write-sub",
                    "--write-auto-subs",
                    "--convert-subs=srt",
                    "--yes-playlist",
                    "--continue",
                    "--no-abort-on-error",
                    "--ignore-errors",
                    "--geo-bypass",
                    "--add-metadata",
                    "--format=(bv*+ba/b)[filesize<=750m][filesize_approx<=?750m]/(bv*+ba/b)",
                    "--skip-download",
                    "--cache-dir=/data/yt-dlp-cache/",
                    "--cookies=/data/yt-dlp-cache/cookies.txt",
                    "--proxy=socks5://tor-socks-proxy:9150",
                    "--no-playlist",
                    "https://david.guillot.me/en/posts/tech/proposal-for-a-django-project-template/"
                ],
                "cmd_version": "2024.10.7",
                "end_ts": "2024-11-19T07:02:55.554848+00:00",
                "index_texts": [],
                "output": "media/",
                "pwd": "/data/archive/1731999737.051356",
                "schema": "ArchiveResult",
                "start_ts": "2024-11-19T07:02:49.811812+00:00",
                "status": "succeeded"
            }
        ],
        "mercury": [
            {
                "cmd": [
                    "/home/archivebox/.npm/bin/postlight-parser",
                    "https://david.guillot.me/en/posts/tech/proposal-for-a-django-project-template/"
                ],
                "cmd_version": "2.2.3",
                "end_ts": "2024-11-19T07:02:48.020715+00:00",
                "index_texts": null,
                "output": "mercury/",
                "pwd": "/data/archive/1731999737.051356",
                "schema": "ArchiveResult",
                "start_ts": "2024-11-19T07:02:44.406172+00:00",
                "status": "succeeded"
            }
        ],
        "pdf": [],
        "readability": [
            {
                "cmd": [
                    "/home/archivebox/.npm/bin/readability-extractor",
                    "/tmp/tmptatiz9mw",
                    "https://david.guillot.me/en/posts/tech/proposal-for-a-django-project-template/"
                ],
                "cmd_version": "0.0.11",
                "end_ts": "2024-11-19T07:02:34.621855+00:00",
                "index_texts": [
                    "\ud83e\uddd0 Why?\n\nBecause after 7 years working on the same big Django codebase, when I wanted to start a new project from scratch (a small demo for another blog post that\u2019s yet to come \ud83d\ude05), I had a hard time\nBecause none of the Django project templates I found on the web felt right to me (either too opinionated, or too weak on some aspects)\nBecause tooling has evolved greatly recently\n\nOne of the reasons Django is awesome is because it\u2019s unopinionated: it lets you make your own choices. But sometimes it\u2019s intimidating. The idea here is to be opinionated on what\u2019s around Django (Python tooling, structure, environment, UI tooling) but let you make your own implementation choices when it comes to your application.\n\ud83d\udea4 TL;DR\nThis blog post introduces a Django project template\n I\u2019ve been working on in the past few weeks. You can find it here and try it by yourself (and you can also find an example project created using the template\n), but as it\u2019s a bit unusual, you may want to read this.\n\n\ud83d\udc0d Python dependency management: uv, what else?\nI\u2019ve been a long-time pip-tools\n + native venv\n (+ sometimes pyenv\n) user. When pipenv\n, then poetry\n, then pdm\n came, some people tried to tell me that I should switch. I was never convinced. I was always doubtful about an all-in-one tool pretending to replace my custom-tailored assembly of well established specialized tools, I didn\u2019t see enough value.\nBut then came uv\n. It made me realize that no matter my theoretical doubts, if a tool a just 100x faster, I had to embrace it.\nSo don\u2019t be surprised to find this Django project template using uv.\n\u2699\ufe0f Task runner: just use just\nI\u2019ve been a long-time Makefile user. But I was frustrated by the .PHONY thing, and by the fact that some behaviors seemed weird to me (because I was hacking a build tool to make a task runner out of it).\njust\n is what I needed from the beginning, and it doesn\u2019t just avoid Makefile\u2019s caveats, it also offers other advantages. So I\u2019ve used it in this template.\n\ud83c\udfd7\ufe0f Project structure\nThe default project structure proposed by the official Django tutorials always felt odd to me. At the end of step 1\n, you end up with:\n.\n\u251c\u2500\u2500 mysite\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 manage.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 mysite\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 asgi.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 settings.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 urls.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 wsgi.py\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 polls\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 admin.py\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 apps.py\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 migrations\n\u2502\u00a0\u00a0     \u2502\u00a0\u00a0 \u2514\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 models.py\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 tests.py\n\u2502\u00a0\u00a0     \u2514\u2500\u2500 views.py\n\u2514\u2500\u2500 pyproject.toml\nWhat I don\u2019t like about it (ordered by -criticity):\n\nHaving my Django apps nested under my Django project: not only it doesn\u2019t help me thinking about my apps as potentially reusable (ensuring separation of concerns), but also it creates a situation where the configuration folder (mysite) will be lost in the middle of apps folders. I would rather have an apps folder where I would put all my apps, wouldn\u2019t you?\nHaving a redundant mysite naming (one for my Git root, one for my Django project): not only it feels weird to type mysite/mysite when I need to edit my site configuration, but also the Django project itself will mostly contain configuration, so I would rather name it conf\nNot having manage.py at my Git root\nNot having wsgi.py/asgi.py at the same level as manage.py, while their job is essentially the same (bootstrap the application and load the configuration, just for different interfaces)\n\nIt may seem like minor concerns, but when you work with a codebase on a daily basis, some of them can feel annoying.\nHere is the proposed structure:\n.\n\u251c\u2500\u2500 apps\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 polls\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 admin.py\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 apps.py\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 migrations\n\u2502\u00a0\u00a0     \u2502\u00a0\u00a0 \u2514\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 models.py\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 tests.py\n\u2502\u00a0\u00a0     \u2514\u2500\u2500 views.py\n\u251c\u2500\u2500 conf\n\u2502   \u251c\u2500\u2500 settings.py\n\u2502   \u2514\u2500\u2500 urls.py\n\u251c\u2500\u2500 asgi.py\n\u251c\u2500\u2500 manage.py\n\u251c\u2500\u2500 pyproject.toml\n\u2514\u2500\u2500 wsgi.py\nWith this, anyone can immediately tell that my project is a collection of apps assembled with some configuration, and anyone immediately knows where to find anything.\nFortunately, Django is awesome: native commands startproject and startapp take arguments! I just had to write some recipes to get what I wanted. The only thing that I couldn\u2019t get to work is having asgi.py and wsgi.py at the same level as manage.py, but that was my least critical issue.\n\n\ud83d\udca1 In case you wonder how it\u2019s done:\n\nAs said in the message of the commit\n creating the template structure, the command run was uv run manage.py startproject conf .\nThe just recipe to start a Django app in this structure can be seen here\n\n\n\n\ud83e\uddf1 Configuration\nThis is one of the main pain points I\u2019ve seen in web development in general, and using vanilla Django doesn\u2019t help as much as it should/could, at least when the project grows. We have our conf/settings.py file, it\u2019s fine. But what happens when we add many third-party apps with their own settings, and when we write many home-made apps, as it\u2019s usually done as soon as Django is used beyond a simple blog? (and please, use Django for way more than a simple blog, it\u2019s awesome! \u2728). Here are the key issues:\n\nHow do we split our settings to keep them readable? Is it gonna be django-split-settings\n? Do we need a third-party lib for that?\nHow do we separate environments (devel/testing/staging/prod)? Is it gonna be django-configurations\n? Do we need a third-party lib for that?\nHow do we even manage environment variables? Is it gonna be .env files? If so, what about secrets? And how are we going to load envvars into Django? Is it gonna be environs\n? python-dotenv\n? I thought loading envvars into an application was the environments job, not the applications\u2026\n\nBut what if I told you you can solve all this without any Python dependency?\n\ud83c\udfb7 Django settings\n\n\ud83d\udde8\ufe0f A place for everything and everything in its place\n\nConsider this structure:\n.\n\u251c\u2500\u2500 apps\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 \u2026\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 \u2026\n\u251c\u2500\u2500 conf\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 \u2026\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 settings\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 apps\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 myapp.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 base.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 default.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 devel.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 testing.py\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 \u2026\nHere is how it works:\n\nsettings/base.py is basically the settings.py that startproject command generated\ndefault.py imports everything from base.py, and everything from every Python module found in settings/apps ; it\u2019s called default because everything it contains is imported by __init__.py ; this makes conf.settings a perfectly valid Django settings module, allowing default manage.py/wsgi.py/asgi.py to work out-of-the-box\n\nWhenever you use the just startapp myapp recipe, a myapp.py is created within settings/apps\nWhenever you install a third-party app, you can put its settings in settings/apps/THE_APP.py in order to find them easily later\n\n\ndevel.py and testing.py import everything from default.py and exist in order to override things for development or testing only ; it\u2019s usually small files with a few overrides\n\nWith this simple Python package/module/import structure, leveraging nothing else than Pythons awesomeness, I\u2019ve found myself able to easily manage settings of dozens of Django apps (both third-party and home-made) and multiple environments (devel/testing/staging/prod) without any friction:\n\nEach settings file stays thin, even base.py\nEach apps settings file can insert stuff into MIDDLEWARE by simply from ..base import MIDDLEWARE\nEnvironment-specific overrides can be found immediately\n\n\n\ud83e\udd14 You might think that this settings structure is too much for many Django projects. And you would be right! This Django project template doesn\u2019t target small Django applications: it proposes solutions that have proven to be effective when working on a Django project that went big (~15 third-party apps + ~15 home-made apps).\n\n\ud83d\uddc3\ufe0f Environment variables and secrets\nEnvironment variables (envvars) are the right way to load environment-specific settings into an application, we know that for some time now. The .env non-standard has become a de-facto standard, but it comes with its own challenges:\n\nHow to load envvars from the .env file into our application\u2019s environment?\nWhat about version-control? Do we distribute a generic .env.dist file that each developer has to copy? What about the production .env file?\nAs the .env files are physical files, how do we manage secrets?\n\nIn my experience this has been a giant pain ; not because it\u2019s impossible to solve, but because the solutions I\u2019ve seen working always felt horribly over-complicated to my taste. And everybody seems to be fine with it. To be clear:\n\nI don\u2019t want two separate mechanisms to manage my envvars, according to their need of secrecy\nI don\u2019t want to ask myself \u201cIs this a secret?\u201d every time I add an envvar\nAnd hell I don\u2019t want to be forced to depend on a cloud providers \u201cvault\u201d feature, even if the cost is near-zero\n\n\n\ud83d\udde8\ufe0f (slamming the table) There must be a better way!\n\nAnd there is! Behold\u2026 SOPS\n. Erm, yeah I know, I know, it\u2019s not exactly new. But I haven\u2019t met a Django project that makes use of it yet, so I gave it a try.\nIn this Django project template, every environment gets a version-controlled .env file containing all its envvars, including the secrets!\nSee how easy it is to edit an encrypted .env file using this small PyCharm plugin\n:\n\n\nI also chose to put all .env files in a dedicated directory named\u2026 envs, with a dedicated subdirectory for all devel environments of your team. After project initialization (just init david), you get:\n.\n\u251c\u2500\u2500 apps\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 \u2026\n\u251c\u2500\u2500 conf\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 \u2026\n\u251c\u2500\u2500 envs\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 devel\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 david.enc.env\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 production.enc.env\n\u2514\u2500\u2500 \u2026\nFiles with extension .enc.env can safely be version-controlled, as they are encrypted by SOPS.\n\n\ud83d\udca1 You can see what SOPS-encrypted .env files look like in the example project based on the template here\n\n\n\ud83d\udd10 Secrets encryption\nIn order to be fully independent, I chose age\n (pronounced [a\u0261e\u031e]\n), the encryption tool that comes with SOPS. Future versions of this project template may include the ability of choosing your encryption method.\n\ud83e\udd1d Collaboration\nAs SOPS keeps .env files keys human-readable (only values are encrypted), you can see when one of your teammates adds a new envvar somewhere (but you can\u2019t see the value if it\u2019s in their own .env file).\nI chose to generate two distinct encryption key pairs:\n\nOne for you, i.e. for your own .env file\nOne for the team, i.e. for shared .env files, such as production\n\nBoth public keys live in envvars defined in your .env file.\nThe secret keys live in a file within your $HOME, so you need to find a way to share the shared secret keys with your teammates. And probably you should make backups \ud83d\ude2c.\n\ud83d\ude80 Deployment and decryption\nSOPS comes with a handy exec-env command (see the docs\n) that decrypts an encrypted .env file and exports every envvar found in it before starting as a child process the command you want it to execute. Perfect for working with Gunicorn!\nObviously the tricky part is to provision the production secret key to your production infrastructure. Methods will vary greatly, but you have an example with Flux there\n.\n\ud83d\udcbb Working on local devel environment\nObviously on your local environment you will keep an unencrypted version of your .env file (which is Git-ignored in the template), and it will be automatically loaded if you start Django using the following command:\nYes! just loads .env files, and I wrote the recipe to leverage uv in order to start the right Python, with the right virtualenv. Everything works out-of-the-box! You can find a list of handful commands in the template\u2019s README\n.\n\n\ud83d\udca1 Introduction of SOPS and secrets management to the project template can be seen here\n.\n\n\ud83d\udc85 \u201cFront-end\u201d Web UI\nNow that is an area of web development where I feel the Django ecosystem could do better. We are stuck in a place where:\n\nDjango is awesome!\n\nIts templating system is both powerful and easy to use\nThe built-in staticfiles app makes a wonderful job at collecting JS/CSS/image files from every installed app and put them into a unified storage ready to be served. It can even avoid the cache hell with the cache-busting feature of the ManifestStaticFilesStorage!\n\n\nBut for modern web UIs, static files are not enough:\n\nThe CSS that CSS specialists want to write is not the one they want to send to the browsers: they want it minified, they want to use nested selectors that are not yet supported, maybe even SCSS mixins and third-party libs!\nThe same is true for Javascript, but at a much higher severity: Javascript specialists want to write ES modules and classes, they want to use import for using third-party code, they want linting, maybe even typing!\nAnd guess what: they are right! We simply cannot deliver static jQuery plugins like it\u2019s 2010: web UI elements of a website cannot be considered second-class citizens!\n\n\n\nBetween 2015 and 2022, many teams have worked around this by reducing Django to an API \u201cback-end\u201d, and writing a Javascript-first \u201cfront-end\u201d with horrific maintenance costs. That was, in many cases, an absurd choice. Fortunately, some people are getting convinced that the web platform should (and can) follow the hypermedia principles, and more and more websites are sending HTML over the wire again.\n\u2753 So how do we make web UI stuff first-class citizens with Django? I introduced this in my follow-up of the \u201cMother of all Htmx Demos\u201d\n, but not in a detailed way. Today I\u2019m going a step further: it\u2019s integrated in my proposed Django project template. Here is what it offers:\n\nCSS and Javascript dependency management with standard package.json for use with npm: never import CSS/JS from CDN again, and keep your dependencies up-to-date!\nFast build/transpilation/lint/bundling/minification with esbuild\n\nWrite your CSS and JS code as you want in each Django apps static_src directory, it gets compiled to the static directory, so that Django\u2019s staticfiles awesomeness can shine (you can use {% static %} templatetag, compiled files get collected)\nManifestStaticFilesStorage enabled by default\nA Django app named ui where you can centralize your foundation UI stuff like base styles, components, UI kit, etc.\n\nBasically the idea is to get the best of both worlds, in order to make it realistic to build a modern website or webapp with Django, and make web UI specialists want to work with Django!\n\n\ud83d\udca1 How it works is visible here\n.\n\n\n\ud83d\udca1 A typical integration of the famous Bootstrap UI framework can be seen in the example project based on the template, here\n. Yes it\u2019s a bit more verbose than just downloading Bootstrap from a CDN, but it\u2019s the cost of getting things done the right way.\n\n\u2753 So what now?\nThis project template is at very early stage. Some things are missing, others are imperfect. But I hope you get the idea, and I hope that the idea makes sense to you. Feedback is more than welcome, because:\n\nIf it\u2019s considered useless, I\u2019ll stop working on it\nIf it\u2019s considered useful but flawed, I may take some time to improve it\nIf it\u2019s considered useful but just imperfect/incomplete, I will take some time to fix bugs and add features\n\n\ud83d\udcdc Legal notice\nThe repository is released under GPLv3 license, which means:\n\nYou are allowed to use this template for any kind of project, unconditionally\nIf you modify this template or include it as part of a larger project template, you have to release your changes and your derived work under GPLv3"
                ],
                "output": "readability/",
                "pwd": "/data/archive/1731999737.051356",
                "schema": "ArchiveResult",
                "start_ts": "2024-11-19T07:02:31.164378+00:00",
                "status": "succeeded"
            }
        ],
        "screenshot": [],
        "singlefile": [],
        "title": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--max-time",
                    "60",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://david.guillot.me/en/posts/tech/proposal-for-a-django-project-template/"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2024-11-19T07:02:30.088694+00:00",
                "index_texts": null,
                "output": "Proposal for a Django project template | David Guillot",
                "pwd": "/data/archive/1731999737.051356",
                "schema": "ArchiveResult",
                "start_ts": "2024-11-19T07:02:30.050975+00:00",
                "status": "succeeded"
            }
        ],
        "wget": []
    },
    "icons": null,
    "is_archived": true,
    "is_static": false,
    "latest": {
        "archive_org": "https://web.archive.org/web/20241119070309/https://david.guillot.me/en/posts/tech/proposal-for-a-django-project-template/",
        "dom": "output.html",
        "favicon": "favicon.ico",
        "git": null,
        "media": "media/",
        "pdf": null,
        "screenshot": null,
        "singlefile": null,
        "title": "Proposal for a Django project template | David Guillot",
        "warc": null,
        "wget": null
    },
    "link_dir": "/data/archive/1731999737.051356",
    "newest_archive_date": "2024-11-19T07:02:55.607022+00:00",
    "num_failures": 0,
    "num_outputs": 9,
    "oldest_archive_date": "2024-11-19T07:02:18.716271+00:00",
    "path": "/en/posts/tech/proposal-for-a-django-project-template/",
    "schema": "Link",
    "scheme": "https",
    "snapshot_abid": "snp_01JD1KN97K88D82F0F01B2VA9X",
    "snapshot_id": "d9521a1b-2840-4010-a11e-ce5b962da93d",
    "sources": [
        "/data/sources/1731999735-import.txt"
    ],
    "tags": null,
    "tags_str": "",
    "timestamp": "1731999737.051356",
    "title": "Proposal for a Django project template | David Guillot",
    "url": "https://david.guillot.me/en/posts/tech/proposal-for-a-django-project-template/"
}