Django进阶

Django进阶

Django的请求生命周期

admin的配置

中间件

Cookie和Session

Django中的FBV和CBV

Django自身安全机制 - XSS和CSRF

序列化

分页

图片/文件上传

信号

缓存

Form

ModelForm

 


Django的请求生命周期

HTTP请求及服务端响应中传输的所有数据都是字符串.

在Django中, 当访问一个的url时, 会通过路由匹配进入相应的html网页中.

Django的请求生命周期是指当用户在浏览器上输入url到用户看到网页的这个时间段内, Django后台所发生的事情

Django请求生命周期 (从发起到返回经历了什么) :

  请求(经过中间件)完到url, url到视图函数, 在视图函数(CBV,FBV)里去数据库拿数据, 去模板拿数据, 得到一个字符串, 返回(经过中间件)(经过渲染)给用户.

1. 当用户在浏览器中输入url时,浏览器会生成请求头和请求体发给服务端请求头和请求体中会包含浏览器的动作(action),这个动作通常为get或者post,体现在url之中.

2. url经过Django中的wsgi,再经过Django的中间件,最后url到过路由映射表,在路由中一条一条进行匹配, 一旦其中一条匹配成功就执行对应的视图函数,后面的路由就不再继续匹配了.

3. 视图函数根据客户端的请求查询相应的数据.返回给Django,然后Django把客户端想要的数据做为一个字符串返回给客户端.

4. 客户端浏览器接收到返回的数据,经过渲染后显示给用户.

wsgi -> 中间件 -> 路由系统 -> 视图函数(ORM, Template, 渲染)

 


admin的配置

admin是Django提供的一个后台管理页面, 改管理页面提供完善的html和css, 它能够从数据库中读取数据, 呈现在页面中, 可以对数据进行增删改查,而使用django admin 则需要以下步骤:

  • 创建后台管理员
  • 注册和配置django admin后台管理页面

可将admin界面由英文换成中文页面, 在setting.py 文件中修改以下选项

# LANGUAGE_CODE = 'en-us'  
LANGUAGE_CODE = 'zh-hans'

1. 创建后台管理员

python manage.py createsuperuser

2. 注册和配置django admin后台管理页面

  ModelAdmin: 管理界面的定制类, 如需扩展特定的model界面需从该类继承.

  创建定制类

class myAdmin(admin.ModelAdmin):
    pass

  注册medel类到admin的两种方式:

    方式一: 使用register的方法

admin.site.register(Book,MyAdmin)

    方式二: 使用register的装饰器

@admin.register(Book)

  部分定制类设置技巧

  •     list_display:     可以指定要显示的字段
  •     search_fields:  为数据表添加搜索功能, 可以指定搜索的字段, 会出现一个搜索框
  •     list_filter:        添加快速过滤功能, 可以指定列表过滤器, 会出现一个过滤器
  •     ordering:      可以指定排序字段
from django.contrib import admin
from app01.models import *
# Register your models here.

# @admin.register(Book) # 方式一
class MyAdmin(admin.ModelAdmin):
    list_display = ("title","price","publisher")
    search_fields = ("title","publisher")
    list_filter = ("publisher",)  #记得加逗号
    ordering = ("price",)
    fieldsets =[
        (None,               {'fields': ['title']}),
        ('price information', {'fields': ['price',"publisher"], 'classes': ['collapse']}),
    ]

admin.site.register(Book,MyAdmin)  #方式二
admin.site.register(Publish)
admin.site.register(Author)

 


中间件

在Django中, 中间件(middleware)就是一个类, 在请求到来和结束后, Django会根据自己的规则在合适的时机执行中间件中相应的方法.

在Django项目的settings模块中, 有一个MIDDLEWARE变量, 其中每一个元素就是一个中间件.

中间件中可以定义五个方法,分别是:

process_request(self,request)
process_view(self, request, callback, callback_args, callback_kwargs)
process_template_response(self,request,response)
process_exception(self, request, exception)
process_response(self, request, response)

(每一个请求都是先通过中间件中的 process_request 函数)

以上方法的返回值可以是None和HttpResonse对象. 如果是None, 则继续按照Django定义的规则向下执行, 处理其他中间件. 如果是HttpResonse对象, 则直接将该对象返回给用户.

中间件的运行流程 

自定义中间件

1、创建中间件类

class RequestExeute(object):
    def process_request(self,request):
        pass
    def process_view(self, request, callback, callback_args, callback_kwargs):
        pass
    def process_exception(self, request, exception):
        pass
    def process_response(self, request, response):
        return response

2、注册中间件

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',
    'charon.middleware.auth.RequestExeute', 
    # 与mange.py在同一目录下的文件夹 charon/middleware下的auth.py文件中的RequestExeute类
)
# ------- md/middle1.py ------------
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse

class MyMiddlewareMixin(object):
    def __init__(self, get_response=None):
        self.get_response = get_response
        super(MyMiddlewareMixin, self).__init__()

    def __call__(self, request):
        response = None
        if hasattr(self, 'process_request'):
            # 执行当前中间件的process_request
            response = self.process_request(request)
        if not response:
            # 执行下一个中间件的 __call__
            response = self.get_response(request)
        if hasattr(self, 'process_response'):
            # 执行当前中间件的process_response
            response = self.process_response(request, response)
        return response

class Middle1(MyMiddlewareMixin):
    def process_request(self, request):
        print('middle1-->process_request')
    def process_view(self, request, view_func, view_args, view_kwargs):
        print('middle1-->process_view')

    def process_response(self, request, response):
        print('middle1-->process_response')
        return response

    def process_exception(self, request, exception):
        print('middle1-->process_exception')

    def process_template_response(self,request,response):
        print('middle1--> process_template_response')
        return response



class Middle2(MyMiddlewareMixin):
    def process_request(self, request):
        print('middle2-->process_request')
    def process_view(self, request, view_func, view_args, view_kwargs):
        print('middle2-->process_view')

    def process_response(self, request, response):
        print('middle2-->process_response')
        return response

    def process_exception(self, request, exception):
        print('middle2-->process_exception')

    def process_template_response(self,request,response):
        print('middle2--> process_template_response')
        return response

# ----------- settings.py ------------
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',
    'md.middle1.Middle1',
    'md.middle1.Middle2',
]
例子

应用

拦截器, 拦截恶意访问网站的人.

比如统计一分钟访问页面数太多次, 就把他的 IP 加入到黑名单 BLOCKED_IPS(这部分没有提供代码, 主要讲中间件部分)

#项目 charon 文件名 charon/middleware.py
 
class BlockedIpMiddleware(object):
    def process_request(self, request):
        if request.META['REMOTE_ADDR'] in getattr(settings, "BLOCKED_IPS", []):
            return http.HttpResponseForbidden('<h1>Forbidden</h1>')

# 这里的代码的功能就是 获取当前访问者的 IP (request.META['REMOTE_ADDR']),如果这个 IP 在黑名单中就拦截,如果不在就返回 None (函数中没有返回值其实就是默认为 None),把这个中间件的 Python 路径写到settings.py中.
View Code

 


Cookie 和 Session

Cookie

  会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话. 常用的会话跟踪技术是Cookie与Session. Cookie通过在客户端记录信息确定用户身份, Session通过在服务器端记录信息确定用户身份.
  在程序中, 会话跟踪是很重要的事情. 理论上, 一个用户的所有请求操作都应该属于同一个会话, 而另一个用户的所有请求操作则应该属于另一个会话, 二者不能混淆. 例如, 用户A购买的商品应该放在A的购物车内, 不论是用户A什么时间购买的, 这都是属于同一个会话的, 不能放入用户B或用户C的购物车内, 这不属于同一个会话.
  Web应用程序是使用HTTP协议传输数据的. HTTP协议是无状态的协议. 一旦数据交换完毕, 客户端与服务器端的连接就会关闭, 再次交换数据需要建立新的连接, 这就意味着服务器无法从连接上跟踪会话. 即用户A购买了一件商品放入购物车内, 当再次购买商品时服务器已经无法判断该购买行为是属于用户A的会话还是用户B的会话了.
  为了跟踪该会话于是引入了Cookie, 这一机制, 弥补了HTTP协议无状态的不足. 在Session出现之前, 基本上所有的网站都采用Cookie来跟踪会话.
  Cookie工作原理: 由于HTTP是一种无状态的协议, 服务器单从网络连接上无从知道客户身份. 于是就给客户端们颁发一个通行证吧, 每人一个, 无论谁访问都必须携带自己通行证, 这样服务器就能从通行证上确认客户身份了. Cookie实际上是一小段的文本信息. 客户端请求服务器, 如果服务器需要记录该用户状态, 就使用response向客户端浏览器颁发一个Cookie. 客户端浏览器会把Cookie保存起来, 当浏览器再请求该网站时, 浏览器把请求的网址连同该Cookie一同提交给服务器. 服务器检查该Cookie, 以此来辨认用户状态. 服务器还可以根据需要修改Cookie的内容.  

  Cookie就是保存在浏览器端的键值对:

  • 主要应用于用户登录 (如果浏览器不支持Cookie或者把Cookie禁用了, Cookie功能就会失效, 则用户无法登陆)
  • 保存在用户浏览器 (保存在客户端)
  • 可以主动清除也可以被"伪造" (客户端可以设置, 服务器端也可以设置)
  • Cookie具有不可跨域名性 (浏览器访问Google只会携带Google的Cookie, 而不会携带Baidu的Cookie. Google也只能操作Google的Cookie, 而不能操作Baidu的Cookie)
1、设置Cookie:
rep = HttpResponse(...) 或 rep = render(request, ...)
 
# 普通
rep.set_cookie(key,value,...)
# 加盐 - 普通cookie是明文传输的, 可以直接在客户端直接打开. 所以需要加盐, 只有在解盐之后才能查看.
rep.set_signed_cookie(key,value,salt='加密盐',...)
    参数:
        key,              键
        value='',         值
        max_age=None,     cookie生效的时间, 单位是秒
        expires=None,     具体过期日期, 时间格式为datetime格式
        path='/',         Cookie生效的路径, / 表示根路径, 根路径的cookie可以被任何url的页面访问
        domain=None,      Cookie生效的域名. 同一个一级域名下的两个二级域名如"www.google.com""image.google.com"不能交互使用. 但如果设置为"google.com", 则所有以"google.com"结尾的域名都可以访问该Cookie
        secure=False,     该Cookie是否仅被使用安全协议传输. 安全协议有HTTPS, SSL等, 在网络上传输数据之前先将数据加密, 默认为false
        httponly=False    只能http协议传输, 无法被JavaScript获取 (不是绝对, 底层抓包可以获取到也可以被覆盖)
2、获取Cookie:
# 普通
xx = request.COOKIES['key']  或  xx = request.COOKIES.get('key')
# 加盐
xx = request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)
    参数:
        default: 默认值
           salt: 加密盐
        max_age: 后台控制过期时间

由于Cookie保存在客户端的电脑上, 所以JavaScript或jQuery也可以操作Cookie. (使用jQuery操作Cookie需下载jquery.cookie.js插件)

<script src="/static/jquery-3.3.1.js"></script>
<script src="/static/jquery.cookie.js"></script>

<script>
    console.log(document.cookie)
    $.cookie('k','v',{'path':'/'})
    //一个参数: $.cookie(v) 获取值 (读取)
    //两个参数: $.cookie(k, v) 设置值 (操作)
    $.cookie('the_cookie', null);   //通过传递null作为cookie的值来删除cookie

    d = new Date();
    d = d.setDate(d.getDate()+1);
    $.cookie('the_cookie','the_value',{
    expires:d,   //也可以设置为一个整数, 单位是天
    path:'/',
    domain:'jquery.com',
    secure:true
}) 
</script>

