![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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) #拿到反向关联字段所在类名称
一,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请求,如何进行甄别?
2.30(补)时间时区设置
- 在Django的配置文件
settings.py
中,有两个配置参数是跟时间与时区有关的,分别是TIME_ZONE
和USE_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 = False
,TIME_ZONE = 'Asia/Shanghai'
, 则使用上海的UTC
时间。
一,DIY一个web框架
1.1什么是web框架
web开发: html css js
web应用:类似socket,客户端是浏览器,作用是接受socket请求 处理 响应数据字符串
1.2用socket模拟B-S的服务端
浏览器端发送的是报文,就是一堆字符串,包括URL,请求方式等响应的数据也有固定格式:响应首行+响应头+响应体 响应体和前面的用两个 隔开,前面的所有用一个 分隔
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()
上面的方式发出去浏览器解析不了,提示“收到的响应无法解析”
1.3,浏览器端的network查看
如果响应的是<h1>helloworld</h1>在浏览器端审查,network-request,preview,response
response可以看到响应的字符串,preview看到的是浏览器渲染后的标签
1.4,request格式
get一般做查询操作,没有请求体,数据放在url后面以?分隔开,post一般做数据库更新操作,有请求体
请求头:
请求体:
服务器端怎样区分请求首行,请求头,请求体?
1.5,response格式
响应首行:HTTP/1.1 200 ok 协议版本 状态码 状态码说明。
响应头:键值对格式,例如: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请求,这个是干什么的呢?而且两次返回的内容一样。那页面的内容是哪一次返回的内容呢,这样不会覆盖吗?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:UsersAdministratorAppDataLocalProgramsPythonPython36-32Scriptsdjango-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以上还要注释掉:
..Python37Libsite-packagesdjangodbackendsmysqlase.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里进行分发:
![](https://img2020.cnblogs.com/i-beta/1310329/202003/1310329-20200308194136374-401258662.png)
分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