django~2

本节内容:

1:cookie和session

2:请求生命周期的FBV和CBV

3:model的操作,复习

4:分页的实现

5:Django Form

6:页面请求models里面的all时候的几种实现

7:文件上传

一:cookie和session

1:COOKIE诞生的背景和工作原理

cookie不属于http协议范围,由于http协议无法保持状态,但实际情况,我们却又需要“保持状态”,因此cookie就是在这样一个场景下诞生。

cookie的工作原理是:由服务器产生内容,浏览器收到请求后保存在本地磁盘上的;当浏览器再次访问时,浏览器会自动带上cookie,这样服务器就能通过cookie的内容来判断这个是“谁”了。

2:session诞生的背景

cookie虽然在一定程度上解决了“保持状态”的需求,但是由于cookie本身最大支持4096字节,以及cookie本身保存在客户端,可能被拦截或窃取,
因此就需要有一种新的东西,它能支持更多的字节,并且他保存在服务器,有较高的安全性。这就是session。

3:cookie和session的结合

问题来了,基于http协议的无状态特征,服务器根本就不知道访问者是“谁”。那么上述的cookie就起到桥接的作用。

(1)我们可以给每个客户端的cookie分配一个唯一的id,这样用户在访问时,通过cookie,服务器就知道来的人是“谁”。然后我们再根据不同的cookie的id,在服务器上保存一段时间的私密资料,如“账号密码”等等。

(2)总结而言:cookie弥补了http无状态的不足,让服务器知道来的人是“谁”;但是cookie以文本的形式保存在本地,自身安全性较差;所以我们就通过cookie识别不同的用户,对应的在session里保存私密的信息以及超过4096字节的文本。

(3)另外,上述所说的cookie和session其实是共通性的东西,不限于语言和框架

 4:设置cookie

def login(request):
    print("COOKIE",request.COOKIES)
    ##COOKIE {'csrftoken': 'R3Z7KuPa1pUW63PWPIog9CNGzMOE427NQRFDXNPA2tEq9O5VKFenQ5ixcvELu6e6', 'sessionid': 'naqh3zfp2pd6zl8dh2hvuzv5wihhjbwh'}
    if request.method == "POST" and request.POST.get("username") == "yuan":
        ret = redirect("/index/")
        ret.set_cookie("username","yuan")  ###设置cookie 组成一组键值对
        return ret
    return render(request,"login.html")

5:cookie和session的配合

"COOKIE":
{
"sessionid":"就是session给你的随机字符串,用于打开session的""csrftoken":"中间件防止跨站伪造请求"
}

"SESSION":
{
"COOKIES中的随机字符串":{"服务端的私密的数据"},
}
注意:session可以保存在任何的位置,文件、数据库、缓存都是可以的。为什么我们再建表的时候会产生那么多表?
其中一个表就是session表。

实现cookie和session的配合:

from django.shortcuts import render,redirect,HttpResponse

# Create your views here.


def login(request):
    print("COOKIE",request.COOKIES)
    print("session",request.session) ##会发现没有提示,到setting的MIDDLEWARE的改成MIDDLEWARE_CLASSES
    ##COOKIE {'csrftoken': 'R3Z7KuPa1pUW63PWPIog9CNGzMOE427NQRFDXNPA2tEq9O5VKFenQ5ixcvELu6e6', 'sessionid': 'naqh3zfp2pd6zl8dh2hvuzv5wihhjbwh'}
    if request.method == "POST" and request.POST.get("username") == "yuan":
        # ret = redirect("/index/")
        # ret.set_cookie("username","yuan")  ###设置cookie
        # return ret
        request.session["is_login"] = True
        request.session["username"] = "yuan"
        return redirect("/index/")
    return render(request,"login.html")


def index(request):
    # username = request.COOKIES.get("username",None)
    # print(request.COOKIES)
    # ###{'csrftoken': 'R3Z7KuPa1pUW63PWPIog9CNGzMOE427NQRFDXNPA2tEq9O5VKFenQ5ixcvELu6e6', 'sessionid': 'naqh3zfp2pd6zl8dh2hvuzv5wihhjbwh', 'username': 'yuan'}
    # if username == "yuan":
    #     return HttpResponse("hello")
    if request.session.get("is_login") and request.session.get("username") == "yuan":
        return HttpResponse("hello")
    else:
        return redirect("/login/")
View Code

如果出现如下错误:

因为上文说了:session默认是保存到session的表中的,而你没有这个表就出错了:解决python manage.py migrate

D:pythonprojectcookie_session>python manage.py makemigrations
No changes detected

D:pythonprojectcookie_session>python manage.py migrate

 

 下面我们再来最后的总结一下cookie和session的知识点:

