TOP

python学习笔记 Django进阶

 

https://www.cnblogs.com/wupeiqi/articles/6144178.html

一、Ajax

jquery的ajax

添加:

1、下载引入jquery

2、

$.ajax({
    url:'/add_classes.html',
    type:'GET',
    data:{'username':'root','password':'123'}
    success:function(asg){
        //回调函数,asg是服务器端返回的数据
    }
})

补充 js主动刷新页面:

window.location.reload()

 通过serialize()自动获取form标签input框内容

serialize() 方法通过序列化表单值,创建 URL 编码文本字符串。

$.ajax({
      data:$('#formid').serialize()        
})

 

二、分页

1、分页原理

最基本的分页原理,通过手动设置完成

将数据进行切片发送给前端,前端根据切片数据进行显示。

下一页下一页也是依据后端传输的值来前端设置

 views

from django.shortcuts import render
import math
# Create your views here.

# 模拟数据,自动生成一个列表
user_list = []
for i in range(1, 999):
    user_list.append('admin' + str(i) + '-000' + str(i))


def index(request):
    # 获取当前页码
    p = int(request.GET.get('p'))
    # 计算切片的起始位置
    start = (p-1) * 10
    # 计算切片的结束位置
    end = p * 10
    # 对数据列表进行切片
    obj_list = user_list[start:end]
    # 计算出总页数
    count_page = math.ceil(len(user_list)/10)
    
    # 对上一页进行判断
    # 如果已经到了首页了,就将上一页的页码设置为首页 
    if p <= 1:
        per_page = 1
    # 否则上一页的页码等于当前页码减一
    else:
        per_page = p - 1
        
    # 对下一页进行判断
    # 如果已经到了最后一页了,就将上一页的页码设置为总页数
    if p >= count_page:
        next_page = count_page
    # 否则下一页页码等于当前页码加一
    else:
        next_page = p + 1
    # 给前端发送切割后的数据,以及上一页页码,下一页页码
    return render(request, 'index.html', {"user_list": obj_list, 'per_page': per_page, 'next_page': next_page})

html

<body>
{% for i in user_list %}
    <li>{{ i }}</li>
{% endfor %}
<a href="/index?p={{ per_page }}">上一页</a>
<a href="/index?p={{ next_page }}">下一页</a>
</body>

2、Django内置分页

如果单单是为了完成上面的功能,可以利用Djando内置的分页方法来完成

- 两个对象 Paginator Page, 需要传入全部数据,每页显示条目数,当前页码

- 一定要利用include来减少html页码代码冗余

首先需要导入模块

from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
views

from django.shortcuts import render
from django.core.paginator import Paginator, PageNotAnInteger,  EmptyPage

# 模拟数据,自动生成一个列表
user_list = []
for i in range(1, 999):
    user_list.append('admin' + str(i) + '-000' + str(i))


def index(request):
    current_page = request.GET.get('p')
    # 实例化一个Paginator对象,需要传入数据列表和每页显示多少条数据的值
    paginator = Paginator(user_list, 10)
    # Paginator 的方法
    # per_page: 每页显示条目数量
    # count:    数据总个数
    # num_pages:总页数
    # page_range:总页数的索引范围,如: (1,10),(1,200)
    # page:     page对象
    try:
        # 实例化一个page对象,需要传入当前页码,这两个对象都有自己的方法,切两个对象互通
        posts = paginator.page(current_page)
        # page 的方法
        # has_next              是否有下一页
        # next_page_number      下一页页码
        # has_previous          是否有上一页
        # previous_page_number  上一页页码
        # object_list           分页之后的数据列表,已经切片好的数据
        # number                当前页
        # paginator             paginator对象
    # 判断当前页码是否为整数,如果是非整数则将当前页设置为1
    except PageNotAnInteger:
        posts = paginator.page(1)
    # 判断当前页码是否为空,意思是像-1 和 10000不在页码索引范围内的值,设置为总页数也就是最后一页
    except EmptyPage:
        posts = paginator.page(paginator.num_pages)
    return render(request, 'index.html', {'posts': posts})

html

<body>
{% for i in posts.object_list %}
    <li>{{ i }}</li>
{% endfor %}
{% if posts.has_previous %}
    <a href="/index?p={{ posts.previous_page_number }}">上一页</a>
{% else %}
    <a href="/index?p=#">上一页</a>
{% endif %}
{% if posts.has_next %}
    <a href="/index?p={{ posts.next_page_number }}">下一页</a>
{% else %}
    <a href="/index?p={{ posts.paginator.num_pages }}">下一页</a>
{% endif %}
</body>

3、扩展Django内置分页

 自定义一个类CustomPaginator继承Django的Paginator类,然后可以实现页码列表的扩展

