章节索引 :

Django 操作 Cookie 和 Session

上一节介绍了 Cookie 和 Session 的相关概念,本节就要在 Django 中操作 Cookie 和 Session,同时我也会继续带领大家追踪相关的代码,这样可以更好的理解相关操作。

操作 Cookie 同样是考察4个基本动作:增删改查。现在分别从这4个角度看 Django 如何操作 Cookie :

:对于视图函数或者视图类的三种返回 Response 响应 (HttpResponse、render、redircet),之前的做法是直接 return,现在可以在 return 之前,使用 set_cookie() 或者 set_signed_cookied() 方法给客户端颁发一个 cookie,然后再带着颁发的 cookie 响应用户请求。操作代码结构如下。

def xxxx(request, *args, **kwargs):
    # ...
    
    rep = HttpResponse(...)
    # 或者
    rep = render(request, ...)
    # 或者
    rep = redirect( ...)

    # 两种设置cookie的方法,一种不加salt,另一个加salt 
    rep.set_cookie(key, value,...)
    rep.set_signed_cookie(key, value, salt='加密盐', max_age=None, ...)

    return rep

:查询 cookie 是在发送过来的 HTTP 请求中的,因此对应的查询 Cookie 方法封装在 HttpRequest 类中,对应的操作语句如下:

request.COOKIES['key']
request.COOKIES.get['key']
# 对应前面使用前面加密的cookie
request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)

:调用前面的 set_cookie() 或者 set_signed_cookie() 方法修改 Cookie 即可;

:直接使用 HttpReponse 类的 delete_cookie() 删除 cookie 中对应 key 值。

案例1:Django 中 Cookie 实操。我们在前面的登录表单功能上改造视图函数,保证一次登录后,后续再次 GET 请求时能自动识别登录用户。此外还设置一个 Cookie 过期时间,过期之后再次 GET 请求时又回到登录页面。

调整登录表单的视图类:

class TestFormView2(TemplateView):
    template_name = 'test_form2.html'

    def get(self, request, *args, **kwargs):
        success = False
        form = LoginForm()
        print("[{}] cookies:{}".format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), request.COOKIES))
        if request.get_signed_cookie('user', default='anonymous', salt=default_salt) == 'spyinx':
            success = True
        return self.render_to_response(context={'success': success, 'form': form})

    def post(self, request, *args, **kwargs):
        form = LoginForm(request.POST)
        success = True
        err_msg = ""
        rep = self.render_to_response(context={'success': success, 'err_msg': err_msg, 'form': form})
        if form.is_valid():
            login_data = form.clean()
            name = login_data['name']
            password = login_data['password']
            if name != 'spyinx' or password != 'SPYinx123456':
                success = False
                err_msg = "用户名密码不正确"
            else:
                print('设置cookie')
                rep.set_signed_cookie('user', 'spyinx', salt=default_salt, max_age=10)
        else:
            success = False
            err_msg = form.errors['password'][0]
        return rep

可以看到,在 get()方法中我们通过 get_signed_cookie() 方法获取 cookie 中的 user 信息,判断是否为 spyinx。若正确则返回成功登录的页面,否则返回登录页面。在 post() 方法中,对于登录成功的情况我们通过 set_signed_cookie() 方法颁发了一个 cookie 给客户端,并设置过期时间为10s,后续客户端的请求中都会自动带上这个 cookie。

2. Django 中操作 Session

2.1 Session 的相关配置

由于 Session 的数据是保存在服务器端的,所以很多工作是需要在服务器端来完成的,所以 Django 中 Session 的操作相比 Cookie 操作会略显复杂。首先需要介绍 Django 中和 Session 相关的配置,同样是在 settings.py 文件中:

启用 Session:需要在 MIDDLEWARE 值中添加相应的 Session 中间件,去对 Session 拦截和处理。另外,还需要再INSTALLED_APPS 中注册 Session 应用。Django 中默认是有这个配置的。

MIDDLEWARE = [
    # ...
    'django.contrib.sessions.middleware.SessionMiddleware',
    # ...
]

INSTALLED_APPS = [
    # 启用 sessions 应用
    'django.contrib.sessions',
]

