DEV Community

rodbv
rodbv

Posted on • Edited on

Creating a To-Do app with Django and HTMX - Part 1: Creating the Django project with uv

In this series of posts we will create a To-Do app using HTMX and Django, following a TDD (Test-driven development) workflow.

The goal of this series of posts is to document my own learning process of using HTMX with Django.

If you just want to have a look on how to integrate HTMX with Django, you can jump to part 3.

The app will start with a textbox on top to add to-do items, and a list of current to-do items as horizontal components. The first version will simply support the creation and completion of the items.

The next two features we want to have is the concept of due dates and a sidebar which allows us to filter by tasks due today, tomorrow and overdue, and the use of natural language to add to-do items, for example writing "Call doctor tomorrow 8:30" should create an item with the title "Call doctor" and due date on the next day at 8:30 AM.

These are the types of features that can benefit immensely from TDD as it allows us to focus on how to process natural language while not worrying too much on how to integrate it with the frontend.

Prototype of the UI of the to-do app, with a textbox on top, a button with text

Creating the project using uv

We will kickoff the project using uv, which is defined on its website as

"An extremely fast Python package and project manager, written in Rust [...] a single tool to replace pip, pip-tools, pipx, poetry, pyenv, twine, virtualenv, and more."

I am particularly interested on having a simple way to manage dependencies using pyproject.toml and a virtual environment.

To install uv on your machine, follow the instructions in its website.

❯ uv init
Initialized project `todo-mx`
❯ uv venv
Using CPython 3.12.8
Creating virtual environment at: .venv
Activate with: source .venv/bin/activate
❯ source .venv/bin/activate
Enter fullscreen mode Exit fullscreen mode

Now that we have a project and a virtual environment we can add our first two dependencies: Django and pytest. We're also adding an optional dependency, pytest-sugar, which provides a nicer interface to our tests.

❯ uv add django
Resolved 5 packages in 248ms
Installed 3 packages in 350ms
 + asgiref==3.8.1
 + django==5.1.4
 + sqlparse==0.5.3
❯ uv add pytest pytest-sugar --dev
Resolved 12 packages in 206ms
Installed 6 packages in 14ms
 + iniconfig==2.0.0
 + packaging==24.2
 + pluggy==1.5.0
 + pytest==8.3.4
 + pytest-sugar==1.0.0
 + termcolor==2.5.0
Enter fullscreen mode Exit fullscreen mode

Creating the django project

To create the Django project in the current directory, we can run the following uv command:

uv run django-admin startproject todomx .
Enter fullscreen mode Exit fullscreen mode

Note: We're preprending the command with uv run to ensure that we will always run the commands in the correct virtual environment for the project; it's a good habit to develop when we're dealing with several projects and environments, but it's not mandatory.

We can ensure that the installation worked by running the runserver command:

❯ uv run python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
January 01, 2025 - 16:12:00
Django version 5.1.4, using settings 'todomx.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Enter fullscreen mode Exit fullscreen mode

Opening the link shows us the Django welcome screen

Home page for Django with the text

Overriding the default user authentication model

Before we run the pending migrations, as suggested when we executed the runserver command, we will override Django's default user model, which is a best-practice for new projects. There's a lot of literature on why this is a good idea, including the official documentation.

Later we may want to incorporate django-allauth in the project, but we want to keep it simple and have something up and running soon, so let's just create an app called core, create a UserProfile class inheriting from AbstractUser, and set our project to use this class as its authentication model.

❯ uv run python manage.py startapp core
Enter fullscreen mode Exit fullscreen mode

Let's add the model in core/models.py:

from django.contrib.auth.models import AbstractUser

class UserProfile(AbstractUser):
    pass
Enter fullscreen mode Exit fullscreen mode

In settings.py, let's add our new app and change the authentication user model:


INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'core', # <-- NEW
]

AUTH_USER_MODEL = 'core.UserProfile' # <-- NEW
Enter fullscreen mode Exit fullscreen mode

We can now make the migrations, apply them, and create a superuser for the app:

❯ uv run python manage.py makemigrations
Migrations for 'core':
  core/migrations/0001_initial.py
    + Create model UserProfile
❯ uv run python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, core, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0001_initial... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying core.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying sessions.0001_initial... OK
❯ uv run python manage.py createsuperuser
Username: admin
Email address: admin@email.com
Password:
Password (again):
This password is too short. It must contain at least 8 characters.
This password is too common.
This password is entirely numeric.
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.
❯
Enter fullscreen mode Exit fullscreen mode

Running the app again with runserver and opening localhost:8000/admin will show us Django's admin page:

Django admin site

To wrap up part 1, we can register an admin view for UserProfile, :

# core/admin.py

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import UserProfile

@admin.register(UserProfile)
class UserProfileAdmin(UserAdmin):
    model = UserProfile
    list_display = ['email', 'username']
Enter fullscreen mode Exit fullscreen mode

Refreshing the admin site will show us the admin interface for users

User admin in the main page of the admin site

User admin page in the admin site

That's it for the initial setup of our project! In part 2, we will use pytest to implement the first version of the Todo model

Top comments (2)

Collapse
 
saifeddin1 profile image
Saifeddin Matoui

Looking forward for the next part !

Collapse
 
rodbv profile image
rodbv

Thanks! I'm writing this to document my own learning but it's awesome if it's useful for more people :)