《精通Django》第6章 Django表单

6.1 从请求对象中获取数据
  • 每个视图函数的第一个参数都是一个HttpRequest对象
6.1.1 关于URL的信息
  • HttpRequest 对象中有一些关于当前所请求 URL 的信息
    • request.path, 完整的路径,不含域名,但是包含前导斜线,如"/hello/"
    • request.get_host(),主机名(即常说的"域名"),如"127.0.0.1:8000"或"www.example.com"
    • request.get_full_path(), 包含查询字符串的路径,如"/hello/?print=true"
    • request.is_secure(), 通过HTTPS访问时为True,否则为False,
6.1.2 关于请求的其他信息
  • request.META 的值是一个Python字典,包含请求的所有HTTP首部,例如用户的IP地址和用户代理(user-agent)
    • 注意,具体包含哪些首部取决于用户发送了什么首部,以及Web 服务器返回了什么首部
    • 常见的键:
      • HTTP_REFERER:入站前的 URL(可能没有)。(注意,要使用错误的拼写,即REFERER。)
      • HTTP_USER_AGENT:浏览器的用户代理(可能没有)。例如:"Mozilla/5.0 (X11; U; Linux i686; fr-FR; rv:1.8.1.17) Gecko/20080829 Firefox/2.0.0.17"。
      • REMOTE_ADDR:客户端的 IP 地址,例如"12.345.67.89"。(如果请求经由代理,这个首部的值可能是一组 IP 地址,以逗号分隔,例如"12.345.67.89,23.456.78.90"。)
6.1.3 关于提交数据的信息
  • 除了关于请求的基本元数据之外,HttpRequest 对象还有两个属性包含用户提交的信息

    • request.GET 和request.POST
    • POST 数据一般由 HTML表单提交,而GET 数据既可以来自表单,也可以来自页面 URL 中的查询字符串
  • request.GET 和request.POST 是"类似字典的对象",但是不完全一样

    • 相同:都有get()、keys() 和values()等方法,而且可以使用for key in request.GET 迭代键
    • 不同:他俩都有常规的字典没有的额外方法,
6.2 一个简单的表单处理示例
  • 下面创建一个简单的视图,让用户通过书名搜索数据库中的图书

  • 一般来说,表单分为两部分:用户界面 HTML 和处理提交数据的后端视图代码

    *

  • books\views.py

python 复制代码
from django.shortcuts import render
from django.http import HttpResponse
# Create your views here.
def search_form(request):
    return render(request, 'search_form.html')

def search(request):
    if 'q' in request.GET:
        message = 'You searched for: %r' % request.GET['q']
    else:
        message = 'You submitted an empty form.'
    return HttpResponse(message)
  • mysite\templates\search_form.html
html 复制代码
<html>
<head>
<title>Search</title>
</head>
<body>
<form action="/search/" method="get">
    <input type="text" name="q">
    <input type="submit" value="Search">
</form>
</body>
</html>
  • mysite\mysite\urls.py
python 复制代码
from django.contrib import admin
from django.urls import path, re_path
from mysite.views import hello, current_datetime, hours_ahead, \
    current_datetime_template, current_datetime_render, display_meta
import books.views as books_views
urlpatterns = [
    path('admin/', admin.site.urls),
    path('hello/', hello),
    path('time/', current_datetime),
    path('time_template/', current_datetime_template),
    path('time_render/', current_datetime_render),
    re_path('time/plus/(\d{1,2})/$', hours_ahead),
    re_path("^search_form/$", books_views.search_form),
    re_path(r'^search/$', books_views.search),
    path('display_meta/', display_meta),
]
  • 说明
    • HTML表单定义一个变量q,q的值通过GET请求(method = 'get')传给/search/
    • 处理/search/的Django视图(search())通过request.GET访问q的值
6.2.1 查询字符串参数
  • GET数据通过查询字符串传递,使用request.GET获取查询字符串参数
  • books.views.py
python 复制代码
from django.shortcuts import render
from django.http import HttpResponse
from books.models import Book
# Create your views here.
def search_form(request):
    return render(request, 'search_form.html')

def search(request):
    if 'q' in request.GET and request.GET['q']:
        q = request.GET['q']
        books = Book.objects.filter(title__icontains=q)
        return render(request, 'search_results.html', {'books': books, 'query': q})
    else:
        message = 'You submitted an empty form.'
        return HttpResponse(message)
  • mysite\templates\search_results.html
