← 返回首页
🎸

Django框架基础:MTV模式、Admin、ORM与模板

📂 python ⏱ 5 min 983 words

Django框架基础:MTV模式、Admin、ORM与模板

Django是Python最强大的全功能Web框架,遵循"自带电池"的设计理念。它提供了完善的ORM、Admin后台、模板系统等组件,让你能快速构建高质量的Web应用。本文将带你掌握Django的核心基础。

安装Django

pip install django
# 创建项目
django-admin startproject myproject
cd myproject
# 创建应用
python manage.py startapp myapp
# 启动开发服务器
python manage.py runserver

MTV架构模式

Django采用MTV(Model-Template-View)架构:

# 项目结构
"""
myproject/
├── manage.py              # 项目管理脚本
├── myproject/
│   ├── __init__.py
│   ├── settings.py        # 项目配置
│   ├── urls.py            # URL配置
│   ├── wsgi.py            # WSGI入口
│   └── asgi.py            # ASGI入口
└── myapp/
    ├── __init__.py
    ├── admin.py           # Admin配置
    ├── apps.py            # 应用配置
    ├── models.py          # 数据模型
    ├── views.py           # 视图
    ├── tests.py           # 测试
    ├── urls.py            # 应用URL
    └── templates/         # 模板目录
"""

Model定义

# myapp/models.py
from django.db import models
from django.contrib.auth.models import User

class Category(models.Model):
    name = models.CharField(max_length=100, unique=True)
    slug = models.SlugField(unique=True)
    description = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        verbose_name_plural = "categories"
        ordering = ["name"]
    
    def __str__(self):
        return self.name
    
    def get_absolute_url(self):
        return f"/categories/{self.slug}/"

class Article(models.Model):
    STATUS_CHOICES = [
        ("draft", "草稿"),
        ("published", "已发布"),
    ]
    
    title = models.CharField(max_length=200)
    slug = models.SlugField(unique=True)
    content = models.TextField()
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="articles")
    category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
    tags = models.ManyToManyField("Tag", blank=True)
    status = models.CharField(max_length=10, choices=STATUS_CHOICES, default="draft")
    views_count = models.PositiveIntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        ordering = ["-created_at"]
        indexes = [
            models.Index(fields=["-created_at"]),
            models.Index(fields=["status"]),
        ]
    
    def __str__(self):
        return self.title
    
    def increment_views(self):
        self.views_count += 1
        self.save(update_fields=["views_count"])

class Tag(models.Model):
    name = models.CharField(max_length=50, unique=True)
    
    def __str__(self):
        return self.name

ORM操作

from myapp.models import Article, Category, Tag
from django.db.models import Q, Count, Avg, Sum, F
from django.utils import timezone

# 创建对象
category = Category.objects.create(name="Python", slug="python")
article = Article.objects.create(
    title="Django入门",
    slug="django-intro",
    content="内容...",
    author=user,
    category=category,
    status="published"
)

# 查询对象
articles = Article.objects.all()  # 所有文章
published = Article.objects.filter(status="published")  # 过滤
article = Article.objects.get(id=1)  # 获取单个
article = Article.objects.filter(slug="django-intro").first()  # 安全获取

# 链式查询
results = Article.objects.filter(
    status="published",
    category__name="Python"
).order_by("-created_at")[:10]

# Q对象(复杂查询)
articles = Article.objects.filter(
    Q(title__icontains="django") | Q(content__icontains="python")
)

# 聚合查询
stats = Article.objects.filter(status="published").aggregate(
    total=Count("id"),
    avg_views=Avg("views_count"),
    total_views=Sum("views_count")
)

# 分组查询
from django.db.models import Count
category_stats = Category.objects.annotate(
    article_count=Count("article")
).order_by("-article_count")

# 更新对象
Article.objects.filter(status="draft").update(status="published")

# 使用F对象(原子更新)
Article.objects.filter(id=1).update(views_count=F("views_count") + 1)

# 删除对象
Article.objects.filter(status="draft").delete()

# 关联查询
# 正向查询
article = Article.objects.get(id=1)
print(article.category.name)

# 反向查询
category = Category.objects.get(id=1)
articles = category.article_set.all()  # 或使用related_name

Admin后台

# myapp/admin.py
from django.contrib import admin
from .models import Article, Category, Tag

@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
    list_display = ["name", "slug", "created_at"]
    prepopulated_fields = {"slug": ("name",)}
    search_fields = ["name"]

@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    list_display = ["title", "author", "category", "status", "created_at"]
    list_filter = ["status", "category", "created_at"]
    search_fields = ["title", "content"]
    prepopulated_fields = {"slug": ("title",)}
    raw_id_fields = ["author"]
    date_hierarchy = "created_at"
    list_editable = ["status"]
    list_per_page = 20
    
    def get_queryset(self, request):
        qs = super().get_queryset(request)
        if request.user.is_superuser:
            return qs
        return qs.filter(author=request.user)
    
    def save_model(self, request, obj, form, change):
        if not change:  # 新建时
            obj.author = request.user
        super().save_model(request, obj, form, change)

@admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
    list_display = ["name"]

# 创建超级用户
# python manage.py createsuperuser

URL配置

# myproject/urls.py
from django.contrib import admin
from django.urls import path, include

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

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

app_name = "myapp"  # 命名空间

urlpatterns = [
    path("", views.index, name="index"),
    path("articles/", views.article_list, name="article-list"),
    path("articles/<slug:slug>/", views.article_detail, name="article-detail"),
    path("articles/create/", views.article_create, name="article-create"),
    path("articles/<int:pk>/edit/", views.article_edit, name="article-edit"),
    path("articles/<int:pk>/delete/", views.article_delete, name="article-delete"),
    path("categories/<slug:slug>/", views.category_detail, name="category-detail"),
    path("api/", include("myapp.api_urls")),
]

