← Назад к вопросам

Как использовал MVC в Django?

2.0 Middle🔥 231 комментариев
#Django

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Использование MVC паттерна в Django

Django реализует паттерн MTV (Model-Template-View) вместо классического MVC, но функционально это очень похоже. Разберёмся, как это работает и какие лучшие практики стоит применять.

1. Архитектура Django: MTV vs MVC

MVC (Model-View-Controller):

  • Model — бизнес-логика и данные
  • View — презентация (что видит пользователь)
  • Controller — обработка запроса и логика приложения

Django MTV (Model-Template-View):

  • Model — ORM модели и бизнес-логика (как в MVC Model)
  • Template — HTML с переменными (как в MVC View)
  • View — обработка HTTP запроса и логика (как в MVC Controller)

Джанго фактически реализует MVC, просто называет View и Controller по-другому.

2. Model — определение данных

Модели описывают структуру базы данных и бизнес-правила:

# models.py
from django.db import models
from django.utils import timezone

class User(models.Model):
    """Model: описание пользователя"""
    name = models.CharField(max_length=100)
    email = models.EmailField(unique=True)
    created_at = models.DateTimeField(auto_now_add=True)
    is_active = models.BooleanField(default=True)
    
    class Meta:
        ordering = ['-created_at']
    
    def __str__(self):
        return self.name
    
    # Бизнес-логика
    def deactivate(self):
        """Деактивировать пользователя"""
        self.is_active = False
        self.save()

class Post(models.Model):
    """Model: статья с автором"""
    title = models.CharField(max_length=200)
    content = models.TextField()
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
    published_at = models.DateTimeField(null=True, blank=True)
    
    def publish(self):
        """Опубликовать пост"""
        self.published_at = timezone.now()
        self.save()
    
    def is_published(self):
        return self.published_at is not None

3. Views (Controllers) — обработка запросов

Вьюхи — это функции или классы, которые обрабатывают HTTP запросы:

Функциональные представления (Function-Based Views):

# views.py
from django.shortcuts import render, get_object_or_404, redirect
from django.http import JsonResponse
from .models import User, Post

def user_list(request):
    """View: список пользователей (как Controller)"""
    users = User.objects.filter(is_active=True)
    return render(request, 'users/list.html', {'users': users})

def user_detail(request, user_id):
    """View: детали пользователя"""
    user = get_object_or_404(User, id=user_id)
    posts = user.posts.all()
    return render(request, 'users/detail.html', {
        'user': user,
        'posts': posts,
    })

def create_post(request):
    """View: создать новый пост"""
    if request.method == 'POST':
        title = request.POST['title']
        content = request.POST['content']
        author_id = request.POST['author_id']
        
        post = Post.objects.create(
            title=title,
            content=content,
            author_id=author_id,
        )
        return redirect('post_detail', post_id=post.id)
    
    return render(request, 'posts/create.html')

def publish_post(request, post_id):
    """View: опубликовать пост"""
    post = get_object_or_404(Post, id=post_id)
    
    # Проверка прав доступа
    if request.user != post.author:
        return JsonResponse({'error': 'Permission denied'}, status=403)
    
    post.publish()
    return JsonResponse({'status': 'published'})

Class-Based Views (современный подход):

from django.views import View
from django.views.generic import ListView, DetailView, CreateView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse_lazy

class UserListView(ListView):
    """View: список пользователей (класс-ориентированный)"""
    model = User
    template_name = 'users/list.html'
    context_object_name = 'users'
    paginate_by = 20
    
    def get_queryset(self):
        return User.objects.filter(is_active=True)

class UserDetailView(DetailView):
    """View: детали пользователя"""
    model = User
    template_name = 'users/detail.html'
    context_object_name = 'user'
    pk_url_kwarg = 'user_id'
    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['posts'] = self.object.posts.all()
        return context

class CreatePostView(LoginRequiredMixin, CreateView):
    """View: создать пост (только для авторизованных)"""
    model = Post
    fields = ['title', 'content']
    template_name = 'posts/create.html'
    success_url = reverse_lazy('post_list')
    
    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)

class PublishPostView(LoginRequiredMixin, View):
    """View: опубликовать пост (произвольная логика)"""
    def post(self, request, post_id):
        post = get_object_or_404(Post, id=post_id)
        
        if request.user != post.author:
            return JsonResponse({'error': 'Permission denied'}, status=403)
        
        post.publish()
        return JsonResponse({'status': 'published'})

4. Templates — представление данных

Темплейты отрисовывают HTML с данными из вьюхи:

<!-- templates/users/list.html -->
{% extends 'base.html' %}

{% block content %}
<h1>Users</h1>
<ul>
    {% for user in users %}
        <li>
            <a href="{% url 'user_detail' user.id %}">{{ user.name }}</a>
            <span class="email">{{ user.email }}</span>
        </li>
    {% endfor %}
</ul>
{% endblock %}

<!-- templates/users/detail.html -->
{% extends 'base.html' %}

{% block content %}
<h1>{{ user.name }}</h1>
<p>Email: {{ user.email }}</p>
<p>Posts: {{ user.posts.count }}</p>

<h2>Recent Posts</h2>
<ul>
    {% for post in posts %}
        <li>
            <a href="{% url 'post_detail' post.id %}">{{ post.title }}</a>
            {% if post.is_published %}
                <span class="published">Published</span>
            {% endif %}
        </li>
    {% endfor %}
</ul>
{% endblock %}

5. URLs — маршрутизация (Controller routing)

URLы связывают маршруты с вьюхами:

# urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('users/', views.UserListView.as_view(), name='user_list'),
    path('users/<int:user_id>/', views.UserDetailView.as_view(), name='user_detail'),
    path('posts/create/', views.CreatePostView.as_view(), name='create_post'),
    path('posts/<int:post_id>/publish/', views.PublishPostView.as_view(), name='publish_post'),
]

6. Лучшие практики MVC в Django

Разделение ответственности:

# ❌ Плохо: логика в темплейте
<h1>{{ user.name.upper() }}</h1>
{% if user.created_at|date:"Y" == "2024" %}
    <span>New user</span>
{% endif %}

# ✅ Хорошо: логика в модели или вьюхе
class User(models.Model):
    def is_new(self):
        return self.created_at.year == 2024

# В темплейте:
{% if user.is_new %}
    <span>New user</span>
{% endif %}

Переиспользование логики:

# ✅ Создавать менеджеры и querysets для переиспользования
class ActiveUserManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(is_active=True)

class User(models.Model):
    objects = models.Manager()  # default
    active = ActiveUserManager()

# Использование:
active_users = User.active.all()
all_users = User.objects.all()

Services для сложной логики:

# services.py
class UserService:
    @staticmethod
    def create_user_with_profile(name, email):
        """Создать пользователя с профилем (сложная операция)"""
        user = User.objects.create(name=name, email=email)
        UserProfile.objects.create(user=user)
        return user

# в views.py
from .services import UserService

class CreateUserView(CreateView):
    def form_valid(self, form):
        user = UserService.create_user_with_profile(
            name=form.cleaned_data['name'],
            email=form.cleaned_data['email'],
        )
        return redirect('user_detail', user_id=user.id)

Ключевые моменты

  • Django использует MTV (Model-Template-View), но это по сути MVC
  • Models — бизнес-логика и данные
  • Views — обработка запросов (контроллеры)
  • Templates — презентация данных
  • Используй Class-Based Views для типичной логики
  • Разделяй ответственность между моделями, вьюхами и темплейтами
  • Создавай менеджеры и services для переиспользуемой логики

Правильное применение MVC паттерна в Django делает код чистым, тестируемым и легко поддерживаемым.