一、操作Cookie

  获取cookie:request.COOKIES[key]

  设置cookie:response.set_cookie(key,value)

由于cookie保存在客户端的电脑上,所以,jquery也可以操作cookie。

<script src='http://830909.blog.51cto.com/static/js/jquery.cookie.js'></script>
$.cookie("list_pager_num", 30,{ path: '/' });

二、操作Session(session默认在服务器端保存15天)

  获取session:request.session[key]

  设置session:reqeust.session[key] = value

  删除session:del request.session[key]    

(这个删除其实就是把数据库的session_data更新为一个其他的值了,并没有立即删除)

request.session.set_expiry(value)
* 如果value是个整数,session会在些秒数后失效。
* 如果value是个datatime或timedelta,session就会在这个时间后失效。
* 如果value是0,用户关闭浏览器session就会失效。
* 如果value是None,session会依赖全局session失效策略。

二、请求生命周期的FBV和CBV

1:Django的请求生命周期:

指的是当用户在浏览器输入了:http://127.0.0.1:8888/index.html 后都发生了什么事情
1:生成请求体,发送Http请求
2:服务端接收 从上到下匹配url 
3:匹配成功后,执行指定的views的函数或类
    URL -->(对应) 函数  ===》这叫FBV  
    URL -->(对应) 类   ===》 这叫CBV     
        请求到达CBV的类的时候,不会马上执行get或post方法
        他直接执行继承View的dispatch方法通过反射去执行,get或post方法。 
        执行完get或post之后,要把结果给dispatch,由dispatch交给用户。
        
4:业务处理
    -根据个人需求自定
    -操作数据库
        -支持原生sql 
        -Django的 ORM(可能是最牛的ORM框架)
    ===》上面都处理完了产生返回给用户的结果《===
    -响应内容
        -响应头
        -响应体 

怎么往响应头加点东西:

def  post(self,request):
    ret =  HttpResponse("CBV.POST") ##响应内容就是"CBV.POST"
    ret['h1'] = 'v1'   ##这就是往响应头加上了h1 = v1
    ret.set_cookie('c1','v2') ##还可以往响应头设置cookie
    return ret
        
View Code

2:CBV的实现:

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^CBV$', views.CBV.as_view()),  ##cbv后面的/ 不加django会默认给你加的
    url(r'^FBV$', views.index),
]
urls
from  django.views import View
class CBV(View):   ##要继承View ,是get请求就执行get函数
    def get(self,request):
        ##根据请求头中的request.method执行对应的函数
        # return  HttpResponse("CBV.GET")
        return render(request,"CBV.html")

    def  post(self,request):
        return HttpResponse("CBV.POST")
views
##CBV.html#####

<form action="" method="post">
    <input type="submit">
</form>
template

CBV中的dispatch方法: 找到类 -- >dispatch反射执行函数--->函数执行完 返回结果 --->dispatch函数--->dispatch再返回给用户

from  django.views import View
class CBV(View):   ##要继承View ,是get请求就执行get函数
    def dispatch(self, request, *args, **kwargs):
        result =super(CBV,self).dispatch(request, *args, **kwargs)
        return result   ##dispatch这样写好像是没毛用,我们什么功能都没给他加,不过起码知道了起码要往哪里加功能
    
    def get(self,request):
        ##根据请求头中的request.method执行对应的函数
        # return  HttpResponse("CBV.GET")
        return render(request,"CBV.html")

    def  post(self,request):
        return HttpResponse("CBV.POST")

 三:model的操作,复习

1对多的操作

正向查找:(外键在自己表中,通过自己的外键字段)
    ##通过学生表找到“q学生”对应的班级
        ##基于对象:
        obj = models.Student.objects.filter(sname="q学生").first()
        cname = obj.s2c.cname
        print(cname) ##python1

        ###基于"__"
        obj2 = models.Student.objects.filter(sname="q学生").values("s2c__cname")
        print(obj2) #<QuerySet [{'s2c__cname': 'python1'}]>


反向查找:(通过关联自己为外键的类名_set) 
    ##通过学生表找到“q学生”对应的班级
        基于"__"
        ret2 = models.Classes.objects.filter(student__sname="q学生").values("cname")
        print(ret2) ##<QuerySet [{'cname': 'python1'}]>
        
    查找对应的“python1”班级的学生:
        基于对象:
        ret = models.Classes.objects.get(cname="python1")
        ret_sult = ret.student_set.values("sname")
        print(ret_sult)
View Code

多对多操作 : 

