• 69-django-forms组件源码刨析、cookie与session


    今日内容概要

    • forms组件源码

    • cookie与session

    今日内容详细

    forms组件源码

     1 """
     2 切入点:
     3     form_obj.is_valid()
     4 """
     5 def is_valid(self):
     6         """
     7         Returns True if the form has no errors. Otherwise, False. If errors are
     8         being ignored, returns False.
     9         """
    10    return self.is_bound and not self.errors
    11    # 如果is_valid要返回True的话 那么self.is_bound要为True self.errors要为Flase
    12   
    13   
    14 self.is_bound = data is not None or files is not None  # 只要你传值了肯定为True
    15 
    16 
    17 @property
    18 def errors(self):
    19         "Returns an ErrorDict for the data provided for the form"
    20         if self._errors is None:
    21             self.full_clean()
    22         return self._errors
    23 
    24 # forms组件所有的功能基本都出自于该方法
    25 def full_clean(self):
    26       self._clean_fields()  # 校验字段 + 局部钩子
    27     self._clean_form()  # 全局钩子
    28     self._post_clean() 
    29 
    30 
    31 # 通过看源码得到得第二种添加钩子提示的方式
    32  from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
    33 
    34     def clean_username(self):
    35         username = self.cleaned_data.get('username')
    36         if '666' in username:
    37             # self.add_error('username', '6个粑粑憨逼')
    38             raise ValidationError('6个粑粑憨逼')
    39         return username
    40         

    cookie与session

    """
    发展史
        1.网站都没有保存用户功能的需求 所有用户访问返回的结果都是一样的
            eg:新闻、博客、文章...
        
        2.出现了一些需要保存用户信息的网站
            eg:淘宝、支付宝、京东...
            
            以登陆功能为例:如果不保存用户登陆状态 也就意味着用户每次访问网站都需要重复的输入用户名和密码(你觉得这样的网站你还想用吗?)
            当用户第一次登陆成功之后 将用户的用户名密码返回给用户浏览器 让用户浏览器保存在本地,之后访问网站的时候浏览器自动将保存在浏览器上的用户名和密码发送给服务端,服务端获取之后自动验证
            早起这种方式具有非常大的安全隐患
            
            
            优化:
                当用户登陆成功之后,服务端产生一个随机字符串(在服务端保存数据,用kv键值对的形式),交由客户端浏览器保存
                随机字符串1:用户1相关信息
                随机字符串2:用户2相关信息
                随机字符串3:用户3相关信息
                之后访问服务端的时候,都带着该随机字符串,服务端去数据库中比对是否有对应的随机字符串从而获取到对应的用户信息
                
        
      
    但是如果你拿到了截获到了该随机字符串,那么你就可以冒充当前用户 其实还是有安全隐患的
    
    
    你要知道在web领域没有绝对的安全也没有绝对的不安全
    """
    cookie
        服务端保存在客户端浏览器上的信息都可以称之为cookie
      它的表现形式一般都是k:v键值对(可以有多个)
    session
        数据是保存在服务端的并且它的表现形式一般也是k:v键值对(可以有多个)
        
        
    下述内容暂时了解即可 先给我搞明白最简单的cookie与session使用再说话!
    还想了解见:https://www.cnblogs.com/Dominic-Ji/p/10886902.html
    token
        session虽然数据是保存在服务端的 但是禁不住数据量大
      服务端不再保存数据
          登陆成功之后 将一段用户信息进行加密处理(加密算法之后你公司开发知道)
        将加密之后的结果拼接在信息后面 整体返回给浏览器保存 
        浏览器下次访问的时候带着该信息 服务端自动切去前面一段信息再次使用自己的加密算法
        跟浏览器尾部的密文进行比对
    jwt认证
        三段信息
      (后期会讲 结合django一起使用) 
        
    总结:
        1.cookie就是保存在客户端浏览器上的信息
        2.session就是保存在服务端上的信息
        3.session是基于cookie工作的(其实大部分的保存用户状态的操作都需要使用到cookie)

    cooike

    1 '''
    2 cookie 是一个非常具体的东西,指的就是浏览器里面能永久存储的一种数据,仅仅是
    3 浏览器实现的一种数据存储功能。
    4 
    5 cookie由服务器生成,发送给浏览器,浏览器把cookie以kv形式保存到某个目录下的
    6 文本文件内,下一次请求同一网站时会把该cookie发送给服务器。由于cookie是存在
    7 客户端上的,所以浏览器加入了一些限制确保cookie不会被恶意使用,同时不会占据
    8 太多磁盘空间,所以每个域的cookie数量是有限的。
    9 '''

    session

     1 '''
     2 session 从字面上讲,就是会话。这个就类似于你和一个人交谈,你怎么知道当前和
     3 你交谈的是张三而不是李四呢?对方肯定有某种特征(长相等)表明他就是张三。
     4 
     5 session 也是类似的道理,服务器要知道当前发请求给自己的是谁。为了做这种区分,
     6 服务器就要给每个客户端分配不同的“身份标识”,然后客户端每次向服务器发请求的
     7 时候,都带上这个“身份标识”,服务器就知道这个请求来自于谁了。至于客户端怎么
     8 保存这个“身份标识”,可以有很多种方式,对于浏览器客户端,大家都默认采用 cookie
     9  的方式。
    10 
    11 服务器使用session把用户的信息临时保存在了服务器上,用户离开网站后session会
    12 被销毁。这种用户信息存储方式相对cookie来说更安全,可是session有一个缺陷:如
    13 果web服务器做了负载均衡,那么下一个操作请求到了另一台服务器的时候session会
    14 丢失。 

    Cookie操作

     1 # 虽然cookie是服务端告诉客户端浏览器需要保存内容
     2 # 但是客户端浏览器可以选择拒绝保存 如果禁止了 那么 只要是需要记录用户状态的网站登陆功能都无法使用了
     3 
     4 # 视图函数的返回值
     5 return HttpResponse()
     6 return render()
     7 return redirect()
     8 
     9 
    10 obj1 = HttpResponse()
    11 # 操作cookie
    12 return obj1
    13 
    14 obj2 = render()
    15 # 操作cookie
    16 return obj2
    17 
    18 obj3 = redirect()
    19 # 操作cookie
    20 return obj3
    21 # 如果你想要操作cookie,你就不得不利用obj对象
    22 
    23 
    24 """
    25 设置cookie
    26     obj.set_cookie(key,value)
    27 获取cookie
    28     request.COOKIES.get(key)
    29 在设置cookie的时候可以添加一个超时时间
    30     obj.set_cookie('username', 'jason666',max_age=3,expires=3)
    31     
    32     max_age
    33     expires
    34         两者都是设置超时时间的 并且都是以秒为单位
    35         需要注意的是 针对IE浏览器需要使用expires
    36 主动删除cookie(注销功能)
    37   request.delete_cookie('username')  
    38     
    39 """
    40 # 我们完成一个真正的登陆功能
    41 # 校验用户是否登陆的装饰器
    42 """
    43 用户如果在没有登陆的情况下想访问一个需要登陆的页面
    44 那么先跳转到登陆页面 当用户输入正确的用户名和密码之后
    45 应该跳转到用户之前想要访问的页面去 而不是直接写死
    46 """
    47 def login_auth(func):
    48     def inner(request,*args,**kwargs):
    49         # print(request.path_info)
    50         # print(request.get_full_path())  # 能够获取到用户上一次想要访问的url
    51         target_url = request.get_full_path()
    52         if request.COOKIES.get('username'):
    53             return func(request,*args,**kwargs)
    54         else:
    55             return redirect('/login/?next=%s'%target_url)
    56     return inner
    57 
    58 def login(request):
    59     if request.method == 'POST':
    60         username = request.POST.get('username')
    61         password = request.POST.get('password')
    62         if username == 'jason' and password == '123':
    63 
    64             # 获取用户上一次想要访问的url
    65             target_url = request.GET.get('next')  # 这个结果可能是None
    66             if target_url:
    67                 obj = redirect(target_url)
    68             else:
    69                 # 保存用户登陆状态
    70                 obj = redirect('/home/')
    71             # 让浏览器记录cookie数据
    72             obj.set_cookie('username', 'jason666')
    73             """
    74             浏览器不单单会帮你存
    75             而且后面每次访问你的时候还会带着它过来
    76             """
    77             # 跳转到一个需要用户登陆之后才能看的页面
    78             return obj
    79     return render(request,'login.html')
    80 
    81 
    82 @login_auth
    83 def home(request):
    84     # 获取cookie信息 判断你有没有
    85     # if request.COOKIES.get('username') == 'jason666':
    86     #     return HttpResponse("我是home页面,只有登陆的用户才能进来哟~")
    87     # # 没有登陆应该跳转到登陆页面
    88     # return redirect('/login/')
    89     return HttpResponse("我是home页面,只有登陆的用户才能进来哟~")
    90 
    91 
    92 @login_auth
    93 def logout(request):
    94     obj = redirect('/app01/login/')
    95     obj.delete_cookie('username')
    96     return obj
     1 from django.shortcuts import render, HttpResponse, redirect
     2 
     3 # Create your views here.
     4 
     5 from app01 import models
     6 
     7 
     8 def login_auth(func):
     9     def wrapper(request, *args, **kwargs):
    10         print(request.path)
    11         print(request.path_info)
    12         print(request.get_full_path())
    13         target_url = request.get_full_path()
    14         if request.COOKIES.get('username'):
    15             return func(request, *args, **kwargs)
    16         else:
    17             return redirect('/app01/login/?next=%s'%target_url)
    18 
    19     return wrapper
    20 
    21 
    22 @login_auth
    23 def home(request):
    24     return HttpResponse('欢迎来到app01_home页面')
    25 
    26 
    27 def login(request):
    28     if request.method == 'POST':
    29         username = request.POST.get('username')
    30         password = request.POST.get('password')
    31         if username == 'jason' and password == '123':
    32             print(request.GET.get('next'))
    33             target_url = request.GET.get('next')
    34             if target_url:
    35                 obj = redirect(target_url)
    36             else:
    37                 obj = redirect('/app01/home/')
    38             obj.set_cookie('username', 'jason666')
    39             return obj
    40         else:
    41             return HttpResponse('账号密码错误')
    42     return render(request, 'login.html')
    43 
    44 
    45 @login_auth
    46 def logout(request):
    47     obj = redirect('/app01/login/')
    48     obj.delete_cookie('username')
    49     return obj
    50 
    51 
    52 @login_auth
    53 def index(request):
    54     return HttpResponse('欢迎来到app01_Index页面')
    cookie版本登录认证

    cookie其他方法及参数

    获取Cookie
    
    request.COOKIES['key']
    request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)
    
    
    参数:
    
    default: 默认值
    salt: 加密盐
    max_age: 后台控制过期时间
    
    
    
    设置Cookie
    
    rep = HttpResponse(...)
    rep = render(request, ...)
    
    rep.set_cookie(key,value,...)
    rep.set_signed_cookie(key,value,salt='加密盐', max_age=None, ...)
    
    
    
    参数:
    
    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获取(不是绝对,底层抓包可以获取到也可以被覆盖)
    
    
    删除Cookie
    def logout(request):
        rep = redirect("/login/")
        rep.delete_cookie("user")  # 删除用户浏览器上之前设置的usercookie值
        return rep

    Session操作

     1 """
     2 session数据是保存在服务端的(存?),给客户端返回的是一个随机字符串
     3     sessionid:随机字符串
     4     
     5 1.在默认情况下操作session的时候需要django默认的一张django_session表
     6     数据库迁移命令
     7         django会自己创建很多表    django_session就是其中的一张
     8         
     9 
    10 django默认session的过期时间是14天
    11     但是你也可以人为的修改它
    12     
    13 
    14 设置session    
    15 request.session['key'] = value
    16 
    17 获取session
    18 request.session.get('key')
    19 
    20 设置过期时间
    21 request.session.set_expiry()
    22     括号内可以放四种类型的参数
    23         1.整数                        多少秒
    24         2.日期对象               到指定日期就失效
    25         3.0                                一旦当前浏览器窗口关闭立刻失效
    26         4.不写                        失效时间就取决于django内部全局session默认的失效时间
    27 
    28 清除session    
    29     request.session.delete()  # 只删服务端的 客户端的不删
    30     request.session.flush()  # 浏览器和服务端都清空(推荐使用)
    31 
    32 
    33 session是保存在服务端的 但是session的保存位置可以有多种选择
    34     1.MySQL
    35     2.文件
    36     3.redis
    37     4.memcache
    38     ...
    39     
    40 
    41 django_session表中的数据条数是取决于浏览器的
    42     同一个计算机上(IP地址)同一个浏览器只会有一条数据生效
    43     (当session过期的时候可能会出现多条数据对应一个浏览器,但是该现象不会持续很久,内部会自动识别过期的数据清除 你也可以通过代码清除)
    44     
    45     主要是为了节省服务端数据库资源
    46 """
    47 
    48 request.session['hobby'] = 'girl'
    49     """
    50     内部发送了那些事
    51         1.django内部会自动帮你生成一个随机字符串
    52         2.django内部自动将随机字符串和对应的数据存储到django_session表中
    53             2.1先在内存中产生操作数据的缓存
    54             2.2在响应结果django中间件的时候才真正的操作数据库
    55         3.将产生的随机字符串返回给客户端浏览器保存
    56     """
    57 request.session.get('hobby')
    58     """
    59     内部发送了那些事
    60         1.自动从浏览器请求中获取sessionid对应的随机字符串
    61         2.拿着该随机字符串去django_session表中查找对应的数据
    62         3.
    63             如果比对上了 则将对应的数据取出并以字典的形式封装到request.session中
    64             如果比对不上 则request.session.get()返回的是None
    65     """
    66   
    67   
    68 # 利用session实现登陆验证
     1 from django.shortcuts import render, HttpResponse, redirect
     2 
     3 
     4 # Create your views here.
     5 
     6 
     7 def login_auth(func):
     8     def wrapper(request, *args, **kwargs):
     9         target_url = request.get_full_path()
    10         if request.session.get('jason'):
    11             return func(request, *args, **kwargs)
    12         else:
    13             return redirect('/app02/login/?next=%s'%target_url)
    14 
    15     return wrapper
    16 
    17 
    18 def login(request):
    19     if request.method == 'POST':
    20         username = request.POST.get('username')
    21         password = request.POST.get('password')
    22         if username == 'jason' and password == '123':
    23             target_url = request.GET.get('next')
    24             if target_url:
    25                 obj = redirect(target_url)
    26             else:
    27                 obj = redirect('/app02/home/')
    28             request.session['jason'] = 'haha6666'
    29             request.session.set_expiry(30)
    30             return obj
    31         else:
    32             HttpResponse('账号或密码错误')
    33     return render(request, 'login.html')
    34 
    35 
    36 @login_auth
    37 def home(request):
    38     return HttpResponse('欢迎来到app_02home页面')
    39 
    40 
    41 @login_auth
    42 def index(request):
    43     return HttpResponse('欢迎来到app_02index页面')
    44 
    45 
    46 @login_auth
    47 def logout(request):
    48     request.session.flush()
    49     return redirect('/app02/login/')
    session版本登录认证

    django中的session方法

    # 获取、设置、删除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失效策略。

    django中的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配置

    CBV如何添加装饰器

     1 from django.views import View
     2 from django.utils.decorators import method_decorator
     3 """
     4 CBV中django不建议你直接给类的方法加装饰器
     5 无论该装饰器能都正常给你 都不建议直接加
     6 """
     7 
     8 # @method_decorator(login_auth,name='get')  # 方式2(可以添加多个针对不同的方法加不同的装饰器)
     9 # @method_decorator(login_auth,name='post')
    10 class MyLogin(View):
    11     @method_decorator(login_auth)  # 方式3:它会直接作用于当前类里面的所有的方法
    12     def dispatch(self, request, *args, **kwargs):
    13         return super().dispatch(request,*args,**kwargs)
    14     # @method_decorator(login_auth)  # 方式1:指名道姓
    15     def get(self,request):
    16         return HttpResponse("get请求")
    17 
    18     def post(self,request):
    19         return HttpResponse('post请求')

    今日测验

    1 """
    2 1.什么是cookie和session,你能描述一下它们的由来和工作机制吗(切勿糊弄,敷衍了事)
    3 2.django中如何操作cookie和session,请写出尽量多的操作方法,并针对session的操作
    4 方法详细内部发生的事情,django默认的session失效时间是多久(切勿糊弄,敷衍了事)
    5 3.面相对象中的__init__和__new__的区别是什么,利用__new__可以实现什么
    6 4.如何给CBV添加装饰器,列举你所知道的几种方式
    7 """

    今日内容回顾

    forms组件源码

     1 #入口:form_obj.is_valid()
     2 
     3 # 校验字段和钩子函数的执行
     4 
     5 # 报错提示 其实可以有两种方式(针对局部钩子函数)
     6   1.self.add_error()
     7   2.raise ValidationError()
     8 
     9 """
    10 python源码里面使用最频繁的其实就是反射
    11 """
    forms组件源码

    cookie与session

     1 # cookie与session产生原因
     2         由于http协议是无状态的
     3 
     4 # cookie概念
     5     服务端设置保存在客户端浏览器上的键值对(只要符合前面的定义都可以叫cookie)
     6     cookie虽然是服务端设置的但是浏览器可以选择不保存
     7   
     8 # session概念
     9     存储在服务端上的键值对(用来标识当前用户)    需要基于cookie才能工作
    10   其实大部分的保存状态的实现都需要基于cookie来做
    11 
    12 # 在web领域没有绝对的安全
    13     基本上防御措施都需要程序员自己写代码完善,并且之内完善没法杜绝
    cookie与session

    django操作cookie

     1 # 需要借助于HttpResponse对象
     2 
     3 # 设置cookie
     4 obj.set_cookie(key,value)  
     5 # 超时时间
     6 obj.set_cookie(key,value,max_age/expires)  
     7     expires  针对IE需要用这个参数              数字是以秒为单位
     8 # 加盐
     9 obj.set_signed_cookie(key,value,salt='')
    10 # 获取
    11 request.COOKIES.get(key)
    12 request.get_signed_cookie(key,salt='')
    13 # 删除
    14 obj.delete_cookie(key)
    15 
    16 """
    17 校验用户是否登陆才能访问视图函数的装饰器
    18     能够记录用户在没有登陆之前想要访问的页面,登陆之后跳转到对应的页面
    19     request.path
    20     request.path_info
    21     request.get_full_path()
    22 """
    django操作cookie

    django操作session

     1 """
     2 1.session是存储在服务端的 django默认情况下是需要借助于django_session表
     3 来存储数据 也就意味着如果你想要操作session那么必须先执行数据库迁移命令让
     4 django先把django_session表创建出来(no such table:django_session)
     5 
     6 2.django默认的session过期时间是14天
     7 
     8 3.session存储在服务端 可以有很多地方存储
     9     1.表
    10     2.文件
    11     3.缓存
    12     4.其他
    13     ...
    14 """
    15 # 设置
    16 request.session[key] = value
    17     """
    18     三件事
    19     """
    20 # 获取
    21 request.session.get(key)
    22     """
    23     三件事
    24     """
    25 # 删除
    26 request.session.delete()
    27 request.session.flush()
    28 # 设置超时时间
    29 request.session.set_expiry()
    30     1.数字                                                            秒数
    31   2.datetime/timedelta格式                        日期格式
    32   3.None                                                         参加全局失效策略
    33   4.0                                                                 窗口关闭即失效
    34 
    35 """
    36 基于session实现用户登陆
    37 
    38 有时候如果多个视图函数都需要使用到一些数据的话,你也可以考虑将该数据存储到django_session表中,方便后续的使用
    39     eg:
    40         登陆验证码(bbs作业会涉及到)
    41 """
    django操作session

    CBV如何添加装饰器

     1 """
     2 django针对CBV添加装饰器需要你导入一个模块
     3 """
     4 from django.utils.decorators import method_decorator
     5 
     6 # 第一种  
     7 class MyCBV(View):
     8   def get(self,request):
     9     return HttpResponse()
    10   
    11   @method_decorator(login_auth)
    12      def post(self,request):
    13     return HttpResponse()
    14   
    15 #  第二种
    16 @method_decorator(login_auth,name='post')
    17 @method_decorator(index_de,name='get')
    18 class MyCBV(View):
    19   def get(self,request):
    20     return HttpResponse()
    21  
    22      def post(self,request):
    23     return HttpResponse()
    24 
    25 # 第三种
    26 class MyCBV(View):
    27   @method_decorator(login_auth)
    28   def dispatch(self,request,*args,**kwargs):
    29     """
    30     看CBV源码可以得出 CBV里面所有的方法在执行之前都需要先经过
    31     dispatch方法(该方法你可以看成是一个分发方法)
    32     """
    33     return super().dispatch(request,*args,**kwargs)
    34     
    35   def get(self,request):
    36     return HttpResponse()
    37  
    38      def post(self,request):
    39     return HttpResponse()
    CBV如何添加装饰器

    作业

    """
    1.整理今日内容到博客
    2.利用session实现登陆验证
    3.复习django阶段所学所有知识点,好好整理回顾(后面没时间了)
    4.预习内容:
        https://www.cnblogs.com/Dominic-Ji/p/10881214.html
        
        django中间件
        auth模块
    """
  • 相关阅读:
    第一章:模型层
    第一章:模型层
    第一章:模型层
    第一章:模型层
    第一章:模型层
    第一章:模型层
    第一个Django应用
    第一个Django应用
    第一个Django应用
    第一个Django应用
  • 原文地址:https://www.cnblogs.com/wgwg/p/13051430.html
Copyright © 2020-2023  润新知