Django搭建个人博客:使用Form表单类发表新文章

13744阅读 · 51评论 · 2018/09/26发布  

前面我们已经学会如何用Markdown语法书写文章了。

但是还有问题呀。之前写文章都是在后台中进行的,万一有别的普通用户也要发表文章怎么办?万一我想拓展些后台中没有的提交验证功能又怎么办?

本章即讲述如何在前台中提交新的文章,以便满足开发者各种各样的特殊需求

Forms表单类

在HTML中,表单是在 <form>...</form> 中的一些元素,它允许访客做类似输入文本、选择选项、操作对象或空间等动作,然后发送这些信息到服务端。一些表单界面元素(文本框或复选框)非常简单并内置在HTML中,而其他会复杂些:像弹出日期选择等操作控件。

处理表单是一件挺复杂的事情。想想看Django的admin,许多不同类型的数据可能需要在一张表单中准备显示,渲染成HTML,使用方便的界面进行编辑,传到服务器,验证和清理数据,然后保存或跳过进行下一步处理。

Django的表单功能可以简化上述工作的大部分内容,并且也能比大多数程序员自己编写代码去实现来的更安全。

Django表单系统的核心组件是 Form,它能够描述一张表单并决定它如何工作及呈现。

要使用Form类也很简单,需要在article/中创建forms.py文件,并写入如下代码:

article/forms.py

# 引入表单类
from django import forms
# 引入文章模型
from .models import ArticlePost

# 写文章的表单类
class ArticlePostForm(forms.ModelForm):
    class Meta:
        # 指明数据模型来源
        model = ArticlePost
        # 定义表单包含的字段
        fields = ('title', 'body')

代码中ArticlePostForm类继承了Django的表单类forms.ModelForm,并在类中定义了内部类class Meta(之前提到过,还记得吗),指明了数据模型的来源,以及表单中应该包含数据模型的哪些字段。

ArticlePost模型中,createdupdated字段为自动生成,不需要填入;author字段暂时固定为id=1的管理员用户,也不用填入;剩下的titlebody就是表单需要填入的内容了。

接下来,改写article/views.py,添加一个视图函数以处理写文章的请求:

article/views.py

...

# 引入redirect重定向模块
from django.shortcuts import render, redirect
# 引入HttpResponse
from django.http import HttpResponse
# 引入刚才定义的ArticlePostForm表单类
from .forms import ArticlePostForm
# 引入User模型
from django.contrib.auth.models import User

...

# 写文章的视图
def article_create(request):
    # 判断用户是否提交数据
    if request.method == "POST":
        # 将提交的数据赋值到表单实例中
        article_post_form = ArticlePostForm(data=request.POST)
        # 判断提交的数据是否满足模型的要求
        if article_post_form.is_valid():
            # 保存数据,但暂时不提交到数据库中
            new_article = article_post_form.save(commit=False)
            # 指定数据库中 id=1 的用户为作者
            # 如果你进行过删除数据表的操作,可能会找不到id=1的用户
            # 此时请重新创建用户,并传入此用户的id
            new_article.author = User.objects.get(id=1)
            # 将新文章保存到数据库中
            new_article.save()
            # 完成后返回到文章列表
            return redirect("article:article_list")
        # 如果数据不合法,返回错误信息
        else:
            return HttpResponse("表单内容有误,请重新填写。")
    # 如果用户请求获取数据
    else:
        # 创建表单类实例
        article_post_form = ArticlePostForm()
        # 赋值上下文
        context = { 'article_post_form': article_post_form }
        # 返回模板
        return render(request, 'article/create.html', context)

分析一下上面的代码。当视图函数接收到一个客户端的request请求时,首先根据request.method判断用户是要提交数据(POST)、还是要获取数据(GET)

  • 如果用户是提交数据,将POST给服务器的表单数据赋于article_post_form实例。然后使用Django内置的方法.is_valid()判断提交的数据是否满足模型的要求。

  • 如果满足要求,保存表单中的数据(但是commit=False暂时不提交到数据库,因为author还未指定),并指定author为id=1的管理员用户。然后提交到数据库,并通过redirect返回文章列表。redirect可通过url地址的名字,反向解析到对应的url。

  • 如果不满足要求,则返回一个字符串"表单内容有误,请重新填写。",告诉用户出现了什么问题。

  • 如果用户是获取数据,则返回一个空的表单类对象,提供给用户填写。

