web框架。Django--

 一,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框架全名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请求没有请求体,数据是放在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。

 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()



posted on 2019-04-08 10:37 要一直走下去 阅读( ...) 评论( ...) 编辑 收藏

转载于:https://www.cnblogs.com/staff/p/10669075.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值