Django框架基础知识汇总(有项目版)下

242 阅读15分钟

ORM补充之基本操作(数据行高级操作)


排序

user_list = models.UserInfo.objects.all().order_by('-id','name') 
# —id代表降序,id代表升序

分组

from django.db.models import Count,Sum,Max,Min
v =models.UserInfo.objects.values('ut_id').annotate(xxxx=Count('id'))
# 等价于SELECT `app01_userinfo`.`ut_id`, COUNT(`app01_userinfo`.`id`) AS `xxxx` 
FROM
`app01_userinfo` 
GROUP BY `app01_userinfo`.`ut_id`
ORDER BY NULL
# 带有having 分组条件过滤
v =models.UserInfo.objects.values('ut_id').annotate(xxxx=Count('id')).filter(xxxx__gt=2)
# 等价于SELECT `app01_userinfo`.`ut_id`, COUNT(`app01_userinfo`.`id`) AS `xxxx` 
FROM
`app01_userinfo` GROUP BY `app01_userinfo`.`ut_id`
HAVING COUNT(`app01_userinfo`.`id`) > 2 
ORDER BY NULL
v =models.UserInfo.objects.filter(id__gt=2).values('ut_id').annotate(xxxx=Count('id')).filter(xxxx__gt=2)
# 等价于SELECT `app01_userinfo`.`ut_id`, COUNT(`app01_userinfo`.`id`) AS `xxxx` 
FROM 
`app01_userinfo`
 WHERE
 `app01_userinfo`.`id` > 2 
GROUP BY
 `app01_userinfo`.`ut_id`
 HAVING 
COUNT(`app01_userinfo`.`id`) > 2 ORDER BY NULL
分组格式:model.类名.objects.values(显示的字段名).annotate(作为字段查结结果的别名=Count(字段id/1))
  # annotate依赖于values

条件过滤