其实逻辑并不复杂,不明白的读者请逐句理解。

Django官方文档对Form类做了详细的说明:

Form实例可以绑定到数据,也可以不绑定数据。

  • 如果绑定到数据,就能够验证该数据并将表单呈现为HTML并显示数据。
  • 如果它未绑定,则无法进行验证(因为没有要验证的数据!),但仍然可以将空白表单呈现为HTML。

要将数据绑定到表单,就将数据作为字典作为第一个参数传递给Form类构造函数:

>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': 'foo@example.com',
...         'cc_myself': True}
>>> f = ContactForm(data)

Form对象的主要任务是验证数据。使用绑定后的 Form实例,调用is_valid()方法验证并返回指定数据是否有效的布尔值:

>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': 'foo@example.com',
...         'cc_myself': True}
>>> f = ContactForm(data)
>>> f.is_valid()
True

让我们尝试一些无效的数据。subject为空(错误,因为默认情况下需要所有字段),并且sender不是有效的电子邮件地址:

>>> data = {'subject': '',
...         'message': 'Hi there',
...         'sender': 'invalid email address',
...         'cc_myself': True}
>>> f = ContactForm(data)
>>> f.is_valid()
False

关于Forms类的详细解释看这里:The Forms API

这里特别提醒Django中的缩进不能够空格和tab键混用,否则会报错。由于不同的编辑器对tab的显示不尽相同,因此你应该坚持使用空格键缩进。

大多数文本编辑器可以自动将tab转换为空格,请合理设置。

写好视图之后,就需要写模板文件了。在templates/article/中创建create.html

templates/article/create.html

<!-- extends表明此页面继承自 base.html 文件 -->
{% extends "base.html" %} {% load staticfiles %}
<!-- 写入 base.html 中定义的 title -->
{% block title %} 写文章 {% endblock title %}
<!-- 写入 base.html 中定义的 content -->
{% block content %}
<!-- 写文章表单 -->
<div class="container">
    <div class="row">
        <div class="col-12">
            <br>
            <!-- 提交文章的表单 -->
            <form method="post" action=".">
                <!-- Django中需要POST数据的地方都必须有csrf_token -->
                {% csrf_token %}
                <!-- 文章标题 -->
                <div class="form-group">
                    <!-- 标签 -->
                    <label for="title">文章标题</label>
                    <!-- 文本框 -->
                    <input type="text" class="form-control" id="title" name="title">
                </div>
                <!-- 文章正文 -->
                <div class="form-group">
                    <label for="body">文章正文</label>
                    <!-- 文本区域 -->
                    <textarea type="text" class="form-control" id="body" name="body" rows="12"></textarea>
                </div>
                <!-- 提交按钮 -->
                <button type="submit" class="btn btn-primary">完成</button>
            </form>
        </div>
    </div>
</div>
{% endblock content %}

html文件还是一如既往的长。再重复一次,看不懂html文件语法也没有关系,先照着抄一遍,以后再慢慢理解,不影响目前Django的学习。

对其中的新内容进行审视:

  • <form>..</form>标签中的内容就是需要提交的表单。method="post"指定了表单提交的方式为POST(与视图函数中的request.method相联系);action="."指定了表单提交的地址为默认的当前url。

  • 关于{% csrf_token %},它是Django中一个与网络安全相关的中间件验证。目前我们暂时不去深究它的实现,只需要知道表单中必须包含它就可以了,否则将会得到一个403错误。

  • <input><textarea>标签中的name=''属性指定了当前文本框提交的数据的名称,它必须与表单类中的字段名称对应,否则服务器无法将字段和数据正确的对应起来。

最后老规矩,在article/urls.py中增加一个写文章的url地址:

article/urls.py

urlpatterns = [
    ...

    # 写文章
    path('article-create/', views.article_create, name='article_create'),
]

大功告成了,不要着急,先喝口水,万一有bug又得忙活半天了。如果报错也不要慌张,开发过程一定是曲折的,耐心看看Django给出的错误提示,线索就在其中。

保存修改并运行服务器,地址栏中输入:http://127.0.0.1:8000/article/article-create/,看到如下界面:

