Skip to content

Django视图和URL配置

本章将详细介绍Django的视图系统和URL配置,包括函数视图、类视图、URL路由、参数传递等核心概念,帮助你构建动态的Web应用程序。

视图基础

什么是视图

视图是Django应用程序中的"逻辑"部分。视图接收Web请求并返回Web响应。响应可以是HTML页面、重定向、404错误、XML文档、图片等。

python
# 最简单的视图
from django.http import HttpResponse

def hello_world(request):
    return HttpResponse("Hello, World!")

# 带参数的视图
def hello_name(request, name):
    return HttpResponse(f"Hello, {name}!")

# 返回HTML的视图
def home_page(request):
    html = """
    <html>
    <head><title>我的网站</title></head>
    <body>
        <h1>欢迎来到我的网站</h1>
        <p>这是用Django创建的页面</p>
    </body>
    </html>
    """
    return HttpResponse(html)

HttpRequest对象

python
# views.py
from django.http import HttpResponse
import json

def request_info(request):
    """显示请求信息的视图"""
    info = {
        'method': request.method,
        'path': request.path,
        'full_path': request.get_full_path(),
        'host': request.get_host(),
        'is_secure': request.is_secure(),
        'user': str(request.user),
        'session_key': request.session.session_key,
    }
    
    # GET参数
    if request.GET:
        info['GET_params'] = dict(request.GET)
    
    # POST参数
    if request.POST:
        info['POST_params'] = dict(request.POST)
    
    # HTTP头信息
    info['headers'] = {
        'User-Agent': request.META.get('HTTP_USER_AGENT', ''),
        'Accept': request.META.get('HTTP_ACCEPT', ''),
        'Accept-Language': request.META.get('HTTP_ACCEPT_LANGUAGE', ''),
        'Remote-Addr': request.META.get('REMOTE_ADDR', ''),
    }
    
    # 返回JSON响应
    return HttpResponse(
        json.dumps(info, indent=2, ensure_ascii=False),
        content_type='application/json; charset=utf-8'
    )

def handle_different_methods(request):
    """处理不同HTTP方法的视图"""
    if request.method == 'GET':
        return HttpResponse("这是GET请求")
    elif request.method == 'POST':
        return HttpResponse("这是POST请求")
    elif request.method == 'PUT':
        return HttpResponse("这是PUT请求")
    elif request.method == 'DELETE':
        return HttpResponse("这是DELETE请求")
    else:
        return HttpResponse(f"不支持的方法: {request.method}", status=405)

HttpResponse对象

python
from django.http import (
    HttpResponse, JsonResponse, HttpResponseRedirect,
    HttpResponseNotFound, HttpResponseForbidden,
    HttpResponseServerError, FileResponse
)
from django.shortcuts import redirect
import json

def different_responses(request):
    """演示不同类型的响应"""
    response_type = request.GET.get('type', 'html')
    
    if response_type == 'html':
        response = HttpResponse("<h1>HTML响应</h1>")
        response['Content-Type'] = 'text/html; charset=utf-8'
        return response
    
    elif response_type == 'json':
        data = {'message': 'JSON响应', 'status': 'success'}
        return JsonResponse(data)
    
    elif response_type == 'text':
        return HttpResponse("纯文本响应", content_type='text/plain')
    
    elif response_type == 'redirect':
        return HttpResponseRedirect('/success/')
        # 或使用快捷方式
        # return redirect('success_page')
    
    elif response_type == 'error':
        return HttpResponseNotFound("页面未找到")
    
    elif response_type == 'custom':
        response = HttpResponse("自定义响应")
        response.status_code = 201
        response['X-Custom-Header'] = 'Custom Value'
        response['Cache-Control'] = 'no-cache'
        return response
    
    else:
        return HttpResponse("未知响应类型")

def download_file(request):
    """文件下载响应"""
    file_path = '/path/to/your/file.pdf'
    
    try:
        response = FileResponse(
            open(file_path, 'rb'),
            content_type='application/pdf'
        )
        response['Content-Disposition'] = 'attachment; filename="document.pdf"'
        return response
    except FileNotFoundError:
        return HttpResponseNotFound("文件未找到")

def set_cookie_view(request):
    """设置Cookie的视图"""
    response = HttpResponse("Cookie已设置")
    response.set_cookie('username', 'john_doe', max_age=3600)  # 1小时过期
    response.set_cookie('theme', 'dark', secure=True, httponly=True)
    return response