正向查找:    
##查询python1对应的老师
    #基于对象:
        # obj = models.Classes.objects.filter(cname="python1")[0]
        # ret = obj.c3s.all().values("tname")   ##obj.c3s.all() 获取到所有的tid
        # print(ret)  ##<QuerySet [{'tname': 'alex'}, {'tname': 'eric'}]>
    #基于"__"
        # obj = models.Classes.objects.filter(cname="python1").values("c3s__tname")
        # print(obj) ##<QuerySet [{'c3s__tname': 'alex'}, {'c3s__tname': 'eric'}]>

        
反向查找:
##查询alex教的班级
    #基于对象:
        # obj = models.Teachers.objects.filter(tname="alex").first()
        # cc = obj.classes_set.all().values("cname") #obj.classes_set.all() 拿到的就是所有alex的班级
        # print(cc) ##<QuerySet [{'cname': 'python1'}]>

    #基于"__":
        obj = models.Teachers.objects.filter(tname="alex").values("classes__cname")
        print(obj) #<QuerySet [{'classes__cname': 'python1'}]>
View Code

四.分页的实现

1:分页的原理:

其实际就是不让一次显示全部的数据,而是用切片的方式进行显现数据:

###一共有1-998条的数据
USER_LIST = []
for i in range(1,999):
    temp = "root"+str(i)+"-"+str(i)
    USER_LIST.append(temp)

    
def index(request):
    per_page= 10
    current_page = int(request.GET.get("p"))
    ##p=1  显示数据:1-10  对应列表 0-10
    ##p=2  显示数据:11-20  对应列表 10-20
    ##应用小学学过的知识,简单的计算指定页码要显示的数据
    ##start = (current_pae -1) * 10
    ##end = current_page * 10
    
    start = (current_page -1) * 10
    end = current_page * 10
    data = USER_LIST[start:end]
    return render(request,"index.html",{"user_list":data})

2:Django的内置分页

Django的内置分页,就是给我们提供了2个类,2个对象。 paginator 、page

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

L = []
for i in range(999):
    L.append(i)

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

    paginator = Paginator(L, 10)
    # per_page: 每页显示条目数量
    # count:    数据总个数
    # num_pages:总页数
    # page_range:总页数的索引范围,如: (1,10),(1,200)
    # page:     page对象
    try:
        posts = paginator.page(current_page)
        # has_next              是否有下一页
        # next_page_number      下一页页码
        # has_previous          是否有上一页
        # previous_page_number  上一页页码
        # object_list           分页之后的数据列表
        # number                当前页
        # paginator             paginator对象
    except PageNotAnInteger:  ###p不是一个有效的页码默认显示第一页
        posts = paginator.page(1)
    except EmptyPage:
        posts = paginator.page(paginator.num_pages)  ##一共100页,你请求200页的内容,给你返回最后一页的内容
    return render(request, 'index.html', {'posts': posts})

html

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<ul>
    {% for item in posts %}
        <li>{{ item }}</li>
    {% endfor %}
</ul>

<div class="pagination">
      <span class="step-links">
        {% if posts.has_previous %}
            <a href="?p={{ posts.previous_page_number }}">Previous</a>
        {% endif %}
          <span class="current">
            Page {{ posts.number }} of {{ posts.paginator.num_pages }}.
          </span>
          {% if posts.has_next %}
              <a href="?p={{ posts.next_page_number }}">Next</a>
          {% endif %}
      </span>

</div>
</body>
</html>

Html
View Code

3:Django的扩展内置分页

4:自定义分页

分页组件:

class Pagerator(object):
    def __init__(self,total_count,current_page,per_page_num=7,per_page_data=10):
        self.total_count = total_count
        try:
            v = int(current_page)
        except Exception as  e:
            v = 1
        if v<=1:
            v = 1
        self.current_page = v
        self.per_page_data = per_page_data
        self.per_page_num = per_page_num

    @property
    def start(self):
        return (self.current_page - 1 )* self.per_page_data

    @property
    def end(self):
        return self.current_page * self.per_page_data

    @property
    def max_page_num(self):
        a,b = divmod(self.total_count,self.per_page_data)
        if b:
            a= a+1
        return a


    def page_range(self):
        temp_list = []
        part_page_num = round(self.per_page_num / 2)
        ##首页
        frist = "<li><a href='/index.html/?p=1'>首页</a></li>"
        temp_list.append(frist)

        ##上一页
        if self.current_page == 1:
            pre_page = "<li><a href='#'>上一页</a></li>"
        else:
            pre_page = "<li><a href='/index.html/?p=%s'>上一页</a></li>"%(self.current_page-1)
        temp_list.append(pre_page)
        ##如果当前页面小于4的时候,默认就是1 - 最大页码
        if self.current_page <= part_page_num:
            page_start = 1
            page_end = self.per_page_num
        else:
            page_start = self.current_page - 3
            page_end = self.current_page +3
            ##当end_page加+3之后比最大的页面还大的时候,这就是bug了,加上if判断
            if page_end >= self.max_page_num:
                page_end = self.max_page_num

        for i in range(page_start,page_end+1):
            if self.current_page == i :
                temp = "<li class='active'><a href='/index.html/?p=%s'>%s</a></li>"%(i,i)
            else:
                temp = "<li><a  href='/index.html/?p=%s'>%s</a></li>" % (i, i)
            temp_list.append(temp)

        ##下一页
        if self.current_page == self.max_page_num:
            nex_page = "<li><a href='#'>下一页</a></li>"
        else:
            nex_page = "<li><a href='/index.html/?p=%s'>下一页</a></li>"%(self.max_page_num)
        temp_list.append(nex_page)

        ##尾页
        last = "<li><a href='/index.html/?p=%s'>首页</a></li>"%(self.max_page_num)
        temp_list.append(last)

        return "".join(temp_list)
