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.12 -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==4.2.7
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:
- Party: which consists of the invitation text, the date and time of the party, and the venue.
- 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.
- 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:
- 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. - The
Party
model also includes anorganizer
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. - 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:
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 thesqlite
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==7.4.3
pytest-django==4.7.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