def get_cookie_view(request):
    """获取Cookie的视图"""
    username = request.COOKIES.get('username', '未知用户')
    theme = request.COOKIES.get('theme', '默认主题')
    
    return HttpResponse(f"用户: {username}, 主题: {theme}")

URL配置详解

URL模式匹配

python
# urls.py
from django.urls import path, re_path, include
from . import views

urlpatterns = [
    # 基本路径匹配
    path('', views.home, name='home'),
    path('about/', views.about, name='about'),
    
    # 带参数的路径
    path('articles/<int:id>/', views.article_detail, name='article_detail'),
    path('articles/<slug:slug>/', views.article_by_slug, name='article_by_slug'),
    path('users/<str:username>/', views.user_profile, name='user_profile'),
    
    # 可选参数
    path('blog/', views.blog_list, name='blog_list'),
    path('blog/page/<int:page>/', views.blog_list, name='blog_list_page'),
    
    # 正则表达式路径
    re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive, name='year_archive'),
    re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
    
    # 包含其他URL配置
    path('api/', include('api.urls')),
    path('admin/', include('admin.urls')),
]

# 对应的视图函数
def article_detail(request, id):
    return HttpResponse(f"文章ID: {id}")

def article_by_slug(request, slug):
    return HttpResponse(f"文章Slug: {slug}")

def user_profile(request, username):
    return HttpResponse(f"用户: {username}")

def blog_list(request, page=1):
    return HttpResponse(f"博客列表 - 第{page}页")

def year_archive(request, year):
    return HttpResponse(f"{year}年的文章")

def month_archive(request, year, month):
    return HttpResponse(f"{year}{month}月的文章")

URL命名和反向解析

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

app_name = 'blog'  # 应用命名空间

urlpatterns = [
    path('', views.PostListView.as_view(), name='post_list'),
    path('post/<int:pk>/', views.PostDetailView.as_view(), name='post_detail'),
    path('post/create/', views.PostCreateView.as_view(), name='post_create'),
    path('post/<int:pk>/edit/', views.PostUpdateView.as_view(), name='post_edit'),
    path('post/<int:pk>/delete/', views.PostDeleteView.as_view(), name='post_delete'),
    path('category/<slug:slug>/', views.CategoryView.as_view(), name='category'),
]
python
# 在视图中使用URL反向解析
from django.urls import reverse
from django.http import HttpResponseRedirect

def create_post(request):
    if request.method == 'POST':
        # 处理表单数据
        # ...
        
        # 重定向到文章详情页
        return HttpResponseRedirect(reverse('blog:post_detail', args=[post.id]))
        
        # 或使用kwargs
        return HttpResponseRedirect(reverse('blog:post_detail', kwargs={'pk': post.id}))

# 使用redirect快捷方式
from django.shortcuts import redirect

def create_post_shortcut(request):
    if request.method == 'POST':
        # 处理表单数据
        # ...
        
        # 直接重定向
        return redirect('blog:post_detail', pk=post.id)
        
        # 重定向到外部URL
        return redirect('https://www.example.com/')
html
<!-- 在模板中使用URL反向解析 -->
<a href="{% url 'blog:post_list' %}">文章列表</a>
<a href="{% url 'blog:post_detail' post.id %}">{{ post.title }}</a>
<a href="{% url 'blog:category' category.slug %}">{{ category.name }}</a>

<!-- 带查询参数 -->
<a href="{% url 'blog:post_list' %}?page=2&category=tech">技术文章第2页</a>

URL参数和查询字符串

python
# views.py
def search_view(request):
    """处理搜索请求"""
    # 获取查询参数
    query = request.GET.get('q', '')
    category = request.GET.get('category', 'all')
    page = request.GET.get('page', 1)
    
    # 获取多个值
    tags = request.GET.getlist('tags')  # ?tags=python&tags=django
    
    # 处理搜索逻辑
    results = []
    if query:
        # 执行搜索
        results = search_articles(query, category, tags)
    
    context = {
        'query': query,
        'category': category,
        'page': int(page),
        'tags': tags,
        'results': results,
    }
    
    return render(request, 'search_results.html', context)