View处理

# myapp/views.py
from django.shortcuts import render, get_object_or_404, redirect
from django.http import JsonResponse, HttpResponseForbidden
from django.contrib.auth.decorators import login_required
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from .models import Article, Category
from .forms import ArticleForm

# 函数视图
def index(request):
    latest_articles = Article.objects.filter(status="published")[:5]
    categories = Category.objects.all()
    return render(request, "myapp/index.html", {
        "latest_articles": latest_articles,
        "categories": categories
    })

def article_list(request):
    articles = Article.objects.filter(status="published")
    return render(request, "myapp/article_list.html", {"articles": articles})

def article_detail(request, slug):
    article = get_object_or_404(Article, slug=slug, status="published")
    article.increment_views()
    return render(request, "myapp/article_detail.html", {"article": article})

@login_required
def article_create(request):
    if request.method == "POST":
        form = ArticleForm(request.POST)
        if form.is_valid():
            article = form.save(commit=False)
            article.author = request.user
            article.save()
            return redirect("myapp:article-detail", slug=article.slug)
    else:
        form = ArticleForm()
    return render(request, "myapp/article_form.html", {"form": form})

# 类视图
class ArticleListView(ListView):
    model = Article
    template_name = "myapp/article_list.html"
    context_object_name = "articles"
    paginate_by = 10
    
    def get_queryset(self):
        return Article.objects.filter(status="published")

class ArticleDetailView(DetailView):
    model = Article
    template_name = "myapp/article_detail.html"
    
    def get_object(self):
        obj = super().get_object()
        obj.increment_views()
        return obj

class ArticleCreateView(LoginRequiredMixin, CreateView):
    model = Article
    form_class = ArticleForm
    template_name = "myapp/article_form.html"
    
    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)

class ArticleUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
    model = Article
    form_class = ArticleForm
    template_name = "myapp/article_form.html"
    
    def test_func(self):
        article = self.get_object()
        return self.request.user == article.author

class ArticleDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
    model = Article
    template_name = "myapp/article_confirm_delete.html"
    success_url = "/articles/"
    
    def test_func(self):
        article = self.get_object()
        return self.request.user == article.author

模板系统

<!-- templates/base.html -->
<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}我的博客{% endblock %}</title>
    <link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
    <nav>
        <a href="{% url 'myapp:index' %}">首页</a>
        <a href="{% url 'myapp:article-list' %}">文章</a>
        {% if user.is_authenticated %}
            <a href="{% url 'myapp:article-create' %}">写文章</a>
            <a href="{% url 'admin:index' %}">后台</a>
        {% endif %}
    </nav>
    
    <main>
        {% block content %}{% endblock %}
    </main>
    
    <footer>© 2024 我的博客</footer>
</body>
</html>

<!-- templates/myapp/article_list.html -->
{% extends "base.html" %}

{% block title %}文章列表{% endblock %}

{% block content %}
<h1>文章列表</h1>

{% for article in articles %}
<article>
    <h2><a href="{% url 'myapp:article-detail' slug=article.slug %}">{{ article.title }}</a></h2>
    <p class="meta">
        {{ article.author.username }} | {{ article.created_at|date:"Y-m-d" }} |
        {{ article.category.name }} | 阅读 {{ article.views_count }}
    </p>
    <p>{{ article.content|truncatewords:30 }}</p>
</article>
{% empty %}
<p>暂无文章</p>
{% endfor %}

<!-- 分页 -->
{% if is_paginated %}
<div class="pagination">
    {% if page_obj.has_previous %}
        <a href="?page={{ page_obj.previous_page_number }}">上一页</a>
    {% endif %}
    
    <span>第 {{ page_obj.number }} / {{ page_obj.paginator.num_pages }} 页</span>
    
    {% if page_obj.has_next %}
        <a href="?page={{ page_obj.next_page_number }}">下一页</a>
    {% endif %}
</div>
{% endif %}
{% endblock %}

<!-- 模板标签和过滤器 -->
{% load static %}
{% load myapp_tags %}

{{ article.title|upper }}
{{ article.content|truncatewords:50|linebreaks }}
{{ article.created_at|date:"Y年m月d日" }}
{{ article.views_count|default:"0" }}

{% if user.is_superuser %}
    <a href="{% url 'admin:myapp_article_change' article.id %}">编辑</a>
{% endif %}

表单

# myapp/forms.py
from django import forms
from .models import Article

class ArticleForm(forms.ModelForm):
    class Meta:
        model = Article
        fields = ["title", "slug", "content", "category", "tags", "status"]
        widgets = {
            "title": forms.TextInput(attrs={"class": "form-control"}),
            "content": forms.Textarea(attrs={"class": "form-control", "rows": 10}),
            "slug": forms.TextInput(attrs={"class": "form-control"}),
        }
    
    def clean_title(self):
        title = self.cleaned_data.get("title")
        if len(title) < 5:
            raise forms.ValidationError("标题至少需要5个字符")
        return title

实战示例:博客应用

# 完整的博客应用配置
# settings.py关键配置
INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "myapp",
]

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.sqlite3",
        "NAME": BASE_DIR / "db.sqlite3",
    }
}

LANGUAGE_CODE = "zh-hans"
TIME_ZONE = "Asia/Shanghai"
STATIC_URL = "/static/"
STATICFILES_DIRS = [BASE_DIR / "static"]
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"
LOGIN_URL = "/auth/login/"

总结

Django是一个功能完善的Web框架,适合构建大型复杂的Web应用。掌握MTV架构、Model定义、ORM操作、Admin配置、视图和模板系统后,你就能构建专业的Web应用。建议进一步学习Django REST Framework来构建API,以及Django的认证、权限和中间件系统。