django3基础入门-p05-表单Form

创建Entry列表页,提供创建Entry文章入口

  1. 添加路由 app_demo/urls.py
path('entry', views.entry_list_view, name='entry_list'),
  1. 添加视图函数 app_demo/views.py
def entry_list_view(request):
    entry_list = Entry.objects.all()
    count = Entry.objects.count()
    context = {"entry_list": entry_list, "count": count}
    return render(request, 'app_demo/entry_list.html', context=context)
  1. 添加显示模板 app_demo/templates/app_demo/entry_list.html
{% extends 'app_demo/base.html' %}
{% load static %}

{% block css %}
    <link rel="stylesheet" href="{% static 'app_demo/style.css' %}">
{% endblock %}


{% block content %}
    <h1>Entry列表</h1>
    <a type="button" class="btn btn-primary" href="/blog/entry/normal_add">添加Entry</a>
    <table class="table table-bordered">
        <thead>
        <tr>
            <th>#</th>
            <th>所属博客</th>
            <th>文章标题</th>
            <th>文章内容</th>
            <th>作者</th>
            <th>评论数</th>
            <th>点赞数</th>
            <th>评级</th>
            <th>操作</th>
        </tr>
        </thead>
        <tbody>
        {% for entry in entry_list %}
            <tr>
                <th scope="row">{{ entry.id }}</th>
                <td>{{ entry.blog }}</td>
                <td>{{ entry.headline }}</td>
                <td>{{entry.body_text}}</td>
                <td>{% for author in entry.authors.all %}{{ author}}|{% endfor %}</td>
                <td>{{entry.number_of_comments}}</td>
                <td>{{entry.number_of_likes}}</td>
                <td>{{entry.rating}}</td>
                <td><a href="#">编辑</a>  <a href="#">删除</a></td>
            </tr>
        {% empty %}
            <tr>
                <td scope="row" colspan="8" style="text-align: center">暂无Entry数据...</td>
            </tr>
        {% endfor %}

        </tbody>
    </table>
    <div>博客总数:{{ count }}</div>
{% endblock %}

image-20220331124521204

通过传统的方式处理表单-创建Entry文章

添加路由

app_demo/urls.py

path('entry/normal_add', views.entry_normal_add_view, name='entry_normal_add'),

视图函数处理表单数据

app_demo/views.py

def entry_normal_add_view(request):
    if request.method == 'GET':
        # GET 请求时,渲染空表单,关联表单部分默认值
        blogs = Blog.objects.all()
        authors = Author.objects.all()
        return render(request, 'app_demo/entry_normal_add.html', {"blogs": blogs, "authors": authors})
    # POST 请求时,获取表单POST过来的数据
    blog_id = request.POST.get("blog")
    headline = request.POST.get("headline")
    body_text = request.POST.get("body_text")
    authors = request.POST.getlist("authors")
    # 根据用户所传的数据,存储到数据库
    e = Entry.objects.create(
        blog_id=blog_id,
        headline=headline,
        body_text=body_text
    )
    e.authors.set(authors)

    return redirect(reverse("entry_list"))

前端通过form表单渲染和提交

app_demo/templates/app_demo/entry_normal_add.html

{% extends 'app_demo/base.html' %}
{% load static %}

{% block css %}
    <link rel="stylesheet" href="{% static 'app_demo/style.css' %}">
{% endblock %}


{% block content %}
    <h1>添加Entry</h1>
    <form method="post" action="">
        {% csrf_token %}
        <div class="form-group">
            <label for="blog">所属博客</label>
            <select id="blog" class="form-control" name="blog">
                {% for blog in blogs %}
                    <option value="{{ blog.id }}">{{ blog.name }}</option>
                {% endfor %}
            </select>

        </div>
        <div class="form-group">
            <label for="headline">文章标题</label>
            <input type="text" class="form-control" id="headline" placeholder="文章标题" name="headline">
        </div>

        <div class="form-group">
            <label for="body_text">文章内容</label>
            <textarea class="form-control" id="body_text" placeholder="文章内容" name="body_text"></textarea>
        </div>

        <div class="form-group">
            <label for="authors">作者</label>
            <select multiple id="authors" class="form-control" name="authors">
                {% for author in authors %}
                    <option value="{{ author.id }}">{{ author.name }}</option>
                {% endfor %}
            </select>
        </div>
    
        <button type="submit" class="btn btn-default">提交</button>
    </form>
{% endblock %}

