web框架。Django-- 2.30(补)时间时区设置

时间:2024-04-17 12:24:48

 

model.UserInfo._meta.app_label                       
#获取该类所在app的app名称

model.UserInfo._meta.model_name
#获取该类对应表名(字符串类型)

model.UserInfo._meta.get_field(\'username\')
#获取该类内指定字段信息(对象)

model.UserInfo._meta.fields
#获取该类内所有字段对象

model.UserInfo._meta.get_fields
#获取该类内所有字段信息(对象),包含反向关联的字段

model.UserInfo._meta.many_to_many
#获取该类内多对多字段信息
        
model.UserInfo._meta.get_field(\'username\').verbose_name
#获取该类内‘username’字段,verbose_name 的值

 

--------
  Book:
  list_filter=["state","publish","authors"]
  
  每一个字段相关信息:
   
       字段字符串  : "state"
       字段对象    : Book._meta.get_field("state")
       字段关联数据: 
              if---choice类型字段:
                    字段对象.choices
                    
              if---ForeignKey,ManytoMany:
                    字段对象.rel.to.objects.all()
       

  字段信息封装成类:
  
     class FilterField(object):
           def __init__(self,filter_field_name,filter_field_obj):
               self.filter_field_name=filter_field_name
               self.filter_field_obj=filter_field_obj

               
           def get_data(self):
               if isinstance(self.filter_field_obj,ForeignKey) or isinstance(self.filter_field_obj,ManyToManyField):
                   return self.filter_field_obj.rel.to.objects.all()
               elif self.filter_field_obj.choices:
                   return self.filter_field_obj.choices
               else:
                   pass
       
       
       state=FilterField("state",state_obj)

 

obj = models.UserInfo.objects.create(...)

#源码位置
#from django.db.models.options import Options
#from django.db.models.fields.reverse_related import ManyToOneRel

field = obj._meta.related_objects[0]
#拿到当前记录对象所对应的反向关联字段的queryset

print(field[0].limit_choices_to)
#拿到对应的limit_choices_to的字典的数据

print(field[0].related_name)
#拿到related_name属性所对应的值

print(field[0].field_name)
#拿到反向关联字段里的关联本表的字段

print(field[0].field.model._meta.model_name)
#拿到反向关联字段所在类名称
Django中_Meta

 

一,DIY一个web框架

1.1什么是web框架

1.2用socket模拟B-S的服务端

1.3,浏览器端的network查看

1.4,request格式

1.5,response格式

1.6,初识wsgiref

1.7,wsgiref进行路由控制

1.8,wsgiref做的web框架结构

 二、最广泛使用的web框架--Django

2.1,Django简介

2.2,Django安装,命令

2.3,Django简单demo

 2.4,css/js等静态资源配置

2.5,路由控制之--正则表达式匹配(re_path)

2.6、响应体数据格式(render,HttpResponse,jsonResponse,FileResponse)

2.7,路由控制之--正则表达式匹配(re_path(..?P<param>))

2.8,路由控制之--分发(re_path(r"app01/",include(("app01.urls"))))

2.9,路由控制之--反向解析(在HTML里或在views里)

2.10,反向解析时别名冲突怎么办---使用名称空间(re_path(r"app01/",include(("app01.urls", "app01"))))

2.11,re_path升级版---django2.x版本的path

2.12,视图函数之request对象

2.13,渲染变量 {{  obj  }}

2.14,过滤器    {{ obj | filter_name:param}} 参数个数<=2

2.15,渲染标签:{% url / csrf_token / if / for / with %} 参数个数>2

2.16,自定义标签,过滤器(if条件判断只能用过滤器)

2.17,模板语法之继承

2.18 (补)inclution_tag标签用法(计算变量值得到HTML代码)

 2.19 (补) request.POST取数组只取到最后一个? 

 2.20 (补) 前端和path中的相对路径和绝对路径

2.21(补)Django有两种静态文件

2.22(补)Django用户表原生字段

2.23(补)CNBLOG上传头像到media目录

2.231(补)  CRM通过视图函数下载文件

2.24 (补) Django-admin(不是项目必须)

2.25 (补) 时间不对怎么办(这样调整后数据库存的时间和显示的时间不一样)

2.26(补) Django事务控制

2.261(补) Django-数据库加锁(行锁)

2.27(补) Django发送邮件

2.28(补) Django批量发现权限URL正则(发现反向解析表达式+url正则)

2.29(补) 同一个页面向后端同一个视图函数提交两种post请求,如何进行甄别?

  • 在Django的配置文件settings.py中,有两个配置参数是跟时间与时区有关的,分别是TIME_ZONEUSE_TZ
  • 如果USE_TZ设置为True时,Django会使用系统默认设置的时区,即America/Chicago,此时的TIME_ZONE不管有没有设置都不起作用。
  • 如果USE_TZ 设置为False,而TIME_ZONE设置为None,则Django还是会使用默认的America/Chicago时间。若TIME_ZONE设置为其它时区的话,则还要分情况,如果是Windows系统,则TIME_ZONE设置是没用的,Django会使用本机的时间。如果为其他系统,则使用该时区的时间,设置USE_TZ = FalseTIME_ZONE = \'Asia/Shanghai\', 则使用上海的UTC时间。

 

一,DIY一个web框架

1.1什么是web框架

web框架全名web应用框架
web开发: html css js
web应用:类似socket,客户端是浏览器,作用是接受socket请求 处理 响应数据字符串

1.2用socket模拟B-S的服务端

浏览器端发送的是报文,就是一堆字符串,包括URL,请求方式等
响应的数据也有固定格式:响应首行+响应头+响应体  响应体和前面的用两个\r\n隔开,前面的所有用一个\r\n分隔

import socket
soc=socket.socket()
soc.bind(127.0.0.1,  8800)
soc.listen()

while True:
  conn,addr=soc.accept()
  data=conn.recv(1024)
  conn.send(b"helloworld")
  conn.close()
上面的方式发出去浏览器解析不了,提示“收到的响应无法解析”
因为要按http协议的格式发,所以要HTTP/1.1 200 ok\r\n\r\nhelloworld这样发

1.3,浏览器端的network查看

如果响应的是<h1>helloworld</h1>
在浏览器端审查,network-request,preview,response
response可以看到响应的字符串,preview看到的是浏览器渲染后的标签

1.4,request格式

请求首行+请求头+请求体
请求首行必须要有,请求头可加可不加,反正浏览器已经封装了。请求体不是每次请求都有
请求首行:   
    GET /  HTTP/1.1
           方法(get/post),URI(/form/entity), 协议版本HTTP/1.1
           get一般做查询操作,没有请求体,数据放在url后面以?分隔开,post一般做数据库更新操作,有请求体
请求头:
   包括若干键值对,是这次请求的信息,比如host表示请求的主机名,connection:(keep-alive表示这次请求后过3000ms再断开连接,close表示请求后立马断开),content-type             表示请求的数据类型,content-length表示本次请求发送的字节数
请求体:
           get请求没有请求体,POST请求体里放参数。数据是放在URL后面以?分隔开,最后在接收端收到的URI里面
           get请求与post请求不同点有三:数据放的地方;数据大小;服务器端获取数据的方式。
服务器端怎样区分请求首行,请求头,请求体?
          服务器端接收的报文里,遇到第一个\r\n的前面是请求首行,后面的每个\r\n分割的是请求头的键值对,直到遇到\r\n\r\n后面就是请求体

1.5,response格式

响应首行+响应头+响应体
响应格式跟请求一样,也是/r/n  /r/n/r/n来分隔
响应首行:HTTP/1.1 200 ok 协议版本 状态码 状态码说明。
         2xx表示成功,3xx表示重定向,4xx表示资源未找到,5xx表示服务器代码运行出错,1xx表示请求正在处理。
         重定向理解:比如某网站域名由A.com变成了A1.com,如果有浏览器访问域名A.com,服务器会给它响应一个重定向状态码3xx,让浏览器重新发请求到A1.com,这个过程就叫重           定向。重定向对浏览器来说相当于发了两次请求
响应头:键值对格式,例如:date 时间 content-length:长度  content-type:格式
响应体:发给浏览器需要渲染到页面的内容 

 1.6,初识wsgiref

URL格式:   协议://IP:端口/路径?a=1
根据路径不同返回不同的页面
wsgiref模块,作用是解析请求数据,把请求按http协议解析成字典,或者按http协议把数据封装成响应
wsgiref用法:
from wsgiref.simple_server import make_server
def application(environ, start_response):
   #请求路径。请求的参数封装在这里面
   path=environ.get("PATH_INFO")
   #给响应添加响应首行和响应头
   #响应头[("content-type","xml")]
   start_response(\'200  ok\', [])
   #给响应添加响应体用return
   return  [b\'<h1>hello web</h1>\']
   
# 封装server
httpd=make_server("", "8080",application)
# 开始监听请求
httpd.serve_forever()

 1.7,wsgiref进行路由控制

之前看到的发送第一个请求的时候,会附加发送一个/favicon.ico的get请求,这个是干什么的呢?而且两次返回的内容一样。那页面的内容是哪一次返回的内容呢,这样不会覆盖吗?
答:这个是网站标题的icon图标。是第一次请求的内容,因为favicon.ico的请求是浏览器自动发的,为了获取到标题栏的图标,而且该请求的请求头中有个字典,accept:image/webp image/apng...指的是本次请求期望得到一个图片。服务器端要以rb方式打开图片发过去就行了 
----------------------------------------------------------------
from wsgiref.simple_server import make_server
def index(environ):
    with open(\'index.html\', \'r\', encoding="utf-8") as f:
        data = f.read()
    return [data.encode(\'utf8\')]
def login(environ):
    with open(\'login.html\', \'r\', encoding="utf-8") as f:
        data = f.read()
    return [data.encode(\'utf8\')]
 
 
def application(environ, start_response):
    # 请求路径
    path = environ.get(\'PATH_INFO\')
    path_func = {\'/\': index, \'/login.html\': login}
    response_data = [b\'404!\']
    if path in path_func.keys():
        # 处理请求函数需要用到environ
        response_data = path_func[path](environ)
    # 状态码 响应头
    start_response(\'200 ok\',[])
    return response_data
httpd = make_server("127.0.0.1",8089,application)
httpd.serve_forever()
-----------------------------------------------

1.8,wsgiref做的web框架结构

优化代码,从耦合方面:
main.py:入口函数
urls.py:路径与视图函数的映射关系 --url控制器
views.py:视图函数,固定有一个形参environ --视图函数
templates文件夹:存放HTML文件 --模板部分
models.py:与数据库相关
结构:
yuan
--urls.py
--main.py
--views.py
--templates
   --login.html
   --index.html
现在我们就实现了一个简单的web框架:名叫"yuan"
用户只需要修改urls views templates就可以了

1.main.py

from wsgiref.simple_server import make_server

\'\'\'
main.py: 程序入口
\'\'\'
def application(environ, start_response):
    # 请求路径
    path = environ.get(\'PATH_INFO\')
    from urls import path_func
    response_data = [b\'404!\']
    if path in path_func.keys():
        # 处理请求函数需要用到environ
        response_data = path_func[path](environ)
    # 状态码 响应头
    start_response(\'200 ok\',[])
    return response_data
httpd = make_server("127.0.0.1",8089,application)
httpd.serve_forever()

2.urls.py

from views import index,login,auth

\'\'\'
urls.py: 存放请求路径对应的方法
\'\'\'
path_func = {
    \'/\': index,
    \'/login.html\': login,
    \'/auth\': auth
}

3.views.py

from urllib.parse import parse_qs
from models import query_user

\'\'\'
views.py: 存放各路径的处理函数
\'\'\'
def index(environ):
    with open(\'templates/index.html\', \'r\', encoding="utf-8") as f:
        data = f.read()
    return [data.encode(\'utf8\')]
def login(environ):
    with open(\'templates/login.html\', \'r\', encoding="utf-8") as f:
        data = f.read()
    return [data.encode(\'utf8\')]
def auth(environ):
    # 从请求体中获取username&passwd
    try:
        request_body_size = int(environ.get(\'CONTENT_LENGTH\',0))
    except (ValueError):
        request_body_size = 0
    request_body = environ[\'wsgi.input\'].read(request_body_size)
    data = parse_qs(request_body)
    user = data.get(b\'username\')[0].decode(\'utf8\')
    pwd = data.get(b\'passwd\')[0].decode(\'utf8\')

    # 从db2数据库中获取username&passwd
    pwd_db2 = query_user(user)
    if pwd_db2 and pwd==pwd_db2:
        return [\'登录成功!\'.encode(\'gbk\')]
    else:
        return [b\'<h2>Authcation Failure..</h2>\']

4.models.py

\'\'\'
models.py: 存放数据库处理逻辑
\'\'\'
import ibm_db
conn = ibm_db.pconnect("database=POBC; "
                       "hostname=localhost; "
                       "port=50000; "
                       "protocol=tcpip; "
                       "uid=administrator; "
                       "pwd=wyz", "", "")

# 根据username在SYS_ORG_TB表中查密码
def query_user(username):
    sql = "SELECT PASSWD FROM SYS_USER_TB " \
          "WHERE USERNAME=\'{0}\'".format(username)
    stmt = ibm_db.exec_immediate(conn, sql)
    tuple = ibm_db.fetch_tuple(stmt)
    # 查到了就返回密码,没查到返回False
    if tuple:
        return tuple[0]
    else:
        return False

 二、最广泛使用的web框架--Django

2.1,Django简介

flask 和 Django,flask是轻量级,Django是重量级
MVC模型与MTV模型

M代表模型:负责业务对象和数据库的关系映射----models.py
T代表模板 负责如何把页面展示给用户----templates
V代表视图 负责业务逻辑,并在适当时候调用M和T----views.py
除了以上三层,还需要一个URL分发器,作用是将一个个URL页面请求分发给不同的view处理
view再调用相应model和template。

当某人请求你网站的某一页面时——比如说, "/polls/34/" ,Django 将会载入 mysite.urls 模块,因为这在配置项 ROOT_URLCONF =mysite.urls中设置了。

 

 

 

 2.2,Django安装,命令

第一步:Django安装(2.2是版本号):pip3 install Django==2.2

安装成功会出现django-admin.exe和django-admin.py文件

 

第二步:先创建一个Django项目,名称为“mysite”

先进入想要创建文件的目录,再输入以下命令:

python C:\Users\Administrator\AppData\Local\Programs\Python\Python36-32\Scripts\django-admin.py   startproject   mysite

第三步:然后在“mysite”目录下创建应用“blog”

进入“mysite”目录,输入命令:

python manage.py startapp blog

-------------目录层级结构-----------

一个项目可以有多个应用。比如微信是一个项目,里面有支付应用,聊天应用
与项目相关的东西放在项目的文件夹里

先记住四个文件:
models.py
views.py
settings.py
urls.py
再创建一个templates文件夹,用来放HTML文件

第四步:启动Django,输入命令

vim project/settings.py     把此处该为ALLOWED_HOSTS = [\'*\']    # 表示允许外部地址访问Django项目

python manage.py runserver  0.0.0.0:8080    # 0.0.0.0表示外部地址也能访问

第五步:访问http://127.0.0.1:8080/

2.3,Django简单demo

浏览器访问 localhost:8000/timer  显示当前时间,和数据库中所有书籍

第一步:

settings.py里:    \'DIRS\': [os.path.join(BASE_DIR,\'templates\')],  # 模板HTML

        \'app01\',      # 应用

# MySQL数据库配置,修改NAME和PASSWORD
DATABASES = {
\'default\': {
\'ENGINE\': \'django.db.backends.mysql\',
\'NAME\': \'orm\',
\'USER\': \'root\',
\'PASSWORD\': \'mysql8\',
\'HOST\': \'127.0.0.1\',
\'PORT\': 3306
}
}

第二步:

项目目录下的__init__.py

import pymysql

pymysql.install_as_MySQLdb()

第三步:

urls.py里:    path(\'timer/\', views.timer)

第四步:

views.py里:  

from django.shortcuts import render

def timer(request): #请求函数
import time
ctime = time.time()
books = Book.objects.all()
Book.objects.create(title="python编程思想")
return render(request,"timer.html",{\'date\':ctime,\'books\':books})

models.py里:

from django.db import models

class Book(models.Model): #数据库表
id=models.AutoField(primary_key=True)
title=models.CharField(max_length=32)

第五步:templates下建模板timer.html:

    <h1>当前时间:{{ date }}</h1>
<h2>书籍列表</h2>
{% for book in books %}
<p>{{ book.title }}</p>
{% endfor %}

第六步:

python manage.py makemigrations
python manage.py migrate

访问:http://127.0.0.1:8000/timer/

 

PS:错误处理--

如果Python解释器是3.4以上且Django是2.0以上还要注释掉:
..\Python37\Lib\site-packages\django\db\backends\mysql\base.py里的
if version < (1, 3, 13):
  raise ImproperlyConfigured(\'mysqlclient 1.3.13 or newer is required; you have %s.\' % Database.__version__)

如果报错:AttributeError: \'str\' object has no attribute \'decode\'

query = query.decode(errors=‘replace’)  将decode修改为encode即可

--安装下面的包,不需要在__init__.py中导入pymysql,也不需要改源代码

pip3 install mysqlclient

2.4,css/js等静态资源配置

templates里HTML发给客户端后想让上面的css,js生效,那么引入路径必须写服务器的路径,因为HTML是发送给客户端去执行的
这些css,js属于静态资源

在app下创建一个静态文件夹statics里面有app01放jquery,js,css。
在settings.py里写上:
STATIC_URL = \'/static/\'
STATICFILES_DIRS = [os.path.join(BASE_DIR, "statics")]   只访问js,css等可以不用写这个,先从项目下去找static文件夹,再从应用下去找static文件夹
表明通过地址栏的路径别名\'/static/\'对应去找statics文件夹
STATIC_URL与STATICFILES_DIRS是相对应的。

例如:django_project--statics--app01--jquery、css、js
访问:127.0.0.1:8000/static/app01/jquery就得到jQuery了
127.0.0.1:8000/static/app01/css就得到css了
127.0.0.1:8000/static/app01/jquery就得到jQuery了
咱们页面的的css跟js用别名来引入
<link rel="stylesheet" type="text/css" href="/static/app01/css">
<script type="text/javascript" src="/static/app01/jquery"></script>
<script type="text/javascript" src="/static/app01/js"></script>

如果是其他文件例如.xls等可以通过这种方式来下载

如果保持默认配置:STATIC_URL = \'/static/\'

在每个app下创建static目录:

App01/static/app01/xxx.css  -->  127.0.0.1:8000/static/app01/xxx.css

App02/static/app02/xxx.css  -->  127.0.0.1:8000/static/app02/xxx.css

当然需要注册App01和App02

templates也是同样的操作

2.5,路由控制之--正则表达式匹配(分组)

使用re_path好处:

进行权限配置URL发现的时候可以将URL配置转变为权限的正则表达式,而用path的路径不能变成正则表达式

使用re_path切记:

必须进行精确匹配,加上开始和结束符号: “^” “$”

(如果用正则去匹配urls路由跟方法可以多对一)

urls.py里引入:

from django.urls import re_path
# 路由配置 路径--->视图函数
# 这个是用正则表达式去匹配,匹配成功就执行后面的函数
re_path(r\'^articles/2003/$\', views.special_case_2003)  # 默认会传入request:special_case_2003(request)
re_path(r\'^articles/([0-9]{4})/$\', views.special_case)  #这是分组匹配,相当于special_case_2003(request, year)  views.py方法里可以叫year或别的名字

re_path(r"^$", views.index)   #这样写就是直接访问ip:port返回首页
#只要分组就把分组匹配结果作为参数传入
上面两个,如果访问localhost:8000/articles/2003/ 会执行第一个special_case_2003
因为从前向后匹配,匹配上了就跳出

views.py里:
from django.shortcuts import HttpResponse
def special_case_2003(request):
return HttpResponse("<h1>2003</h1>") # 这里面放响应体


*若要从URL中捕获一个值,只需要在它周围放置一对圆括号
*不需要添加一个前导的反斜杠,因为每个URL都有,例如应该是^articles而不是^/articles
*每个正则表达式前面的\'r\'是可选的但是建议加上。它告诉python这个字符串是“原始的”--字符串中的任何字符都不应该转义

2.6、响应体数据格式(render,HttpResponse,jsonResponse,FileResponse)

1,render前面看过,是响应页面的静态或者页面上带固定位置,固定个数的参数的资源

2,httpresponse响应一串字符串,请求体的内容

return HttpResponse(json.dumps(sys_org,ensure_ascii=False), content-type="application/json,charset=utf-8")

3,json返回的是json格式,前端不用反序列化,拿来就能直接用。

from django.http import JsonResponse
# Create your views here.

def ajax(request):
    response = {\'org\':[{\'type\': None, \'name\': \'其他\'}, {\'type\': None, \'name\': \'*部门\'}]}
    return JsonResponse(response)

4,重定向请求。127.0.0.1:8000/books/

from django.shortcuts import redirect

# Create your views here.

def add_book(request):
    return redirect("/books/")

 

上面两种用法是等价的。

2.7,路由控制之--正则表达式匹配(有名分组)

re_path(r\'^articles/(?P<y>[0-9]{4})/(?P<m>[0-9]{2})/$\', views.special_case)
用“?P<year>”给每个组取个名字(相当于位置形参),他匹配还是按数字匹配,这样写传参就是这样传:views.special_case(request, y=2012, m=12)

在使用的时候,可以不用管y m的顺序,但是必须要用y m。
special_case(request, y, m)或special_case(request, m, y)

2.8,路由控制之--分发

一个项目中应用可以有多个,app01,app02,app03...
假如有10个应用,每个应用100个URL。把1000个URL写在项目的urls.py里看起来就太复杂了
怎么把他们分开呢?

在app01里新建一个urls.py文件表示当前应用的路由,把原来全局urls.py复制进去
在app01--urls.py里写自己的路径,

如果这样写:
在全局的里面引入一下:re_path(r"app01/",include("app01.urls")) 或re_path(r"^app01/",include("app01.urls"))
127.0.0.1:8000/app01/articles/2003/
127.0.0.1:8000/app01/articles/2004/
如果有app02
127.0.0.1:8000/app02/a2/2003/
127.0.0.1:8000/app02/a2/2004/

如果这样写:
re_path(r"^",include("app01.urls"))
就这样访问:
127.0.0.1:8000/articles/2003/
127.0.0.1:8000/articles/2004/
127.0.0.1:8000/a2/2003/
127.0.0.1:8000/a2/2004/

2.9,路由控制之--反向解析(在HTML里或在views里):

引子--登陆验证:
path(\'login/\', views.login)

def login();
  return render(request, "login.html")

<form action="http://127.0.0.1/login" method="post">
用户名:<input type="text" name="user">
密码:<input type="password" name="pwd">
<input type="submit" values="登录">
</form>

action代表一个路径,如果点击提交,就又返回这个页面,同一个路由地址,请求方式不同。
所以在方法里要判断,如果是get请求就返回页面,如果是post请求就校验
试一下:get和post确实都得到同一个页面(要在settings.py里MIDDLEWARE的...csrf...注释)
可以在视图的login方法里看,通过request.method属性得到请求方式
if request.method=="GET":
  #返回登录页面
else:
  #得到GET或POST请求里的数据,用字典来存放
  request.GET
  request.POST
  #取数据
  request.POST.get("user")
  request.POST.get("pwd")
POST请求把上次get请求的页面覆盖了。

正式开始说反向解析:

第一种,在HTML文件里反向解析:
因为URL里的路径\'login/\'会经常变,变了无所谓,但是点提交就不行了
因为写在页面里的action也要变才行。所以就有了反向解析:给路径取别名“Log”
------反向解析-----

得到绝对路径。path(\'del_book/\', views.delete_book, name="del_book"),

{% url \'del_book\' %} 得到 \'/del_book/\'。

path(\'login/\', views.login, name="Log")
action="{% url \'Log\' %}"   #不带参数

action="{% url \'Log\' 1 2 3 %}"   #带分组参数的要传参数,有几个分组就传几个参数

是在render渲染的时候处理的,之前渲染变量,这里渲染URL
模板语法有两个:
{{ }} 和 {% %}

第二种,在views里反向解析:
re_path(r\'^articles/2003/$\', views.special_case_2003, name=\'s_c_2003\')
re_path(r\'^articles/([0-9]{4})/$\', views.special_case, name=\'s_c\'),
--
在任何views里都可以进行反向解析:
from dfango.urls import reverse
url = reverse("s_c_2003") #没有分组的,反向解析直接得到路径
url = reverse("s_c", args=(1111,))
#有分组的,要加参数,随便加,只要符合分组正则表达式就可以,得到articles/1111

应用:反向解析带原页面的请求参数:

--渲染方法:{% memory_previous_param request \'rbac:menu_edit\' menu.id %}

 

@register.simple_tag
def memory_previous_param(request, name, *args, **kwargs):
\'\'\'
渲染时在URL里保存上次请求的参数
需求:“一级菜单列表 http://127.0.0.1:8000/rbac/menu/list/
选择某个一级菜单后,http://127.0.0.1:8000/rbac/menu/list/?mid=1
然后点新增/修改/删除,完成后,一级菜单未选中”的问题
解决方法:需要在选中某个一级菜单时,将之前渲染的“新增按钮”URL:
http://127.0.0.1:8000/rbac/menu/edit/3/
变成:
http://127.0.0.1:8000/rbac/menu/edit/3/?_next=\'mid=1\' 其中\'mid=1\'需要转义
:param request: 请求对象,用于获取当前页面的GET请求参数
:param name: 反向解析的URL别名
:param args: 反向解析参数1
:param kwargs: 反向解析参数2
:return:
\'\'\'
basic_url = reverse(name, args=args, kwargs=kwargs)
if not request.GET:
return basic_url
q = QueryDict(mutable=True)
q[\'_next\'] = request.GET.urlencode()
return "%s?_next=%s" % (basic_url, q.urlencode())

============

2.10,反向解析时别名冲突怎么办---使用名称空间

用在反向解析的过程中
多个app里有相同的别名怎么办?

127.0.0.1:8000/app01/index
127.0.0.1:8000/app02/index

如果不用名称空间,在app01和app02的view里反向解析index得到的都是/app02/index
所以有个覆盖问题得到的都是app02

所以在全局的里面分别规定名称空间:
re_path(r"app01/",include(("app01.urls", "app01")))
re_path(r"^app02/",include(("app02.urls", "app02")))

或者这样写:

总的urls.py里进行分发:

分urls.py中写app_name:

 

 反向解析时:reverse("polls:index")

反向解析的时候指定名称空间:
reverse("app01:index")
reverse("app02:index")

 

2.11,re_path升级版---django2.x版本的path

看看解决什么问题:
re_path(\'articles/(?P<year>[0-9]{4})/\',year_archive)
re_path(\'articles/(?P<article_id>[a-zA-Z0-9]+)/detail/\', detail_view)
re_path(\'articles/(?P<article_id>[a-zA-Z0-9]+)/edit/\', edit_view)
re_path(\'articles/(?P<article_id>[a-zA-Z0-9]+)/delete/\', delete_view)
上面写法有两个问题
1.匹配到的数字,实际传的参数是字符串,能不能让Django完成数据类型转换?
2.同样的正则表达式写了三次
Django提供的path解决这个问题:
path(\'articles/<int:year>/\',year_archive)  #year是分组名称
除了int转换器,还有那些转换器呢?
    str--默认,匹配除了"/"之外的非空字符串
    slug---匹配字母、数字以及横杠、下划线组成的字符串
    uuid--匹配格式化的UUID,例如0932093-3232-4343-3434434
    path--匹配任意非空字符串,包括"/",除了"?"
处了这些,还可以自定义转换器:
新建一个转换器模块:url_convert.py
里面建一个类
class one_convert:
  regex="[0-9]{2}" #只能叫regex
  def to_python(self, value):#匹配
    return int(value)
  def to_url(self, value):#反向解析url
    return \'%04d\' % value

怎么使用呢?在urls.py里:
from django.urls import register_converter
from app01.url_convert import one_convert
register_converter(one_convert, "two_w")

path(\'articles/<two_w:month>/\',month_archive)

这样,就解决了上面提出的两个问题。

 

2.12,视图函数之request对象

views.py:接收请求,返回响应,request存放请求来的所有信息

请求方式get/post:request.method
请求数据:request.GET 和 request.POST
请求路径:request.path #不包括get数据部分
请求头:request.META
request的方法:
request.get_full_path() #包括get的数据部分
request.is_ajax() #判断是不是ajax请求

响应:
return HttpResponse(\'123\') #响应字符串
return render(request, "index.html", {"name":name, "age":age}) #如果要把变量放在页面就有第三个参数

2.13,渲染变量 {{  obj  }}

模板文件不是单纯的HTML文件
模板文件里可以包括模板语法
字典,列表,对象怎么放到模板里呢?
模板语法只有两个:渲染变量{{ }}和渲染标签{% %}
return render(request, "index.html",locals()) #这样就把局部变量都传入了

模板语法之深度查询;用点"."
例如:
a=[1,2,3]
b={\'name\':\'alex\',\'age\':32}
在传给模板之后{{ a }} {{ b }}
要取里面的值就叫“深度查询”:
{{a.1}} 取到的是2
{{b.name}} 取到的是\'alex\'
如果有对象也是按这个方式来取

2.14,过滤器    {{ obj | filter_name:param}} 参数个数<=2

语法:{{ obj|filter_name:param}}
例如:
import datetime
now = datetime.datetime.now()
{{ now|date:"Y-m-d H:i:s"}} date是自带的过滤器,使用后显示“2001-02-03”
系统自带的过滤器:
default: {{value|default:\'null\'}} #如果变量为false或为空,使用给定的值,否则使用变量的值
length:{{value|length}} #显示字符串或列表的长度
filesizeformat:{{value|filesizeformat}} #如果value是12343423,输出是117.7kb
date:{{ now|date:"Y-m-d"}} # 输入是datetime.datetime.now() 格式化输出日期时间
slice:{{value|slice:"2:-1"}} #字符串切片
truncatechars:{{value|truncatechars:9}} #如果字符串长度大于9会被截断,截断的部分用"..."结尾
safe:{{value|safe}} # value="<a href=\'\'>click</a>"   safe过滤器可以在渲染的时候将数字 1 转换成字符串 \'1\'

如果前端是用{{ variable }}这种方式渲染,用上面的safe没有问题,如果前端是用forms组件渲染上面的方式就不起作用了

<label for="{{ field.auto_id }}">{{ field.label }}</label> {{ field|safe }} 这样写不起作用。  

前端是用forms组件渲染,就要用到mark_safe:

from django import forms
from django.utils.safestring import mark_safe

icon_list = [
(\'fa-user\', mark_safe(\'<i class="fa fa-user"></i>\')),
(\'fa-th-list\', mark_safe(\'<i class="fa fa-th-list"></i>\'))
]
class MenuForm(forms.Form):
title = forms.CharField(max_length=30, label="菜单标题", widget=forms.TextInput(attrs={"class": "form-control"}))
icon = forms.ChoiceField(label="菜单图标", choices=icon_list,
widget=forms.RadioSelect())


Django的模板中会对HTML标签和js等语法标签类似"<a><script>"等进行自动转义,
这样是为了安全。如果有脚本标签等,会有安全问题,例如写1000个<script>alert(123)</script>
但是有时候我们可能不希望这些HTML元素被转义
比如做一个内容管理系统,后台添加的文章中是经过修饰的,这些
修饰可能是通过一个类似于FCKeditor编辑加注了HTML修饰符的文本
如果自动转义的话,显示的就是保护HTML标签的源文件。为了在
Django中关闭HTML的自动转义有两种方式,如果是一个单独的变量
可以通过过滤器“|safe”的方式告诉Django这段代码是安全的不必转义

2.15,渲染标签:{% url / csrf_token / if / for / with %} 参数个数>2

for循环生成标签:
列表:如果列表是空的会显示列表为空,要放在循环体里面的后半部分
l=[1,2,3,4]
{% for i in l %}
<p>{{ i }}</p>

{% empty %}
<p>列表为空</p>
{% endfor %}

字典:并加序号{{ forloop.counter }}或{{ forloop.counter0 }}
info=[\'name\':\'alex\',\'age\':12]
{% for key in info %}
<p>{{ forloop.counter }}   {{ key }} {{ info.key }}</p>
{% endfor %}

if标签:
{% if user%}
<p>已登录</p>
{% else %}
<p>未登陆</p>
{% endif %}

with标签:给很长的名字取别名,后面可以用别名代替
{% with person.1.name as n %}
{{ n }}
{{ n }}
{% endwith %}


{% csrf_token %}
之前的登陆页面,第一次提交get请求,第二次提交post请求,由同一个视图函数处理
如果给同一个地址既能提交get请求又能提交post请求,如果提交post请求,会被csrf拦截。
因为服务器不知道这次post请求之前是否进行了一次get请求,所以要在form表单里加csrf_token:
<form>
{% csrf_token
...
</form>
这样第一次提交get请求的时候,就在表单里渲染一个token(类似身份证)一起提交给服务器
第二次提交post请求,服务器发现有这个token,就知道浏览器已经提交了一次get请求,就把结果返回。

2.16,自定义标签,过滤器(if条件判断只能用过滤器)

例如自定义一个乘法过滤器和乘法标签:
1,在settings里注册INSTALLD_APPS里面加上"app01",
2. 在app01下新建文件夹templatetags
3. 创建任意.py文件例如my_tag_filter.py并在里面写:

from django import template

register = template.Library()

@register.filter
def multi_filter(x,y):
return x*y

@register.simple_tag
def multi_tag(x,y):
return x*y
使用:
{% load my_tag_filter %}
{{ i|multi_filter:20 }}
{% multi_tag 7 8 %}
区别?
过滤器只能定义两个形参,标签可以接受很多形参。标签是不是比过滤器好使?
如果有多个参数可以用标签。如果 要在if条件判断后面使用就只能用过滤器
选择的时候要灵活使用
{% if i|multi_filter:10>100 %}  传入两个参数,管道符前面的值传递给方法的第一个参数,冒号后面的值传递给方法的第二个参数
100
{% else %}
{{ i }}
{% endif %}
标签和过滤器解决复用性的问题

2.17,模板语法之继承

模板中的include用法:
新建advertise.html,把要复用的HTML代码块放进去
要在插入的地方写:
{% include \'advertise.html\' %}


继承:比include强大
公用的代码放在base.html里:省略号表示公用的HTML代码
...
{% block con %}
# 这里是个“盒子”叫“con”放可变的内容,父HTML里盒子可以有多个,
{% endblock %} # 盒子里如果有代码也继承,子HTML可以选择重写或者不重写
...
继承自base.html的子HTML:
{% extends \'base.html\' %} #把父盒子的东西拿过来
{% block con %}
写自己的东西
{% endblock %}

如果想把父类盒子的东西拿过来也想加自己的东西怎么办?
{% block con %}
{{ block.super }}
写自己的东西
{% endblock %}

可以增加后缀,提高可读性
{% block con %}

{% endblock con%}

 2.18 (补)inclution_tag标签用法(计算变量值得到HTML代码)

inclution_tag模板语法:
HTML继承,构建的变量要传给base.html
之前是把变量计算到值后传给模板去渲染,渲染成HTML代码
inclution_tag是把数据和样式结合成标签函数,只要调用就拿到HTML代码

跟自定义标签类似:
1,在settings里注册INSTALLD_APPS里面加上"app01",
2. 在app01下新建文件夹templatetags
3. 创建任意.py文件例如my_tag_filter.py并在里面写:
---------------my_tag_filter.py-------------------
from django import template

register = template.Library()

@register.inclusion_tag("classification.html")
def get_classification_style(username):
# 查询该用户
user = UserInfo.objects.filter(username=username).first()
# 该用户所有文章
article_list = Article.objects.filter(user=user)
# 该用户所有文章按分类统计个数
category_c = article_list.values("category__title").annotate(c=Count("category__title"))
# 该用户所有文章按标签统计个数
tags_c = article_list.values("tags__title").annotate(c=Count("tags__title"))
# 该用户所有文章按日期"某年某月"统计文章个数
date_c = article_list.extra(select={"create_date": "date_format(create_time,\'%%Y-%%m\')"}) \
.values(\'create_date\').annotate(c=Count("nid"))
return {"username":username, "category_c":category_c, "tags_c":tags_c, "date_c":date_c}

-----------------------------------------------------
4.在渲染HTML中写:
--------------------"classification.html"---------------

<div class="panel panel-primary">
<div class="panel-heading">分类</div>
<div class="panel-body">
{% for cate in category_c %}
<div><a href="/{{ username }}/category/{{ cate.category__title }}">{{ cate.category__title }} ({{ cate.c }})</a></div>
{% endfor %}
</div>
</div>
<div class="panel panel-warning">
<div class="panel-heading">标签</div>
<div class="panel-body">
{% for tag in tags_c %}
<div><a href="/{{ username }}/tag/{{ tag.tags__title }}">{{ tag.tags__title }} ({{ tag.c }})</a></div>
{% endfor %}
</div>
</div>
<div class="panel panel-danger">
<div class="panel-heading">日期</div>
<div class="panel-body">
{% for dt in date_c %}
<div><a href="/{{ username }}/archive/{{ dt.create_date }}">{{ dt.create_date }} ({{ dt.c }})</a></div>
{% endfor %}
</div>
</div>
---------------------------------------------------------
5.使用。在需要被继承的base.html中写:
(这个自定义标签get_classification_style,可以用在任何模板上,执行过程是:
先将username传给继承了base.html的模板
再执行my_tag_filter.py中自定义的get_classification_style函数
执行结果交给装饰器中的classification.html渲染成HTML代码
再讲渲染后的HTML代码返回给调用的地方)
--------------------base.html-------------------
<div>
{% load my_tags %}
{% get_classification_style username %}
</div>
---------------------------------------------

 2.19 (补) request.POST取数组只取到最后一个?

注意,request.POST的类型是QueryDict,和普通的Dict不同的是,

如果使用request.POST.get方法,只能获得数组的最后一个元素,

必须使用getlist才能获取整个数组例如:request.POST.getlist(\'bands\'),

以Python列表的形式返回所请求键的数据。 若键不存在则返回空列表。 它保证了一定会返回某种形式的list。

2.20 (补) 前端和path中的相对路径和绝对路径

一、在urls.py中:

如果写: path(\'timer/\',views.timer)  则访问 http://127.0.0.1:8000/timer/  请求交给views.timer

如果写: path(\'/timer/\',views.timer)  则访问 http://127.0.0.1:8000//timer/  请求交给views.timer

总结:在urls.py中路径写的什么就按什么去匹配。

二、在前端模板中:

如果写:<a href="/timer/"></a> 则跳转到http://127.0.0.1:8000/timer/

如果写:<a href="timer/"></a> 则跳转到http://127.0.0.1:8000/timer/timer/

总结:在前端模板中,绝对路径是相对IP:PORT; 相对路径是相对当前的路径

三、在url中例如“timer/”后面的斜线必须要写

这样输入http://127.0.0.1:8000/timer或者http://127.0.0.1:8000/timer/  都会跳转到 http://127.0.0.1:8000/timer/

如果不写,输入http://127.0.0.1:8000/timer会跳转成功, 输入http://127.0.0.1:8000/timer/就会出现404

2.21(补)Django有两种静态文件

  /static/  : js ,css, img等网站要用到的文件

  /media/: 用户上传的文件,例如拉勾网的简历等

------------------------------------

  用户上传头像时,

  ORM类中:

    avatar = models.FileField(upload_to=\'avatars/\',default=\'/avatars/default.png\')

  views.py中:

    avatar_obj = request.FILES.get("avatar")

    user_obj = UserInfo.objects.create_user(....., avatar=avatar_obj)

  如果这样写,Django会将用户上传的文件放在项目根目录的avatars文件夹中

----------------------------------------

  Django建议把用户上传的文件放在media目录下。

  media如何配置?

  1、在应用的目录下新建media文件夹

  2、在settings.py里:

    MEDIA_ROOT=os.path.join(BASE_DIR,"media")

  配置好后,用户上传的文件会被放在MEDIA_ROOT路径下的avatars文件夹中(如果没有,Django会自动创建) 

  3、如果想让用户像访问static那样访问media可以:

    settings.py里: MEDIA_URL= \'/media/\'

    urls.py里:

    from django.urls import re_path

    from django.views.static import serve

    from cnblog import settings

    re_path(r"media/(?P<path>.*)$",serve,{"document_root":settings.MEDIA_ROOT})

 

也可以选择给media中的图片按日期创建目录

models.py中:

  course_img = models.ImageField(upload_to="course/%Y-%m", verbose_name="课程的图片")

项目settings.py中:

  # Media配置

  MEDIA_URL="/media/"

  MEDIA_ROOT = os.path.join(BASE_DIR, "media")

urls.py中:

  from django.views.static import serve

  from django.urls import path,include, re_path

  urlpatterns = [

    #media路径配置

    #path(\'media/(?P<path>.*)\', serve, {\'document_root\': settings.MEDIA_ROOT})

    re_path(\'media/(?P<path>.*)\', serve, {\'document_root\': settings.MEDIA_ROOT})

  ]

配置完成后,用Django的admin录入course_img字段xx.jpg,将在app目录下创建media/course/2019-01/xx.jpg

访问xx.jpg资源:127.0.0.1:8008/media/course/2019-01/xx.jpg

2.22(补)Django用户表原生字段

username    varchar(150)   --用户名
password    varchar(128)   --密码
last_login    datetime(6)    --最后一次登录时间
is_superuser    tinyint(1) --是否为超级用户
first_name    varchar(30)    --
last_name    varchar(150)   --
email    varchar(254)       --邮箱
is_staff    tinyint(1)     --是否员工
is_active    tinyint(1)     --是否激活
date_joined    datetime(6)    --加入时间

2.23(补)CNBLOG上传头像到media目录

第一步:models.py里

class UserInfo(AbstractUser):
\'\'\'用户信息扩展字段,每个用户对应一个博客\'\'\'
nid = models.AutoField(primary_key=True)
telephone = models.CharField(max_length=11,null=True,unique=True)
# models.ImageField也是这样用,upload_to是上传的文件存放路径,default为默认头像路径
avatar = models.FileField(upload_to=\'avatars/\',default=\'avatars/default.png\')
create_time = models.DateTimeField(verbose_name=\'创建时间\',auto_now_add=True) # 默认是当前时间

第二步:前端ajax提交

//提交注册
$("#reg-btn").click(function () {
var formdata = new FormData();

{#方式一:#}
{#formdata.append("user",$("#id_user").val());#}
{#formdata.append("pwd",$("#id_pwd").val());#}
{#formdata.append("re_pwd",$("#id_re_pwd").val());#}
{#formdata.append("email",$("#id_email").val()); #}
{#formdata.append("csrfmiddlewaretoken",$("[name=\'csrfmiddlewaretoken\']").val());#}
{#formdata.append("avatar",$("#avatar")[0].files[0]);#}

//方式二
var request_data = $("#form").serializeArray();
$.each(request_data,function (index, data) {
formdata.append(data.name,data.value)
});
formdata.append("avatar",$("#avatar")[0].files[0]);

$.ajax({
url:\'\',
type:\'post\',
contentType:false,
processData:false,
data:formdata,
success:function (data) {
if(data.user){
//校验成功
location.href="/login/";
}else{
//校验失败
//先清空
$("span.error").text("");
$("span.error").parent().removeClass("has-error");
//再加上错误信息
$.each(data.msg,function (name, error_list) {
if(name=="__all__"){
$("#id_re_pwd").next().text(error_list[0]);
$("#id_re_pwd").parent().addClass("has-error");
}
$("#id_"+name).next().text(error_list[0]);
$("#id_"+name).parent().addClass("has-error");
});
}
}
});
});

第三步settings.py配置好存储路径:

第四步views.py里后台处理:

avatar_obj = request.FILES.get("avatar")
if avatar_obj:
UserInfo.objects.create_user(username=user,password=pwd,email=email,avatar=avatar_obj)
else:
UserInfo.objects.create_user(username=user, password=pwd, email=email)

 

2.231(补)  CRM通过视图函数下载文件

为什么需要编写下载视图方法?

当你碰到如下2种情况时,你需要编写自己的视图下载方法。

  • 你希望用户以附件形式获得文件,而不是浏览器直接打开。

  • 你希望允许用户下载一些保密文件,而不希望在html模板中暴露它们。

可以使用HTTPResponse,SteamingHttpResonse,FileResonse。

推荐使用FileResonse:

from django.http import FileResponse
import mimetypes
def tpl(request):
# 这一句很重要,限定用户只能下载指定文件夹下的文件
tpl_path = os.path.join(settings.BASE_DIR, \'web\', \'files\', \'顾客上传模板.xls\')
\'\'\'
获取资源的媒体类型:mimetype
在浏览器中显示的内容有 HTML、有 XML、有 GIF、还有 Flash ……
那么,浏览器是如何区分它们,决定什么内容用什么形式来显示呢?答案是 MIME Type
媒体类型通常是通过 HTTP 协议,由 Web 服务器告知浏览器的,更准确地说,是通过 Content-Type 来表示的
\'\'\'
content_type = mimetypes.guess_type(tpl_path)[0]
response = FileResponse(open(tpl_path, mode=\'rb\'), content_type=content_type)
# Content-Disposition:这是响应头的固定参数,用户下载的文件被命名为customer_excel_tpl.xls
response[\'Content-Disposition\'] = "attachment;filename=%s" % \'customer_excel_tpl.xls\'
return response

2.24 (补) Django-admin(不是项目必须)

  Django的内部的一个组件:后台数据管理组件(web页面)

  1、先创建超级管理员:python manage.py createsuperuser 

        针对用户认证组件对应的用户表才起作用

   2、登录http://127.0.0.1:8000/admin/

  3、注册,在应用的目录下admin.py里注册表orm类:

  from django.contrib import admin
  # Register your models here.
  from blog import models
  admin.site.register(models.UserInfo)
  admin.site.register(models.Blog)
  admin.site.register(models.Category)
  admin.site.register(models.Tag)
  admin.site.register(models.Article)
  admin.site.register(models.ArticleUpDown)
  admin.site.register(models.Article2Tag)

   4、刷新http://127.0.0.1:8000/admin/

2.25 (补) 时间不对怎么办(这样调整后数据库存的时间和显示的时间不一样)

settings.py里

TIME_ZONE = \'Asia/Shanghai\'  # 数据库时间和前端显示时间不一样

...

USE_TZ = False # 如果单表查询的"datetime__month"不好使改为False

 

2.26(补) Django事务控制

下面的comment_obj和Article..两步是原子性的可以放在事务里:

from django.db import transaction
with transaction.atomic():
comment_obj = Comment.objects.create(content=content,article_id=article_id,user_id=user_id,parent_comment_id=pid)
Article.objects.filter(pk=article_id).update(comment_count=F("comment_count")+1)

2.261(补) Django-数据库加锁(行锁)

def action_multi_transfer(self, request, *args, **kwargs):
    \'\'\'
    把公共客户批量变为私人客户,把表中选中数据的consultant_id变为current_user_id
    \'\'\'
    # 当前登录的用户ID
    current_user_id = 7
    # 选中的数据的主键
    pk_list = request.POST.getlist(\'pk\')

    # 限制:私人客户数量
    private_customer_count = models.Customer.objects.filter(status=2, consultant_id=current_user_id).count()
    max_priv = models.Customer.MAX_PRIVATE_CUSTOMER_COUNT
    if private_customer_count + len(pk_list) < max_priv:
        return HttpResponse(\'私人客户数量超限制:最多只能申请%s个.\' % (max_priv - private_customer_count))
    # 符合id,顾问为空,状态为未报名的才能变成私人客户,(为了安全,要加锁)
    flag = False
    with transaction.atomic():
        # 在数据库中加行锁
        origin_queryset = models.Customer.objects.filter(id__in=pk_list, consultant__isnull=True,
                                                         status=2).select_for_update()
        # 如果选中了3个客户,却只查到2个客户
        if len(origin_queryset) == len(pk_list):
            models.Customer.objects.filter(id__in=pk_list, consultant__isnull=True,
                                           status=2).update(consultant_id=current_user_id)
            flag = True
    if not flag:
        return HttpResponse("手速太慢了,部分被选中的客户已被抢走。")

 

2.27(补) Django发送邮件

1、settings.py里:

# 发送邮件
EMAIL_HOST = \'smtp.exmail.qq.com\' # 如果是163改为smtp.163.com
EMAIL_PORT = 465
EMAIL_HOST_USER = \'\'
EMAIL_HOST_PASSWORD = \'\'
# DEFAULT_FROM_EMAIL = EMAIL_HOST_USER # 如果写这个send_mail方法就不用写发送方邮箱
EMAIL_USE_SSL = True

2、views里:

from django.core.mail import send_mail
from cnblog import settings

# send_mail(
# "您的文章%s新增了一条评论内容"%article_obj.title,
# content,
# settings.EMAIL_HOST_USER, #发送方邮箱
# ["323223.qq.com"] # 作者邮箱,可以发给多个人
# )

# 用线程来的快
import threading
t=threading.Thread(target=send_mail,args=(
"您的文章%s新增了一条评论内容"%article_obj.title,
content,
settings.EMAIL_HOST_USER, # 发送方邮箱
["323223.qq.com"] # 作者邮箱,可以发给多个人
))
t.start()

2.28(补) Django批量发现权限URL正则(发现反向解析表达式+url正则)

from collections import OrderedDict
from django.conf import settings
from django.utils.module_loading import import_string
from django.urls import URLResolver, URLPattern
import re

\'\'\'
自动发现所有的URL,用于增加权限用
返回字典:{\'rbac:menu_list\':{name:\'rbac:menu_list\', url:\'/menu/list/\'},...}
使用方法:get_all_url_dict()
思路:递归项目下的urls.py的patterns
path(r"index/", views.index) # URLPattern对象
re_path(r"^", include("web.urls.customer_urls")), # URLResolver对象
\'\'\'

def check_url_exclude(url):
\'\'\'
判断自动发现的url是否在过滤名单里。
:param url:
:return:
\'\'\'
exclude_url = settings.AUTO_DISCOVER_EXCLUDE # [r\'^/admin/.*\', r\'^/login/.*\', r\'^/register/.*\', ]
for regex in exclude_url:
if re.match(regex, url):
return True

def recursion_urls(pre_namespace, pre_url, urlpatterns, url_ordered_dict):
\'\'\'
递归获取URL(叶子节点路由必须有反向解析的name别名)
:param pre_namespace: 路由分发的命名空间,以后用于拼接name
:param pre_url: url前缀,以后用于拼接url
:param urlpatterns: 路由关系列表
:param url_ordered_dict: 保存递归中获取的所有路由
:return: {\'rbac:menu_list\':{name:\'rbac:menu_list\', url:\'/menu/list\'},...}
\'\'\'
for item in urlpatterns:
if isinstance(item, URLPattern): # 非路由分发,添加到url_ordered_dict
if not item.name: # 没有反向解析别名,发现不了,跳过
continue
if pre_namespace:
name = "%s:%s" % (pre_namespace, item.name)
else:
name = item.name
# item.pattern = ^test/(?P<pk>\d+)/$
url = pre_url + item.pattern.__str__()
url = url.replace(\'^\', \'\').replace(\'$\', \'\')
if check_url_exclude(url):
continue
url_ordered_dict[name] = {\'name\': name, \'url\': url}
elif isinstance(item, URLResolver): # 路由分发,递归操作
# 如果路由分发namespace有前缀,也要进行叠加
if pre_namespace:
if item.namespace: # 父级有,自己也有
namespace = "%s:%s" % (pre_namespace, item.namespace)
else: # 父级有,自己没有
namespace = pre_namespace
else:
if item.namespace: # 父级没有, 自己有
namespace = item.namespace
else: # 父级没有,自己也没有
namespace = None
recursion_urls(namespace, pre_url + item.pattern.__str__(), item.url_patterns, url_ordered_dict)


def get_all_url_dict():
\'\'\'
获取项目中所有的URL
:return:
\'\'\'
url_ordered_dict = OrderedDict()
md = import_string(settings.ROOT_URLCONF)
recursion_urls(None, \'/\', md.urlpatterns, url_ordered_dict)
return url_ordered_dict

2.29(补) 同一个页面向后端同一个视图函数提交两种post请求,如何进行甄别?

同一个前端页面向后端同一个视图函数提交post请求,如何进行甄别?

使用post的action进行?参数拼接,后端判断type的类型

<form method="post" action="?type=add">
{% csrf_token %}
{{ update_formset.management_form }}
   ....
<button type="submmit">提交</div>
</form>
....
<form method="post" action="?type=update">
{% csrf_token %}
{{ update_formset.management_form }}
   ....
<button type="submmit">提交</div>
</form>

123123