Django Setup

Part 1, Chapter 3


In this chapter, we'll create a new Django app in which you'll be able to manage party details and a gift registry.

Initial Setup

First, create a new project and initialize a git repo:

$ mkdir django-party && cd django-party
$ git init
$ git checkout -b main

To avoid adding unwanted files to git, add a .gitignore file. Then populate it from the .gitignore file here.

Add the .gitignore file to git, and create a commit:

$ git add .gitignore
$ git commit -m 'Initial commit'

Next, sign up for a GitHub account if you don't already have one. Add a new repo for your project, making sure to add the remote origin repo to your local repo.

Push your changes from your local repository to your new remote repository:

$ git push -u origin main

Having trouble setting up your remote GitHub repo? Review How to Push to GitHub.

Create a virtual environment:

$ python3 -m venv venv
$ source venv/bin/activate
(venv)$

Feel free to swap out virtualenv and Pip for Poetry or Pipenv. For more, review Modern Python Environments.

Create a requirements.txt file and add Django to it:

Django==5.1.2

Install the requirements, and then create a Django project and app:

(venv)$ pip install -r requirements.txt

(venv)$ django-admin startproject core .
(venv)$ django-admin startapp party

So, we created a Django project called core with an app called party that we'll use for creating our app.

Include the party app that you just created in your INSTALLED_APPS:

# core/settings.py

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "party",
]

Before running the migrations, you need to add a custom user model.

Add the following to party/models.py:

# party/models.py

from django.contrib.auth.models import AbstractUser


class CustomUser(AbstractUser):
    pass

Point AUTH_USER_MODEL in the settings file to your new user model:

# core/settings.py

# ... other settings

AUTH_USER_MODEL = "party.CustomUser"

Want to learn more about creating a custom user model? Check out the Creating a Custom User Model in Django article.

Create and apply the migrations:

(venv)$ python manage.py makemigrations
(venv)$ python manage.py migrate

Create the superuser:

(venv)$ python manage.py createsuperuser

I suggest you commit often throughout this course, with meaningful messages -- that way, you can always go back:

(venv)$ git add -A
(venv)$ git commit -m 'Initial Django setup'
(venv)$ git push -u origin main

Models

In the app, we'll deal with three main concepts:

  1. Party: which consists of the invitation text, the date and time of the party, and the venue.
  2. Gift: registry for the gifts that guests can bring to the party. The gift can include an approximate price and a link where it can be seen/bought.
  3. Guest: list with the invitee's name and information if they're attending the party.

That means we need three models:

# party/models.py

import uuid

from django.conf import settings
from django.contrib.auth.models import AbstractUser
from django.db import models


class CustomUser(AbstractUser):
    pass


class Party(models.Model):
    uuid = models.UUIDField(primary_key=True, default=uuid.uuid4)
    party_date = models.DateField()
    party_time = models.TimeField()
    invitation = models.TextField()
    venue = models.CharField(max_length=200)
    organizer = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="organized_parties")

    class Meta:
        verbose_name_plural = "parties"

    def __str__(self):
        return f"{self.venue}, {self.party_date}"


class Gift(models.Model):
    uuid = models.UUIDField(primary_key=True, default=uuid.uuid4)
    gift = models.CharField(max_length=200)
    price = models.FloatField(blank=True, null=True)
    link = models.URLField(max_length=200, blank=True, null=True)
    party = models.ForeignKey(Party, on_delete=models.CASCADE)

    def __str__(self):
        return self.gift


class Guest(models.Model):
    uuid = models.UUIDField(primary_key=True, default=uuid.uuid4)
    name = models.CharField(max_length=200)
    attending = models.BooleanField(default=False)
    party = models.ForeignKey(Party, on_delete=models.CASCADE, related_name="guests")

    def __str__(self):
        return str(self.name)

A couple of things to take note of:

  1. Instead of the default ID that is automatically incremented (making it easy to find a random object just by changing the number in the URL), we set UUID fields as primary keys in all models.
  2. The Party model also includes an organizer field -- the user that will be able to add and edit the records. Instead of directly pointing to the custom user model, it's better to use AUTH_USER_MODEL.
  3. Since the plural of party isn't partys (the way Django pluralizes automatically), we've set a custom plural name with verbose_name_plural.

Create and run the migrations:

(venv)$ python manage.py makemigrations
(venv)$ python manage.py migrate