def article_archive(request, year, month=None, day=None):
    """文章归档视图"""
    from datetime import datetime
    
    # 构建日期过滤条件
    date_filter = {'created_at__year': year}
    
    if month:
        date_filter['created_at__month'] = month
    
    if day:
        date_filter['created_at__day'] = day
    
    # 查询文章
    articles = Article.objects.filter(**date_filter)
    
    # 构建面包屑导航
    breadcrumbs = [{'name': '首页', 'url': '/'}]
    breadcrumbs.append({'name': f'{year}年', 'url': f'/archive/{year}/'})
    
    if month:
        breadcrumbs.append({'name': f'{month}月', 'url': f'/archive/{year}/{month}/'})
    
    if day:
        breadcrumbs.append({'name': f'{day}日', 'url': f'/archive/{year}/{month}/{day}/'})
    
    context = {
        'articles': articles,
        'year': year,
        'month': month,
        'day': day,
        'breadcrumbs': breadcrumbs,
    }
    
    return render(request, 'archive.html', context)

函数视图详解

基本函数视图

python
# views.py
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse, Http404
from .models import Article, Category

def article_list(request):
    """文章列表视图"""
    # 获取所有已发布的文章
    articles = Article.objects.filter(published=True).order_by('-created_at')
    
    # 分页处理
    from django.core.paginator import Paginator
    paginator = Paginator(articles, 10)  # 每页10篇文章
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)
    
    context = {
        'articles': page_obj,
        'page_obj': page_obj,
    }
    
    return render(request, 'blog/article_list.html', context)

def article_detail(request, id):
    """文章详情视图"""
    # 获取文章或返回404
    article = get_object_or_404(Article, id=id, published=True)
    
    # 增加浏览次数
    article.views += 1
    article.save(update_fields=['views'])
    
    # 获取相关文章
    related_articles = Article.objects.filter(
        category=article.category,
        published=True
    ).exclude(id=article.id)[:5]
    
    context = {
        'article': article,
        'related_articles': related_articles,
    }
    
    return render(request, 'blog/article_detail.html', context)

def category_view(request, slug):
    """分类视图"""
    category = get_object_or_404(Category, slug=slug)
    articles = Article.objects.filter(
        category=category,
        published=True
    ).order_by('-created_at')
    
    context = {
        'category': category,
        'articles': articles,
    }
    
    return render(request, 'blog/category.html', context)

处理表单的视图

python
from django.shortcuts import render, redirect
from django.contrib import messages
from .forms import ContactForm, ArticleForm

def contact_view(request):
    """联系表单视图"""
    if request.method == 'POST':
        form = ContactForm(request.POST)
        if form.is_valid():
            # 处理表单数据
            name = form.cleaned_data['name']
            email = form.cleaned_data['email']
            message = form.cleaned_data['message']
            
            # 发送邮件或保存到数据库
            send_contact_email(name, email, message)
            
            # 显示成功消息
            messages.success(request, '您的消息已发送成功!')
            
            # 重定向避免重复提交
            return redirect('contact_success')
    else:
        form = ContactForm()
    
    return render(request, 'contact.html', {'form': form})

def create_article(request):
    """创建文章视图"""
    if request.method == 'POST':
        form = ArticleForm(request.POST, request.FILES)
        if form.is_valid():
            article = form.save(commit=False)
            article.author = request.user
            article.save()
            
            messages.success(request, '文章创建成功!')
            return redirect('blog:article_detail', id=article.id)
    else:
        form = ArticleForm()
    
    return render(request, 'blog/create_article.html', {'form': form})

def edit_article(request, id):
    """编辑文章视图"""
    article = get_object_or_404(Article, id=id)
    
    # 权限检查
    if article.author != request.user:
        messages.error(request, '您没有权限编辑这篇文章')
        return redirect('blog:article_detail', id=article.id)
    
    if request.method == 'POST':
        form = ArticleForm(request.POST, request.FILES, instance=article)
        if form.is_valid():
            form.save()
            messages.success(request, '文章更新成功!')
            return redirect('blog:article_detail', id=article.id)
    else:
        form = ArticleForm(instance=article)
    
    context = {
        'form': form,
        'article': article,
    }
    
    return render(request, 'blog/edit_article.html', context)

视图装饰器

内置装饰器

python
from django.views.decorators.http import require_http_methods, require_POST, require_GET
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.cache import cache_page
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.admin.views.decorators import staff_member_required

# HTTP方法限制
@require_http_methods(["GET", "POST"])
def my_view(request):
    if request.method == 'GET':
        return render(request, 'form.html')
    else:  # POST
        # 处理表单提交
        return redirect('success')