实例 - 简易后台管理和用户登入:

# ------------------ models.py ---------------------
from django.db import models
# Create your models here.
class Classes(models.Model):
    caption = models.CharField(max_length=32)

class Student(models.Model):
    name = models.CharField(max_length=32)
    cls = models.ForeignKey(Classes, on_delete=models.CASCADE)

class Teacher(models.Model):
    name = models.CharField(max_length=32)
    cls = models.ManyToManyField(Classes)

class Administrator(models.Model):
    username = models.CharField(max_length=32)
    pwd = models.CharField(max_length=32)

# ------------------ urls.py ---------------------
from django.contrib import admin
from django.urls import path
from app1 import views
urlpatterns = [
    path('admin/', admin.site.urls),
    path('login/', views.login),
    path('index/', views.index),
]

# ------------------ views.py ---------------------
from django.shortcuts import render,HttpResponse,redirect
from django.views.decorators.csrf import csrf_exempt
from app1 import models

@csrf_exempt
def login(req):
    # models.Administrator.objects.create(
    #     username='charon',
    #     pwd='123'
    # )
    message=''
    if req.method=='POST':
        user = req.POST.get('username')
        pwd = req.POST.get('pwd')

        c = models.Administrator.objects.filter(username=user,pwd=pwd).count()
        if c:
            ret = redirect('/index/')
            # ret.set_cookie('username',user)
            # ret.set_cookie('account','lala')
            # ret.set_cookie('pwd','123123')

            ret.set_signed_cookie('username',user)
            ret.set_signed_cookie('account1','lala')
            ret.set_signed_cookie('pwd1','123123')
            return ret
        else:
            message='用户名或密码错误'
    return render(req,'login.html',{'msg':message})

def index(req):
    # username = req.COOKIES.get('username')
    username = req.get_signed_cookie('username')
    if username:
        return render(req,'index.html',{'username':username})
    else:
        return redirect('/login/')

# ------------------ index.html ---------------------
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>hello {{ username }}</h1>
</body>
</html>

# ------------------ login.html ---------------------
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/login/" method="post">
    {% csrf_token %}
    <div>
        <p>
        用户名<input type="text" name="username">
            <span id="error"></span>
        </p>
        <p>
            密码<input type="password" name="pwd">
        </p>
    </div>
    <div>
        <input type="submit" value="submit">
        <span style="color: red">{{ msg }}</span>
    </div>
</form>
</body>
</html>
View Code

 

Session

  Session是另一种记录客户状态的机制, 不同的是Cookie保存在客户端浏览器中, 而Session保存在服务器上. 客户端浏览器访问服务器的时候, 服务器把客户端信息以某种形式记录在服务器上, 这就是Session. 客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了.

  Cookie机制是通过检查客户身上的"通行证"来确定客户身份的话, 而Session机制是通过检查服务器上的"客户明细表"来确认客户身份. Session相当于程序在服务器上建立的一份客户档案, 客户来访的时候只需要查询客户档案表就可以了.

  Cookie看似解决了HTTP(短连接、无状态)的会话保持问题, 但把全部用户数据保存在客户端, 存在安全隐患, 于是Cookie+Session出现了. 可以把关于用户的数据保存在服务端, 在客户端cookie里加一个sessionID(随机字符串).

  Session作用: 会话保持, 记住用户的登录状态(WEB网站, 分布式架构). 和Cookie的区别: 避免了敏感信息保存在客户端,防止客户端修改cookie信息.

  Cookie+Session的工作流程:

(1)、当用户来访问服务端时, 服务端生成一个随机字符串;

(2)、当用户登录成功后 把 {sessionID :随机字符串} 组织成键值对 加到 cookie里发送给用户;

(3)、服务器以发送给客户端 cookie中的随机字符串做键,用户信息做值,保存用户信息;

Session:

  • Session是保存在服务器端的键值对, 而cookie是保存在客户端的键值对. 
  • Session在服务器端存储的时候, 会给客户端发送一个随机字符串, 当用户再来请求的时候, 只需要带上这个随机字符串即可.
  • Session在服务器端存储的格式是一个字典, 字典的key为发送给客户端的随机字符串, 而value里面是用户的信息, 当用户取常用数据的时候, 不要再次操作数据库了, 直接从字典的value中取即可. 例如:{‘随机字符串’:{"id":1, "name":"charon"}}

  • Session的内部机制依赖Cookie

Django中默认支持Session, 其内部提供了5种类型的Session供开发者使用:

  • 数据库(默认)
  • 缓存 (memchache、redis)
  • 文件
  • 缓存+数据库
  • 加密Cookie
# --------- 1、数据库Session
Django默认支持Session,并且默认是将Session数据存储在数据库中,即:django_session 表中.
a. 配置 settings.py
SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默认)

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,默认修改之后才保存(默认)

b. 使用 
def index(request):
    # 获取、设置、删除Session中数据
    request.session['k1']
    request.session.get('k1',None)
    request.session['k1'] = 123
    request.session.setdefault('k1',123) # 存在则不设置
    del request.session['k1']          #将字典中某一个值删除
    request.session.clear()           #将session清空,主要用于注销的时候

    # 所有 键、值、键值对
    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失效策略。

# --------- 2、缓存Session
a. 配置 settings.py
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # 引擎
SESSION_CACHE_ALIAS = 'default'                            # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置

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,默认修改之后才保存

b. 使用
同上

# --------- 3、文件Session
a. 配置 settings.py
SESSION_ENGINE = 'django.contrib.sessions.backends.file'    # 引擎
SESSION_FILE_PATH = None                                    # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir()    
                                                            # 如:/var/folders/d3/j9tj0gz93dg06bmwxmhh6_xm0000gn/T
                                                            #SESSION_FILE_PATH = os.path.join(BASE_DIR, 'cache')  这时就会创建一个cache目录,在里面存储session

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,默认修改之后才保存

b. 使用
同上

# --------- 4、缓存+数据库Session
数据库用于做持久化,缓存用于提高效率
a. 配置 settings.py
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'        # 引擎

b. 使用
同上

# --------- 5、加密cookie Session
a. 配置 settings.py
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'   # 引擎

b. 使用
同上

# --------- 6、扩展:Session用户验证
def login(func):
    def wrap(request, *args, **kwargs):
        # 如果未登陆,跳转到指定页面
        if request.path == '/test/':
            return redirect('http://www.baidu.com')
        return func(request, *args, **kwargs)
    return wrap
View Code
实例 - 简易后台管理和用户登入(完整):
# ------------------ models.py ---------------------
from django.db import models
# Create your models here.
class Classes(models.Model):
    caption = models.CharField(max_length=32)

class Student(models.Model):
    name = models.CharField(max_length=32)
    cls = models.ForeignKey(Classes, on_delete=models.CASCADE)

class Teacher(models.Model):
    name = models.CharField(max_length=32)
    cls = models.ManyToManyField(Classes)

class Administrator(models.Model):
    username = models.CharField(max_length=32)
    pwd = models.CharField(max_length=32)

# ------------------ urls.py ---------------------
from django.contrib import admin
from django.urls import path
from app1 import views
urlpatterns = [
    path('admin/', admin.site.urls),
    path('login/', views.login),
    path('index/', views.index),
    path('logout/', views.logout),
    path('classes/', views.handle_classes),
]

# ------------------ views.py ---------------------
from django.shortcuts import render,HttpResponse,redirect
from django.views.decorators.csrf import csrf_exempt
from app1 import models

@csrf_exempt
def login(req):
    # models.Administrator.objects.create(
    #     username='charon',
    #     pwd='123'
    # )
    message=''
    if req.method=='POST':
        user = req.POST.get('username')
        pwd = req.POST.get('pwd')

        c = models.Administrator.objects.filter(username=user,pwd=pwd).count()
        if c:
            req.session['is_login'] = True
            req.session['username'] = user
            ret = redirect('/index/')
            return ret
        else:
            message='用户名或密码错误'
    return render(req,'login.html',{'msg':message})

def logout(req):
    req.session.clear()
    return redirect('/login/')

# def index(req):
#     is_login = req.session.get('is_login')
#     if is_login:
#         current_user = req.session.get('username')
#        return render(req,'index.html',{'username':current_user})
#     else:
#         return redirect('/login/')

def auth(func):
    def inner(request, *args, **kwargs):
        is_login = request.session.get('is_login')
        if is_login:
            return func(request, *args, **kwargs)
        else:
            return redirect('/login/')
    return inner

@auth
def index(request):
    current_user = request.session.get('username')
    return render(request, 'index.html',{'username': current_user})

# 用装饰器
@auth
def handle_classes(request):
    current_user = request.session.get('username')
    return render(request, 'classes.html', {'username': current_user})

# 不用装饰器
def handle_student(request):
    is_login = request.session.get('is_login')
    if is_login:
        current_user = request.session.get('username')
        return render(request, 'student.html', {'username': current_user})
    else:
        return redirect('/login/')

def handle_teacher(request):
    is_login = request.session.get('is_login')
    if is_login:
        current_user = request.session.get('username')
        return render(request, 'teacher.html', {'username': current_user})
    else:
        return redirect('/login/')

# ------------------ index.html ---------------------
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>hello {{ username }}</h1>
    <a href="/logout/">注销</a>
</body>
</html>

# ------------------ login.html ---------------------
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/login/" method="post">
    {% csrf_token %}
    <div>
        <p>
        用户名<input type="text" name="username">
            <span id="error"></span>
        </p>
        <p>
            密码<input type="password" name="pwd">
        </p>
    </div>
    <div>
        <input type="submit" value="submit">
        <span style="color: red">{{ msg }}</span>
    </div>
</form>
</body>
</html>

# ------------------ classes.html ---------------------
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>当前登录用户:{{ username }}</h1>
    <div>....</div>
</body>
</html>
View Code

 


Django中的FBV和CBV

Django中请求处理方式有2种:FBV 和 CBV.

FBV(function base views) 就是在视图里使用函数处理请求

# ---------------- views.py -------------------
def login(req):
    message=''
    if req.method=='POST':
        user = req.POST.get('username')
        pwd = req.POST.get('pwd')

        c = models.Administrator.objects.filter(username=user,pwd=pwd).count()
        if c:
            req.session['is_login'] = True
            req.session['username'] = user
            ret = redirect('/index/')
            return ret
        else:
            message='用户名或密码错误'
    return render(req,'login.html',{'msg':message})

CBV(class base views) 就是在视图里使用类处理请求

# ---------------- urls.py -------------------
urlpatterns = [
    # path('login/', views.login),    #FBV
    path('login/', views.Login.as_view()),     #使用CBV记得加.as_view()
]

# ---------------- views.py -------------------
# 1. 对某一种请求做处理:单一装饰器
from django import views
from django.utils.decorators import method_decorator
def outer(func):
    def inner(request, *args, **kwargs):
        print(request.method)
        return func(request, *args, **kwargs)
    return inner

class Login(views.View):
    @method_decorator(outer)
    def get(self, request, *args, **kwargs):
        return render(request,'login.html',{'msg':''})

    @method_decorator(outer)
    def post(self, request, *args, **kwargs):
        user = request.POST.get('username')
        pwd = request.POST.get('pwd')

        c = models.Administrator.objects.filter(username=user, pwd=pwd).count()
        if c:
            request.session['is_login'] = True
            request.session['username'] = user
            ret = redirect('/index/')
            return ret
        else:
            message = '用户名或密码错误'
            return render(request, 'login.html', {'msg': message})