views

from django.shortcuts import render
from django.core.paginator import Paginator, PageNotAnInteger,  EmptyPage

# 模拟数据,自动生成一个列表
user_list = []
for i in range(1, 666):
    user_list.append('admin' + str(i) + '-000' + str(i))


class CustomPaginator(Paginator):
    def __init__(self, current_page, max_pager_num, *args, **kwargs):
        # 通过当前页和最多显示页码、总页码,可以计算出页码的范围
        # 当前页
        self.current_page = int(current_page)
        # 页码最多显示多少页
        self.max_pager_num = max_pager_num
        # 通过super继承Paginator类的所有方法
        super(CustomPaginator, self).__init__(*args, **kwargs)

    def page_num_range(self):
        # 对极值的判断
        # 先判断总页数如果少于最多的页数时,范围为1到总页数
        if self.num_pages < self.max_pager_num:
            return range(1, self.num_pages)
        # 当总页数大于最多显示页数时,先取最多显示页码的一半
        part = int(self.max_pager_num/2)
        # 当当前页小于最多页码的一半时,页码范围为1到总页数
        # 对最左侧极值的判断,避免出现-1页
        if self.current_page <= part:
            return range(1, self.max_pager_num+1)
        # 当当前页加上part大于总页数时,显示的是当前页减去最多显示页码加一 到 当前页加上part的范围
        # 对最右侧极值的判断,避免出现大于总页数的页码
        if self.current_page + part >= self.num_pages:
            return range(self.num_pages-self.max_pager_num+1, self.num_pages+1)
        # 对于正常页码就是当前页码减去part 到 当前页码加上part的范围
        return range(self.current_page-part, self.current_page+part+1)


def index(request):
    current_page = request.GET.get('p')

    # 扩展就需要使用自定义的类CustomPaginator来实例化
    # 根据自定义需要传入当前页,最多显示页码, 数据列表,每页显示的条数
    paginator = CustomPaginator(current_page, 11, user_list, 10)

    try:
        # 实例化一个page对象,需要传入当前页码,这两个对象都有自己的方法,切两个对象互通
        posts = paginator.page(current_page)

    # 判断当前页码是否为整数,如果是非整数则将当前页设置为1
    except PageNotAnInteger:
        posts = paginator.page(1)
    # 判断当前页码是否为空,意思是像-1 和 10000不在页码索引范围内的值,设置为总页数也就是最后一页
    except EmptyPage:
        posts = paginator.page(paginator.num_pages)
    return render(request, 'index.html', {'posts': posts})

html

<body>
{% for i in posts.object_list %}
    <li>{{ i }}</li>
{% endfor %}
{% include 'include/pager.html' %}
</body>

include

{% if posts.has_previous %}
    <a href="/index?p={{ posts.previous_page_number }}">上一页</a>
{% else %}
    <a href="/index?p=#">上一页</a>
{% endif %}
{% for i in posts.paginator.page_num_range  %}
    {% if  i  == posts.number %}
        <a style="font-size: 25px" href="/index?p={{ i }}">{{ i }}</a>
    {% else %}
        <a href="/index?p={{ i }}">{{ i }}</a>
    {% endif %}

{% endfor %}
{% if posts.has_next %}
    <a href="/index?p={{ posts.next_page_number }}">下一页</a>
{% else %}
    <a href="/index?p={{ posts.paginator.num_pages }}">下一页</a>
{% endif %}
<span>
    {{ posts.number }}/{{ posts.paginator.num_pages }}
</span>

4、自定义分页

 上面传统的分页需要传入几个参数 

  ----所有的数据user_list

  ----当前页

  ----每页显示30条

  ----最多页码数

自定义分页,不需要传入多有的数据,影响性能

  ---所有数据的总个数

  ---当前页

  ---每页显示条数

  ---最多显示页码数

自定义一个py文件

pager.py

import math
from django.utils.safestring import mark_safe


