Веб-приложение на Python Django: от идеи до реализации
Основы создания веб-приложения на Django
Эффективная разработка веб-приложения на Django начинается с создания виртуального окружения и установки фреймворка. После этого создается проект и приложение. Далее описываются модели, выполняются миграции, создаются представления и шаблоны. Ниже приведен минимальный пример.
python -m venv venv
source venv/bin/activate # Linux/Mac
venv\Scripts\activate # Windows
pip install django
django-admin startproject myproject
cd myproject
python manage.py startapp myapp
веб приложение python django (веб-приложение на django)
В файле models.py определяется модель:
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
pub_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
После создания модели выполняется команда для создания миграций и их применения:
python manage.py makemigrations
python manage.py migrate
В views.py определяется представление, которое возвращает список статей:
from django.shortcuts import render
from .models import Article
def article_list(request):
articles = Article.objects.all()
return render(request, 'myapp/article_list.html', {'articles': articles})
В urls.py приложения добавляется маршрут:
from django.urls import path
from . import views
app_name = 'myapp'
urlpatterns = [
path('', views.article_list, name='article_list'),
]
Подключаются URL-ы приложения в корневом urls.py:
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('myapp.urls')),
]
Создается шаблон templates/myapp/article_list.html:
<!DOCTYPE html>
<html>
<head><title>Статьи</title></head>
<body>
<h1>Список статей</h1>
{% for article in articles %}
<h2>{{ article.title }}</h2>
<p>{{ article.content|truncatewords:30 }}</p>
{% empty %}
<p>Нет статей.</p>
{% endfor %}
</body>
</html>
После запуска сервера (python manage.py runserver) приложение становится доступным по адресу http://127.0.0.1:8000. Данный подход подходит для простых проектов, где требуется быстрое прототипирование.
Как выбрать между функциональными и классовыми представлениями?
Функциональные представления (FBV) просты в написании и подходят для несложных страниц. Классовые представления (CBV) предоставляют готовые методы для стандартных действий (список, деталь, создание, обновление, удаление) и уменьшают дублирование кода.
Пример CBV для списка статей:
from django.views.generic import ListView
from .models import Article
class ArticleListView(ListView):
model = Article
template_name = 'myapp/article_list.html'
context_object_name = 'articles'
Цель использования: CBV рекомендуется применять, когда логика представления повторяет стандартные паттерны (отображение списка, деталей, форм). Если требуется нестандартная обработка запроса, FBV даёт больше гибкости.
Типичные ошибки: в CBV часто забывают указать template_name или context_object_name, что приводит к использованию шаблонов по умолчанию, которые могут отсутствовать. В FBV разработчики иногда дублируют код в разных представлениях.
Проблема: при переопределении методов CBV (например, get_queryset) можно случайно нарушить работу пагинации. Решение: всегда вызывать родительский метод через super().
Как создать RESTful API на основе моделей Django?
Для создания API используется Django REST Framework (DRF). Он предоставляет сериализаторы, классы представлений и маршрутизацию.
# serializers.py
from rest_framework import serializers
from .models import Article
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = ['id', 'title', 'content', 'pub_date']
# views.py
from rest_framework import generics
from .models import Article
from .serializers import ArticleSerializer
class ArticleListCreate(generics.ListCreateAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path('api/articles/', views.ArticleListCreate.as_view()),
]
После добавления rest_framework в INSTALLED_APPS и запуска сервера API становится доступным. Для полного CRUD можно использовать ViewSet и Router:
# views.py
from rest_framework import viewsets
from .models import Article
from .serializers import ArticleSerializer
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
# urls.py
from rest_framework.routers import DefaultRouter
from . import views
router = DefaultRouter()
router.register(r'articles', views.ArticleViewSet)
urlpatterns = router.urls
Цель использования: DRF подходит для создания бэкенда под мобильные приложения, SPA на React/Vue/Angular или для интеграции с внешними сервисами.
Ошибки: неверное указание queryset в представлении, отсутствие прав доступа (permissions), нехватка сериализаторов для вложенных объектов.
Проблема: при использовании ViewSet без аутентификации все операции доступны любому. Решение: добавить permission_classes в ViewSet, например IsAuthenticated.
Как настроить интерфейс администратора Django под свои нужды?
Django предоставляет мощную административную панель, которую можно кастомизировать с помощью класса ModelAdmin в файле admin.py.
from django.contrib import admin
from .models import Article
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ('title', 'pub_date', 'is_published')
list_filter = ('pub_date', 'is_published')
search_fields = ('title', 'content')
fieldsets = (
(None, {
'fields': ('title', 'content')
}),
('Дата и статус', {
'fields': ('pub_date', 'is_published'),
'classes': ('collapse',)
}),
)
Можно также добавлять встроенные модели (inlines) для связанных объектов.
class CommentInline(admin.TabularInline):
model = Comment
extra = 1
class ArticleAdmin(admin.ModelAdmin):
inlines = [CommentInline]
Цель использования: кастомизация админки полезна для удобного управления данными без написания отдельного интерфейса. Часто используется в проектах, где админка выступает основным интерфейсом для контент-менеджеров.
Ошибки: неправильно указанный model в декораторе или встроенной модели, отсутствие регистрации модели в админке.
Проблема: при добавлении встроенной модели может возникнуть ошибка, если не настроен ForeignKey на связанную модель. Решение: убедиться, что модель имеет внешний ключ на основную модель.
Как обрабатывать данные форм с кастомной валидацией?
Django предоставляет классы Form и ModelForm. Кастомная валидация реализуется через методы clean_
from django import forms
from .models import Article
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ['title', 'content', 'is_published']
def clean_title(self):
title = self.cleaned_data['title']
if len(title) < 10:
raise forms.ValidationError('Заголовок должен содержать не менее 10 символов')
return title
def clean(self):
cleaned_data = super().clean()
content = cleaned_data.get('content')
if content and 'spam' in content.lower():
raise forms.ValidationError('Контент не должен содержать слово spam')
Использование формы в представлении:
from django.shortcuts import render, redirect
from .forms import ArticleForm
def create_article(request):
if request.method == 'POST':
form = ArticleForm(request.POST)
if form.is_valid():
form.save()
return redirect('myapp:article_list')
else:
form = ArticleForm()
return render(request, 'myapp/article_form.html', {'form': form})
Цель использования: формы с кастомной валидацией необходимы, когда стандартные проверки (тип поля, обязательность) недостаточны. Подходит для любых сайтов с пользовательским вводом.
Ошибки: вызов raise forms.ValidationError без указания кода или с неправильным аргументом, забыв выполнить super().clean() при переопределении clean.
Проблема: сообщение об ошибке может не отображаться в шаблоне, если форма не передана в контекст или шаблон не содержит {{ form.errors }}. Решение: всегда рендерить {{ form.as_p }} или выводить ошибки явно.
Расширенные примеры кода с пошаговыми инструкциями
Ниже приведены дополнительные примеры, охватывающие менее распространенные, но полезные аспекты разработки на Django.
Пример 1: Использование сигналов для автоматического создания профиля пользователя
Сигналы позволяют выполнять код при определенных событиях модели. Создадим сигнал для создания профиля при регистрации нового пользователя.
# signals.py
from django.db.models.signals import post_save
from django.contrib.auth.models import User
from django.dispatch import receiver
from .models import Profile
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
# apps.py
from django.apps import AppConfig
class MyappConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'myapp'
def ready(self):
import myapp.signals
# __init__.py
# (можно оставить пустым, либо указать default_app_config)
Результат: при создании User автоматически создается связанный Profile.
Пример 2: Middleware для логирования запросов
Middleware обрабатывает запрос до и после передачи его представлению. Пример логирования времени выполнения запроса.
# middleware.py
import time
from django.utils.deprecation import MiddlewareMixin
class RequestTimingMiddleware(MiddlewareMixin):
def process_request(self, request):
request.start_time = time.time()
def process_response(self, request, response):
total = time.time() - request.start_time
print(f"Запрос к {request.path} выполнен за {total:.4f} сек")
return response
# settings.py
MIDDLEWARE = [
# ...
'myapp.middleware.RequestTimingMiddleware',
]
Результат: в консоль будет выводиться время обработки каждого запроса.
Пример 3: Тестирование представлений с помощью pytest-django
Напишем тест для проверки списка статей.
# requirements.txt (добавить pytest-django)
# tests/test_views.py
import pytest
from django.urls import reverse
from myapp.models import Article
@pytest.mark.django_db
def test_article_list_view(client):
Article.objects.create(title='Тестовая статья', content='Содержимое')
response = client.get(reverse('myapp:article_list'))
assert response.status_code == 200
assert 'Тестовая статья' in response.content.decode()
Результат: выполнение pytest покажет успешное прохождение теста.
Пример 4: Использование кастомного менеджера моделей
Менеджер позволяет инкапсулировать часто используемые запросы.
# managers.py
from django.db import models
class PublishedManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(is_published=True)
# models.py
class Article(models.Model):
# ...
is_published = models.BooleanField(default=False)
objects = models.Manager() # стандартный менеджер
published = PublishedManager() # кастомный
# Использование:
# Article.published.all() - только опубликованные
Результат: вызов Article.published.all() вернет только статьи с is_published=True.
Пример 5: Команда управления для импорта данных из CSV
Создадим кастомную команду import_articles.
# management/commands/import_articles.py
from django.core.management.base import BaseCommand
from myapp.models import Article
import csv
class Command(BaseCommand):
help = 'Импортирует статьи из CSV файла'
def add_arguments(self, parser):
parser.add_argument('csv_file', type=str, help='Путь к CSV файлу')
def handle(self, *args, **options):
with open(options['csv_file'], newline='', encoding='utf-8') as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
Article.objects.create(
title=row['title'],
content=row['content'],
is_published=row.get('is_published', 'True') == 'True'
)
self.stdout.write(self.style.SUCCESS('Импорт завершен'))
# Использование:
# python manage.py import_articles articles.csv
Результат: данные из CSV будут загружены в базу данных.
Пример 6: Использование кэширования для уменьшения нагрузки на БД
Кэширование часто запрашиваемых данных с помощью Django cache framework.
from django.core.cache import cache
from .models import Article
def get_article_list():
articles = cache.get('article_list')
if not articles:
articles = Article.objects.all()
cache.set('article_list', articles, timeout=300) # 5 минут
return articles
# В представлении:
# articles = get_article_list()
Результат: повторные запросы к списку статей будут браться из кэша до истечения таймаута.