一篇博客,要将其找出来,就需要有一个唯一的标识。Django 的模型中默认有一个唯一的且未自增长的主键,即 id 字段,我们可以以此为关键字来定位一篇博客。
1. 编辑路由
编辑myblog/urls.py,新增内容:
from django.contrib import admin
from django.urls import path
from blog.views import get_blogs,get_detail
urlpatterns = [
path('admin/', admin.site.urls),
path('blog/',get_blogs,name='blog_get_blogs'),
path('detail/<blog_id>',get_detail,name='blog_get_detail'),
]
2. 生成和处理评论
Django Form 能帮助我们快速实现这一需求。构建一个表单,新建文件 blog/forms.py
from django import forms class CommentForm(forms.Form): """评论表单""" name = forms.CharField(label='称呼', max_length=16, error_messages={ 'required': '请填写您的称呼', 'max_length': '称呼太长' }) email = forms.EmailField(label='邮箱', error_messages={ 'required': '请填写您的邮箱', 'invalid': '邮箱格式不正确' }) content = forms.CharField(label='评论内容', error_messages={ 'required': '请填写您的评论内容', 'max_length': '评论内容太长' })
代码中定义了一个评论表单的类并根据需求定义了三个字段:称呼、邮箱和评论内容。这样我们就能利用它来快速生成表单并验证用户的输入。
3. 编辑视图,修改如下:
Blog/views.py
from .forms import CommentForm from django.http import Http404 def get_detail(request, blog_id): try: blog = Blog.objects.get(id=blog_id) except Blog.DoesNotExist: raise Http404 if request.method == 'GET': form = CommentForm() else: form = CommentForm(request.POST) if form.is_valid(): cleaned_data = form.cleaned_data cleaned_data['blog'] = blog Comment.objects.create(**cleaned_data) ctx = { 'blog': blog, 'comments': blog.comment_set.all().order_by('-created'), 'form': form } return render(request, 'blog-detail.html', ctx)
- get_detail() 视图函数首先利用传入的 blog_id 到数据库中查询该条博客记录。注意这使用了 try 块,因为 model.objects.get() 方法会在未能查询到数据的情况下抛出 model.DoesNotExist 的异常。如果没有对此异常进行拦截的话,就会导致服务器 500 错误,这显然不是用户想看到的。而是应该在发生此错误的情况下通知用户“你要访问的内容未能找到”。一般情况下网站都会在这个时候向用户抛出一个 404 错误并定义一些“生动有趣”的话语来提示用户同时又不会显得太突兀而让用户产生反感;
- 中间一个部分是对表单的初始化和校验处理。这里会涉及到 HTTP 的 GET 请求和 POST 请求。一般来说,GET 请求用于获取数据,而 POST 请求则为提交数据。这里也遵循此一般约定,在用户做 GET 请求时,仅初始化一个空表单供用户填写;而如果是 POST 请求的话,则在初始化的同时将用户传入的数据传入。用户传入的数据会收集在 request.POST 中,是一个类似于字典的对象。接下来是调用 form 类的 is_valid() 方法来对用户输入做校验,如果校验成功,则创建一条评论记录;
- ctx 依然是要传入到模板的上下文参数,其中 blog 是博客对象,comments 是利用 ORM 的反查方法找到当前博客包含的所有评论,并以发布时间的倒序方式进行排列,另外还有一个 form,是之前定义的 CommentForm 的实例化对象,它用于初始化评论表单。
4. 定义博客内容模板templates/blog-detail.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{{ blog.title }}</title> <style> .blog { padding: 20px 0px; } .blog .info span { padding-right: 10px; } .blog .summary { padding-top: 20px; } </style> </head> <body> <div class="header"> <span><a href="{% url 'blog_get_blogs' %}">博客</a> - <a href="{% url 'blog_get_detail' blog.id %}">{{ blog.title }}</a></span> </div> <div class="content"> <div class="blog"> <div class="title"> <a href="#"><h2>{{ blog.title }}</h2></a> </div> <div class="info"> <span class="category" style="color: #ff9900;">{{ blog.category.name }}</span> <span class="author" style="color: #4a86e8">{{ blog.author }}</span> <span class="created" style="color: #6aa84f">{{ blog.created|date:"Y-m-d H:i" }}</span> </div> <div class="summary"> {{ blog.content }} </div> </div> <div class="comment"> <div class="comments-display" style="padding-top: 20px;"> <h3>评论</h3> {% for comment in comments %} <div class="comment-field" style="padding-top: 10px;"> {{ comment.name }} 说: {{ comment.content }} </div> {% endfor %} </div> <div class="comment-post" style="padding-top: 20px;"> <h3>提交评论</h3> <form action="{% url 'blog_get_detail' blog.id %}" method="post"> {% csrf_token %} {% for field in form %} <div class="input-field" style="padding-top: 10px"> {{ field.label }}: {{ field }} </div> <div class="error" style="color: red;"> {{ field.errors }} </div> {% endfor %} <button type="submit" style="margin-top: 10px">提交</button> </form> </div> </div> </div> </body> </html>
- {% url 'blog_get_blogs' %} 可以看作是 reverse 方法的“模板语言”版,其作用是根据 URLConf 中的 name 定义对 url 进行反解析,转换成真实的 URL 地址。比如这里在转换之后会变成 “/”。如果所指定的 url 定义中包含参数,则需要将参数跟在后面,如 {% url 'blog_get_detail' blog.id %};
- 观察表单的内容,{% csrf_token %} 用于防跨域请求,可参考一些关于 CSRF 相关的资料并阅读 Django 官方文档关于这一块实现的描述。{% for field in form %}{% endfor %} 是对表单的各个 field 进行迭代并生成相应的表单元素,并对校验过程中出现的错误进行显示以提示用户做相应的修改。最后还生成一个 button 用于点击提交评论。
5. 修改主页的博客标题链接
将templates/blog-list.html中之前设置的#改为如下内容:
{% url 'blog_get_detail' blog.id %}