html 复制代码
<html>
<head>
<title>Book Search</title>
</head>
<body>
<p>You searched for: <strong>{{ query }}</strong></p>
{% if books %}
<p>Found {{ books|length }} book{{ books|pluralize }}.</p>
<ul>
{% for book in books %}
<li>{{ book.title }}</li>
{% endfor %}
</ul>
{% else %}
<p>No books matched your search criteria.</p>
{% endif %}
</body>
</html>
  • 注意,这里用到了pluralize 模板过滤器,它会根据找到的图书数量输出正确的单复数。
6.3 改进这个简单的表单处理示例
python 复制代码
def search(request):
    if 'q' in request.GET and request.GET['q']:
        q = request.GET['q']
        books = Book.objects.filter(title__icontains=q)
        return render(request, 'search_results.html', {'books': books, 'query': q})
    else:
        return render(request, 'search_form.html', {'error': True})
  • mysite\templates\search_form.html 修改
html 复制代码
<html>
<head>
<title>Search</title>
</head>
<body>
{% if error %}
    <p style="color: red;">Please submit a search term.</p>
{% endif %}
<form action="/search/" method="get">
    <input type="text" name="q">
    <input type="submit" value="Search">
</form>
</body>
</html>
  • 当输入空的查询后,会更改显示

  • 下面移除search_form 视图

    • mysite\mysite\urls.py 注释 re_path("^search_form/$", books_views.search_form),
python 复制代码
from django.shortcuts import render
from django.http import HttpResponse
from books.models import Book
# Create your views here.

def search(request):
    error = False
    if 'q' in request.GET:
        q = request.GET['q']
        if not q:
            error = True
        else:
            books = Book.objects.filter(title__icontains=q)
            return render(request, 'search_results.html', {'books': books, 'query': q})
    return render(request, 'search_form.html', {'error': True})
html 复制代码
<html>
<head>
<title>Search</title>
</head>
<body>
{% if error %}
    <p style="color: red;">Please submit a search term.</p>
{% endif %}
<form action="" method="get">
    <input type="text" name="q">
    <input type="submit" value="Search">
</form>
</body>
</html>
  • <form action="/search/" method="get"> 改为<form action="" method="get">
    • action="" 的意思是: 把表单提交到与当前页面相同的 URL
6.4 简单的验证
  • mysite\books\views.py search 代码
python 复制代码
def search(request):
    errors = []
    if 'q' in request.GET:
        q = request.GET['q']
        if not q:
            errors.append("Enter a search term.")
        elif len(q) > 20:
            errors.append("Please enter at most 20 characters. ")
        else:
            books = Book.objects.filter(title__icontains=q)
            return render(request, 'search_results.html', {'books': books, 'query': q})
    return render(request, 'search_form.html', {'errors': errors})
  • mysite\templates\search_form.html代码
html 复制代码
<html>
<head>
<title>Search</title>
</head>
<body>
{% if errors %}
    <ul>
        {% for error in errors %}
            <li>{{ error }}</li>
        {% endfor %}
    </ul>

{% endif %}
<form action="" method="get">
    <input type="text" name="q">
    <input type="submit" value="Search">
</form>
</body>
</html>
6.5 创建一个联系表单
6.5.1 第一个表单类
  • Django 自带了一个表单库,django.forms,它能处理本章所述的多数问题,从显示 HTML 表单到验证

  • mysite\mysite\forms.py

python 复制代码
# -*- encoding: utf-8 -*-
from django import forms
class ContactForm(forms.Form):
    subject = forms.CharField()
    email = forms.EmailField(required=False)
    message = forms.CharField()
  • python manage.py shell

    • from mysite.forms import ContactForm

    • f = ContactForm()

    • 显示整个表单的方式

      • print(f)
      • print(f.as_ul())
      • print(f.as_p())
      • 刚才的输出中没有、
        • 和起始和结束标签
          * 所以你可以根据需要添加字段,或者做其他定制。
    • 只显示某个字段的 HTML

      • print(f['subject'])
      • print f['message']
  • Form 对象能做的第二件事是验证数

    • 创建一个Form 对象,传入一个数据字典,把字段名映射到数据上
      • f = ContactForm({'subject': 'Hello', 'email': 'adrian@example.com', 'message': 'Nice site!'})
    • 为Form 实例关联数据后,得到了一个"受约束的"表单
      • f.is_valid() (True)
    • 如果不传入email 字段,仍然是有效的,因为我们为那个字段指定了required=False
      • f = ContactForm({'subject': 'Hello', 'message': 'Nice site!'})
      • f.is_valid() (True)
    • 如果不提供subject 或message 字段,表单对象就变成无效的了
      • f = ContactForm({'subject': 'Hello', 'message': ''})
      • f.is_valid() (False)
      • 深入查看各个字段的错误消息
        • f['message'].errors
          • 'This field is required.'

        • f['subject'].errors
        • f['email'].errors
  • 每个受约束的表单实例都有一个errors 属性,它的值是一个字典,把字段名称映射到错误消息列表上

  • 最后,具有有效数据的表单实例有个cleaned_data 属性,它的值是一个字典,存储着"清理后的"提交数据

    f = ContactForm({'subject': 'Hello', 'email': 'adrian@example.com',
    'message': 'Nice site!'})
    f.is_valid() True
    f.cleaned_data
    {'message': 'Nice site!', 'email': 'adrian@example.com', 'subject':
    'Hello'}