Page.py

view:

USER_LIST = []
for i in range(1,666):
    temp = "root"+str(i)+"-"+str(i)
    USER_LIST.append(temp)

def index(request):
    from  app01 import  Page
    current_page = request.GET.get("p")
    page_obj=Page.Pagerator(666,current_page)  ##传入当前页和总数
    data = USER_LIST[page_obj.start:page_obj.end] ##
    per_page_num = page_obj.page_range()  ##获取每页显示的页码

    return render(request,"index.html",{"user_list":data,"per_page_num":per_page_num})
View Code
<!-- 数据-->
{% for user in user_list %}
    <li>{{ user }}</li>
{% endfor %}


<hr />

<!-- 页码-->
<ul class="pagination">
    {{ per_page_num|safe }}
</ul>
template

效果图:

 五、Form

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

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

1、创建Form类

from django.forms import Form
from django.forms import widgets
from django.forms import fields
 
class MyForm(Form):
    user = fields.CharField(
        widget=widgets.TextInput(attrs={'id': 'i1', 'class': 'c1'})  ##给他设置属性
    )
 
    gender = fields.ChoiceField(
        choices=((1, ''), (2, ''),),
        initial=2,
        widget=widgets.RadioSelect
    )
 
    city = fields.CharField(
        initial=2,
        widget=widgets.Select(choices=((1,'上海'),(2,'北京'),))
    )
 
    pwd = fields.CharField(
        widget=widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True)
    )
View Code

2、View函数处理

from django.shortcuts import render, redirect
from .forms import MyForm
 
 
def index(request):
    if request.method == "GET":
        obj = MyForm()
        return render(request, 'index.html', {'form': obj})
    elif request.method == "POST":
        obj = MyForm(request.POST, request.FILES)
        if obj.is_valid():   ##进行验证
            values = obj.clean()    ##获取验证后的数据
            print(values)
        else:
            errors = obj.errors  ##获取验证失败的数据   里面的数据是一个个key:[错误列表]
            print(errors)
        return render(request, 'index.html', {'form': obj})
    else:
        return redirect('http://www.google.com')
View Code

3、生成HTML

<form action="/" method="POST" enctype="multipart/form-data">
    <p>{{ form.user }} {{ form.user.errors }}</p>
    <p>{{ form.gender }} {{ form.gender.errors }}</p>
    <p>{{ form.city }} {{ form.city.errors }}</p>
    <p>{{ form.pwd }} {{ form.pwd.errors }}</p>
    <input type="submit"/>
</form>
View Code

1、Django内置字段如下:

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)  ##文件单独放在了request.FILES
    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)    ##有 01 两个值
    ...
 
NullBooleanField(BooleanField) ##有 01null 三个值
    ...
 
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)   ##ChoiceField中比如select的value是字符串,可以将coerce = lambda x :int(x) 变成int类型  
    coerce = lambda val: val   对选中的值进行一次转换
    empty_value= ''            空值的默认值
 
MultipleChoiceField(ChoiceField)  ###多选select
    ...
 
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类型(了解)
    ...
View Code

 注:UUID是根据MAC以及当前时间等创建的不重复的随机字符串