# 2. 对所有请求做处理:dispatch 或者 dispatch+装饰器
from django import views
from django.utils.decorators import method_decorator
def outer(func):
    def inner(request, *args, **kwargs):
        print(request.method)
        return func(request, *args, **kwargs)
    return inner

@method_decorator(outer, name='dispatch')   # 可以在class上加装饰器, 但要记得加name='dispatch', 也可以不使用装饰器
class Login(views.View):
    @method_decorator(outer)   # 可以在dispatch上加装饰器, 也可以不使用装饰器
    def dispatch(self, request, *args, **kwargs):
        # if request.method == 'GET':
        #     return HttpResponse('FUCK OFF')
        print(request.method)
        
        ret = super(Login, self).dispatch(request, *args, **kwargs)  # 调用父类中的dispatch方法
        return ret

    def get(self, request, *args, **kwargs):
        return render(request, 'login.html', {'msg': ''})

    def post(self, request, *args, **kwargs):
        user = request.POST.get('username')
        pwd = request.POST.get('pwd')

        c = models.Administrator.objects.filter(username=user, pwd=pwd).count()
        if c:
            request.session['is_login'] = True
            request.session['username'] = user
            ret = redirect('/index/')
            return ret
        else:
            message = '用户名或密码错误'
            return render(request, 'login.html', {'msg': message})

注意: 记得import View类 => from django.views import View.

注意: 类要继承View类, 类中函数名必须小写.

FBV和CBV两种方式没有优劣, 都可以使用.

 


Django自身安全机制 - XSS和CSRF

XSS攻击(WEB注入)

XSS又叫CSS  (Cross Site Script)), 是一种HTML注入, 跨站脚本攻击. 它指的是恶意攻击者往Web页面里插入恶意html代码, 当用户浏览该页之时, 嵌入其中Web里面的html代码会被执行, 这些脚本代码可以用来获取合法用户的数据, 如Cookie信息. 做法: 例如在评论里面或者url输入栏里加上<script>操作.

它与SQL注入攻击类似, SQL注入攻击中以SQL语句作为用户输入, 从而达到查询/修改/删除数据的目的, 而在XSS攻击中, 通过插入恶意脚本, 实现对用户游览器的控制, 获取用户的一些信息.

重点: 注入本质上就是把输入的数据变成可执行的程序语句.

实施XSS攻击需要具备两个条件:

  一、需要向web页面注入恶意代码.

  二、这些恶意代码能够被浏览器成功的执行.

解决办法:

  1、一种方法是在表单提交或者url参数传递前, 对需要的参数进行过滤.
  2、在后台对从数据库获取的字符串数据进行过滤, 判断关键字.
  3、设置安全机制.
Django框架:内部机制默认阻止了. 它会判定传入的字符串是不安全的, 就不会渲染而以字符串的形式显示. 

若要解除这种默认的安全机制有两种方法:

  - 在客户端: 模版页面上对拿到的数据后写上safe. ----> {{XXXX|safe}}
  - 在服务器端: 导入模块 - from django.utils.safestring import mark_safe, 把要传给页面的字符串做安全处理 ----> s = mark_safe(s)

实例:

# ---------------- views.py -----------------
from django.shortcuts import render
msg = []
def comment(request):
        if request.method == "GET":
        return render(request,'comment.html')
    else:
        v = request.POST.get('content')
        if "script" in v:
            return render(request,'comment.html',{'error': '小比崽子还黑我'})
        else:
            msg.append(v)
            return render(request,'comment.html')

def index(request):
    return render(request,'index.html',{'msg':msg})

def test(request):
    from django.utils.safestring import mark_safe
    temp = "<a href='http://www.baidu.com'>百度</a>"
    newtemp = mark_safe(temp)
    return render(request,'test.html',{'temp':newtemp}


# ---------------- comment.html -----------------
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    <form method="POST" action="/comment/">
        <h4>评论</h4>
        <input type="text" name="content"/>
        <input type="submit" value="提交" />{{ error }}
    </form>
</body>
</html>

# ---------------- index.html -----------------
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    <h1>评论内容</h1>
    {% for item in msg %}
        <div>{{ item|safe }}</div>
    {% endfor %}
</body>
</html>
View Code

 

跨站请求伪造

CSRF, Cross Site Request Forgery, 跨站点请求伪造. 例如, 某个恶意的网站上有一个指向你的网站的链接, 如果某个用户已经登录到你的网站上了, 那么当这个用户点击这个恶意网站上的那个链接时, 就会向你的网站发来一个请求, 你的网站会以为这个请求是用户自己发来的, 其实这个请求是那个恶意网站伪造的.

Django为用户实现防止跨站请求伪造的功能, 通过中间件 django.middleware.csrf.CsrfViewMiddleware 来完成. 而对于Django中设置防跨站请求伪造功能有分为全局和局部.

全局:

  中间件 django.middleware.csrf.CsrfViewMiddleware

局部:

导入模块 - from django.views.decorators.csrf import csrf_exempt, csrf_protect

  • @csrf_protect, 为当前函数强制设置防跨站请求伪造功能, 即便settings中没有设置全局中间件.
  • @csrf_exempt, 取消当前函数防跨站请求伪造功能, 即便settings中设置了全局中间件.

Django 提供的 CSRF 防护机制

  Django 第一次响应来自某个客户端的请求时(get方式), 会在服务器端随机生成一个 token, 然后把这个 token 写在用户请求的 cookie 里, 同时也会给客户端页面发送一个随机的 token (form表单中以{% csrf_token %}方式获取)用以认证. 之后客户端每次以 POST 方式向服务端提交请求时, 都会带上这个 token, 这样就能避免被 CSRF 攻击.

  1. 在返回的 HTTP 响应的 cookie 里, Django 会为你添加一个 csrftoken 字段,其值为一个自动生成的 token;
  2. 在所有的 POST 表单中, 必须包含一个 csrfmiddlewaretoken 字段 - {% csrf_token %}
  3. 在处理 POST 请求之前, Django 会验证这个请求的 cookie 里的 csrftoken 字段的值和提交的表单里的 csrfmiddlewaretoken 字段的值是否一样. 如果一样, 则表明这是一个合法的请求, 否则这个请求可能是来自于别人的 csrf 攻击, 返回 403 Forbidden.
  4. 在所有 AJAX POST 请求里, 添加一个 X-CSRFTOKEN header, 其值为 cookie 里的 csrftoken 的值.

注: GET 请求不要用.

1. 基本应用

直接在form表单中添加:
    {% csrf_token %} -----> 转换成一个hidden属性的input标签


    {{ csrf_token }} -----> 直接获取csrf的随机字符串
    #本地的cookies中也会添加随机字符串

2. 全站禁用

整个框架不使用csrf安全机制,直接在settings.py文件中注销,整个网站都不再应用.
    # 'django.middleware.csrf.CsrfViewMiddleware',

3. 局部禁用: 全局使用, 但是某些函数不需要应用

setting中: 'django.middleware.csrf.CsrfViewMiddleware'
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')

4. 局部使用: 全局不使用, 但是某些函数需要应用

setting中注销: # 'django.middleware.csrf.CsrfViewMiddleware'

from django.views.decorators.csrf import csrf_exempt,csrf_protect
        @csrf_protect  #全站不用,某个函数需要使用认证的时候
        def csrf1(request):
 
            if request.method == 'GET':
                return render(request,'csrf1.html')
            else:
                return HttpResponse('ok')

5. CBV使用

from django.views import View
from django.utils.decorators import method_decorator  #必须使用这个方法
    
    #请求来了, 都是先执行类View的内置函数dispatch, 然后再映射到对应的方法上. 所以给dispatch添加上就相当于给所有的方法添加了
    @method_decorator(csrf_protect,name='dispatch') 
    class Foo(View):            
        def get(self,request):
             pass
        def post(self,request):
            pass        

6. Ajax提交数据时候, 携带CSRF:

# a. POST方式提交, 放置在data中携带     
            <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-3.3.1.js"></script>
            <script>
                function submitForm(){
                    var csrf = $('input[name="csrfmiddlewaretoken"]').val();  # 获取随机字符串
                    var user = $('#user').val();
                    $.ajax({
                        url: '/csrf1/',
                        type: 'POST',
                        data: { "user":user,'csrfmiddlewaretoken': csrf}, 
       #注意csrf随机字符串,后台有固定的名字接收,这种方式是通过标签获取的对应值
                        success:function(arg){
                            console.log(arg);
                        }
                    })
                }
            </script>   
  
# b. POST方式提交, 信息放在请求头中, 从cookies中获取的csrf随机字符串
                <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-3.3.1.js"></script>
                <script src="/static/jquery.cookie.js"></script>
 
                <script>
                    function submitForm(){
                        var token = $.cookie('csrftoken');   # 获取随机字符串
                        var user = $('#user').val();
                        $.ajax({
                            url: '/csrf1/',
                            type: 'POST',
                            headers:{'X-CSRFToken': token},  #注意csrf随机字符串后台有固定的名字接收
                            data: { "user":user},
                            success:function(arg){
                                console.log(arg);
                            }
                        })
                    }
                </script>    

# c. 在b的基础上可以加个ajax全局设置, 和对post请求的判断
    <input type="button" onclick="Do();"  value="Do it"/>
  
    <script src="/static/plugin/jquery/jquery-3.3.1.js"></script>
    <script src="/static/plugin/jquery/jquery.cookie.js"></script>
    <script>
        var csrftoken = $.cookie('csrftoken');
  
        function csrfSafeMethod(method) {
            # these HTTP methods do not require CSRF protection
            return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
        }
        $.ajaxSetup({    # 对全局的ajax进行设置
            beforeSend: function(xhr, settings) {
                if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                    xhr.setRequestHeader("X-CSRFToken", csrftoken);
                }
            }
        });
        function Do(){
  
            $.ajax({
                url:"/csrf2/",
                data:{id:1},
                type:'POST',
                # {#headers: {'X-CSRFToken':$.cookie('csrftoken')},#}
                success:function(data){
                    console.log(data);
                }
            });
  
        }
    </script>
View Code

 


序列化

Django中的序列化主要应用在将数据库中检索的数据返回给客户端用户, 别的Ajax请求一般返回的为Json格式.

普通类型的数据序列化可以使用JSON解决. 而Django  QuerySet对象类型的数据则使用Django框架内部封装了序列化的方法 serializers, 用于QuerySet对象的序列化.

1、serializers

from django.core import serializers
 
ret = models.BookType.objects.all()
 
data = serializers.serialize("json", ret)

2、json.dumps

import json
 
#ret = models.BookType.objects.all().values('caption')
ret = models.BookType.objects.all().values_list('caption')
 
ret=list(ret)
 
result = json.dumps(ret)

实例 - 二级联动, 外键, 下拉菜单:

# ------------ models.py -----------------
class Province(models.Model):
    name = models.CharField(max_length=32)

class City(models.Model):
    name = models.CharField(max_length=32)
    pro = models.ForeignKey("Province", on_delete=models.CASCADE)

class Xian(models.Model):
    name = models.CharField(max_length=32)
    cy = models.ForeignKey("City", on_delete=models.CASCADE)

# ------------ urls.py -----------------
from django.urls import path, re_path
from tets1 import views

urlpatterns = [
    re_path('^menu.html$', views.menu),
]