models.UserInfo.objects.filter(id__gt=1)  # id>1
……(id__lt=1# id<1
……(id__lte=1) #id<=1
……(id__gte=1) # id>=1
……(id__in=[1,2,3]) #id in [1,2,3]
……(name__startswith='xxxx')  #
……(name__contains='xxxx') #
……exclude(id=1)  # not in (id=1)

F,Q,extra方法

  • F
from django.db.models import F
models.UserInfo.objects.all().update(age=F("age")+1)  # F()用来取对象中某列值
  • Q(构造复杂的查询条件)
# 对象方式(不推荐)

from django.db.models import Q
models.UserInfo.objects.filter(Q(id__gt=1))
models.UserInfo.objects.filter(Q(id=8) | Q(id=2)) # or
models.UserInfo.objects.filter(Q(id=8) & Q(id=2)) # and
  • 方法方式
from django.db.models import Q
q1 = Q()
q1.connector = 'OR'
q1.children.append(('id__gt', 1))
q1.children.append(('id', 10))
# 通过OR将3个条件进行连接组装

q2 = Q()
q2.connector = 'OR'
q2.children.append(('c1', 1))
q2.children.append(('c1', 10))

q3 = Q()
q3.connector = 'AND' #通过AND将2个条件进行连接组装
q3.children.append(('id', 1))
q3.children.append(('id', 2))
q1.add(q3,'OR') #还可将q3嵌入到q1条件组中

# 将q1和q2条件组通过AND汇总到一起,q1和q2内部分别用or组合条件
con = Q()
con.add(q1, 'AND')
con.add(q2, 'AND')

  • 方法方式实际应用(多条件组合查询时)
condition_dict = {  #用户将选择的条件组合成字典格式
    'k1':[1,2,3,4],
    'k2':[1,],
}
con = Q()
for k,v in condition_dict.items():
    q = Q()
    q.connector = 'OR'
    for i in v:
        q.children.append(('id', i))
    con.add(q,'AND')
models.UserInfo.objects.filter(con)

***********************************************************************
q1 = Q()
q1.connector = 'OR'
q1.children.append(('id', 1))
q1.children.append(('id', 10))
q1.children.append(('id', 9))


q2 = Q()
q2.connector = 'OR'
q2.children.append(('c1', 1))
q2.children.append(('c1', 10))
q2.children.append(('c1', 9))

q3 = Q()
q3.connector = 'AND'
q3.children.append(('id', 1))
q3.children.append(('id', 2))

q1.add(q3,'OR')

con = Q()
con.add(q1, 'AND')
con.add(q2, 'AND')
#以上构造结果等介于(id=1 or id = 10 or id=9 or (id=1 and id=2)) and (c1=1 or c1=10 or c1=9)
  • extra(添加额外的自定义sql语句)
models.UserInfo.objects.extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
 a. 映射
 select 
 select_params=None
select 此处 from 表
 b. 条件
 where=None
 params=None,
 select * fromwhere 此处
 c. 表
 tables
 select * from 表,此处
 c. 排序
order_by=None
 select * fromorder by 此处
v = models.UserInfo.objects.all().extra(
    select={
        'n':"select count(1) from app01_usertype where id=%s or id=%s",
        'm':"select count(1) from app01_usertype where id=%s or id=%s",
    },
    select_params=[1,2,3,4])
for obj in v:
    print(obj.name,obj.id,obj.n) 

----------
等价于将查询结果作为字段显示列:
select
# id,
# name,
# (select count(1) from tb) as n
# from xb where ....
models.UserInfo.objects.extra(
select={'newid':'select count(1) from app01_usertype where id>%s'},
select_params=[1,],
where = ['age>%s'],
params=[18,],
order_by=['-age'],
tables=['app01_usertype']
)

----------
等价于原生sql语句如下:
select 
app01_userinfo.id,
(select count(1) from app01_usertype where id>1) as newid
from app01_userinfo,app01_usertype
where 
app01_userinfo.age > 18
order by 
app01_userinfo.age desc

result = models.UserInfo.objects.filter(id__gt=1).extra(
where=['app01_userinfo.id < %s'],
params=[100,],
tables=['app01_usertype'],
order_by=['-app01_userinfo.id'],
select={'uid':1,'sw':"select count(1) from app01_userinfo"} #添加查询字段
)


----------
SELECT (1) AS "uid", (select count(1) from app01_userinfo) AS "sw", "app01_userinfo"."id", "app01_userinfo"."name", "app01_userinfo"."age", "app01_userinfo"."ut_id" 
FROM
 "app01_userinfo" , "app01_usertype" 
WHERE
 ("app01_userinfo"."id" > 1 AND (app01_userinfo.id < 100))
 ORDER BY ("app01_userinfo".id) 
DESC

  • 取特定字段值
v = models.UserInfo.objects.all().only('id','name')
# 获取字段以外的字段会再发出第二次sql请求
  • 取当前字段以外的所有值
 v = models.UserInfo.objects.all().defer('name')
  • 反转
v = models.UserInfo.objects.all().order_by('-id','name').reverse()
# 只有在order_by()方法时才有效果
  • 使用数据库引擎
models.UserInfo.objects.all().using('db2')

  • 聚合
#统计总数
from django.db.models import Count
result = models.UserInfo.objects.aggregate(k=Count('ut_id', distinct=True), n=Count('id'))
# distinct代表去重
print(ruselt.query())
  • 以字典格式添加数据
obj = models.UserType.objects.create(**{'title': 'xxx'})
  • 以关键字参数添加数据
obj = models.UserType.objects.create(title='xxx')
  • 批量增加数据
objs = [
    models.UserInfo(name='r11'),
]
models.UserInfo.objects.bulk_create(objs, 10) # 10为一次提交10次数据,建议不超过999
  • 创建/获取
obj, created = models.UserInfo.objects.get_or_create(  #如果存在数据则获取,否则直接创建
    username='root1',
    pwd='ff',
    defaults={'email': '1111111','u_id': 2, 't_id': 2})
  • 条件范围
models.UserInfo.objects.in_bulk([1,2,3]) #根据主键进行查询
相当于models.UserInfo.objects.filter(id__in=[1,2,3])
  • raw(书写原生sql语句)
name_map = {'title': 'name'} # 将下面的title转换为name
v1 = models.UserInfo.objects.raw('SELECT id,title FROM app01_usertype',translations=name_map)
for i in v1:
    print(i,type(i))
  • select_related:查询主动做连表,一次性获取所有连表中的数据(性能相关:数据量少的情况下使用)
q = models.UserInfo.objects.all().select_related('ut','gp')
#等价于select * from userinfo inner join usertype on ...
for row in q:
    print(row.name,row.ut.title) #采用.的形式获取连表数据
  • prefetch_related:不做连表,但会做多次查询(性能相关:数据量多,查询频繁下使用)
 q = models.UserInfo.objects.all().prefetch_related('ut')
# select * from userinfo;
# Django内部:ut_id = [2,4]
# select * from usertype where id in [2,4]
for row in q:
    print(row.id,row.ut.title)

XSS攻击(跨站脚本攻击)


模拟攻击时:前提需要将对应设置注释
MIDDLEWARE = [
# 'django.middleware.csrf.CsrfViewMiddleware',
]

urls

url(r'^index/', views.index),
url(r'^comment/', views.comment),

views

def comment(request):
    if request.method == "GET":
        return render(request,'comment.html')
    else:
        v = request.POST.get('content')
        msg.append(v)
       return render(request,'comment.html')
def index(request):   
    return render(request,'index.html',{'msg':msg})

html

<h1>评论</h1>
{% for item in msg %}
    <div>{{ item|safe}}</div> #需要给响应的值添加safe
{% endfor %}
# 同时也可在视图函数中添加safe:
def test(request):
    from django.utils.safestring import mark_safe
    temp = "<a href='http://www.baidu.com'>百度</a>"
    newtemp = mark_safe(temp) #将内容处理成safe安全数据
    return render(request,'test.html',{'temp':newtemp})
黑客可通过伪造网站,进行xss攻击,获得用户访问正式网站中的cookies,
从而伪装该用户可到正式网站进行操作,所以cookies很重要,要xss要处于启动状态(默认xss为启用状态)

CSRF(跨站请求伪装攻击)


urls

# 前提需要setting文件中crsf验证开启
url(r'^csrf1.html$', views.csrf1)

views


def csrf1(request):
    if request.method == 'GET':
        return render(request,'csrf1.html')
    else:
        return HttpResponse('ok')

html

<form method="POST" action="/csrf1.html">
    {% csrf_token %}   # 需添加服务器发送的csrf随机字符串,才能访问成功
    <input id="user" type="text" name="user" />
    <input type="submit" value="提交"/>
    <a onclick="submitForm();">Ajax提交</a>
</form>

补充:csrf第二种处理方式:添加装饰器

from django.views import View
from django.utils.decorators import method_decorator

# CBV装饰器,无法将内置的@csrf_protect装饰器直接应用到函数上,而需手动写装饰器进行应用
def wrapper(func):
    def inner(*args,**kwargs):
        return func(*args,**kwargs)
    return inner
 #在指定方法上添加装饰器
    class Foo(View):
        @method_decorator(wrapper)
        def get(self,request):
            pass
        def post(self,request):
            pass
# 在类上添加
    @method_decorator(wrapper,name='XXX') #name表示应用的函数名称
    class Foo(View):
        def get(self,request):
            pass
        def post(self,request):
            pass

CSRF(Ajax请求模式)

  • html
<form method="POST" action="/csrf1.html">
    {% csrf_token %}
    <input id="user" type="text" name="user" />
    <input type="submit" value="提交"/>
    <a onclick="submitForm();">Ajax提交</a>
</form>
<script src="/static/jquery-1.12.4.js"></script>
<script src="/static/jquery.cookie.js"></script>
  • js
<script>
    function submitForm(){
        var token = $.cookie('csrftoken'); # 获得浏览器里cookies中的csrf随机字符串
        var user = $('#user').val();
        $.ajax({
            url: '/csrf1.html',
            type: 'POST',
            headers:{'X-CSRFToken': token}, # 将数据添加到请求头中,让Django去取,硬性规定
            data: { "user":user},
            success:function(arg){
                console.log(arg);
            }
        })
    }
</script>

views

def csrf1(request):
    if request.method == 'GET':
        return render(request,'csrf1.html')
    else:
        return HttpResponse('ok')

ORM函数相关(html模版上使用函数)simple_tag


模版中使用内置函数

  • html
{{ name|upper }} # upper表示内置函数,将所有字母变大写
  • views
def test(request):
return render(request,'test.html',{'name':'aaaaAA'})

模版中使用自定义函数

  • 创建templatetags文件夹,再创建xx.py模块
from django import template
register = template.Library() # 规定写法,不能修改

@register.filter # 该定义的方法只能传入2个参数
def my_upper(value,arg):
    return value + arg

@register.filter  # 应用于html模版中的if判断语名
def my_bool(value):
    return False

@register.simple_tag # 推荐使用
def my_lower(value,a1,a2,a3):
    return value + a1 + a2 + a3
  • views
def test(request):
return render(request,'test.html',{'name':'aaaaAA'})
  • html
{% load xx %}{#导入加载xx模块#}
<h2>filter</h2>
    {{ name|my_upper:"666" }} # 最多支持2个参数
    {{ name|upper }}

    {% if name|my_bool %}
        <h3>真</h3>
    {% else %}
        <h3>假</h3>
    {% endif %}

<h2>tag</h2>
    {% my_lower "ALEX" "x" "SB" "V" %}
  • setting注册程序块
INSTALLED_APPS = [
    ……
    'app01',
]
  • 总结:
- simple_filter
- 最多两个参数,格式: {{第一个参数|函数名称:"第二个参数"}}
- 可以做条件判断
- simple_tag
- 无限制: {% 函数名 参数 参数%}

include小组件


html

 <div>
    <h3>特别漂亮的组件</h3>
    <div class="title">标题:{{ name }}</div>
    <div class="content">内容:{{ name }}</div>
</div>
……
{% include 'pub.html' %}
……

cookie和session(推荐使用session)


cookie是保存在客户端浏览器上的键值对,
Session是保存在服务端的数据(本质是键值对)
因为单独使用cookies,它会保留用户具体的明文形式(不转化成字符串的敏感信息)发送给浏览器(不安全),所以推荐使用session,
session发送的是随机字符串,不包含用户敏感信息(安全),其中session依赖于cookies,

urls

urlpatterns = [
   ……
    url(r'^login/', views.login),
    url(r'^index/', views.index),
]

views

def login(request):
    if request.method == 'GET':
        return render(request,'login.html')
    else:
        u = request.POST.get('user')
        p = request.POST.get('pwd')
        obj = models.UserAdmin.objects.filter(username=u,password=p).first()
        if obj:
            # 1. 生成随机字符串
            # 2. 通过cookie发送给客户端
            # 3. 服务端保存
            # {
            #   随机字符串1: {'username':'alex','email':x''...}
            # }
            request.session['username'] = obj.username
            return redirect('/index/')
        else:
            return render(request,'login.html',{'msg':'用户名或密码错误'})

def index(request):
# 获取客户端cookies中的随机字符串,去session中查找有无该字符串, 再通过字符串去session对应key的value中查看是有username,并获得它对应的值

    v = request.session.get('username')   #  v为username对应的具体值
    if v:
        return HttpResponse('登录成功:%s' %v)
    else:
        return redirect('/login/')

setting


# SESSION_COOKIE_NAME = "sessionid"                        # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串
# SESSION_COOKIE_PATH = "/"                                # Session的cookie保存的路径
# SESSION_COOKIE_DOMAIN = None                              # Session的cookie保存的域名
# SESSION_COOKIE_SECURE = False                             # 是否Https传输cookie
# SESSION_COOKIE_HTTPONLY = True                            # 是否Session的cookie只支持http传输
# SESSION_COOKIE_AGE = 1209600                              # Session的cookie失效日期(2周)
# SESSION_EXPIRE_AT_BROWSER_CLOSE = False                   # 是否关闭浏览器使得Session过期
# SESSION_SAVE_EVERY_REQUEST = False(推荐True)             # 是否每次请求都保存Session,默认修改之后才保存

SESSION_ENGINE = 'django.contrib.sessions.backends.cashe'   #引擎,缓存+数据库,推荐使用
SESSION__CASHE_ALLAS ='default'                             # 使用缓存别名

用户登陆demo


models

from django.db import models
class Boy(models.Model):
    nickname = models.CharField(max_length=32)
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=63)

class Girl(models.Model):
    nickname = models.CharField(max_length=32)
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=63)