很好,似乎正常工作起来了。接着随便输入些Markdown语法的文章,测试功能是否正常:

点击完成按钮后,页面会回到文章列表:

刚才提交的文章神奇的出现在列表中了。

点击阅读本文按钮,进入文章详情页面:

出现了具有Markdown语法的优美的文章。

是不是非常有成就感呢?

别激动,还有一些收尾工作需要做。

优化写文章入口

与之前类似,我们需要在导航栏中设置一个写文章的入口,优化使用体验。

将下列代码加入到templates/header.html中:

<li class="nav-item">
    <a class="nav-link" href="{% url 'article:article_create' %}">写文章</a>
</li>

读者是否清楚,上面的代码应该放置在什么位置呢?

保存后刷新浏览器界面,导航栏有了如下变化:

点击写文章按钮,就可以进入写新文章的页面了,从此再也不用手动输入繁琐的url地址了。

世界是多么的美好。

总结

本章学习了使用Django的表单类,完成了提交新文章的基本功能。当然目前暂时还没有真正将文章和登录的用户关联起来;等到学习了用户管理的知识,再回头来处理这部分的内容。

下一章继续学习如何删除一篇文章。




本文作者: 杜赛
发布时间: 2018年09月26日 - 22:25
最后更新: 2019年09月28日 - 21:47
知识共享许可协议   转载请保留原文链接及作者

鉴于沟通的低效,博主暂时关闭了评论功能。如需技术交流,欢迎加入 QQ 水友群提问、或通过页脚任意一种方式联系博主。


avatar
tata 么么哒! 7
本回复已被 tata 删除

1年前


avatar
Gungnir66 么么哒! 7

请问点击按钮“完成”之后会直接报错:

Page not found (404)

Request Method:POSTRequest URL:http://127.0.0.1:8000/article/

Using the URLconf defined in my_blog_new.urls, Django tried these URL patterns, in this order:

    1.admin/
    2.article/ admin/
    3.article/ article-list/ [name='article_list']
    4.article/ article-detail/<int:id>/ [name='article_detail']
    5.article/ article-create/ [name='article_create']
    6.article/ article-delete/<int:id>/ [name='article_delete']
The current path, article/, didn't match any of these.

会跳转错误,而在Console里面没有任何错误,代码是copy的,请问可能哪里出错了?

而下一个“删除”模块没有问题

1年前


avatar
杜赛 [博主] Gungnir66 么么哒! 5

你的代码向 article/ 提交了POST,但是这个 url 并不存在。

你是不是忘了注册路由?或者提交到错误的url了。

1年前


avatar
pidada Gungnir66 么么哒! 6

不要copy代码

我是自己认真敲的 没有问题

1年前


avatar
xuzhiyi1998 Gungnir66 么么哒! 5

我的问题也是这样,请问解决了吗

9个月前


avatar
pidada 么么哒! 6

博主请问下  写文章的代码是放在什么位置呢?我后面好像碰到这个问题啦

1年前


avatar
杜赛 [博主] pidada 么么哒! 6

前面的章节里有,找一下

1年前


avatar
adamas 么么哒! 6
本回复已被 adamas 删除

1年前


avatar
ac1864 么么哒! 6

pass, mark it.

1年前


avatar
Nl-Lin 么么哒! 6

文章提交成功后,跳转到主页面,视乎好像没有创建成功?我看了后台也没有数据!!!

1年前


avatar
杜赛 [博主] Nl-Lin 么么哒! 6

文章没有正确save()吧

1年前


avatar
Nl-Lin 杜赛 [博主] 么么哒! 6

不是,在前台那里,form的action行为,action="." 就可以,action="{%url 'article:article_list' %}",就不行!!! 纳闷~sad

1年前


avatar
13242934754 么么哒! 6

博主请问下,提交文章时,报错django.db.utils.IntegrityError: NOT NULL constraint failed: article_articlepost.auther_id

但是我看我的数据表中auther_id明明是存在的啊

1年前


avatar
杜赛 [博主] 13242934754 么么哒! 6

这句话意思是告诉你auther_id的值不能为NULL,也就是不能为空值。

所以你必须给auther_id赋值,或者更改配置

1年前