# ------------ views.py -----------------
def menu(request):
    # for i in range(10):
    #     models.Province.objects.create(name="河北"+str(i))
    # for i in range(5):
    #     models.City.objects.create(name="廊坊" + str(i),pro_id=1)
    # for i in range(4):
    #     models.Xian.objects.create(name='县'+ str(i), cy_id=1)

    pro_list = models.Province.objects.all()
    return render(request, 'menus.html', {"pro_list": pro_list})

def fetch_city(request):
    # 根据用户传入的省份ID,获取与其相关的所有市ID
    province_id = request.GET.get('province_id')

    # 方法一(won't work, don't know why)
    # result = models.City.objects.filter(pro_id=province_id)
    # print(result)
    # from django.core import serializers
    # data = serializers.serialize("json", result)

    # 方法二
    result = models.City.objects.filter(pro_id=province_id).values('id','name')
    result = list(result)
    import json
    data = json.dumps(result)

    # 方法三(won't work, don't know why)
    # result = models.City.objects.filter(pro_id=province_id).values_list('id','name')
    # result = list(result)
    # import json
    # data = json.dumps(result)

    return HttpResponse(data)

def fetch_xian(request):
    city_id = request.GET.get('city_id')
    xian_list = models.Xian.objects.filter(cy_id=city_id).values('id','name')
    xian_list = list(xian_list)
    return HttpResponse(json.dumps(xian_list))

# ------------ menus.html -----------------
{% extends "layout.html" %}

{% block css %}

{% endblock %}


{% block content %}
    <h1>二级联动</h1>
    <select id="province">
        <option value="-1">请选择省份</option>
        {% for p in pro_list %}
            <option value="{{ p.id }}">{{ p.name }}</option>
        {% endfor %}
    </select>

    <select id="city">
        <option value="-1">请选择市</option>
    </select>

    <select id="xian">
        <option value="-1">请选择县</option>
    </select>

{% endblock %}


{% block js %}
    <script>
        $(function () {
            bindProvinceEvent();
            bindCityEvent()
        });
        
        function bindCityEvent() {
            $('#city').change(function () {
                var v = $(this).val();
                if (v == "-1"){

                }else{
                    $('#xian option:gt(0)').remove();
                    $.ajax({
                        url: '/fetch_xian.html',
                        type: 'GET',
                        data: {'city_id': v},
                        dataType: 'json',
                        success:function (arg) {
                            $.each(arg, function(k,v){
                                var tag = document.createElement('option');
                                tag.innerHTML = v.name;
                                tag.setAttribute('value', v.id);
                                $('#xian').append(tag);
                            });
                        }
                    })
                }
            })
        }
        
        function bindProvinceEvent(){
            $('#province').change(function () {
                var v = $(this).val();
                if(v == '-1'){

                }else{
                    $('#city option:gt(0)').remove();
                    $('#xian option:gt(0)').remove();
                    $.ajax({
                        url: '/fetch_city.html',
                        type: 'GET',
                        data: {'province_id': v},
                        dataType: 'json',
                        success: function (arg) {
                            $.each(arg, function(k,v){   //k是索引, v是值
                                var tag = document.createElement('option');
                                tag.innerHTML = v.name;
                                tag.setAttribute('value', v.id);
                                $('#city').append(tag);
                            });
                        }
                    })
                }
            })
        }
    </script>
{% endblock %}
二级联动

由于json.dumps时无法处理datetime日期, 所以可以通过自定义处理器来做扩展, 如:

import json  
from datetime import date  
from datetime import datetime  
   
class JsonCustomEncoder(json.JSONEncoder):  
    
    def default(self, field):  
     
        if isinstance(field, datetime):  
            return o.strftime('%Y-%m-%d %H:%M:%S')  
        elif isinstance(field, date):  
            return o.strftime('%Y-%m-%d')  
        else:  
            return json.JSONEncoder.default(self, field)  
   
   
# ds = json.dumps(d, cls=JsonCustomEncoder) 
View Code

在前端中, ajax

<form id = 'f1'>
    <input type='text' name='user1'>
    <input type='text' name='user2'>
    <input type='text' name='user3'>
    <input type='text' name='user4'>
    <select>
    </select>
</form>

$.ajax({
    url: '....',
    data: $('#f1').seralize(),
    ...
})  # 会自动获取表单内所有的name和值, 自动组成键值对

# ------------------

<form id = 'f1'>
    <input type='text' name='user1'>
    <input type='text' name='user2'>
    <input type='text' name='user3'>
    <input type='text' name='user4'>
    <select multiple>
        <option></option>
    </select>
</form>

$.ajax({
    url: '....',
    data: $('#f1').seralize(),
    traditional: true,
    ...
})  # 有multiple得时候, 要加个trandition:true, 因为嵌套

 


分页

Django内置分页

基础语法和参数

# ------------ Paginator对象 ---------------
//类Paginator:
    class Paginator(object_list,per_page,orphans=0,allow_empty_first_page=True)

//必须提供的参数:
    object_list:一个列表或元组,元素是django QuerySet或是包含count()或__len__()方法的可切片对象。
    per_page:包含在一页中最多的条目数量。

//可选参数:
    orphans:在最后一页中充许的最少条目数量,默认是0.当最后一页条目数量小于或等于orphans时,这些条目加到本页的上一页中。
    allow_empty_first_page:是否充许第一页为空。如设为False且object_list为空,则抛出EmptyPage异常。

//方法:
    Paginator.page(number):返回一个Page对象,序号是始于1.如给出的页号不存在,抛出InvalidPage异常。

//属性:
    Paginator.num_pages:页面总页数
    Paginator.page_range:页面数的范围,始于1,如[1,2,3,4]。

//InvalidPage异常:
    如要求的页面无效或页面中没有对象,page()抛出InvalidPage异常。
    PageNotAnInterger:当提供给page()的数不是整数是抛出该异常。
    EmptyPage:当提供给page()的数是一个有效数,但在该页没有对象存在时,抛出该异常。

# ------------ Page对象 ---------------
    class Page(object_list,number,paginator):
   #一般不手工创建Pages,可以使用Paginator.page().

//方法:
    Page.has_next():如有下一页则返回True
    Page.has_previous():如有上一页则返回True
    Page.has_other_pages():如有上一页或下一页返回True
    Page.next_page_number():返回下一页的页码。不管下一页是否存在都返回。
    Page.previous_page_number():返回上一页的页码。不管上一页是否存在都返回。
    Page.start_index():返回当前页面中第一个对象的序号,序号始于1.例如:将一个包含5个对象的列表分成每页2个对象,则第二页的start_index()返回3.
    Page.end_index():返回当前页面中最一个对象的序号。

//属性:
    Page.object_list:当前页面中所有的对象
    Page.number:当前页面的页码,始于1
    Page.paginator:页面相关的Pageinator对象
View Code

简单使用

分页是Web应用常用的手法,Django提供了一个分页器类Paginator(django.core.paginator.Paginator), 可以很容易的实现分页的功能. 该类有两个构造参数,一个是数据的集合,另一个是每页放多少条数据.

#Paginator的基本使用如下:
$python manage.py shell

>>> from django.core.paginator import Paginator

>>> objects = ['john', 'paul', 'george', 'ringo']

>>> p = Paginator(objects, 2)      #每页两条数据的一个分页器

>>> p.count        #数据总数

>>> p.num_pages      #总页数

>>>p.page_range       #页码的列表
range(1,3)

>>> page1 = p.page(1)     #第1页

>>> page1
<Page 1 of 2>

>>> page1.object_list     #第1页的数据
['john', 'paul']

>>> page2 = p.page(2)

>>> page2.object_list      #第2页的数据
['george', 'ringo']

>>> page2.has_next()     #是否有后一页
False

>>> page2.has_previous()   #是否有前一页
True

>>> page2.has_other_pages()   #是否有其他页
True

>>> page2.next_page_number()  #后一页的页码
3

>>> page2.previous_page_number()  #前一页的页码
1

>>> page2.start_index()   # 本页第一条记录的序数(从1开始)
3

>>> page2.end_index()    # 本页最后录一条记录的序数(从1开始)
4

>>> p.page(0)               #错误的页,抛出异常
...EmptyPage: That page number is less than 1

>>> p.page(3)              #错误的页,抛出异常
...EmptyPage: That page contains no results
View Code

实例

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

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

# ------------ views.py ---------------
from django.shortcuts import render
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger

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

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

    paginator = Paginator(L, 10)
    # per_page: 每页显示条目数量
    # count:    数据总个数
    # num_pages:总页数
    # page_range:总页数的索引范围,如: (1,10),(1,200)
    # page:     page对象
    try:
        posts = paginator.page(current_page)
        # has_next              是否有下一页
        # next_page_number      下一页页码
        # has_previous          是否有上一页
        # previous_page_number  上一页页码
        # object_list           分页之后的数据列表
        # number                当前页
        # paginator             paginator对象
    except PageNotAnInteger:
        posts = paginator.page(1)
    except EmptyPage:
        posts = paginator.page(paginator.num_pages)
    return render(request, 'page_divide.html', {'posts': posts})
View Code

 

自定义分页

分页时需要做三件事:

  • 创建处理分页数据的类
  • 根据分页数据获取数据
  • 输出分页HTML
# ------------- /utils/page.py ---------------
from django.utils.safestring import mark_safe

class PagerHelper:
    def __init__(self,total_count,current_page,base_url,per_page=10):
        self.total_count = total_count
        self.current_page = current_page
        self.base_url = base_url
        self.per_page = per_page

    @property
    def db_start(self):
        return (self.current_page -1) * self.per_page

    @property
    def db_end(self):
        return self.current_page * self.per_page

    def total_page(self):
        v, a = divmod(self.total_count, self.per_page)
        if a != 0:
            v += 1
        return v

    def pager_str(self):

        v = self.total_page()

        pager_list = []
        if self.current_page<=1:
            pager_list.append('<a href="javascript:void(0);">首页</a>')
        else:
            pager_list.append('<a href="%s?p=%s">首页</a>' % (self.base_url, 1,))

        if self.current_page == 1:
            pager_list.append('<a href="javascript:void(0);">上一页</a>')
        else:
            pager_list.append('<a href="%s?p=%s">上一页</a>' % (self.base_url, self.current_page - 1,))

        #总页数<11
            #0 -- totalpage
        #总页数>11
            #当前页大于5 currentPage-6 -- currentPage+5
                #currentPage+5是否超过总页数,超过总页数,end就是总页数
            #当前页小于5 0 -- 11
        if v <= 11:
            pager_range_start = 0
            pager_range_end = v
        else:
            if self.current_page <= 5:
                pager_range_start = 0
                pager_range_end = 11
            else:
                pager_range_start = self.current_page - 6
                pager_range_end = self.current_page + 5
                if pager_range_end > v:
                    pager_range_start = v - 10
                    pager_range_end = v


        for i in range(pager_range_start+1, pager_range_end+1):
            if i == self.current_page:
                pager_list.append('<a class="active" href="%s?p=%s">%s</a>' % (self.base_url, i, i,))
            else:
                pager_list.append('<a href="%s?p=%s">%s</a>' % (self.base_url, i, i,))

        if self.current_page == v:
            pager_list.append('<a href="javascript:void(0);">下一页</a>')
        else:
            pager_list.append('<a href="%s?p=%s">下一页</a>' % (self.base_url, self.current_page + 1,))

        if self.current_page>=v:
            pager_list.append('<a href="javascript:void(0);">末页</a>')
        else:
            pager_list.append('<a href="%s?p=%s">末页</a>' % (self.base_url, v,))


        pager = ''.join(pager_list)
        return mark_safe(pager)

