[Python自学] day-22 (2) (缓存、信号、Form组件)

一、缓存

Django为我们提供了5种缓存机制:

1)开发调试用的缓存(什么都不干)

2)内存缓存

3)文件缓存

4)数据库缓存

5)memcache缓存

1.通用配置

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': 'func_name'  # 生成key的函数(默认函数会生成为:【前缀:版本:key】,默认函数名为 default_key_func)
    }
}

2.开发调试缓存

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.dummy.DummyCache',  # 开发调试缓存 引擎

        #通用配置放这里
    }
}

3.内存缓存

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',  # 内存缓存引擎
        'LOCATION': 'unique-snowflake',  # 保证命名唯一,因为内存缓存就是一个全局变量

        #通用配置放这里
    }
}

4.文件缓存

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',  # 文件缓存引擎
        'LOCATION': os.path.join(BASE_DIR,'cache'),  # 文件存放位置
        
        #通用配置放这里
    }
}

5.数据库缓存

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.db.DatabaseCache',  #数据库缓存引擎
        'LOCATION': 'my_cache_table',  # 使用的数据库表

        #通用配置放这里
    }
}

使用数据库缓存之前,要执行命令先创建一张缓存表:

python manage.py createcachetable

6.Memcache缓存

# 使用单台memcache
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': '127.0.0.1:11211',
    }
}
# 使用本地memcache文件
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.240:11211',5),
            '172.19.26.242:11211',                     #  ('172.19.26.242:11211',15),
        ]
    }
}

memcache的引擎除了有MemcachedCache(这个使用的是python-memcached模块),还可以使用PyLibMCCache引擎(这个使用的是pylibmc模块)。

二、缓存的三种应用

Django提供了以上所述的几种缓存,但是缓存到底缓存什么东西?

1.缓存视图函数(页面缓存)

对视图函数的结果进行一定时间的缓存,当在这个时间段内,用户再次请求这个页面,Django会直接从缓存中获取数据并返回。

例如我们写一个简单页面,显示当前时间:

html页面:

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

视图函数

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

urls.py

from django.contrib import admin
from django.urls import path, re_path, include
from mgmt import views

urlpatterns = [
    path('admin/', admin.site.urls),
    re_path('login', views.login),
    re_path('index', views.index),
    re_path('logout', views.logout),
    re_path('cache', views.cache),
]

未使用缓存时的效果:

我们每次请求该页面,时间都会发生变化。

当我们为视图函数加上装饰器后

from django.views.decorators.cache import cache_page


@cache_page(10)
def cache(request):
    import time
    ctime = time.time()
    return render(request, 'cache.html', {'ctime': ctime})

在加上这个装饰器后,表示对该视图函数的返回结果进行缓存,缓存时间为10s。

使用缓存的效果:

首次请求后,返回的结果会缓存10秒,10秒内再次访问页面的话,时间不会变化。10秒过去后,再请求,时间会重新生成,然后又放入缓存。如此反复。

如果视图函数有参数(例如分页),则Django会分别做缓存

from django.views.decorators.cache import cache_page


@cache_page(10)
def cache(request,page_num):
    import time
    ctime = time.time()
    return render(request, 'cache.html', {'page_num':page_num,'ctime': ctime})

效果:

 这两页面的数据分别缓存10s,互不影响。

2.局部缓存(细粒度缓存)

局部缓存就是指在一个页面中,某些部分的数据使用缓存中的数据,而其他数据由视图函数生成。

主要原理是在render的时候,对模板中模板语言标记的替换部分进行数据替换时,对指定使用缓存的部分用缓存数据进行替换,而不是用视图函数获取的新数据。 

例如。使用视图函数动态生成的当前时间替换模板中的多个位置

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>CTime</title>
</head>
<body>
    <h1>{{ page_num }}</h1>
    <h2>{{ ctime }}</h2>
    <h2>{{ ctime }}</h2>
    <h2>{{ ctime }}</h2>
</body>
</html>

从上述HTML代码中可以看出,我们需要用ctime(当前时间)替换三个部分。

如果使用页面缓存(即@cache_page装饰器),则页面上的三个时间会同步缓存和同步更新。

使用局部缓存:

{% load cache %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>CTime</title>
</head>
<body>
    <h1>{{ page_num }}</h1>
    <h2>{{ ctime }}</h2>
    <h2>{{ ctime }}</h2>
    {% cache 10 c1 %}
        <h2>{{ ctime }}</h2>
    {% endcache %}
</body>
</html>

{% calue 10 c1 %}中,10表示缓存10秒,c1表示cache_id,会和 KEY_PREFIX以及VERSION拼接生成真正的key。

注意,同时使用局部缓存的时候,要注意和页面缓存之间的影响。

3.全站缓存(缓存所有数据)

全站缓存是对用户所请求的所有数据进行缓存,既然是对全部的response数据进行缓存,则直接使用中间件处理就可以了。

在settings.py中添加两个中间件配置:

MIDDLEWARE = [
    # 全站缓存
    'django.middleware.cache.UpdateCacheMiddleware',
    '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',
    # 全站缓存
    'django.middleware.cache.FetchFromCacheMiddleware',
]

这两个中间件分别对response进行缓存存储和获取,如下图所示:

查看UpdateCacheMiddleware的源码:

class UpdateCacheMiddleware(MiddlewareMixin):
    """
    Response-phase cache middleware that updates the cache if the response is
    cacheable.

    Must be used as part of the two-part update/fetch cache middleware.
    UpdateCacheMiddleware must be the first piece of middleware in MIDDLEWARE
    so that it'll get called last during the response phase.
    """
    def __init__(self, get_response=None):
        self.cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
        self.key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
        self.cache_alias = settings.CACHE_MIDDLEWARE_ALIAS
        self.cache = caches[self.cache_alias]
        self.get_response = get_response

    def _should_update_cache(self, request, response):
        return hasattr(request, '_cache_update_cache') and request._cache_update_cache

    def process_response(self, request, response):
        """Set the cache, if needed."""
        if not self._should_update_cache(request, response):
            # We don't need to update the cache, just return.
            return response

        if response.streaming or response.status_code not in (200, 304):
            return response

        # Don't cache responses that set a user-specific (and maybe security
        # sensitive) cookie in response to a cookie-less request.
        if not request.COOKIES and response.cookies and has_vary_header(response, 'Cookie'):
            return response

        # Don't cache a response with 'Cache-Control: private'
        if 'private' in response.get('Cache-Control', ()):
            return response

        # Try to get the timeout from the "max-age" section of the "Cache-
        # Control" header before reverting to using the default cache_timeout
        # length.
        timeout = get_max_age(response)
        if timeout is None:
            timeout = self.cache_timeout
        elif timeout == 0:
            # max-age was set to 0, don't bother caching.
            return response
        patch_response_headers(response, timeout)
        if timeout and response.status_code == 200:
            cache_key = learn_cache_key(request, response, timeout, self.key_prefix, cache=self.cache)
            if hasattr(response, 'render') and callable(response.render):
                response.add_post_render_callback(
                    lambda r: self.cache.set(cache_key, r, timeout)
                )
            else:
                self.cache.set(cache_key, response, timeout)
        return response
View Code

可以看到,UpdateCacheMiddleware的源码中只有process_response函数,该函数判断response是否有缓存,没有则缓存,有的话就直接返回给用户。

查看FetchFromCacheMiddleware的源码:

class FetchFromCacheMiddleware(MiddlewareMixin):
    """
    Request-phase cache middleware that fetches a page from the cache.

    Must be used as part of the two-part update/fetch cache middleware.
    FetchFromCacheMiddleware must be the last piece of middleware in MIDDLEWARE
    so that it'll get called last during the request phase.
    """
    def __init__(self, get_response=None):
        self.key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
        self.cache_alias = settings.CACHE_MIDDLEWARE_ALIAS
        self.cache = caches[self.cache_alias]
        self.get_response = get_response

    def process_request(self, request):
        """
        Check whether the page is already cached and return the cached
        version if available.
        """
        if request.method not in ('GET', 'HEAD'):
            request._cache_update_cache = False
            return None  # Don't bother checking the cache.

        # try and get the cached GET response
        cache_key = get_cache_key(request, self.key_prefix, 'GET', cache=self.cache)
        if cache_key is None:
            request._cache_update_cache = True
            return None  # No cache information available, need to rebuild.
        response = self.cache.get(cache_key)
        # if it wasn't found and we are looking for a HEAD, try looking just for that
        if response is None and request.method == 'HEAD':
            cache_key = get_cache_key(request, self.key_prefix, 'HEAD', cache=self.cache)
            response = self.cache.get(cache_key)

        if response is None:
            request._cache_update_cache = True
            return None  # No cache information available, need to rebuild.

        # hit, return cached response
        request._cache_update_cache = False
        return response
View Code

可以看到,FetchFromCacheMiddleware的源码中只有process_request函数,该函数检查缓存中是否有对应的response数据,如果有则无需调用视图函数处理,直接拿缓存中的数据返回。如果没有,则将请求交给视图函数处理。

4.三个级别缓存的优先级

实际上Django提供的三个级别的缓存:全站缓存、页面缓存、局部缓存,下面讨论一下他们的优先级关系。

 

实验现象:

当全站缓存、页面缓存共存时,页面缓存生效。

当全站缓存、局部缓存共存时,全站缓存生效,局部缓存不生效(因为局部缓存生效的条件是视图函数被执行)。

当全站缓存、页面缓存、局部缓存三者共存时,页面缓存和局部缓存生效。

当页面缓存、局部缓存共存时,局部缓存时间大于页面缓存时间时,可以看出效果,否则看不出效果。

总结:我们只需要关心全站缓存和页面缓存是否同时启用。页面缓存优先级更高。而局部缓存是在页面缓存生效的基础上才生效。

三、信号

在Django框架中,如果我们想在某些操作的前后做一些自定义操作(例如在插入数据库的前后写日志),则可以使用Django为我们提供的信号。

如果不使用信号,我们可能会使用装饰器来进行处理,但是不是特别好,因为可能有很多地方需要添加装饰器,导致代码很凌乱。

Django为我们提供了很多种信号,所谓信号就是Django在执行某些操作的前后给我们预留的可以自定义操作的钩子

1.Django提供的信号种类

Django为我们提供了以下信号:

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          # 创建数据库连接时,自动触发

我们以pre_init信号为例,这个信号是在model执行构造函数之前触发的,也就是在我们生成一条数据库记录之前(还未save),即obj = models.TB(username='leo') 之前。

触发这个信号可以执行我们为其绑定的自定义函数(所以称之为钩子)。

2.为信号绑定自定义函数

我们用以下方式为信号绑定自定义函数:(创建一个.py文件,例如sg.py)

#sg.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("pre_init_callback")
    print(sender, kwargs)


pre_init.connect(callback)

首先导入信号,然后使用信号名.connect(call_back_func)来绑定自定义函数(可以绑定多个函数,触发时会按顺序执行)

最后,需要在Django中导入该py文件:(可以选择在工程目录的__init__.py中,因为Django程序开始运行时就会运行__init__.py)

# Django程序目录/__init__.py

import sg

这样,Django程序一开始运行就会导入sg,从而为信号绑定自定义函数。

至此以后,我们每次在插入数据库之前都会执行一次callback函数。

callback函数中的参数:

def callback(sender, **kwargs):
    print("pre_init_callback")
    print(sender, kwargs)

当Django触发信号时,调用我们的自定义函数callback,并且会为我们传入一系列参数,这些参数包含我们所需的所有数据(例如model构造函数相关的数据),我们拿到这些数据就可以写到日志中(或者做其他操作)。

3.信号是怎么触发的

Django中信号是怎么被触发的?

我们看一段Django-ORM的源码:(from django.db.models import Model

在Model类的源码中,有一个save方法,还有一个save_base方法。我们主要看save_base方法(省略了多余部分):

def save_base(self, raw=False, force_insert=False,
              force_update=False, using=None, update_fields=None):
    
    #......
    
    if not meta.auto_created:
        pre_save.send(
            sender=origin, instance=self, raw=raw, using=using,
            update_fields=update_fields,
        )
    
    #......

我们可以看到,在save_base方法中,触发了一个pre_save信号,触发的方法是  信号名.send(),而send方法的一系列参数,就是传递给我们的callback(自定义函数)的,在callback函数中用sender和**kwargs来接收。

所以,信号的触发方式其实很简单,就是 信号.send()。那么在后面我们自定义信号的时候,也可以使用这样的方法来触发。

四、自定义信号

Django虽然为我们提供了十多个常用的信号(钩子),但也可能不能满足我们多样的业务需求,所以我们可能需要自定义信号。

Django提供的预定义信号,我们只需要为其绑定自定义函数即可,使用很方便。自定义信号也差不多。

1.自定义信号

import django.dispatch

my_signal = django.dispatch.Signal(providing_args=['arg1', 'arg2'])

自定义信号很简单,就是创建一个Signal对象。provideing_args这个参数是一个列表,里面的元素是发送该信号需要传入的参数名(不包含sender参数)。

2.为自定义信号绑定函数

def my_callback(sender, **kwargs):
    request = kwargs['arg1']
    print(request)
    page_num = kwargs['arg2']
    print("用户请求的页数是:%s" % page_num)


my_signal.connect(my_callback)

3.触发自定义信号

我们以cache视图函数为例,用户请求/cache/1页面,触发信号,并将request和page_num作为参数传递给自定义函数。

def cache(request, page_num):
    # 导入自定义信号
    from sg import my_signal
    # 触发自定义信号
    my_signal.send(sender=None, arg1=request, arg2=page_num)

    import time
    ctime = time.time()
    return render(request, 'cache.html', {'page_num': page_num, 'ctime': ctime})

关于参数sender,理论上应该传递信号发送方的信息,例如对象、类、方法,但这个参数我们有可以随意传递任何信息,例如传递一个发送方函数名,方便日志记录。

自定义信号方便简单,我们可以灵活的应用在某些我们想做固定处理的地方。感觉很好用。。。

五、Django的Form组件

在前面的章节中,我们如果要使用Form,都是在html上写上<form>表单,然后使用POST提交数据,后台进行验证。

而Django为我们提供了自动生成Form表单的功能,并且提供了方便的验证功能。

1.在后台定义一个表单的类

from django import forms


# 定义一个表单类
class FM(forms.Form):
    user = forms.CharField()
    pwd = forms.CharField()
    email = forms.EmailField()

这个类的对象会自动生成<form>中的<input>标签。

2.在视图函数中使用表单类

def formmodel(request):
    if request.method == 'GET':
        # 生成一个表单对象
        obj = FM()
        # 将表单对象传到html模板
        return render(request, 'formmodel.html', {'obj': obj})
    if request.method == 'POST':
        pass

我们将表单对象obj,传递给了html模板。

如果需要显示的表单有初始值,则使用如下方式:

def formmodel(request):
    if request.method == 'GET':
        dict = {
            'user': 'leo',
            'pwd': '123456',
            'email': 'leo4774177@gmail.com'
        }
        # 生成一个表单对象
        obj = FM(initial=dict)
        # 将表单对象传到html模板
        return render(request, 'formmodel.html', {'obj': obj})
    if request.method == 'POST':
        pass

3.实现html模板

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>FormModel</title>
</head>
<body>
    <form action="/formmodel" method="post">
        {% csrf_token %}
        <p>{{ obj.user }}</p>
        <p>{{ obj.pwd }}</p>
        <p>{{ obj.email }}</p>
        <input type="submit" value="提交"/>
    </form>
</body>
</html>

这样就能显示效果:

4.表单验证

def formmodel(request):
    if request.method == 'GET':
        # 生成一个表单对象
        obj = FM()
        # 将表单对象传到html模板
        return render(request, 'formmodel.html', {'obj': obj})
    if request.method == 'POST':
        # 将POST数据传递给FM
        obj = FM(request.POST)
        # 开始验证POST数据
        res = obj.is_valid()

        if res:
            # 如果验证成功,则拿到POST数据
            print(obj.cleaned_data)
            # 如果是用户注册,插入数据库
            # models.TB.objects.create(**obj.cleaned_data)
            return HttpResponse('sign up OK')
        else:
            print(obj.errors)
            return render(request, 'formmodel.html', {"obj": obj})

解释:

1)当用户使用表单提交POST请求时(例如注册),视图函数拿到request.POST

2)生成一个FM对象,并将request.POST给他