配置 Session 引擎:主要是配置 Session 保存方式,比如数据库保存、内存保存、文件系统保存等。

# 数据库Session
SESSION_ENGINE = 'django.contrib.sessions.backends.db'       # 默认引擎

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

# 文件Session
SESSION_ENGINE = 'django.contrib.sessions.backends.file'    
# 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir() 
SESSION_FILE_PATH = None                                     

# 缓存+数据库
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'       

# 加密Cookie Session
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies' 

其他配置如下:

# Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
SESSION_COOKIE_NAME = "sessionid"                      
# Session的cookie保存的路径(默认)
SESSION_COOKIE_PATH = "/"        
# Session的cookie保存的域名(默认)
SESSION_COOKIE_DOMAIN = None       
# 是否Https传输cookie(默认)
SESSION_COOKIE_SECURE = False       
# 是否Session的cookie只支持http传输(默认)
SESSION_COOKIE_HTTPONLY = True       
# Session的cookie失效日期(2周)(默认)
SESSION_COOKIE_AGE = 1209600   
# 是否关闭浏览器使得Session过期(默认)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False   
# 是否每次请求都保存Session,默认修改之后才保存(默认)
SESSION_SAVE_EVERY_REQUEST = False           

关于上述这些未出现在 settings.py 中的配置,默认的值都会在 django/conf/global_settings.py 中找到,如下图所示:

图片描述

2.2 操作 Session

在 Django 中,如果我们配置好了 Session 中间件并注册了 Session应用 ,那么任何视图函数的第一个参数,也就是 HttpRequest 对象,将会包含一个 session 属性,我们可以在视图函数的任何位置使用 request.session 读取和写 Session 信息。这个 session 属性是前面配置的 SESSION_ENGINE 模块中 SessionStore 类的实例,而SessionStore 类又继承自 django.contrib.sessions.backends.base.SessionBase 类,它的具有如下操作方法:

  • get(self, key, default=None):获取 Session 中的 key 值对应的 value。这种方式比较推荐。因为在 key 不存在时可以取默认值,而request.session[key] 这样的写法在 key 不存在时会抛出异常;
  • pop(key, default=__not_given):返回 Session 中 key 对应的 value 值,并将其从 Session 中删除;
  • keys():返回 Session 中所有的 key 值
  • setdefault(key, value):给某个 key 设置对应的默认 value
  • set_expiry(value):给 Session 设置对应的过期时间;

还有很多的方法可以在源码中找到,我们会在第3部分详细介绍这些代码。可以看到,目前我们操作 Session 就像操作字典那样简单,只需要使用 SessionBase 提供的各种方法即可随意操作 Session,这些都是 Django 在背后给我们做了许多工作,我们也会在第3部分详细介绍这些工作。

实验2:使用 Session 完成一个简单的登录操作,和上面 Cookie 实验类似。我们的模板文件不变,同样只需要改造下视图函数即可。

改造视图函数,使用 Session 保存登录信息

class TestFormView2(TemplateView):
    template_name = 'test_form2.html'

    def get(self, request, *args, **kwargs):
        success = False
        form = LoginForm()
        if request.session.get('has_login', False):
            return HttpResponse('已经登陆成功,无需再次登陆')
        return self.render_to_response(context={'success': success, 'form': form})

    def post(self, request, *args, **kwargs):
        form = LoginForm(request.POST)
        success = True
        err_msg = ""
        if form.is_valid():
            login_data = form.clean()
            name = login_data['name']
            password = login_data['password']
            if name != 'spyinx' or password != 'SPYinx123456':
                success = False
                err_msg = "用户名密码不正确"
            else:
                print('设置session')
                request.session['has_login'] = True
                # 设置10s后过期
                request.session.set_expiry(10)
        else:
            success = False
            err_msg = form.errors['password'][0]
        return self.render_to_response(context={'success': success, 'err_msg': err_msg, 'form': form})

我们运行服务,来看相应的演示效果:

上面也可以看到,这里实现了和之前 Cookie 一样的效果。不过不同的是,在 Django 中我们默认的使用数据库的方式保存 Session 数据,这种方式在用户量大时频繁读写数据库会拖累 Web 服务的响应时间。