# ------------- views.py ---------------
def handle_classes(request):
    if request.method == "GET":
        # for i in range(100):
        #     models.Classes.objects.create(caption='班级'+str(i))

        # 获取当前页
        current_page = request.GET.get('p',1)
        current_page = int(current_page)

        # 所有数据的个数
        total_count = models.Classes.objects.all().count()

        from utils.page import PagerHelper   # 导入自定义page模块
        obj = PagerHelper(total_count, current_page, '/classes.html',10)
        pager = obj.pager_str()

        cls_list = models.Classes.objects.all()[obj.db_start:obj.db_end]

        return render(request,
                      'classes.html',
                      {'cls_list': cls_list, 'str_pager': pager})

    elif request.method == "POST":
        pass
View Code

 


图片/文件上传

需求: 将图片上传并显示到页面

一. 刷新页面以显示图片. form表单上传文件, 注意添加enctype属性.

# ----------------- upload.html --------------------
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form method="POST" action="/upload.html" enctype="multipart/form-data">
        <p><input type="text" name="user" /></p>
        <p><input type="file" name="fafafa" /></p>
        <input type="submit" value="提交" />
    </form>

    <div>
        {% for item in img_list %}
            <img style="height: 200px;width: 200px;" src="/{{ item.path }}" />
        {% endfor %}
    </div>
</body>
</html>
# ----------------- views.py --------------------
import os
def upload(request):
    if request.method == 'GET':
        img_list = models.Img.objects.all()
        return render(request,'upload.html',{'img_list': img_list})
    elif request.method == "POST":
        user = request.POST.get('user')
        obj = request.FILES.get('fafafa') # 前端的form表单记得加enctype="multipart/form-data"

        file_path = os.path.join('static','upload',obj.name)
        print(user,obj.name,obj.size,file_path)

        f = open(file_path, 'wb')
        for chunk in obj.chunks():
            f.write(chunk)
        f.close()

        models.Img.objects.create(path=file_path)
        return redirect('/upload.html')

# ----------------- urls.py --------------------
    re_path('upload.html$', views.upload),
View Code

二. 不刷新页面, 将图片添加到页面, 三种方式

1. 原生Ajax - XMLHttpRequest, 依赖FormData对象. 

# ----------------- upload.html --------------------
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .container img{
            width: 200px;
            height: 200px;
        }
    </style>

    <script>
        function li(arg) {
            console.log(arg);
        }
    </script>
</head>
<body>
    <h1>图片列表</h1>
    <div class="container" id="imgs">
        {% for img in img_list %}
            <img src="/{{ img.path }}">
        {% endfor %}
    </div>

    <input type="file" id="img" />
    <input type="button" value="提交XML" onclick="UploadXML()" />

    <script src="/static/jquery-3.3.1.js"></script>
    <script>
        function UploadXML() {
            var dic = new FormData();  //!!!!
            dic.append('user', 'v1');
            dic.append('fafafa', document.getElementById('img').files[0]);

            var xml = new XMLHttpRequest();
            xml.open('post', '/upload.html', true);
            xml.onreadystatechange = function () {
                if(xml.readyState == 4){
                    var obj = JSON.parse(xml.responseText);
                    if(obj.status){
                        var img = document.createElement('img');
                        img.src = "/" + obj.path;
                        document.getElementById("imgs").appendChild(img);
                    }
                }
            };
            xml.send(dic);
        }
    </script>
</body>
</html>

# ----------------- views.py --------------------
import os,json
def upload(request):
    if request.method == 'GET':
        img_list = models.Img.objects.all()
        return render(request,'upload.html',{'img_list': img_list})
    elif request.method == "POST":
        user = request.POST.get('user')
        obj = request.FILES.get('fafafa') # 前端的form表单记得加enctype="multipart/form-data"

        file_path = os.path.join('static','upload',obj.name)
        print(user,obj.name,obj.size,file_path)

        f = open(file_path, 'wb')
        for chunk in obj.chunks():
            f.write(chunk)
        f.close()

        models.Img.objects.create(path=file_path)

        ret = {'status': True, 'path': file_path}
        return HttpResponse(json.dumps(ret))

# ----------------- urls.py --------------------
    re_path('upload.html$', views.upload),
View Code

2. jQuery Ajax, 依赖FormData对象, 这个对象可以传文件也可以传字符串. 

传输文件需要添加两条属性, 告诉jquery不再做数据的处理.
    contentType:false,
    processData:false,

# ----------------- upload.html --------------------
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .container img{
            width: 200px;
            height: 200px;
        }
    </style>

    <script>
        function li(arg) {
            console.log(arg);
        }
    </script>
</head>
<body>
    <h1>图片列表</h1>
    <div class="container" id="imgs">
        {% for img in img_list %}
            <img src="/{{ img.path }}">
        {% endfor %}
    </div>

    <input type="file" id="img" />
    <input type="button" value="提交jQuery" onclick="Uploadjq()" />
    <script src="/static/jquery-3.3.1.js"></script>
    <script>
        function Uploadjq() {
            var dic = new FormData();   //!!!!!
            dic.append('user', 'v1');
            dic.append('fafafa', document.getElementById('img').files[0]);

            $.ajax({
                url: '/upload.html',
                type: 'POST',
                data: dic,
                processData: false,  // tell jQuery not to process the data
                contentType: false,  // tell jQuery not to set contentType
                dataType: 'JSON',
                success: function (arg) {
                    if (arg.status){
                        var img = document.createElement('img');
                        img.src = "/" + arg.path;
                        $('#imgs').append(img);
                    }
                }
            })
        }
</body>
</html>

# ----------------- views.py --------------------
import os,json
def upload(request):
    if request.method == 'GET':
        img_list = models.Img.objects.all()
        return render(request,'upload.html',{'img_list': img_list})
    elif request.method == "POST":
        user = request.POST.get('user')
        obj = request.FILES.get('fafafa') # 前端的form表单记得加enctype="multipart/form-data"

        file_path = os.path.join('static','upload',obj.name)
        print(user,obj.name,obj.size,file_path)

        f = open(file_path, 'wb')
        for chunk in obj.chunks():
            f.write(chunk)
        f.close()

        models.Img.objects.create(path=file_path)

        ret = {'status': True, 'path': file_path}
        return HttpResponse(json.dumps(ret))

# ----------------- urls.py --------------------
    re_path('upload.html$', views.upload),
View Code

3. iframe + Form表单 (伪Ajax操作, 写复杂, 但兼容性好, 因为早期浏览器不支持FormData这种格式). form表单上传文件, 注意添加enctype属性

# ----------------- upload.html --------------------
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .container img{
            width: 200px;
            height: 200px;
        }
    </style>

    <script>
        function li(arg) {
            console.log(arg);
        }
    </script>
</head>
<body>
    <h1>测试Iframe功能</h1>
    <input type="text" id="url" />
    <input type="button" value="点我" onclick="iframeTest();" />
    <iframe  id="ifr" src=""></iframe>

    <hr/>

    <h1>基于iframe实现form提交</h1>
    <form action="/upload.html" method="post" target="iframe_1" enctype="multipart/form-data">
        <iframe style="display: none"  id="iframe_1" name="iframe_1" src="" onload="loadIframe();"></iframe>
        <input type="text" name="user" />
        <input type="file" name="fafafa" />
        <input type="submit" />
    </form>

    <hr/>

    <h1>图片列表</h1>
    <div class="container" id="imgs">
        {% for img in img_list %}
            <img src="/{{ img.path }}">
        {% endfor %}
    </div>

    <script src="/static/jquery-2.1.4.min.js"></script>
    <script>
        function  iframeTest() {
            var url = $('#url').val();
            $('#ifr').attr('src', url);
        }

        function loadIframe() {
            console.log(1);
            // 获取iframe内部的内容
            var str_json = $('#iframe_1').contents().find('body').text();
            var obj = JSON.parse(str_json);
            if (obj.status){
                var img = document.createElement('img');
                img.src = "/" + obj.path;
                $('#imgs').append(img);
            }
        }
    </script>
</body>
</html>

# ----------------- views.py --------------------
import os,json
def upload(request):
    if request.method == 'GET':
        img_list = models.Img.objects.all()
        return render(request,'upload.html',{'img_list': img_list})
    elif request.method == "POST":
        user = request.POST.get('user')
        obj = request.FILES.get('fafafa') # 前端的form表单记得加enctype="multipart/form-data"

        file_path = os.path.join('static','upload',obj.name)
        print(user,obj.name,obj.size,file_path)

        f = open(file_path, 'wb')
        for chunk in obj.chunks():
            f.write(chunk)
        f.close()

        models.Img.objects.create(path=file_path)

        ret = {'status': True, 'path': file_path}
        return HttpResponse(json.dumps(ret))

# ----------------- urls.py --------------------
    re_path('upload.html$', views.upload),
View Code

以上统一补充一个models.py, 传到数据库的是文件/图片的位置

class Img(models.Model):
    path = models.CharField(max_length=128)

 


信号

Django中提供了"信号调度", 用于在框架执行操作时解耦. 通俗来讲, 就是一些动作发生的时候, 信号允许特定的发送者去提醒一些接受者. 信号就是Django预留的钩子

1. 内置信号

Model signals
    pre_init                    # django的modal执行其构造方法前,自动触发
    post_init                   # django的modal执行其构造方法后,自动触发
    pre_save                    # django的modal对象保存前,自动触发
    post_save                   # django的modal对象保存后,自动触发
    pre_delete                  # django的modal对象删除前,自动触发
    post_delete                 # django的modal对象删除后,自动触发
    m2m_changed                 # django的modal中使用m2m字段操作第三张表(add,remove,clear)前后,自动触发
    class_prepared              # 程序启动时,检测已注册的app中modal类,对于每一个类,自动触发
Management signals
    pre_migrate                 # 执行migrate命令前,自动触发
    post_migrate                # 执行migrate命令后,自动触发
Request/response signals
    request_started             # 请求到来前,自动触发
    request_finished            # 请求结束后,自动触发
    got_request_exception       # 请求异常后,自动触发
Test signals
    setting_changed             # 使用test测试修改配置文件时,自动触发
    template_rendered           # 使用test测试渲染模板时,自动触发
Database Wrappers
    connection_created          # 创建数据库连接时,自动触发

对于Django内置的信号, 仅需注册指定信号, 当程序执行相应操作时, 自动触发注册函数:

# -------------- 在project的__init__.py文件里写 -------------
    from django.core.signals import request_finished
    from django.core.signals import request_started
    from django.core.signals import got_request_exception

    from django.db.models.signals import class_prepared
    from django.db.models.signals import pre_init, post_init
    from django.db.models.signals import pre_save, post_save
    from django.db.models.signals import pre_delete, post_delete
    from django.db.models.signals import m2m_changed
    from django.db.models.signals import pre_migrate, post_migrate

    from django.test.signals import setting_changed
    from django.test.signals import template_rendered

    from django.db.backends.signals import connection_created


    def callback(sender, **kwargs):
        print("post save")
        print(sender,kwargs)
    def callback1(sender, **kwargs):
        print("pre init")
        print(sender,kwargs)
    pre_init.connect(callback)    # 可以是以上任何 xx.connect(callback)
    post_save.connect(callback1)



# -----------------------------------------------------
from django.core.signals import request_finished
from django.dispatch import receiver

@receiver(request_finished)
def my_callback(sender, **kwargs):
    print("Request finished!")
View Code

2. 自定义信号

  a. 定义信号

import django.dispatch
pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"])

  b. 注册信号

def callback(sender, **kwargs):
    print("callback")
    print(sender,kwargs)
 