GET请求,页面渲染如下:

image-20220331125431436

存在的问题

  • 用户提交的数据没有校验
  • 页面上缺少对应错误信息提示
  • 前端需要每个字段编写一个html控件
  • 关联的字段,需要后端手动获取然后前端循环显示

通过Django组件-Form组件处理表单-创建Entry文章

添加路由

app_demo/urls.py

path('entry/forms_add', views.entry_forms_add_view, name='entry_forms_add'),

定义Form表单

app_demo/forms.py

from django import forms
from django.core.exceptions import ValidationError

from app_demo.models import Blog, Author


class EntryForm(forms.Form):
    # label 指定 form.label的值,默认为属性名,如blog
    # queryset 指定关联model字段默认值
    # widge 指定样式
    blog = forms.ModelChoiceField(label="所属博客",
                                  queryset=Blog.objects.all(),
                                  widget=forms.Select(attrs={"class": "form-control"}))
    # form 字段校验,required=False 非必填项;nitial='xxx',设置默认值
    headline = forms.CharField(label="文章标题",
                               min_length=2,
                               error_messages={  # 定制错误信息
                                   'min_length': '长度不能小于2'
                               }
                               )
    body_text = forms.CharField(label="文章内容",
                                widget=forms.Textarea(attrs={"cols": "40", "rows": "5"}))
    authors = forms.ModelMultipleChoiceField(label="作者", queryset=Author.objects.all())

    # 批量添加样式:attrs={'class':'form_control'}应用bootstrap样式
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for name, field in self.fields.items():
            if name != 'blog':
                field.widget.attrs.update({"class": "form-control"})

    # 通过钩子方法进行字段校验,clean_字段名
    def clean_headline(self):
        headline = self.cleaned_data.get("headline")

        if len(str(headline).strip()) > 32:
            # 验证不通过
            raise ValidationError("长度不能大于32!")

        # 验证通过,返回用户输入信息
        return headline

视图函数处理表单数据

app_demo/views.py

def entry_forms_add_view(request):
    if request.method == "GET":
        # GET请求,渲染一个空的form
        form = EntryForm()
        return render(request, "app_demo/entry_forms_add.html", {"form": form})
	# form绑定用户输入数据
    form = EntryForm(request.POST)
    # form内置的数据校验
    if form.is_valid():
        # 逐个取出传入的数据,将数据存储到数据库中。
        e = Entry.objects.create(
            blog=form.cleaned_data.get("blog"),
            headline=form.cleaned_data.get("headline"),
            body_text=form.cleaned_data.get("body_text")
        )
        e.authors.set(form.cleaned_data.get("authors"))
        return redirect(reverse("entry_list"))
    # 如果POST输入数据校验失败,form.errors 中包含其验证失败信息,重新渲染表单
    return render(request, "app_demo/entry_forms_add.html", {"form": form})
  • form = EntryForm(request.POST): form表单数据绑定
  • form.is_valid(): form表单校验

前端通过form表单渲染和提交

app_demo/templates/app_demo/entry_forms_add.html

{% extends 'app_demo/base.html' %}
{% load static %}

{% block css %}
    <link rel="stylesheet" href="{% static 'app_demo/style.css' %}">
{% endblock %}


{% block content %}
    <h1>添加Entry</h1>
    <form method="post" action="" novalidate>
        {% csrf_token %}
        {% for filed in form %}
            <div class="form-group">
                {{ filed.label }}
                {{ filed }}
                <span class="label label-danger">{{ filed.errors.0 }}</span>
            </div>
        {% endfor %}
        <button type="submit" class="btn btn-default">提交</button>
    </form>
{% endblock %}

image-20220331210436414

form组件的主要功能总结

  • 生成页面可用的HTML标签
  • 对用户提交的数据进行校验
  • 保留上次输入内容,包含验证失败信息

通过Django组件-ModelForm组件处理表单-创建Entry文章

添加路由

app_demo/urls.py

path('entry/model_forms_add', views.entry_model_forms_add_view),

定义ModelForm表单

app_demo/forms.py