class B2G(models.Model):
    b = models.ForeignKey(to='Boy', to_field='id',on_delete='')
    g = models.ForeignKey(to='Girl', to_field='id',on_delete='')

urls

urlpatterns = [
    url('admin/', admin.site.urls),
    url(r'^login.html$', account.login),
    url(r'^index.html$', love.index),
    url(r'^loginout.html$',account.loginout),
    url(r'^others.html$',love.others),
]

views(创立文件夹的形式用来区分模块关系)

  • account模块
from django.shortcuts import render,HttpResponse,redirect
from app01 import models
def login(request):
    """
    用户登陆 
    :param request: 
    :return: 
    """
    if request.method == 'GET':
        return render(request,'login.html')
    else:
        user = request.POST.get('username')
        pwd = request.POST.get('password')
        gender = request.POST.get('gender')
        rmb = request.POST.get('rmb')
        # 性别判断
        if gender == "1":
            obj = models.Boy.objects.filter(username=user,password=pwd).first()
        else:
            obj = models.Girl.objects.filter(username=user,password=pwd).first()
        if not obj:
            # 未登录
            return render(request,'login.html',{'msg': '用户名或密码错误'})
        else:
            request.session['user_info'] = {'user_id':obj.id,'gender':gender,'username':user,'nickname':obj.nickname}
            return redirect('/index.html')

def loginout(request):
    """
    注销
    :param request: 
    :return: 
    """
    if request.session.get('user_info'):
        request.session.clear() # 清除服务端数据库session(推荐)
        # request.session.delete(request.session.session_key) #清除客户端session
    return redirect('/login.html')
  • love模块