pizza_done.connect(callback)

  c. 触发函数

from 路径 import pizza_done
 
pizza_done.send(sender='charon',toppings=123, size=456)

 


缓存

由于Django是动态网站, 所以每次请求均会对数据进行相应的操作, 当程序访问量大时, 耗时必然会更加明显, 最简单解决方式是使用: 缓存.  (缓存就是把渲染成功的字符串放在文件中)

缓存将某个views的返回值保存至内存或者memcache中, 5分钟内再有人来访问时, 则不再去执行views中的操作, 而是直接从内存或者Redis中之前缓存的内容拿到, 并返回.

Django中提供了6种缓存方式:

  • 开发调试
  • 内存
  • 文件
  • 数据库
  • Memcache缓存(python-memcached模块)
  • Memcache缓存(pylibmc模块)

1、配置

a、开发调试

# 此为开始调试用,实际内部不做任何操作
    # 配置:
        CACHES = {
            'default': {
                'BACKEND': 'django.core.cache.backends.dummy.DummyCache',     # 引擎
                'TIMEOUT': 300,                                               # 缓存超时时间(默认300,None表示永不过期,0表示立即过期)
                'OPTIONS':{
                    'MAX_ENTRIES': 300,                                       # 最大缓存个数(默认300)
                    'CULL_FREQUENCY': 3,                                      # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
                },
                'KEY_PREFIX': '',                                             # 缓存key的前缀(默认空)
                'VERSION': 1,                                                 # 缓存key的版本(默认1)
                'KEY_FUNCTION' 函数名                                          # 生成key的函数(默认函数会生成为:【前缀:版本:key】)
            }
        }


    # 自定义key
    def default_key_func(key, key_prefix, version):
        """
        Default function to generate keys.

        Constructs the key used by all other methods. By default it prepends
        the `key_prefix'. KEY_FUNCTION can be used to specify an alternate
        function with custom key making behavior.
        """
        return '%s:%s:%s' % (key_prefix, version, key)

    def get_key_func(key_func):
        """
        Function to decide which key function to use.

        Defaults to ``default_key_func``.
        """
        if key_func is not None:
            if callable(key_func):
                return key_func
            else:
                return import_string(key_func)
        return default_key_func
View Code

b、内存

# 此缓存将内容保存至内存的变量中
    # 配置:
        CACHES = {
            'default': {
                'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
                'LOCATION': 'unique-snowflake',
            }
        }

    # 注:其他配置同开发调试版本
View Code

c、文件

# 此缓存将内容保存至文件
    # 配置:

        CACHES = {
            'default': {
                'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
                'LOCATION': '/var/tmp/django_cache',
            }
        }
    # 注:其他配置同开发调试版本
View Code

d、数据库

# 此缓存将内容保存至数据库

    # 配置:
        CACHES = {
            'default': {
                'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
                'LOCATION': 'my_cache_table', # 数据库表
            }
        }

    # 注:执行创建表命令 python manage.py createcachetable
View Code

e、Memcache缓存(python-memcached模块)

# 此缓存使用python-memcached模块连接memcache

    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': 'unix:/tmp/memcached.sock',
        }
    }   

    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
            'LOCATION': [
                '172.19.26.240:11211',
                '172.19.26.242:11211',
                ('172.19.26.244:11211',10),
            ]
        }
    }
View Code

f、Memcache缓存(pylibmc模块)

# 此缓存使用pylibmc模块连接memcache
    
    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
            'LOCATION': '127.0.0.1:11211',
        }
    }

    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
            'LOCATION': '/tmp/memcached.sock',
        }
    }   

    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
            'LOCATION': [
                '172.19.26.240:11211',
                '172.19.26.242:11211',
            ]
        }
    }
View Code

g. Redis缓存(依赖:pip3 install django-redis)

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "CONNECTION_POOL_KWARGS": {"max_connections": 100}
            # "PASSWORD": "密码",
        }
    }
}

# ------------ 视图中连接并操作 --------------
from django_redis import get_redis_connection
conn = get_redis_connection("default")
View Code

 

2、应用

a. 全站使用

使用中间件, 经过一系列的认证等操作, 如果内容在缓存中存在, 则使用FetchFromCacheMiddleware获取内容并返回给用户, 当返回给用户之前, 判断缓存中是否已经存在, 如果不存在则UpdateCacheMiddleware会将缓存保存至缓存, 从而实现全站缓存

    MIDDLEWARE = [
        'django.middleware.cache.UpdateCacheMiddleware',
        # 其他中间件...
        'django.middleware.cache.FetchFromCacheMiddleware',
    ]

    CACHE_MIDDLEWARE_ALIAS = ""
    CACHE_MIDDLEWARE_SECONDS = ""
    CACHE_MIDDLEWARE_KEY_PREFIX = ""
View Code

b. 单独视图缓存

    方式一:
        from django.views.decorators.cache import cache_page

        @cache_page(60 * 15)
        def my_view(request):
            ...

    方式二:
        from django.views.decorators.cache import cache_page

        urlpatterns = [
            url(r'^foo/([0-9]{1,2})/$', cache_page(60 * 15)(my_view)),
        ]
View Code
# ----------- settings.py ---------------
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': os.path.join(BASE_DIR,'cache'),
    }
}
# ----------- views.py ------------------
from django.shortcuts import render,HttpResponse
from django.views.decorators.cache import cache_page

@cache_page(10)   #设置超时时间 默认为300
def cache(request):
    import time
    v = time.time()
    return HttpResponse(v)

# ------------- urls.py -----------------
from app01.views import cache_
urlpatterns = [
    path('cache/', cache_.cache),
]

# 所以127.0.0.1/cache/ 刷新, 在十秒内没有改变
缓存保存在文件

c、局部视图使用

    a. 引入TemplateTag

        {% load cache %}

    b. 使用缓存

        {% cache 5000 缓存key %}
            缓存内容
        {% endcache %}
View Code
# ----------- settings.py ---------------
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': os.path.join(BASE_DIR,'cache'),
    }
}
# ----------- views.py ------------------
from django.shortcuts import render,HttpResponse
from django.views.decorators.cache import cache_page

def part(request):
    import time
    v = time.time()
    return render(request,'part.html',{'v':v})

# ------------- part.html -----------------
{% load cache %}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>{{ v }}</h1>

    {% cache 6 kkk %}     # 因为有kkk指定, 所以缓存会保存在同一个文件
    <h3>{{ v }}</h3>
    {% endcache %}
</body>
</html>

# ------------- urls.py -----------------
from app01.views import cache_
urlpatterns = [
    path('part/', cache_.part),
]

# 所以127.0.0.1/part/ 刷新, 在十秒内没有改变
缓存保存在文件

 


Django的Form组件

原生Form是前端中的表单.

原生Form的实例和缺陷:

#*****************************models.py#*****************************
from django.db import models

class Info(models.Model):

    username = models.CharField(max_length=64)
    telephone = models.CharField(max_length=64)
    email=models.CharField(max_length=64,default='')
    password=models.CharField(max_length=64,default='')
    
#*****************************url.py#*****************************
from django.contrib import admin
from django.urls import path,
from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('register/', views.register,name='register'),

]

#*****************************views.py#*****************************
from django.shortcuts import render,HttpResponse
from app01.models import *

def register(req):
    if req.method=='POST':
        # 问题1:每一字段都要单独取,单独设置

        user=req.POST.get('user')
        phone=req.POST.get('phone')
        email=req.POST.get('email')
        pwd=req.POST.get('pwd')

        obj=Info.objects.filter(username=user,
                                telephone=phone,
                                password=pwd,

                                email=email,)
        # 问题2 所有的输入是否正确,完全需要自己从头写
        error_obj=""
        if len(user)==0:
            error_obj="用户名不能为空!"

        if not (obj or error_obj) :

            Info.objects.create(username=user,
                                telephone=phone,
                                password=pwd,
                                email=email,
                                )

            return HttpResponse('添加成功!')

        else:
            # 问题3: 页面不断刷新,导致已写对的字段信息也没了,需要重写(ajax不需要)
            return render(req,'register.html',locals())

    return render(req,'register.html')

#*****************************register.html#*****************************
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>添加个人信息</title>

</head>
<body>
     <h1>注册个人信息</h1>

     <form action="{% url 'register' %}" method="post">

           姓名 <input type="text" name="user"> <span class="user">{{ error_obj }}</span><br>
           手机 <input type="text" name="phone"><span class="phone"></span><br>
           邮箱 <input type="text" name="email"><span class="email"></span><br>
           密码 <input type="text" name="pwd">  <span class="pwd"></span><br>

           <input type="submit" value="提交"><br>

          {% csrf_token %}
     </form>
</body>
</html>
View Code

 

Django的Form中, 用于验证用户请求数据合法性的一个组件. 所有的Form类都作为django.forms.Form的子类创建, 包括ModelForm

Django的表单系统主要分两种:

  • 基于django.forms.Form:所有表单类的父类
  • 基于django.forms.ModelForm:可以和模型类绑定的Form

Model & Form & ModelForm
Model:

  用于数据库操作
Form:

  用于用户请求的验证
ModelForm:
  用于数据库操作(部分)
  用于用户请求的验证(部分)

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

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

实现步骤:

  • 1、创建模版 class LoginForm(forms.Form): ...
  • 2、将请求交给模版,创建一个对象 obj = LoginForm(request.POST)
  • 3、进行验证 obj.is_valid()
  • 4、获取正确信息 obj.clean()
  • 5、获取错误信息 obj.errors
  • 6、提交(Form提交 - errors.字段.0, Ajax提交- errors.as_json(), errors.as_data()) 

示例

# -------------- views.py -----------------将form写在views.py不规范, 应再创建另一个.py文件来专门放form
from django import forms
from django.shortcuts import render,redirect,HttpResponse

class LoginForm(forms.Form):
    user = forms.CharField(min_length=6,error_messages={'required':'Username Cannot Be Empty','min_length':'Username cannot less than 6 characters'})
    email = forms.EmailField(error_messages={'required':'Email Cannot Be Empty','invalid':'Incorrect Format'})

def login(request):
    if request.method=='GET':
        loginform_obj = LoginForm()   #第一次访问时没有数据, 会自动生成html标签 (没有参数)
        return render(request,'login.html',{'loginform_obj':loginform_obj})
    elif request.method=='POST':
        loginform_obj = LoginForm(request.POST)
        if loginform_obj.is_valid():
            value_dict = loginform_obj.clean()
            print(value_dict)
        else:
            pass
            # error_obj = obj.errors   # 封装了所有错误信息
            # error_obj = obj.errors.as_json()  #默认为.as_ul()
            # print(type(error_obj),error_obj)
            # from django.forms.utils import ErrorDict
            # print(error_obj['email'][0])
            # print(error_obj['user'][0])
        return render(request,'login.html',{'loginform_obj':loginform_obj})

# ---------------- login.html --------------
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>FORM submit data</h1>
    <form action="/login.html" method="post">
        {% csrf_token %}
        <p>
            {{ loginform_obj.user }}
            <span>{{ loginform_obj.errors.user.0 }}</span>
        </p>
        <p>
            {{ loginform_obj.email }}
            <span>{{ loginform_obj.errors.email.0 }}</span>
        </p>
        <p><input type="password" placeholder="password" name="pwd"></p>
        <input type="submit" value="submit">
    </form>
</body>
</html>
form提交
# --------------- views.py ----------------------
from django import forms
from django.shortcuts import render,redirect,HttpResponse

