0) Prereqs

1) Create a project folder & virtual env

# pick a folder
mkdir mysite && cd mysite

# create & activate venv (Windows)
python -m venv .venv
.venv\Scripts\activate

# macOS/Linux:
# python3 -m venv .venv
# source .venv/bin/activate

2) Install Django and essentials

pip install --upgrade pip
pip install django psycopg[binary] python-decouple gunicorn whitenoise

Why:

3) Start the Django project

django-admin startproject config .

Tree now:

mysite/
  config/
    __init__.py
    asgi.py
    settings.py
    urls.py
    wsgi.py
  manage.py

4) Run it once

python manage.py runserver

Open http://127.0.0.1:8000 — you should see “The install worked!”

5) Create your first app

python manage.py startapp blog

Add blog to INSTALLED_APPS in config/settings.py.

6) Make a simple model (CRUD ready)

blog/models.py

from django.db import models
from django.contrib.auth import get_user_model

User = get_user_model()

class Post(models.Model):
    title = models.CharField(max_length=200)
    slug = models.SlugField(unique=True)
    body = models.TextField()
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="posts")
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    published = models.BooleanField(default=True)

    class Meta:
        ordering = ["-created"]

    def __str__(self):
        return self.title

Apply migrations:

python manage.py makemigrations
python manage.py migrate

Create a superuser:

python manage.py createsuperuser

7) Register in the admin

blog/admin.py

from django.contrib import admin
from .models import Post

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    list_display = ("title", "author", "published", "created")
    prepopulated_fields = {"slug": ("title",)}
    search_fields = ("title", "body")
    list_filter = ("published", "created")

8) URLs, views, templates

Create app URLs:
blog/urls.py

from django.urls import path
from . import views

urlpatterns = [
    path("", views.post_list, name="post_list"),
    path("<slug:slug>/", views.post_detail, name="post_detail"),
]

Wire them in project URLs:
config/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("", include("blog.urls")),
]

Now visit http://127.0.0.1:8000/admin and add a Post.

Views:
blog/views.py

from django.shortcuts import get_object_or_404, render
from .models import Post

def post_list(request):
    posts = Post.objects.filter(published=True)
    return render(request, "blog/post_list.html", {"posts": posts})

def post_detail(request, slug):
    post = get_object_or_404(Post, slug=slug, published=True)
    return render(request, "blog/post_detail.html", {"post": post})

Templates:
Create templates/blog/post_list.html:

<!doctype html>
<html>
  <head><meta charset="utf-8"><title>Blog</title></head>
  <body>
    <h1>Posts</h1>
    <ul>
      {% for p in posts %}
        <li><a href="{{ p.get_absolute_url|default:'/'|add:p.slug|add:'/' }}">{{ p.title }}</a></li>
      {% empty %}
        <li>No posts yet.</li>
      {% endfor %}
    </ul>
  </body>
</html>

Create templates/blog/post_detail.html:

<!doctype html>
<html>
  <head><meta charset="utf-8"><title>{{ post.title }}</title></head>
  <body>
    <a href="/">← Back</a>
    <h1>{{ post.title }}</h1>
    <p><small>By {{ post.author }} — {{ post.created|date:"Y-m-d H:i" }}</small></p>
    <div>{{ post.body|linebreaks }}</div>
  </body>
</html>

Tell Django where templates live:
config/settings.py

from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent

TEMPLATES[0]["DIRS"] = [BASE_DIR / "templates"]

9) Static & media (images, CSS, JS)

config/settings.py

STATIC_URL = "static/"
STATICFILES_DIRS = [BASE_DIR / "static"]        # for dev
STATIC_ROOT = BASE_DIR / "staticfiles"          # for collectstatic (prod)

MEDIA_URL = "media/"
MEDIA_ROOT = BASE_DIR / "media"

During development:
config/urls.py

from django.conf import settings
from django.conf.urls.static import static

urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Create folders:

mkdir static media templates
mkdir templates/blog static/css static/js

10) Frontend 

Navigate to http://127.0.0.1:8000/ you should see the published posts.