from django.shortcuts import render,redirect,HttpResponse
from app01 import models
def index(request):
    """
    首页信息展示
    :param request: 
    :return: 
    """
    if not request.session.get('user_info'): # 获取浏览器中的session随机字符串
        return redirect('/login.html')
    else:
        # 男:女生列表
        # 女:男生列表
        gender = request.session.get('user_info').get('gender')
        if gender == '1':
            user_list  = models.Girl.objects.all()
        else:
            user_list = models.Boy.objects.all()
        return render(request,'index.html',{'user_list':user_list})

def others(request):
    """
    获取与当前用户有关系的异性
    :param request:
    :return:
    """
    current_user_id = request.session.get('user_info').get('user_id')
    gender = request.session.get('user_info').get('gender')

    if gender == '1':
        user_list = models.B2G.objects.filter(b_id=current_user_id).values('g__nickname')
    else:
        user_list = models.B2G.objects.filter(g_id=current_user_id).values('b__nickname')
    print('result', user_list)
    return render(request,'others.html',{'user_list':user_list})

html

  • login.html
<form method="POST" action="/login.html">
    {% csrf_token %}
    <p>用户:<input type="text" name="username" /></p>
    <p>密码:<input type="password" name="password" /></p>
    <p>
        性别:
            男<input type="radio" name="gender" value="1" /><input type="radio" name="gender" value="2" />
    </p>
    <p>
        <input type="checkbox" name="rmb" value="11"  /> 一个月免登录
    </p>
    <input type="submit" value="提交" />{{ msg }}
</form>
  • 建立html组件:user_header
<h1>当前用户:{{ request.session.user_info.nickname }}</h1>
<a href="/logout.html">注销</a>
  • index.html
{% include 'user_header.html' %}
<h3>异性列表</h3>
<a href="/others.html">查看和我有关系的异性</a>
<ul>
    {% for row in user_list %}
        <li>{{ row.nickname }}</li>
    {% endfor %}
</ul>
  • others.html
{% include 'user_header.html' %}
<h1>有关系的异性列表</h1>
<ul>
    {% for row in user_list %}
        {% if row.g__nickname %}
            <li>{{ row.g__nickname }}</li>
        {% else %}
            <li>{{ row.b__nickname }}</li>
        {% endif %}
    {% endfor %}
</ul>

Form组件


初识Form组件

  • html
<form method="post" action="/login/">
    {% csrf_token %}
    <p>username:<input type="text" name="username">{{obj.errors.username.0 }}</p>
    <p>password:<input type="password" name="password">{{obj.errors.password.0  }}</p>
    <input type="submit" value="submit"> 
</form>
  • urls
urlpatterns = [
……
    url(r'^login/$', views.login),
]

views

from django.forms import Form,fields
# -------定义Form验证规则类
class LoginForm(Form):
    # 正则验证: 不能为空,6-18
    username = fields.CharField(
        max_length=18,
        min_length=6,
        required=True,
        error_messages={
            'required': '用户名不能为空',
            'min_length': '太短了',
            'max_length': '太长了',
        }
    )
    # 正则验证: 不能为空,16+
    password = fields.CharField(min_length=16,required=True)

def login(request):  #Form表单提交形式
    if request.method == "GET":
        return render(request,'login.html')
    else:
       obj = LoginForm(request.POST) #将提交的数据交给Form组件验证
       if obj.is_valid():
           # 用户输入格式正确
           print(obj.cleaned_data) # 字典类型,只包含Form组件校验后的字段数据
           return redirect('http://www.baidu.com')
       else:
           # 用户输入格式错误
           return render(request,'login.html',{'obj':obj})

Form验证流程分析

第一步:实例化,将字段转换为self.fields格式
LoginForm实例化时,
self.fields={
'user': 正则表达式
'pwd': 正则表达式
}
第二步:循环self.fields,获得字段
 flag = True
 errors 
 cleaned_data

 for k,v in self.fields.items():
input_value = request.POST.get(k)   # 循环获得k字段的值(k需与前端字段名保持一致)
校验input_value的值是否匹配正则表达式  
flag = False
 return flag

---------------------------------------
if obj.is_valid():    #返回结果为True则通过验证
print(obj.cleaned_data)
else:
print(obj.errors)
return render(request,'login.html')

Form和Ajax提交验证(Ajax提交不会刷新,上次内容自动保留)

  • html
<h1>用户登录</h1>
<form id="f1" action="/login/" method="POST">
    {% csrf_token %}
    <p>
        <input type="text" name="user" />{{ obj.errors.user.0 }}
    </p>
    <p>
        <input type="password" name="pwd" />{{ obj.errors.pwd.0 }}
    </p>
    <input type="submit" value="提交" />
    <a onclick="submitForm();">提交</a>
</form>
<script src="/static/jquery-1.12.4.js"></script>
<script>
    function submitForm(){
        $('.c1').remove();
        $.ajax({
            url: '/ajax_login/',
            type: 'POST',
            data: $('#f1').serialize(),// 序列化:user=alex&pwd=456&csrftoen=dfdf
            dataType:"JSON",
            success:function(arg){
                console.log(arg);
                if(arg.status){

                }else{
                    $.each(arg.msg,function(index,value){  #index为字段名,value为错误值                     
                        var tag = document.createElement('span');
                        tag.innerHTML = value[0];
                        tag.className = 'c1';
                        $('#f1').find('input[name="'+ index +'"]').after(tag);
                    })
                }
            }
        })
    }
</script>
  • urls
……
url(r'^ajax_login/', views.ajax_login),

  • views
class LoginForm(Form): #定义Form组件类,用来验证请求数据
    user = fields.CharField(required=True,min_length=6)
    pwd = fields.CharField(min_length=18)
def ajax_login(request):
    import json
    ret={'status':True,'msg':None}
    obj = LoginForm(request.POST)
    if obj.is_valid():
        print(obj.cleaned_data)
    else:
        ret['status']=False
        ret['msg']=obj.errors # 获得字典,k为字段名,v为错误信息列表
    v=json.dumps(ret)
    print(obj.errors) #print时,会自动调用__str__(),在该方法中将字典组装成了<ul>标签
    return HttpResponse(v)

Form组件常用字段及参数