>>> import uuid

    # make a UUID based on the host ID and current time
    >>> uuid.uuid1()    # doctest: +SKIP
    UUID('a8098c1a-f86e-11da-bd1a-00112444be1e')

    # make a UUID using an MD5 hash of a namespace UUID and a name
    >>> uuid.uuid3(uuid.NAMESPACE_DNS, 'python.org')
    UUID('6fa459ea-ee8a-3ca4-894e-db77e160355e')

    # make a random UUID
    >>> uuid.uuid4()    # doctest: +SKIP
    UUID('16fd2706-8baf-433b-82eb-8c7fada847da')

    # make a UUID using a SHA-1 hash of a namespace UUID and a name
    >>> uuid.uuid5(uuid.NAMESPACE_DNS, 'python.org')
    UUID('886313e1-3b8a-5372-9b90-0c9aee199e5d')

    # make a UUID from a string of hex digits (braces and hyphens ignored)
    >>> x = uuid.UUID('{00010203-0405-0607-0809-0a0b0c0d0e0f}')

    # convert a UUID to a string of hex digits in standard form
    >>> str(x)
    '00010203-0405-0607-0809-0a0b0c0d0e0f'

    # get the raw 16 bytes of the UUID
    >>> x.bytes
    b'x00x01x02x03x04x05x06x07x08	
x0bx0c
x0ex0f'

    # make a UUID from a 16-byte string
    >>> uuid.UUID(bytes=x.bytes)
    UUID('00010203-0405-0607-0809-0a0b0c0d0e0f')
View Code

2、Django内置插件:

我们说Form为什么我们写个:

class myForm(forms.Form):
    username = fields.CharField(max_length=17)
    

就可以在前端{{ obj.username }} 就能够显示出。input框出来,
obj是myForm的对象,而username 是属于CharField字段的,那么它的默认内置插件就是一个<input />

###CharField的继承说明
##CharField继承Field。Field的默认插件就是TextInput。TextInput继承Input。Input返回input的字符
class CharField(Field):  ==> class Field(object):  ==>  class TextInput(Input):  ==>
                                widget = TextInput         input_type = 'text'              


class Input(Widget):
    return format_html('<input{} />', flatatt(final_attrs))
TextInput(Input)
NumberInput(TextInput)
EmailInput(TextInput)
URLInput(TextInput)
PasswordInput(TextInput)
HiddenInput(TextInput)
Textarea(Widget)
DateInput(DateTimeBaseInput)
DateTimeInput(DateTimeBaseInput)
TimeInput(DateTimeBaseInput)
CheckboxInput
Select
NullBooleanSelect
SelectMultiple
RadioSelect
CheckboxSelectMultiple
FileInput
ClearableFileInput
MultipleHiddenInput
SplitDateTimeWidget
SplitHiddenDateTimeWidget
SelectDateWidget
内置插件

3:常用选择插件(上面说的那么多插件,了解就好)这个要记住了

CharField的就是返回,是一个字符串的就可以,而要形成什么input是由里面的widget去指定。
# 单radio,值为字符串
# user = fields.CharField(
#     initial=2,
#     widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),)) ##只有select比较特殊,里面还有choices,去指定值。
# )


### 单radio,返回值为数字
### user = fields.IntegerField(
###     initial=2,
###     widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),)) ##只有select比较特殊,里面还有choices,去指定值。
### )

 
# 单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,值为列表
# 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
# )

在使用选择标签时,需要注意choices的选项可以从数据库中获取,但是由于是静态字段 ***获取的值无法实时更新***,那么需要自定义构造方法从而达到此目的。

方式一:

from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.validators import RegexValidator
 
class MyForm(Form):
 
    user = fields.ChoiceField(   
        # choices=((1, '上海'), (2, '北京'),),   
        initial=2,
        widget=widgets.Select
    )
 
    def __init__(self, *args, **kwargs):
        super(MyForm,self).__init__(*args, **kwargs)
        # self.fields['user'].widget.choices = ((1, '上海'), (2, '北京'),)
        # 或
        self.fields['user'].widget.choices = models.Classes.objects.all().value_list('id','caption')
        
        ##super必须放在第一个位置,因为它会执行父类的init方法,拷贝所有的静态字段,复制给self.fields


def    form1(request):
    if request.method = "GET":
        obj= MyForm()    ##每次创建一个obj对象的时候,就去执行MyForm中的init方法。这样就能做到了实时更新
    else:
        obj = MyForm(request.POST)
        obj.is_valid():
    return render(request,"form1.html",locals())
View Code

方式二:(不推荐,它要是想显示中文依赖model中的__str__)

使用django提供的ModelChoiceField和ModelMultipleChoiceField字段来实现

from django import forms
from django.forms import fields
from django.forms import widgets
from django.forms import models as form_model
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
 
class FInfo(forms.Form):
    authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all())
    # authors = form_model.ModelChoiceField(
                                            queryset=models.NNewType.objects.all(),
                                            to_field_name='id')
    
    ##默认select里面values是id,而我们要改变也是可以的里面的value  
    ##默认不能显示中文,依赖model
            
View Code

 4:ajax实现Form