avatar
isLeegege 杜赛 [博主] 么么哒! 5

我也是这个问题,改了还是没用,是我改得方式不对吗?请问该怎么改呢?

11个月前


avatar
isLeegege isLeegege 么么哒! 6

11个月前


avatar
HHHuaji 么么哒! 6
本回复已被 HHHuaji 删除

1年前


avatar
ac1864 么么哒! 6

第二次过关。
前端还是出了10几次bug,都是敲错了。
基本能理解代码,但是不理解前端代码。

1年前


avatar
杜赛 [博主] ac1864 么么哒! 6

你学得太急躁了。

慢下来,好好研究Bootstrap、css和JavaScript

1年前


avatar
Austin 么么哒! 6
本回复已被 Austin 删除

1年前


avatar
Jiangyciauh-Dawn 么么哒! 6

我想问一下:你发表文章是自己用Markdown语法写,还是直接用的Markdown编辑器写?重点在文章内图片的保存问题!谢谢!

11个月前


avatar
杜赛 [博主] Jiangyciauh-Dawn 么么哒! 6

我是用typora编辑器写的,你搜一下。

我对图片上传的要求不高,所以是先上传到服务器,再引用链接的。

11个月前


avatar
yym 么么哒! 6

博主的文章写得很详细, 非常感谢~

10个月前


avatar
杜赛 [博主] yym 么么哒! 5

10个月前


avatar
Mingxiu233 么么哒! 6

这里对Form提交的方法还不太理解,mark一下

10个月前


avatar
xzm1274913976 么么哒! 5

好像有个Bug.

没登录也可以无限发表文章.

10个月前


avatar
杜赛 [博主] xzm1274913976 么么哒! 5

不是bug。这里还没讲用户管理,后面会修正这个小问题

10个月前


avatar
xzm1274913976 杜赛 [博主] 么么哒! 5

嗯,感谢博主分享这些教程.很全面很细节.目前正在缓慢的学习 XD.
另外特别感兴趣博主的打赏模块,希望博主后续可以更新.

10个月前


avatar
杜赛 [博主] xzm1274913976 么么哒! 6

谢谢。打赏暂时不打算讲,因为会涉及到微信的支付信息流接口,而开通接口是需要付费的(目前普遍几百块左右)。据我了解大部分读者是反感付费的。

10个月前


avatar
xzm1274913976 杜赛 [博主] 么么哒! 5

哦哦. 感觉那个是很有意思的数据流向.如果时机可以的话希望博主可以讲述此方面功能[毕竟全网都没人讲这方面的] ^_^.

力挺博主!

9个月前


avatar
xuzhiyi1998 么么哒! 5

我的代码跟你完全一样,但是数据并没有保存到数据库中,这是怎么回事

9个月前


avatar
杜赛 [博主] xuzhiyi1998 么么哒! 5

把 article 用 print 打印出来看看

9个月前


avatar
Eliot00 么么哒! 5

发现从这个页面写文章是按时间最新的排在后面的,而在后台建的文章是新的在前面

8个月前


avatar
LXL Eliot00 么么哒! 5

你把article的views.py中的

def article_list(request):
    # 取出所有博客文章
    articles = ArticlePost.objects.all()
    # 需要传递给模板(templates)的对象
    context = { 'articles': articles }
    # render函数:载入模板,并返回context对象
    return render(request, 'article/list.html', context)

改成

def article_list(request):
    articles = ArticlePost.objects.all().order_by('-created')
    return render(request, 'article/list.html', context={'articles': articles})

 

7个月前


avatar
stone 么么哒! 5

文章列表的部分展示的还是非markdown格式,这个如何调整那。文章详情页是渲染之后的markdown格式。