6.6 在视图中使用表单对象
  • 如果不向用户显示,这个联系表单没有什么用,为此,首先要更新mysite/views

  • mysite\mysite\views.py 新增代码

python 复制代码
from mysite.forms import ContactForm
from django.http import HttpResponseRedirect
from django.core.mail import send_mail
def contact(request):
    if request.method == 'POST':
        form = ContactForm(request.POST)
        if form.is_valid():
            cd = form.cleaned_data
            send_mail(
                cd['subject'],
                cd['message'],
                cd.get('email', 'noreply@example.com'),
                ['18624323263@163.com']
            )
            return HttpResponseRedirect('/contact/thanks/')
    else:
        form = ContactForm()
    return render(request, 'contact_form.html', {'form': form})
  • mysite\templates\contact_form.html
html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Contact us</title>
</head>
<body>
    <h1>Contact us</h1>

    {% if form.errors %}

        <p style="color: red;">
            Please correct the error{{ form.errors|pluralize}} below.
        </p>
    {% endif %}

    <form action="" method="post">
        <table>
            {{ form.as_table}}
        </table>
        {% csrf_token %}
        <input type="submit" value="Submit">
    </form>
</body>
</html>
  • mysite\mysite\urls.py
python 复制代码
from django.contrib import admin
from django.urls import path, re_path
from mysite.views import hello, current_datetime, hours_ahead, \
    current_datetime_template, current_datetime_render, display_meta, \
    contact
import books.views as books_views
urlpatterns = [
    path('admin/', admin.site.urls),
    path('hello/', hello),
    path('time/', current_datetime),
    path('time_template/', current_datetime_template),
    path('time_render/', current_datetime_render),
    re_path('time/plus/(\d{1,2})/$', hours_ahead),
    # re_path("^search_form/$", books_views.search_form),
    re_path(r'^search/$', books_views.search),
    path('display_meta/', display_meta),
    path('contact/', contact),
]
  • 这个表单使用POST 处理(要修改数据),因此要关心跨站请求伪造(Cross Site Request Forgery,CSRF)
    • Django 提供了非常易用的防护系统
    • 所有通过POST 指向内部URL 的表单都应该使用{% csrf_token %} 模板标签
6.7 改变字段的渲染方式
  • mysite\mysite\forms.py
python 复制代码
# -*- encoding: utf-8 -*-
from django import forms
class ContactForm(forms.Form):
    subject = forms.CharField()
    email = forms.EmailField(required=False)
    message = forms.CharField(widget=forms.Textarea)
  • 表单框架把各个字段的表现逻辑交给widget 参数负责。
  • 每种字段的widget 参数都有默认值,不过可以轻易覆盖,或者自定义。
  • Field 类表示验证逻辑,而widget 表示表现逻辑。
6.8 设定最大长度
  • 最常见的验证需求是检查字段中的值是否为特定的长度
    • 只需为CharField 指定max_length 参数
    • subject = forms.CharField(max_length=100)
    • 此外,还有个min_length 参数。
6.9 设定初始值
  • 创建Form 实例时可以提供initial 参数:
    • form = ContactForm(initial={"subject": "I love u"})
  • 注意, 传递初始值与传递绑定表单的数据不同,二者之间最大的区别是,
    • 传递初始数据时,表单不受约束,因此不会产生任何错误消息。
6.10 自定义验证规则
  • mysite\mysite\forms.py