3)开始验证,EmailField会验证email是否是邮箱格式,还会验证user、pwd、email是否为空

4)如果验证通过,则插入数据库,返回signup OK

5)如果未验证通过,将obj对象交给模板,然后在模板中获取错误信息并显示

5.带错误信息的html模板

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>FormModel</title>
</head>
<body>
    <form action="/formmodel" method="post">
        {% csrf_token %}
        <p>{{ obj.user }}{{ obj.errors.user.0 }}</p>
        <p>{{ obj.pwd }}{{ obj.errors.pwd.0 }}</p>
        <p>{{ obj.email }}{{ obj.errors.email.0 }}</p>
        <input type="submit" value="提交"/>
    </form>
</body>
</html>

效果如下:

6.设置中文错误信息

首先,我们在视图函数中打印一下obj.errors的信息:

print(obj.errors)
print(obj.errors.as_json())

得到打印结果:

<ul class="errorlist"><li>user<ul class="errorlist"><li>This field is required.</li></ul></li><li>email<ul class="errorlist"><li>Enter a valid email address.</li></ul></li></ul>
{"user": [{"message": "This field is required.", "code": "required"}], "email": [{"message": "Enter a valid email address.", "code": "invalid"}]}

主要关注后面的json格式,这里的code就是错误信息的key,而message就是错误信息的value