class LoginForm(forms.Form):
    user = forms.CharField(min_length=6,error_messages={'required':'Username Cannot Be Empty','min_length':'Username cannot less than 6 characters'})
    email = forms.EmailField(error_messages={'required':'Email Cannot Be Empty','invalid':'Incorrect Format'})

import json
def login_ajax(request):
    if request.method == "GET":
        return render(request, 'login_ajax.html')
    elif request.method == "POST":
        ret = {'status': True, 'error':None, 'data': None}
        obj = LoginForm(request.POST)  # 注意参数
        if obj.is_valid():
            print(obj.clean())
        else:
            # 方式一
            res_str = obj.errors.as_json() # res_str是一个字符串, 封装了所有错误信息
            ret['status'] = False
            ret['error'] = res_str
            # 两次反序列化
        return HttpResponse(json.dumps(ret))

            # 方式二:
            # ret['status'] = False
            # ret['error'] = obj.errors.as_data()  # {'user': [ValidationError(['Username cannot less than 6 characters'])], 'email': [ValidationError(['Incorrect Format'])]}
            # # 一次反序列化
        # return HttpResponse(json.dumps(ret,cls=JsonCustomEncoder))

from django.core.validators import ValidationError
class JsonCustomEncoder(json.JSONEncoder):
    def default(self, field):
        if isinstance(field, ValidationError):
            return {'code': field.code, 'message': field.message}
        else:
            return json.JSONEncoder.default(self, field)

# -------------- login_ajax.html ------------------
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .error-msg{
            color: red;
            font-size: 12px;
        }
    </style>
</head>
<body>
    <h1>Form提交数据</h1>
    <form id="f1">
    <p>
        <input id="u" type="text" name="user" placeholder="用户名" />

    </p>
    <p>
        <input id="e" type="text" name="email" placeholder="邮箱" />
    </p>
    <p>
        <input id="p" type="text" name="pwd" placeholder="密码" />
    </p>
    <input id="ajax_submit" type="button" value="Ajax提交"  />
        </form>
    <script src="/static/jquery-2.1.4.min.js"></script>
    <script>
        $(function () {
            $('#ajax_submit').click(function () {
                $.ajax({
                    url:"/login_ajax.html",
                    //data: {user: $('#u').val(), email: $('#e').val(), pwd: $('#p').val()},
                    data: $('#f1').serialize(),
                    type: 'POST',
                    success:function (arg) {
                        $('.error-msg').remove();
                        var v1 = JSON.parse(arg);
                        console.log(v1);
                        if(!v1.status){
                            var error_obj = JSON.parse(v1.error);   //对应方式一
                            //var error_obj = v1.error;  //对应方式二

                            {#console.log(111,error_obj['user'][0].message);#}
                            $.each(error_obj,function (k,v) {
                                // k: user 或 email
                                // v: [{}{}{},]
                                {#console.log(k,'------',v);#}
                                var tag = document.createElement('span');
                                tag.className = 'error-msg';
                                tag.innerHTML = v[0].message;
                                $("input[name='" + k + "']").after(tag);
                            })
                        }else{
                            location.href = "/index.html"
                        }
                    }
                })
            })
        })
    </script>
</body>
</html>
ajax提交

初始化标签

在Web应用程序中开发编写功能时, 常用到获取数据库中的数据并将值初始化在HTML中的标签上

# 1、Form
from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.validators import RegexValidator

class MyForm(Form):
    user = fields.CharField()
 
    city = fields.ChoiceField(
        choices=((1, '上海'), (2, '北京'),),
        widget=widgets.Select
    )

# 2、Views
from django.shortcuts import render, redirect
from .forms import MyForm

def index(request):
    if request.method == "GET":
        values = {'user': 'root', 'city': 2}
        obj = MyForm(values)
 
        return render(request, 'index.html', {'form': obj})
    elif request.method == "POST":
        return redirect('http://www.google.com')
    else:
        return redirect('http://www.google.com')

# 3、HTML
# 方式一
<form method="POST" enctype="multipart/form-data">
    {% csrf_token %}
    <p>{{ form.user }} {{ form.user.errors }}</p>
    <p>{{ form.city }} {{ form.city.errors }}</p>
    <input type="submit"/>
</form>
# 方式二
<form method="POST" enctype="multipart/form-data">
    {% csrf_token %}
    {{ form.as_p }}      # 还有 as_ul, as_table (记得加<table>标签)
    <input type="submit"/>
</form>

 

Form类

创建Form类时, 主要涉及到 字段 和 部件, 字段用于对用户请求数据的验证, 部件用于自动生成HTML标签.

字段和其参数:

Field
    required=True,               是否允许为空
    widget=None,                 HTML插件
    initial=None,                初始值
    help_text='',                帮助信息(在标签旁边显示)
    error_messages=None,         错误信息 {'required': '不能为空', 'invalid': '格式错误'}
    show_hidden_initial=False,   是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直)
    validators=[],               自定义验证规则
    localize=False,              是否支持本地化
    disabled=False,              是否可以编辑
    label=None,                  用于生成Label标签或显示内容
    label_suffix=None            Label内容后缀
    id_for_label 
    label_tag

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类型
    ...
View Code
UUID是根据MAC以及当前时间等创建的不重复的随机字符串

>>> import uuid

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

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

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

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

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

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

    # get the raw 16 bytes of the UUID
    >>> x.bytes
    b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f'

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

  常用字段:

            CharField     
            IntegerField            
            IP,Email,URL,Slug         
            ChoiceField = CharField + widget=widgets.Select
            MultipleChoiceField            
            RegexField

  常用参数(实例):

# ------------ form.py ----------------
from django import forms
from django.forms import fields
from django.forms import widgets
from django.core.validators import RegexValidator

class FieldForm(forms.Form):
    f1 = fields.CharField(
        max_length=6,
        required=True,
        initial="charon",
        validators=[RegexValidator(r'^[0-9]+$', '必须数字',code='f1'), RegexValidator(r'^159[0-9]+$', '必须159开头',code='f2')],
        error_messages={'required': '不能为空','f1': '不是数字','f2': '159开头','max_length': '过长'},
    )  
    #error_messages优先过validators里的错误信息

    f2 = fields.CharField(
        widget=widgets.TextInput(attrs={'id': 'i1', 'class': 'c1'})
    )
 
    f3 = fields.CharField(
        initial=2,
        widget=widgets.Select(choices=((1,'上海'),(2,'北京'),))
    )
 
    pwd = fields.CharField(
        widget=widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True)

# ----------------- views.py -------------------
def field(request):
    from app01 import form
    if request.method == 'GET':
        obj = form.FieldForm()
        return render(request, 'field.html', {'obj': obj})
    elif request.method == "POST":
        obj = form.FieldForm(request.POST,request.FILES)
        obj.is_valid()
        print(obj.clean())
        print(obj.errors.as_json())
        return render(request, 'field.html', {'obj': obj})

# --------------- field.html ------------------
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form method="POST" action="/field.html" enctype="multipart/form-data">
        {{ obj.f1 }}
        {{ obj.f2 }}    
        {{ obj.f3 }}
        <input type="submit" value="提交" />
    </form>
</body>
</html>

 

部件:

widget = 
    TextInput(Input)
    NumberInput(TextInput)
    EmailInput(TextInput)
    URLInput(TextInput)
    PasswordInput(TextInput)
    HiddenInput(TextInput)
    Textarea(Widget)
    DateInput(DateTimeBaseInput)
    DateTimeInput(DateTimeBaseInput)
    TimeInput(DateTimeBaseInput)
    CheckboxInput
    Select
    NullBooleanSelect
    SelectMultiple
    RadioSelect
    CheckboxSelectMultiple
    FileInput
    ClearableFileInput
    MultipleHiddenInput
    SplitDateTimeWidget
    SplitHiddenDateTimeWidget
    SelectDateWidget
View Code

  常用选择部件:

# 单radio,值为字符串
user = fields.CharField(
    initial=2,
    widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),))
)
 
# 单radio,值为字符串
user = fields.ChoiceField(
   choices=((1, '上海'), (2, '北京'),),
   initial=2,
   widget=widgets.RadioSelect
)
 
# 单select,值为字符串
user = fields.CharField(
    initial=2,
    widget=widgets.Select(choices=((1,'上海'),(2,'北京'),))
)
 
# 单select,值为字符串
user = fields.ChoiceField(
    choices=((1, '上海'), (2, '北京'),),
    initial=2,
    widget=widgets.Select
)
 
# 多选select,值为列表
user = fields.MultipleChoiceField(
    choices=((1,'上海'),(2,'北京'),),
    initial=[1,],
    widget=widgets.SelectMultiple
)
 
 
# 单checkbox
user = fields.CharField(
    widget=widgets.CheckboxInput()
)
 
 
# 多选checkbox,值为列表
user = fields.MultipleChoiceField(
    initial=[2, ],
    choices=((1, '上海'), (2, '北京'),),
    widget=widgets.CheckboxSelectMultiple
)

实时显示新插入的数据

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

方式一

from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.validators import RegexValidator
 
class MyForm(Form):
    user = fields.ChoiceField(
        # choices=((1, '上海'), (2, '北京'),),
        initial=2,
        widget=widgets.Select
    )
 
    def __init__(self, *args, **kwargs):
        super(MyForm,self).__init__(*args, **kwargs)
        # self.fields['user'].widget.choices = ((1, '上海'), (2, '北京'),)
        #
        self.fields['user'].widget.choices = models.Classes.objects.all().value_list('id','caption')
# ----------------- models.py ----------------
from django.db import models

# Create your models here.

class UserInfo(models.Model):
    name = models.CharField(max_length=32)
    ut = models.ForeignKey('UserType', on_delete=models.CASCADE)

class UserType(models.Model):
    caption = models.CharField(max_length=32)

# ----------------- form.py ----------------
class InitialForm(forms.Form):
    username = fields.CharField()
    user_type = fields.IntegerField(
        widget=widgets.Select(choices=[])
    )

    def __init__(self,*args, **kwargs):
        # 执行父类的构造方法
        super(InitialForm,self).__init__(*args, **kwargs)
        self.fields['user_type'].widget.choices = models.UserType.objects.all().values_list('id','caption')

# ----------------- views.py ----------------
def initial(request):
    from app01 import form
    from app01 import models
    if request.method == 'GET':
        nid = request.GET.get('nid',1)
        print(nid)
        m = models.UserInfo.objects.filter(id=nid).first()
        dic = {'username': m.name, 'user_type': m.ut_id, 'favor': [1,2,3]}
        obj = form.InitialForm(dic)
        return render(request, 'initial.html', {'obj': obj,'nid':m.ut_id})

# ----------------- initial.html ----------------
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <a href="/initial.html?nid={{ nid }}">刷新</a>
    {{ obj.username }}
    {{ obj.user_type }}
</body>
</html>
View Code

方式二 - 使用django提供的ModelChoiceField和ModelMultipleChoiceField字段来实现

from django import forms
from django.forms import fields
from django.forms import widgets
from django.forms import models as form_modelclass FInfo(forms.Form):
    authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all())
    # authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all())
from django.shortcuts import render

# Create your views here.
from django import forms
from django.forms import fields
from django.forms import models as models_fields
from django.core.exceptions import ValidationError
from app01 import models


class UserForm(forms.Form):
    username = fields.CharField(label='用户名')
    email = fields.CharField(label='邮箱')

    user_type1 = fields.ChoiceField(choices=models.UserType.objects.values_list('id','name'))

    user_type2 = models_fields.ModelChoiceField(queryset=models.UserType.objects.all(),
                                                empty_label='请选择用户类型',
                                                to_field_name="id",
                                                limit_choices_to={'id':1})

    user_type3 = models_fields.ModelMultipleChoiceField(queryset=models.UserType.objects.all(),
                                                to_field_name="id",
                                                limit_choices_to={'id': 1})

