前端相關
展示內(nèi)容:瀏覽器接收后端返回的html文本(經(jīng)過模板渲染)內(nèi)容并在頁面展示.
與用戶交互信息:js將用戶產(chǎn)生數(shù)據(jù)通過form表單形式或者ajax形式將數(shù)據(jù)傳輸?shù)胶笈_并接收返回信息,從而達到數(shù)據(jù)交互的作用.
請求的本質(zhì)
CS架構(gòu):本質(zhì)上django程序就是一個socket服務端汰瘫,瀏覽器其實就是一個socket客戶端.
django自帶的wsgi模塊處理瀏覽器的請求信息掂榔,用戶只需要實現(xiàn)路由和視圖函數(shù)驻襟、模板等代碼部分.
django請求的生命周期
指當用戶在瀏覽器上輸入url到用戶看到網(wǎng)頁的這個時間段內(nèi)疲吸,Django程序內(nèi)部所發(fā)生的事情,具體步驟如下:
- 當用戶在瀏覽器輸入url時, 然后瀏覽器會生成請求頭和請求體發(fā)送給服務器;
- url經(jīng)過wsgi---中間件---最后到達路由映射表赃承,隨后按順序進行正則匹配,若匹配到,則進入對應的視圖函數(shù)或者類(CBV)中悴侵;
- 視圖函數(shù)根據(jù)客戶端的請求,取出數(shù)據(jù)并渲染到模板中,其中可能涉及ORM操作(增刪改查)或從緩存取出數(shù)據(jù), 最終以字符串的形式返回.
Form
表單提交瞧剖,頁面與web服務器交互數(shù)據(jù)最重要的方式.
基礎信息詳見:http://www.cnblogs.com/fqh202/p/8483862.html.
Django之Form組件
根據(jù)創(chuàng)建的form類,可以在頁面自動生成form的input標簽.
創(chuàng)建form類:
可以直接在app目錄下新建forms.py文件.
在類中定義input標簽.
from django.forms import Form
from django.forms import widgets
from django.forms import fields
class MyForm(Form):
username = fields.CharField(
# 可以對輸入的數(shù)據(jù)進行初步篩選
max_length=6,
min_length=2,
widget=widgets.TextInput(attrs={'id': 'i1', 'class': 'c1'})
)
# 多選一
gender = fields.ChoiceField(
choices=((1, '男'), (2, '女'),),
initial=2,
widget=widgets.RadioSelect
)
city = fields.CharField(
initial=2,
widget=widgets.Select(choices=((1, '上海'), (2, '北京'),)) # 下拉框
)
pwd = fields.CharField(
min_length=6,
widget=widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True)
)
構(gòu)建views.py邏輯:
導入自定義的form類并實例化:obj = MyForm()
.
若填充數(shù)據(jù)可免,is_valid()
根據(jù)自定義的規(guī)則對表單數(shù)據(jù)進行驗證抓于,沒有錯誤則通過obj.clean()
以字典形式取出數(shù)據(jù),若有錯誤浇借,則錯誤信息以字典形式保存在obj.errors
中.
from django.shortcuts import render, redirect
from .forms import MyForm
def login(request):
if request.method == "GET":
obj = MyForm()
return render(request, 'login.html', {'form': obj})
elif request.method == "POST":
obj = MyForm(request.POST, request.FILES)
errors={}
if obj.is_valid():
values = obj.clean()
print(values)
else:
errors = obj.errors
print(errors)
return render(request, 'login.html', {'form': obj,'errors':errors})
else:
return redirect('http://www.baidu.com')
在login.html動態(tài)生成input標簽:可以很方便的顯示錯誤信息.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登錄頁面</title>
<style>
div{
margin-bottom: 10px;
}
.error{
font-size: 12px;
color: red;
}
</style>
</head>
<body>
<form method="post" novalidate>
{# 1.常規(guī)制作input標簽 #}
{# <label for="id_username">用戶名</label><input type="text" id="id_username" name="username">#}
{# <label for="id_password">密碼</label><input type="text" id="id_password" name="password">#}
{# <input type="submit">#}
{# 2.模板語言結(jié)合自定義form動態(tài)生成input標簽 #}
<div>用戶名{{ form.username }}<span class="error">{{ form.username.errors.0 }}</span></div>{# 可以直接顯示錯誤信息 #}
<div>性別{{ form.gender }}</div>
<div>城市{{ form.city }}</div>
<div>密碼{{ form.pwd }}<span class="error">{{ form.pwd.errors.0 }}</span></div>
<input type="submit">
{% csrf_token %}
</form>
</body>
</html>
常用控件
// radio單選按鈕捉撮,值為字符串==========================
gender = forms.ChoiceField(
choices=((1, '男性'),(2, '女性'), (3, '中性'), ), initial=1,
widget=widgets.RadioSelect
)
gender = forms.CharField(
initial=1, widget=widgets.RadioSelect(choices=((1, '男性'),(2, '女性'), (3, '中性'), ))
)
// 單select,值為字符串========================================
user = fields.CharField(
initial=2,
widget=widgets.Select(choices=((1,'上海'),(2,'北京'),))
)
user = fields.ChoiceField(
choices=((1, '上海'), (2, '北京'),),
initial=2,
widget=widgets.Select
)
// 多選select妇垢,值為列表==========================================
user = fields.MultipleChoiceField(
choices=((1,'上海'),(2,'北京'),),
initial=[1,],
widget=widgets.SelectMultiple
)
// 單checkbox
gender = forms.ChoiceField(initial=[2, ],choices=((1, '上海'), (2, '北京'),), widget=widgets.CheckboxInput)
// 多選checkbox,值為列表
user = fields.MultipleChoiceField(
initial=[2, ],
choices=((1, '上海'), (2, '北京'),),
widget=widgets.CheckboxSelectMultiple
)
自定義不能為空的錯誤信息:
class RegisterForm(forms.Form):
username = forms.CharField(min_length=2,error_messages={'required':'用戶名不能為空'},
正則自定義錯誤信息:
1.存在多條驗證時, 以列表或者元祖形式導入實例化的RegexValidator
對象, django會按照順序逐個驗證,直到拋出錯誤信息;
2.傳入兩個參數(shù): 匹配的正則表達式
和 錯誤信息
;
3.原理是若輸入與正則不匹配,則拋出ValidationError(錯誤信息)
from django.form import Form
from django.core.validators import RegexValidator
class UserForm(Form):
username = forms.CharField(
validators=[RegexValidator(r'^[0-9]+$', '請輸入數(shù)字'),
RegexValidator(r'^159[0-9]+$', '數(shù)字必須以159開頭')],
widget=widgets.TextInput(attrs={'class': 'form-control'}))
自定義驗證函數(shù)
from django.core.exceptions import ValidationError
import re
def mobile_validate(value):
mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
if not mobile_re.match(value):
raise ValidationError('手機號碼格式錯誤')
class UserForm(forms.Form):
username = forms.CharField(validators=[mobile_validate, ], widget=widgets.TextInput(attrs={'class': 'form-control'}),)
局部鉤子:重載內(nèi)置的clean_field()
方法巾遭,在form字段中定義的驗證完成后,會執(zhí)行clean_field()
方法闯估,此時通過cleaned_data
取出值進行判斷.
from django import forms
from django.forms import widgets
from django.core.exceptions import ValidationError
class UserForm(Form):
username = forms.CharField(widget=widgets.TextInput(attrs={'class': 'form-control'}),)
def clean_username(self):
value = self.cleaned_data['username']
if value == 'alex':
raise ValidationError('用戶名已存在')
return value
全局鉤子:
重載系統(tǒng)的clean()
方法(clean(self)
是在clean_field(self)
之后運行的).
錯誤信息是保存在obj.errors.__all__
中灼舍,可以通過自定義過濾器取出自定義的錯誤信息并在前端顯示.
# forms.py
from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.validators import RegexValidator,ValidationError
class MyForm(Form):
....
def clean(self):
if self.cleaned_data.get('pwd1') != self.cleaned_data.get('pwd2'):
raise ValidationError('密碼不一致')
else:
return self.cleaned_data
// 自定義過濾器
from django import template
register = template.Library()
@register.filter
def get_error(error_dict):
if error_dict.get('__all__'):
return error_dict.get('__all__')[0]
// 前端顯示自定義錯誤信息
<div id="errors">
{% if errors %}
{{ errors|get_error }}
{% endif %}
</div>
文件上傳
指定傳輸數(shù)據(jù)處理方式: enctype="multipart/form-data"
.
取出文件:文件的二進制格式保存在request.FILES
中,在后臺直接以字典方式取出.
django配置media上傳文件路徑:
// 1. setting.py配置
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media', "fileholder_name", ...) # 若存在多級目錄,可以逐個寫入文件夾名稱即可
// 2. urls.py配置
from django.views.static import serve
from onlineLearningSys import settings
# 僅限于debug模式
urlpatterns += [
url(r'^media/(?P<path>.*)$', serve, {
'document_root': settings.MEDIA_ROOT,
}),
]
// 3. models.py
# 圖片是以路徑字符串的形式存儲在數(shù)據(jù)庫的;
image = models.ImageField(upload_to="user/%Y/%m", verbose_name='圖片', max_length=100, default='static/images/xgt.jpg')
// 4. html文件,類似于imageField存儲的格式是: organization/2018/03/1499239092.png, 必須在加上media前綴
<a href="org-detail-homepage.html">
<img width="200" height="120" class="scrollLoading" data-url="/media/{{ org.org_image }}"/>
</a>
后臺取出文件并利用model方式保存:
# views.py 保存用戶圖片信息
def ***()
img = request.FILES.get('img')
user = UerProfile.objects.create_user(
username=username,
password=password,
img= img,
)
user.save()
手動保存文件:很少用到涨薪!
file_obj = request.FILES.get('upload_file')
f1 = open(file_obj.name, "wb")
# 方法1
for i in file_obj:
f1.write(i)
# 方法2
for i in file_obj.chunks():
f1.write(i)
補充
在form表單中骑素,只有<input type="button" id="btn" vaule='提交'>
觸發(fā)的事件不會自動提交表單,<button>
和<input type="submit" id="btn">
都會自動提交表單刚夺,切記献丑!
<form>
<input type="text">
<input type="button" id="btn" value='提交'>
</form>
Ajax
通過js或者jquery在當前頁面獲取局部的數(shù)據(jù),并將提交到后臺.
異步傳輸:不會阻塞進程光督。
局部刷新:不會刷新整個頁面阳距。
Jquery實現(xiàn)Ajax發(fā)送
1.常規(guī)方法.
手動構(gòu)建data,包括跨域請求csrf鍵值對.
只能傳輸簡單的數(shù)據(jù)结借,不能傳輸文件筐摘,傳輸?shù)胶笈_的是文件的路徑.
<script>
$('#submit_btn').click(function () {
$.ajax({
url:'/login.html/',
type:"POST",
data: {'username':$('#username').val(),
'password': $("#password").val(),
"csrfmiddlewaretoken":$('[name="csrfmiddlewaretoken"]').val()
},
success:function (data) {
alert(data)
}
})
});
</script>
2.serialize方法
直接將form標簽序列化:data: $('.post_form').serialize(),
相對更簡單,同樣不能傳遞文件.
3.formdata
構(gòu)建:var form = new FormData()
.
有手動填充和自動填充兩種方式.
可以傳輸文件.
// 手動填充form
$('#submit_btn').click(function () {
var form = new FormData();
form.append("username",$('#id_username').val());
form.append("password",$('#id_password').val());
form.append('csrfmiddlewaretoken', $('[name="csrfmiddlewaretoken"]').val());
# 文件傳輸;
form.append('file',$('#upload_file')[0].files[0]);
$.ajax({
url: '/login.html/',
type: 'post',
data: form,
processData: false,
contentType:false,
success: function (data) {
}
})
});
// 自動填充
$('#submit_btn').click(function () {
var form = new FormData($('#login_form')[0]); // 注意,必須傳入DOM對象
$.ajax({
url: '/login.html/',
type: 'post',
data: form,
processData: false,
contentType:false,
success: function (data) {
alert(data)
}
})
});
JS實現(xiàn)Ajax發(fā)送
json簡單介紹
javascript object notation縮寫船老,基于javascript語言的輕量級數(shù)據(jù)交換格式.
作用:可靠性傳遞數(shù)據(jù)咖熟,不改變其類型,例如字典和列表.
dict >> str >> | bytes>> | str >>> dict |
---|---|---|
json.dumps() | encode/decode | json.loads() |
兩種格式:
-
{key:value, ...}
:key是字符串,value可以為所有類型; [{k1:v1}, {k2:v2}, ...]
類型:string, number, boolean, array, object, null
易混淆概念:
字符串:雙引號或單引號包括的字符.
json字符串:符合json格式的字符串.
json對象:指符號json要求的js對象.
在js中使用方法:
# 創(chuàng)建json對象
var obj = {
key: value,
k2: [],
k3: {k4:v4,...}
}
# 查詢:obj.key
# 增加鍵值對或修改:obj.key = v2
# 刪除:delete obj.key
靜態(tài)文件配置
將所有靜態(tài)文件放到一個static文件夾下, 主要是項目固有的js, css, img等不會更改的文件柳畔;
每個應用都應該有自己的靜態(tài)文件夾馍管;
-
settings.py
配置STATIC_URL='static/' STATICFILES_DIRS=[os.path.join(BASE_DIR, 'static')]
-
前端引入:
{% load staticfiles %} <link rel="stylesheet" href={% static 'css/bootstrap.css' %}">
Cookies & Session
由于WEB是無狀態(tài)請求, 服務器不能直接識別用戶的狀態(tài)信息。
cookies簡單使用
獲取Cookies: request.COOKIES.get("islogin",None)
薪韩。
設置Cookies:obj.set_cookie("islogin",True)
确沸。
刪除Cookies: obj.delete_cookie("cookie_key",path="/",domain=name)
實現(xiàn)cookies的簡單用戶認證登錄:
# views.py
def index(request):
# 從本地的cookie查詢,若找到對應的用戶,則直接跳轉(zhuǎn)主頁面,否則重新登錄
is_login = request.COOKIES.get("bob", None)
if is_login:
return render(request, 'index.html')
else:
return redirect('/login/')
def my_login(request):
if request.method=='POST':
username = request.POST.get('username')
pwd = request.POST.get('password')
if username == 'bob' and pwd == 'abc123':
obj = redirect("/index/")
obj.set_cookie("bob", 'abc123', max_age=10) # 設置cookie鍵值對,會保存在本地, 最大期限為10s;
return obj
return render(request, 'login.html')
cookies 和 session 結(jié)合使用
COOKIE:緩存在本地捌锭,每次請求攜帶信息{SESSION_ID: 'ABC'}
;
SESSION:保存在服務器罗捎,即為SESSION表
SESSION_KEY = 'ABC'
SESSION_DATA = HASH({'DATA1', 'K2':'DATA2', ...})
流程:
1.客戶端首次發(fā)送請求未攜帶cookie信息观谦,django會給客戶端返回cookie鍵值對{session_id:'隨機字符串'}
,作為客戶端瀏覽器標識桨菜;
2.用戶登錄豁状,設置session存儲用戶狀態(tài)信息:
authenticate(username='', password='') # 檢驗取出用戶對象,
login(request, user) # 登錄,生成session記錄
3.當客戶端再訪問server則根據(jù)攜帶的cookie從session中取出對應的用戶信息,也就是本地瀏覽器在session的有效期內(nèi)再次訪問服務器,無需登錄,可以直接訪問授權(quán)的頁面倒得。具體用戶的狀態(tài)信息怎么保存在session_data中,暫時不深究!
session基本操作:
# 1.設置session值
request.session["session_name"]="admin"
# 2.獲取session值
session_name = request.session("session_name")
# 3.刪除session值
del request.session["session_name"] 刪除一組鍵值對
request.session.flush() 刪除一條記錄
# 4. 檢測是否操作session值
`if "session_name" is request.session:`
# 5. 取值
get(key, default=None)
fav_color = request.session.get('fav_color', 'red')
# 6.pop(key)
fav_color = request.session.pop('fav_color')
# 7泻红、keys()
# 8、items()
10霞掺、flush() # 刪除當前的會話數(shù)據(jù)并刪除會話的Cookie谊路,django.contrib.auth.logout() 函數(shù)中就會調(diào)用它。
# 用戶session的隨機字符串
request.session.session_key
# 將所有Session失效日期小于當前日期的數(shù)據(jù)刪除
request.session.clear_expired()
# 檢查 用戶session的隨機字符串 在數(shù)據(jù)庫中是否
request.session.exists("session_key")
# 刪除當前用戶的所有Session數(shù)據(jù)
request.session.delete("session_key")
session和cookies登錄實例
# 視圖函數(shù)views.py
def index(request):
# 直接取出之前存儲的值
if not request.session.get("is_login"):
return redirect('/login/')
else:
return render(request, 'index.html')
def login_(request):
if request.method == "GET":
return render(request, 'login.html')
username = request.POST.get('username')
pwd = request.POST.get('password')
if username == 'kate' and pwd == 'abc123':
# 此時cookie信息django已經(jīng)自動生成
print(request.COOKIES)
# {'csrftoken':'3KV1lSv2JqMVxTOtq1YNiUh8OjmhwACqoyCdSxwgCAcIK6NWWIozg1WtiMN4zfXL'}
# 需要我們自己設置session存儲的狀態(tài)信息
print(request.session) # <django.contrib.sessions.backends.db.SessionStore object at 0x00000000042E77F0>
request.session['is_login'] = True
request.session['user'] = 'kate'
return redirect('/index/')
后端
django相關命令:
下載: pip install django
創(chuàng)建項目: django-admin.py startproject pro_name
.
創(chuàng)建應用: python manage.py startapp appname
.
中間件Middlewares
位于wsgi和路由映射之間的一個模塊根悼,能夠在全局上改變django的輸入與輸出凶异,例如用戶信息驗證/日志記錄等.
中間件方法: process_request
,process_response
挤巡,
自定義中間件
1.在項目目錄下創(chuàng)建文件夾utils
, 內(nèi)部創(chuàng)建my_middleware.py
文件, 寫入類Md1
, 文件名稱和類名稱自定義;
from django.utils.deprecation import MiddlewareMixin
class Md1(MiddlewareMixin):
def process_request(self, reqeust):
print('md1_process_request')
def process_response(self, requst, response):
print('md1_process_response')
return response # 必須帶返回值
class Md2(MiddlewareMixin):
def process_request(self, request):
print('md2_process_request')
def process_response(self, request, response):
print('md2_process_response')
return response # 必須帶返回值
2.在配置文件中加入加上的中間件:
MIDDLEWARE = [
'utils.my_middleware.Md1',
'utils.my_middleware.Md2',
...
]
3.此時django所有的請求和相應都會依次經(jīng)過process_request()
和process_response
函數(shù).
# 結(jié)果
md1_process_request
md2_process_request
md2_process_response
md1_process_response
process_request返回值:
默認為return None
,當需要過濾某些請求時酷麦,可以直接return.
當存在返回值矿卑,就不會繼續(xù)往下執(zhí)行,就地返回相應的返回值至瀏覽器.
class Md1(MiddlewareMixin):
def process_request(self, request):
print('md1_process_request')
return HttpResponse('<h1>你好</h1>')
process_response返回值:
返回值就是Httpresponse返回的字符串沃饶,返回的字符串會經(jīng)過中間件處理, 例如加上響應頭數(shù)據(jù)母廷,必須帶返回值, 否則會出現(xiàn)系統(tǒng)錯誤.
版本向后兼容:為提高兼容性, 直接在自定義的Md上方寫入以下源碼,直接繼承, 此時就無須導入(有時候版本不同, 文件位置會有變動)
class MiddlewareMixin(object):
def __init__(self, get_response=None):
self.get_response = get_response
super(MiddlewareMixin, self).__init__()
def __call__(self, request):
response = None
if hasattr(self, 'process_request'):
response = self.process_request(request)
if not response:
response = self.get_response(request)
if hasattr(self, 'process_response'):
response = self.process_response(request, response)
return response
自定義中間件實現(xiàn)用戶登錄
# my_middleware.py
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import render
class Md1(MiddlewareMixin):
def process_request(self, request):
print('md1_process_request')
# 若本次請求是login頁面, 則當前中間件不做任何操作
if request.path == '/login.html':
return None
# 若未取不到對應的信息,則跳轉(zhuǎn)至登錄頁面,此處直接render
if not request.session.get('user_info'):
return render(request, 'login.html')
def process_response(self, request, response):
print('md1_process_response')
return response # 必須帶返回值
在配置文件中,自定制中間件盡可能放在尾部,因為必須經(jīng)過django自帶的中間件處理再進行自定義判斷.
注意重定向的問題糊肤,若取不到session信息就直接redirect登錄url的話,就會出現(xiàn)無限從定向問題,因為下一次回到這里還是沒有session信息,所以直接render頁面,避免此問題;
process_view方法
到達路由映射匹配視圖函數(shù)時又再一次有上到下運行每個中間件的process_view
方法.
process_view
函數(shù)默認return None
琴昆,若帶返回值的話,那么后面中間件的process_view
都不會執(zhí)行, 直接跳轉(zhuǎn)到尾部執(zhí)行process_response
, 返回值為process_view
的返回值.
process_exception捕捉異常
捕捉請求url相關的視圖函數(shù)中的異常馆揉,若沒有發(fā)生異常,此函數(shù)是不會運行的.
中間件會按照由下至上的順序執(zhí)行此方法业舍,若存在返回值,則直接將返回值交給process_response
返回,但是上面的中間件仍然會執(zhí)行完此方法!
[圖片上傳失敗...(image-74d451-1530180354286)]
路由映射urls
url路徑與視圖一一對應的關系.
傳過來的請求路徑與urls中的正則從上至下匹配,若匹配到則直接調(diào)動對應的視圖函數(shù).
命名
name:對url命名升酣,當url有變動時候舷暮,無須修改其他地方的引用!
# urls.py
url(r'^test/(?P<i>\d{1,4})', app1_view.test, name='test'),
# index.html 在頁面直接通過name屬性定位url噩茄,且可以傳遞一個參數(shù)
<a href="{% url 'test' 111 %}">點我</a>
namespace:在include中使用下面,針對多層級urls.py.
初步匹配后進入include中的app的urls進一步匹配;
在利用模板語言標簽{% url 'namespace: name' %}可以調(diào)用,保證url的唯一性.
# 項目主目錄
from django.conf.urls import url,include
urlpatterns = [
url(r'^user/', include('app01.urls',namespace='users')),
]
# app01.urls
from django.conf.urls import url,include
urlpatterns = [
url(r'^register/(\d+)/', app1_view.register,name='register'),
]
# index.html 點擊a標簽會直接定位到對應的url
<a href="{% url 'users:register' %} ">點我2</a>
請求路徑相關
<a href='/login/'>
:以斜杠開始的路徑, 直接當前域名后追加此路徑绩聘;
<a href='login/'>
:沒有反斜杠,在當前url后追加此路徑沥割;
<a href='?city="wuhan"&province="hubei"'></a>
:直接在后臺通過request.GET中以字典方式取出數(shù)據(jù)耗啦,{city:"wuhan;province:"hubei"}
,可以作為分組判斷條件机杜;
<a href='/test/1/'></a>
:
此處的數(shù)字 1 可以在 后臺作為參數(shù)接收.
無名分組:url(r'^test/(\d{0,4})/', views.f1),
芹彬,函數(shù)接收參 f1(request, id)
;
命名分組:若路徑中存在多個參數(shù)叉庐,那么可以采用命名分組:url(r'^test/(?P<id>\d{4})/(?P<month>\d{2})', views.func),
.
<form action=''>
和ajax中的url=''
同理.
視圖VIEW
兩種請求方式:get
舒帮,post
.
三種方法
render(request, 'index.html', {k:v,..})
:從數(shù)據(jù)庫中取出數(shù)據(jù)渲染到模板,再返回給瀏覽器展示陡叠,或者直接返回模板內(nèi)容到瀏覽器.
HttpResponse(str)
:返回固定字符串玩郊,若為標簽文本,頁面亦可以識別.
redirect('/login/')
:重新到路由配置下匹配url.
函數(shù)方式:url對應的是視圖函數(shù)
CBV模型
url對應一個視圖類.此類調(diào)用父類的dispatch()
方法枉阵,會根據(jù)http請求頭里的方法是get
還是post
方法來執(zhí)行相應的函數(shù)译红。在觸發(fā)視圖類的時候可以在dispatch函數(shù)內(nèi)部實現(xiàn)一些邏輯判斷.
from django.views import View
from django.shortcuts import render, redirect,HttpResponse
class LoginView(View):
def dispatch(self, request, *args, **kwargs):
'''可以在內(nèi)部做出邏輯判斷'''
if request.POST.get('username')=='alex':
return HttpResponse('用戶名已注冊')
ret = super(LoginView, self).dispatch(request, *args, **kwargs)
return ret
def get(self, request):
obj = MyForm()
return render(request, 'login.html', {'form': obj})
def post(self, request):
obj = MyForm(request.POST, request.FILES)
errors = {}
if obj.is_valid():
values = obj.clean()
print(values)
else:
errors = obj.errors
print(errors)
return render(request, 'login.html', {'form': obj, 'errors': errors})
弊端:每個視圖類內(nèi)部都需要定義dispatch函數(shù),造成重復代碼.
模板Template
通過模板語法渲染從數(shù)據(jù)庫中取出的數(shù)據(jù).
變量
一般是以字典形式放在上下文中:{'book', book_obj}
兴溜,通過視圖邏輯render到模板中.
查詢語法:
{{ iterable.0 }}
:若后端傳入的集合為列表或者元祖侦厚,那么可以根據(jù)索引取出元素;
{{ dict.key}}
:若為字典拙徽;
{{ book.index.author}}
:若為類對象.
標簽
語法:{% %}
循環(huán):在前端遍歷對象集合并取出屬性值
{% for person in person_list %}
<p>{{person.name}}</p>
{% empty %) // 判斷是否為空刨沦,內(nèi)置標簽判斷
<p>沒有符合的結(jié)果</p>
{% endfor %}
分支判斷:
{% if i > 100 %}
<p>100</p>
{% else %}
<p>{{i}}</p>
{% endif %}
{% with %}
:使用一個簡單地名字緩存一個復雜的變量,當你需要使用一個“昂貴的”方法(比如訪問數(shù)據(jù)庫)很多次的時候是非常有用的
{% with total=business.employees.count %}
{{ total }} employee{{ total|pluralize }}
{% endwith %}
當前循環(huán)計數(shù):{{forloop.counter}}
膘怕;
內(nèi)置過濾器
取值:
{{html_str|first}}
:返回value的第一個元素, 對有序集合和列表都適用.
{{html_str|last}}
:同first相反.
{{ value|get_digit:"2" }}
:value為數(shù)字想诅,返回從右往左數(shù)第2個數(shù)字.
修改:
{{html_str|slice:10}}
:返回前10個元素.
{{str|truncatechars:'int'}}
:截斷字符,超過部分用...
取代.
增:
{{value|add: arg}}
:將arg添加至value中。若value為4岛心,argu為2来破,則輸出6;若value為[1,2,3], argu為[4,5,6]忘古,則結(jié)果為[1,2,3,4,5,6].
刪:
{{value|cut: arg}}
:刪除value中所有的argu徘禁。若value為'hello world' argu為 '',則輸出 helloworld.
功能:
length
:返回value的長度.
capfirst
: 首字母大寫.
lower
:小寫.
make_list
:將value轉(zhuǎn)換為list,例如value為‘123’,則輸出[1,2,3]
random
從value中隨 機挑選一個元素并返回
{{html_str|safe}}
:保證頁面的安全性芋膘,默認將變量內(nèi)容轉(zhuǎn)換成純文本顯示.加上safe,確定你的數(shù)據(jù)是安全的才能被當成是標簽
{{ datetime_instance|date:"Y-m-j G:i" }}
:顯示結(jié)果2016-8-8 8:08
.
自定義過濾器
配置:
1.在settings.py
中的INSTALLED_APPS
配置當前app骤菠,不然django無法找到自定義的simple_tag
;
2.在對應的app應用目錄下創(chuàng)建templatetags
模塊(模塊名只能是templatetags)疤孕;
3.在templatetags里面創(chuàng)建任意my_tags.py
文件商乎;
無參數(shù):
# 1. 生成注冊類
from django import template
register = template.Library()
# 2. 定義過濾器并注冊
@register.filter
def square(value):
try:
return int(value)**2
except:
return '輸入必須為數(shù)字'
有參數(shù):{{ html_str|add:"11" }}
,只能傳遞一個參數(shù).
@register.filter
def add(value, arg):
try:
return int(value)+int(arg)*10
except:
return '輸入必須為數(shù)字'
在模板中使用{% load 'filename' %}
標簽裝載自定義標簽或者裝飾器.
自定義標簽
在后端可以先在后臺自定義標簽中將要展現(xiàn)的內(nèi)容制作成標簽字符串祭阀,在前端直接調(diào)用標簽語言就可以呈現(xiàn)內(nèi)容.
from django import template
from django.utils.safestring import mark_safe
register = template.Library()
@register.simple_tag
def get_html(s1, s2):
html = mark_safe('<h1>標題1{0}</h1><h2>標題2{1}</h2>'.format(s1, s2))
return html
# 在前端直接引用
{% load my_tag%}
{% tag_name arg1 arg2 %}
模板繼承
{% extends 'base.html' %}
必須放在首行.
覆蓋父模板盒子中的內(nèi)容:{% block name %} 重新定制的代碼 {% endblock %}
鹉戚,盒子越多越好鲜戒,靈活性更好.
{% include "test.html" %}
:直接在當前模板引入文件test.html
的內(nèi)容;
取到父模板盒子的內(nèi)容,然后再對其追加內(nèi)容:{{ block.super }}
.
Model
以類的方式創(chuàng)建和保存數(shù)據(jù).
具體步驟
1.在終端創(chuàng)建數(shù)據(jù)庫db_pro
抹凳;
2.在setting中配置連接數(shù)據(jù):
# 若使用mysql
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 數(shù)據(jù)庫引擎
'NAME': 'db_pro', # 你要存儲數(shù)據(jù)的庫名遏餐,事先要創(chuàng)建之
'USER': 'root', # 數(shù)據(jù)庫用戶名
'PASSWORD': '...', # 密碼
'HOST': 'localhost', # 主機
'PORT': '3306', # 數(shù)據(jù)庫使用的端口
}
}
3.在app的init文件中配置,本身就是通過pymysql對數(shù)據(jù)庫進行操作:
# 先安裝pymysql模塊赢底,pip install pymysql
import pymysql
pymysql.install_as_MySQLdb()
4. 在model中創(chuàng)建模型
5.數(shù)據(jù)遷移失都,若需要使用后臺添加數(shù)據(jù)則需要創(chuàng)建超級用戶:python manage.py createsuperuser
python manage.py makemigrations
python manage.py migrate
6.生成表
django自帶的表:session表,auth權(quán)限表幸冻,遷移記錄表
model用戶新建的表:app_class_name
在model中自定義錯誤信息:
創(chuàng)建表結(jié)構(gòu)
自定義model類.
常用model字段:
# 示例
class Course(models.Model):
name = models.CharField(max_length=50, verbose_name='課程名')
detail = models.TextField(verbose_name='課程詳情')
degree = models.CharField(choices=(('CJ', '初級'), ('ZJ', '中級'), ('GJ', '高級')), max_length=10)
learn_times = models.IntegerField(default=0, verbose_name='學習時長(分鐘)')
org = models.ForeignKey(CourseOrg, verbose_name="所屬課程機構(gòu)")
image = models.ImageField(upload_to="course/%Y/%m",verbose_name='課程圖片', max_length=100)
add_time = models.DateTimeField(default=datetime.now,verbose_name='課程添加時間')
recommend = models.BooleanField(default=False,verbose_name='是否推薦',max_length=2)
FileField
EmailField
model中的字段類型自帶檢測功能粹庞,不滿足條件時候會報錯.在admin添加記錄時候自動截斷,例如限制最大長度洽损,那么只取最大長度的字符串.
表關系同mysql數(shù)據(jù)庫.
admin相關
可以在admin后臺對表進行記錄操作.
注冊model:必須在app下的admin.py
注冊model的類后才能在后臺顯示.
from django.contrib import admin
from app.models import Book
admin.site.register(Book)
修改顯示表名:
def __str__:
return self.name
修改顯示的字段名:verbose_name = ''
定義顯示的字段:
# admin.py
from django.contrib import admin
from blog.models import Blog
#Blog模型的管理器
class BlogAdmin(admin.ModelAdmin):
list_display=('id', 'caption', 'author', 'publish_time')
#在admin中注冊綁定
admin.site.register(Blog, BlogAdmin)
修改表名:
class Meta:
db_table = 'new_name'
表記錄操作
增加記錄:實例化并賦值, 隨后save();
# 方法1
book = Book(**kwargs)
book.save()
# 方法2
Book.create(**kwargs)
刪記錄:Book.objects.filter(title='').delete()
改記錄:
# 方法1:
Book.objects.filter(title='t1').update(title='t2')
# 方法2:
b = Book.objects.get('')
b.title = ''
b.save()
單表查詢
ORM操作主要是查詢操作.
# 只針對int類型字段大小比較
models.Tb1.objects.filter(id__gt=1) # 獲取id大于1的值
models.Tb1.objects.filter(id__gte=1) # 獲取id大于等于1的值
models.Tb1.objects.filter(id__lt=10) # 獲取id小于10的值
models.Tb1.objects.filter(id__lte=10) # 獲取id小于10的值
models.Tb1.objects.filter(id__lt=10, id__gt=1) # 獲取id大于1 且 小于10的值
# in范圍比較
models.Tb1.objects.filter(id__in=[11, 22, 33]) # 獲取id等于11庞溜、22、33的數(shù)據(jù)
# not in
models.Tb1.objects.exclude(id__in=[11, 22, 33]) # not in
# range
models.Tb1.objects.filter(id__range=[1, 2]) # 范圍bettwen and
# isnull
Entry.objects.filter(pub_date__isnull=True)
# 其他類似
startswith碑定,istartswith, endswith, iendswith,
# contains包含
models.Tb1.objects.filter(name__contains="ven")
models.Tb1.objects.filter(name__icontains="ven") # icontains大小寫不敏感
models.Tb1.objects.exclude(name__icontains="ven")
# regex正則匹配流码,iregex 不區(qū)分大小寫
Entry.objects.get(title__regex=r'^(An?|The) +')
Entry.objects.get(title__iregex=r'^(an?|the) +')
# 升序
orderby('id')
orderby('-id') # 降序列
# 切片
models.Tb1.objects.all()[10:20]
.get() # 有就返回首個,沒有報錯;
.only('id', 'title') # 只取某些字段
基于對象的查詢
# 查詢linux這本書的出版社的地址
book_obj = Book.objects.filter(title="linux").first()
addr = book_obj.publisher.address
# 查詢這本書的所有作者
book_obj = Book.objects.filter(title="蔣勛說唐詩").first()
authors = book_obj.author.all()
基于雙下劃線的跨表查詢
通過表之間的外鍵產(chǎn)生聯(lián)系才能連表查詢。以下練習是基于書|出版社|作者三張表延刘。主要分為正向和反向查詢兩種思維方式漫试。
分組annotate:
ret = Book.objects.values('category_title').annotate(count=Count('id')
avg_price = Avg('price')
)
結(jié)果 <QuerySet [{'count': 3, 'avg_price': 59.0, 'category__title': '歷史'},
{'count': 2, 'avg_price': 55.0, 'category__title': '小說'}]>
聚合函數(shù)aggregate:
ret = Book.objects.aggregate(count=Count('id'), avg_price= Avg('price'))
結(jié)果 {'avg_price': 57.4, 'count': 5}
values('id', 'title')
:每一行記錄為一個字典,整體是list列表访娶,
例如[{'id':1,'title':'asa'},...]
.
value_list('id','title')
:每一行記錄的為一個元祖,整體是list列表.
例如[(1,'asas'),...]
.
練習1.查詢linux這本書的出版社地址:
# 方法1. 先定位book表, 在values中聯(lián)表查詢
addr = Book.objects.filter(title="linux").values("publisher__name","publisher__address")
print(addr)
# 方法2. 先定位出版社, 在filter中聯(lián)表查詢
result = Publisher.objects.filter(book__title="linux").values("address", "name")
print(result)
聯(lián)系2.查詢"蔣勛說唐詩"這本書的所有作者的名字:
# 方法1: 正向查詢,先找出書,隨后連表查詢關聯(lián)的作者
authors = Book.objects.filter(title="蔣勛說唐詩").values("author__name")
print(authors)
# 方法2:先找出作者,在過濾中連表篩選
res = Author.objects.filter(book__title="蔣勛說唐詩").values("name")
print(res)
練習3.查詢價格大于40的書籍的作者姓名
# 方法1:正向查詢, 從書入手
res = Book.objects.filter(price__gt=40).values("author__name").distinct()
# 反向查詢,從作者入手
res = Author.objects.filter(book__price__gt=40).values("book__title").distinct()
聚合查詢aggregate()
作用對象是整個query_set
集合或者一個列表商虐,返回一個字典: {要計算的字段名: 函數(shù)返回的結(jié)果, ..}
,此方法需要配合計算函數(shù)使用: Avg
, Sum
, Count
, Max
, Min
.
練習1:查詢所有圖書的平均價格
from django.db.models import Avg, Sum,Count,Max,Min
ret = models.Book.objects.all().aggregate(Avg('price'), Sum('price'))
mysql> select avg(price), sum(price) from booksys_book;
結(jié)果: {'price__avg': 70.63636363636364, 'price__sum': 777}
練習2: 查詢"人民出版社"出版的最貴的書
res = Publisher.objects.get(name="人民出版社").book_set.values("price",'title').aggregate(Max("price"))
結(jié)果: {'price__max': 111}
// 多個字段一樣查詢
models.Publisher.objects.get(id=1).books.values('title', 'price').aggregate(Max('price'), Sum('price'))
{'price__max': 111, 'price__sum': 400}
分組查詢annotate()
作用對象是每個分組, 可以通過values()
指定分組的字段;
- 返回的是
query_set
集合的鍵值對, 包含分組字段和查詢字段以及對應的值; - 此方法需要配合計算函數(shù)使用:
Avg
,Sum
,Count
,Max
,Min
練習1: 查詢每一個作者出版過書的最高價格
# values相當于對書籍分類進行分組崖疤,隨后再利用annotate對每個分組用聚合函數(shù)
res = Book.objects.values("type").annotate(Max("price")))
<QuerySet [{'type': '1', 'price__max': 66}, {'type': '10', 'price__max': 23}, {'type': '2', 'price__max': 33}, {'type': '3', 'price__max': 200}]>
練習2: 每個出版社出版過的最高價格的書
models.Book.objects.values('publisher').distinct().annotate(Max('price'))
<QuerySet [{'publisher': 1, 'price__max': 111}, {'publisher': 2, 'price__max': 200}]>
F查詢
可以在查詢中引用字段,來比較同一個 model 實例中兩個不同字段的值典勇。
// 練習1: 查看評論數(shù)大于閱讀數(shù)的書
models.Book.objects.filter(comment_num=F('read_num'))
// 練習2: 給每本書漲價20元
mysql> update booksys_book set price+=10;
# 貌似每次最大遞增有限制
Book.objects.all().update(price=F("price")+20)
Q查詢
支持的邏輯運算符: 與&
, 或|
, 非~
# 查詢以"蔣"開頭且價格大于50的書
book = Book.objects.filter(title__startswith="蔣", price__gt=50).values('title', "price")
# 查詢以"鋼"開頭或價格大于100的所有書
models.Book.objects.filter(Q(title__startswith='鋼')|Q(price__gt=100)).all()
查看數(shù)據(jù)庫的sql語句(加在settings.py)
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console':{
'level':'DEBUG',
'class':'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'propagate': True,
'level':'DEBUG',
},
}
}
Django內(nèi)置功能
信號
內(nèi)置信號:
pre_init # Django中的model對象執(zhí)行其構(gòu)造方法前,自動觸發(fā)
post_init # Django中的model對象執(zhí)行其構(gòu)造方法后,自動觸發(fā)
pre_save # Django中的model對象保存前,自動觸發(fā)
post_save # Django中的model對象保存后,自動觸發(fā)
pre_delete # Django中的model對象刪除前,自動觸發(fā)
post_delete # Django中的model對象刪除后,自動觸發(fā)
m2m_changed #Django中的model對象使用m2m字段操作數(shù)據(jù)庫的第三張表(add,remove,clear,update),自動觸發(fā)
class_prepared # 程序啟動時,檢測到已注冊的model類,對于每一個類,自動觸發(fā)
request_started # 請求到來前,自動觸發(fā)
request_finished # 請求結(jié)束后,自動觸發(fā)
got_request_exception # 請求異常時,自動觸發(fā)
setting_changed # 配置文件改變時,自動觸發(fā)
template_rendered # 模板執(zhí)行渲染操作時,自動觸發(fā)
connection_created # 創(chuàng)建數(shù)據(jù)庫連接時,自動觸發(fā)
內(nèi)置信號使用步驟:
1.自定義功能函數(shù)劫哼,并且將其綁定到內(nèi)置信號上:
// 項目啟動目錄init.py
#導入需要引用的內(nèi)置信號
from django.db.models.signals import pre_save, post_save
def pre_save_func(sender, **kwargs):
print('pre_save_fun')
print('pre_save_msg:', sender, kwargs)
def post_save_func(sender, **kwargs):
print('post_save_func')
print('post_save_msg:', sender,kwargs)
pre_save.connect(pre_save_func) # 綁定功能函數(shù)到信號
post_save.connect(post_save_func)
# 打印結(jié)果
sender: <class 'app01.models.UserInfo'> # model類
參數(shù)(鍵值對): {'signal': <django.db.models.signals.ModelSignal object at 0x0000000002E5F0B8>, 'instance': <UserInfo: UserInfo object>, '
created': True, 'update_fields': None, 'raw': False, 'using': 'default'
}
自定義信號
1.在項目根目錄下創(chuàng)建signal_test.py
文件, 定義信號
import django.dispatch
action=django.dispatch.Signal(providing_args=["aaa","bbb"])
2.還是在項目應用的init
文件中完成功能函數(shù)和綁定信號
from signal_test import action
def pre_save_func(sender, **kwargs):
print("pre_save_func")
print("pre_save_msg:", sender, kwargs)
action.connect(pre_save_func)
3.在視圖函數(shù)中觸發(fā)信號
def add_user(request):
UserInfo.objects.create(name='alex', age=12)
action.send(sender='python', aaa=111, bbb=222)
return HttpResponse('添加成功')
4.結(jié)果展示
sender:python
參數(shù): {'signal': <django.dispatch.dispatcher.Signal object at 0x0000000003CCC5F8>, 'aaa': 111, 'bbb': 222}
信號和中間件的區(qū)別:信號比中間件散布的范圍更廣。在后臺的許多動作事件都可以觸發(fā)內(nèi)置的信號割笙,從而執(zhí)行與之綁定的函數(shù)权烧,也可以自定義信號擴展其功能;而中間件常用的就只有四個函數(shù)伤溉,且僅在接受請求和返回請求的過程中起作用般码。
緩存
由于Django是動態(tài)網(wǎng)站,所有每次請求可能進行數(shù)據(jù)庫相關操作乱顾,當程序訪問量大時板祝,耗時必然會更加明顯,最簡單解決方式是使用緩存走净。緩存將一個某個views的返回值保存至內(nèi)存或者memcache中券时,在有效時間內(nèi)不再去執(zhí)行view中的操作孤里,而是直接從內(nèi)存或者Redis中之前緩存的內(nèi)容拿到并返回。
[圖片上傳失敗...(image-46f969-1530180354286)]
按照存儲位置的六種緩存方式:
1.內(nèi)存中緩存: 內(nèi)容以字符串形式存儲在內(nèi)存中, 但是位置無法確定!
# setting.py
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake',
'TIMEOUT': 300, # 緩存超時時間(默認300橘洞,None表示永不過期捌袜,0表示立即過期)
'OPTIONS': {
'MAX_ENTRIES': 300, # 最大緩存?zhèn)€數(shù)(默認300)
'CULL_FREQUENCY': 3, # 緩存到達最大個數(shù)之后,剔除緩存?zhèn)€數(shù)的比例炸枣,即:1/CULL_FREQUENCY(默認3)
},
}
}
2虏等,文件中緩存: 保存至指定的文件
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': '/var/tmp/django_cache', # 需要指定文件夾路徑
}
}
# 注:其他配置同開發(fā)調(diào)試版本
3,數(shù)據(jù)庫中緩存
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'my_cache_table', # 數(shù)據(jù)庫表
}
}
# 注:執(zhí)行創(chuàng)建表命令 python manage.py createcachetable
4适肠,Memcache緩存(python-memcached模塊)
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': '127.0.0.1:11211', # 類似于連接遠程緩存服務器
}
}
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': [
# # 支持簡單的分布式,將數(shù)據(jù)分別存儲在多個內(nèi)存中, 防止其中一個緩存清空
'172.19.26.240:11211',
'172.19.26.242:11211',
]
}
}
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': 'unix:/tmp/memcached.sock',
}
}
按照緩存的使用范圍分類
1霍衫,全棧緩存
url經(jīng)過一系列的認證等操作,如果請求的內(nèi)容在緩存中存在迂猴,則使用FetchFromCacheMiddleware
獲取內(nèi)容并返回給用戶慕淡,當返回給用戶之前,判斷緩存中是否已經(jīng)存在沸毁,如果不存在則UpdateCacheMiddleware
會將緩存保存至緩存峰髓,從而實現(xiàn)全站緩存。
# setting.py配置
MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware',
# 中間放其他中間件
'django.middleware.cache.FetchFromCacheMiddleware',
]
CACHE_MIDDLEWARE_SECONDS = 5 # 每隔5s更新數(shù)據(jù)
# views.py
# 可以模擬每次訪問, 看是否user信息是否更新, 結(jié)果是每5s一次更新一條數(shù)據(jù), 說明前4s的所有請求都沒有到views視圖函數(shù)中,直接從緩存中拿取數(shù)據(jù)
def add_user(request):
UserInfo.objects.create(name='alex', age=12)
user_list = UserInfo.objects.all() # 在有效時間內(nèi)有限從緩存中取數(shù)據(jù)息尺,所以不會及時更新
return render(request, 'index.html', {
'user_list': user_list
})
2携兵,單獨視圖實現(xiàn)緩存,例如某些靜態(tài)頁面可以使用此方法
Bв 方式一:
from django.views.decorators.cache import cache_page
# 參數(shù)為更新的時間(s)
@cache_page(60 * 15)
def my_view(request):
...
P旖簦 方式二:
from django.views.decorators.cache import cache_page
urlpatterns = [
url(r'^foo/([0-9]{1,2})/$', cache_page(60 * 15)(my_view)),
]
3,頁面的局部更新炭懊,例如購物網(wǎng)站中商品信息和價格都需要要實時更新并级,但是剩余數(shù)量一般都需要實時更新。
利用模板局部緩存時候侮腹,請求會進行數(shù)據(jù)庫相關操作嘲碧,唯一不同的是,在更新時間內(nèi)不會將數(shù)據(jù)渲染到模板中而已。
<!DOCTYPE html>
# 在模板中導入內(nèi)置的中間件
{% load cache %}
<html lang="en">
<head>
<meta charset="UTF-8">
<title>主頁面</title>
</head>
<body>
<h1>主界面</h1>
{% cache 3 '局部刷新' %}
{% for user in user_list %}
{{ user.name }}
{% endfor %}
{% endcache %}
</body>
</html>