class Paginator(object):
    def __init__(self, totalCount, currentPage, baseUrl, perItemPageNum=10, maxPageNum=7):
        # 数据的总个数
        self.total_count = totalCount
        try:
            format_currentPage = int(currentPage)
            if format_currentPage < 0:
                self.current_page = 1
            else:
                self.current_page = format_currentPage
        except Exception as e:
            # 当前页
            self.current_page = 1
        # 基础页
        self.base_url = baseUrl
        # 每页显示条目数
        self.per_item_pager_num = perItemPageNum
        # 最多显示多少页
        self.max_pager_num = maxPageNum

    # 数据切片的开始位置
    def start(self):
        if self.current_page > self.num_pages:
            self.current_page = self.num_pages
            return (self.num_pages - 1) * self.per_item_pager_num
        return (self.current_page - 1) * self.per_item_pager_num

    # 数据切片的结束位置
    def end(self):
        if self.current_page > self.num_pages:
            return self.num_pages * self.per_item_pager_num
        return self.current_page * self.per_item_pager_num

    # 数据的总页数
    @property
    def num_pages(self):
        # 利用了math的向上取整
        return math.ceil(self.total_count/self.per_item_pager_num)

    # 数据页码范围
    def page_num_range(self):
        # 对极值的判断
        # 先判断总页数如果少于最多的页数时,范围为1到总页数
        if self.num_pages < self.max_pager_num:
            return range(1, self.num_pages+1)
        # 当总页数大于最多显示页数时,先取最多显示页码的一半
        part = int(self.max_pager_num/2)
        # 当当前页小于最多页码的一半时,页码范围为1到总页数
        # 对最左侧极值的判断,避免出现-1页
        if self.current_page <= part:
            return range(1, self.max_pager_num+1)
        # 当当前页加上part大于总页数时,显示的是当前页减去最多显示页码加一 到 当前页加上part的范围
        # 对最右侧极值的判断,避免出现大于总页数的页码
        if self.current_page + part >= self.num_pages:
            return range(self.num_pages-self.max_pager_num+1, self.num_pages+1)
        # 对于正常页码就是当前页码减去part 到 当前页码加上part的范围
        return range(self.current_page-part, self.current_page+part+1)

    # 页码的html标签
    def page_str(self):
        page_list = []
        if self.current_page <= 1:
            first = "<li><a href=''>首页</a></li>"
        else:
            first = "<li><a href='{}{}'>首页</a></li>".format(self.base_url, 1)
        page_list.append(first)
        if self.current_page == 1:
            perv = '<li><a href="#">上一页</a>'
        else:
            perv = '<li><a href="{}{}">上一页</a></li>'.format(self.base_url, self.current_page-1)
        page_list.append(perv)
        for i in self.page_num_range():
            if i == self.current_page:
                temp = '<li class="active"><a href="{}{}">{}</a></li>'.format(self.base_url, i, i)
            else:
                temp = '<li><a href="{}{}">{}</a></li>'.format(self.base_url, i, i)
            page_list.append(temp)
        if self.current_page >= self.num_pages:
            nex = '<li><a href="#">下一页</a>'
        else:
            nex = '<li><a href="{}{}">下一页</a>'.format(self.base_url, self.current_page+1)
        page_list.append(nex)
        if self.current_page >= self.num_pages:
            last = "<li><a href=''>尾页</a></li>"
        else:
            last = "<a href='{}{}'>尾页</a></li>".format(self.base_url, self.num_pages)
        page_list.append(last)
        result = ''.join(page_list)
        # 把字符串转成html语言,否则就需要在html页面加上 |safe
        return mark_safe(result)

views

from django.shortcuts import render
from app01.pager import Paginator # 导入自定义模块

# 模拟数据,自动生成一个列表
user_list = []
for i in range(1, 600):
    user_list.append('admin' + str(i) + '-000' + str(i))

def index2(request):
    current_page = request.GET.get('p')
    baseurl = request.path + '?p=' # 获取基础页
    page_obj = Paginator(600, current_page, baseurl)
    data = user_list[page_obj.start():page_obj.end()]
    return render(request, 'index2.html', {'data': data, 'page_obj': page_obj})

html

<body>
{% for i in data %}
    <li>{{ i }}</li>
{% endfor %}
<ul class="pagination">
    {{ page_obj.page_str}}
</ul>
</body>

总结,分页时需要做三件事,自定义分页不需要将多有数据传入进行切片,只需要知道数据总个数就可以,增加后台的可靠性:

  • 创建自定义的类,需要传入数据总个数,当前页,基础页参数
  • 根据总个数和每页条目数计算出总页数,根据当前页计算出数据的切片位置,根据当前页和最多显示页数计算出页码的范围
  • 创建标签文本列表,转换成字符串(首页尾页上一页下一页,【1】【2】【3】等),然后通过mark_safe方法将字符串转换成html语言,否则需要在前端文件添加 |safe

 

Form组件

Django的Form主要具有一下几大功能:

  • 生成HTML标签
  • 验证用户数据(显示错误信息)
  • HTML Form提交保留上次提交数据
  • 初始化页面显示内容

1、对用户请求的验证

  -ajax

  -form

views:

from django.shortcuts import render, HttpResponse
# 导入forms和fields
from django import forms
from django.forms import fields