# ------------ views.py ---------------
def index(request):
    if request.method == "GET":
        obj = UserForm()
        return render(request,'f.html',{'obj': obj})
    elif request.method == "POST":
        obj = UserForm(request.POST)
        obj.is_valid()
        # data = obj.clean()
        # obj.cleaned_data
        # print(obj.errors)
        return render(request, 'f.html', {'obj': obj})

# ------------ f.html ---------------
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/f/" method="post">
        {{ obj.as_p }}
        <input type="submit" value="提交">
    </form>
</body>
</html>
View Code

自定义验证规则

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


# 方式二:
import re
from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.exceptions import ValidationError
 
 
# 自定义验证规则
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 PublishForm(Form):
 
 
    title = fields.CharField(max_length=20,
                            min_length=5,
                            error_messages={'required': '标题不能为空',
                                            'min_length': '标题最少为5个字符',
                                            'max_length': '标题最多为20个字符'},
                            widget=widgets.TextInput(attrs={'class': "form-control",
                                                          'placeholder': '标题5-20个字符'}))
 
 
    # 使用自定义验证规则
    phone = fields.CharField(validators=[mobile_validate, ],
                            error_messages={'required': '手机不能为空'},
                            widget=widgets.TextInput(attrs={'class': "form-control",
                                                          'placeholder': u'手机号码'}))
 
    email = fields.EmailField(required=False,
                            error_messages={'required': u'邮箱不能为空','invalid': u'邮箱格式错误'},
                            widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'邮箱'}))


# 方法三:自定义方法
from django import forms
    from django.forms import fields
    from django.forms import widgets
    from django.core.exceptions import ValidationError
    from django.core.validators import RegexValidator
 
    class FInfo(forms.Form):
        username = fields.CharField(max_length=5,
                                    validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid extension.', 'invalid')], )
        email = fields.EmailField()
 
        def clean_username(self):
            """
            Form中字段中定义的格式匹配完之后,执行此方法进行验证
            :return:
            """
            value = self.cleaned_data['username']
            if "666" in value:
                raise ValidationError('666已经被玩烂了...', 'invalid')
            return value


# 方式四:同时生成多个标签进行验证
from django.forms import Form
from django.forms import widgets
from django.forms import fields
 
from django.core.validators import RegexValidator
 
############## 自定义字段 ##############
class PhoneField(fields.MultiValueField):
    def __init__(self, *args, **kwargs):
        # Define one message for all fields.
        error_messages = {
            'incomplete': 'Enter a country calling code and a phone number.',
        }
        # Or define a different message for each field.
        f = (
            fields.CharField(
                error_messages={'incomplete': 'Enter a country calling code.'},
                validators=[
                    RegexValidator(r'^[0-9]+$', 'Enter a valid country calling code.'),
                ],
            ),
            fields.CharField(
                error_messages={'incomplete': 'Enter a phone number.'},
                validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid phone number.')],
            ),
            fields.CharField(
                validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid extension.')],
                required=False,
            ),
        )
        super(PhoneField, self).__init__(error_messages=error_messages, fields=f, require_all_fields=False, *args,
                                         **kwargs)
 
    def compress(self, data_list):
        """
        当用户验证都通过后,该值返回给用户
        :param data_list:
        :return:
        """
        return data_list
 
############## 自定义部件 ##############
class SplitPhoneWidget(widgets.MultiWidget):
    def __init__(self):
        ws = (
            widgets.TextInput(),
            widgets.TextInput(),
            widgets.TextInput(),
        )
        super(SplitPhoneWidget, self).__init__(ws)
 
    def decompress(self, value):
        """
        处理初始值,当初始值initial不是列表时,调用该方法
        :param value:
        :return:
        """
        if value:
            return value.split(',')
        return [None, None, None]
View Code

Form验证进阶

通过覆盖is_vaild()中的方法来增强验证

1. is_valid() (循环Form对象中所有字段) -> 2.full_clean() ->
                          -> 3.1 每个字段的正则, 每个字段的方法 clean_字段名()
                          -> 3.2 _clean_form -> clean(钩子)
                          -> 3.3 _post_clean(钩子)
===》form的最终产物
返回值is_valid()
cleaned_data
errors

# form和views.py
from django.shortcuts import render
from django import forms
from django.forms import fields
from django.core.exceptions import ValidationError
from app01 import models

class UserForm(forms.Form):
    username = fields.CharField(label='用户名')
    email = fields.CharField(label='邮箱')

    # Way1
    # def clean_username(self):
    #     #
    #     value = self.cleaned_data['username']
    #     if value == 'root':
    #         return value
    #     else:
    #         raise ValidationError('用户名错误')

    # Way2
    def clean(self):
        v1 = self.cleaned_data['username']
        v2 = self.cleaned_data['email']
        if v1 == "root" and v2 == "root@live.com":
            pass
        else:
            raise ValidationError('用户名或邮箱错误!!!')

        return self.cleaned_data

    # Way3 - 不允许抛出异常, 所以要自己添加
    # def _post_clean(self):
    #     v1 = self.cleaned_data['username']
    #     v2 = self.cleaned_data['email']
    #     if v1 == "root" and v2 == "root@live.com":
    #         pass
    #     else:
    #         self.add_error("__all__", ValidationError('用户名或邮箱错误...'))#__all__代表所有字段

def index(request):
    if request.method == "GET":
        obj = UserForm()
        return render(request,'f.html',{'obj': obj})
    elif request.method == "POST":
        obj = UserForm(request.POST)
        obj.is_valid()
        # data = obj.clean()
        # obj.cleaned_data
        # print(obj.errors)
        return render(request, 'f.html', {'obj': obj})

# f.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/f/" method="post">
        {{ obj.as_p }}
        <input type="submit" value="提交">
    </form>
</body>
</html>

源码

is_valid() -> errors() -> full_clean() -> _clean_field(), _clean_form(), _post_clean()

 


Django的ModelForm组件

Form:

               UserForm -> Form -> BaseForm
ModelForm:
UserModelForm -> ModelForm -> BaseModelForm -> BaseForm

功能:1. 增加数据  2. 修改数据

Model + Form ==> ModelForm. model和form的结合体, 所以有以下功能:

  • 验证
  • 数据库操作

model有操作数据库的字段, form验证也有相同字段, 虽然耦合度降低, 但是代码是有重复的. 如果利用model里的字段, 那form里的字段就不用重写了. ModelForm使用非常方便, 比如增加修改之类的操作. 但是也带来额外不好的地方, model和model之间耦合了. 如果不耦合的话, save()方法也无法直接提交保存. 但是耦合的话使用场景通常局限用于小程序, 写大程序就最好不用了.

1. 定义ModelForm类

from django import forms
from app01 import models
 
class UserModelForm(forms.ModelForm):
      class Meta:
            model,                           # 对应Model的
            fields=None,                     # 字段  fileds='__all__'   "__all__"表示所有字段
            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',) # 本地化,如:根据不同时区显示数据
            如:
                数据库中
                    2018-04-21 04:10:57
                setting中的配置
                    TIME_ZONE = 'Asia/Shanghai'
                    USE_TZ = True
                则显示:
                    2018-04-21 12:10:57

2. ModelForm验证执行的过程

Form所有的钩子ModelForm都有

is_valid() --> self.errors --> full_clean() --> self._clean_fields() --> 1. clean_字段名(自定义方法)

                                2. self._clean_form() -->  clean(self) 
                                3. self._post_clean() (整体错误)

  字典字段验证: clean_字段名 (自定义方法) 可以抛出异常

def clean_username(self):
    value = self.cleaned_data['username']
    if value == 'root':
        return value
    else:
        raise ValidationError("Not allow to enter")

3. 验证

obj = UserModelForm()
obj.is_valid()
obj.errors.as_json()
obj.clean()
obj.cleaned_data

4. 创建

user_obj = models.User.objects.get(id=nid)
model_form_obj = UserModelForm(instance=user_obj)
model_form_obj = UserModelForm(request.POST)
model_form_obj.is_valid()
model_form_obj.save()    #默认save(commit=True), 默认保存多对多, 当commit=False时可以拆分保存操作
  #可拆分为:
    obj = obj.save(commit=False)
    obj.save()    #保存请求表数据(单表信息)
    model_form_obj.save_m2m()  #保存关联多对多数据

5. 更新和初始化

# 更新
obj = model.tb.objects.get(id=1)
model_form_obj = XXOOModelForm(request.POST,instance=obj)  # 要是没有instance=obj就变成创建了!!
if model_form_obj.is_valid():
  model_form_obj.save()
# 初始化 model_form_obj = XXOOModelForm(initial={...})

例:

# ------ views.py --------
from django.shortcuts import render
from django import forms
from app01 import models
from django.forms import widgets as ws

# db更改, select自动更新
class UserModelForm(forms.ModelForm):
    # email = forms.CharField()  # 重名, 只用作页面显示跟用户验证, 跟保存数据库没关系
    pwd = forms.CharField()  # 不重名, 只用作页面显示跟额外操作, 跟保存数据库没关系
    class Meta:
        model = models.User
        fields = "__all__"
        # fields = ['name']
        # exclude = ['name']
        labels = {
            'email':'邮箱'
        }
        help_texts={
            'email':'*'
        }
        widgets = {
            'name': ws.Textarea(attrs={'class':'c1'})
        }
        error_messages = {
            'name':{'required':'必填','invalid':'格式错误'}
        }
        field_classes = {
            'name':forms.EmailField   #做二次验证
        }
    def clean_email(self):
        # ...
        return 1
    def clean(self):
        return self.cleaned_data

    def _post_clean(self):
        from django.core.exceptions import ValidationError
        # try
        # self.add_error(None,ValidationError('efrgge',code='f'))
        pass

def index(request):
    if request.method == 'GET':
        obj = UserModelForm()
        return render(request,'mf.html',{'obj':obj})
    elif request.method == 'POST':
        obj = UserModelForm(request.POST,request.FILES)  # 如果有文件上传的话
        if obj.is_valid():
            # print(obj.cleaned_data)
            # models.User.objects.create(**obj.cleaned_data)
            obj.save()  # 在ModelForm用一个save就可以增加数据
            '''
            obj.save(commit=True)
            相当于
            mobj = obj.save(commit=False)
            mobj.save()
            obj.save_m2m()
            '''
        print(obj.errors)
        return render(request,'mf.html',{'obj':obj})

def edit_index(request,nid):
    if request.method == "GET":
        model_obj = models.User.objects.get(id=nid)
        obj =UserModelForm(instance=model_obj)
        return render(request, 'mf1.html', {'obj': obj,'nid': nid})
    elif request.method == 'POST':
        model_obj = models.User.objects.get(id=nid)

        obj = UserModelForm(request.POST, instance=model_obj)
        if obj.is_valid():
            obj.save()
        return render(request, 'mf1.html', {'obj': obj})

# ------ urls.py --------
urlpatterns = [
    re_path('edit-mf-(\d+)/', mf.edit_index),
]

# ------ edit-mf-1.html --------
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/edit-mf-{{nid}}/" method="POST">
        {{ obj.as_p }}
        <input type="submit" value="提交" />
    </form>
</body>
</html>
View Code

 

 

 

 

 

 
posted @ 2018-04-04 20:59  Charonnnnn  阅读(289)  评论(0)    收藏  举报