一,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,路由控制之--正则表达式匹配(分组)
2.6、响应体数据格式(render,字符串,json)
2.7,路由控制之--正则表达式匹配(有名分组)
2.8,路由控制之--分发
2.9,路由控制之--反向解析(在HTML里或在views里):
2.10,反向解析时别名冲突怎么办---使用名称空间
2.11,re_path升级版---django2.x版本的path
2.12,视图函数之request对象
2.13,渲染变量{{ variable }}
2.14,渲染变量{{ obj|filter_name:param}}
2.15,渲染标签:{% tag %}
2.16,模板语法:自定义渲染标签,渲染变量-过滤器
2.17,模板语法之继承
2.18 (补)inclution_tag标签用法(计算变量值得到HTML代码)
2.19 (补) request.POST取数组只取到最后一个?
2.20 (补) 前端和path中的相对路径和绝对路径
2.21(补)Django有两种静态文件
2.22(补)Django用户表原生字段
2.23(补)Django上传文件
2.24 (补) Django-admin(不是项目必须)
2.25 (补) 时间不对怎么办(这样调整后数据库存的时间和显示的时间不一样)
2.26(补) Django事务控制
2.27(补) Django发送邮件
settings.py里:
TIME_ZONE = 'Asia/Shanghai'
一,DIY一个web框架
1.1什么是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()
上面的方式发出去浏览器解析不了,提示“收到的响应无法解析”
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。
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,输入命令
python manage.py runserver 127.0.0.1:8080
第五步:访问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即可
2.4,css/js等静态资源配置
templates里HTML发给客户端后想让上面的css,js生效,那么引入路径必须写服务器的路径,因为HTML是发送给客户端去执行的
这些css,js属于静态资源
在项目下创建一个静态文件夹statics里面有app01放jquery,js,css。
在settings.py里写上:
STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, "statics")]
表明通过地址栏的路径别名'/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等可以通过这种方式来下载
2.5,路由控制之--正则表达式匹配(分组)
(如果用正则去匹配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,字符串,json)
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
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")))
反向解析的时候指定名称空间:
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,渲染变量{{ variable }}
模板文件不是单纯的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}}
语法:{{ obj|filter_name:param}}
例如:
import datetime
now = datetime.datetime.now()
{{ now|date:"Y-m-d"}} 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>"
Django的模板中会对HTML标签和js等语法标签类似"<a><script>"等进行自动转义,
这样是为了安全。如果有脚本标签等,会有安全问题,例如写1000个<script>alert(123)</script>
但是有时候我们可能不希望这些HTML元素被转义
比如做一个内容管理系统,后台添加的文章中是经过修饰的,这些
修饰可能是通过一个类似于FCKeditor编辑加注了HTML修饰符的文本
如果自动转义的话,显示的就是保护HTML标签的源文件。为了在
Django中关闭HTML的自动转义有两种方式,如果是一个单独的变量
可以通过过滤器“|safe”的方式告诉Django这段代码是安全的不必转义
2.15,渲染标签:{% tag %}
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,模板语法:自定义渲染标签,渲染变量-过滤器
例如自定义一个乘法过滤器和乘法标签:
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 %}
上面有上面区别?
过滤器只能定义两个形参,标签可以接受很多形参。
标签是不是比过滤器好使?
如果 传给模板的变量i乘以10之后是不是大于100,是则显示100,否则显示i,
这样就只能用过滤器做了。选择的时候要灵活使用
{% 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})
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(补)Django上传文件
第一步: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.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.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()