# 创建一个类,继承forms.Form
class FormF1(forms.Form):
    # 创建字段,匹配前端数据(包含正则表达式)
    user = fields.CharField(
        # 验证数据
        # 最长6位
        max_length=6,
        # 不能为空
        required=True,
        # 错误信息中文表示
        error_messages={
            'required': '不能为空',
            'max_length': '最长6位数',
            'invalid': '格式错误',
        }
    )
    pwd = fields.CharField(
        min_length=8,
        required=True
    )
    age = fields.IntegerField(
        required=True,
        error_messages={
            'required': '不能为空',
            'invalid': '格式错误',
        }
    )
    email = fields.EmailField(
        required=True,
        error_messages={
            'required': '不能为空',
            'invalid': '格式错误',
        }
    )


def index(request):
    if request.method == 'GET':
        return render(request, 'index.html')
    else:
        # 将post数据传入FormF1类实例化对象obj
        obj = FormF1(request.POST)
        # 通过is_valid方法判断数据是否正确
        if obj.is_valid():
            # 如果数据验证通过,可以通过obj.cleaned_data得到字典
            print(obj.cleaned_data)
            return HttpResponse('ok')
        
        else:
            # 如果验证失败可以通过obj.errors来获取错误提示
            print(obj.errors)
            return render(request, 'index.html', {'obj': obj})

index.html:

<form action="/index/" method="post">
    {#  post数据传入后端,通过name进行匹配字段进行验证  #}
    {#  obj.errors.user.0 因为错误提示可能有多条,此处选择优先显示第一条即可   #}
    <p><input type="text" name="user">{{ obj.errors.user.0 }}</p>
    <p><input type="text" name="pwd">{{ obj.errors.pwd.0 }}</p>
    <p><input type="text" name="age">{{ obj.errors.age.0 }}</p>
    <p><input type="text" name="email">{{ obj.errors.email.0 }}</p>
    <input type="submit">
</form>
</body>

 

2、生成HTML代码

上面的FormF1类有另外一个功能就是生成HTML代码,可以避免前端和后端同时开发时,因为name名不统一造成无法匹配

对于上面views的改动

def index(request):
    if request.method == 'GET':
        # 不传参数,生成对象,前端可以通过obj.user....生成对应的HTML标签
        obj = FormF1()
        return render(request, 'index.html', {'obj': obj})
    else:
        # 将post数据传入FormF1类实例化对象obj
        obj = FormF1(request.POST)
        # 通过is_valid方法判断数据是否正确
        if obj.is_valid():
            # 如果数据验证通过,可以通过obj.cleaned_data得到字典
            print(obj.cleaned_data)
            return HttpResponse('ok')

        else:
            # 如果验证失败可以通过obj.errors来获取错误提示
# HTML Form提交保留上次提交数据
            print(obj.errors)
            return render(request, 'index.html', {'obj': obj})

index.html:

前端不需要自己再写input框了,通过后端传的obj直接生成html标签

<form action="/index/" method="post">
    {#  通过{{ obj.user }}调用html代码  #}
    <p>{{ obj.user }}{{ obj.errors.user.0 }}</p>
    <p>{{ obj.pwd }}{{ obj.errors.pwd.0 }}</p>
    <p>{{ obj.age }}{{ obj.errors.age.0 }}</p>
    <p>{{ obj.email }}{{ obj.errors.email.0 }}</p>
    <input type="submit">
</form>
</body>

 3、Form简单运用

  简单的input框 增 改 查

框架结构,form类用一个单独的py文件

 

 models:

from django.db import models

# 创建数据库,字段
class UserInfo(models.Model):
    username = models.CharField(max_length=32)
    email = models.CharField(max_length=32)

formr.py

from django import forms
from django.forms import fields

class UserForm(forms.Form):
    username = fields.CharField(min_length=6, required=True)
    email = fields.EmailField()

views:

from django.shortcuts import render
from django.shortcuts import redirect
# 导入form类
from app01 import formr
# 导入数据库模板
from app01 import models


#
def users(request):
    if request.method == 'GET':
        user_list = models.UserInfo.objects.all()
        obj = formr.UserForm()
        return render(request, 'users.html', {'obj': obj, 'user_list': user_list})


#
def add_user(request):
    if request.method == 'GET':
        # 通过form类直接生成html标签
        obj = formr.UserForm()
        return render(request, 'add_user.html', {'obj': obj})
    else:
        obj = formr.UserForm(request.POST)
        if obj.is_valid():
            # 将验证通过的字典,直接传入数据库创建, 原理是create可以接受字典数据
            models.UserInfo.objects.create(**obj.cleaned_data)
            return redirect('/users/')
        else:
            return render(request, 'add_user.html', {'obj': obj})


#
def edit_user(request):
    # 通过nid定位
    nid = request.GET.get('nid')
    if request.method == 'GET':
        user = models.UserInfo.objects.filter(id=nid).first()
        # 通过nid查询到的值,传入form类,生成html标签时自带默认值,form类也可以传入字典数据
        obj = formr.UserForm({'username': user.username, 'email': user.email})
        return render(request, 'edit_user.html', {'obj': obj, 'nid': nid})
    else:
        obj = formr.UserForm(request.POST)
        if obj.is_valid():
            models.UserInfo.objects.filter(id=nid).update(**obj.cleaned_data)
            return redirect("/users")
        else:
            return render(request, 'edit_user.html', {'obj': obj})

html文件 前端也用一行代码代替{{obj.as_p}},所有的obj字段转换成一个p标签

-----------------users.html--------------------
<body>
<a href="/add_user/">添加</a>
<ul>
    {% for row in user_list %}
        <li>{{ row.id }}-{{ row.username }}-{{ row.email }}<a href="/edit_user/?nid={{ row.id }}">编辑</a></li>
    {% endfor %}
</ul>
</body>

--------------add_user.html----------------
<body>
<form action="/add_user/" method="post" novalidate>
    {% csrf_token %}
    <p>{{ obj.username }}{{ obj.username.errors.0 }}</p>
    <p>{{ obj.email }}{{ obj.email.errors.0 }}</p>
    <p><input type="submit" value="提交"></p>
</form>
</body>

----------------edit_user.html-------------
<body>
<form action="/edit_user/?nid={{ nid }}" method="post" novalidate>
    {% csrf_token %}
    <p>{{ obj.username }}{{ obj.username.errors.0 }}</p>
    <p>{{ obj.email }}{{ obj.email.errors.0 }}</p>
    <p><input type="submit" value="提交"></p>
</form>
</body>

 4、Form类常用字段和插件

      创建Form类时,主要涉及到 【字段】 和 【插件】,字段用于对用户请求数据的验证,插件用于自动生成HTML;

(1)、Django内置字段如下:红色为重点掌握

 所有字段都继承到了Field

Field
    required=True,               是否允许为空
    widget=None,                 HTML插件
    label=None,                  用于生成Label标签或显示内容
    initial=None,                初始值
    help_text='',                帮助信息(在标签旁边显示)
    error_messages=None,         错误信息 {'required': '不能为空', 'invalid': '格式错误'}
    show_hidden_initial=False,   是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直)
    validators=[],               自定义验证规则
    localize=False,              是否支持本地化
    disabled=False,              是否可以编辑
    label_suffix=None            Label内容后缀
 
 
CharField(Field)
    max_length=None,             最大长度
    min_length=None,             最小长度
    strip=True                   是否移除用户输入空白
 
IntegerField(Field)
    max_value=None,              最大值
    min_value=None,              最小值
 
FloatField(IntegerField)
    ...
 
DecimalField(IntegerField)
    max_value=None,              最大值
    min_value=None,              最小值
    max_digits=None,             总长度
    decimal_places=None,         小数位长度
 
BaseTemporalField(Field)
    input_formats=None          时间格式化   
 
DateField(BaseTemporalField)    格式:2015-09-01
TimeField(BaseTemporalField)    格式:11:12
DateTimeField(BaseTemporalField)格式:2015-09-01 11:12
 
DurationField(Field)            时间间隔:%d %H:%M:%S.%f
    ...
 
RegexField(CharField)
    regex,                      自定制正则表达式
    max_length=None,            最大长度
    min_length=None,            最小长度
    error_message=None,         忽略,错误信息使用 error_messages={'invalid': '...'}
 
EmailField(CharField)      
    ...
 
FileField(Field)
    allow_empty_file=False     是否允许空文件
 
ImageField(FileField)      
    ...
    注:需要PIL模块,pip3 install Pillow
    以上两个字典使用时,需要注意两点:
        - form表单中 enctype="multipart/form-data"
        - view函数中 obj = MyForm(request.POST, request.FILES)
 
URLField(Field)
    ...
 
 
BooleanField(Field)  
    ...
 
NullBooleanField(BooleanField)
    ...
 
ChoiceField(Field)
    ...
    choices=(),                选项,如:choices = ((0,'上海'),(1,'北京'),)
    required=True,             是否必填
    widget=None,               插件,默认select插件
    label=None,                Label内容
    initial=None,              初始值
    help_text='',              帮助提示
 
 
ModelChoiceField(ChoiceField)
    ...                        django.forms.models.ModelChoiceField
    queryset,                  # 查询数据库中的数据
    empty_label="---------",   # 默认空显示内容
    to_field_name=None,        # HTML中value的值对应的字段
    limit_choices_to=None      # ModelForm中对queryset二次筛选
     
ModelMultipleChoiceField(ModelChoiceField)
    ...                        django.forms.models.ModelMultipleChoiceField
 
 
     
TypedChoiceField(ChoiceField)
    coerce = lambda val: val   对选中的值进行一次转换
    empty_value= ''            空值的默认值
 
MultipleChoiceField(ChoiceField)
    ...
 
TypedMultipleChoiceField(MultipleChoiceField)
    coerce = lambda val: val   对选中的每一个值进行一次转换
    empty_value= ''            空值的默认值
 
ComboField(Field)
    fields=()                  使用多个验证,如下:即验证最大长度20,又验证邮箱格式
                               fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])
 
