1. Django模板语法深度解析作为全栈开发的核心环节Django模板系统承担着前后端数据交互的桥梁作用。我在实际项目中最常使用的模板语法可以归纳为三类基础结构1.1 变量渲染与过滤器变量渲染是模板中最基础的功能通过{{ variable }}语法实现。但很多人不知道的是Django默认开启了自动HTML转义机制这是防范XSS攻击的重要措施。当需要显示原生HTML时必须显式使用safe过滤器{{ user_input|safe }} !-- 慎用需确保内容可信 --常用的内置过滤器还有date:Y-m-d日期格式化truncatechars:50字符串截断filesizeformat文件大小人性化显示经验之谈在商业项目中我建议自定义过滤器而非在模板中写复杂逻辑。比如创建custom_filters.pyregister.filter def highlight_search(text, query): return text.replace(query, fspan classhighlight{query}/span)1.2 逻辑控制标签{% if %}和{% for %}是最常用的逻辑标签。需要特别注意判断相等时使用而非{% if user.role admin %}遍历查询集时添加{% empty %}处理{% for item in items %} {{ item.name }} {% empty %} p暂无数据/p {% endfor %}循环计数器技巧{% for obj in objs %} {% cycle row1 row2 as rowclass %} tr class{{ rowclass }}.../tr {% endfor %}1.3 模板继承体系合理的模板继承能减少70%的重复代码。我的标准做法是创建base.html作为根模板!DOCTYPE html html head title{% block title %}默认标题{% endblock %}/title {% block css %}{% endblock %} /head body {% block content %}{% endblock %} {% block js %}{% endblock %} /body /html子模板通过{% extends %}继承{% extends base.html %} {% block title %}子页面标题{% endblock %} {% block content %} h1自定义内容区/h1 {% endblock %}避坑指南避免继承链超过3层否则会显著增加维护成本。我曾接手过一个5层继承的项目调试模板耗时增加3倍。2. 请求处理机制剖析2.1 HttpRequest对象详解Django将HTTP请求封装为HttpRequest对象常用属性有属性类型说明methodstrGET/POST/PUT等GETQueryDictURL参数POSTQueryDict表单数据FILESMultiValueDict上传文件userUser认证用户pathstr不含域名的路径实际开发中几个实用技巧获取客户端IPrequest.META.get(REMOTE_ADDR)判断AJAX请求request.headers.get(X-Requested-With) XMLHttpRequest获取Content-Typerequest.content_type2.2 多场景参数获取GET参数处理def search(request): query request.GET.get(q, ) # 第二个参数为默认值 page int(request.GET.get(page, 1)) # 建议添加参数校验 if page 1: page 1POST表单处理def register(request): if request.method POST: username request.POST[username] # 直接访问可能引发KeyError password request.POST.get(password, ) # 更安全的做法 try: email request.POST[email] except KeyError: return HttpResponseBadRequest(Missing email)JSON数据处理import json def api_endpoint(request): if request.content_type application/json: try: data json.loads(request.body) param1 data.get(param1) except json.JSONDecodeError: return JsonResponse({error: Invalid JSON}, status400)2.3 文件上传实战文件上传需要特别注意表单必须设置enctypemultipart/form-data视图函数中通过request.FILES获取文件def upload_avatar(request): if request.method POST: file request.FILES.get(avatar) if not file: return HttpResponseBadRequest(No file uploaded) # 验证文件类型 allowed_types [image/jpeg, image/png] if file.content_type not in allowed_types: return HttpResponseBadRequest(Invalid file type) # 限制文件大小5MB if file.size 5*1024*1024: return HttpResponseBadRequest(File too large) # 保存文件 with open(favatars/{request.user.id}.jpg, wb) as f: for chunk in file.chunks(): f.write(chunk) return HttpResponse(Upload success)3. 响应生成高级技巧3.1 HttpResponse的灵活运用除了基础的字符串响应Django提供了多种响应类型from django.http import ( HttpResponse, JsonResponse, HttpResponseRedirect, HttpResponseNotFound ) # 普通文本响应 def text_response(request): return HttpResponse(Hello World, content_typetext/plain) # JSON响应两种方式 def json_response1(request): data {status: success, code: 200} return JsonResponse(data) def json_response2(request): data [apple, banana, orange] return HttpResponse( json.dumps(data), content_typeapplication/json ) # 重定向 def redirect_view(request): return HttpResponseRedirect(/new-url/) # 404响应 def not_found(request): return HttpResponseNotFound(h1Page not found/h1)3.2 流式响应优化性能对于大文件下载或实时生成的内容使用StreamingHttpResponse能显著降低内存占用from django.http import StreamingHttpResponse import csv def large_csv_export(request): def data_generator(): # 生成CSV头部 yield name,age,email\n # 分批生成数据 for i in range(100000): yield fuser{i},{20i%30},user{i}example.com\n response StreamingHttpResponse( data_generator(), content_typetext/csv ) response[Content-Disposition] attachment; filenameusers.csv return response3.3 自定义响应头实战通过response.headers可以设置各种HTTP头def set_headers(request): response HttpResponse() response[X-Custom-Header] MyValue response[Cache-Control] max-age3600 response.headers[X-API-Version] 1.0 # Django 3.2推荐方式 return response4. 常见问题排查手册4.1 模板渲染问题问题1变量未显示检查视图是否传递了该变量确认变量名拼写一致区分大小写使用{% debug %}标签查看所有可用变量问题2静态文件404确保STATIC_URL配置正确开发环境需要django.contrib.staticfiles生产环境需要运行collectstatic4.2 请求参数问题问题1POST数据为空检查表单是否有methodPOST确认enctype是否正确文件上传需要multipart/form-data查看中间件是否过滤了请求如CSRF保护问题2获取不到URL参数检查URLconf是否捕获了参数使用request.GET.dict()打印所有参数确认没有同名的GET/POST参数覆盖4.3 响应异常问题问题1中文乱码设置response.charset utf-8确保数据库和模板都是UTF-8编码JSON响应使用ensure_asciiFalsereturn JsonResponse(data, json_dumps_params{ensure_ascii: False})问题2下载文件损坏确认设置了正确的Content-Type二进制文件必须使用wb模式写入大文件使用StreamingHttpResponse5. 性能优化实践5.1 模板渲染优化启用模板缓存TEMPLATES [ { BACKEND: django.template.backends.django.DjangoTemplates, DIRS: [], APP_DIRS: True, OPTIONS: { context_processors: [...], loaders: [ (django.template.loaders.cached.Loader, [ django.template.loaders.filesystem.Loader, django.template.loaders.app_directories.Loader, ]), ], }, }, ]减少模板继承层级每增加一层继承会增加约15%的渲染时间慎用{% include %}频繁包含小模板会显著降低性能5.2 请求处理优化批量查询代替N1查询# 错误做法产生N1查询 books Book.objects.all() for book in books: print(book.author.name) # 每次循环都查询数据库 # 正确做法使用select_related books Book.objects.select_related(author).all()合理使用缓存from django.core.cache import cache def get_popular_articles(): articles cache.get(popular_articles) if not articles: articles Article.objects.filter(...)[:10] cache.set(popular_articles, articles, timeout3600) return articles异步任务处理 对于耗时操作如发送邮件、处理图片使用Celery等异步任务队列shared_task def process_uploaded_file(file_id): file UploadedFile.objects.get(idfile_id) # 耗时处理逻辑... # 视图中调用 process_uploaded_file.delay(new_file.id)6. 安全防护要点6.1 XSS防护自动转义机制Django模板默认开启HTML转义需要显示原始HTML时使用|safe过滤器或者标记内容为安全from django.utils.safestring import mark_safe message mark_safe(bSafe HTML/b)CSP策略 通过中间件设置内容安全策略MIDDLEWARE [csp.middleware.CSPMiddleware] CSP_DEFAULT_SRC (self,) CSP_SCRIPT_SRC (self, cdn.example.com)6.2 CSRF防护表单必须包含{% csrf_token %}form methodpost {% csrf_token %} !-- 表单内容 -- /formAJAX请求处理// 从cookie获取CSRF token function getCookie(name) { let cookieValue null; if (document.cookie document.cookie ! ) { const cookies document.cookie.split(;); for (let i 0; i cookies.length; i) { const cookie cookies[i].trim(); if (cookie.substring(0, name.length 1) (name )) { cookieValue decodeURIComponent( cookie.substring(name.length 1) ); break; } } } return cookieValue; } // 设置AJAX请求头 const csrftoken getCookie(csrftoken); fetch(/api/endpoint/, { method: POST, headers: { X-CSRFToken: csrftoken, Content-Type: application/json }, body: JSON.stringify(data) })6.3 SQL注入防护永远不要拼接SQL# 危险 query fSELECT * FROM users WHERE name {request.GET[name]} # 安全 User.objects.filter(namerequest.GET[name])使用ORM的安全方法filter()和exclude()get()和create()原始SQL查询必须使用参数化from django.db import connection with connection.cursor() as cursor: cursor.execute(SELECT * FROM users WHERE name %s, [name]) row cursor.fetchone()7. 项目结构最佳实践7.1 模板目录布局推荐的项目模板结构templates/ ├── base/ # 基础模板 │ ├── base.html │ ├── head.html # 头部区块 │ └── footer.html ├── accounts/ # 应用相关模板 │ ├── login.html │ └── profile.html ├── blog/ │ ├── list.html │ └── detail.html └── includes/ # 公共组件 ├── navbar.html └── messages.html7.2 视图代码组织基于类的视图(CBV)from django.views.generic import ListView, DetailView class ArticleListView(ListView): model Article template_name blog/list.html paginate_by 10 context_object_name articles class ArticleDetailView(DetailView): model Article template_name blog/detail.html def get_context_data(self, **kwargs): context super().get_context_data(**kwargs) context[related] Article.objects.filter( categoryself.object.category ).exclude(idself.object.id)[:5] return context视图混入(Mixins)from django.contrib.auth.mixins import LoginRequiredMixin class ProtectedView(LoginRequiredMixin, TemplateView): login_url /login/ template_name secret.html自定义装饰器def staff_required(view_func): wraps(view_func) def _wrapped_view(request, *args, **kwargs): if not request.user.is_staff: return HttpResponseForbidden() return view_func(request, *args, **kwargs) return _wrapped_view staff_required def admin_dashboard(request): return render(request, admin/dashboard.html)8. 调试技巧大全8.1 模板调试{% debug %}标签 在模板中添加{% debug %}可以显示所有可用变量当前模板上下文已加载的模板标签Django Debug Toolbar 安装配置后可以查看SQL查询详情模板渲染时间缓存命中情况请求/响应头信息8.2 请求响应调试打印请求信息def debug_view(request): print(fMethod: {request.method}) print(fGET: {request.GET.dict()}) print(fPOST: {request.POST.dict()}) print(fHeaders: {dict(request.headers)}) return HttpResponse(Check console output)使用curl测试API# GET请求 curl http://localhost:8000/api/items/?qtest # POST JSON数据 curl -X POST -H Content-Type: application/json \ -d {key:value} http://localhost:8000/api/create/ # 带认证头 curl -H Authorization: Token abc123 http://localhost:8000/api/protected/8.3 性能分析SQL查询分析from django.db import connection def query_debug_view(request): # 重置查询记录 connection.queries_log.clear() # 执行查询操作 books Book.objects.select_related(author).filter( publish_date__year2023 ) # 输出SQL查询 for query in connection.queries: print(f{query[time]}s: {query[sql]}) return render(request, debug.html, {books: books})使用cProfile分析import cProfile from io import StringIO import pstats def profile_view(request): profiler cProfile.Profile() profiler.enable() # 被分析的代码 result some_expensive_operation() profiler.disable() stream StringIO() stats pstats.Stats(profiler, streamstream) stats.sort_stats(cumulative) stats.print_stats() return HttpResponse(stream.getvalue())9. 测试驱动开发实践9.1 模板测试from django.test import TestCase class TemplateTests(TestCase): def test_homepage_template(self): response self.client.get(/) self.assertTemplateUsed(response, home.html) self.assertContains(response, Welcome) self.assertNotContains(response, Error) def test_context_data(self): response self.client.get(/blog/) self.assertIn(articles, response.context) self.assertEqual(len(response.context[articles]), 10)9.2 请求响应测试class ViewTests(TestCase): def test_redirect(self): response self.client.get(/old-url/) self.assertRedirects(response, /new-url/) def test_api_response(self): response self.client.post( /api/data/, data{key: value}, content_typeapplication/json ) self.assertEqual(response.status_code, 200) self.assertJSONEqual( response.content, {status: success} ) def test_file_upload(self): with open(test.png, rb) as fp: response self.client.post( /upload/, {file: fp} ) self.assertEqual(response.status_code, 200)9.3 性能测试from django.test import TestCase from django.urls import reverse class PerformanceTests(TestCase): classmethod def setUpTestData(cls): # 创建测试数据 for i in range(1000): Book.objects.create( titlefBook {i}, authorAuthor.objects.create(namefAuthor {i}) ) def test_list_performance(self): with self.assertNumQueries(3): # 预期查询次数 response self.client.get(reverse(book-list)) self.assertEqual(response.status_code, 200) def test_detail_performance(self): with self.assertNumQueries(2): book Book.objects.first() response self.client.get( reverse(book-detail, args[book.pk]) ) self.assertEqual(response.status_code, 200)10. 进阶技巧与扩展10.1 自定义模板标签创建更强大的模板功能# blog/templatetags/blog_tags.py from django import template register template.Library() register.simple_tag(takes_contextTrue) def breadcrumb(context, *args): request context[request] path_parts request.path.split(/)[1:-1] links [] url_so_far / for part in path_parts: url_so_far part / links.append((part.title(), url_so_far)) return { links: links, current: args[-1] if args else path_parts[-1].title() }模板中使用{% load blog_tags %} {% breadcrumb Current Page as bc %} nav {% for text, url in bc.links %} a href{{ url }}{{ text }}/a raquo; {% endfor %} span{{ bc.current }}/span /nav10.2 中间件开发创建自定义中间件处理请求/响应# middleware.py import time from django.utils.deprecation import MiddlewareMixin class TimingMiddleware(MiddlewareMixin): def process_request(self, request): request.start_time time.time() def process_response(self, request, response): duration time.time() - request.start_time response[X-Response-Time] f{duration:.2f}s # 记录慢请求 if duration 2: print(fSlow request: {request.path} took {duration:.2f}s) return response注册中间件MIDDLEWARE [ ... myapp.middleware.TimingMiddleware, ... ]10.3 REST API集成结合Django REST Framework构建API# serializers.py from rest_framework import serializers from .models import Article class ArticleSerializer(serializers.ModelSerializer): author_name serializers.CharField( sourceauthor.name, read_onlyTrue ) class Meta: model Article fields [id, title, content, author_name] read_only_fields [id] # views.py from rest_framework import generics from .models import Article from .serializers import ArticleSerializer class ArticleListCreateView(generics.ListCreateAPIView): queryset Article.objects.select_related(author) serializer_class ArticleSerializer def perform_create(self, serializer): serializer.save(authorself.request.user) class ArticleRetrieveUpdateView(generics.RetrieveUpdateAPIView): queryset Article.objects.all() serializer_class ArticleSerializer10.4 实时功能扩展使用Channels实现WebSocket# consumers.py from channels.generic.websocket import AsyncJsonWebsocketConsumer class ChatConsumer(AsyncJsonWebsocketConsumer): async def connect(self): self.room_name self.scope[url_route][kwargs][room_name] self.room_group_name fchat_{self.room_name} await self.channel_layer.group_add( self.room_group_name, self.channel_name ) await self.accept() async def disconnect(self, close_code): await self.channel_layer.group_discard( self.room_group_name, self.channel_name ) async def receive_json(self, content): await self.channel_layer.group_send( self.room_group_name, { type: chat.message, message: content[message], username: content[username] } ) async def chat_message(self, event): await self.send_json({ message: event[message], username: event[username] })路由配置# routing.py from django.urls import re_path from . import consumers websocket_urlpatterns [ re_path(rws/chat/(?Proom_name\w)/$, consumers.ChatConsumer.as_asgi()), ]11. 项目实战博客系统开发11.1 模型设计from django.db import models from django.contrib.auth.models import User from django.urls import reverse from django.utils.text import slugify class Category(models.Model): name models.CharField(max_length50) slug models.SlugField(uniqueTrue) def save(self, *args, **kwargs): if not self.slug: self.slug slugify(self.name) super().save(*args, **kwargs) def __str__(self): return self.name class Tag(models.Model): name models.CharField(max_length30) slug models.SlugField(uniqueTrue) def save(self, *args, **kwargs): if not self.slug: self.slug slugify(self.name) super().save(*args, **kwargs) def __str__(self): return self.name class Post(models.Model): STATUS_CHOICES ( (draft, Draft), (published, Published), ) title models.CharField(max_length200) slug models.SlugField(unique_for_datepublish_date) author models.ForeignKey(User, on_deletemodels.CASCADE) content models.TextField() excerpt models.CharField(max_length300, blankTrue) status models.CharField( max_length10, choicesSTATUS_CHOICES, defaultdraft ) publish_date models.DateTimeField(nullTrue, blankTrue) created_at models.DateTimeField(auto_now_addTrue) updated_at models.DateTimeField(auto_nowTrue) category models.ForeignKey( Category, on_deletemodels.SET_NULL, nullTrue, related_nameposts ) tags models.ManyToManyField(Tag, related_nameposts, blankTrue) featured_image models.ImageField( upload_topost_images/, nullTrue, blankTrue ) class Meta: ordering [-publish_date] indexes [ models.Index(fields[-publish_date]), ] def save(self, *args, **kwargs): if not self.slug: self.slug slugify(self.title) if not self.excerpt and self.content: self.excerpt self.content[:300] ... super().save(*args, **kwargs) def get_absolute_url(self): return reverse(post_detail, kwargs{ year: self.publish_date.year, month: self.publish_date.month, day: self.publish_date.day, slug: self.slug }) def __str__(self): return self.title11.2 视图逻辑from django.views.generic import ListView, DetailView from django.shortcuts import get_object_or_404 from .models import Post, Category, Tag from django.db.models import Count class PostListView(ListView): model Post template_name blog/post_list.html context_object_name posts paginate_by 10 def get_queryset(self): queryset super().get_queryset() queryset queryset.filter(statuspublished).select_related( author, category ).prefetch_related(tags) # 分类过滤 category_slug self.kwargs.get(category_slug) if category_slug: category get_object_or_404(Category, slugcategory_slug) queryset queryset.filter(categorycategory) # 标签过滤 tag_slug self.kwargs.get(tag_slug) if tag_slug: tag get_object_or_404(Tag, slugtag_slug) queryset queryset.filter(tagstag) return queryset def get_context_data(self, **kwargs): context super().get_context_data(**kwargs) # 添加分类和标签云 context[categories] Category.objects.annotate( post_countCount(posts) ).filter(post_count__gt0) context[tags] Tag.objects.annotate( post_countCount(posts) ).filter(post_count__gt0) # 添加当前分类/标签信息 if category_slug in self.kwargs: context[current_category] get_object_or_404( Category, slugself.kwargs[category_slug] ) if tag_slug in self.kwargs: context[current_tag] get_object_or_404( Tag, slugself.kwargs[tag_slug] ) return context class PostDetailView(DetailView): model Post template_name blog/post_detail.html context_object_name post def get_queryset(self): return super().get_queryset().filter( statuspublished ).select_related( author, category ).prefetch_related(tags) def get_context_data(self, **kwargs): context super().get_context_data(**kwargs) # 相关文章 post self.object context[related_posts] Post.objects.filter( statuspublished, categorypost.category ).exclude( idpost.id )[:5] return context11.3 模板实现base.html:!DOCTYPE html html langen head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title{% block title %}My Blog{% endblock %}/title link relstylesheet href{% static css/style.css %} {% block extra_css %}{% endblock %} /head body header h1a href{% url post_list %}My Django Blog/a/h1 nav ul lia href{% url post_list %}Home/a/li {% for category in categories %} li a href{% url post_list_by_category category.slug %} {{ category.name }} /a /li {% endfor %} /ul /nav /header main {% block content %}{% endblock %} /main aside h3Tags/h3 ul classtag-cloud {% for tag in tags %} li a href{% url post_list_by_tag tag.slug %} stylefont-size: {{ tag.post_count|add:10 }}px {{ tag.name }} /a /li {% endfor %} /ul /aside footer pcopy; {% now Y %} My Django Blog/p /footer script src{% static js/main.js %}/script {% block extra_js %}{% endblock %} /body /htmlpost_detail.html:{% extends base.html %} {% block title %}{{ post.title }}{% endblock %} {% block content %} article header h1{{ post.title }}/h1 div classmeta spanPublished on {{ post.publish_date|date:F j, Y }}/span spanby {{ post.author.username }}/span spanin a href{% url post_list_by_category post.category.slug %} {{ post.category.name }} /a /span /div {% if post.featured_image %} img src{{ post.featured_image.url }} alt{{ post.title }} {% endif %} /header div classcontent {{ post.content|linebreaks }} /div footer div classtags {% for tag in post.tags.all %} a href{% url post_list_by_tag tag.slug %} classtag {{ tag.name }} /a {% endfor %} /div /footer /article section classrelated h2Related Posts/h2 ul {% for post in related_posts %} li a href{{ post.get_absolute_url }}{{ post.title }}/a /li {% endfor %} /ul /section {% endblock %}12. 部署上线指南12.1 生产环境配置settings.py关键配置DEBUG False ALLOWED_HOSTS [yourdomain.com, www.yourdomain.com] # 安全配置 SECURE_SSL_REDIRECT True SESSION_COOKIE_SECURE True CSRF_COOKIE_SECURE True SECURE_HSTS_SECONDS 31536000 # 1 year SECURE_HSTS_INCLUDE_SUBDOMAINS True SECURE_HSTS_PRELOAD True # 静态文件 STATIC_URL /static/ STATIC_ROOT os.path.join(BASE_DIR, staticfiles) STATICFILES_DIRS [ os.path.join(BASE_DIR, static), ] # 媒体文件 MEDIA_URL /media/ MEDIA_ROOT os.path.join(BASE_DIR, media) # 数据库 DATABASES { default: { ENGINE: django.db.backends.postgresql, NAME: mydatabase, USER: mydatabaseuser, PASSWORD: mypassword, HOST: localhost, PORT: 5432, } } # 缓存 CACHES { default: { BACKEND: django.core.cache.backends.redis.RedisCache, LOCATION: redis://127.0.0.1:6379/1, } } # 日志 LOGGING { version: 1, disable_existing_loggers: False, handlers: { file: { level: DEBUG, class: logging.FileHandler, filename: /var/log/django/debug.log, }, }, loggers: { django: { handlers: [file], level: DEBUG, propagate: True, }, }, }12.2 Gunicorn配置gunicorn.conf.py:bind 0.0.0.0:8000 workers 3 worker_class gthread threads 3 max_requests 1000 max_requests_jitter 50 timeout 30 keepalive