class TestForm(Form):
    t1 = fields.CharField(
        required=True,
        max_length=8,
        min_length=2,
        error_messages={
            'required': '不能为空',
            'max_length': '太长',
            'min_length': '太短',
        }
    )
    t2 = fields.IntegerField(
        min_value=10,
        max_value=1000,
        error_messages={
            'required': 't2不能为空',
            'invalid': 't2格式错误,必须是数字',
            'min_value': '必须大于10',
            'max_value': '必须小于1000',
        },
    )
    t3 = fields.EmailField(
        error_messages={
            'required': 't3不能为空',
            'invalid': 't3格式错误,必须是邮箱格式',
        }
    )

   # 为空,长度,格式,正则t4=fields.URLField()
t5=fields.SlugField()
t6=fields.GenericIPAddressField()
t7=fields.DateField()
t8=fields.DateTimeField()
t9=fields.RegexField('139\d+')  # 自定义正则表达式的字段验证规则
生成HTML标签:
widget=widgets.Select, ******** 用于指定生成怎样的HTML,select,text,input/.
label='用户名',  # obj.t1.label
disabled=False,              # 是否可以编辑
label_suffix='--->',            # Label内容后缀  需要在html模版中添加{{ obj.as_p }}来显示出所有Form类中定义的字段
initial='666',            # 无用,猜测有问题应该在input框中显示默认值
help_text='。。。。。。',  # 提供帮助信息

Form组件之保留上次输入框内容

  • html
# 采用Form组件生成的表单组件作为页面标签才能完成保留上次输入框的数据
<form action="/register/" method="POST" novalidate> #novalidate是忽略浏览器的表单验证规则
    {% csrf_token %}
    <p>
        {{ obj.user }} {{ obj.errors.user.0 }}
    </p>
    <p>
        {{ obj.email }} {{ obj.errors.email.0 }}
    </p>
    <p>
        {{ obj.password }} {{ obj.errors.password.0 }}
    </p>
    <p>
        {{ obj.phone }} {{ obj.errors.phone.0 }}
    </p>
    <input type="submit" value="提交"  />
  • urls
url(r'^register/', views.register),
  • views
class RegiterForm(Form):
    user = fields.CharField(min_length=8)
    email = fields.EmailField()
    password = fields.CharField()
    phone = fields.RegexField('139\d+')
    
def register(request):  
    if request.method == 'GET':
        obj = RegiterForm()
        return render(request,'register.html',{'obj':obj}) # 只返回表单组件类的HTML文本,无数值返回
    else:
        obj = RegiterForm(request.POST) # 因用户提交了数据,Form组件会返回带有输入框值的HTML文本
        if obj.is_valid():
            print(obj.cleaned_data)
        else:
            print(obj.errors)
        return render(request,'register.html',{'obj':obj})

Form组件完成学员管理系统


models

from django.db import models
class Classes(models.Model): #默认生成id
    title = models.CharField(max_length=32)
    def __str__(self):  # 重载方法,打印对象时,打印的内容为对象包含的title属性
        return self.title

class Student(models.Model):
    name = models.CharField(max_length=32)
    email = models.CharField(max_length=32)
    age = models.IntegerField(max_length=32)
    cls = models.ForeignKey('Classes',on_delete='')

class Teacher(models.Model):
    tname = models.CharField(max_length=32)
    c2t = models.ManyToManyField('Classes') # 自动生成第3张关联表,表中自动生成两张表的外键关联id

班级管理

# urls


url(r'^class_list/',views.class_list),
# views

def class_list(request):
    cls_list = models.Classes.objects.all()
    return render(request,'class_list.html',{'cls_list':cls_list})
# html


<h1>班级列表</h1>
<div>
    <a href="/add_class/">添加</a>
    <ul>
        {% for row in cls_list %}
            <li>{{ row.title }}<a href="/edit_class/{{ row.id }}">编辑</a></li>
        {% endfor %}
    </ul>
</div>
# urls

url(r'^add_class/',views.add_class),
# views

class ClassForm(Form):
    title = fields.RegexField('全栈\d+')  # 定义From组件校验规则
    
def add_class(request):
    if request.method == 'GET':
        obj = ClassForm() # 用Form组件生成表单标签到页面上
        return render(request,'add_class.html',{'obj':obj})
    else:
        obj = ClassForm(request.POST) # 通过Form组件进行校验
        if obj.is_valid():            
            models.Classes.objects.create(**obj.cleaned_data)
            return redirect('/class_list/')
        return render(request, 'add_class.html', {'obj': obj})

# html

<h1>添加班级</h1>
<form method="post" action="/add_class/" novalidate> # 忽略浏览器数据校验
    {% csrf_token %}
    {{ obj.title }}{{ obj.errors.title.0 }}  # 0代表错误信息列表中的第1条数据
    <input type="submit" value="提交">
# urls

url(r'edit_class/(\d+)',views.edit_class),#接收任意数字id的正则
# views

……
省略Form组件定义类
……

def edit_class(request, nid):
    if request.method == 'GET':
        row = models.Classes.objects.filter(id=nid).first()  # 让页面显示初始值
        # obj = ClassForm(data={'title': row.title}) #发送到前端时,Form组件会进行校验
        obj = ClassForm(initial={'title': row.title}) #Form组件不会进行校验
        return render(request,'edit_class.html',{'nid':nid,'obj':obj})
    else:
        obj = ClassForm(request.POST)
        if obj.is_valid():
            # models.Classes.objects.filter(id=nid).update(title = obj.cleaned_data['title'])
            models.Classes.objects.filter(id=nid).update(**obj.cleaned_data) # 以字典格式插入数据
            return redirect('/class_list/')
        return render(request,'edit_class.html',{'nid': nid,'obj':obj})
# html

<h1>编辑班级</h1>
<form method="POST" action="/edit_class/{{ nid }}/">
    {% csrf_token %}
    <p>
        {{ obj.title }} {{ obj.errors.title.0 }}
    </p>
    <input type='submit' value="提交" />
</form>

学生管理

# urls

url(r'^student_list/', views.student_list),
# views

def student_list(request):
    stu_list = models.Student.objects.all()
    return render(request, 'student_list.html', {'stu_list':stu_list})
# html

<a href="/add_student/">添加</a>
<ul>
    {% for row in stu_list %}
        <li>
            {{ row.name }}-{{ row.email }}-{{ row.age }}-{{ row.cls_id }}-{{ row.cls.title }}
            <a href="/edit_student/{{ row.id }}">编辑</a>
        </li>
    {% endfor %}