MultiValueField(Field)
    PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用
 
SplitDateTimeField(MultiValueField)
    input_date_formats=None,   格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
    input_time_formats=None    格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
 
FilePathField(ChoiceField)     文件选项,目录下文件显示在页面中
    path,                      文件夹路径
    match=None,                正则匹配
    recursive=False,           递归下面的文件夹
    allow_files=True,          允许文件
    allow_folders=False,       允许文件夹
    required=True,
    widget=None,
    label=None,
    initial=None,
    help_text=''
 
GenericIPAddressField
    protocol='both',           both,ipv4,ipv6支持的IP格式
    unpack_ipv4=False          解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用
 
SlugField(CharField)           数字,字母,下划线,减号(连字符)
    ...
 
UUIDField(CharField)           uuid类型 UUID是根据MAC以及当前时间等创建的不重复的随机字符串
    ...

(2)、Django内置插件:

 1 TextInput(Input)
 2 NumberInput(TextInput)
 3 EmailInput(TextInput)
 4 URLInput(TextInput)
 5 PasswordInput(TextInput)
 6 HiddenInput(TextInput)
 7 Textarea(Widget)
 8 DateInput(DateTimeBaseInput)
 9 DateTimeInput(DateTimeBaseInput)
10 TimeInput(DateTimeBaseInput)
11 CheckboxInput
12 Select
13 NullBooleanSelect
14 SelectMultiple
15 RadioSelect
16 CheckboxSelectMultiple
17 FileInput
18 ClearableFileInput
19 MultipleHiddenInput
20 SplitDateTimeWidget
21 SplitHiddenDateTimeWidget
22 SelectDateWidget

5、常用选择插件

# 单radio,值为字符串
# user = fields.CharField(
#     initial=2,
#     widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),))
# )
 
# 单radio,值为字符串
# user = fields.ChoiceField(
#     choices=((1, '上海'), (2, '北京'),),
#     initial=2,
#     widget=widgets.RadioSelect
# )
 
# 单select,值为字符串
# user = fields.CharField(
#     initial=2,
#     widget=widgets.Select(choices=((1,'上海'),(2,'北京'),))
# )
 
# 单select,值为字符串
# user = fields.ChoiceField(
#     choices=((1, '上海'), (2, '北京'),),
#     initial=2,
#     widget=widgets.Select
# )


# 多选select,值为列表
# xdb = fields.CharField(
# widget=widgets.SelectMultiple(choices=[(1, '武昌'), (2, '江岸'), (3, '汉口')],)
# )

#
多选select,值为列表 # user = fields.MultipleChoiceField( # choices=((1,'上海'),(2,'北京'),), # initial=[1,], # widget=widgets.SelectMultiple # ) # 单checkbox # user = fields.CharField( # widget=widgets.CheckboxInput() # ) # 多选checkbox,值为列表 # user = fields.MultipleChoiceField( # initial=[2, ], # choices=((1, '上海'), (2, '北京'),), # widget=widgets.CheckboxSelectMultiple # )

 

特殊的单选或多选时,数据能否实时更新??

 

 

结果是 ----数据库数据删除了一组数据,变成两个用户,而页面没有实时更新,还是三个用户!!! 如果想要实时更新,需要重启程序。这个不合理!

原因是页面加载时,首先执行了obj = Loveform(), Loveform()是一个类,所以实例化的过程首先执行了类属性__init__方法,并将结果加载到内存中

Loveform()默认没有__init__方法,所以调用了父类的init方法,将price和user_id的值加入了内存中,所以无论怎么刷新也不会实时更新,必须重启程序,

可以得到正确的结果。

代码如下

views:

class Loveform(forms.Form):
    price = fields.IntegerField()
    user_id = fields.IntegerField(
        widget=widgets.Select(choices=models.UserInfo.objects.values_list('id', 'username'))
    )


def love(request):
    obj = Loveform()
    return render(request,'love.html', {'obj':obj})

html:

<form action="">
    <p>价格:{{ obj.price }}</p>
    <p>姓名:{{ obj.user_id }}</p>
</form>

model:

class UserInfo(models.Model):
    username = models.CharField(max_length=32)
    email = models.CharField(max_length=32)

解决办法

方式一:自定义构造方法

1、自定义init方法,同时继承父类的init方法,这个步骤相当于没有做任何操作,但是可以自定制__init__方法了

  def __init__(self, *args, **kwargs):
        super(Loveform, self).__init__(*args, **kwargs)

2、已知每次执行Loveform()都会先执行__init__方法, 所以将user_id的值在__init__中定义,并将数据库查询结果赋值,再加载到内存中的记录。

    def __init__(self, *args, **kwargs):
        # self.fields必须在super后面,因为super会先将Lovefrom中的字段拷贝一份到self.fields中,所以必须在前面
        # 否则self.fields中就还没有user_id这个字段
        super(Loveform, self).__init__(*args, **kwargs)
        # self.fields 就是Loveform这些字段,字段里面有user_id,然后这个字段又有widget.choices
        self.fields['user_id'].widget.choices = models.UserInfo.objects.values_list('id', 'username')

代码如下

views:

class Loveform(forms.Form):
    price = fields.IntegerField()
    user_id = fields.IntegerField(
        widget=widgets.Select()
    )

    def __init__(self, *args, **kwargs):
        # self.fields必须在super后面,因为super会先将Lovefrom中的字段拷贝一份到self.fields中,所以必须在前面
        # 否则self.fields中就还没有user_id这个字段
        super(Loveform, self).__init__(*args, **kwargs)
        # self.fields 就是Loveform里面的所有字段,字段里面有user_id,然后这个字段又有widget.choices,可以赋值
        self.fields['user_id'].widget.choices = models.UserInfo.objects.values_list('id', 'username')


def love(request):
    obj = Loveform()
    return render(request,'love.html', {'obj': obj})

html:

方式二:实时更新的另一种办法,django内置,不推荐使用

ModelChoiceField(ChoiceField)
    ...                        django.forms.models.ModelChoiceField
    queryset,                  # 查询数据库中的数据
    empty_label="---------",   # 默认空显示内容
    to_field_name=None,        # HTML中value的值对应的字段
    limit_choices_to=None      # ModelForm中对queryset二次筛选
     
ModelMultipleChoiceField(ModelChoiceField)
    ...                        django.forms.models.ModelMultipleChoiceField

Model这两个是跟数据库相关的字段,里面有个参数是queryset,直接可以拿数据库查询结果

代码如下:

from django.forms.models import ModelChoiceField

class Loveform(forms.Form):
    price = fields.IntegerField()
    # user_id = fields.IntegerField(
    #     widget=widgets.Select()
    # )
    user_id2 = ModelChoiceField(
        queryset=models.UserInfo.objects.all(),
        to_field_name='username'
    )

def love(request):
    obj = Loveform()
    return render(request,'love.html', {'obj': obj})

 

 数据是实时更新了,但是页面显示的是一个对象名,不是我们想要的username

这个需要在model中加一个str方法: 如果说这个方法简单的话,他也有一个致命的弱点,那就是耦合性太高

这次我需要的是username,如果还需要email实时更新的话就没办法实现了,因为str方法只有一个,同时也只

能return一个参数,所以此方法不推荐使用!!!

class UserInfo(models.Model):
    username = models.CharField(max_length=32)
    email = models.CharField(max_length=32)

    def __str__(self):
        return self.username

 6、Form组件扩展,自定义验证规则

 方式一:

from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.validators import RegexValidator


class MyForm(forms.Form):
    user = fields.CharField(
        # 利用父类的validators方法自定义验证规则,此规则是一个列表,可以同时验证多个规则
        validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],
    )
RegexValidator两个关键参数,第一个是正则表达式,第二个是错误信息

方式二:

class MyForm(forms.Form):
    # 利用RegexField 字段实现自定义规则
    user = fields.RegexField(
        regex=r'^[0-9]+$',
        error_messages={
            'invalid': '请输入数字',
            'required': '输入不能为空',
        }
    )

 

 方式三:

import re
from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.exceptions import ValidationError
 
 
# 自定义验证规则
def mobile_validate(value):
    mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
    if not mobile_re.match(value):
        raise ValidationError('手机号码格式错误')
 
 
class PublishForm(Form):
    # 使用自定义验证规则
    phone = fields.CharField(validators=[mobile_validate, ],
                            error_messages={'required': '手机不能为空'},
                            widget=widgets.TextInput(attrs={'class': "form-control",
                                                          'placeholder': u'手机号码'}))

方式四:自定义方法

当我们需要验证form表单数据同时,也需要判断该数据时候在数据库中存在,比如ID

第一思路是在  if obj.is_valid(): 下面做各种判断,如果不正确那么errors字典中添加错误。