@require_POST
def delete_article(request, id):
    """只允许POST请求删除文章"""
    article = get_object_or_404(Article, id=id)
    
    # 权限检查
    if article.author != request.user:
        return HttpResponseForbidden("没有权限删除")
    
    article.delete()
    messages.success(request, '文章已删除')
    return redirect('blog:article_list')

# 登录要求
@login_required
def profile_view(request):
    """需要登录才能访问的视图"""
    return render(request, 'profile.html', {'user': request.user})

# 权限要求
@permission_required('blog.add_article')
def create_article_view(request):
    """需要特定权限才能创建文章"""
    # 创建文章的逻辑
    pass

# 管理员要求
@staff_member_required
def admin_dashboard(request):
    """只有管理员可以访问"""
    return render(request, 'admin_dashboard.html')

# 缓存装饰器
@cache_page(60 * 15)  # 缓存15分钟
def cached_view(request):
    """缓存的视图"""
    # 执行一些耗时操作
    expensive_data = perform_expensive_operation()
    return render(request, 'cached_template.html', {'data': expensive_data})

# CSRF豁免(谨慎使用)
@csrf_exempt
def api_endpoint(request):
    """API端点,豁免CSRF检查"""
    if request.method == 'POST':
        import json
        data = json.loads(request.body)
        # 处理API请求
        return JsonResponse({'status': 'success'})
    
    return JsonResponse({'error': 'Method not allowed'}, status=405)

自定义装饰器

python
from functools import wraps
from django.http import HttpResponseForbidden
from django.shortcuts import redirect
import time

def timing_decorator(func):
    """计时装饰器"""
    @wraps(func)
    def wrapper(request, *args, **kwargs):
        start_time = time.time()
        response = func(request, *args, **kwargs)
        end_time = time.time()
        
        # 添加执行时间到响应头
        response['X-Execution-Time'] = f"{end_time - start_time:.3f}s"
        return response
    return wrapper

def owner_required(model_class, pk_param='pk'):
    """要求用户是对象的所有者"""
    def decorator(func):
        @wraps(func)
        def wrapper(request, *args, **kwargs):
            if not request.user.is_authenticated:
                return redirect('login')
            
            # 获取对象ID
            object_id = kwargs.get(pk_param)
            if not object_id:
                return HttpResponseForbidden("缺少对象ID")
            
            # 检查所有权
            try:
                obj = model_class.objects.get(pk=object_id)
                if obj.author != request.user:
                    return HttpResponseForbidden("您不是该对象的所有者")
            except model_class.DoesNotExist:
                raise Http404("对象不存在")
            
            return func(request, *args, **kwargs)
        return wrapper
    return decorator

def rate_limit(max_requests=10, window=60):
    """简单的速率限制装饰器"""
    def decorator(func):
        @wraps(func)
        def wrapper(request, *args, **kwargs):
            from django.core.cache import cache
            
            # 获取客户端IP
            ip = request.META.get('REMOTE_ADDR')
            cache_key = f"rate_limit_{ip}_{func.__name__}"
            
            # 检查请求次数
            current_requests = cache.get(cache_key, 0)
            if current_requests >= max_requests:
                return HttpResponse("请求过于频繁,请稍后再试", status=429)
            
            # 增加请求计数
            cache.set(cache_key, current_requests + 1, window)
            
            return func(request, *args, **kwargs)
        return wrapper
    return decorator

# 使用自定义装饰器
@timing_decorator
@login_required
@owner_required(Article, 'id')
def edit_my_article(request, id):
    """编辑我的文章"""
    article = get_object_or_404(Article, id=id)
    # 编辑逻辑
    return render(request, 'edit_article.html', {'article': article})

@rate_limit(max_requests=5, window=300)  # 5分钟内最多5次请求
def api_search(request):
    """搜索API"""
    query = request.GET.get('q', '')
    results = perform_search(query)
    return JsonResponse({'results': results})

实践示例

示例1:博客系统视图

python
# models.py
from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse

class Category(models.Model):
    name = models.CharField(max_length=100)
    slug = models.SlugField(unique=True)
    description = models.TextField(blank=True)
    
    def __str__(self):
        return self.name
    
    def get_absolute_url(self):
        return reverse('blog:category', kwargs={'slug': self.slug})

class Article(models.Model):
    title = models.CharField(max_length=200)
    slug = models.SlugField(unique=True)
    content = models.TextField()
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    published = models.BooleanField(default=False)
    views = models.PositiveIntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        ordering = ['-created_at']
    
    def __str__(self):
        return self.title
    
    def get_absolute_url(self):
        return reverse('blog:article_detail', kwargs={'slug': self.slug})