</ul>
# urls

url(r'add_student/', views.add_student),
# views

# Form组件定义:
class StudentForm(Form):
    name = fields.CharField(
        min_length=2,
        max_length=6,
        widget=widgets.TextInput(attrs={'class': 'form-control'})  #表单组件样式选择及属性设置,默认组件样式为text文本框
    )
    email = fields.EmailField(widget=widgets.TextInput(attrs={'class': 'form-control'}))
    age = fields.IntegerField(min_value=18,max_value=25,widget=widgets.TextInput(attrs={'class': 'form-control'}))

    cls_id = fields.IntegerField(
        # widget=widgets.Select(choices=[(1,'上海'),(2,'北京')])
        widget=widgets.Select(choices=models.Classes.objects.values_list('id','title'),attrs={'class': 'form-control'}) 
      # 获得单选下拉框
       
       # 另外一种写法:cls_id=fields.ChoiceField(
                      choices = models.Class.objests.values_list('id','title')
                       widget = widgets.Select(attr={'class':''form-control})
                      )
     )

def add_student(request):
    if request.method == 'GET':
        obj = StudentForm()
        return render(request,'add_student.html',{'obj':obj})
    else:
        obj = StudentForm(request.POST)
        if obj.is_valid():
            models.Student.objects.create(**obj.cleaned_data)
            return redirect('/student_list/')
        else:
            return render(request,'add_student.html',{'obj':obj})

# html


<form action="/add_student/" method="POST" novalidate>
    {% csrf_token %}
    <p>
     姓名:{{ obj.name }}{{ obj.errors.name.0 }}
    </p>
    <p>
     邮箱:{{ obj.email }}{{ obj.errors.email.0 }}
    </p>
    <p>
     年龄:{{ obj.age }}{{ obj.errors.age.0 }}
    </p>
    <p>
     班级:{{ obj.cls_id }}{{ obj.errors.cls_id.0 }}
    </p>
    <input type="submit" value="提交" />
</form>
# urls

url(r'^edit_student/(\d+)/', views.edit_student),
# views

……省略Form组件……
def edit_student(request,nid):
    if request.method == 'GET':
        row = models.Student.objects.filter(id=nid).values('name','email','age','cls_id').first() #如果不添加first会报错,错误为“要解压的值太多”
        obj = StudentForm(initial=row)    # 将数据封装至Form组件中,initial设置为不做数据验证
        return render(request,'edit_student.html',{'nid':nid, 'obj':obj})
    else:
        obj = StudentForm(request.POST)
        if obj.is_valid():
            models.Student.objects.filter(id=nid).update(**obj.cleaned_data)
            return redirect('/student_list/')
        else:
            return render(request,'/edit_student.html',{'nid':id, 'obj':obj})
# html

<link rel="stylesheet" href="/static/bootstrap-3.3.5-dist/css/bootstrap.css"/>

<div style="width: 500px;margin: 0 auto;">
    <form class="form-horizontal" method="POST" action="/edit_student/{{ nid }}/">
        {% csrf_token %}
        <div class="form-group">
            <label class="col-sm-2 control-label">姓名:</label>

            <div class="col-sm-10">
                {{ obj.name }}
            </div>
        </div>
        <div class="form-group">
            <label class="col-sm-2 control-label">邮箱:</label>

            <div class="col-sm-10">
                {{ obj.email }}
            </div>
        </div>
         <div class="form-group">
            <label class="col-sm-2 control-label">年龄:</label>

            <div class="col-sm-10">
                {{ obj.age }}
            </div>
        </div>
         <div class="form-group">
            <label class="col-sm-2 control-label">班级:</label>

            <div class="col-sm-10">
                {{ obj.cls_id }}
            </div>
        </div>
        <div class="form-group">
            <div class="col-sm-offset-2 col-sm-10">
                <input type="submit" class="btn btn-default" value="提交" />
            </div>
        </div>
    </form>
</div>

老师管理

# urls

url(r'^teacher_list/', views.teacher_list),
# views

def teacher_list(request):
    tea_list = models.Teacher.objects.all()
    return render(request,'teacher_list.html',{'tea_list':tea_list})
# html

<h1>老师列表</h1>
<div>
    <a href="/add_teacher/">添加</a>
</div>
<table border="1">
    <thead>
        <tr>
            <th>ID</th>
            <th>老师姓名</th>
            <th>任教班级</th>
            <th>编辑</th>
        </tr>
    </thead>
    <tbody>
        {% for row in tea_list %}
            <tr>
                <td>{{ row.id }}</td>
                <td>{{ row.tname }}</td>
                <td>{{ row.c2t }}</td>
                <td>
                    <a href="/edit_teacher/{{ row.id }}/">编辑</a>
                </td>
            </tr>
        {% endfor %}
    </tbody>
</table>
# urls

url(r'^add_teacher/', views.add_teacher),
# views

#Form组件定义:(实现数据动态显示)
class TeacherForm(Form):
    tname=fields.CharField(min_length=2)

    cls_id = fields.MultipleChoiceField(  # 多选模式,过滤出字典中包含的是列表格式数据{'cls_id': ['2', '3']},而非列表字符串格式{'cls_id': “['2', '3']”}
        # choices=models.Classes.objects.values_list('id','title'),
        #  生成下拉框对应的值,有了__init__()构造方法后可以不用写choices关键字参数
        widget=widgets.SelectMultiple # 多选下拉框表单组件
    )
    #因From组件对象不会重新启动获得数据库的值,
    #所以需要每一次类加载实例化的构造方法来重新获取数据库的值,实现数据动态显示
    def __init__(self,*args,**kwargs):
        super(TeacherForm,self).__init__(*args,**kwargs) # 调用父类构造方法
        self.fields['cls_id'].widget.choices=models.Classes.objects.values_list('id','title')
         # 获得字典中字段的插件widget中的choices

# Form组件执行方式
# obj = TeacherForm()
# 1. 找到所有字段
# 2. self.fields = {   # 加载字典中的所有字段
#       tname: fields.CharField(min_length=2)
# }