Form :ajax提交
    1:不能在后台进行 redirect 
        1.1:要想实现:obj.is_valid()的时候,给他json.dumps(一个status的字典)
        1.2:前端success:function(arg){
            判断(status="true"){
                windows.localtion.href = 'http://www.baidu.com'}
            }
    2:错误信息需要自己显示
from django.shortcuts import render
from django.shortcuts import redirect,HttpResponse

# Create your views here.


from  django import  forms
from django.forms import  fields
from django.forms import widgets
class UserInfo(forms.Form):
    username = fields.CharField(min_length=6,)
    email = fields.EmailField(error_messages={"invalid":"格式不正确"})
    city = fields.IntegerField(
        widget=widgets.Select(
            choices=[(1,'北京'),(2,'武汉'),(3,'天津')],
        ),
    )

def ajax_form(request):
    if request.method == "GET":
        obj = UserInfo()
        return  render(request,"ajax_form.html",locals())
    else:
        import json
        ret = {"status":False,"message":None}
        obj = UserInfo(request.POST)
        if obj.is_valid():
            print(obj.cleaned_data)
            # return redirect("http://www.baidu.com")
            ret["status"] = True
            return HttpResponse(json.dumps(ret))
        else:
            print(type(obj.errors))
            ##<class 'django.forms.utils.ErrorDict'> 继承dict  默认能json只有基本数据类型,
            ##而ErrorDict 继承dict 所以可以被json.dumps
            ##ErrorDict就是帮你把数据变成了as_ul,as_data,as_text默认是as_ul
            ret["message"] = obj.errors
            from  django.forms.utils import ErrorDict
            print(obj.errors)
            print(obj.errors.as_ul())
            print(obj.errors.as_data())
            print(obj.errors.as_text())
            return HttpResponse(json.dumps(ret))
views
from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    # url(r'^my_form/', views.my_form),
    url(r'^ajax_form.html/', views.ajax_form),
]
urls
form action="/" method="POST" enctype="multipart/form-data">
    <p>{{ form.user }} {{ form.user.errors }}</p>
    <p>{{ form.gender }} {{ form.gender.errors }}</p>
    <p>{{ form.city }} {{ form.city.errors }}</p>
    <p>{{ form.pwd }} {{ form.pwd.errors }}</p>
    <input type="submit"/>
</form>


###第二种都显示所有的数据
<form>
    {{form.as_table}}
</form>

###第三种都显示所有的数据
{{form.as_p}}

##第四种都显示所有的数据
{{form.as_ul}}
template

5:自定义验证规则

方式一:

from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.validators import RegexValidator
 
class MyForm(Form):
    user = fields.CharField(
        validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],
    )

方式二:

class UserInfo1(fforms.Form):
    username = fields.CharField()
    phone = fields.RegexField(r'^159[0-9]+$',error_messages={'invalid':"必须以159开始的手机"})

方式三:

要是username是unique的,注册的时候不能重复,我们怎么通过Form进行判断说,“用户已经存在”

前戏1:

Form源码解析:
当我们obj.is_valid()的时候,会有两个产物 cleand_data 、errors

一:
    def is_valid(self):
        """
        Returns True if the form has no errors. Otherwise, False. If errors are
        being ignored, returns False.
        """
        return self.is_bound and not self.errors  
        ##会去执行self.errors的函数
        
        
二:
    @property
    def errors(self):
        "Returns an ErrorDict for the data provided for the form"
        if self._errors is None:
            self.full_clean()   ###默认_errors 是None执行full_clean()方法
        return self._errors
        

三:
    def full_clean(self):
        self._errors = ErrorDict()  ##创建一个ErrorDict的字典
        if not self.is_bound:  # Stop further processing.
            return
        self.cleaned_data = {}   ##通过验证的字典
        if self.empty_permitted and not self.has_changed():
            return
            
        self._clean_fields()  ##执行_clean_fields方法,对每个字典进行匹配规则
        self._clean_form()
        self._post_clean()
        
        
四:
    def _clean_fields(self):
        for name, field in self.fields.items():  ##依次遍历Form字段
            # value_from_datadict() gets the data from the data dictionaries.
            # Each widget type knows how to retrieve its own data, because some
            # widgets split data over several HTML fields.
            if field.disabled:
                value = self.initial.get(name, field.initial)
            else:
                value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
            try:
                if isinstance(field, FileField):
                    initial = self.initial.get(name, field.initial)
                    value = field.clean(value, initial)
                else:
                    value = field.clean(value)
                self.cleaned_data[name] = value   ##验证成功临时放入cleaned_data中["username":"crik","pwd":123]
                if hasattr(self, 'clean_%s' % name):  ##要是在Form中有clean_字段名,要执行,通过这个,才真正的放入cleaned_data字段中
                    value = getattr(self, 'clean_%s' % name)()
                    self.cleaned_data[name] = value
            except ValidationError as e:   ##如果有ValidationError,就add_error (name=字段名字,而e是错误)
                self.add_error(name, e)
        