从前面的操作 Cookie 讲解中,我们只用到了和两部分的方法,分别对应 HttpResponse 和 HttpRequest 两个类。接下来,我们去对应的源码中查找所涉及的和 Cookie 相关的代码。

request.COOKIES['xxx']
request.COOKIES.get('xxx', None)
# 源码位置:django/core/handlers/wsgi.py
class WSGIRequest(HttpRequest):
    # ...
    
    @cached_property
    def COOKIES(self):
        raw_cookie = get_str_from_wsgi(self.environ, 'HTTP_COOKIE', '')
        return parse_cookie(raw_cookie)
    # ...
# 源码位置:django/http/cookie.py
from http import cookies

# For backwards compatibility in Django 2.1.
SimpleCookie = cookies.SimpleCookie

# Add support for the SameSite attribute (obsolete when PY37 is unsupported).
cookies.Morsel._reserved.setdefault('samesite', 'SameSite')


def parse_cookie(cookie):
    """
    Return a dictionary parsed from a `Cookie:` header string.
    """
    cookiedict = {}
    for chunk in cookie.split(';'):
        if '=' in chunk:
            key, val = chunk.split('=', 1)
        else:
            # Assume an empty name per
            # https://bugzilla.mozilla.org/show_bug.cgi?id=169091
            key, val = '', chunk
        key, val = key.strip(), val.strip()
        if key or val:
            # unquote using Python's algorithm.
            cookiedict[key] = cookies._unquote(val)
    return cookiedict

上面的代码并不复杂,在 WSGIRequest 类中的 COOKIES 属性是先从客户端请求中取出 Cookie 信息,调用 get_str_from_wsgi() 方法是从 WSGI 中拿到对应的 Cookie 字符串。接下来用 parse_cookie() 方法将原始 Cookie 字符串中的 key=value 解析出来做成字典形式并返回。这就是为什么我们能像操作字典一样操作 request.COOKIES 的原因。下面的方法是实验1中调用的 get_signed_cookie() 的源码,也不复杂,同样是从self.COOKIES 中取出对应 key 的 value 值,然后使用对应的 salt 解密即可。

# 源码位置:django/http/request.py  
class HttpRequest:
    # ...
    
    def get_signed_cookie(self, key, default=RAISE_ERROR, salt='', max_age=None):
        """
        Attempt to return a signed cookie. If the signature fails or the
        cookie has expired, raise an exception, unless the `default` argument
        is provided,  in which case return that value.
        """
        try:
            cookie_value = self.COOKIES[key]
        except KeyError:
            if default is not RAISE_ERROR:
                return default
            else:
                raise
        try:
            value = signing.get_cookie_signer(salt=key + salt).unsign(
                cookie_value, max_age=max_age)
        except signing.BadSignature:
            if default is not RAISE_ERROR:
                return default
            else:
                raise
        return value
        
    # ...

接下来是涉及到创建 Cookie 的方法,我们需要查找 HttpResponse 类或者相关的父类:

# 源码位置:django/http/response.py
class HttpResponseBase:
    # ...
    def set_cookie(self, key, value='', max_age=None, expires=None, path='/',
                   domain=None, secure=False, httponly=False, samesite=None):
        """
        Set a cookie.

        ``expires`` can be:
        - a string in the correct format,
        - a naive ``datetime.datetime`` object in UTC,
        - an aware ``datetime.datetime`` object in any time zone.
        If it is a ``datetime.datetime`` object then calculate ``max_age``.
        """
        self.cookies[key] = value
        if expires is not None:
            if isinstance(expires, datetime.datetime):
                if timezone.is_aware(expires):
                    expires = timezone.make_naive(expires, timezone.utc)
                delta = expires - expires.utcnow()
                # Add one second so the date matches exactly (a fraction of
                # time gets lost between converting to a timedelta and
                # then the date string).
                delta = delta + datetime.timedelta(seconds=1)
                # Just set max_age - the max_age logic will set expires.
                expires = None
                max_age = max(0, delta.days * 86400 + delta.seconds)
            else:
                self.cookies[key]['expires'] = expires
        else:
            self.cookies[key]['expires'] = ''
        if max_age is not None:
            self.cookies[key]['max-age'] = max_age
            # IE requires expires, so set it if hasn't been already.
            if not expires:
                self.cookies[key]['expires'] = http_date(time.time() + max_age)
        if path is not None:
            self.cookies[key]['path'] = path
        if domain is not None:
            self.cookies[key]['domain'] = domain
        if secure:
            self.cookies[key]['secure'] = True
        if httponly:
            self.cookies[key]['httponly'] = True
        if samesite:
            if samesite.lower() not in ('lax', 'strict'):
                raise ValueError('samesite must be "lax" or "strict".')
            self.cookies[key]['samesite'] = samesite
            
    def set_signed_cookie(self, key, value, salt='', **kwargs):
        value = signing.get_cookie_signer(salt=key + salt).sign(value)
        return self.set_cookie(key, value, **kwargs)

    def delete_cookie(self, key, path='/', domain=None):
        # Most browsers ignore the Set-Cookie header if the cookie name starts
        # with __Host- or __Secure- and the cookie doesn't use the secure flag.
        secure = key.startswith(('__Secure-', '__Host-'))
        self.set_cookie(
            key, max_age=0, path=path, domain=domain, secure=secure,
            expires='Thu, 01 Jan 1970 00:00:00 GMT',
        )
        
    # ...

从上面的代码可以看到,最核心的方法是 set_cookie(),而删除 cookie 和 设置加盐的 cookie 方法最后都是调用 set_cookie() 这个方法。而这个方法也比较简单,就是将对应的传递过来的参数值加到 self.cookies 这个字典中。最后我们思考下,难道就这样就完了吗?是不是还需要有一步是需要将 self.cookies 中的所有 key-value 值组成字符串,放到头部中,然后才返回给前端?事实上,肯定是有这一步的,代码如下。在用 “#” 号包围起来的那一段代码正是将 self.cookies 中的所有 key-value 值组成字符串形式,然后放到头部的 “Set-Cookie” 中,正是有了这一步的动作,我们前面设置的 self.cookie 内部的 key-value 值才能真正生效。

# 源码位置:django/core/handlers/wsgi.py

class WSGIHandler(base.BaseHandler):
    request_class = WSGIRequest

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.load_middleware()

    def __call__(self, environ, start_response):
        set_script_prefix(get_script_name(environ))
        signals.request_started.send(sender=self.__class__, environ=environ)
        request = self.request_class(environ)
        response = self.get_response(request)

        response._handler_class = self.__class__

        status = '%d %s' % (response.status_code, response.reason_phrase)
        ##############################################################################
        response_headers = [
            *response.items(),
            *(('Set-Cookie', c.output(header='')) for c in response.cookies.values()),
        ]
        #############################################################################
        start_response(status, response_headers)
        if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'):
            response = environ['wsgi.file_wrapper'](response.file_to_stream)
        return response

3.2 Django 中 Session 操作相关源码

Django 中和 Session 相关的代码较多,我们不去深入追究源码细节,主要是程序的执行过程。比如在哪里设置的 request.session 值以及 session 相关信息如何保存到数据库中的等等。我们先整体看下 Session 相关的代码位置:

图片描述

这里有几个比较重要的地方,一个是 backends 目录,下面是不同保存 Session 数据方式的代码,如使用数据库存储、缓存存储或者文件系统存储等,每种存储方式对应着一个代码文件,里面定义的类和方法都是一致的,这样就可以无缝切换存储引擎。

第二个是 middleware.py 文件,我们先要了解下 Django 中 MIDDLEWARE 的工作流程,可以如下图所示:
图片描述

由于在 settings.py 中间设置了 Session 的中间件,所以 request 和 response 也会经历 Session 中间件的这个流程,看 session 目录下的 middleware.py 文件的代码:

class SessionMiddleware(MiddlewareMixin):
    def __init__(self, get_response=None):
        self.get_response = get_response
        engine = import_module(settings.SESSION_ENGINE)
        self.SessionStore = engine.SessionStore

    def process_request(self, request):
        session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
        request.session = self.SessionStore(session_key)

    def process_response(self, request, response):
        """
        If request.session was modified, or if the configuration is to save the
        session every time, save the changes and set a session cookie or delete
        the session cookie if the session has been emptied.
        """
        try:
            accessed = request.session.accessed
            modified = request.session.modified
            empty = request.session.is_empty()
        except AttributeError:
            pass
        else:
            # First check if we need to delete this cookie.
            # The session should be deleted only if the session is entirely empty
            if settings.SESSION_COOKIE_NAME in request.COOKIES and empty:
                response.delete_cookie(
                    settings.SESSION_COOKIE_NAME,
                    path=settings.SESSION_COOKIE_PATH,
                    domain=settings.SESSION_COOKIE_DOMAIN,
                )
            else:
                if accessed:
                    patch_vary_headers(response, ('Cookie',))
                if (modified or settings.SESSION_SAVE_EVERY_REQUEST) and not empty:
                    if request.session.get_expire_at_browser_close():
                        max_age = None
                        expires = None
                    else:
                        max_age = request.session.get_expiry_age()
                        expires_time = time.time() + max_age
                        expires = http_date(expires_time)
                    # Save the session data and refresh the client cookie.
                    # Skip session save for 500 responses, refs #3881.
                    if response.status_code != 500:
                        try:
                            request.session.save()
                        except UpdateError:
                            raise SuspiciousOperation(
                                "The request's session was deleted before the "
                                "request completed. The user may have logged "
                                "out in a concurrent request, for example."
                            )
                        response.set_cookie(
                            # 默认值便是session_id
                            settings.SESSION_COOKIE_NAME,
                            request.session.session_key, max_age=max_age,
                            expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
                            path=settings.SESSION_COOKIE_PATH,
                            secure=settings.SESSION_COOKIE_SECURE or None,
                            httponly=settings.SESSION_COOKIE_HTTPONLY or None,
                            samesite=settings.SESSION_COOKIE_SAMESITE,
                        )
        return response

首先看 __init__() 方法中的设置了对应 Session 的存储引擎,接下来的 process_request() 方法中我们可以看到 request.session = self.SessionStore(session_key) 这行语句正是给 request 的 session 属性赋值,而这个值正是存储引擎模块下的 SessionStore 类的实例。而对于 process_response() 方法,从代码中可以看到,它完成了 Session 数据的保存以及将 session_id 值写到 cookie 中去并返回给客户端,调用的方法正是我们前面介绍到的 set_cookie() 方法。

接下来,我们看看其他几个 python 文件中的代码。例如下面的 models.py 定义了 Session 的 model 模型,包括字段以及管理器:

# 源码位置:django/contrib/sessions/models.py
from django.contrib.sessions.base_session import (
    AbstractBaseSession, BaseSessionManager,
)

class SessionManager(BaseSessionManager):
    use_in_migrations = True

class Session(AbstractBaseSession):
    objects = SessionManager()

    @classmethod
    def get_session_store_class(cls):
        from django.contrib.sessions.backends.db import SessionStore
        return SessionStore

    class Meta(AbstractBaseSession.Meta):
        db_table = 'django_session'

从这段代码可以看到 Session 如果使用数据库保存数据的话,其建立的表名为:django_session,其字段类型并不在这里定义,而是继承的父类 BaseSessionManager,这个类定义就在 base_session.py 文件中:

class BaseSessionManager(models.Manager):
    def encode(self, session_dict):
        """
        Return the given session dictionary serialized and encoded as a string.
        """
        session_store_class = self.model.get_session_store_class()
        return session_store_class().encode(session_dict)

    def save(self, session_key, session_dict, expire_date):
        s = self.model(session_key, self.encode(session_dict), expire_date)
        if session_dict:
            s.save()
        else:
            s.delete()  # Clear sessions with no data.
        return s


class AbstractBaseSession(models.Model):
    session_key = models.CharField(_('session key'), max_length=40, primary_key=True)
    session_data = models.TextField(_('session data'))
    expire_date = models.DateTimeField(_('expire date'), db_index=True)

    objects = BaseSessionManager()

    class Meta:
        abstract = True
        verbose_name = _('session')
        verbose_name_plural = _('sessions')

    def __str__(self):
        return self.session_key

    @classmethod
    def get_session_store_class(cls):
        raise NotImplementedError

    def get_decoded(self):
        session_store_class = self.get_session_store_class()
        return session_store_class().decode(self.session_data)

