Django Cheat Sheet
Tags: beginners, python, django
Published: 2022-03-24 20:40:00

## Table of Contents
1. [Workflow](#workflow)
2. [Set Up](#set-up)
3. [Django Project Initiation](#django-project-initiation)
    1. [Virtual Environment](#create-your-virtual-environment)
    2. [Django Installation](#install-the-django-package)
    3. [Dependencies](#install-your-dependencies)
    4. [Generate Project](#generate-the-django-project)
    5. [Development Server](#development-server)
4. [Models](#models)
    1. [Creating Models](#create-your-models)
    2. [Model Relationships](#relationships-between-models)
    3. [Admin](#models-in-admin-panel)
5. [Paths](#paths)
    1. [Routing](#routing)
    2. [App Paths](#app-paths)
6. [Forms](#forms)
7. [Views](#views)
    1. [Class-based views](#classbased-views)
    2. [Function-based views](#functionbased-views)
8. [Templates](#templates)
    1. [base.html](#base-template)
    2. [paginate.html](#pagination-template)
    3. [delete.html](#deleteview-template)
    4. [detail.html](#detailview-template)
    5. [edit.html](#updateview-template)
    6. [list.html](#listview-template)
    7. [new.html](#createview-template)
9. [Authentication](#authentication)
    1. [LoginView](#loginview)
    2. [LoginView Template](#loginview-template)
    3. [LogoutView](#logoutview)
    4. [LogoutView Template](#logoutview-template)
    5. [SignupView](#signupview)
    6. [SignupView Template](#signupview-tempalte)
    7. [Accounts paths](#accounts-paths)
10. [Helpful Commands](#helpful-commands)
    1. [migrations](#django-migrations)
    2. [project config](#django-project-configuration)


---


<center>
## Workflow
</center>

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

> :warning: **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!

> :memo: **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!


---


<center> 
## Set Up
 </center>

### If starting from scratch:

```python
# 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:

```python
# fork the project (if necessary)

# clone your forked copy to your computer
~$ git clone <<repo_url>>

# access that directory
~$ cd <<project_name>>
```


---


<center>
## Django Project Initiation:
</center>

### Create your virtual environment:

```python
# 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:

```python
# 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:

```python
# 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

> :memo: **Note:** Don't forget the "." after your project name!

```python
# 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
```python
# 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",
    . . . 
]
```


---


<center>
## Models
</center>

### 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. 

```python
# 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

```python
# 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: 

```python 
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.  

```python
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)
```

---


<center>
## Paths
</center>


### 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.

```python
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

```python
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"),
]

```


---


<center>
## Forms
</center>

```python
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>

```


---


<center>
## Views
</center>

### Class-based Views

```python
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

```python
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)
```


---


<center>
## Templates
</center>

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

```python
{% 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

```python
{% extends 'base.html' %}

{% block pagination %}
<div class="pagination">
    <span class="step-links">
        {% if page_obj.has_previous %}
        <a href="?page=1">&laquo; 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 &raquo;</a>
        {% endif %}
    </span>
</div>
{% endblock pagination %}
```

### DeleteView Template

```python
{% 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

```python
{% 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

```python
{% 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

```python
{% 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

```python
{% 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 %}
```


---


<center>
## Authentication
</center>


### LoginView

This is done in accounts/urls.py, as the LoginView is already pre-made for you by Django.

```python
# import the pre-made LoginView from Django
# in your accounts/urls.py file
from django.contrib.auth.views import LoginView
```

### LoginView Template

```python
# 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

```python
# this one is also pre-created by Django
# import it into accounts/urls.py
from django.contrib.auth.views import LogoutView
```

### LogoutView Template

```python
{% 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.  

```python
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

```python
{% 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). 

```python
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"),
]
```


---


<center>
## Helpful Commands
</center>


### 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.

```python
# to make migrations
~$ python manage.py makemigrations

# to run migrations
~$ python manage.py migrate
``` 


### Django project configuration

```python
# 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'
```