python 复制代码
# -*- encoding: utf-8 -*-
from django import forms
class ContactForm(forms.Form):
    subject = forms.CharField()
    email = forms.EmailField(required=False)
    message = forms.CharField(widget=forms.Textarea)

    def clean_message(self):
        message = self.cleaned_data['message']
        num_words = len(message.split())
        if num_words < 4:
            raise forms.ValidationError("Not enough words")
        return message
  • Django 的表单系统会自动查找名称以clean_ 开头、以字段名结尾的方法。
    • 如果存在这样的方法,在验证过程中调用。
    • clean_message() 方法会在指定字段的默认验证逻辑(这个CharField 是必填的)执行完毕后调用。
    • 这个异常中指定的字符串会出现在错误消息列表中显示给用户。注意,方法的最后一定要显式返回那个字段清理后的值
6.11 指定标注
  • 在 Django 自动生成的表单 HTML 中,默认的标注是把下划线替换成空格,并把首字母变成大写,
  • 与 Django 的模型一样,字段的标注是可以自定义的。方法很简单,指定label 参数即可,
  • email = forms.EmailField(required=False, label='Your e-mail address')
6.12 自定义表单的外观
html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Contact us</title>
</head>

<style type="text/css">
ul.errorlist {
margin: 0;
padding: 0;
}
.errorlist li {
background-color: red;
color: white;
display: block;
font-size: 10px;
margin: 0 0 3px;
padding: 4px 5px;
}
</style>
<body>
    <h1>Contact us</h1>

    {% if form.errors %}

        <p style="color: red;">
            Please correct the error{{ form.errors|pluralize}} below.
        </p>
    {% endif %}

    <form action="" method="post">
        <div class="field">
            {{ form.subject.errors }}
            <label for="id_subject">Subject:</label>
            {{ form.subject }}
        </div>
        <div class="field">
            {{ form.email.errors }}
            <label for="id_email">Your e-mail address: </label>
            {{ form.email }}
        </div>
       <div class="field{% if form.message.errors %} errors{% endif %}">
            {% if form.message.errors %}
                <ul>
                    {% for error in form.message.errors %}
                        <li><strong>{{ error }}</strong></li>
                    {% endfor %}
                </ul>
            {% endif %}
            <label for="id_message">Message:</label>
                {{ form.message }}
        </div>
        <!--<table>-->
            <!--{{ form.as_table}}-->
        <!--</table>-->
        {% csrf_token %}
        <input type="submit" value="Submit">
    </form>
</body>
</html>

最后打个广告

🌟 立即开启您的Python学习之旅!

欢迎来到 Python99网https://python99.com/python-courses

在这里,我们提供全面的 Python 体系课程,帮助您掌握编程技能,开启职业新篇章。仅需 2999元,即可享受终生课程更新和支持!


🚀 为什么选择我们?

  • 高效学习法

    探索项目实战,通过实践来掌握核心知识,遵循"用20%的时间,学会80%的知识"的原则,助您事半功倍!

  • 终身学习保障

    在快速变化的技术领域,学习永无止境。我们的课程会持续更新,确保您永远走在时代的前沿。

  • 多元化课程选择

    爬虫工程师后端开发 ,再到算法工程师,多样化的课程方向让您能根据个人发展目标灵活选择!

相关推荐
河码匠2 小时前
Django rest framework 自定义url
后端·python·django
哥只是传说中的小白2 小时前
无需验证手机Sora2也能用!视频生成,创建角色APi接入教程,开发小白也能轻松接入
数据库·人工智能
todoitbo2 小时前
书单之华为数据之道:企业数字化转型的实战宝典
数据库·华为·企业数字化转型·书单
曹牧2 小时前
Oracle:导出SQL
数据库·oracle
_OP_CHEN2 小时前
【Coze智能体开发】(三)解锁 Coze 智能体超能力:插件 + 知识库 + 数据库全解析,让 AI 从 “会聊天“ 到 “能办事“!
数据库·知识库·插件·coze·智能体开发·coze资源
Full Stack Developme2 小时前
达梦(DM8)对 JSON 与 XML 的使用教程
xml·数据库·json
想摆烂的不会研究的研究生10 小时前
每日八股——Redis(1)
数据库·经验分享·redis·后端·缓存
码熔burning10 小时前
MySQL 8.0 新特性爆笑盘点:从青铜到王者的骚操作都在这儿了!(万字详解,建议收藏)
数据库·mysql
猫头虎10 小时前
2025最新OpenEuler系统安装MySQL的详细教程
linux·服务器·数据库·sql·mysql·macos·openeuler