How I Start: Django, Tailwind, HTMX (part 2)

In part 1, I set up the project, a sensible .gitignore, tailwind, and live reloading along with some run targets for PyCharm. Now it’s time to finish up the housekeeping.

Housekeeping: Move Configuration into the Environment

This doesn’t strictly need to be done at this early stage, but I find that doing so is a protective habit, especially for projects that sit on postgres. Thanks to the .gitignore from part 1, if secrets go into .env files, they can never land in source control by mistake.

Before starting, make sure we can use .env files with django-environ:

poetry add python-dotenv

First add the relevant values to the .env file:

echo DEBUG=True >>.env
echo DJANGO_SECRET_KEY=$(poetry run python -c "import secrets; print(secrets.token_urlsafe())") >>.env

Then add import environ at the top of config/ and change DEBUG and DJANGO_SECRET_KEY to pull from there instead:

import environ
from dotenv import load_dotenv


env = environ.FileAwareEnv(
    # set casting, default value
    DEBUG=(bool, False)


# False if not in os.environ because of casting above
DEBUG = env('DEBUG')

# SECURITY WARNING: keep the secret key used in production secret!
# Raises Django's ImproperlyConfigured
# exception if DJANGO_SECRET_KEY not in os.environ

If I were using postgres for this project, the URL for that would also come from .env. See the django-environ documentation for details.

Housekeeping: Custom User Model

Having once wasted quite a bit of time adding a custom user model after the fact, I now do this before I run migrate for the first time.

Until/unless I’m ready to move my project to allauth, I like the starting point provided by django-authuser. I copy it into my tree since the likelihood that I want to modify it is high and the likelihood that I want to track upstream changes is low.

curl -L -o
mv django-authuser-main authuser

Then I add it to INSTALLED_APPS and set my user model in config/


AUTH_USER_MODEL = "authuser.User"

and configure config/ to route traffic to its views:

urlpatterns = [
    path("accounts/", include("authuser.urls")),
    path("__reload__/", include("django_browser_reload.urls")),
    path("", include("reading_journal.urls")),

With that done, I run migrate for the first time and create a superuser to make sure everything is working:

poetry run python makemigrations
poetry run python migrate
poetry run python createsuperuser

If everything is working, I should be prompted for my superuser’s email and password but not a username.

That was a short Part 2, but this is a good stopping point. Part 3 will build out models and views.

I’m trying on Kev Quirk’s “100 Days To Offload” idea. You can see details and join yourself by visiting

This is day 3.