Starter template
The Forge starter template is a GitHub repo with what we consider to be the best Django project layout.
It includes all of the public Forge packages, and focuses on a specific set of tools.
You can browse the repo itself on GitHub →
Tools
Poetry
Poetry is our preferred way to manage Python dependencies. You can check the Poetry documentation for more information, but here is what you need to know for daily use.
The files managed by Poetry are pyproject.toml
and poetry.lock
.
Poetry has a command to add a new dependency:
poetry add <package>
If the package you want is purely for development (ex. "pytest-cov") then you can use the dev
flag:
poetry add pytest-cov --dev
Poetry stores the exact versions installed in poetry.lock
.
You should commit this file in your repo,
because it is how you tell Heroku and other people on your team exactly what versions should be installed.
You can update your poetry.lock
with:
poetry update
Tailwind CSS
Tailwind CSS is integrated through the forge-tailwind
package.
This allows you to use Tailwind without actually installing Node or JavaScript dependencies!
In Forge we consider Tailwind to be the "official" way to style your site. By doing this, we can include styled templates in the Forge packages without requiring additional, custom CSS. Everything uses Tailwind which makes it easy to override templates.
Docker
We use Docker to run a local Postgres database during forge work
.
We do not use Docker to run your Django/Python code. In our experience, it's easier to run those processes locally and introducing Docker can be a learning curve and layer of abstraction that does more harm than good. This does mean that you need to have Python 3.x installed on your machine and might occasionally need to pay attention to the exact version being used.
Heroku
Several parts of Forge are designed intentionally to work well with Heroku, and we anticipate building more in the future.
GitHub Actions
In our opinion, GitHub is the best place to host git repos and it integrates well with Heroku. GitHub Actions is also a great and low-cost/free CI solution for running your tests.
The only thing you need to use GitHub Actions is a file called .github/workflows/test.yml
in your repo.
Models
The starter template comes with two sets of models that are useful for most projects.
The first is a custom users
app with a User
model.
If you need to store custom data related to a user,
you can put fields directly on this model.
A custom user model is the recommended way to start a project according to the Django docs.
The second is a teams
app, that includes the Team
and TeamMembership
models.
Most projects inevitably need some type of "Team" concept,
so it's one of the few places we like to provide a starting point.
You can call it something else in your frontend HTML (ex. "Organization" or "Group"),
but in general the Team
is where you'll group users together and attach billing information.
You don't have to use the
teams
app if you know you aren't going to need it or prefer a different naming convention. We would strongly recommend you keep a customusers
app/model though.
Users
We include two modifications to what the Django AbstractUser
provides:
- the email for a user must be unique (helps with login and duplication)
- the
uuid
field is added viaUUIDModel
(helps with identifying users without exposing the standard auto-incrementing ID)
When you need to store something directly on a user,
go ahead and write your code in the User
model:
class User(AbstractUser, UUIDModel):
email = models.EmailField(unique=True)
# Add your own fields and methods here!
SignupView
The quickstart comes with all of the django.contrib.auth.urls
enabled at the root (ex. "example.com/login/"). This includes:
- login
- logout
- password changes
- password resets
We also include a standard signup form to let users create an account using username, email address, and password.
urlpatterns = [
# ...
path("signup/", SignupView.as_view(), name="signup"),
path("", include("django.contrib.auth.urls")),
]
When someone completes the signup form,
they'll be redirected to log in using their new account.
You can customize the SignupView
to log them in immediately, show welcome messages, or start an email confirmation flow first.
Teams
When you're setting up the relationships in your project, most of the time you'll want to associate things with a "team" rather than a "user".
Whether you need teams right off the bat or plan to support them later,
it's a good idea to tie "projects", billing, etc. to a Team
even if that team is just a single User
.
Down the road,
if you need to transfer a "project" from a user to their company team,
you can effectively do a Team->Team
transfer which is much simpler than changing the type of relationship from a User
to a Team
.
Likewise,
if you plan to support subscriptions for individuals and businesses,
you should consider both of those to be a Team
on the backend which will simplify a lot of the billing logic.
Teams in Forge come with a few fields we think are useful, but you can customize is from there:
class Team(TimestampModel, UUIDModel):
name = models.CharField(max_length=255)
members = models.ManyToManyField(
"users.User",
related_name="teams",
through="TeamMembership"
)
# Add your own fields and methods here!
class TeamRoles(models.TextChoices):
...
class TeamMembership(TimestampModel, UUIDModel):
...
Roles
When you add people to teams,
we also provide a role
field with basic ADMIN
or MEMBER
options.
You can add to these as needed,
but if you aren't using roles right away then just make everyone a MEMBER
so you can add admin-specific features later!
team = Team.objects.create("A new team")
team_member1 = TeamMembership.objects.create(
team=team,
user=user,
role=TeamRoles.MEMBER,
)
Templates
A new Forge project will come with a base.html
to get you started.
The look and feel of your app is totally up to you,
and you can immediately start customizing it by changing the Tailwind classes.
The blocks in the default base.html
are just suggestions.
But you should keep a {% block content %}
and {% block html_title %}
as those are used in the Forge authentication templates.
The quickstart base.html
looks something like this:
{% load static %}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>{% block html_title %}{{ html_title }}{% endblock %}</title>
<link href="{% static 'dist/tailwind.css' %}" rel="stylesheet">
{% block head_scripts %}{% endblock %}
</head>
<body class="{% block body_class %}flex flex-col bg-gray-50 min-h-screen{% endblock %}">
{% block header %}
{% if request.user.is_authenticated %}
<div class="flex bg-white border-b border-gray-200">
<div class="container flex items-center justify-between px-4 py-4 mx-auto">
<a class="px-3 py-2 text-gray-800 rounded hover:bg-gray-200" href="/">Home</a>
<div class="flex space-x-2">
<a class="flex items-center px-3 py-2 text-gray-800 rounded hover:bg-gray-200" href="{% url 'my_account' %}">
My Account
</a>
<a class="flex items-center px-3 py-2 text-gray-800 rounded hover:bg-gray-200" href="{% url 'logout' %}">
Log out
</a>
</div>
</div>
</div>
{% endif %}
{% endblock %}
{% block content_container %}
<div class="container flex flex-col flex-grow px-4 pt-4 mx-auto">
{% block content %}{% endblock %}
</div>
{% endblock %}
{% block footer %}
<div class="container px-4 mx-auto mt-10">
...
</div>
{% endblock %}
{% block footer_scripts %}{% endblock %}
</body>
</html>
Repo layout
The app
directory
Your Django code is all inside the app
directory.
You might have used repos before where all of your Django code it at the root of the repo,
but our preference is to isolate the Django/Python code from other "meta" files like .gitignore
, .github/
, pyproject.toml
, Procfile
, etc. because when you're working in your app,
you don't need access to those files to be right next to your Python code.
Don't rename the
app
directory — Forge expects it to be namedapp
, it has no effect on your Django code, and it is one less thing to worry about "getting right" when starting a project.
Top-level urls.py
, views.py
, and settings.py
In Django you have a handful of top-level files that act as "entrypoints" to your app.
But they're often buried an additional layer deep in your project (myproject/myproject/settings.py
).
We don't really see the point in that,
and think that mentally it is easier to reason about a project hierarchy when your settings.py
and urls.py
(set as ROOT_URLCONF
) are located right at the top (ex. app/settings.py
).
Additionally,
there are some views like the homepage that don't really belong in a particular app,
so a top-level app/views.py
can be handy for those as well as view mixins that are shared across the project.
Top-level static
and templates
Like the settings.py
and other top-level files,
we include a top-level static
and templates
directory.
The static
will include your Tailwind CSS files and other project-wide assets like a logo.
The templates
directory will include your base.html
and other project-wide templates or pages like home.html
.
Where's manage.py
?!
We've completely removed the manage.py
, wsgi.py
, and asgi.py
files from your repo.
They are automatically loaded from Forge itself, because you rarely need to make any changes to them.
If you do need to customize them, you can simply place a copy in your app
directory.
The forge django <subcommand>
is essentially an alias to python manage.py <subcommand>
.