zoukankan      html  css  js  c++  java
  • Django之cookie、session和分页

    Django之cookie、session和自定义分页

    1.1 cookie的由来

    大家都知道HTTP协议是无状态的。

    无状态的意思是每次请求都是独立的,它的执行情况和结果与前面的请求和之后的请求都无直接关系,它不会受前面的请求响应情况直接影响,也不会直接影响后面的请求响应情况。

    一句有意思的话来描述就是人生只如初见,对服务器来说,每次的请求都是全新的。

    状态可以理解为客户端和服务器在某次会话中产生的数据,那无状态的就以为这些数据不会被保留。会话中产生的数据又是我们需要保存的,也就是说要“保持状态”。因此Cookie就是在这样一个场景下诞生。

    1.2 什么是cookie

    Cookie具体指的是一段小信息,它是服务器发送出来存储在浏览器上的一组组键值对,下次访问服务器时浏览器会自动携带这些键值对,以便服务器提取有用信息。

    1.3 cookie的原理

    cookie的工作原理是:由服务器产生内容,浏览器收到请求后保存在本地;当浏览器再次访问时,浏览器会自动带上Cookie,这样服务器就能通过Cookie的内容来判断这个是“谁”了。

    1.4 查看cookie

    我们使用Chrome浏览器,打开开发者工具。

    二 Django中操作cookie

    2.1 获取cookie

    request.COOKIES['key']
    request.get_signed_cookie(key, default=RAISE_ERROR, salt='加密盐', max_age=None) # default默认是抛异常,可手动指定为空
    

    参数:

    • default:默认值
    • salt:加盐
    • max_age: 后台控制过期时间

    2.2 设置cookie

    rep = HttpResponse(...)
    rep = render(request, ...)
    
    rep.set_cookie(key,value,...)
    rep.set_signed_cookie(key,value,salt='加密盐',...)
    

    **参数: **

    • key,键
    • value='', 值
    • max_age=None, 超时时间
    • expires=None,超时时间,(IE requires expires, so set it if hasn't been already.)
    • path='/', Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问
    • domain=None, Cookie生效的域名
    • secure=False, https传输
    • httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)

    2.3 删除cookie

    def logout(request):
        rep = redirect("/login/")
        rep.delete_cookie("user")  # 删除用户浏览器上之前设置的usercookie值
        return rep
    

    Cookie版登陆校验

    def check_login(func):
        @wraps(func)
        def inner(request, *args, **kwargs):
            next_url = request.get_full_path()
            if request.get_signed_cookie("login", salt="SSS", default=None) == "yes":
                # 已经登录的用户...
                return func(request, *args, **kwargs)
            else:
                # 没有登录的用户,跳转刚到登录页面
                return redirect("/login/?next={}".format(next_url))
        return inner
    
    
    def login(request):
        if request.method == "POST":
            username = request.POST.get("username")
            passwd = request.POST.get("password")
            if username == "xxx" and passwd == "dashabi":
                next_url = request.GET.get("next")
                if next_url and next_url != "/logout/":
                    response = redirect(next_url)
                else:
                    response = redirect("/class_list/")
                response.set_signed_cookie("login", "yes", salt="SSS")
                return response
        return render(request, "login.html")
    

    三 session

    3.1 Session的由来

    Cookie虽然在一定程度上解决了“保持状态”的需求,但是由于Cookie本身最大支持4096字节,以及Cookie本身保存在客户端,可能被拦截或窃取,因此就需要有一种新的东西,它能支持更多的字节,并且他保存在服务器,有较高的安全性。这就是Session。

    问题来了,基于HTTP协议的无状态特征,服务器根本就不知道访问者是“谁”。那么上述的Cookie就起到桥接的作用。

    我们可以给每个客户端的Cookie分配一个唯一的id,这样用户在访问时,通过Cookie,服务器就知道来的人是“谁”。然后我们再根据不同的Cookie的id,在服务器上保存一段时间的私密资料,如“账号密码”等等。

    总结而言:Cookie弥补了HTTP无状态的不足,让服务器知道来的人是“谁”;但是Cookie以文本的形式保存在本地,自身安全性较差;所以我们就通过Cookie识别不同的用户,对应的在Session里保存私密的信息以及超过4096字节的文本。

    另外,上述所说的Cookie和Session其实是共通性的东西,不限于语言和框架。

    3.2 为什么要有session

    3.2.1 cookie的缺点:

    1. 数据量只有4096
    2. 数据都保存在客户端(浏览器)上

    3.2.2 Session流程解析

    保存在服务端的键值对:
    e4e81a1163903112e99ee49c0d5ad26a.png

    1. 请求来了之后,还是生成随机字符串
    2. 以随机字符串为key,在服务端生成一个大字典,真正保存数据是value
    3. 把随机字符串以cookie的形式回复给浏览器
    4. 下一次请求再来的时候,会携带上一步的随机字符串
    5. 从请求中拿到随机字符串
    6. 去后端以该字符串为key找到对应的value
    7. value里存的就是真正有用的数据

    3.3 Django中如何使用session

    3.3.1 操作session

    无论设置还是获取session都是针对request请求
    全局配置项写在settings.py

    # 获取、设置、删除Session中数据
    request.session['k1']
    request.session.get('k1',None)
    request.session['k1'] = 123
    request.session.setdefault('k1',123) # 存在则不设置
    del request.session['k1']
    
    
    # 所有 键、值、键值对
    request.session.keys()
    request.session.values()
    request.session.items()
    request.session.iterkeys()
    request.session.itervalues()
    request.session.iteritems()
    
    # 会话session的key
    request.session.session_key
    
    # 将所有Session失效日期小于当前日期的数据删除
    request.session.clear_expired()
    
    # 检查会话session的key在数据库中是否存在
    request.session.exists("session_key")
    
    # 删除当前会话的所有Session数据
    request.session.delete()
      
    # 删除当前的会话数据并删除会话的Cookie。
    request.session.flush() 
        这用于确保前面的会话数据不可以再次被用户的浏览器访问
        例如,django.contrib.auth.logout() 函数中就会调用它。
    
    # 设置会话Session和Cookie的超时时间
    request.session.set_expiry(value)
        * 如果value是个整数,session会在些秒数后失效。
        * 如果value是个datatime或timedelta,session就会在这个时间后失效。
        * 如果value是0,用户关闭浏览器session就会失效。
        * 如果value是None,session会依赖全局session失效策略。
    

    3.3.2 session版登录验证

    from functools import wraps
    
    
    def check_login(func):
        @wraps(func)
        def inner(request, *args, **kwargs):
            next_url = request.get_full_path()
            if request.session.get("user"):
                return func(request, *args, **kwargs)
            else:
                return redirect("/login/?next={}".format(next_url))
        return inner
    
    
    def login(request):
        if request.method == "POST":
            user = request.POST.get("user")
            pwd = request.POST.get("pwd")
    
            if user == "alex" and pwd == "alex1234":
                # 设置session
                request.session["user"] = user
                # 获取跳到登陆页面之前的URL
                next_url = request.GET.get("next")
                # 如果有,就跳转回登陆之前的URL
                if next_url:
                    return redirect(next_url)
                # 否则默认跳转到index页面
                else:
                    return redirect("/index/")
        return render(request, "login.html")
    
    
    @check_login
    def logout(request):
        # 删除所有当前请求相关的session
        request.session.delete()
        return redirect("/login/")
    
    
    @check_login
    def index(request):
        current_user = request.session.get("user", None)
        return render(request, "index.html", {"user": current_user})
    
    Session版登录验证
    

    3.3.3 Django中的session配置

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

    1. 数据库Session
    SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默认)
    
    2. 缓存Session
    SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # 引擎
    SESSION_CACHE_ALIAS = 'default'                            # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置
    
    3. 文件Session
    SESSION_ENGINE = 'django.contrib.sessions.backends.file'    # 引擎
    SESSION_FILE_PATH = None                                    # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir() 
    
    4. 缓存+数据库
    SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'        # 引擎
    
    5. 加密Cookie Session
    SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'   # 引擎
    
    其他公用设置项:
    SESSION_COOKIE_NAME = "sessionid"                       # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
    SESSION_COOKIE_PATH = "/"                               # Session的cookie保存的路径(默认)
    SESSION_COOKIE_DOMAIN = None                             # Session的cookie保存的域名(默认)
    SESSION_COOKIE_SECURE = False                            # 是否Https传输cookie(默认)
    SESSION_COOKIE_HTTPONLY = True                           # 是否Session的cookie只支持http传输(默认)
    SESSION_COOKIE_AGE = 1209600                             # Session的cookie失效日期(2周)(默认)
    SESSION_EXPIRE_AT_BROWSER_CLOSE = False                  # 是否关闭浏览器使得Session过期(默认)
    SESSION_SAVE_EVERY_REQUEST = False                       # 是否每次请求都保存Session,默认修改之后才保存(默认)
    
    Django中Session相关设置
    

    3.3.4 CBV中加装饰器相关
    CBV实现登录视图

    class LoginView(View):
    
        def get(self, request):
            """
            处理GET请求
            """
            return render(request, 'login.html')
    
        def post(self, request):
            """
            处理POST请求 
            """
            user = request.POST.get('user')
            pwd = request.POST.get('pwd')
            if user == 'alex' and pwd == "alex1234":
                next_url = request.GET.get("next")
                # 生成随机字符串
                # 写浏览器cookie -> session_id: 随机字符串
                # 写到服务端session:
                # {
                #     "随机字符串": {'user':'alex'}
                # }
                request.session['user'] = user
                if next_url:
                    return redirect(next_url)
                else:
                    return redirect('/index/')
            return render(request, 'login.html')
    

    要在CBV视图中使用我们上面的check_login装饰器,有以下三种方式

    from django.utils.decorators import method_decorator
    

    1.加在CBV视图的get或post方法上

    from django.utils.decorators import method_decorator
    
    
    class HomeView(View):
    
        def dispatch(self, request, *args, **kwargs):
            return super(HomeView, self).dispatch(request, *args, **kwargs)
    
        def get(self, request):
            return render(request, "home.html")
        
        @method_decorator(check_login)
        def post(self, request):
            print("Home View POST method...")
            return redirect("/index/")
    

    1.加在dispatch方法上

    from django.utils.decorators import method_decorator
    
    
    class HomeView(View):
    
        @method_decorator(check_login)
        def dispatch(self, request, *args, **kwargs):
            return super(HomeView, self).dispatch(request, *args, **kwargs)
    
        def get(self, request):
            return render(request, "home.html")
    
        def post(self, request):
            print("Home View POST method...")
            return redirect("/index/")
    

    因为CBV中首先执行的就是dispatch方法,所以这么写相当于给get和post方法都加上了登录校验。

    3.直接加在视图类上
    method_decorator必须传 name 关键字参数
    如果get方法和post方法都需要登录校验的话就写两个装饰器。

    from django.utils.decorators import method_decorator
    
    @method_decorator(check_login, name="get")
    @method_decorator(check_login, name="post")
    class HomeView(View):
    
        def dispatch(self, request, *args, **kwargs):
            return super(HomeView, self).dispatch(request, *args, **kwargs)
    
        def get(self, request):
            return render(request, "home.html")
    
        def post(self, request):
            print("Home View POST method...")
            return redirect("/index/")
    

    四 自定义分页

    4.1 自定义分页

    分页代码

    data = []
    
    for i in range(1, 302):
        tmp = {"id": i, "name": "alex-{}".format(i)}
        data.append(tmp)
    
    print(data)
    
    
    def user_list(request):
    
        # user_list = data[0:10]
        # user_list = data[10:20]
        try:
            current_page = int(request.GET.get("page"))
        except Exception as e:
            current_page = 1
    
        per_page = 10
    
        # 数据总条数
        total_count = len(data)
        # 总页码
        total_page, more = divmod(total_count, per_page)
        if more:
            total_page += 1
    
        # 页面最多显示多少个页码
        max_show = 11
        half_show = int((max_show-1)/2)
    
        if current_page <= half_show:
            show_start = 1
            show_end = max_show
        else:
            if current_page + half_show >= total_page:
                show_start = total_page - max_show
                show_end = total_page
            else:
                show_start = current_page - half_show
                show_end = current_page + half_show
    
        # 数据库中获取数据
        data_start = (current_page - 1) * per_page
        data_end = current_page * per_page
    
        user_list = data[data_start:data_end]
    
        # 生成页面上显示的页码
        page_html_list = []
        # 加首页
        first_li = '<li><a href="/user_list/?page=1">首页</a></li>'
        page_html_list.append(first_li)
        # 加上一页
        if current_page == 1:
            prev_li = '<li><a href="#">上一页</a></li>'
        else:
            prev_li = '<li><a href="/user_list/?page={}">上一页</a></li>'.format(current_page - 1)
        page_html_list.append(prev_li)
        for i in range(show_start, show_end+1):
            if i == current_page:
                li_tag = '<li class="active"><a href="/user_list/?page={0}">{0}</a></li>'.format(i)
            else:
                li_tag = '<li><a href="/user_list/?page={0}">{0}</a></li>'.format(i)
            page_html_list.append(li_tag)
    
        # 加下一页
        if current_page == total_page:
            next_li = '<li><a href="#">下一页</a></li>'
        else:
            next_li = '<li><a href="/user_list/?page={}">下一页</a></li>'.format(current_page+1)
        page_html_list.append(next_li)
    
        # 加尾页
        page_end_li = '<li><a href="/user_list/?page={}">尾页</a></li>'.format(total_page)
        page_html_list.append(page_end_li)
    
        page_html = "".join(page_html_list)
    
        return render(request, "user_list.html", {"user_list": user_list, "page_html": page_html})
    

    4.2 分页代码封装

    分页封装类

    # 封装分页类
    class MyPage(object):
    
        def __init__(self, current_page, total_count, url_prefix, per_page=10, max_show=11):
            """
            初始化一个我自己定义的分页实例
            :param current_page: 当前页码
            :param total_count: 总的数据量
            :param url_prefix: 分页中a标签的url前缀
            :param per_page: 每一个显示多少条数据
            :param max_show: 页面上最多显示多少个页码
            """
            self.total_count = total_count
            self.per_page = per_page
            self.max_show = max_show
            self.url_prefix = url_prefix
    
            # 最多显示页码数的一半
            half_show = max_show // 2
            #    因为URL取到的参数是字符串格式,需要转换成int类型
            try:
                current_page = int(current_page)
            except Exception as e:
                # 如果输入的页码不是正经页码,默认展示第一页
                current_page = 1
            # 求总共需要多少页显示
            total_page, more = divmod(total_count, per_page)
            if more:
                total_page += 1
            # 如果输入的当前页码数大于总数据的页码数,默认显示最后一页
            if current_page > total_page:
                current_page = total_page
            self.current_page = current_page
    
            # 计算一下显示页码的起点和终点
            show_page_start = current_page - half_show
            show_page_end = current_page + half_show
            # 特殊情况特殊处理
            # 1. 当前页码 - half_show <= 0
            if current_page - half_show <= 0:
                show_page_start = 1
                show_page_end = max_show
            # 2. 当前页码数 + hale_show >= total_page
            if current_page + half_show >= total_page:
                show_page_end = total_page
                show_page_start = total_page - max_show + 1
            # 3. 总共需要的页码数 < max_show
            if total_page < max_show:
                show_page_start = 1
                show_page_end = total_page
    
            self.show_page_start = show_page_start
            self.show_page_end = show_page_end
            self.total_page = total_page
    
        # 数据切片的起点
        @property
        def start(self):
            return (self.current_page - 1) * self.per_page
    
        # 数据切片的终点
        @property
        def end(self):
            return self.current_page * self.per_page
    
        # 分页的html代码
        def page_html(self):
            tmp = []
            page_html_start = '<nav aria-label="Page navigation" class="text-center"><ul class="pagination">'
            page_html_end = '</ul></nav>'
            tmp.append(page_html_start)
            # 添加一个首页
            tmp.append('<li><a href="/{}?page=1">首页</a></li>'.format(self.url_prefix))
            # 添加一个上一页
            # 当当前页是第一页的时候不能再点击上一页
            if self.current_page - 1 <= 0:
                tmp.append(
                    '<li class="disabled"><a href="#" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a></li>')
            else:
                tmp.append(
                    '<li><a href="/{}?page={}" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a></li>'.format(
                        self.url_prefix, self.current_page - 1))
            # for循环添加要展示的页码
            for i in range(self.show_page_start, self.show_page_end + 1):
                # 如果for循环的页码等于当前页码,给li标签加一个active的样式
                if self.current_page == i:
                    tmp.append('<li class="active"><a href="/{1}?page={0}">{0}</a></li>'.format(i, self.url_prefix))
                else:
                    tmp.append('<li><a href="/{1}?page={0}">{0}</a></li>'.format(i, self.url_prefix))
            # 添加一个下一页
            # 当前 当前页已经是最后一页,应该不让下一页按钮能点击
            if self.current_page + 1 > self.total_page:
                tmp.append(
                    '<li class="disabled"><a href="#" aria-label="Previous"><span aria-hidden="true">&raquo;</span></a></li>')
            else:
                tmp.append(
                    '<li><a href="/{}?page={}" aria-label="Previous"><span aria-hidden="true">&raquo;</span></a></li>'.format(
                        self.url_prefix, self.current_page + 1))
            # 添加一个尾页
            tmp.append('<li><a href="/{}?page={}">尾页</a></li>'.format(self.url_prefix, self.total_page))
            tmp.append(page_html_end)
    
            page_html = "".join(tmp)
            return page_html
    

    调用分装代码

    class HostsList(views.View):
    
        @method_decorator(check_login)
        def get(self, request):
            hosts_obj = models.Hosts.objects.all()
            total_count = hosts_obj.count()
            current_page = request.GET.get('page', None)
            # 调用分页
            page_obj = mypage.MyPage(current_page, total_count, url_prefix='hosts_list', )
            data = hosts_obj[page_obj.start:page_obj.end]
            page_html = page_obj.page_html()
            return render(request, 'hosts_list.html', {'data': data, 'page_html': page_html, })
    

    4.3 Django内置分页

    from django.shortcuts import render
    from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
    
    L = []
    for i in range(999):
        L.append(i)
    
    def index(request):
        current_page = request.GET.get('p')
    
        paginator = Paginator(L, 10)
        # 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, 'index.html', {'posts': posts})
    
    

    内置分页HTML

    from django.shortcuts import render
    from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
    
    L = []
    for i in range(999):
        L.append(i)
    
    def index(request):
        current_page = request.GET.get('p')
    
        paginator = Paginator(L, 10)
        # 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, 'index.html', {'posts': posts})
    
  • 相关阅读:
    spring mvc给参数起别名
    聊聊分布式定时任务中间件架构及其实现--转
    Batch Normalization的算法本质是在网络每一层的输入前增加一层BN层(也即归一化层),对数据进行归一化处理,然后再进入网络下一层,但是BN并不是简单的对数据进行求归一化,而是引入了两个参数λ和β去进行数据重构
    终端安全工具 gartner 排名
    When Cyber Security Meets Machine Learning 机器学习 安全分析 对于安全领域的总结很有用 看未来演进方向
    DNS隧道之DNS2TCP实现——dns2tcpc必须带server IP才可以,此外ssh可以穿过墙的,设置代理上网
    DNS隧道之DNS2TCP使用心得教程——是可以用来穿透qiang的,ubuntu下直接apt install dns2tcp
    DNS隧道工具汇总——补充,还有IP over DNS的工具NSTX、Iodine、DNSCat
    Data Mining and Machine Learning in Cybersecurity PDF
    ES failed to notify ClusterStateListener java.lang.IllegalStateException: environment is not locked
  • 原文地址:https://www.cnblogs.com/wangph/p/9521600.html
Copyright © 2011-2022 走看看