View Code

前戏2:

通过源码,我们可以知道了在Form class中我们可以定义一个以"clean_"前缀的字段方法。
进行额外的判断:

注意:方法的返回值必须是self.cleand_data[name]=value 因为在第一次只是临时放入到cleand_data字典正确数据,

实现:

from  django import forms as fforms
from django.forms import fields
from django.forms import  widgets

from django.core.validators import RegexValidator
from app01 import  models
from django.core.exceptions import NON_FIELD_ERRORS,ValidationError
class UserInfo(fforms.Form):
    username = fields.CharField()
    phone = fields.CharField(
        validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],
        )
    def clean_username(self):
        v = self.cleaned_data['username']
        if models.Users.objects.filter(username=v).count(): ##存在username=发过来的username,就报错
            raise ValidationError('用户名已存在')
        return v

views

from app01.myform import *
import json
def exform(request):
    if request.method == "GET":
        obj = UserInfo()
        return render(request,"exForm.html",{"obj":obj})
    else:
        obj = UserInfo(request.POST)
        if obj.is_valid():
            print(obj.cleaned_data)
            return HttpResponse("OK")
        else:
            print(obj.errors)
            ##<ul class="errorlist"><li>username<ul class="errorlist"><li>用户名已存在</li></ul></li><li> phone<ul class="errorlist"><li>数字必须以159开头</li></ul></li></ul>
            return  HttpResponse(json.dumps(obj.errors))

 方式四:

对于方式三的时候,我们可以对其单个字段进行一个扩展,但是在设计数据库的时候
我们是有存在联合唯一的key的,往往需要两个或三个字段唯一才报错。

前戏:源码解析

一: 

    def is_valid(self):
        """
        Returns True if the form has no errors. Otherwise, False. If errors are
        being ignored, returns False.
        """
        return self.is_bound and not self.errors  ##执行self.errors函数
        
二:
    @property
    def errors(self):
        "Returns an ErrorDict for the data provided for the form"
        if self._errors is None:
            self.full_clean()  ##执行full_clean函数
        return self._errors
        
三:
    def full_clean(self):
        """
        Cleans all of self.data and populates self._errors and
        self.cleaned_data.
        """
        self._errors = ErrorDict()
        if not self.is_bound:  # Stop further processing.
            return
        self.cleaned_data = {}
        # If the form is permitted to be empty, and none of the form data has
        # changed from the initial data, short circuit any validation.
        if self.empty_permitted and not self.has_changed():
            return

        self._clean_fields()  ##在方式三的时候,这个函数是逐一进行匹配单个字段的、并且存在一个clean_字段名的扩展
        self._clean_form()    ##开始执行_clean_form()函数 
        self._post_clean()

四:
    def _clean_form(self):
        try:
            cleaned_data = self.clean()  ##执行self.clean()方法,当clean里面有ValidationError的时候,就会被捕捉到
        except ValidationError as e:    
            self.add_error(None, e)        ##以None为key的错误
        else:
            if cleaned_data is not None:
                self.cleaned_data = cleaned_data  
                
        ##from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
        ##NON_FIELD_ERRORS = '__all__'  定义的是以None为key,而他进行了一个转换none==__all__ 
                

五:
    def clean(self):  ##这就是一个钩子、我们可以做任何的事情。但是要记得必须要return self.cleaned_data
        """
        Hook for doing any extra form-wide cleaning after Field.clean() has been
        called on every field. Any ValidationError raised by this method will
        not be associated with a particular field; it will have a special-case
        association with the field named '__all__'.
        """
        return self.cleaned_data
View Code

myforms文件

from  django import forms as fforms
from django.forms import fields
from django.forms import  widgets

from django.core.validators import RegexValidator
from app01 import  models
from django.core.exceptions import NON_FIELD_ERRORS,ValidationError
class UserInfo(fforms.Form):
    username = fields.CharField()
    phone = fields.CharField(
        validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],
        )
    def clean_username(self):
        v = self.cleaned_data['username']
        if models.Users.objects.filter(username=v).count():
            raise ValidationError('用户名已存在')
        return v

    def clean(self):
        username = self.cleaned_data['username']  ##这里可以获取到cleand_data的数据,因为单个字段验证完成
        phone = self.cleaned_data['phone']
        if username == "root" and phone == '159159': ##这里就做一个简单的实例,正常是去数据库匹配数据
            raise ValidationError("用户已经注册")
        return self.cleaned_data   ##必须返回cleand_data