在FM类中定义中文的错误信息:

class FM(forms.Form):
    user = forms.CharField(error_messages={'required': "用户名不能为空"})
    pwd = forms.CharField(error_messages={'required': "密码不能为空"})
    email = forms.EmailField(error_messages={'required': "email不能为空", 'invalid': "Email格式错误"})

在error_message参数中,以上面的errors.as_json()结果中的code作为key,中文错误信息作为value。

页面效果:

7.更简单的html模板(不灵活,不建议)

第一种:

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

页面效果:

Django会自动在前面加上文本。

第二种:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>FormModel</title>
</head>
<body>
    <form action="/formmodel" method="post">
        {% csrf_token %}
        {{ obj.as_ul }}
        <input type="submit" value="提交"/>
    </form>
</body>
</html>

页面效果:

第三种:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>FormModel</title>
</head>
<body>
    <form action="/formmodel" method="post">
        {% csrf_token %}
        <table>
            {{ obj.as_table }}
        </table>
        <input type="submit" value="提交"/>
    </form>
</body>
</html>

以表格形式排版,但前面要加上<table>标签。

页面效果

六、Form组件的定制

在第五节中,只是Form组件的简单用法。

Django提供的Form model可以进行多元化的定制:

class FM(forms.Form):
    user = forms.CharField(error_messages={'required': "用户名不能为空"})
    pwd = forms.CharField(error_messages={'required': "密码不能为空"})
    email = forms.EmailField(error_messages={'required': "email不能为空", 'invalid': "Email格式错误"})