class EntryModelForm(forms.ModelForm):
    class Meta:
        model = Entry
        # fields="__all__"
        # exclude=["authors"]
        fields = ["blog", "headline", "body_text", "authors"]
        widgets = {
            "body_text": forms.Textarea(attrs={"cols": "40", "rows": "5"})
        }

    # 批量添加样式:attrs={'class':'form_control'}应用bootstrap样式
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for name, field in self.fields.items():
            field.widget.attrs.update({"class": "form-control"})

    # 通过钩子方法进行字段校验,clean_字段名
    def clean_headline(self):
        headline = self.cleaned_data.get("headline")

        if len(str(headline).strip()) > 32:
            # 验证不通过
            raise ValidationError("长度不能大于32!")

        # 验证通过,返回用户输入信息
        return headline

ModelFormForm 的不同点ModelForm中直接绑定models,根据models中定义的字段生成form属性,无需一个个字段重新定义。

class Meta:
    model = Entry

视图函数处理表单数据

app_demo/views.py

def entry_model_forms_add_view(request):
    if request.method == "GET":
        form = EntryModelForm()
        return render(request, "app_demo/entry_forms_add.html", {"form": form})

    form = EntryModelForm(request.POST)
    if form.is_valid():
        # 默认保存的是用户的输入值,如果想在用户输入以外增加一些逻辑处理值:form.instance.字段名=值
        form.instance.rating=2
        form.save()
        return redirect(reverse("entry_list"))
    return render(request, "app_demo/entry_forms_add.html", {"form": form})
  • ModelFormForm 的不同点ModelForm 中多了一个save()方法,直接保存到数据库,而Form则需要逐个请求数据提取出来传入到Model构造函数中进行创建。

前端通过form表单渲染和提交

app_demo/templates/app_demo/entry_forms_add.html

{% extends 'app_demo/base.html' %}
{% load static %}

{% block css %}
    <link rel="stylesheet" href="{% static 'app_demo/style.css' %}">
{% endblock %}


{% block content %}
    <h1>添加Entry</h1>
    <form method="post" action="" novalidate>
        {% csrf_token %}
        {% for filed in form %}
            <div class="form-group">
                {{ filed.label }}
                {{ filed }}
                <span class="label label-danger">{{ filed.errors.0 }}</span>
            </div>
        {% endfor %}
        <button type="submit" class="btn btn-default">提交</button>
    </form>
{% endblock %}

通过Django组件-ModelForm组件处理表单-编辑Entry文章

添加路由

app_demo/urls.py

path('entry/<int:e_id>/edit', views.entry_edit_view, name='entry_edit'),

修改app_demo/templates/app_demo/entry_list.html 中的“编辑”链接

<a href="{% url 'entry_edit' entry.id %}">编辑</a>

定义ModelForm表单

app_demo/forms.py 复用原有的EntryModelForm

视图函数处理表单数据

app_demo/views.py

def entry_edit_view(request, e_id):
    entry = get_object_or_404(Entry, pk=e_id)
    if request.method == "GET":
        form = EntryModelForm(instance=entry)
        return render(request, "app_demo/entry_edit.html", {"form": form})
    form = EntryModelForm(request.POST, instance=entry)
    if form.is_valid():
        form.save()
        return redirect(reverse("entry_list"))
    return render(request, "app_demo/entry_edit.html", {"form": form})
  • form = EntryModelForm(request.POST, instance=entry) 通过instance指定将要编辑的实例,如果不指定instance,则会数据库新建一条记录

前端通过form表单渲染和提交

app_demo/templates/app_demo/entry_edit.html

代码同 app_demo/templates/app_demo/entry_forms_add.html

{% extends 'app_demo/base.html' %}
{% load static %}

{% block css %}
    <link rel="stylesheet" href="{% static 'app_demo/style.css' %}">
{% endblock %}


{% block content %}
    <h1>编辑Entry</h1>
    <form method="post" action="" novalidate>
        {% csrf_token %}
        {% for filed in form %}
            <div class="form-group">
                {{ filed.label }}
                {{ filed }}
                <span class="label label-danger">{{ filed.errors.0 }}</span>
            </div>
        {% endfor %}
        <button type="submit" class="btn btn-default">提交</button>
    </form>
{% endblock %}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。
My Show My Code