def add_teacher(request):
    if request == 'GET':
        obj = TeacherForm()
        return render(request,'add_teacher.html',{'obj':obj})
    else:
        obj = TeacherForm(request.POST)
        if obj.is_valid():
            cls_id= obj.cleaned_data.pop('cls_id') # 单独提取出cls_id的值
            row = models.Teacher.objects.create(**obj.cleaned_data)
            # **字典,会自动将字典格式{'tname': 'tom'}转换成tname='tom'格式的数据
            row.c2t.add(*cls_id) # *代表列表格式插入
            return redirect('/teacher_list/')
        return render(request,'add_teacher.html',{'obj':obj})
# html

<form method="POST" action="/add_teacher/" novalidate>
    {% csrf_token %}
    <p>
        姓名:{{ obj.tname }}
    </p>
    <p>
        班级:{{ obj.cls_id }}
    </p>
    <input type="submit" value="提交" />
</form>
# urls

url(r'^edit_teacher/(\d+)/', views.edit_teacher),

# views

def edit_teacher(request,nid):
    if request.method == "GET":
        row = models.Teacher.objects.filter(id=nid).first()
        class_ids = row.c2t.values_list('id')   #获得关联的班级id,值为[(3,),(1,)]
        # zip()将[(3,),(1,)]转换为[(3,1),]
        id_list = list(zip(*class_ids))[0] if list(zip(*class_ids)) else []      
        # obj = TeacherForm(initial={'tname':row.tname,'xx':[1,2,3]})
        obj = TeacherForm(initial={'tname':row.tname,'cls_id':id_list})
        return render(request,'edit_teacher.html',{'obj':obj})
# html

{{ obj.tname }}
{{ obj.cls_id }}

Form常用组件定制


class TestForm(Form):
    t1 = fields.MultipleChoiceField(    # 验证多选框的值
        choices =[(1,'篮球'),(2,'足球')],   # 制定显示值
        widget=widgets.CheckboxSelectMultiple   # 生成多选框组件
    )
    t2 =fields.MultipleChoiceField( 
        choices=[(1,'篮球'),(2,'足球')],
        widget=widgets.RadioSelect # 单选按钮组件
    )
    t3 = fields.FileField(
        widget=widgets.FileInput    # 文件输入框
    )

Form组件中的钩子(扩展自定义函数)


class TestForm(Form):
	user = fields.CharField( # 添加自定义正则表达式
	validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), 
				RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],)

email = fields.EmailField()    
def clean_user(self): # 常用于扩展用户名是否在数据库中存在,验证码是否匹配,传参request
    v = self.cleaned_data['user']
    if models.Student.object.filter(name=v).count():
        raise ValuedationError('用户已存在')
    return self.cleaned_data['user']

def clean(self):    # 常用于字段的联合唯一判断
    user=self.cleaned_data.get('user')
    email=self.cleaned_data.get('email')
    if models.Stuent.objects.filter(user=user,email=email).count():
        raise ValuedationError('用户名和邮箱联合已经存在')
    return self.cleaned_data

Ajax提交数据部分


原生Ajax提交数据

// 原生Ajax完成GET方式提交数据
function add2() {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange=function () {
        if (xhr.readyState == 4){
            // 状态值为4表示接收全部数据
            alert(xhr.responseText);
        }
    }
    xhr.open('GET','/add2/?i1=12&i2=19');
    xhr.send();
}

// 原生Ajax完成POST方式提交数据
function add2() {
    var xhr =new XMLHttpRequest();
    xhr.onreadystatechange=function () {
        if (xhr.readyState == 4){
            alert(xhr.responseText);
        }
    }
    xhr.open('POST','/add2/');
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded ')
    # 必须要添加Http请求头数据才能从body中取出数据
    xhr.send('i1=12&i2=19');
}

伪Ajax提交数据

<form id="f1" method="POST" action="/fake_ajax/" target="ifr"> // 将由name=ifram的标签去发送请求到
     // ifram自动发送请求,且接收响应值显示在该标签内,标签外的内容不变化
    <iframe id="ifr" name="ifr" style="display: none"></iframe>
    <input type="text" name="user" />
    <a onclick="submitForm();">提交</a>
</form>

<script>
    function submitForm(){
        // 先绑定loadIframe,将loadIframe加载到内存后再提交,解决由上到下执行顺序的问题
        // onload表示有返回的内容时加载该标签,会再次动触发加载到内存中的loadIframe函数
        document.getElementById('ifr').onload = loadIframe; 
        document.getElementById('f1').submit();
    }

    function loadIframe(){
        var content = document.getElementById('ifr').contentWindow.document.body.innerText; // 获得标签内的数据
        alert(content);
    }
</script>

上传文件


原生Ajax上传文件

  • html
 <h1>原生Ajax上传文件</h1>
    <input type="file" id="i1" />
    <a onclick="upload1();">上传</a>
    <div id="container1"></div>

<script>
     function upload1(){
            var formData = new FormData(); # 构造需要发送给服务器的内容
            formData.append('k1','v1');
            formData.append('fafafa',document.getElementById('i1').files[0]); # 获得文件对象

            var xhr = new XMLHttpRequest();
            xhr.onreadystatechange = function(){
                if(xhr.readyState == 4){
                    var  file_path = xhr.responseText;
                    var tag = document.createElement('img');
                    tag.src = "/"+ file_path;
                    document.getElementById('container1').appendChild(tag);
                }
            };
            xhr.open('POST','/upload/');
            xhr.send(formData);
        }
</script>
  • views
import os
def upload(request):
    if request.method == 'GET':
        return render(request, 'upload.html')
    else:     
        file_obj = request.FILES.get('fafafa') # 获得浏览器发送的文件
        file_path = os.path.join('static', file_obj.name) # 组装文件路径
        with open(file_path, 'wb') as f:
            for chunk in file_obj.chunks(): # 把文件块以字节的形式写入文件
                f.write(chunk)
        return HttpResponse(file_path)
  • JQuery Ajax上传文件
  • html
<h1>JQuery Ajax上传文件</h1>
<input type="file" id="i2" />
<a onclick="upload2();">上传</a>
<div id="container2"></div>