在FM类中,除了能定制错误信息,还能定制其他很多东西。

定制插件(标签)

CharField主要用作验证数据是否满足规则,而控制生成什么类型的<input>标签,实际上是由插件控制的。

查看CharField父类(Field类)的源码:

class Field:
    widget = TextInput  # Default widget to use when rendering this type of Field.
    hidden_widget = HiddenInput  # Default widget to use when rendering this as "hidden".
    default_validators = []  # Default set of validators
    # Add an 'invalid' entry to default_error_message if you want a specific
    # field error message not raised by the field validators.
    default_error_messages = {
        'required': _('This field is required.'),
    }
    empty_values = list(validators.EMPTY_VALUES)

    def __init__(self, *, required=True, widget=None, label=None, initial=None,
                 help_text='', error_messages=None, show_hidden_initial=False,
                 validators=(), localize=False, disabled=False, label_suffix=None):

        # .......

可以看到widget默认为TextInput,实际上对应的就是<input type='text'/>输入框。而如果我们在创建CharField时指定widget参数,则可以修改为其他类型的<input>框。

例如,将user的CharField的类型修改为Textarea:

from django import forms
from django.forms import widgets


# 定义一个表单类
class FM(forms.Form):
    user = forms.CharField(
        widget=widgets.Textarea(attrs={'class': 'c1'}),
        error_messages={'required': "用户名不能为空"})
    pwd = forms.CharField(error_messages={'required': "密码不能为空"})
    email = forms.EmailField(error_messages={'required': "email不能为空", 'invalid': "Email格式错误"})

除了可以修改他的类型,我们还可以为其添加css样式等属性。

在前面,我们都使用的是forms.xxx。实际上django.forms只是多个python model的集合,看以下forms的源码:

from django.core.exceptions import ValidationError  # NOQA
from django.forms.boundfield import *  # NOQA
from django.forms.fields import *  # NOQA
from django.forms.forms import *  # NOQA
from django.forms.formsets import *  # NOQA
from django.forms.models import *  # NOQA
from django.forms.widgets import *  # NOQA

所以,我们可以把上面的FM类修改一下:

from django import forms
from django.forms import widgets
from django.forms import fields


# 定义一个表单类
class FM(forms.Form):
    user = fields.CharField(
        widget=widgets.Textarea(attrs={'class': 'c1'}),
        error_messages={'required': "用户名不能为空"})
    pwd = fields.CharField(error_messages={'required': "密码不能为空"})
    email = fields.EmailField(error_messages={'required': "email不能为空", 'invalid': "Email格式错误"})

七、Form组件中的字段、属性和插件