其实Django有更简单的方法,先看下面的源码。

 

 当数据经过正则匹配以后会查找是否有clean_字段名 这样一个方法,如果有就执行这个方法此时就可以自定制规则,再做一次判断

如果有错误需要raise ValidationError错误,只有这个错误才会被捕捉到,然后返回错误信息

代码如下:

class MyForm(forms.Form):
    # 利用RegexField 字段实现自定义规则
    username = fields.RegexField(
        regex=r'^[\u4e00-\u9fa5]+$',
        error_messages={
            'invalid': '请输入中文',
            'required': '输入不能为空',
        }
    )
    
    # 自定义方法 clean_字段名, Form中字段中定义的格式匹配完之后,执行此方法进行验证
    # 返回值必须是self.cleaned_data['字段名']
    # 如果判断不正确,需要手动抛出错误raise ValidationError(),错误提示信息直接加到括号内
    def clean_username(self):
        v = self.cleaned_data['username']
        if models.UserInfo.objects.filter(username=v).count():
            raise ValidationError('用户名已存在')
        else:
            return v

此方法的缺陷是自定义方法 clean_字段名 只能对当前字段验证,不能验证其他字段

 

 方式四:

验证多个条件时:利用def clean(self):

    def clean(self):
        value_dict = self.cleaned_data
        v1 = value_dict.get('user_name')
        v2 = value_dict.get("user_id")
        if v1 == 'root' and v2 == '1':
            raise ValidationError('整体信息错误!')
        return self.cleaned_data

 

7、序列化

在通过数据库调用数据是,大概有四种类型的数据

第一种是Queryset集合集合中是对象,由于对象不是python的基本数据类型,所以不能直接用json.dumps


from django.core import serializers

def
get_data(request): ret = {'status': False, "message": None} try: user_list = models.Cpu_name.objects.all() print(user_list) # <QuerySet [<Cpu_name: Cpu_name object (1)>, <Cpu_name: Cpu_name object (13)>]> ret['message'] = serializers.serialize('json', user_list) return HttpResponse(json.dumps(ret)) except Exception: pass

返回给前端的是一个字符串,user_list被serialize序列化了一次,还有一次json.dumps,所以需要在前端进行两次json.parse反序列化

注意serialize只能序列化对象,其他的类型不可以

 

第二种是Queryset集合中是一个列表,列表是python的基本数据类型所以可以直接利用json.dumps序列化

def get_data(request):
    ret = {'status': False, "message": None}
    try:
        user_list = models.Cpu_name.objects.all().values('cpu_name', 'cpu_code')
        print(user_list)
        # <QuerySet [{'cpu_name': '骁龙 865', 'cpu_code': 'sm8250'}, {'cpu_name': '2', 'cpu_code': '3'}]>
        # QuerySet中是一个列表,我们只需要通过list将他转化成列表即可
        ret['message'] = list(user_list)
        print(ret['message'])
        # [{'cpu_name': '骁龙 865', 'cpu_code': 'sm8250'}, {'cpu_name': '2', 'cpu_code': '3'}]

        return HttpResponse(json.dumps(ret))
    except Exception:
        pass

 

第三种和第二种大致相同,结果是一个元祖,元祖也是基本数据类型

def get_data(request):
    ret = {'status': False, "message": None}
    try:
        user_list = models.Cpu_name.objects.all().values_list('cpu_name', 'cpu_code')
        print(user_list)
        # <QuerySet [('骁龙 865', 'sm8250'), ('2', '3')]>
        # QuerySet中是一个列表,我们只需要通过list将他转化成列表即可
        ret['message'] = list(user_list)
        print(ret['message'])
        # [('骁龙 865', 'sm8250'), ('2', '3')]

        return HttpResponse(json.dumps(ret))
    except Exception:
        pass

 

第四种,时间日期

由于json.dumps时无法处理datetime日期,所以可以通过自定义处理器来做扩展,如:

import json 
from datetime import date 
from datetime import datetime 
   
class JsonCustomEncoder(json.JSONEncoder): 
    
    def default(self, field): 
     
        if isinstance(field, datetime): 
            return o.strftime('%Y-%m-%d %H:%M:%S') 
        elif isinstance(field, date): 
            return o.strftime('%Y-%m-%d') 
        else: 
            return json.JSONEncoder.default(self, field) 
   
   
# ds = json.dumps(d, cls=JsonCustomEncoder) 

 

 

总结,四种数据类型,如果是对象,需要用serialize先进行序列化,然后再用json.dumps序列化

          如果是列表或者元祖可以直接利用json.dumps序列化



 
posted @ 2020-11-05 00:13  liqinsan  阅读(102)  评论(0)    收藏  举报