views

from django.shortcuts import render
from django.shortcuts import redirect,HttpResponse


from app01.myform import *
import json
def exform(request):
    if request.method == "GET":
        obj = UserInfo()
        return render(request,"exForm.html",{"obj":obj})
    else:
        obj = UserInfo(request.POST)
        if obj.is_valid():
            print(obj.cleaned_data)
            return HttpResponse("OK")
        else:
            print(obj.errors)
            ##<ul class="errorlist"><li>__all__<ul class="errorlist nonfield"><li>用户已经注册</li></ul></li></ul>
            return  HttpResponse(json.dumps(obj.errors))

 六、页面请求models里面的all时候的几种实现

方式1:一次撸到底,单一页面

#####views####

def v1_all(request):
    user_list = models.Users.objects.all()
    return render(request,"v1_all.html",{"user_list":user_list})    

    
    
#####v1_all.html####    
        
<body>

<h1>用户列表</h1>
<table>
    {% for row in user_list %}
        <tr>
            <td>{{ row.id }}</td>
            <td>{{ row.username }}</td>
        </tr>
    {% endfor %}
</table>

</body>
View Code

方式2:两个url,一个加载页面,一个加载数据

views:

def v1_all(request):
    return render(request,"v1_all.html")


def get_data(request):
    user_list = models.Users.objects.all()
    return render(request,"get_data.html",{"user_list":user_list})
View Code

template:

{% for row in user_list %}
    <tr>
        <td>{{ row.id }}</td>
        <td>{{ row.username }}</td>
    </tr>
{% endfor %}
get_data.html
<body>

<h1>用户列表</h1>
<table id="tb">

</table>

<script src="/static/jquery-3.3.1.min.js"></script>
<script>
    (function () {
        GetData(); //加载完自动执行函数
    })();
    function GetData() {
        $.ajax({
            url:"/get_data/",
            type:"get",
            success:function (arg) {
                $("#tb").append(arg)  //返回来的是一堆tr包裹着td的标签
            }
        })
    }
</script>

</body>
v1_all.html

方式3:序列化的方式

所谓的序列化,就是把一个对象转成字符串,能够保存在文件的东西,
这一过程就是序列化。 
import json
from django.core import serializers
def v1_all(request):
    return render(request, "v1_all.html")


def get_data(request):
    ret = {"status": False, "message": None}
    user_list = models.Users.objects.all()
    if user_list.exists():  ##user_list存在数据,这一过程不会执行sql语句,执行去看一下是否有数据
        ret["status"] = True
        ret["message"] = serializers.serialize("json", user_list) ##
    return HttpResponse(json.dumps(ret))
views

django的返回数据有:

QuerySet的对象类型; 
    ##不是基本类型,不能直接采用json.dumps而是要采用Django内置的,serializers进行序列化,然后再进行json.dumps
values:QuerySet 包裹着字典 ; 
values_list :QuerySet 包裹着元组;
    ##对其上的两种,我们可以采用去掉外面的QuerySet外衣 直接 list[user_list],
    ##把QuerySet外衣变成list的外衣;然后就可以进行json.dumps

七、文件上传

views

def upload(request):
    if request.method == "GET":
        return render(request,"upload.html")
    else:
        # print(request.POST) #<QueryDict: {'csrfmiddlewaretoken': ['k24X8CeB8v75MbSqe18167yF4L5WAgrLjQKtlVe19zRzPW8p9YY8NA3wHuV30ky4'], 'user': ['dad']}>
        # print(request.FILES) # <MultiValueDict: {'img': [<InMemoryUploadedFile: 个人简历模板.doc (application/msword)>]}>
        user = request.POST.get("user") ##user是字符串
        file = request.FILES.get("img") ##file是一个对象,包含文件内容、大小、文件名称等
        # print(file.name) ##个人简历模板.doc
        # print(file.size) ##25088
        f = open(file.name,'wb')
        for line in file.chunks():   ##文件的内容,默认是生成一个迭代器,要一点一点的去取数据
            f.write(line)
        f.close()

        return HttpResponse(".....OK")

template

<body>
<form action="/upload.html" method="post" enctype="multipart/form-data">
    {% csrf_token %}
    <input type="text" name="user" placeholder="用户名"/>
    <div style="position: relative">
        <a href="">NB上传</a>
        <input type="file" name="img" style="opacity: 0;position: absolute;top: 0;left: 0; "/>  ##可以自定制上传按钮。
    </div>
    <input type="submit" value="提交" />
</form>

</body>
原文地址:https://www.cnblogs.com/hero799/p/8874364.html