+ 代码高亮 ```python def article_detail(request, id): article = ArticlePost.objects.get(id=id) ...

6个月前


avatar
杜赛 [博主] stone 么么哒! 5

对返回值再过滤一次,比如用正则表达式

6个月前


avatar
stone 杜赛 [博主] 么么哒! 4

请问具体是在前端进行还是后端用正则

6个月前


avatar
杜赛 [博主] stone 么么哒! 4

6个月前


avatar
blearch 么么哒! 3

博主,在创建新文章的时候,不传递表单好像也能创建,后台也能看到正常的文章,所以我这里有点疑惑,教程里面的传递表单有什么作用?

def article_create(request):
    if request.method=="POST":
        artcle_post_form=ArticlePostForm(data=request.POST)
        if artcle_post_form.is_valid():
            new_article=artcle_post_form.save(commit=False)
            new_article.author=User.objects.get(id=1)
            new_article.save()
            return redirect("article:article_list")
        else:
            return HttpResponse("表单内容有误,请重新填写")
    else:
        # article_post_form=ArticlePostForm()
        # context={'article_post_form':article_post_form}
        return  render(request,'article/create.html')

 

6个月前


avatar
杜赛 [博主] blearch 么么哒! 3

是,之前有解释过,这里传表单确实没有用上,因为表单是自己手写的,没有用到自动生成的功能

6个月前


avatar
samshui 么么哒! 3

基本上都实现了,但是有一个问题就是我写了点测试的文章,点击提交的时候,会表示“表单有误”(403错误),所以有点好奇is_valid的标准是啥

3个月前


avatar
samshui samshui 么么哒! 3

我试着把判断is_valid删除了,提交就会有这些警告

ValueError at /article/article-create/
The ArticlePost could not be created because the data didn't validate.
Request Method:	POST
Request URL:	http://127.0.0.1:8000/article/article-create/
Django Version:	2.2.4
Exception Type:	ValueError
Exception Value:	
The ArticlePost could not be created because the data didn't validate.
Exception Location:	C:\Users\17306\AppData\Local\Programs\Python\Python37-32\lib\site-packages\django\forms\models.py in save, line 453
Python Executable:	C:\Users\17306\AppData\Local\Programs\Python\Python37-32\python.exe
Python Version:	3.7.3
Python Path:	
['C:\\Users\\17306\\Desktop\\django_project\\my_blog',
 'C:\\Users\\17306\\AppData\\Local\\Programs\\Python\\Python37-32\\python37.zip',
 'C:\\Users\\17306\\AppData\\Local\\Programs\\Python\\Python37-32\\DLLs',
 'C:\\Users\\17306\\AppData\\Local\\Programs\\Python\\Python37-32\\lib',
 'C:\\Users\\17306\\AppData\\Local\\Programs\\Python\\Python37-32',
 'C:\\Users\\17306\\AppData\\Roaming\\Python\\Python37\\site-packages',
 'C:\\Users\\17306\\AppData\\Roaming\\Python\\Python37\\site-packages\\win32',
 'C:\\Users\\17306\\AppData\\Roaming\\Python\\Python37\\site-packages\\win32\\lib',
 'C:\\Users\\17306\\AppData\\Roaming\\Python\\Python37\\site-packages\\Pythonwin',
 'C:\\Users\\17306\\AppData\\Local\\Programs\\Python\\Python37-32\\lib\\site-packages']
Server time:	Wed, 18 Mar 2020 03:07:28 +0000

 

3个月前


avatar
samshui samshui 么么哒! 3

解决啦!!!一定注意别打错东西呜呜呜

3个月前


avatar
Joshua-leyer 么么哒! 3

为什么,讲到from类的时候,就要新建个文件。不是在原来model文件,上面引入from类。在继承,在里面写不就行了?

还有,为什么视图函数传递给模板东西的时候,都要弄成字典,然后传过去?直接传过去对象不行吗?

3个月前


avatar
杜赛 [博主] Joshua-leyer 么么哒! 3

与其问,不如试

3个月前


avatar
fenghua-n 么么哒! 3

博主讲得很详细,给很多小白,甚至没接触过的带来很大的动力!必须支持

2个月前


avatar
一只小小熊 么么哒! 3

楼主,我想请问下,我按您的代码写的banner导航条,文章,写文章为什么是纵向排列的?

这样:

文章

写文章

 

谢谢

2个月前


avatar
杜赛 [博主] 一只小小熊 么么哒! 3

Bootstrap 官方文档找答案

2个月前


avatar
思远道 么么哒! 3

打卡,2020-05-29

1个月前


avatar
思远道 么么哒! 3

写文章现在有个问题,作者都是默认作者 超级管理员。

等我改好再回来devil

1个月前


avatar
wuyan 么么哒! 3
本回复已被 wuyan 删除

28天前