# views.py
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.core.paginator import Paginator
from django.db.models import Q
from .models import Article, Category

def home_view(request):
    """首页视图"""
    # 获取最新文章
    latest_articles = Article.objects.filter(published=True)[:5]
    
    # 获取热门文章
    popular_articles = Article.objects.filter(published=True).order_by('-views')[:5]
    
    # 获取所有分类
    categories = Category.objects.all()
    
    context = {
        'latest_articles': latest_articles,
        'popular_articles': popular_articles,
        'categories': categories,
    }
    
    return render(request, 'blog/home.html', context)

def article_list_view(request):
    """文章列表视图"""
    articles = Article.objects.filter(published=True)
    
    # 分类过滤
    category_slug = request.GET.get('category')
    if category_slug:
        articles = articles.filter(category__slug=category_slug)
    
    # 搜索功能
    search_query = request.GET.get('search')
    if search_query:
        articles = articles.filter(
            Q(title__icontains=search_query) |
            Q(content__icontains=search_query)
        )
    
    # 分页
    paginator = Paginator(articles, 12)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)
    
    # 获取所有分类用于过滤器
    categories = Category.objects.all()
    
    context = {
        'page_obj': page_obj,
        'categories': categories,
        'current_category': category_slug,
        'search_query': search_query,
    }
    
    return render(request, 'blog/article_list.html', context)

def article_detail_view(request, slug):
    """文章详情视图"""
    article = get_object_or_404(Article, slug=slug, published=True)
    
    # 增加浏览次数
    article.views += 1
    article.save(update_fields=['views'])
    
    # 获取上一篇和下一篇文章
    prev_article = Article.objects.filter(
        published=True,
        created_at__lt=article.created_at
    ).order_by('-created_at').first()
    
    next_article = Article.objects.filter(
        published=True,
        created_at__gt=article.created_at
    ).order_by('created_at').first()
    
    # 获取同分类的相关文章
    related_articles = Article.objects.filter(
        category=article.category,
        published=True
    ).exclude(id=article.id)[:4]
    
    context = {
        'article': article,
        'prev_article': prev_article,
        'next_article': next_article,
        'related_articles': related_articles,
    }
    
    return render(request, 'blog/article_detail.html', context)

@login_required
def create_article_view(request):
    """创建文章视图"""
    if request.method == 'POST':
        form = ArticleForm(request.POST, request.FILES)
        if form.is_valid():
            article = form.save(commit=False)
            article.author = request.user
            article.save()
            
            messages.success(request, '文章创建成功!')
            return redirect('blog:article_detail', slug=article.slug)
    else:
        form = ArticleForm()
    
    return render(request, 'blog/create_article.html', {'form': form})

def search_view(request):
    """搜索视图"""
    query = request.GET.get('q', '').strip()
    results = []
    
    if query:
        # 搜索文章标题和内容
        results = Article.objects.filter(
            Q(title__icontains=query) |
            Q(content__icontains=query),
            published=True
        ).order_by('-created_at')
        
        # 高亮搜索关键词(简单实现)
        for article in results:
            article.highlighted_title = article.title.replace(
                query, f'<mark>{query}</mark>'
            )
    
    # 分页
    paginator = Paginator(results, 10)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)
    
    context = {
        'query': query,
        'page_obj': page_obj,
        'total_results': results.count() if results else 0,
    }
    
    return render(request, 'blog/search_results.html', context)

本章小结

本章详细介绍了Django的视图和URL配置:

关键要点:

  • 视图概念:处理HTTP请求并返回响应的Python函数
  • HttpRequest对象:包含请求信息的对象
  • HttpResponse对象:各种类型的HTTP响应
  • URL配置:将URL模式映射到视图函数
  • URL参数:从URL中提取参数传递给视图

重要概念:

  • MVT模式:Model-View-Template架构
  • URL反向解析:通过名称生成URL
  • 视图装饰器:为视图添加额外功能
  • 请求方法处理:GET、POST等不同方法的处理

最佳实践:

  • 使用有意义的URL模式和命名
  • 合理使用装饰器增强视图功能
  • 正确处理不同的HTTP方法
  • 使用快捷函数简化常见操作
  • 实现适当的错误处理和权限检查

在下一章中,我们将学习Django的模板系统,了解如何创建动态的HTML页面。

延伸阅读

本站内容仅供学习和研究使用。