<script src="/static/jquery-1.12.4.js"></script>
function upload2() {
    var formData = new FormData();
    formData.append('fafafa',$('#i2').files[0]);
    $.ajax({
        url:'/upload/',
        type:'POST',
        data:formData,
        contentType:false, # 避免 JQuery 对其操作,从而失去分界符,而使服务器不能正常解析文件,ajax会默认添加请求头 
        processData:false, # 交由FormData进行处理
        success:function (arg) {
            var tag = document.createElement('img')
            tag.src='/'+arg;
            $('#container2').append(tag)
        }
    })
}
  • views
同原生Ajax的后台处理一样

伪Ajax上传文件(浏览器兼容性更好)

  • html
<h1>伪Ajax上传文件</h1>
<form id="f1" method="POST" action="/upload/" target="ifr" enctype="multipart/form-data">
    <iframe id="ifr" style="display: none" name="ifr"></iframe>
    <input type="file" name="fafafa"/>
    <a onclick="upload3();">上传</a>
</form>
<div id="container3"></div>

function upload3() {
    document.getElementById('ifr').onload = loadIframe;
    document.getElementById('f1').submit();
}
function loadIframe() {
    var content = document.getElementById('ifr').contentWindow.document.body.innerText;
    var tag = document.createElement('img');
    tag.src='/'+content;
    $('#container3').append(tag);
}
  • views
同原生Ajax的后台处理一样

jsonp(解决跨域问题,需请求方与响应方约定规则)


属于一种技巧/技术

Ajax使用时:
访问自己域名URL-可以
访问其他域名URL - 被阻止(跨站请求)

被阻止原因:
浏览器:因为同源策略,Ajax跨域发送请求时,浏览器拒绝接收响应数据。
但script标签发送请求时,返回的数据可以被允许接收,不被浏览器禁止。
示例:
<a onclick="sendMsg();">发送</a>
<script src="/static/jquery-1.12.4.js"></script>

{#      // 加载该内容时会将文件中的函数加载到内存中,当响应的数据中包含该函数名时,会执行内存中的该函数#}
<script src="/static/common1.js"></script>
<script src="/static/common2.js"></script>

function sendMsg() {
    var tag = document.createElement('script')
    
# 通过scripte向该网址发送请求,然后将该页面内容全部下载到内存中
# 该网址将返回调用list函数的执行方法: list(……),会立即执行js文件中的函数
    tag.src='http://www.jxntv.cn/data/jmd-jxtv2.html?callback=list&_=1454376870403' 
    document.head.appendChild(tag)
}

# 使用该方式的要求:
客户端: 发送函数名,URL?callback = xxx
         定义函数,function xxx(arg){}

服务端:获取发送过来的函数名 funcname = request.GET.get(callback)
        返回时,将需要返回的数据作为参数传入函数名中 funcname(……)一起发送回来,然后自动执行内存中存在的该方法

注:实现跨域数据交互时,需要双方事先约定函数规则
# common1.js文件内容

function f1() {
    alert('kkk')
}

# common2.js文件内容
function list(arg){  # 定义list函数
    console.log(arg)
}

自定义API用作返回数据

  • urls
url(r'^users/', views.users),
  • views
def users(request):
    print('请求到来')
    callback = request.GET.get('funcname')
    user_list=[
        'leo', 'tom', 'jack'
    ]
    temp = '%s(%s)'%(callback, user_list)
    print(temp)
    # return HttpResponse(json.dumps(user_list))
    return HttpResponse(temp) # 返回一个字符串对象
  • setting配置‘
ALLOWED_HOSTS = ['www.s4.com'] # 允许访问的主机
  • 本地PC配置
C:\Windows\System32\drivers\etc  

往hosts文件中添加内容:127.0.0.1 www.s4.com
  • 2种解决方案
<input type="button" onclick="sendMsg();" value="ajax提交">
<input type="button" onclick="sendMsg2();" value="script标签方式提交">
<script src="/static/jquery-3.3.1.min.js"></script>
<script>
  {#方案一:使用jsonp形式跨站完成数据交互#}
    function sendMsg() {      
        $.ajax({
            url: 'http://www.s4.com:8001/users/',
            type: 'GET',
            {#声明时,使用的是jsonp发送数据,不采用XMLHttpResponse发送数据,jsonp采用GET形式,即使声明的是POST形式也会自动转换成GET #}
            dataType: 'JSONP',
            {#其它服务器接收到的K值,从而获得函数名bbb#}
            jsonp: 'funcname',
            {#定义回调函数#}
            jsonpCallback: 'bbb'
        })
    }

   {#方案二:使用js形式跨站完成数据交互,与jsonp原理一样#}
    function sendMsg2() {
        var tag = document.createElement('script');
        {#返回结果为:bbb(['leo', 'tom', 'jack'])#}
        tag.src = "http://www.s4.com:8001/users/?funcname=bbb";
        document.head.appendChild(tag);
    }

    function bbb(arg) {     
        alert(arg);
    }

CORS解决跨域问题(跨来源资源共享)(响应头添加值)


简单请求

<input type="button" onclick="getUsers();" value="cors跨来源资源共享方式">
function getUsers() {
    $.ajax({
        url:'http://www.s4.com:8001/new_users/',
        type:'GET',
        success:function (arg) {
            alert(arg)
        }
    })
}

第三方服务器

 url(r'^new_users/', views.new_users),
def new_users(request):
    user_list = [
        'lleo', 'tom', 'jack'
    ]
    obj = HttpResponse(json.dumps(user_list))
    # 添加令牌,添加响应头信息
    obj["Access-Control-Allow-Origin"]='http://www.s5.com:8000' # 允许跨站的数据响应返回,浏览器不设阻拦
    # obj["Access-Control-Allow-Origin"]=”*“
    print('请求')
    return obj

复杂请求

  • 第三方服务器
# 复杂请求的处理
def new_users(request):
    if request.method == 'OPTIONS':
        obj = HttpResponse()
        obj["Access-Control-Allow-Origin"] ='*'
        obj["Access-Control-Allow-Origin"] ='DELETE'
        return obj
    
    obj = HttpResponse('adsf')
    obj["Access-Control-Allow-Origin"] = '*'
    return obj