Django知识点总结
Django知识点总结
web架构简介
主流python web框架
Django、Torando、Flask、Bottle
MVC和MTV
model(模型、数据库)、views(html模版)、controllers(业务逻辑处理) --> MVC
model(模型、数据库)、templates(html模版)、view(业务逻辑处理) --> MTV
程序的三层架构
数据处理层
- User表的增,删,改,查
业务处理层
- 登录
- 首页
…
UI层(交互界面)
- web端
- 客户端
Django项目目录结构
/Projname
__init__.py
settings.py # 用户级的配置文件
urls.py # 路由配置,为用户输入的url匹配对应的视图函数
/app01 # app文件
/migration/ # 数据迁移记录
__init__.py
admin.py # Django自带的后台管理功能
apps.py # app注册配置文件
models.py # 模型
test.py # 单元测试
view.py # 业务处理,循环函数 # 可将view变换成目录,并分块
/templates # 模板目录(html)
/static # 静态文件目录(自定)
manage.py # 程序启动文件
# 以下可选
/repository # 可将主程序的Models提取到此处
/api # 专门的api处理
/backend # 用于用户的后台管理
Django配置入门
开始
安装
sudo pip3 install django
# 或
pip3 install django==2.0.1 # 指定版本
创建Django项目(Project)
django-admin startproject projectname
进入程序目录
cd projectname
启动服务器,等待用户发送请求
python manage.py runserver 127.0.0.1:8080
模版及静态文件路径配置
——在settings.py
文件下操作
模版路径配置
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')] # 此处为设置模版搜索路径
# 'templates'名称要与模版文件所在的目录一致
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
静态文件路径配置
STATIC_URL = '/static/' # 路径前缀,程序将在根目录的此路径下搜索静态文件
STATICFILES_DIRS =( # 新增此变量配置
os.path.join(BASE_DIR, 'static'), # 一定要加逗号,'static'与 STATIC_URL保持一致
)
模板文件即可使用该目录下的静态文件
<link rel="stylesheet" href="/static/css-filename.css">
创建app
终端创建
cd到项目目录下
python3 manage.py startapp app01 # 可有多个,用于主站、后台管理、运维平台等等
通过pycharm创建
在创建Project的时候输入app名
高级配置文件
——适用于非浏览器的的客户端
参考django内部的配置文件,制作支持用户配置及默认配置文件
django.conf import global_settings # 默认配置文件
django.conf import settings # 用户配置文件
在客户端的start.py文件下
import os
os.environ['USER_SETTINGS'] = "config.settings" # 执行时自动将用户配置文件设为环境变量
# config.py文件下
import os
from . import global_settings
class Settings(object):
def __init__(self):
# 找到默认配置,先导入,如有重复以用户为准
for name in dir(global_settings):
if name.isupper():
value = getattr(global_settings, name)
setattr(self, name, value)
# 找到用户自定义配置
settings_module = os.environ['USER_SETTINGS']
if not settings_module:
return
m = importlib.import_module(settings_module)
for name in dir(m):
if name.isupper():
value = getattr(m, name)
setattr(self, name, value)
小技巧:
#测试模式配置
DEBUG=True
# 函数中
DEBUG = settings.DEBUG
if DEBUG:
# 启动测试模式专用程序
# except显示错误堆栈信息
try:
...
except Exception:
print(traceback.format_exc())
程序启动方法的设置(不用manage.py启动)
在启动文件中输入以下代码,达到类似manage.py启动的效果
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "项目名.settings") # 设置环境变量
import django
django.setup() # 手动注册django所有的app
新建Django文件总结:
- 创建project
- 创建APP
- 创建数据库
- 静态文件路径配置
urls.py
新建url模式指向视图函数。view.py
创建创建视图函数返回模板。- (如有)创建form表单约束规则。
- 新建前端模版。
- 重复5、6、7、8步
Views——业务逻辑处理
FBV和CBV
指的是使用函数来作为业务逻辑处理的方式,CBV是指url对应类;FBV指url对应函数,FBV更为灵活。
FBV
urlpatterns = [
url(r'^index/', index), # FBV:url对应函数
]
CBV
urls.py
urlpatterns = [
url(r'^login/', view.Login.as_view()), # CBV:url对应类,自动处理GET和POST等请求
]
view.py
from django.shortcuts import View
# 通过反射作用实现
class Login(View): # 继承View类
def get(self, request): # 定义get请求时的函数
return render(request, 'login.html')
def post(self, request):
return render(request, 'login.html')
方法命名应遵循REST API规则
URL定位资源,用HTTP动词(GET,POST,DELETE,DETC)描述操作。
GET查找、POST创建、PUT更新、DELETE删除
其余PATCH、HEAD、OPTIONS、TRACE
Restful API
面向资源编程:把网络上的任何东西当作资源
1.method:方法
# GET
# POST
# PUT
# DELETE
2.状态码
# 200
# 404
# 500
# 403
3.url必须是名词
参考资料: http://www.ruanyifeng.com/blog/2014/05/restful_api.html
小贴士
重写源码中的dispatch功能
class Login(View):
def get(self, request): # 定义get请求时的函数
return render(request, 'login.html')
def post(self, request):
return render(request, 'login.html')
def dispatch(self, request, *args, **kwargs):
obj = super(Login, self).dispatch(request, *args, **kwargs) # 调用View中的dispatch 方法再进行改写
'''
可在此加入改写代码
'''
return obj
cookie控制
什么是cookie?
- 保存在浏览器上的键值对
- 服务端可以向用户浏览器端写cookie
- 客户端每次发请求时会携带cookie去
应用于投票、登录等
使用方法
def login(request)
obj = HttpResponse('OK')
# 设置cookie超时
# 方式1 设置时间(推荐)
obj.set_cookie(
'key', # 第一个参数为key
value='value', # 第二个参数为value
max_age=100 # 超时时间为100秒
)
# 方式2 设置日期
import datetime
from datetime import timedelta
ct = datetime.datetime.utcnow()
v = timedelta(seconds=10) # 计算10秒以后的日期
value = ct + v
obj.set_cookie(
'key',
value='value',
expires=value # 设置最长超时日期
)
return obj
# 其他设置:url、域名等
obj.set_cookie(
'key',
value='value',
path='/url/' # 设置可用的url
domain=None, # 设置可用域名,默认为当前域名
secure=False, # 证书:为https提供的功能,使用https需要改成True
httponly=False # 只能通过http请求中进行传输,js代码无法获取
)
HttpResponse、render、redirect均可使用
获得cookie
request.COOKIES
request.get_signed_cookie('k1', salt='jjjj')
cookie签名
tk = obj.set_signed_cookie('ticket','123123sda',salt='jjjj')
创建自己的签名规则
- 在创建新模块c1,自己写一个TimestampSigner
from django.core.signing import TimestampSigner
class Mysigner(TimestampSigner):
def sign(self, value):
# 此处可自行加密
return value + '12345678'
def unsign(self, value, max_age=None):
print(value)
return value[0:-8]
settings.py
文件中将配置改成
SIGNING_BACKEND = "c1.MySigner"
小贴士
用户登录状态的cookie应用——使用装饰器对函数进行修改
Session控制
session与cookie
cookie是保存在客户端浏览器上的键值对
Session即保存在服务器端的数据(本质是键值对)
应用:依赖cookie
作用:保持会话(web网站)
session一般使用过程
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.服务端保存(django_session表里面)
# {
# 随机字符串1:{'username': 'alex', 'email': '11',...}
# }
request.session['username'] = obj.username
return redirect('/index/')
else:
return render(request, 'login.html', {'msg': "用户名或密码错误"})
def index(request):
# 1.获取客户端cookie中的随机字符串
# 2.去session中查找有没有随机字符串
# 3.去session对应的key的value中查看是否有username
v = request.session.get('username')
if v:
return HttpResponse("登录成功")
else:
return redirect('/login/')
session相关操作
def index(request):
# 获取、设置、删除Session中数据
request.session['k1']
request.session.get('k1',None) # 没有k1,则创建默认给None
request.session['k1'] = 123
request.session.setdefault('k1',123) # 存在则不设置
del request.session['k1'] # 删除值,不删除对
# 所有 键、值、键值对
request.session.keys()
request.session.values()
request.session.items()
request.session.iterkeys() # 以迭代器的方式,一个一个取
request.session.itervalues()
request.session.iteritems()
# 用户session的随机字符串
request.session.session_key
# 将所有Session失效日期小于当前日期的数据删除
request.session.clear_expired()
# 检查 用户session的随机字符串 在数据库中是否
request.session.exists("session_key")
# 删除当前用户的所有Session数据
request.session.delete("session_key")
request.session.set_expiry(value)
#* 如果value是个整数,session会在些秒数后失效。
#* 如果value是个datatime或timedelta,session就会在这个时间后失效。
#* 如果value是0,用户关闭浏览器session就会失效。
#* 如果value是None,session会依赖全局session失效策略。
修改session默认配置
Django原本已有一套默认配置,如需自定义,须在settings.py
下增加以下变量
数据保存配置
# 数据库Session
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 引擎(默认)
# 文件Session
SESSION_ENGINE = 'django.contrib.sessions.backends.file' # 使用文件保存
SESSION_FILE_PATH = None # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir()
# 缓存Session
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
# 将session数据放到另外一台机子的服务器内存上。
SESSION_CASHE_ALIAS = 'default' # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置
# 需配合Django缓存配置
# 缓存+数据库Session
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
# 数据库用于做持久化,缓存用于提高效率
# 存于cookie中
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies' # 引擎
其他相关配置
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 # 是否每次请求都保存Session,默认修改之后才保存(默认)
路由系统
路由关系配置
——编辑url并定义处理函数
主目录urls.py
文件下
urlpatterns = [
# url(r'^admin/', admin.site.urls), # 在此列表新增url,把admin替换成url后缀
url(r'^login/', view.login), # 例:替换成r'^login/'和login函数
# 终止符
url(r'^index/', view.index), # r'^index/'为正则表达式,以^开头,$或/表示结束
url(r'^index/(\d+)/', view.index), # 使用正则表达式来匹配url,匹配出的组作为参数传到views
# FBV接收 --> def index(request,a1)
# 位置参数式
url(
r'^index/(\w+)/(\w+)', # 两个及以上正则表达式
# FBV接收 --> def index(request,a1,a2) 按顺序接收
view.index
),
# 指定参数式
url(
r'^index/(?P<a1>\w+)/(?P<a2>\w+)', # 两个及以上正则表达式
# FBV接收 --> def index(request,a1,a2) 指定参数名称为a1,a2,并接收
view.index
),
# 混合式
没有混合式,必须统一,要么位置参数。
]
正则补充:
\w
匹配字母或数字或下划线或汉字 等价于'\[^A-Za-z0-9_]\'
。
伪静态
静态的好处是速度快(搜索引擎更偏爱)
动态因为要到数据库查找数据所以慢
伪静态是为了做SEO,让搜索引擎觉得你是个静态网站
url(r'^index/(\w+).html$',index), # 加上.html结尾,伪装为静态网站
路由分发
使用include函数分发
- 当用户浏览器输入
127.0.0.1/app01/index.html
- 主目录下匹配前面的部分
url(r'^app01/',include('app01.urls')), # 将url分发到app01执行
- 转交app01匹配剩余的部分
url(r'^index.html',view.index), # 在app01下匹配index函数
错误url处理
url(r'^',view.index), # 将错误的url给到首页
循环函数别名反向生成url
路由配置
# 位置参数版
url(r'^index.html/(\d+)/',view.index, name='n1'), # 为url命名
# 命名参数版
url(r'^index.html/(?P<a1>\d+)/',view.index, name='n2'), # 为url命名
函数获取url
import reverse
# 位置参数版
v = reverse('n1', args=(1,)) # args为返回url:'/index/a' 中的a
# 命名参数版
v = reverse('n2', kwargs={'a1: 111'}) # a1为命名参数指定名称的参数
配合特殊标记的用法
<form method='POST' action='{% url "n1" %}' /> // 模版特殊标记通过名称反生成url
数据替换结果为
<form method='POST' action='/index/1/' /> // 模版特殊标记通过名称反生成url
可在标记中加入参数
<ul>
{% for i in user_list %}
<li> {{ i }} | <a herf="/edit/{{ i }}/">编辑</a></li>
// 下面语句与上面效果一样
<li> {{ i }} | <a herf="{% url 'n1' i %}">编辑</a></li>
{% endfor %}
</ul>
权限管理中,保存别名,然后反生成菜单的url作为action的值
注:别的框架不支持别名
UI层——模版语言
路由关系配置
路由与视图函数的连接
views.py
部分
import HttpResponse,render
def login(request): # 定义处理url的函数
return HttpResponse('<input type="text" />') # 利用HttpResponse返回字符串
def index(request):
return render(request,"index.html") #直接读取html文件,读取内容并返回字符串
# 注:请提前做好模版文件路径配置
render(request, 模版路径, dict)
HttpResponse(request, str(or dict))
redirect("网址(or 新url)")
urls.py
部分
urlpatterns = [
# url(r'^admin/', admin.site.urls), # 在此列表新增url,把admin替换成url后缀
url(r'^login/', views.login), # 例:替换成r'^login/'和login函数
url(r'^index/', views.index),
]
request方法简介
用get()、getlist()(用于多选下拉框)方法可以拿到用户请求的相关信息
GET —— 只有request.GET有值
POST——两者都有值
method——请求的方法
FILES——请求中附带的文件
更多方法:https://www.cnblogs.com/scolia/archive/2016/07/01/5633351.html
视图函数与模板文件的连接
模版html文件下
<form method="post" action="/login/"> {# method作为提交的方法,value作为提交的值 #}
<input type="text" name="username"> {# name作为提交的键,value作为提交的值 #}
<input type="password" name="password">
<input type="submit" value="登录">
{{ msg }} {# 特殊字符 放提示语的地方 #}
</form>
views.py
下
def login(request):
if request.method == 'get':
return render(request, 'login.html') # 判断客户端使用的方法及对其进行处理
else:
# request.GET或request.POST 储存用户发过来的值
u = request.POST.get('user') # 用户POST提交的username
u = request.POST.get('pwd') # 用户POST提交的password
if u == 'root' and p == '123':
return redirect('http://www.xxx.com')
else:
# 用户名密码错误时,动态显示 提示信息
return render(request, 'login.html', {'msg':'用户名或密码错误'})
模版渲染
特殊标记返回值不一定是字符串,可以为列表或字典
return render(
request,
'index.html',
{
'name': "alex",
'users': ['李志', '李杰'],
'user_dict':{'k1': 'v1', 'k2': 'v2'},
'user_list_dict':[
{'id':1, 'name':'alex', 'email': 'alex3714@163.com'},
{'id':2, 'name':'alex2', 'email': 'alex23714@163.com'},
{'id':3, 'name':'alex3', 'email': 'alex33714@163.com'},
]
}
)
普通取值
<p>{{ name }}</p>
<p>{{ users.0 }}</p> # 获取列表中第一个元素
<p>{{ users.1 }}</p>
<p>{{ user_dict.k1 }}</p> # 获取字典中key为k1的value值
<p>{{ user_dict.k2 }}</p>
循环取值
<ul>
{% for item in users %} # 循环开始
<li>{{ item }}</li>
{% end for %} # 循环结束
</ul>
<table>
{% for row in users %} # 循环开始
<tr>
<td>{{ row.id }}</td>
<td>{{ row.name }}</td>
<td>{{ row.email }}</td>
<td>
<a>编辑</a><a herf="/del"={{ row.id }}></a>
</td>
</tr>
{% end for %}
</table>
{# 字典循环 #}
{% for k, v in userinfo.items %}
<h6>{{ k }}-{{ v }}</h6>
{% endfor %}
母版继承
母版:存放所有页面公用部分
子版:继承母版公用部分及定制私有部分
{% extends 'layout.html' %} {# 表示继承母版 #}
{% block html %} {# 母版中应有此block html标记 #}
<div>...</div> {# 在母版中有此block标记的地方插入以下代码 #}
{% endblock %}
{# block的其他用途 #}
{% block css %}
<style></style> {# 导入自己专用的css #}
{% endblock %}
{% block js %}
<script></script> {# 导入自己专用的js #}
{% endblock %}
用include导入小组件
创建小组件如pub.html
{# 注意:删除其他标签,只剩下组件部分 #}
<div>
<h3>特别漂亮的小组件</h3>
<div class="title">标题:{{ name }}</div>
<div class="content">内容:{{ name }}</div>
</div>
程序会先导入所有组件、母版后再开始渲染,因此组件内的特殊标记也会被渲染成功
在需要用到小组件的地方导入
{% include "pub.html"%}
自定义simple_tag
simple_tag是指下方的upper
这种有函数作用的标签
{{ name|upper }}
创建simple_tag有以下步骤:
a、在app中创建templatetags模块
b、创建任意 .py 文件,如:xx.py
from django import template
from django.utils.safestring import mark_safe
register = template.Library()
@register.filter
def my_upper(value, args) # fillter 最多只能传2个参数,可传入列表后逐个提取
return value.upper() + args
@register.simple_tag
def my_simple_time(v1,v2,v3):
return v1 + v2 + v3
@register.simple_tag
def my_input(id,arg):
result = "<input type='text' id='%s' class='%s' />" %(id,arg,)
c、在使用自定义simple_tag的html文件中导入之前创建的 xx.py 文件名
{% load xx %}
d、使用simple_tag
{% name|my_upper:"1,2,3" %} {# @register.filter可作为条件语句进行判断使用。注意冒号后不可有空格 #}
{% if name|my_bool %}
<h3>真</h3>
{% else %}
<h3>假</h3>
{% endif %}
{% my_simple_time 1 2 3 %} {# @register.simple_tag不可用于条件语句 #}
{% my_input 'id_username' 'hide' %}
e、在settings中配置当前app,不然django无法找到自定义的simple_tag
Ajax应用
Ajax是前端与后台数据交互的技术,即偷偷地向后台发请求,可用于模态对话框(自制的弹框)。
另外一种类似的方式是'新url',就是跳转到新的url,返回新的页面 ,用于处理较大量的数据。
使用form表单提交,页面会刷新,Ajax提交不刷新
使用jquery中的ajax方法
$.ajax({
url: '要提交的地址',
type: 'post', // GET或POST,提交方式
data: {'k1': 'v1', 'k2': 'v2'}, // 提交的数据的值,支持列表,不支持字典,只能通过序列化
traditional: true, // 如果提交的数据的值有列表则需要添加此属性
dataType: 'JSON' // 返回的数据反序列化
success: function (data) {
// 当前服务端处理完毕后,自动执行的回调函数
// data为返回的数据
location.herf = 'www.baidu.com'
}
})
原生Ajax——XMLHttpRequest
function add2(){
var xhr = new XMLHttpRequest();
var onreadystatechange = function(){
if (xhr.readyState == 4) // 判断准备状态是否在已经获得相应
alert(xhr.responseText); // 响应的结果数据
};
// GET请求
xhr.open('GET', '/add2/?i1=12&i2=19') // 注明请求方法及需要打开的url
xhr.send(); // 发送请求
// POST请求
xhr.open('POST', '/add2/'); // 使用POST方法,将数据值藏在请求体内
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); # 设置请求头告知处理请求体
xhr.send('i1=12&i2=19');
}
视图函数部分
def add2():
if request.method == "GET":
i1 = int(request.GET.get('i1'))
i2 = int(request.GET.get('i2'))
return HttpResponse(i1 + i2)
XMLHttpRequest的主要方法
a. void open(String method,String url,Boolen async)
用于创建请求
参数:
method: 请求方式(字符串类型),如:POST、GET、DELETE...
url: 要请求的地址(字符串类型)
async: 是否异步(布尔类型)
b. void send(String body)
用于发送请求
参数:
body: 要发送的数据(字符串类型)
c. void setRequestHeader(String header,String value)
用于设置请求头
参数:
header: 请求头的key(字符串类型)
vlaue: 请求头的value(字符串类型)
d. String getAllResponseHeaders()
获取所有响应头
返回值:
响应头数据(字符串类型)
e. String getResponseHeader(String header)
获取响应头中指定header的值
参数:
header: 响应头的key(字符串类型)
返回值:
响应头中指定的header对应的值
f. void abort()
终止请求
XMLHttpRequest的主要属性
a. Number readyState
状态值(整数)
详细:
0-未初始化,尚未调用open()方法;
1-启动,调用了open()方法,未调用send()方法;
2-发送,已经调用了send()方法,未接收到响应;
3-接收,已经接收到部分响应数据;
4-完成,已经接收到全部响应数据;
b. Function onreadystatechange
当readyState的值改变时自动触发执行其对应的函数(回调函数)
c. String responseText
服务器返回的数据(字符串类型)
d. XmlDocument responseXML
服务器返回的数据(Xml对象)
e. Number states
状态码(整数),如:200、404...
f. String statesText
状态文本(字符串),如:OK、NotFound...
jQuery Ajax——内部基于"原生Ajax",不生产Ajax,它只是Ajax的搬运工:
$.ajax({
...
})
伪Ajax,非XMLHttpRequest,另外一种技术iframe
iframe具有不刷新整个页面发送http请求的特性
<form method="POST" action="/fake_ajax/" target="ifr">
<iframe name="ifr" id='ifr' style='display: none'></iframe>
<input type="text" name="user" />
<a onclick="submitForm();">提交</a> # 绑定提交表格的函数
</form>
<script>
function submitForm(){
document.getElementById('ifr').onload = loadIframe; # 提交表格时执行loadIframe函数
document.getElementById('f1').submit();
}
function loadIframe(){
var content = document.getElementById('f1').contentWindow.document.body.innerText;
alert(content);
}
</script>
视图函数部分
def fake_ajax(request):
if request.method == 'GET':
return render(request, 'fake_ajax.html')
else:
print(request.POST)
return HttpResponse("返回值")
基于Ajax上传文件
<h1>原生Ajax上传文件</h1>
<input type='file' id='i1'/>
<a onclick="upload1()" id='i1'>上传</a>
<div id='container1'></div> {# 预览功能 #}
<h1>jQuery.Ajax上传文件</h1>
<input type='file' id='i2'/>
<a onclick="upload2()" id='i2'>上传</a>
<div id='container2'></div>
<h1>伪Ajax上传文件</h1>
<form method="POST" action="/upload/" target="ifr" enctype="mutipart/form-data">
<iframe name="ifr" id='ifr' style='display: none'></iframe>
<input type="file" name="fafafa" />
<a onclick="upload3();">上传</a>
</form>
<script src="/static/jquery-1.12.4.js"></script>
<script>
function upload1(){ // 原生Ajax上传文件
var formData = new FormData(); # 重要,添加文件的重要载体
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); # 非字符串,无需再发送请求头
function upload2(){ // jQuery.Ajax上传文件
var formData = new FormData();
formData.append('fafafa', $('i2')[0].files[0])); // jQuery 转换
$.ajax({
url: '/upload/',
tyoe: 'POST',
data: formData,
contentType:false, // 告知jQuery不用处理数据(设置请求头)
processData:false, // 告知jQuery不用处理数据(设置请求头)
success: function(arg){
var tag = document.createElement('img');
tag.src = '/' + arg;
$('container2').append(tag); // 将上传的图片即时展现
}
})
}
function upload3(){ // 伪Ajax上传文件
document.getElementById('ifr').onload = loadIframe; # 提交表格时执行loadIframe函数
document.getElementById('f1').submit();
}
function loadIframe(){
var content = document.getElementById('f1').contentWindow.document.body.innerText;
var tag = document.createElement('img');
tag.src = '/' + content;
document.getElementById('container3').appendChild(tag); // 将上传的图片即时展现
}
</script>
小tips——jQuery对象和DOM对象的互换
$('#i2') --> $('#i2')[0] // jQuery转DOM
document.getElementById('i1') --> $(document.getElementById('i1')) // DOM转jQuery
视图函数部分
def upload(request):
if request.method == 'GET':
return render(request, 'upload.html')
else:
print(request.POST, request.FILES)
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)
其他
跨域Ajax:JASONP技术是一种解决跨域问题的方式
问题描述:浏览器的同源策略不支持跨域发送Ajax请求(Ajax发送跨域请求时,再回来时浏览器拒绝接受)
突破口1:script标签没有被禁止跨域。
局限性:只能用GET请求,服务端和前端必须约定好
<a onclick="getUsers();">发送</a>
<ul id='usernames'></ul>
<script>
function getUsers(){
var tag = document.createElement('script');
// tag.src = "http://www.jxntv.cn/data/jmd-jxtv2/html?callback=list&_1454376870403"
tag.src = "http://www.s4.com:8001/users/?callback=bbb"
document.head.appendChild(tag);
}
function bbb(arg){
for (var i=0, i<arg.length, i++){
var tag_li = document.createElement('li');
tag_li.innerText = arg[i];
document.getElementByID("usernames").appendChild(tag_li)
}
}
</script>
// jQuery版
<script>
function getUsers(){
$.ajax(
url: 'http://www.s4.com:8001/user/',
// url将拼接为'http://www.s4.com:8001/user/?callback=bbb'
type: 'GET',
dataType: 'JSONP',
jsonp: 'callback',
jsonpCallback: 'bbb'
)
}
function bbb(arg){
for (var i=0, i<arg.length, i++){
var tag_li = document.createElement('li');
tag_li.innerText = arg[i];
document.getElementByID("usernames").appendChild(tag_li)
}
}
</script>
视图函数部分
def users(request):
v = request.GET.get('callback')
user_list = [
'alex', 'eric', 'egon'
]
user_list_str = json.dumps(user_list)
temp = "%s(%s)" % (v, user_list_str,)
return HttpResponse(temp)
小tips1——修改Hosts可以将本地IP指向不同的域名
127.0.0.1 www.s4.com
小tips2——允许域名设置
到settings.py
加入以下配置即可使用新加入的域名
ALLOWED_HOSTS = ['http://www.s4.com',]
突破口2:CORS跨站资源共享。修改视图函数配置,修改响应头。
局限性:但需要服务器方开放共享资源。
简单请求
def new_users(request):
user_list = [
'alex', 'eric', 'egon'
]
user_list_str = json.dumps(user_list)
obj = HttpResponse(user_list_str)
obj['Access-Control-Allow-Origin'] = 'http://www.s5.com:8000' # 加入允许访问的域名
obj['Access-Control-Allow-Origin'] = '*' # 即所有人均可访问
return obj
复杂请求
简单请求(仅发送一次请求):HEAD
、GET
、POST
复杂请求(发送两次请求,一次为预检请求):OPTIONS
def new_users(request):
if request.method == "OPTIONS":
print('预检...')
obj = HttpResponse()
obj['Access-Control-Allow-Origin'] = "*"
obj['Access-Control-Request-Methods'] = 'DELETE'
return obj
user_list = [
'alex', 'eric', 'egon'
]
user_list_str = json.dumps(user_list)
obj = HttpResponse(user_list_str)
obj['Access-Control-Allow-Origin'] = '*' # 即所有人均可访问
return obj
其他突破口:通过服务端发送请求再返回给浏览器
总结:
- 优先使用jQuery,不允许的情况下使用XMLHttpRequest或伪Ajax
- 保证兼容性使用伪Ajax
- 上传文件使用伪Ajax
参考资料:
http://www.cnblogs.com/wupeiqi/articles/5703697.html
ajax用POST方法传字典时收不到数据
在ajax中加入参数: contentType:"application/x-www-form-urlencoded",
Models部分——ORM操作
参考资料
http://www.cnblogs.com/wupeiqi/articles/5246483.html
http://www.cnblogs.com/wupeiqi/articles/6216618.html
在Django中使用mysql数据库
Django默认使用sqlite,因此要修改成mysql
- 到项目下
settings.py
修改
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 修改为mysql
'NAME': 'DjangoTEST', # 修改数据库名
'USER': 'root',
'PASSWORD':'123',
'HOST': '127.0.0.1',
'PORT': 3306
}
}
- 到
__init__.py
文件下加入
import pymysql
pymysql.install_as_MySQLdb()
- 到
settings.py
下注册app
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01', # here
]
- 导入Django自带表:
python3 manage.py migrate # 将上述APP的数据导入到数据库
连接其他数据库的方法
settings.py
下default为默认connection,可加入多个数据库
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 修改为mysql
'NAME': 'djangodb', # 修改数据库名
'USER': 'root',
'PASSWORD':'123',
'HOST': '127.0.0.1',
'PORT': 3306
}
'db2': {
'ENGINE': 'django.db.backends.sqlite3', # 增加一个sqlite
'NAME': 'djangodb2', # 修改数据库名
}
}
获得cursor
from django.db import connection, connections
cursor = connection.cursor() # connection=default数据
cursor = connections['db2'].cursor()
cursor.execute("select * ……")
cursor与pymysql用法一致
用pymysql写属于自己的sqlhelper模块(models)
web请求生命周期
- 用户输入url
- 客户端发送请求头(post方法有请求体)
- 服务端接收并提取请求内容
- 交由路由关系匹配
- 函数进行模版数据渲染
- 返回到用户(响应头+响应体)
自定义的sqlhelper
import pymysql
class SqlHelper:
def __init__(self):
self.connect()
def connect(self, sql, args)
self.conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='123', db='mysite',charset='utf-8')
self.cursor = self.conn.cursor(cursor=pymysql.cursors.DictCursor)
def modify(self, sql, args):
self.cursor.execute(sql, args)
self.conn.commit()
def multiple_modify(self, sql)
self.cursor.executemany(sql, args)
self.conn.commit()
def getlist(self, sql, args):
self.cursor.execute(sql, args)
result = self.cursor.fetchall()
self.conn.commit()
return result
def getone(self, sql, args):
self.cursor.execute(sql, args)
result = self.cursor.fetchone()
self.conn.commit()
return result
def create(self, sql, args):
self.cursor.execute(sql, args)
self.conn.commit()
return self.cursor.lastrowid
def close(self):
self.cursor.close()
self.conn.close()
ORM相关操作
操作表
创建表
到model.py
文件下创建表
class UserInfo(models.Model):
nid = models.BigAutoField(primary_key=True) # 自增字段 可以不写,Django会自动生成id = models.AutoField()
username = models.CharField(max_length=32)
password = models.CharField(max_length=64)
创建数据表命令:
python3 manage.py makemigrations # 根据model里面的规则创建表
python3 manage.py migrate
修改表
在model.py
中修改
class UserInfo(models.Model):
id = models.BigAutoField(primary_key=True) # 自增ID
# id = models.AutoField()
username = models.CharField(max_length=32)
password = models.CharField(max_length=64)
age = models.IntegerField() # 增加此字段
再次执行命令即可(改字段名同理):
python3 manage.py makemigrations # 根据model里面的规则创建表
python3 manage.py migrate
每一次修改会在migrations中记录每次修改进行比对,所以别删里面的文件
出现以下问题是因为默认新增字段不能为空
You are trying to add a non-nullable field 'age' to userinfo without a default; we can't do that (the database needs something to populate existing rows). Please select a fix: 1) Provide a one-off default now (will be set on all existing rows with a null value for this column) 2) Quit, and let me add a default in models.py Select an option: 1 Please enter the default value now, as valid Python The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now Type 'exit' to exit this prompt >>>
作以下修改即可
age = models.IntegerField(null=True) # 修改为可以为空 # 或 age = models.IntegerField(default=1) # 修改默认值为1
如发现表没有创建成功请再次执行(必须两条均执行)
python3 manage.py makemigrations python3 manage.py migrate
外键
class UserGroup(models.Model):
title = models.CharField(max_length=32)
class UserInfo(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=64)
age = models.IntegerField()
ug = models.ForeignKey('UserGroup', null=True)
# 新增外键,没有数据时要设为可以为空
# 生成的字段名默认生成为ug_id
ur = models.ForeignKey('self', null=True, blank=True) # ForeignKey的自关联: 引用自己的ID
字段类型设定
AutoField(Field)
- int自增列,必须填入参数 primary_key=True
BigAutoField(AutoField)
- bigint自增列,必须填入参数 primary_key=True
注:当model中如果没有自增列,则自动会创建一个列名为id的列
from django.db import models
class UserInfo(models.Model):
# 自动创建一个列名为id的且为自增的整数列
username = models.CharField(max_length=32)
class Group(models.Model):
# 自定义自增列
nid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
SmallIntegerField(IntegerField):
- 小整数 -32768 ~ 32767
PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
- 正小整数 0 ~ 32767
IntegerField(Field)
- 整数列(有符号的) -2147483648 ~ 2147483647
PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
- 正整数 0 ~ 2147483647
BigIntegerField(IntegerField):
- 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807
自定义无符号整数字段
class UnsignedIntegerField(models.IntegerField):
def db_type(self, connection):
return 'integer UNSIGNED'
PS: 返回值为字段在数据库中的属性,Django字段默认的值为:
'AutoField': 'integer AUTO_INCREMENT',
'BigAutoField': 'bigint AUTO_INCREMENT',
'BinaryField': 'longblob',
'BooleanField': 'bool',
'CharField': 'varchar(%(max_length)s)',
'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
'DateField': 'date',
'DateTimeField': 'datetime',
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
'DurationField': 'bigint',
'FileField': 'varchar(%(max_length)s)',
'FilePathField': 'varchar(%(max_length)s)',
'FloatField': 'double precision',
'IntegerField': 'integer',
'BigIntegerField': 'bigint',
'IPAddressField': 'char(15)',
'GenericIPAddressField': 'char(39)',
'NullBooleanField': 'bool',
'OneToOneField': 'integer',
'PositiveIntegerField': 'integer UNSIGNED',
'PositiveSmallIntegerField': 'smallint UNSIGNED',
'SlugField': 'varchar(%(max_length)s)',
'SmallIntegerField': 'smallint',
'TextField': 'longtext',
'TimeField': 'time',
'UUIDField': 'char(32)',
BooleanField(Field)
- 布尔值类型
NullBooleanField(Field):
- 可以为空的布尔值
CharField(Field)
- 字符类型
- 必须提供max_length参数, max_length表示字符长度
TextField(Field)
- 文本类型
EmailField(CharField):
- 字符串类型,Django Admin以及ModelForm中提供验证机制
IPAddressField(Field)
- 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制
GenericIPAddressField(Field)
- 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6
- 参数:
protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6"
unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启刺功能,需要protocol="both"
URLField(CharField)
- 字符串类型,Django Admin以及ModelForm中提供验证 URL
SlugField(CharField)
- 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号)
CommaSeparatedIntegerField(CharField)
- 字符串类型,格式必须为逗号分割的数字
UUIDField(Field)
- 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证
FilePathField(Field)
- 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能
- 参数:
path, 文件夹路径
match=None, 正则匹配
recursive=False, 递归下面的文件夹
allow_files=True, 允许文件
allow_folders=False, 允许文件夹
FileField(Field)
- 字符串,路径保存在数据库,文件上传到指定目录
- 参数:
upload_to = "" 上传文件的保存路径
storage = None 存储组件,默认django.core.files.storage.FileSystemStorage
ImageField(FileField)
- 字符串,路径保存在数据库,文件上传到指定目录
- 参数:
upload_to = "" 上传文件的保存路径
storage = None 存储组件,默认django.core.files.storage.FileSystemStorage
width_field=None, 上传图片的高度保存的数据库字段名(字符串)
height_field=None 上传图片的宽度保存的数据库字段名(字符串)
DateTimeField(DateField)
- 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]
DateField(DateTimeCheckMixin, Field)
- 日期格式 YYYY-MM-DD
TimeField(DateTimeCheckMixin, Field)
- 时间格式 HH:MM[:ss[.uuuuuu]]
DurationField(Field)
- 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型
FloatField(Field)
- 浮点型
DecimalField(Field)
- 10进制小数
- 参数:
max_digits,小数总长度
decimal_places,小数位长度
BinaryField(Field)
- 二进制类型
字段参数设定
# 单个字段
null=True
default='111'
db_index=True
unique=True
# 多个组合
class Meta:
unique_together(
(u,g),
)
index_together(
(u,g),
)
# ADMIN参数
blank=True # 可以为空
verbose_name='用户名' # 修改字段名
editable=False # 不可编辑,将被隐藏
help_text='这是提示信息' # 输入提示信息
choice=[(0, 阿斯顿),(1, 地方)], # 通常跟个default注明默认值
error_messages # 自定义错误信息,如 {'null': 不能为空, 'invaild':'格式错误',……} 字典键:null, blank, invaild, invaild_choice, unique, unique for date
validators # 使用正则表达式自定义错误验证(列表类型),从而定制想要的验证规则
validators例
from django.core.validators import RegexValidator
from django.core.validators import EmailValidator,URLValidator,DecimalValidator,MaxLengthValidator,MinLengthValidator,MaxValueValidator,MinValueValidator
如:
test = models.CharField(
max_length=32,
error_messages={
'c1': '优先错信息1',
'c2': '优先错信息2',
'c3': '优先错信息3',
},
validators=[
RegexValidator(regex='root_\d+', message='错误了', code='c1'),
RegexValidator(regex='root_112233\d+', message='又错误了', code='c2'),
EmailValidator(message='又错误了', code='c3'),
]
)
枚举字段
color_list = (
(1, "黑色"),
(2, "白色"),
(3, "蓝色"),
)
color = models.InterField(choice=color_list) # 使用choice参数
如果变量固定不变,使用此方法。选项动态时使用ForeignKey
删除表
删除类后执行命令直接删除
操作数据行
def index(request):
from app01 import models
# 增加
models.UserGroup.objects.create(title='销售部') # 输入一个数据
models.UserInfo.objects.create(user='root', password='123', age='18', ug_id=1)
# 查找
group_list = models.UserGroup.objects.all() # 获得对象(row)列表
group_list = models.UserGroup.objects.first() # 获得第一个
group_list = models.UserGroup.objects.filter(id=1, title='销售部') # AND关系
# 神奇的双下划线
group_list = models.UserGroup.objects.filter(id__gt=1)
# id_gt 大于
# id_lt 小于
# 删除
group_list = models.UserGroup.objects.
filter(id__gt=1).delete() #
# 更新
group_list = models.UserGroup.objects.filter(id__gt=1).update(title='公关部')
# 字段为空
group_list = models.UserGroup.objects.filter(age__isnull=True).update(title='公关部')
return render(request, 'newindex.html', {"group_list": group_list})
基本操作一览
##################################################################
# PUBLIC METHODS THAT ALTER ATTRIBUTES AND RETURN A NEW QUERYSET #
##################################################################
def all(self)
# 获取所有的数据对象
def filter(self, *args, **kwargs)
# 条件查询 —— 过滤
# 条件可以是:参数,字典,Q
def exclude(self, *args, **kwargs)
# 条件查询 —— 排除
# 条件可以是:参数,字典,Q
def select_related(self, *fields) # 第一次查询的时候就做了连表,减少查询次数
性能相关:表之间进行join连表操作,一次性获取关联的数据。
model.tb.objects.all().select_related()
model.tb.objects.all().select_related('表名')
model.tb.objects.all().select_related('表名_外键字段(或表名)','表名_外键字段(或表名)')
def prefetch_related(self, *lookups) # 不做连表,做多次查询
性能相关:多表连表操作时速度会慢,使用其执行多次SQL查询在Python代码中实现连表操作。
# 获取所有用户表
# 获取用户类型表where id in (用户表中的查到的所有用户ID)
models.UserInfo.objects.prefetch_related('外键字段')
from django.db.models import Count, Case, When, IntegerField
Article.objects.annotate(
numviews=Count(Case(
When(readership__what_time__lt=treshold, then=1),
output_field=CharField(),
))
)
students=Student.objects.all().annotate(num_excused_absences=models.Sum(
models.Case(
models.When(absence__type='Excused', then=1),
default=0,
output_field=models.IntegerField()
)))
def annotate(self, *args, **kwargs)
# 用于实现聚合group by查询
from django.db.models import Count, Avg, Max, Min, Sum
v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id'))
# SELECT u_id, COUNT(ui) AS `uid` FROM UserInfo GROUP BY u_id
v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id')).filter(uid__gt=1)
# SELECT u_id, COUNT(ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1
v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id',distinct=True)).filter(uid__gt=1)
# SELECT u_id, COUNT( DISTINCT ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1
def distinct(self, *field_names)
# 用于distinct去重
models.UserInfo.objects.values('nid').distinct()
# select distinct nid from userinfo
注:只有在PostgreSQL中才能使用distinct进行去重
def order_by(self, *field_names)
# 用于排序
models.UserInfo.objects.all().order_by('-id','age')
def extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
# 构造额外的查询条件或者映射,如:子查询
Entry.objects.extra(select={'new_id': "select col from sometable where othercol > %s"}, select_params=(1,))
Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"])
Entry.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['-nid'])
def reverse(self):
# 倒序
models.UserInfo.objects.all().order_by('-nid').reverse()
# 注:如果存在order_by,reverse则是倒序,如果多个排序则一一倒序
def defer(self, *fields):
models.UserInfo.objects.defer('username','id')
或
models.UserInfo.objects.filter(...).defer('username','id')
#映射中排除某列数据
def only(self, *fields):
#仅取某个表中的数据(对象)
models.UserInfo.objects.only('username','id')
或
models.UserInfo.objects.filter(...).only('username','id')
替代方法:
models.UserInfo.objects.values('username','id') # 区别为字典or对象
def using(self, alias):
指定使用的数据库,参数为别名(setting中的设置)
##################################################
# PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS #
##################################################
def raw(self, raw_query, params=None, translations=None, using=None):
# 执行原生SQL
models.UserInfo.objects.raw('select * from userinfo')
# 如果SQL是其他表时,必须将名字设置为当前UserInfo对象的主键列名
models.UserInfo.objects.raw('select id as nid from 其他表')
# 为原生SQL设置参数
models.UserInfo.objects.raw('select id as nid from userinfo where nid>%s', params=[12,])
# 将获取的到列名转换为指定列名
name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk': 'id'}
Person.objects.raw('SELECT * FROM some_other_table', translations=name_map)
# 指定数据库
models.UserInfo.objects.raw('select * from userinfo', using="default")
################### 原生SQL ###################
from django.db import connection, connections
cursor = connection.cursor() # cursor = connections['default'].cursor()
cursor.execute("""SELECT * from auth_user where id = %s""", [1])
row = cursor.fetchone() # fetchall()/fetchmany(..)
def values(self, *fields):
# 获取每行数据为字典格式
def values_list(self, *fields, **kwargs):
# 获取每行数据为元祖
def dates(self, field_name, kind, order='ASC'):
# 根据时间进行某一部分进行去重查找并截取指定内容
# kind只能是:"year"(年), "month"(年-月), "day"(年-月-日)
# order只能是:"ASC" "DESC"
# 并获取转换后的时间
- year : 年-01-01
- month: 年-月-01
- day : 年-月-日
models.DatePlus.objects.dates('ctime','day','DESC')
def datetimes(self, field_name, kind, order='ASC', tzinfo=None):
# 根据时间进行某一部分进行去重查找并截取指定内容,将时间转换为指定时区时间
# kind只能是 "year", "month", "day", "hour", "minute", "second"
# order只能是:"ASC" "DESC"
# tzinfo时区对象
models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.UTC)
models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.timezone('Asia/Shanghai'))
"""
pip3 install pytz
import pytz
pytz.all_timezones
pytz.timezone(‘Asia/Shanghai’)
"""
def none(self):
# 空QuerySet对象
####################################
# METHODS THAT DO DATABASE QUERIES #
####################################
def aggregate(self, *args, **kwargs):
# 聚合函数,获取字典类型聚合结果
from django.db.models import Count, Avg, Max, Min, Sum
result = models.UserInfo.objects.aggregate(k=Count('u_id', distinct=True), n=Count('nid'))
===> {'k': 3, 'n': 4}
def count(self):
# 获取个数
def get(self, *args, **kwargs):
# 获取单个对象
def create(self, **kwargs):
# 创建对象
# 建议使用**dic的方式去传参数
def bulk_create(self, objs, batch_size=None):
# 批量插入
# batch_size表示一次插入的个数
objs = [
models.DDD(name='r11'),
models.DDD(name='r22')
]
models.DDD.objects.bulk_create(objs, 10)
# 可使用for循环批量生成对象,再通过bulk_create一次过导入数据库。
def get_or_create(self, defaults=None, **kwargs):
# 如果存在,则获取,否则,创建
# defaults 指定创建时,其他字段的值
obj, created = models.UserInfo.objects.get_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 2})
def update_or_create(self, defaults=None, **kwargs):
# 如果存在,则更新,否则,创建
# defaults 指定创建时或更新时的其他字段
obj, created = models.UserInfo.objects.update_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 1})
def first(self):
# 获取第一个
def last(self):
# 获取最后一个
def in_bulk(self, id_list=None):
# 根据主键ID进行查找
id_list = [11,21,31]
models.DDD.objects.in_bulk(id_list)
def delete(self):
# 删除
def update(self, **kwargs):
# 更新
def exists(self):
# 是否有结果
其他操作
连表
正向操作
通过外键关联找到类型id对应的名称
objs = models.UserInfo.objects.all()
for obj in objs:
print(
obj.name,
obj.age,
obj.ut_id,
obj.ut.title, # 单表连接:直接使用外键连接的表中的字段
obj.ut.fo.caption, # 多表连接:使用本表外键中的源表里面的外键中的字段
)
反向操作
通过类型表找到属于这个类型的所有用户
obj = models.UserType.objects.all().first()
for row in obj.userinfo_set.all(): # obj.userinfo_set为属于obj这个类型的在userinfo表里的所有行对象的集合
print(row.name, row.age)
只取部分字段
objs = models.UserInfo.objects.all() # 返回结果是多个对象[obj,obj,...]
objs = models.UserInfo.objects.all().first() # 返回结果是单个对象 obj
objs = models.UserInfo.objects.all().values('id','name') # 返回结果是字典 [{id:v1,name:v1},{id:v2,name:v2},...]
objs = models.UserInfo.objects.all().values_list('id','name') # 返回结果是元组 [(v1,v1),(v2,v2),...]
# 跨表操作
objs = models.UserInfo.objects.all().values('id','name', 'ut__title') # 双下划线取跨表关联值。ut为外键,title为源表中的值
直接返回JasonResponse
# 注意必须是字典,否则会报错
from django.http import JsonResponse
# 如果想传列表,需要加参数safe=False
return JsonResponse([1,2,3], safe=False)
queryset序列化方法
方法1: django自带序列化queryset, 到达前端直接DataType
from django.core import serializers
v = models.Server.objects.all()
data = serializers.serialize("json", v)
方法2: 使用values()
v = models.Server.objects.values("id", "hostname", 'create_at')
时间的序列化方法——扩展JSON
由于json无法序列化时间,所以需要对其扩展
import json
from datetime import date
from datetime import datetime
class JsonCustomEncoder(json.JSONEncoder): # 继承JSON原生编码类
def default(self, field):
if isinstance(field, datetime):
return field.strftime('%Y-%m-%d %H:%M:%S')
elif isinstance(field, date):
return field.strftime('%Y-%m-%d')
else:
return json.JSONEncoder.default(self, field)
data = json.dumps(list(v), cls=JsonCustomEncoder) # 每序列化一个字段之前都要先调用此类
排序
在表中加入__str__()
方法,方便用print()查看排序结果
class UserInfo(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=64)
age = models.IntegerField()
ug = models.ForeignKey('UserGroup', null=True)
def __str__(self):
return "%s-%s"(self.id, self.name) # 快速查看结果
使用order_by方法
user_list = models.UserInfo.objects.all().order_by('id')
user_list = models.UserInfo.objects.all().order_by('-id') # 倒序
user_list = models.UserInfo.objects.all().order_by('-id', 'name') #加入第二个排序条件
print(user_list)
分组
from django.db.models import Count,Sum,Max,Min # 可引入多个聚合函数
v = models.UserInfo.objects.values('ug_id').annotate(xxx=Count('id'))
print(v.query) # 可查看生成的SQL语句
结果
SELECT 'app01_usergroup'.'ug_id', COUNT('app01_userinfo'.'id') AS 'xxx' FROM 'app01_userinfo'.'ug_id';
筛选
from django.db.models import Count,Sum,Max,Min
v = models.UserInfo.objects.filter(id__gt=2).values('ug_id').annotate(xxx=Count('id')).filter(xxx__gt=2)
print(v.query) # 可查看生成的SQL语句
结果
SELECT 'app01_usergroup'.'ug_id', COUNT('app01_userinfo'.'id') AS 'xxx' FROM 'app01_userinfo' WHERE 'app01_userinfo'.'id' > 2 GROUP BY 'app01_userinfo'.'ug_id' HAVING COUNT('app01_userinfo'.'id') > 2; // filter 在前面是 WHERE 在后面是 HAVING
其他使用方法
filter(id__gt)
filter(id__lt)
filter(id__lte)
filter(id__gte)
filter(id__in[1,2,3])
filter(id__range[1, 2]) # between
filter(name__startswith='xxx')
filter(name__contains='xxx')
exclude(id=1) # 排除,即id!=1
多对多操作
内置方法ManyToManyField()
class Boy():
name = CharField(32)
m = models.ManyToManyField("Girl") # 自动创建第三张表
class Girl():
nick = CharField(32)
boyobj = models.Boy.objects.filter(name='boyname')
boyobj.m.all() # 正向查询获得Girl对象
boyobj.m.add(1,2) # 增加
boyobj.m.remove(*[1,2]) # 删除
boyobj.m.set([1]) # 重置:清空然后加入此条关系
boyobj.m.clear() # 清空
girlobj = models.Boy.objects.filter(name='girlname')
girlobj.boy_set() # 反向查询获得Boy对象
自定义方法
class Love(models.Model):
b = models.ForeignKey('Boy')
g = models.ForeignKey('Girl')
class Meta:
unique_together = [ # 联合唯一
(b, g),
]
混合方法
class Boy():
name = CharField(32)
m = models.ManyToManyField("Girl", through='Love', through_fields=('b','g')) # 使用自定义的表作为关系表
class Girl():
nick = CharField(32)
class Love(models.Model):
b = models.ForeignKey('Boy')
g = models.ForeignKey('Girl')
class Meta:
unique_together = [ # 联合唯一
(b, g),
]
# 此方法仅可使用以下两种方法
boyobj.m.all()
boyobj.m.clear()
ManyToManyField
其他参数介绍
ManyToManyField(
RelatedField # 要进行关联的字段名
to, #
related_name=None, # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all()
related_query_name=None, # 反向操作时,使用的连接前缀,用于替换【表名】 如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
limit_choices_to=None, # 在Admin或ModelForm中显示关联数据时,提供的条件:
# 如:
# - limit_choices_to={'nid__gt': 5}
# - limit_choices_to=lambda : {'nid__gt': 5}
# from django.db.models import Q
# - limit_choices_to=Q(nid__gt=10)
# - limit_choices_to=Q(nid=8) | Q(nid__gt=10)
# - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
symmetrical=None, # 仅用于多对多自关联时,symmetrical用于指定内部是否创建反向操作的字段
# 做如下操作时,不同的symmetrical会有不同的可选字段
# models.BB.objects.filter(...)
# 可选字段有:code, id, m1
# class BB(models.Model):
# code = models.CharField(max_length=12)
# m1 = models.ManyToManyField('self',symmetrical=True)
# 可选字段有: bb, code, id, m1
# class BB(models.Model):
# code = models.CharField(max_length=12)
# m1 = models.ManyToManyField('self',symmetrical=False)
through=None, # 自定义第三张表时,使用字段用于指定关系表
through_fields=None, # 自定义第三张表时,使用字段用于指定关系表中那些字段做多对多关系表
# from django.db import models
# class Person(models.Model):
# name = models.CharField(max_length=50)
# class Group(models.Model):
# name = models.CharField(max_length=128)
# members = models.ManyToManyField(
# Person,
# through='Membership',
# through_fields=('group', 'person'),
# )
# class Membership(models.Model):
# group = models.ForeignKey(Group, on_delete=models.CASCADE)
# person = models.ForeignKey(Person, on_delete=models.CASCADE)
# inviter = models.ForeignKey(
# Person,
# on_delete=models.CASCADE,
# related_name="membership_invites",
# )
# invite_reason = models.CharField(max_length=64)
db_constraint=True, # 是否在数据库中创建外键约束
db_table=None, # 默认创建第三张表时,数据库中表的名称
)
内置分页功能(分批获取数据)
创建Paginator对象
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) # 实例化分页对象
# Paginator的参数解释:
# 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: # 如非整型数字错误则把当前页设为1
posts = paginator.page(1)
except EmptyPage: # 如页面为空则把当前页设为最后一页
posts = paginator.page(paginator.num_pages)
return render(request, 'index.html', {'posts': posts})
HTML部分设置
<ul>
{% for item in posts %} {# 打印当前页内容 #}
<li>{{ item }}</li>
{% endfor %}
</ul>
<div class="pagination">
<span class="step-links"> {# 如果有上一页则显示Previous按钮 #}
{% if posts.has_previous %}
<a href="?p={{ posts.previous_page_number }}">Previous</a>
{% endif %}
<span class="current"> {# 显示效果为 '当前页码' of '总页码' #}
Page {{ posts.number }} of {{ posts.paginator.num_pages }}.
</span>
{% if posts.has_next %} {# 如果有下一页则显示Next按钮 #}
<a href="?p={{ posts.next_page_number }}">Next</a>
{% endif %}
</span>
</div>
自定义分页功能
分页时需要做三件事:
- 创建处理分页数据的类
- 根据分页数据获取数据
- 输出分页HTML,即:[上一页][1][2][3][4][5][下一页]
#!/usr/bin/env python
# _*_coding:utf-8_*_
from django.utils.safestring import mark_safe
class PageInfo(object):
def __init__(self, current, totalItem,peritems=5):
self.__current=current
self.__peritems=peritems
self.__totalItem=totalItem
def From(self):
return (self.__current-1)*self.__peritems
def To(self):
return self.__current*self.__peritems
def TotalPage(self): #总页数
result=divmod(self.__totalItem,self.__peritems)
if result[1]==0:
return result[0]
else:
return result[0]+1
def Custompager(baseurl,currentPage,totalpage): #基础页,当前页,总页数
perPager=11
#总页数<11
#0 -- totalpage
#总页数>11
#当前页大于5 currentPage-5 -- currentPage+5
#currentPage+5是否超过总页数,超过总页数,end就是总页数
#当前页小于5 0 -- 11
begin=0
end=0
if totalpage <= 11:
begin=0
end=totalpage
else:
if currentPage>5:
begin=currentPage-5
end=currentPage+5
if end > totalpage:
end=totalpage
else:
begin=0
end=11
pager_list=[]
if currentPage<=1:
first="<a href=''>首页</a>"
else:
first="<a href='%s%d'>首页</a>" % (baseurl,1)
pager_list.append(first)
if currentPage<=1:
prev="<a href=''>上一页</a>"
else:
prev="<a href='%s%d'>上一页</a>" % (baseurl,currentPage-1)
pager_list.append(prev)
for i in range(begin+1,end+1):
if i == currentPage:
temp="<a href='%s%d' class='selected'>%d</a>" % (baseurl,i,i)
else:
temp="<a href='%s%d'>%d</a>" % (baseurl,i,i)
pager_list.append(temp)
if currentPage>=totalpage:
next="<a href='#'>下一页</a>"
else:
next="<a href='%s%d'>下一页</a>" % (baseurl,currentPage+1)
pager_list.append(next)
if currentPage>=totalpage:
last="<a href=''>末页</a>"
else:
last="<a href='%s%d'>末页</a>" % (baseurl,totalpage)
pager_list.append(last)
result=''.join(pager_list)
return mark_safe(result) #把字符串转成html语言
F、Q、extra
F:用于引用并改造原有的数据
from django.db.models import F
model.UserInfo.object.all().update(age=F("age" + 1))
condition = {
'id':1,
'name':'root'
}
model.UserInfo.object.all().filter(**condition)
Q:用于构造复杂查询条件
第一种:方法方式
from django.db.models import Q
model.UserInfo.object.all().filter(Q(id=8) | Q(id=2)) # Q的or用法
# model.UserInfo.object.all().filter(Q(id=8) & Q(id=2))
q1 = Q()
q1.connector = 'OR'
q1.children.append(('c1', 1))
q1.children.append(('c1', 10))
q1.children.append(('c1', 9))
q2 = Q()
q2.connector = 'OR'
q2.children.append(('c1', 2))
q2.children.append(('c1', 5))
q2.children.append(('c1', 6))
q3 = Q()
q3.connector = 'AND'
q3.children.append(('id', 1))
q3.children.append(('id', 2))
q2.add(q3, 'OR')
con = Q()
con.add(q1, 'AND')
con.add(q2, 'AND')
第二种:对象方式
# 使用for将字段转化为Q
# 假如这是一个前端的条件选择器
condition_dict = {
'k1': [1, 2, 3, 4],
'k2': [1,],
'k3': [11,]
}
# 服务端无需修改
con = Q()
for k,v in condition_dict.items():
q = Q()
q.connect = 'OR'
for i in v:
q.children.append(('id',i))
con.add(q, 'AND')
model.UserInfo.object.all().filter(con)
extra:额外的
类似临时表的使用方法
select参数
v = model.UserInfo.object.all().extra(select={
'n': "SELECT COUNT(1) FROM app01_usertype"
})
"""
SELECT
id,
name,
(SELECT COUNT(1) FROM app01_usertype) as n
FROM app01_userinfo;
"""
v = model.UserInfo.object.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])
where参数
v = model.UserInfo.object.all().extra(
where=['id=1 or id=2',"name='alex"] # 列表之间以AND连接,元素可以为SQL原生语句如or,and等..
,params=[]) # 同理可使用占位符
还有其他参数...
tables 类似连表及笛卡尔积的用法
# 笛卡尔积
v = model.UserInfo.object.all().extra(
tables=['app01_usertype'] # 列表之间以AND连接,元素可以为SQL原生语句如or,and等..
,params=[])
所有参数使用方法示例
v = model.UserInfo.object.all().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) AS newid
FROM app01_userinfo, app01_usertype
WHERE
app01_userinfo.age > 18
ORDER BY
app01_userinfo.age DESC
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类型
...
FORM表单验证
定义规则
from django.forms import Form
from django.forms import fields
class LoginForm(Form):
username = fields.CharField( # 与表单<input>中的name一致
max_length=18, # 最长长度
min_length=6, # 最短长度
required=True, # 不能为空
error_messages={ # 错误信息重写
'required': "用户名不能为空",
'min_length': "太短了",
'max_length': "太长了",
}
)
password = fields.CharField(max_length=18,required=True)
def login(request):
if request.method == "GET":
return render(request, 'index.html')
else:
obj = LoginForm(request.POST) # 拿到POST信息
if obj.is_valid: # 是否通过验证
obj.cleand_data # 拿到用户输入正确的信息(字典)
else:
obj.errors # 这是一个对象,加了__str__()
obj.errors['username'] # 获取错误信息列表
obj.errors['password']
return render(request, 'login.html', {'obj': obj} )
在模版中加入
{{ obj.errors.username.0 }}
{{ obj.errors.password.0 }}
规则详细说明
class LoginForm(Form):
# 继承Field类,有required,help_text,error_messages等常用参数
t1 = fields.CharField() # 默认required等于Ture
t2 = fields.IntegerField(
error_messages={
'required': 't2不能为空',
'invaild': '格式错误,必须为数字',
'max_value': '必须小于1000',
'min_value': '必须大于10'
})
### 继承CharField类的所有参数,另有min_length,max_length,strip等参数 ###
t3 = fields.EmailField(
error_messages={
'required': 't3不能为空',
'invaild': '格式错误,必须为邮箱格式'
})
t4 = fields.URLField()
t5 = fields.SlugField() # 除特殊字符外的字符
t6 = fields.GenericIPAddressField()
t7 = fields.DateField()
t8 = fields.DateTimeField()
t9 = fields.RegexField( # 自定义正则的规则
'139\d+', # 正则表达式
error_massage={
'invaild': '输入有效的号码'
}
)
########################################################
field参数解释
from django.forms import fields
from django.forms import widgets
class TestForm(Form):
t1 = fields.CharField(
#############组合使用自动生成HTML标签###############
widget=widgets.Textarea # 控制生成的input类型:默认是text
label='用户名', # 标签
initial='user1', # 提供默认值
help_text='请输入你的用户名' # 帮助信息
localize=False, # 时间本地化
disable=False, # 禁用
label_suffix=':' # 标签后的符号
#############组合使用自动生成HTML标签###############
validators=[],
)
# widget多种用法
cls_id = fields.IntegerField(
# widget.Select()
widget=widgets.Select(choices=[(1, '上海'),(2, '北京')]) # 增加固定值
widget=widgets.Select(
choices=models.Classes.objects.values_list('id', 'title')) # 使用数据库内的值作为选项
# widget.TextInput()
widget=widgets.TextInput(attrs={'class': 'form-contrl'}) # 为插件增加属性,每个插件都有这个参数
)
注意!这里有BUG!:choice的值无法自动更新,因为class只在启动时将变量启动一次,后续示例化不会再重新取值,choice值亦不会变化。加入以下代码解决问题:
# 方法一
def __init__(self, *args, **kwargs):
super(TeacherForm, self).__init__(*args, **kwargs)
self.fields['cls_id'].choices = models.Classes.objects.values_list('id','title')
# 方法二 : 耦合性高,建议小的应用程序使用
from django.forms import models as form_model
cls_id = form_model.ModelMultipleChoiceField(queryset=models.Classes.objects.all())
# 需要在Classes中加入__str__配合使用
def __str__():
return self.title
模版部分
<p>
{{ obj.as_p }} {# 顺序为: label|label_suffix|input输入框[默认值](disable)|help_text #}
</p>
<ul>
{{ obj.as_ul }} {# 生成ul #}
</ul>
<table>
{{ obj.as_table }} {# 生成table #}
</table>
后附全部字段参数的解释
用ajax提交表单
好处:不刷新,上次内容自动保留
模版部分
<form id='f1' action='/login' method='POST'>
{% csrf_token %}
<input type='text' name="username">
<input type='password' name="password">
<input type='submit' values="提交">
</form>
<a onclick="submitForm()">Ajax提交</a>
<script>
function summitForm(){
$('.c1').remove;
$.ajax({
url: '/ajax_login',
type: 'POST',
data: $('#f1').serialize() // 将数据变成user=alex&pwd=456&csrftoken=defefaasd
dataType: 'JASON'
success: function(arg){
if (arg.status){
// 空
}
else {
$.each(arg.msg, function(index, value)) {
var tag = document.createElement('span');
tag.innerHTML = value[0]
$('#f1').find('input[name="' + index + '"]').after(tag);
}
}
}
})
}
</script>
视图函数部分
import json
def ajax_login(request):
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
v = json.dumps(obj.username)
return HttpResponse(v)
保留form表单输入内容
模版部分login.py
<form id='f1' action='/login/' method='POST' novalidate> {# novalidate禁止浏览器本身验证功能 #}
{% csrf_token %}
<p>
{{ obj.username }}{{ obj.errors.username.0 }} {# 自动生成input标签class=username #}
</p>
<p>
{{ obj.password }}{{ obj.errors.password.0 }}
</p>
<input type='submit' values="提交">
</form>
<a onclick="submitForm()">Ajax提交</a>
视图函数部分
class LoginForm(Form):
username = field.CharField(min_length=8)
password = field.CharField()
def login(request):
if request.method == "GET":
obj = LoginForm() # 第一次GET进来value不带值
return render(request, "login.html", {'obj': obj})
else:
obj = LoginForm(request.POST) # 第二次POST进来value带着用户输入的值
if obj.is_vaild():
print(obj.cleaned_data)
else:
return render(request, 'login.html', {'obj': obj})
多对多
小tips——快速取出以元组为元素的序列
id_list = list(zip(*class_ids))[0] if list(zip(*class_ids)) else []
常用插件
class TestForm(Form):
t1 = fields.CharField(
widget=widgets.Textarea(attrs={})
# widget=widgets.TextInput(attrs={})
# widget=widgets.PasswordInput(attrs={})
)
t2 = fields.CharField(
widget=widgets.CheckboxInput() # 单项选择框
)
t3 = fields.MultipleChoiceField(
widget=widgets.CheckboxSelectMultiple # 多选
)
t4 = fields.ChoiceField(
widdget=widgets.RadioSelect # 单选
)
t5 = fields.FileField(
widdget=widgets.FileInput # 文件上传
)
def test(request):
obj = TestForm(initial={'t3': [2, 3]}) # 设置默认值
return render(request, 'test.html', {'obj': obj})
验证规则扩展
关注返回值,写错就会有问题,建议看源码
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
class TestForm(Form):
user = fields.CharField(
validators=[RegexVaildator(r'^[0-9]+¥', '请输入数字')] # 此处可再额外增加正则表达式
)
# 第一个钩子
def clean_user(self): # 单独对字段进行扩展,函数名格式为"clean_变量名"
v = self.cleaned_data['user'] # 必须已经取得值
if models.Student.objects.filter(name=v).count(): # 可以到数据库中查看是否有此值
raise ValidationError('用户名已经存在')
# raise ValidationError('用户名已经存在', code='invaild') # code对应error_msg中的键
return v # 为cleaned_data中的
# 第二个钩子
def clean(self): # 对整体的值做验证
user = self.cleaned_data.get('user')
email = self.cleaned_data.get('email')
if models.Student.objects.filter(user=user, email=email).count():
self.add_error('user', ValidationError('用户名已经存在')) # 可将此错误归为user错误
raise ValidationError('用户名已经存在') # 或直接抛错误,归到__all__键
return self.cleaned_data # 若为None会返回未经处理的原值
# 第三个钩子
def _post_clean(self): # 一般不会用到(用clean基本足够)
pass
注:先执行正则,再执行此函数,如不通过正则,则不会执行此函数。
看源码:is_valid --> self.errors --> self.full_clean() --> 最下面三个函数
附件:字段参数解释
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类型
...
中间件
内置中间件
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
自定义中间件
对所有请求或部分请求做批量处理
正常顺序:process_request【1、2、3】--> process_view【1、2、3】--> process_response【1、2、3】
class MyRequestExeute(object):
def process_request(self,request): # 处理请求
pass # 不要返回request,否则直接跳过其他中间件,1.10版本以后有所不同
def process_view(self, request, callback, callback_args, callback_kwargs): # 路由匹配,执行视图函数
i =1
pass
def process_exception(self, request, exception): # 发生错误时执行
pass
def process_response(self, request, response): # 处理响应信息
return response # 需要返回response
def process_template_reponse(self, request, response):
return response
注册中间件
MIDDLEWARE_CLASSES = (
'模块.middleware.auth.MyRequestExeute', # 加入自己的模块路径
)
WSGI网络服务协议
默认:wsgiref + django
生产环境:uwsgi + django
XSS攻击和CSRF防范
XSS攻击预防
XSS攻击是指那些利用输入框写入代码来控制用户客户端的行为
注:Django本身内置防XSS攻击机制,除非注明数据为safe
后台处理
msg = []
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): # 将评论在index页面展示
return render(request, 'index.html', {'msg': msg})
评论提交页面
<form method='POST' action='/comment/'>
<input type='text' name='content' />
<input type='submit' name='提交' />
</form>
评论展示页面
解除XXS防御的标记,前端代码加safe
<h1>评论</h1>
{% for item in msg %}
<div>{{ item }}</div> {#
{# <div>{{ item | safe }}</div> 如果要非用safe必须将特殊字符排除#}
{% endfor %}
标记数据为安全的函数mark_safe()
newtemp = mark_safe(temp) # 做此标记后,数据会被前端认为是安全数据
CSRF(跨站请求伪造)处理
在模版form表单中添加token
防止黑客利用修改URL上的参数进行盗取用户信息或控制浏览器的行为,主要采用token验证身份的方法。
{% csrf_token %}
Ajax下获取token以通过验证
第一种方法:在form表单中获取
<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>
function submitForm(){
var csrf = $("input[name='csrfmiddlewaretoken']").val() // 获取csrf_token里面的值
var user = $("#user").val() // 获取用户名
$.ajax({
url: '/csrf.html',
type: 'POST',
data: {"user": user, "csrfmiddlewaretoken": csrf},
success:function(arg){
console.log(arg);
}
})
}
</script>
第二种方法:获取cookie中的信息
cookie = document.cookie // 原生的js获取cookie的方法
使用jquery.cookie插件
<script src="/static/jquery-1.12.4.js"></script>
<script src="/static/jquery.cookie.js"> </script>
<script>
function submitForm(){
var token = $.cookie('csrftoken'); // 获取cookie里面的token
var user = $("#user").val(); // 获取用户名
$.ajax({
url: '/csrf.html',
type: 'POST',
headers: {"X-CSRFToken": token} // 添加请求头传token
data: {"user": user},
success:function(arg){
console.log(arg);
}
})
}
</script>
禁用Django自带csrf防御功能
-
全站禁用
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware', 将此项注释掉
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
-
局部禁用
from django.views.decorators.csrf import csrf_exempt, csrf_protect
@csrf_exempt # 此函数不可用
def csrf1(request):
if request.method == 'GET':
return render(request, 'csrf1.html')
else:
return HttpResponse('OK')
@csrf_protect # 此函数可用
def csrf2(request):
if request.method == 'GET':
return render(request, 'csrf1.html')
else:
return HttpResponse('OK')
-
CBV情况下
from django.views import View
from django.utils.decorators import method_decorator
@method_decorator(csrf_exempt) # CBV需使用此装饰器
@method_decorator(csrf_exempt, name='get') # 可用name参数注明给哪个方法加,给'dispatch'加即全部加
class Foo(View):
def dispatch(self, request, *args, **kwargs):
pass
def get(self, request):
pass
@method_decorator(csrf_exempt) # 可在指定方法加装饰器
def post(self, request):
pass
ModelForm
将Model和Form结合,以达到快速创建表单和数据验证并提交的效果。
参考资料:http://www.cnblogs.com/wupeiqi/articles/6229414.html
ModelForm的基本用法
from django.forms.models import ModelForm
class TestModelForm(ModelForm): # 想自定义仍可自定义
class Meta:
model = models.UserInfo # 无需重新定义表单属性,直接根据models生成
field = '__all__'
error_message = {
'user': {'required': '用户名不能为空'},
'email': {'required': '邮箱不能为空', 'invalid': '邮箱格式错误'}
}
def test(request):
if request.method == 'GET':
form = TestModelForm()
context = {
'form': form
}
return render(request, 'test.html', context)
else:
form = TestModelForm(request.POST)
if form.is_vaild():
form.save()
return redirect('http//:www.google.com')
context = {
'form': form
}
return render(request, 'test.html', context)
def edit(request, nid):
obj = models.UserInfo.object.filter(id=nid).first()
if request.method == "GET":
form = TestModelForm(instance=obj)
context = {
'form': form
}
return render(request, 'edit.html', context)
else:
form = TestModelForm(instance=obj, data=request.POST, file=request.FILES)
if form.is_vaild():
form.save() # 保存数据到数据库
return redirect('http://www.google.com')
context = {
'form': form
}
return render(request, 'test.html', context)
ModelForm组件的其他参数
ModelForm
a. class Meta:
model, # 对应Model的
fields=None, # 字段
exclude=None, # 排除字段
labels=None, # 提示信息
help_texts=None, # 帮助提示信息
widgets=None, # 自定义插件
error_messages=None, # 自定义错误信息(整体错误信息from django.core.exceptions import NON_FIELD_ERRORS)
field_classes=None # 自定义字段类 (也可以自定义字段)
localized_fields=('birth_date',) # 本地化,如:根据不同时区显示数据
如:
数据库中
2016-12-27 04:10:57
setting中的配置
TIME_ZONE = 'Asia/Shanghai'
USE_TZ = True
则显示:
2016-12-27 12:10:57
b. 验证执行过程
is_valid -> full_clean -> 钩子 -> 整体错误
c. 字典字段验证
def clean_字段名(self):
# 可以抛出异常
# from django.core.exceptions import ValidationError
return "新值"
d. 用于验证
model_form_obj = XXOOModelForm()
model_form_obj.is_valid()
model_form_obj.errors.as_json()
model_form_obj.clean()
model_form_obj.cleaned_data
e. 用于创建
model_form_obj = XXOOModelForm(request.POST)
#### 页面显示,并提交 #####
# 默认保存多对多
obj = form.save(commit=True)
# 不做任何操作,内部定义 save_m2m(用于保存多对多)
obj = form.save(commit=False)
obj.save() # 保存单表信息
obj.save_m2m() # 保存关联多对多信息
f. 用于更新和初始化
obj = model.tb.objects.get(id=1)
model_form_obj = XXOOModelForm(request.POST,instance=obj)
...
PS: 单纯初始化
model_form_obj = XXOOModelForm(initial={...})
django自带验证
前提:使用django自带的user表
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required
@login_required()
def acc_login(request):
error = ''
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
user = authenticate(username=username, password=password)
if user:
login(request, user)
return redirect('/')
else:
error = 'wrong password or username'
return render(request, 'login/', 'error': error)
@login_required(login_url='/login/')
def acc_logout(request):
logout(request)
return redirect(request.GET.get('next', '/'))
@login_required(login_url='/login/') # 可以在此处单独注明,也可以在settings里面加入LOGIN_URL,即可全局控制
def host_list(request):
{{ request.user }} // 前端模板调用user
{{ request.path }} // 获取当前url
{{ request.user.account.host_group.all }} // 使用request中的user进行查找,像使用object一样
function() {
$("#miannav-menu a[herf='{{ request.path }}']").parent().addClass('active-link') // 模板中的按键的上一级与url对应的目录变成激活状态
}
//
$.get("{% url 'get_host_list' %}",{'gid': gid}, function(callback){
console.log(callback);
})
使用jquery 的get、POST方法传ajax到服务器
function getHostlist(self,bind){
$.get("{% url 'get_host_list' %}",{'gid': gid, 'csrfmiddlewaretoken': '{{ csrf_token }}'}, function(callback){
console.log(callback);
})
}
// 注意self是click事件绑定时将this传入
function getToken(self, bind_host_id){
$.post("{% url 'get_token' %}",{'bind_host_id': bind_host_id,'csrfmiddlewaretoken': '{{ csrf_token }}'}, function(callback){
console.log(callback);
})
}
信号、缓存、序列化
http://www.cnblogs.com/wupeiqi/articles/5246483.html