1.字段(Field 以及其参数):

Field
    required=True,               是否允许为空
    widget=None,                 HTML插件
    label=None,                  用于生成Label标签或显示内容
    initial=None,                初始值
    help_text='',                帮助信息(在标签旁边显示)
    error_messages=None,         错误信息 {'required': '不能为空', 'invalid': '格式错误'}
    show_hidden_initial=False,   是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直)
    validators=[],               自定义验证规则
    localize=False,              是否支持本地化
    disabled=False,              是否可以编辑
    label_suffix=None            Label内容后缀
 
 
CharField(Field)
    max_length=None,             最大长度
    min_length=None,             最小长度
    strip=True                   是否移除用户输入空白
 
IntegerField(Field)
    max_value=None,              最大值
    min_value=None,              最小值
 
FloatField(IntegerField)
    ...
 
DecimalField(IntegerField)
    max_value=None,              最大值
    min_value=None,              最小值
    max_digits=None,             总长度
    decimal_places=None,         小数位长度
 
BaseTemporalField(Field)
    input_formats=None          时间格式化   
 
DateField(BaseTemporalField)    格式:2015-09-01
TimeField(BaseTemporalField)    格式:11:12
DateTimeField(BaseTemporalField)格式:2015-09-01 11:12
 
DurationField(Field)            时间间隔:%d %H:%M:%S.%f
    ...
 
RegexField(CharField)
    regex,                      自定制正则表达式
    max_length=None,            最大长度
    min_length=None,            最小长度
    error_message=None,         忽略,错误信息使用 error_messages={'invalid': '...'}
 
EmailField(CharField)      
    ...
 
FileField(Field)
    allow_empty_file=False     是否允许空文件
 
ImageField(FileField)      
    ...
    注:需要PIL模块,pip3 install Pillow
    以上两个字典使用时,需要注意两点:
        - form表单中 enctype="multipart/form-data"
        - view函数中 obj = MyForm(request.POST, request.FILES)
 
URLField(Field)
    ...
 
 
BooleanField(Field)  
    ...
 
NullBooleanField(BooleanField)
    ...
 
ChoiceField(Field)
    ...
    choices=(),                选项,如:choices = ((0,'上海'),(1,'北京'),)
    required=True,             是否必填
    widget=None,               插件,默认select插件
    label=None,                Label内容
    initial=None,              初始值
    help_text='',              帮助提示
 
 
ModelChoiceField(ChoiceField)
    ...                        django.forms.models.ModelChoiceField
    queryset,                  # 查询数据库中的数据
    empty_label="---------",   # 默认空显示内容
    to_field_name=None,        # HTML中value的值对应的字段
    limit_choices_to=None      # ModelForm中对queryset二次筛选
     
ModelMultipleChoiceField(ModelChoiceField)
    ...                        django.forms.models.ModelMultipleChoiceField
 
 
     
TypedChoiceField(ChoiceField)
    coerce = lambda val: val   对选中的值进行一次转换
    empty_value= ''            空值的默认值
 
MultipleChoiceField(ChoiceField)
    ...
 
TypedMultipleChoiceField(MultipleChoiceField)
    coerce = lambda val: val   对选中的每一个值进行一次转换
    empty_value= ''            空值的默认值
 
ComboField(Field)
    fields=()                  使用多个验证,如下:即验证最大长度20,又验证邮箱格式
                               fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])
 
MultiValueField(Field)
    PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用
 
SplitDateTimeField(MultiValueField)
    input_date_formats=None,   格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
    input_time_formats=None    格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
 
FilePathField(ChoiceField)     文件选项,目录下文件显示在页面中
    path,                      文件夹路径
    match=None,                正则匹配
    recursive=False,           递归下面的文件夹
    allow_files=True,          允许文件
    allow_folders=False,       允许文件夹
    required=True,
    widget=None,
    label=None,
    initial=None,
    help_text=''
 
GenericIPAddressField
    protocol='both',           both,ipv4,ipv6支持的IP格式
    unpack_ipv4=False          解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用
 
SlugField(CharField)           数字,字母,下划线,减号(连字符)
    ...
 
UUIDField(CharField)           uuid类型
    ...

2.内置插件(标签)

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

3.常用选择插件

# 单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的选项可以从数据库中获取,但是由于是静态字段 ***获取的值无法实时更新***,那么需要自定义构造方法从而达到此目的。

八、

原文地址:https://www.cnblogs.com/leokale-zz/p/12088998.html