从这里就可以看到 django_session 表有3个字段,分别是session_keysession_dataexpire_date。继承这个基类必须要实现 get_session_store_class() 这个方法。另外 app.py 用于指明 session 应用名称,exceptions.py 定义了两个简单的异常,serializers.py 的内容也比较简单,仅仅使用 pickle 模块封装了一个用于序列化的类:

import pickle

from django.core.signing import JSONSerializer as BaseJSONSerializer

class PickleSerializer:
    """
    Simple wrapper around pickle to be used in signing.dumps and
    signing.loads.
    """
    protocol = pickle.HIGHEST_PROTOCOL

    def dumps(self, obj):
        return pickle.dumps(obj, self.protocol)

    def loads(self, data):
        return pickle.loads(data)

JSONSerializer = BaseJSONSerializer

我们也可以简单使用下这个类,其实就是熟悉 pickle 模块的 dumps()loads() 方法。

(django-manual) [root@server first_django_app]# python manage.py shell
Python 3.8.1 (default, Dec 24 2019, 17:04:00) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.contrib.sessions.serializers import PickleSerializer
>>> from hello_app.views import Member
>>> Member.objects.all().get(name='spyinx-1')
<Member: <spyinx-1, 18017715080>>
>>> m = Member.objects.all().get(name='spyinx-1')
>>> PickleSerializer().dumps(m)
b'\x80\x05\x95o\x01\x00\x00\x00\x00\x00\x00\x8c\x15django.db.models.base\x94\x8c\x0emodel_unpickle\x94\x93\x94\x8c\thello_app\x94\x8c\x06Member\x94\x86\x94\x85\x94R\x94}\x94(\x8c\x06_state\x94h\x00\x8c\nModelState\x94\x93\x94)\x81\x94}\x94(\x8c\x06adding\x94\x89\x8c\x02db\x94\x8c\x07default\x94ub\x8c\x02id\x94K\x05\x8c\x04name\x94\x8c\x08spyinx-1\x94\x8c\x03age\x94\x8c\x0225\x94\x8c\x03sex\x94K\x00\x8c\noccupation\x94\x8c\x07teacher\x94\x8c\tphone_num\x94\x8c\x0b18017715080\x94\x8c\x05email\x94\x8c\n221@qq.com\x94\x8c\x04city\x94\x8c\x08shenzhen\x94\x8c\rregister_date\x94\x8c\x08datetime\x94\x8c\x08datetime\x94\x93\x94C\n\x07\xe4\x04\t\x10\x18\n\x00\x00\x00\x94\x85\x94R\x94\x8c\x0cvip_level_id\x94N\x8c\x0f_django_version\x94\x8c\x062.2.11\x94ub.'
>>> m1 = PickleSerializer().loads(s)
>>> type(m1)
<class 'hello_app.models.Member'>
>>> m1.name
'spyinx-1'

最后,关于 Session 的引擎不用过多细究,里面默认用到的是 backends/db.py。如果使用的是缓存引擎,代码内容也是大同小异的。主要认真研究两个类:

  • backends/base.py 中的 SessionBase 类。这个是所有 SessionStore 的基类,它具有的方法正是我们操作 session 的方法;
  • backends/db.py 中的 SessionStore 类。前面的 request.session 便是该类的一个实例,它的代码内容也是不复杂的,主要针对该种存储方式需要完成特定的保存(save())、删除(delete()) 以及导入(load())等方法。

到此为止,Django 中关于 Session 的代码就这么多了。剩下的代码细节还需要各位去慢慢专研,也希望大家能认真钻研 Django 的源码,多多思考,然后多多实践。

4. 小结

本小节中我们讲解了在 Django 中如何操作 Cookie 和 Session,并各给出了一个实战案例。接下来我们学习了 Django 中操作 Cookie 和 Session 的方法的源码,在熟悉了这些代码之后,对于我们操作 Cookie 和 Session 会更加得心应手 。