Django知識點總結

Django知識點總結

web架構簡介

主流python web框架

Django、Torando、Flask、Bottlejavascript

MVC和MTV

model(模型、數據庫)、views(html模版)、controllers(業務邏輯處理) --> MVCcss

model(模型、數據庫)、templates(html模版)、view(業務邏輯處理) --> MTVhtml

程序的三層架構

數據處理層

- 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保持一致
)

模板文件便可使用該目錄下的靜態文件java

<link rel="stylesheet" href="/static/css-filename.css">

建立app

終端建立

cd到項目目錄下python

python3 manage.py startapp app01     # 可有多個,用於主站、後臺管理、運維平臺等等
經過pycharm建立

在建立Project的時候輸入app名mysql

高級配置文件

——適用於非瀏覽器的的客戶端jquery

參考django內部的配置文件,製做支持用戶配置及默認配置文件
django.conf import global_settings  # 默認配置文件
django.conf import settings         # 用戶配置文件

在客戶端的start.py文件下git

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)

小技巧:web

#測試模式配置
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文件總結:

  1. 建立project
  2. 建立APP
  3. 建立數據庫
  4. 靜態文件路徑配置
  5. urls.py新建url模式指向視圖函數。
  6. view.py建立建立視圖函數返回模板。
  7. (若有)建立form表單約束規則。
  8. 新建前端模版。
  9. 重複五、六、七、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?

  1. 保存在瀏覽器上的鍵值對
  2. 服務端能夠向用戶瀏覽器端寫cookie
  3. 客戶端每次發請求時會攜帶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')
建立本身的簽名規則
  1. 在建立新模塊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]
  1. 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
複雜請求

簡單請求(僅發送一次請求):HEADGETPOST

複雜請求(發送兩次請求,一次爲預檢請求):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

其餘突破口:經過服務端發送請求再返回給瀏覽器

總結:
  1. 優先使用jQuery,不容許的狀況下使用XMLHttpRequest或僞Ajax
  2. 保證兼容性使用僞Ajax
  3. 上傳文件使用僞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

  1. 到項目下settings.py修改
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',   # 修改成mysql
        'NAME': 'DjangoTEST',                   # 修改數據庫名
        'USER': 'root',                 
        'PASSWORD':'123',
        'HOST': '127.0.0.1',
        'PORT': 3306
    }
}
  1. __init__.py文件下加入
import pymysql
pymysql.install_as_MySQLdb()
  1. 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
]
  1. 導入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請求生命週期

  1. 用戶輸入url
  2. 客戶端發送請求頭(post方法有請求體)
  3. 服務端接收並提取請求內容
  4. 交由路由關係匹配
  5. 函數進行模版數據渲染
  6. 返回到用戶(響應頭+響應體)

自定義的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【一、二、3】--> process_view【一、二、3】--> process_response【一、二、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防護功能
  1. 全站禁用
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',
]
  1. 局部禁用
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')
  1. 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

Admin

http://www.cnblogs.com/wupeiqi/articles/7444717.html

相關文章
相關標籤/搜索