Table of Contents
- Workflow
- Set Up
- Django Project Initiation
- Models
- Paths
- Forms
- Views
- Templates
- Authentication
- Helpful Commands
Workflow
Whenever I create a Django project, I tend to follow the proceeding workflow order. Keep in mind, this is just one possible workflow, and you may find one that works better for you!
- Prepare the project directory
- Create the virtual environment
- Install/Upgrade Django
- Install Dependencies
- Create the Django project
- Create the Django app
- Create superuser
- Add app to the project settings.py
- Run initial migrations
- Start the development server
- Create your models
- Write/run tests on your models
- Make your migrations and run them
- Create your forms, if any
- Create your views, either function or class
- Update your paths with your views
- Create templates for the views
- Write/run tests on your views
⚠️ Don't Skip the Docs! I mostly wrote this guide to help myself, and while I am more than happy to share it with anyone who finds it useful, I can't recommend enough getting comfortable with reading the official documentation!
📝 Note: The structure of this cheat sheet more or less follows the order of the workflow above. There is also a section of helpful tips and other general syntax bits at the bottom. Feel free to skip around!
Set Up
If starting from scratch:
# create the project directory
~$ mkdir <<project_name>>
# access that directory
~$ cd <<project_name>>
# initialize a git repo within that directory
~$ git init
If starting from an existing repo:
# fork the project (if necessary)
# clone your forked copy to your computer
~$ git clone <<repo_url>>
# access that directory
~$ cd <<project_name>>
Django Project Initiation:
Create your virtual environment:
# create your virtual environment (make sure you are
# still within that directory!)
~$ python -m venv .venv
# activate that virtual environment
~$ source .venv/bin/activate # Mac OS
C:> ./.venv/Scripts/Activate.ps1 # Windows
# to deactivate your virtual environment
~$ deactivate
Install the Django package:
# install Django
~$ python -m pip install Django
# upgrade Django (if necessary)
~$ pip install -U Django
# upgrade pip (if necessary)
~$ python -m pip install --upgrade pip # Mac OS
C:> py -m pip install --upgrade pip # Windows
Install your dependencies:
# to create a requirements file that contains
# your project dependencies
~$ pip freeze > requirements.txt
# to install your project requirements
# (if a file already exists)
~$ pip install -r requirements.txt
Generate the Django project
📝 Note: Don't forget the "." after your project name!
# create your django project
~$ django-admin startproject <<name>> .
# create your django app(s)
~$ python manage.py startapp <<name>>
# to update your database for the migrations that
# come with a default django installation
~$ python manage.py migrate
# create a superuser to access the admin
~$ python manage.py createsuperuser
# add your app(s) to the django project's settings.py
INSTALLED_APPS = [
"app_name.apps.AppNameConfig",
. . .
]
Development server
# to start your development server
~$ python manage.py runserver => ex. http://127.0.0.1:8000
# to add localhost for use in development in
# project's settings.py
ALLOWED_HOSTS = [
"localhost",
. . .
]
Models
Create your models
These models need to be created as database tables with the migrations commands. Please note that the "id" field is automatically created by Django for models by default.
# app_name/models.py
from django.db import models
class Customer(models.Model)
name = models.Charfield('Customer', max_length=120)
age = models.IntegerField()
note = models.TextField(blank=True, null = True)
email = models.EmailField(max_length=255, blank=True, null=True)
credit = models.FloatField(blank=True)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
# Create a Select Field (return value, display value)
# If this is given, the default form widget will be a
# select box instead of the standard text field and will
# limit choices to the choices given.
TYPE_CHOICES = (
('Customer', 'Customer'),
('Supplier', 'Supplier'),
('Student', 'Student'),
)
type = models.CharField(choices=TYPE_CHOICES)
# model string representation
def __str__(self):
return self.name
Relationships between models
# One-to-Many:
supplier = models.ForeignKey(Supplier, blank=True, null=True, on_delete=models.CASCADE)
# where "Supplier" is the name of the model that the
# field is referencing
# on_delete can be set to models.CASCADE, models.ST_DEFAULT or models.SET_NULL
# Many-to-Many:
tags = models.ManyToManyField(Tag, blank=True)
# One to One
User = models.OneToOneField(User, on_delete=models.CASCADE)
Here are examples of several models, some displaying one-to-many and some displaying many-to-many relationships:
from django.db import models
from django.conf import settings
USER_MODEL = settings.AUTH_USER_MODEL
# Create your models here.
class Recipe(models.Model):
name = models.CharField(max_length=125)
author = models.ForeignKey(
USER_MODEL,
related_name="recipes",
on_delete=models.CASCADE,
null=True,
)
description = models.TextField()
image = models.URLField(null=True, blank=True)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
servings = models.PositiveSmallIntegerField(null=True)
def __str__(self):
return f"{self.name} by {self.author}"
class Step(models.Model):
recipe = models.ForeignKey(
"Recipe",
related_name="steps",
on_delete=models.CASCADE,
)
order = models.PositiveSmallIntegerField()
directions = models.CharField(max_length=300)
food_items = models.ManyToManyField("FoodItem", blank=True)
def __str__(self):
return f"{self.order} {self.directions}"
Models in admin panel
To display your model objects in the admin panel at localhost:8000/admin, register the model in the app's admin file at app_name/admin.py. You can also specify the fields you want to use in the admin panel.
from django.contrib import admin
from app_name.models import ModelName
# Register your models here
# Custom model Admin (admin.py):
class BlogAdmin(admin.ModelAdmin)
fields = ("title", "description") # Fields to use for add/edit/show page
list_display = ("title", "description") # fields to display in search page
list_display_links = ("title") # fields that will be a link in search page
ordering("date_created",) # Ordering allowed in the search page
search_fields("title", "description") # Search fields allowed in the search page
# Register app
admin.site.register(Blog, BlogAdmin)
Paths
Routing
You need to connect the paths stored in each app's urls.py file to your project's urls.py in project_name/urls.py.
from django.contrib import admin
from django.urls import path, include, reverse_lazy
from django.views.generic.base import RedirectView
urlpatterns = [
path('admin/', admin.site.urls), # Django adds this automatically
path("", include('app_name.urls')) # include your app urls with include()
path("recipes/", include('recipes.urls')),
path("", RedirectView.as_view(url=reverse_lazy("recipes_list")), name="home"), # write a redirect view for your home page like this
]
App Paths
from django.urls import path
from recipes.views import (
RecipeCreateView,
RecipeDeleteView,
RecipeUpdateView,
log_rating,
create_shopping_item,
delete_all_shopping_items,
ShoppingItemListView,
RecipeDetailView,
RecipeListView,
)
urlpatterns = [
path("", RecipeListView.as_view(), name="recipes_list"),
path("<int:pk>/", RecipeDetailView.as_view(), name="recipe_detail"),
path("<int:pk>/delete/", RecipeDeleteView.as_view(), name="recipe_delete"),
path("new/", RecipeCreateView.as_view(), name="recipe_new"),
path("<int:pk>/edit/", RecipeUpdateView.as_view(), name="recipe_edit"),
path("<int:recipe_id>/ratings/", log_rating, name="recipe_rating"),
path("shopping_items/create", create_shopping_item, name="shopping_item_create"),
path("shopping_items/delete", delete_all_shopping_items, name="delete_all_shopping_items"),
path("shopping_items/", ShoppingItemListView.as_view(), name="shopping_items_list"),
]
Forms
from django import forms
from recipes.models import Rating
class RatingForm(forms.ModelForm):
class Meta:
model = Rating
fields = ["value"]
# Render form in templates
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<div class="control">
<button class="button">Create</button>
</div>
</form>
Views
Class-based Views
from django.shortcuts import redirect
from django.urls import reverse_lazy
from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from django.views.generic.list import ListView
from django.contrib.auth.mixins import LoginRequiredMixin
from recipes.forms import RatingForm
from recipes.models import Recipe, Ingredient, ShoppingItem
class RecipeListView(ListView):
model = Recipe
template_name = "recipes/list.html"
paginate_by = 2
class RecipeDetailView(DetailView):
model = Recipe
template_name = "recipes/detail.html"
# Optional: change context data dictionary
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["rating_form"] = RatingForm()
foods = []
for item in self.request.user.shopping_items.all():
foods.append(item.food_item)
context["servings"] = self.request.GET.get("servings")
context["food_in_shopping_list"] = foods
return context
class RecipeCreateView(LoginRequiredMixin, CreateView):
model = Recipe
template_name = "recipes/new.html"
fields = ["name", "servings", "description", "image"]
success_url = reverse_lazy("recipes_list")
# Optional: overwrite form data (before save)
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
class RecipeUpdateView(LoginRequiredMixin, UpdateView):
model = Recipe
template_name = "recipes/edit.html"
fields = ["name", "servings", "description", "image"]
success_url = reverse_lazy("recipes_list")
class RecipeDeleteView(LoginRequiredMixin, DeleteView):
model = Recipe
template_name = "recipes/delete.html"
success_url = reverse_lazy("recipes_list")
Function-based Views
from django.shortcuts import redirect
from django.urls import reverse_lazy
from django.db import IntegrityError
# Create your views here.
def log_rating(request, recipe_id):
if request.method == "POST":
form = RatingForm(request.POST)
if form.is_valid():
rating = form.save(commit=False)
try:
rating.recipe = Recipe.objects.get(pk=recipe_id)
except Recipe.DoesNotExist:
return redirect("recipes_list")
rating.save()
return redirect("recipe_detail", pk=recipe_id)
def create_shopping_item(request):
ingredient_id = request.POST.get("ingredient_id")
ingredient = Ingredient.objects.get(id=ingredient_id)
user = request.user
try:
ShoppingItem.objects.create(
food_item=ingredient.food,
user=user,
)
except IntegrityError:
pass
return redirect("recipe_detail", pk=ingredient.recipe.id)
Templates
Your HTML templates should be stored in app_folder/templates/app_name.
Base Template
'base.html' needs to be stored in your root template directory, project_folder/templates
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %} {% endblock %}Scrumptious Recipes</title>
<link rel="stylesheet" href="{% static 'css/app.css' %}">
</head>
<body>
<header>
<nav>
<ul>
<li>
<a href="{% url 'home' %}">Scrumptious</a>
</li>
{% if user.is_staff %}
<li>
<a href="{% url 'admin:index' %}">Admin</a>
</li>
{% endif %}
<li>
<a href="{% url 'recipe_new' %}">Write a recipe</a>
</li>
<li>
<a href="{% url 'tags_list' %}">Tags</a>
</li>
{% if not user.is_authenticated %}
<li>
<a href="{% url 'login' %}">Login</a>
</li>
{% endif %}
{% if user.is_authenticated %}
<li>
<a href="{% url 'meal_plans_list' %}">Meal Plans</a>
</li>
<li>
<a href="{% url 'shopping_items_list' %}">Shop List ({{ user.shopping_items.all|length }})</a>
</li>
<li>
<a href="{% url 'logout' %}">Logout</a>
</li>
{% endif %}
</ul>
</nav>
{% block pagination %}
{% endblock pagination %}
{% block create %}
{% endblock create %}
</header>
{% block content %}
{% endblock content %}
</body>
</html>
Pagination Template
{% extends 'base.html' %}
{% block pagination %}
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
<a href="?page=1">« first</a>
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">next</a>
<a href="?page={{ page_obj.paginator.num_pages }}">last »</a>
{% endif %}
</span>
</div>
{% endblock pagination %}
DeleteView Template
{% extends 'base.html' %}
{% block title %}
Delete {{ recipe.name }} |
{% endblock %}
{% block content %}
<main>
<form method="post">
{% csrf_token %}
<p>Are you sure you want to delete "{{ recipe }}"?</p>
{{ form.as_p }}
<div class="control">
<button class="button">Delete</button>
</div>
</form>
</main>
{% endblock content %}
DetailView Template
{% extends 'base.html' %}
{% load markdownify %}
{% load resizer %}
{% block title %}
{{ recipe.name }} |
{% endblock %}
{% block content %}
<main class="recipe-detail">
<div>
<a href="{% url 'recipe_edit' recipe.id %}">Edit</a>
<a href="{% url 'recipe_delete' recipe.id %}">Delete</a>
</div>
{% if recipe.image %}
<img src="{{ recipe.image }}" class="pull-right">
{% endif %}
<h1>{{ recipe.name }}</h1>
<h2>by: {{ recipe.author | default_if_none:"Unknown" }}</h2>
<p>
Created on {{ recipe.created }} |
Updated on {{ recipe.updated }}
</p>
{% if recipe.servings %}
<p>
Serves {{ servings|default_if_none:recipe.servings }}
</p>
<form method="GET">
<input required type="number" name="servings">
<button>Resize</button>
</form>
{% endif %}
<form method="post" action="{% url 'recipe_rating' recipe.id %}">
{% csrf_token %}
<div class="rating-form-grid">
{{ rating_form.as_p }}
<button class="button">Rate</button>
</div>
</form>
<p>Tags: {% for tag in recipe.tags.all %}{{ tag.name }} {% endfor %}</p>
<p>{{ recipe.description | markdownify }}</p>
<h2>Ingredients</h2>
<table>
<thead>
<tr>
<th colspan="2">Amount</th>
<th>Food item</th>
</tr>
</thead>
<tbody>
{% for ingredient in recipe.ingredients.all %}
<tr>
<td>{{ ingredient|resize_to:servings }}</td>
<td>{{ ingredient.measure.name }}</td>
<td>{{ ingredient.food.name }}</td>
<td>
{% if ingredient.food not in food_in_shopping_list %}
<form method="POST" action="{% url 'shopping_item_create' %}">
{% csrf_token %}
<input type="hidden" name="ingredient_id" value="{{ ingredient.id }}">
<button>+ shopping list</button>
</form>
{% else %}
in your list
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<h2>Steps</h2>
<ol>
{% for step in recipe.steps.all %}
<li>{{ step.directions }}</li>
{% endfor %}
</ol>
</main>
{% endblock content %}
UpdateView Template
{% extends 'base.html' %}
{% block title %}
Edit {{ recipe.name }} |
{% endblock %}
{% block content %}
<main class="recipe-form">
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<div class="control">
<button class="button">Update</button>
</div>
</form>
</main>
{% endblock content %}
ListView Template
{% extends 'paginate.html' %}
{% block title %}
Recipes |
{% endblock %}
{% block content %}
<main class="recipe-grid">
{% for recipe in recipe_list %}
<div class="recipe-card">
<h2 class="recipe-card-title">
<a href="{% url 'recipe_detail' recipe.pk %}">{{ recipe.name }}</a>
</h2>
<p class="recipe-card-date">{{ recipe.updated }}</p>
</div>
{% endfor %}
</main>
{% endblock content %}
CreateView Template
{% extends 'base.html' %}
{% block title %}
New Recipe |
{% endblock %}
{% block content %}
<main class="recipe-form">
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<div class="control">
<button class="button">Create</button>
</div>
</form>
</main>
{% endblock content %}
Authentication
LoginView
This is done in accounts/urls.py, as the LoginView is already pre-made for you by Django.
# import the pre-made LoginView from Django
# in your accounts/urls.py file
from django.contrib.auth.views import LoginView
LoginView Template
# By default the LoginView will try to open a
# template name 'registration/login.html' and
# send a login form with it.
# create a template at that location
{% extends 'base.html' %}
{% block title %}
Login |
{% endblock title %}
{% block content %}
<main>
<div class="narrow-form">
<h1>Login</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button>Login</button>
</form>
</div>
</main>
{% endblock content %}
LogoutView
# this one is also pre-created by Django
# import it into accounts/urls.py
from django.contrib.auth.views import LogoutView
LogoutView Template
{% extends 'base.html' %}
{% block title %}
Logged Out |
{% endblock title %}
{% block content %}
<div class="narrow-form">
<h1>Logged out</h1>
<p>You're all logged out!</p>
</div>
{% endblock content %}
SignupView
This view is NOT created by default like LoginView and LogoutView. The UserCreationForm is created by Django by default; however, the SignupView is not, so a view needs to be created that handles showing the sign-up form, and handles its submission.
from django.contrib.auth import login
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
from django.shortcuts import render, redirect
def signup(request):
if request.method == "POST":
form = UserCreationForm(request.POST)
if form.is_valid():
username = request.POST.get("username")
password = request.POST.get("password1")
user = User.objects.create_user(
username=username,
password=password,
)
user.save()
login(request, user)
return redirect("home")
else:
form = UserCreationForm()
context = {
"form": form,
}
return render(request, "registration/signup.html", context)
Signup Template
{% extends 'base.html' %}
{% block title %}
Sign up |
{% endblock title %}
{% block content %}
<main>
<h1>Sign Up</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Sign Up</button>
</form>
</main>
{% endblock content %}
Accounts Paths
Don't forget to add paths to reach all those views you just created! Here is an example of what the Accounts app's urls.py file will look like (accounts/urls.py).
from django.urls import path
from django.contrib.auth.views import LoginView, LogoutView
from accounts.views import signup
urlpatterns = [
path("login/", LoginView.as_view(), name="login"),
path("logout/", LogoutView.as_view(), name="logout"),
path("signup/", signup, name="signup"),
]
Helpful Commands
Django migrations
Django migrations go hand in hand with models: whenever you write a new model, or update an existing one, you need to generate a migration to create the necessary table in the database. Note: the actual migration files will be created under app_name/migrations in your directory.
# to make migrations
~$ python manage.py makemigrations
# to run migrations
~$ python manage.py migrate
Django project configuration
# App templates folder
create folder app_folder/templates/app_name
# Project templates folder:
create folder project_name/templates
# settings.py template config
Project templates settings.py:
TEMPLATES = [
{ …
'DIRS': [BASE_DIR / 'templates', ],
… }
# Create Static folder:
project_name\static\
# Static folder (settings.py):
STATIC_URL = '/static/'
STATICFILES_DIRS = [ BASE_DIR / 'static' ]
STATIC_ROOT = 'static_root'
Top comments (2)
Here is an excellent package for starting Django projects from stracth:
github.com/cookiecutter/cookiecutt...
It is one of the best starting scaffold for Django, starting by this template you can change some part of it based on your opinion
Thank you so much, super useful! I refer to it almost every other day.