Как создать сайт на Django: пошаговое руководство с примером
Создание сайта на Django с нуля на примере каталога: проект и приложение, модели, админка, шаблоны, форма заявки, статика, тесты и подготовка к деплою.
Содержание · 16 разделов
- Что получится в итоге
- Что нужно перед началом
- Установите Django в виртуальное окружение
- Создайте проект и приложение
- Настройте settings.py
- Опишите модели данных
- Наполните сайт через админку
- Напишите представления и маршруты
- Соберите шаблоны страниц
- Подключите статику: CSS и картинки оформления
- Добавьте форму заявки
- Проверьте сайт тестами
- Подготовьте сайт к деплою
- Вынесите настройки в переменные окружения
- Зафиксируйте зависимости и соберите статику
- Проверьте настройки безопасности
- Выберите, где разместить сайт
- Что добавить на сайт дальше
- Частые ошибки при создании сайта
- TemplateDoesNotExist: base.html
- CSS и фото не загружаются, в логе 404
- The STATICFILES_DIRS setting should not contain the STATIC_ROOT setting
- Cannot use ImageField because Pillow is not installed
- 403 Forbidden: CSRF verification failed
- NoReverseMatch: ‘catalog’ is not a registered namespace
- Частые вопросы
- Сколько времени нужно, чтобы сделать сайт на Django?
- Нужно ли знать HTML, CSS и JavaScript?
- Почему slug из русского названия получается пустым?
- Django или WordPress (конструктор) для сайта?
- Можно ли сделать сайт на Django без базы данных?
- Как перенести сайт на другой компьютер или сервер?
- Итог
Чтобы создать сайт на Django, установите Django в виртуальное окружение, создайте проект командой django-admin startproject и приложение командой startapp, опишите модели и примените миграции, наполните базу через админку, а страницы соберите из view, маршрутов и шаблонов. Затем подключите CSS, добавьте форму, проверьте всё тестами и подготовьте настройки к деплою.
В этом руководстве мы пройдём весь путь создания сайта на Django — от пустой папки до работающего каталога небольшой мастерской: категории и карточки товаров с фото, постраничный вывод, форма заявки, которая сохраняется в базу и видна в админке. Все примеры кода проверены на Django 6.1 и Python 3.12; на Django 5.2 LTS они работают без изменений, отличие в почтовых настройках отмечено в тексте. Если вы впервые слышите о Django, сначала прочитайте обзор Django для начинающих: там объяснено, что такое фреймворк и как устроен путь запроса.
Что получится в итоге
Сайт из трёх типов страниц и админки:
| Адрес | Что показывает |
|---|---|
/ | все опубликованные товары, по 9 на странице |
/category/tables/ | товары одной категории |
/product/oak-table/ | карточка товара с фото, описанием и формой заявки |
/admin/ | админка: товары, категории и полученные заявки |
Структура готового проекта:
mysite/
├── manage.py
├── config/ # пакет настроек проекта
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py, asgi.py
├── catalog/ # приложение каталога
│ ├── admin.py
│ ├── forms.py
│ ├── models.py
│ ├── tests.py
│ ├── urls.py
│ ├── views.py
│ ├── migrations/
│ └── templates/catalog/
│ ├── product_list.html
│ └── product_detail.html
├── templates/
│ └── base.html # общий макет всех страниц
├── static/
│ └── css/style.css
└── media/ # фото, загруженные через админку
Тот же порядок работы подходит для блога, портфолио, сайта услуг или небольшого веб-приложения: меняются модели и шаблоны, шаги остаются прежними.
Что нужно перед началом
- Python 3.12 или новее — Django 6.x не работает на более старых версиях. Для Python 3.10–3.11 берите Django 5.2 LTS.
- Терминал: PowerShell на Windows, Terminal на macOS и Linux.
- Редактор кода, например VS Code или PyCharm.
- Базовый Python (функции, классы, импорты) и основы HTML.
Отдельный сервер базы данных не нужен: Django по умолчанию использует SQLite, которая встроена в Python и хранит данные в одном файле db.sqlite3. PostgreSQL понадобится позже, для продакшна.
Установите Django в виртуальное окружение
Создайте папку проекта, виртуальное окружение и установите Django вместе с Pillow — библиотекой для работы с изображениями. Без неё Django не позволит использовать поле ImageField для фото товаров.
Windows (PowerShell):
mkdir mysite
cd mysite
py -m venv .venv
.venv\Scripts\activate
python -m pip install django pillow
macOS и Linux:
mkdir mysite
cd mysite
python3 -m venv .venv
source .venv/bin/activate
python -m pip install django pillow
Проверьте версию:
python -m django --version
# → 6.1.2
Если команда не находится или PowerShell запрещает запуск скрипта активации, решения собраны в статье «Как установить Django».
Создайте проект и приложение
Проект — это сайт целиком: настройки, корневые маршруты, точки входа для веб-серверов. Приложение — модуль с одной областью ответственности, у нас это каталог. Точка в конце startproject создаёт проект в текущей папке без лишней вложенности:
django-admin startproject config .
python manage.py startapp catalog
Подробный разбор файлов проекта и вариантов запуска — в статье «Как создать проект Django», а устройство приложения и типичные ошибки при его создании — в статье «Как создать приложение в Django».
Настройте settings.py
Откройте config/settings.py и внесите пять изменений: подключите приложение, укажите папку общих шаблонов, язык и часовой пояс, пути к статике и загружаемым файлам.
# config/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'catalog', # наше приложение
]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'], # общие шаблоны проекта
'APP_DIRS': True,
# OPTIONS оставьте как есть
},
]
LANGUAGE_CODE = 'ru'
TIME_ZONE = 'Europe/Moscow'
STATIC_URL = 'static/'
STATICFILES_DIRS = [BASE_DIR / 'static'] # ваши CSS, JS, картинки оформления
STATIC_ROOT = BASE_DIR / 'staticfiles' # сюда collectstatic соберёт всё для продакшна
MEDIA_URL = 'media/'
MEDIA_ROOT = BASE_DIR / 'media' # файлы, загруженные пользователями
LANGUAGE_CODE = 'ru' переводит на русский админку, сообщения об ошибках форм и названия месяцев. Разница между статикой и медиа важна: статика — файлы оформления, которые вы пишете сами и храните в Git; медиа — файлы, которые загружают пользователи и администраторы через сайт. Их хранят и раздают раздельно.
Опишите модели данных
Модель — класс Python, которому соответствует таблица в базе. Для каталога нужны три модели: категория, товар и заявка.
# catalog/models.py
from django.db import models
from django.urls import reverse
class Category(models.Model):
name = models.CharField('Название', max_length=100)
slug = models.SlugField('URL', unique=True)
class Meta:
ordering = ['name']
verbose_name = 'категория'
verbose_name_plural = 'категории'
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('catalog:category', args=[self.slug])
class Product(models.Model):
category = models.ForeignKey(
Category, on_delete=models.PROTECT, related_name='products',
verbose_name='Категория',
)
name = models.CharField('Название', max_length=200)
slug = models.SlugField('URL', unique=True)
description = models.TextField('Описание', blank=True)
price = models.DecimalField('Цена, ₽', max_digits=10, decimal_places=2)
image = models.ImageField('Фото', upload_to='products/', blank=True)
is_published = models.BooleanField('Опубликован', default=True)
created_at = models.DateTimeField('Создан', auto_now_add=True)
class Meta:
ordering = ['-created_at']
verbose_name = 'товар'
verbose_name_plural = 'товары'
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('catalog:product', args=[self.slug])
class Inquiry(models.Model):
product = models.ForeignKey(
Product, on_delete=models.SET_NULL, null=True, related_name='inquiries',
verbose_name='Товар',
)
name = models.CharField('Имя', max_length=100)
phone = models.CharField('Телефон', max_length=30)
message = models.TextField('Комментарий', blank=True)
created_at = models.DateTimeField('Получена', auto_now_add=True)
class Meta:
ordering = ['-created_at']
verbose_name = 'заявка'
verbose_name_plural = 'заявки'
def __str__(self):
return f'{self.name}, {self.phone}'
Несколько решений, которые стоит понять:
SlugFieldхранит человекочитаемую часть адреса:/product/oak-table/вместо/product/17/. Такие адреса понятнее посетителям и поисковым системам.on_delete=models.PROTECTне даст удалить категорию, пока в ней есть товары. У заявкиSET_NULL: если товар удалят, заявка останется в базе.DecimalFieldхранит цену без ошибок округления, которые бывают уfloat.get_absolute_url()возвращает адрес объекта. Шаблоны, админка (кнопка «Смотреть на сайте») иredirect()используют его, поэтому адреса не придётся собирать вручную.verbose_nameзадаёт русские подписи полей в админке и формах.
Типы полей, параметры Meta и связи подробно разобраны в статье «Модели в Django».
Создайте и примените миграции — файлы, которые описывают изменения структуры базы:
python manage.py makemigrations catalog
python manage.py migrate
Migrations for 'catalog':
catalog/migrations/0001_initial.py
+ Create model Category
+ Create model Product
+ Create model Inquiry
После каждого изменения моделей повторяйте эти две команды.
Наполните сайт через админку
Админка Django — готовый интерфейс для управления данными. Зарегистрируйте в ней модели:
# catalog/admin.py
from django.contrib import admin
from .models import Category, Inquiry, Product
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ['name', 'slug']
prepopulated_fields = {'slug': ['name']}
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ['name', 'category', 'price', 'is_published']
list_editable = ['price', 'is_published']
list_filter = ['category', 'is_published']
search_fields = ['name', 'description']
prepopulated_fields = {'slug': ['name']}
@admin.register(Inquiry)
class InquiryAdmin(admin.ModelAdmin):
list_display = ['name', 'phone', 'product', 'created_at']
readonly_fields = ['product', 'name', 'phone', 'message', 'created_at']
prepopulated_fieldsзаполняет slug автоматически, пока вы печатаете название, и транслитерирует кириллицу: «Стол из дуба» превратится вstol-iz-duba.list_editableпозволяет менять цену и публикацию прямо в списке товаров.readonly_fieldsу заявок защищает их от случайной правки: менеджер только читает.
Создайте администратора и запустите сервер разработки:
python manage.py createsuperuser
python manage.py runserver
Откройте http://127.0.0.1:8000/admin/, войдите и добавьте 2–3 категории и несколько товаров с фото. Загруженные изображения Django сохранит в media/products/.
Напишите представления и маршруты
Представление (view) принимает HTTP-запрос и возвращает ответ. Одно представление покажет список товаров — весь или по категории, второе — карточку товара с формой заявки. Форму создадим чуть ниже.
# catalog/views.py
from django.contrib import messages
from django.core.paginator import Paginator
from django.shortcuts import get_object_or_404, redirect, render
from .forms import InquiryForm
from .models import Category, Product
def product_list(request, category_slug=None):
category = None
products = Product.objects.filter(is_published=True).select_related('category')
if category_slug:
category = get_object_or_404(Category, slug=category_slug)
products = products.filter(category=category)
page = Paginator(products, 9).get_page(request.GET.get('page'))
return render(request, 'catalog/product_list.html', {
'category': category,
'categories': Category.objects.all(),
'page': page,
})
def product_detail(request, slug):
product = get_object_or_404(Product, slug=slug, is_published=True)
if request.method == 'POST':
form = InquiryForm(request.POST)
if form.is_valid():
inquiry = form.save(commit=False)
inquiry.product = product
inquiry.save()
messages.success(request, 'Спасибо! Мы перезвоним вам в течение дня.')
return redirect(product)
else:
form = InquiryForm()
return render(request, 'catalog/product_detail.html', {
'product': product,
'form': form,
})
Что здесь происходит:
filter(is_published=True)скрывает черновики, аget_object_or_404отвечает кодом 404 на несуществующий или снятый с публикации товар — вместо ошибки сервера.select_related('category')загружает категории одним запросом вместе с товарами. Без него каждая карточка делала бы к базе отдельный запрос.Paginator(...).get_page()режет список на страницы и сам обрабатывает некорректный номер в?page=.- После успешной отправки формы view делает редирект. Без него обновление страницы в браузере отправило бы заявку повторно.
Маршруты приложения связывают адреса с представлениями:
# catalog/urls.py
from django.urls import path
from . import views
app_name = 'catalog'
urlpatterns = [
path('', views.product_list, name='product_list'),
path('category/<slug:category_slug>/', views.product_list, name='category'),
path('product/<slug:slug>/', views.product_detail, name='product'),
]
Подключите их в корневом config/urls.py и разрешите серверу разработки отдавать загруженные фото:
# config/urls.py
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('catalog.urls')),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
app_name = 'catalog' задаёт пространство имён: в шаблонах адрес строится как {% url 'catalog:product_list' %}, и имена маршрутов разных приложений не конфликтуют. Строка со static() работает только при DEBUG = True — в продакшне медиафайлы отдаёт веб-сервер.
Соберите шаблоны страниц
Общий макет — шапку, подвал, подключение CSS и вывод сообщений — вынесите в базовый шаблон. Страницы будут наследовать его и заполнять только блок контента.
{# templates/base.html #}
{% load static %}
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}Мастерская{% endblock %}</title>
<link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
<header class="header">
<a class="logo" href="{% url 'catalog:product_list' %}">Мастерская</a>
</header>
<main class="container">
{% for message in messages %}
<p class="message">{{ message }}</p>
{% endfor %}
{% block content %}{% endblock %}
</main>
<footer class="footer">© {% now "Y" %} Мастерская</footer>
</body>
</html>
Список товаров с фильтром по категориям и пагинацией:
{# catalog/templates/catalog/product_list.html #}
{% extends 'base.html' %}
{% block title %}{{ category.name|default:'Каталог' }} — Мастерская{% endblock %}
{% block content %}
<h1>{{ category.name|default:'Каталог' }}</h1>
<nav class="categories">
<a href="{% url 'catalog:product_list' %}">Все</a>
{% for cat in categories %}
<a href="{{ cat.get_absolute_url }}">{{ cat.name }}</a>
{% endfor %}
</nav>
<div class="grid">
{% for product in page %}
<article class="card">
{% if product.image %}
<img src="{{ product.image.url }}" alt="{{ product.name }}">
{% endif %}
<h2><a href="{{ product.get_absolute_url }}">{{ product.name }}</a></h2>
<p class="price">{{ product.price|floatformat:"0g" }} ₽</p>
</article>
{% empty %}
<p>Товаров пока нет.</p>
{% endfor %}
</div>
{% if page.has_other_pages %}
<nav class="pagination">
{% if page.has_previous %}
<a href="?page={{ page.previous_page_number }}">← Назад</a>
{% endif %}
<span>Страница {{ page.number }} из {{ page.paginator.num_pages }}</span>
{% if page.has_next %}
<a href="?page={{ page.next_page_number }}">Вперёд →</a>
{% endif %}
</nav>
{% endif %}
{% endblock %}
Карточка товара с формой:
{# catalog/templates/catalog/product_detail.html #}
{% extends 'base.html' %}
{% block title %}{{ product.name }} — Мастерская{% endblock %}
{% block content %}
<a href="{{ product.category.get_absolute_url }}">← {{ product.category.name }}</a>
<h1>{{ product.name }}</h1>
{% if product.image %}
<img class="photo" src="{{ product.image.url }}" alt="{{ product.name }}">
{% endif %}
<p class="price">{{ product.price|floatformat:"0g" }} ₽</p>
{{ product.description|linebreaks }}
<h2>Оставить заявку</h2>
<form method="post">
{% csrf_token %}
{{ form.as_div }}
<button type="submit">Отправить</button>
</form>
{% endblock %}
Пояснения к шаблонам:
- Шаблоны приложения лежат в
catalog/templates/catalog/— второй уровеньcatalogзащищает от конфликта имён, если в другом приложении тоже окажетсяproduct_list.html. - Фильтр
floatformat:"0g"округляет цену до рублей и добавляет разделитель разрядов по правилам языка сайта: 25000 выводится как «25 000 ₽». {% csrf_token %}обязателен в каждой POST-форме: без него Django отклонит отправку с ошибкой 403.{% if product.image %}нужен, потому что фото необязательно: обращение к.urlу пустого поля вызываетValueError.
Наследование, фильтры и теги подробно описаны в статье «Шаблоны Django».
Подключите статику: CSS и картинки оформления
Файлы оформления положите в папку static/ в корне проекта, которую мы указали в STATICFILES_DIRS:
/* static/css/style.css */
body {
margin: 0;
font-family: system-ui, sans-serif;
color: #222;
}
.header,
.footer {
padding: 16px 24px;
background: #0c4b33;
color: #fff;
}
.header a {
color: #fff;
text-decoration: none;
font-weight: 700;
}
.container {
max-width: 960px;
margin: 0 auto;
padding: 24px;
}
.categories a {
margin-right: 12px;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 16px;
}
.card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 16px;
}
.card img,
.photo {
max-width: 100%;
border-radius: 6px;
}
.price {
font-weight: 700;
}
.message {
padding: 12px;
background: #e6f4ea;
border-radius: 6px;
}
.errorlist {
color: #b00020;
}
В шаблоне файл подключается тегом {% static 'css/style.css' %} после {% load static %} в начале шаблона. Тег строит адрес из STATIC_URL, поэтому при переносе статики на CDN шаблоны менять не придётся. Логотип и иконки кладите туда же, например в static/img/, и подключайте так же.
Для разработки этого достаточно: при DEBUG = True сервер runserver сам находит статику в STATICFILES_DIRS и в папках static/ приложений. Готовые CSS-фреймворки вроде Bootstrap подключаются через CDN-ссылку в base.html или файлом в static/.
Добавьте форму заявки
Форма проверяет данные посетителя и сохраняет заявку в базу. ModelForm строит поля по модели, поэтому описывать их второй раз не нужно:
# catalog/forms.py
from django import forms
from .models import Inquiry
class InquiryForm(forms.ModelForm):
class Meta:
model = Inquiry
fields = ['name', 'phone', 'message']
widgets = {'message': forms.Textarea(attrs={'rows': 3})}
def clean_phone(self):
phone = self.cleaned_data['phone']
digits = ''.join(ch for ch in phone if ch.isdigit())
if len(digits) < 10:
raise forms.ValidationError('Укажите телефон полностью, с кодом.')
return phone
fieldsперечисляет только поля, которые заполняет посетитель. Товар view подставляет сам черезform.save(commit=False), поэтому подменить его из браузера нельзя.- Метод
clean_phone()— своя проверка поля. Если она не прошла, форма вернётся на страницу с текстом ошибки под полем, а введённые данные сохранятся. - Сообщение «Спасибо!» выводит фреймворк сообщений (
django.contrib.messages): view кладёт его перед редиректом, а цикл вbase.htmlпоказывает на следующей странице.
Запустите сервер, откройте карточку товара и отправьте заявку — она появится в админке в разделе «Заявки». Чтобы менеджер узнавал о заявках сразу, добавьте отправку письма или сообщения в мессенджер после inquiry.save(). Виджеты, валидацию нескольких полей и оформление форм разбирает статья «Формы в Django».
Проверьте сайт тестами
Пара тестов страхует от поломок при следующих изменениях. Django запускает их во временной базе, ваши данные не пострадают:
# catalog/tests.py
from django.test import TestCase
from django.urls import reverse
from .models import Category, Inquiry, Product
class CatalogTests(TestCase):
@classmethod
def setUpTestData(cls):
cls.category = Category.objects.create(name='Столы', slug='tables')
cls.product = Product.objects.create(
category=cls.category, name='Стол из дуба', slug='oak-table', price=25000,
)
def test_product_list(self):
response = self.client.get(reverse('catalog:product_list'))
self.assertContains(response, 'Стол из дуба')
def test_hidden_product_returns_404(self):
self.product.is_published = False
self.product.save()
response = self.client.get(self.product.get_absolute_url())
self.assertEqual(response.status_code, 404)
def test_inquiry_is_saved(self):
response = self.client.post(
self.product.get_absolute_url(),
{'name': 'Анна', 'phone': '+7 900 123-45-67', 'message': ''},
)
self.assertRedirects(response, self.product.get_absolute_url())
self.assertEqual(Inquiry.objects.get().product, self.product)
def test_short_phone_is_rejected(self):
response = self.client.post(
self.product.get_absolute_url(), {'name': 'Анна', 'phone': '123'},
)
self.assertContains(response, 'Укажите телефон полностью')
self.assertFalse(Inquiry.objects.exists())
python manage.py test
Found 4 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
....
----------------------------------------------------------------------
Ran 4 tests in 0.083s
OK
Destroying test database for alias 'default'...
Подготовьте сайт к деплою
runserver предназначен только для разработки. Перед публикацией сайта в интернете нужно вынести секреты из кода, выключить режим отладки и собрать статику.
Вынесите настройки в переменные окружения
Ключ шифрования, режим отладки и список доменов отличаются на вашем компьютере и на сервере, поэтому их читают из переменных окружения:
# config/settings.py
import os
from pathlib import Path
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'django-insecure-dev-only')
DEBUG = os.environ.get('DJANGO_DEBUG') == '1'
ALLOWED_HOSTS = os.environ.get('DJANGO_ALLOWED_HOSTS', '127.0.0.1,localhost').split(',')
Теперь режим отладки по умолчанию выключен — случайно оставить его включённым на сервере не получится. Если забыть задать DJANGO_SECRET_KEY, check --deploy предупредит о небезопасном ключе (security.W009). Локально включайте его перед запуском сервера:
# Windows (PowerShell)
$env:DJANGO_DEBUG = "1"
python manage.py runserver
# macOS и Linux
export DJANGO_DEBUG=1
python manage.py runserver
На сервере задайте DJANGO_SECRET_KEY (длинная случайная строка, например из python -c "import secrets; print(secrets.token_urlsafe(50))") и DJANGO_ALLOWED_HOSTS с вашим доменом.
Зафиксируйте зависимости и соберите статику
python -m pip freeze > requirements.txt
python manage.py collectstatic --noinput
131 static files copied to '/home/user/mysite/staticfiles'.
collectstatic копирует в STATIC_ROOT ваш CSS и стили админки. Если после деплоя админка открылась без оформления, обычно забыт именно этот шаг. В .gitignore добавьте .venv/, db.sqlite3, media/ и staticfiles/: окружение, база и загруженные файлы в Git не хранятся.
Проверьте настройки безопасности
python manage.py check --deploy
Команда перечислит, что ещё нужно включить для работы по HTTPS: SECURE_SSL_REDIRECT, SESSION_COOKIE_SECURE, CSRF_COOKIE_SECURE, SECURE_HSTS_SECONDS. В проекте на Django 6.1 она также выдаёт ошибку mail.E001: шаблон settings.py настраивает консольный почтовый бэкенд, который печатает письма в терминал вместо отправки.
Выберите, где разместить сайт
| Вариант | Что делаете вы | Кому подходит |
|---|---|---|
| VPS с Gunicorn и Nginx | настраиваете сервер, базу, HTTPS и обновления | нужен полный контроль, несколько сайтов |
| Docker на VPS или PaaS | пишете Dockerfile, платформа запускает контейнер | хотите одинаковое окружение везде |
| Хостинг с поддержкой Python | загружаете код и указываете WSGI-файл в панели | небольшой сайт, минимум администрирования |
На Linux-сервере сайт запускает сервер приложений Gunicorn, а Nginx принимает запросы из интернета, раздаёт staticfiles/ и media/ и обеспечивает HTTPS:
python -m pip install gunicorn
gunicorn config.wsgi:application --bind 127.0.0.1:8000
Gunicorn работает только на Linux и macOS; на Windows для проверки используйте runserver. Настройку systemd, Nginx, HTTPS и PostgreSQL по шагам описывает статья «Деплой Django: Gunicorn, Nginx и чек-лист продакшна».
Что добавить на сайт дальше
- Пользователи и личный кабинет — регистрация, вход,
@login_requiredдля закрытых страниц. Приложениеdjango.contrib.authуже подключено. - Поиск — поле
?q=и фильтрname__icontainsвproduct_list. - Статические страницы «О нас» и «Контакты» —
TemplateViewс отдельным шаблоном или модельPage, которую редактируют в админке. - SEO — уникальные
<title>иdescriptionчерез блоки шаблона,sitemap.xmlвстроенным приложениемdjango.contrib.sitemaps. - API для мобильного приложения или фронтенда на JavaScript — Django REST Framework.
- PostgreSQL вместо SQLite для продакшна с заметной нагрузкой и одновременной записью.
Частые ошибки при создании сайта
TemplateDoesNotExist: base.html
Django не нашёл шаблон. Проверьте, что в TEMPLATES указано 'DIRS': [BASE_DIR / 'templates'], папка templates лежит рядом с manage.py, а шаблоны приложения находятся в catalog/templates/catalog/ и приложение есть в INSTALLED_APPS.
CSS и фото не загружаются, в логе 404
При DEBUG = False Django не отдаёт ни статику, ни медиа. Локально включите DJANGO_DEBUG=1; на сервере раздавайте staticfiles/ и media/ через Nginx. Если стилей нет даже в режиме отладки — проверьте {% load static %} в начале шаблона и путь static/css/style.css.
The STATICFILES_DIRS setting should not contain the STATIC_ROOT setting
Ошибка staticfiles.E002 появляется, если STATIC_ROOT и STATICFILES_DIRS указывают на одну папку, например обе на static. Папка с исходниками и папка для collectstatic должны быть разными: static и staticfiles.
Cannot use ImageField because Pillow is not installed
Ошибка fields.E210 при makemigrations или runserver. Установите Pillow в активное виртуальное окружение: python -m pip install pillow.
403 Forbidden: CSRF verification failed
В форме нет {% csrf_token %}, либо сайт работает за HTTPS-прокси, а домен не указан в CSRF_TRUSTED_ORIGINS со схемой: https://example.ru.
NoReverseMatch: ‘catalog’ is not a registered namespace
Django не знает пространство имён из {% url 'catalog:...' %}. Проверьте app_name = 'catalog' в catalog/urls.py и подключение include('catalog.urls') в config/urls.py.
Частые вопросы
Сколько времени нужно, чтобы сделать сайт на Django?
Каталог из этого руководства собирается за один-два вечера, если вы знаете основы Python. Сайт с личным кабинетом, оплатой и интеграциями — недели работы: основное время уходит на бизнес-логику, вёрстку и тестирование, настройка самого Django занимает малую часть.
Нужно ли знать HTML, CSS и JavaScript?
HTML и базовый CSS нужны: шаблоны Django — это HTML с переменными и тегами. JavaScript для сайта с серверной отрисовкой необязателен. Интерактивность без отдельного фронтенда добавляют библиотекой HTMX, а готовую вёрстку — CSS-фреймворком Bootstrap или Tailwind.
Почему slug из русского названия получается пустым?
Функция django.utils.text.slugify() по умолчанию удаляет кириллицу: slugify('Стол из дуба') вернёт пустую строку. С параметром allow_unicode=True получится стол-из-дуба — адрес с кириллицей. Латинский slug из русского текста делает поле prepopulated_fields в админке или сторонние пакеты транслитерации, например python-slugify.
Django или WordPress (конструктор) для сайта?
Для сайта-визитки или блога без особой логики быстрее взять готовую CMS. Django выбирают, когда нужны свои модели данных, бизнес-логика, личные кабинеты, интеграции и API: всё это пишется кодом без ограничений плагинов. Если нужна именно CMS на Python с удобным редактором страниц, посмотрите Wagtail — он построен на Django.
Можно ли сделать сайт на Django без базы данных?
Технически да: оставьте DATABASES пустым и отключите приложения, которым нужна база, — админку, auth, сессии. Но тогда пропадают главные преимущества Django. Для чисто статического сайта удобнее генератор статики, а SQLite не требует установки и подходит даже для небольших сайтов в продакшне.
Как перенести сайт на другой компьютер или сервер?
Храните код в Git вместе с requirements.txt и миграциями. На новой машине создайте виртуальное окружение, выполните python -m pip install -r requirements.txt и python manage.py migrate. Данные из SQLite переносят копированием файла db.sqlite3 или командами dumpdata и loaddata, загруженные файлы — копированием папки media/.
Итог
Сайт на Django строится в одном порядке независимо от темы: виртуальное окружение и Django, проект и приложение, модели и миграции, админка для наполнения, view и маршруты, шаблоны с общим макетом, статика, формы, тесты и подготовка настроек к продакшну. Собранный каталог — рабочая основа: замените модели на посты, услуги или проекты, и получится блог, сайт компании или портфолио.
Каждый шаг подробнее раскрыт в статьях, на которые мы ссылались по ходу руководства, а общий маршрут изучения фреймворка — в обзоре Django для начинающих.
- #Django
- #Python
- #Веб-разработка
- #Для начинающих
- #Практика