You want to be able to access those models in Django admin, so add the following to the admin.py file:

# party/admin.py

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

from .models import CustomUser, Party, Gift, Guest


@admin.register(CustomUser)
class UserAdmin(UserAdmin):
    pass


@admin.register(Party)
class PartyAdmin(admin.ModelAdmin):
    readonly_fields = ("uuid",)


@admin.register(Gift)
class GiftAdmin(admin.ModelAdmin):
    readonly_fields = ("uuid",)


@admin.register(Guest)
class GuestAdmin(admin.ModelAdmin):
    readonly_fields = ("uuid",)

Pre-fill the Database

Since our application uses a lot of data -- parties, gifts, and guests -- I prepared JSON files with some initial data.

Inside the party app, create a "fixtures" directory and add the following three files to it:

  1. initial_parties.json
  2. initial_gifts.json
  3. initial_guests.json

Run the following to load the data into the database:

(venv)$ python manage.py loaddata initial_parties.json
(venv)$ python manage.py loaddata initial_gifts.json
(venv)$ python manage.py loaddata initial_guests.json

initial_parties.json provides a list of parties, including the organizer's foreign key. Take note that the organizer of the imported parties has ID of 1, which should be the superuser's ID.

If your superuser for some reason doesn't have an ID of 1, I suggest you either replace every "organizer": 1 inside the initial_parties.json with "organizer": YOUR_ID or delete the sqlite db and create the superuser again.

A big part of the app will only be accessible to the party organizer, so ensure that the user you're logged in with matches the organizer of most of the parties.

initial_gifts.json and initial_guests.json both include a party foreign key, meaning you get an error if you didn't also import the parties. Almost all of the gifts belong to the same party (uuid: 00bb45a4-3936-477b-8113-323c57f51f08, venue: Neuschwanstein castle). The gift registry and the guest list for that party are meant to be the playground for your manual testing.

The gift named Not on the same party list and the guest named Not invited to the Neuschwanstein Castle do not belong to the same party to ensure that only the associated objects are displayed.

Base Template

We need a base template that will be used by all other templates.

Create a "templates" directory including a directory "party" for namespacing purposes:

(venv)$ cd party && mkdir -p templates/party

Create a new file called base.html inside "party/templates/party".

This is the structure you should now have:

├── .gitignore
├── core
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── manage.py
├── party
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── fixtures
│   │   ├── initial_gifts.json
│   │   ├── initial_guests.json
│   │   └── initial_parties.json
│   ├── migrations
│   │   ├── 0001_initial.py
│   │   ├── 0002_party_guest_gift.py
│   │   └── __init__.py
│   ├── models.py
│   ├── templates
│   │   └── party
│   │       └── base.html
│   ├── tests.py
│   └── views.py
└── requirements.txt

This is just a typical base template, so add the following to it:

<!--party/templates/party/base.html-->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Party!</title>
</head>
<body>
<main>
    {% block content %}
    {% endblock %}
</main>
</body>
</html>

Commit and push the changes you made:

(venv)$ git add -A
(venv)$ git commit -m 'Models and base template added.'
(venv)$ git push -u origin main

pytest

Throughout part 2, we'll be using pytest for our testing.

Let's set it up.

Add pytest and pytest-django to your requirements:

Django==4.2.7
pytest==8.3.3
pytest-django==4.9.0

And install the requirements:

(venv)$ pip install -r requirements.txt

In the root directory, create a pytest.ini file and add the following to it:

# pytest.ini

[pytest]
DJANGO_SETTINGS_MODULE = core.settings
python_files = tests.py test_*.py

With this, pytest will discover all tests inside either a tests.py file or any Python file that starts with test_.

Remove the party/tests.py file and create a tests package:

(venv)$ mkdir -p party/tests
(venv)$ touch party/tests/__init__.py

New to pytest? Learn more about it in Pytest for Beginners.

Commit and push the changes you made:

(venv)$ git add -A
(venv)$ git commit -m 'Pytest added.'
(venv)$ git push -u origin main

What Have You Done?

In this chapter, you've created a Django project called django-party that includes the party app. You've created three models -- Party, Gift, and Guest -- and filled the database with test data. You've also created a base.html template and added pytest.

With that, we can start adding HTMX, Tailwind CSS, and Alpine.js to your Django project, which we'll do in the next three chapters.




Mark as Completed