学习视频网址:https://www.bilibili.com/video/BV1mW411G7g6?from=search&seid=11980423788703295979

1 web框架本质

1.1 socket服务端

HTTP协议

客户端和服务端交互遵循Http协议
发送:get请求 127.0.0.1/index?p=123image.png
请求头
GET /index HTTP/1.1
Host: 127.0.0.1:8080\r\nConnection: keep-alive
sec-ch-ua: “Google Chrome”;v=”87”, “ Not;A Brand”;v=”99”, “Chromium”;v=”87”\r\nsec-ch-ua-mobile: ?0\r\nUpgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9

请求体:
p=123

请求头第一行GET斜杠后面就是url,默认就是/ 如果是127.0.0.1/index 则请求头第一行应该是GET/index);

响应:
普通响应:页面直接显示
重定向响应:再发一次http请求

响应头
Status Code:200
age: 88788
ali-swift-global-savetime: 1614057834
content-encoding:gzip
content-length: 177480
content-type: text/html; charset=UTF-8
date: Tue, 23 Feb 2021 05:23:54 GMT
eagleid: 78dddf9b16141466222165103e
link: http://www.runoob.com/wp-json/; rel=”https://api.w.org/
server: Tengine
timing-allow-origin: *
vary: Accept-Encoding
via: cache71.l2cn2623[0,200-0,H], cache80.l2cn2623[10,0], cache9.cn2429[0,200-0,H], cache7.cn2429[1,0]
x-cache: HIT TCP_MEM_HIT dirn:1:14559382
x-swift-cachetime: 86400
x-swift-savetime: Wed, 24 Feb 2021 00:30:22 GMT

响应体(response)
用户可以看到的页面内容(本质是字符串) 是经过浏览器渲染得到的

浏览器(socket客户端)
2.输入域名通过DNS服务器解析成要请求的ip,指定端口80
sk.socket()
sk.connect(ip)
sk.send(‘我想要…’)
5.接收
6.连接断开

博客园(socket服务端)
1.服务器运行:监听ip和端口
while True:
等待用户连接
3.收到’我想要…’
4.响应:’好’
断开连接
处理请求过程交给别人去做

socket服务端代码

第一版本

  1. import socket
  2. def handle_request(client):
  3. buf = client.recv(1024) # 获取用户请求
  4. buf = str(buf, encoding='utf-8')
  5. hearders, bodys = buf.split('\r\n\r\n')
  6. temp_list = hearders.split('\r\n')
  7. method, url, protocal = temp_list[0].split(' ')
  8. if url == '/':
  9. client.send(b"HTTP/1.1 200 OK\r\n\r\n")
  10. client.send(b"Hello, liuxuebin")# 服务端发送数据
  11. else:
  12. client.send(b"HTTP/1.1 404 NO\r\n\r\n")
  13. client.send(b'404 not found')
  14. def main():
  15. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  16. # 127.0.0.1 指的是自己的主机 不是公网ip
  17. sock.bind(('127.0.0.1', 8080))
  18. sock.listen(5)
  19. while True:
  20. connection, address = sock.accept()
  21. handle_request(connection)
  22. connection.close()
  23. if __name__ == '__main__':
  24. main()

因为url较多,如果这样写就要写大量代码来判断url

第二版本

  1. import socket
  2. def f1(request):
  3. """
  4. 处理用户请求,并返回响应信息
  5. :param request: 用户请求信息
  6. :return:
  7. """
  8. request = bytes(request, encoding='utf-8')
  9. return request
  10. def f2():
  11. return b'f2'
  12. routers = [
  13. ('/xxx', f1),
  14. ('/ooo', f2)
  15. ]
  16. def handle_request(client):
  17. buf = client.recv(1024) # 获取用户发送的数据
  18. buf = str(buf, encoding='utf-8')
  19. hearders, bodys = buf.split('\r\n\r\n')
  20. temp_list = hearders.split('\r\n')
  21. method, url, protocal = temp_list[0].split(' ')
  22. func_name = None
  23. for item in routers:
  24. if item[0] == url:
  25. func_name = item[1]
  26. break
  27. if func_name:
  28. client.send(b"HTTP/1.1 200 OK\r\n\r\n")
  29. response = func_name(buf)
  30. else:
  31. client.send(b"HTTP/1.1 404 OK\r\n\r\n")
  32. response = b'404'
  33. client.send(response)
  34. def main():
  35. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  36. # 127.0.0.1 指的是自己的主机 不是公网ip
  37. sock.bind(('127.0.0.1', 8080))
  38. sock.listen(5)
  39. while True:
  40. connection, address = sock.accept()
  41. handle_request(connection)
  42. connection.close()
  43. if __name__ == '__main__':
  44. main()

f1和f2函数不止可以返回字节还可以返回html文件或者是数据库的数据,返回的html本质就是字节类型的字符串

第三个版本
返回静态html

  1. import socket
  2. def f1(request):
  3. """
  4. 处理用户请求,并返回响应信息
  5. :param request: 用户请求信息
  6. :return:
  7. """
  8. request = bytes(request, encoding='utf-8')
  9. return request
  10. def f2():
  11. f = open('index.html', 'rb')
  12. data = f.read()
  13. print(data)
  14. f.close()
  15. return data
  16. routers = [
  17. ('/xxx', f1),
  18. ('/ooo', f2)
  19. ]
  20. def handle_request(client):
  21. buf = client.recv(1024) # 获取用户发送的数据
  22. buf = str(buf, encoding='utf-8')
  23. hearders, bodys = buf.split('\r\n\r\n')
  24. temp_list = hearders.split('\r\n')
  25. method, url, protocal = temp_list[0].split(' ')
  26. func_name = None
  27. for item in routers:
  28. if item[0] == url:
  29. func_name = item[1]
  30. break
  31. if func_name:
  32. client.send(b"HTTP/1.1 200 OK\r\n\r\n")
  33. response = func_name()
  34. else:
  35. client.send(b"HTTP/1.1 404 OK\r\n\r\n")
  36. response = b'404'
  37. client.send(response)
  38. def main():
  39. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  40. # 127.0.0.1 指的是自己的主机 不是公网ip
  41. sock.bind(('127.0.0.1', 8080))
  42. sock.listen(5)
  43. while True:
  44. connection, address = sock.accept()
  45. handle_request(connection)
  46. connection.close()
  47. if __name__ == '__main__':
  48. main()

但现在的网站是静态网站,数据是不变的。

第四版本
动态网站:html只是一个模板,里面的数据是变化的

  1. import socket
  2. import time
  3. def f1(request):
  4. """
  5. 处理用户请求,并返回响应信息
  6. :param request: 用户请求信息
  7. :return:
  8. """
  9. request = bytes(request, encoding='utf-8')
  10. return request
  11. def f2():
  12. f = open('index.html', 'r', encoding='utf-8')
  13. data = f.read()
  14. f.close()
  15. ctime = time.time()
  16. data = data.replace('@time@', str(ctime)) #数据是动态的
  17. return bytes(data, encoding='utf-8')
  18. routers = [
  19. ('/xxx', f1),
  20. ('/ooo', f2)
  21. ]
  22. def handle_request(client):
  23. buf = client.recv(1024) # 获取用户发送的数据
  24. buf = str(buf, encoding='utf-8')
  25. hearders, bodys = buf.split('\r\n\r\n')
  26. temp_list = hearders.split('\r\n')
  27. method, url, protocal = temp_list[0].split(' ')
  28. func_name = None
  29. for item in routers:
  30. if item[0] == url:
  31. func_name = item[1]
  32. break
  33. if func_name:
  34. client.send(b"HTTP/1.1 200 OK\r\n\r\n")
  35. response = func_name()
  36. else:
  37. client.send(b"HTTP/1.1 404 OK\r\n\r\n")
  38. response = b'404'
  39. client.send(response)
  40. def main():
  41. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  42. # 127.0.0.1 指的是自己的主机 不是公网ip
  43. sock.bind(('127.0.0.1', 8080))
  44. sock.listen(5)
  45. while True:
  46. connection, address = sock.accept()
  47. handle_request(connection)
  48. connection.close()
  49. if __name__ == '__main__':
  50. main()

第5版本
从数据库获取数据返回前端

  1. import socket
  2. import time
  3. import pymysql
  4. def f1(request):
  5. """
  6. 处理用户请求,并返回响应信息
  7. :param request: 用户请求信息
  8. :return:
  9. """
  10. request = bytes(request, encoding='utf-8')
  11. return request
  12. def f2():
  13. """
  14. 获取当前时间
  15. :return:
  16. """
  17. f = open('index.html', 'r', encoding='utf-8')
  18. data = f.read()
  19. f.close()
  20. ctime = time.time()
  21. data = data.replace('@time@', str(ctime))
  22. return bytes(data, encoding='utf-8')
  23. def f3():
  24. """
  25. 连接数据库并获取查询结果
  26. :return:
  27. """
  28. conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='362514', db='djangodata')
  29. # 创建游标
  30. cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
  31. # 执行SQL,并返回收影响行数
  32. cursor.execute("select * from userlist")
  33. # 获取查询到的所有数据
  34. user_list = cursor.fetchall()
  35. # 提交,不然无法保存新建或者修改的数据
  36. conn.commit()
  37. # 关闭游标
  38. cursor.close()
  39. # 关闭连接
  40. conn.close()
  41. print(user_list) #[{'id': 1, 'username': 'ssda', 'password': '123'}, {'id': 2, 'username': 'acasc', 'password': '233'}, {'id': 3, 'username': 'wad', 'password': '231'}, {'id': 4, 'username': 'aada', 'password': '432'}, {'id': 5, 'username': '撒打算', 'password': '129'}]
  42. content_list = []
  43. for row in user_list:
  44. tp = '<tr><td>%s</td><td>%s</td><td>%s</td></tr>'%(row['id'],row['username'],row['password'])
  45. content_list.append(tp)
  46. content = ''.join(content_list)
  47. f = open('index.html', 'r', encoding='utf-8')
  48. template = f.read()
  49. f.close()
  50. # 模板渲染
  51. data = template.replace('@content@', content)
  52. return bytes(data, encoding='utf-8')
  53. routers = [
  54. ('/xxx', f1),
  55. ('/ooo', f2),
  56. ('/userlist', f3)
  57. ]
  58. def handle_request(client):
  59. buf = client.recv(1024) # 获取用户发送的数据
  60. buf = str(buf, encoding='utf-8')
  61. hearders, bodys = buf.split('\r\n\r\n')
  62. temp_list = hearders.split('\r\n')
  63. method, url, protocal = temp_list[0].split(' ')
  64. func_name = None
  65. for item in routers:
  66. if item[0] == url:
  67. func_name = item[1]
  68. break
  69. if func_name:
  70. client.send(b"HTTP/1.1 200 OK\r\n\r\n")
  71. response = func_name()
  72. else:
  73. client.send(b"HTTP/1.1 404 OK\r\n\r\n")
  74. response = b'404'
  75. client.send(response)
  76. def main():
  77. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  78. # 127.0.0.1 指的是自己的主机 不是公网ip
  79. sock.bind(('127.0.0.1', 8080))
  80. sock.listen(5)
  81. while True:
  82. connection, address = sock.accept()
  83. handle_request(connection)
  84. connection.close()
  85. if __name__ == '__main__':
  86. main()

基于jinjia2模板渲染

不需要后端写html标签

  1. import socket
  2. import time
  3. import pymysql
  4. from jinja2 import Template
  5. def f1():
  6. """
  7. 处理用户请求,并返回响应信息
  8. :param request: 用户请求信息
  9. :return:
  10. """
  11. conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='362514', db='djangodata')
  12. # 创建游标
  13. cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
  14. # 执行SQL,并返回收影响行数
  15. cursor.execute("select * from userlist")
  16. # 获取查询到的所有数据
  17. user_list = cursor.fetchall()
  18. # 提交,不然无法保存新建或者修改的数据
  19. conn.commit()
  20. # 关闭游标
  21. cursor.close()
  22. # 关闭连接
  23. conn.close()
  24. f = open('jinjia2模板渲染.html', 'r', encoding='utf-8')
  25. data = f.read()
  26. f.close()
  27. template = Template(data)
  28. data = template.render(user_list=user_list, user=user_list)
  29. print(data)
  30. return data.encode('utf-8')
  31. routers = [
  32. ('/host.htm', f1)
  33. ]
  34. def handle_request(client):
  35. buf = client.recv(1024) # 获取用户发送的数据
  36. buf = str(buf, encoding='utf-8')
  37. hearders, bodys = buf.split('\r\n\r\n')
  38. temp_list = hearders.split('\r\n')
  39. method, url, protocal = temp_list[0].split(' ')
  40. func_name = None
  41. for item in routers:
  42. if item[0] == url:
  43. func_name = item[1]
  44. break
  45. if func_name:
  46. client.send(b"HTTP/1.1 200 OK\r\n\r\n")
  47. response = func_name()
  48. else:
  49. client.send(b"HTTP/1.1 404 OK\r\n\r\n")
  50. response = b'404'
  51. client.send(response)
  52. def main():
  53. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  54. # 127.0.0.1 指的是自己的主机 不是公网ip
  55. sock.bind(('127.0.0.1', 8080))
  56. sock.listen(5)
  57. while True:
  58. connection, address = sock.accept()
  59. handle_request(connection)
  60. connection.close()
  61. if __name__ == '__main__':
  62. main()

总结

  1. 自己写网站<br />a.socket服务端<br />b.根据不同url返回不同内容<br />路由系统:<br />url->函数<br />c.字符串返回给浏览器<br />模板引擎渲染:<br />html充当模板<br />自己创造任意数据

Http请求生命周期:请求头-》提取url-》路由关系匹配-》函数(模板+数据渲染)-》返回用户(响应体+相响应头)
web框架
框架种类:
功能包含:
-a,b,c —>Tornado
-b,c ,第三方a—>Django a用的是wsgiref
-b,第三方a,c —>Flask c用的是jinjia2 ,a用的是wsgiref

2 初识Django

2.1 创建和使用Django

用pycharm专业版新建项目-django项目(djangoProject1)
image.png
asgi.py: 一个 ASGI 兼容的 Web 服务器的入口,以便运行你的项目。
setting:配置文件
urls:路由系统:url->函数
wsgi:用于定义django用socket,wsgiref,uwsgi
manage:对当前django程序所有操作可以基于python manage.py runserver
views:写处理请求的函数
创建第一个项目教程:
https://www.runoob.com/django/django-first-app.htmlhttps://www.runoob.com/django/django-first-app.html

2.2 django静态模板以及配置

template下面创建一个html文件
image.png
views.py下的代码

  1. from django.http import HttpResponse
  2. from django.shortcuts import render
  3. def hello(request):
  4. """
  5. 处理用户请求,并返回内容
  6. :param request: 用户请求相关的所有信息(对象)
  7. :return:
  8. """
  9. return HttpResponse("<input/> ")
  10. def login(request):
  11. # 自动找到模板路径下的login.html文件,读取内容并返回给用户
  12. return render(request, 'login.html')

urls.py添加对应路由
之所以可以找到login.html是因为配置文件setting中
image.png
引用css文件

image.png
要进行setting的配置
image.png

  1. STATICFILES_DIRS = (
  2. os.path.join(BASE_DIR, 'static')
  3. )

2.3 用户登录示例

views.py

  1. from django.http import HttpResponse
  2. from django.shortcuts import render, redirect
  3. def hello(request):
  4. """
  5. 处理用户请求,并返回内容
  6. :param request: 用户请求相关的所有信息(对象)
  7. :return:
  8. """
  9. return HttpResponse("<input/> ")
  10. def login(request):
  11. # print(request.GET) get请求携带的数据 <QueryDict: {'p': ['11233']}>
  12. # 自动找到模板路径下的login.html文件,读取内容并返回给用户
  13. if request.method == 'GET':
  14. return render(request, 'login.html')
  15. else:
  16. # 用户POST提交的数据(请求体)
  17. # print(request.POST) # post请求携带的数据 <QueryDict: {'username': ['liuxuebin'], 'password': ['123']}>
  18. user = request.POST.get('username')
  19. password = request.POST.get('password')
  20. if user == 'liuxuebin' and password == '123':
  21. # 登陆成功,跳转到某个地址
  22. return redirect('https://www.baidu.com/')
  23. else:
  24. # 登陆失败,这里是将msg对应的值显示到前端,前端用特殊的语法,底层其实用了replace替换然后返回给前端字符串
  25. return render(request, 'login.html', {'msg': '用户名或密码错误!'})

login.html
image.png
总结
创建Django步骤
1.创建项目
2.配置
-模板路径
-静态文件路径
-注释一条语句
image.png
3.启动项目

需要注意:
post请求:request.post->来自请求体
get请求:request.get->来自请求头的url中

2.4 django模板语言

简单的特殊标记

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <h1>模板标记学习</h1>
  9. <!-- 后台name的值-->
  10. <p>{{ name }}</p>
  11. <!-- 列表取索引值-->
  12. <p>{{ users.0 }}</p>
  13. <p>{{ users.1 }}</p>
  14. <!-- 字典取索引值-->
  15. <p>{{ user_dicts.k1 }}</p>
  16. <h3>循环</h3>
  17. <ul>
  18. {% for item in users %}
  19. <li>{{ item }}</li>
  20. {% endfor %}
  21. </ul>
  22. <table border="1">
  23. {% for row in user_list_dict %} {# user_list_dict #} 是一个字典列表
  24. <tr>
  25. <td>{{ row.id }}</td>
  26. <td>{{ row.name }}</td>
  27. </tr>
  28. {% endfor %}
  29. </table>
  30. </body>
  31. </html>

母版

最开始我们开发一个一个管理系统,如下列学院系统
classes.html文件
image.png
students.html文件image.png
可以发现多个html文件的导航栏和左边菜单栏都是相同的,通常我们的做法是重复性的复制这段代码,加入到不同的html中,如果这段被复用的代码发生了修改,则使用这段代码的多个html都要进行修改,这样无疑是麻烦的,影响开发效率!
所以提出了一个好的方法呢就是——————-母版
示例
layout.html是母版
layout页面
image.png
classes是要继承母版的子版
image.png
执行这段代码过程:首先读取classes.html文件,看到extends就会把后面的html拿过来进行替换,classes发生变化,看到替换后的第一个block的时候会把子板里面的block(也就是此时classes.html里面的第二个block)里面的代码替换第一个block位置处的代码,生成全新html,返回给前端。

一般情况下母版会写3处block css body js各写一个 是为了子版扩充自己的css js body是使用的,所以说母版只放所有子版共用的东西,类似于父类子类,公共部分写在父类。

函数

模板自定义函数
-使用前五个步骤
-@register.filter
-@register.simple_tag —-常用

include

场景:有一个组件会在多个页面多个位置使用到
被使用组件:
image.png
如果页面里多处使用到,以往的方法就是在每个地方写一遍,这样代码可读性低,代码量太大,因为人们的需求就出现了include
未使用include

  1. {% load xx %}
  2. <!DOCTYPE html>
  3. <html lang="en">
  4. <head>
  5. <meta charset="UTF-8">
  6. <title>Title</title>
  7. </head>
  8. <body>
  9. <h3>组件</h3>
  10. <div class="title">标题</div>
  11. <div class="content">内容</div>
  12. {% for row in v %} {# v接收字典,但是for迭代的是键 #}
  13. {{ row }}
  14. {% endfor %}
  15. {# filter装饰器装饰的函数调用方式 #}
  16. {# 将接收到的字符串转为大写 相当于调用函数 #}
  17. {{ name|my_upper}}
  18. {# 将name的值和666拼接起来 #}
  19. {{ name|splice:'666'}}
  20. <h3>组件</h3>
  21. <div class="title">标题</div>
  22. <div class="content">内容</div>
  23. <h2>tag</h2>
  24. {# simple_tag装饰器装饰的函数调用方式 #}
  25. {% my_lower 'ALEX' %}
  26. {% splice 'alex' 'nihao' 'woshihaoren' %}
  27. {# filter装饰的函数可以搭配if使用,而simple_tag不可以 #}
  28. {% if name|my_bool %}
  29. <h3></h3>
  30. {% else %}
  31. <h3></h3>
  32. {% endif %}
  33. <h3>组件</h3>
  34. <div class="title">标题</div>
  35. <div class="content">内容</div>
  36. </body>
  37. </html>

引入include

  1. {% load xx %}
  2. <!DOCTYPE html>
  3. <html lang="en">
  4. <head>
  5. <meta charset="UTF-8">
  6. <title>Title</title>
  7. </head>
  8. <body>
  9. {% include 'public_module.html' %}
  10. {% for row in v %} {# v接收字典,但是for迭代的是键 #}
  11. {{ row }}
  12. {% endfor %}
  13. {# filter装饰器装饰的函数调用方式 #}
  14. {# 将接收到的字符串转为大写 相当于调用函数 #}
  15. {{ name|my_upper}}
  16. {# 将name的值和666拼接起来 #}
  17. {{ name|splice:'666'}}
  18. {% include 'public_module.html' %}
  19. <h2>tag</h2>
  20. {# simple_tag装饰器装饰的函数调用方式 #}
  21. {% my_lower 'ALEX' %}
  22. {% splice 'alex' 'nihao' 'woshihaoren' %}
  23. {# filter装饰的函数可以搭配if使用,而simple_tag不可以 #}
  24. {% if name|my_bool %}
  25. <h3></h3>
  26. {% else %}
  27. <h3></h3>
  28. {% endif %}
  29. {% include 'public_module.html' %}
  30. </body>
  31. </html>
  1. <h3>组件</h3>
  2. <div class="title">标题:{{ name }}div>
  3. <div class="content">内容:{{ name }}</div>

在组件里面写入{{name}}页面也会渲染出来,因为在render执行的时候,先读取test.html发现include、extend特殊标记先进行关联替换,然后再对其他的特殊标记进行渲染,传给前端页面解析出来

3 学员管理

表:
班级
学生
老师
单表操作:classes表




一对多操作:students表




多对多操作:teacher_class表



3.1 学员管理之数据库表结构设计

image.png

3.2 学员管理之查看班级列表以及添加班级

功能实现分析:
1.首先 classes.html页面加一个添加标签,点击添加会跳转到另一个url,返回添加班级页面
2.添加班级页面
要有文本框输入和添加按钮
填写完表单,点击添加按钮会向数据库中添加一条记录,所以添加按钮要对应一个url发送的是post请求
返回添加班级页面和提交按钮返回的页面(提交按钮返回的页面是添加记录后的班级列表页面也就是classes.html)写在一个函数里,否则写两个函数,函数体太短,用请求方法来进行判断,并返回相应页面。
3.添加班级页面没有班级名称点击提交会报错,需要完善:当点击提交时提示用户!

3.3 学员管理之删除班级

功能实现分析:
1.每一条记录后面添加删除标签
2.点击删除标签进行url访问(get请求)要携带id参数,发送给后端,根据id进行在数据库中删除记录
3.后端拿到参数操作数据库进行删除记录
4.重定向/classes/
意思是再次访问/classes/路由,发出get请求,执行classes函数。
可以理解为return redirect(‘/classes/‘)等价于classes函数体,如果写return render(…)就要把classes函数体再写一遍。
return redirect(‘/classes/‘)就是后台返回响应头和响应体,响应头就是127.0.0.1/classes,发送给浏览器,浏览器再次访问这个路由地址

3.4 学员管理之编辑班级

功能实现分析:
1.添加编辑标签
2.点击编辑标签跳转到编辑页面,要存在被编辑内容
3.编辑完成,点击提交,重定向回查看班级页面

3.5 学员管理之查看学生列表

功能实现分析:与查看班级表的区别只有在写sql语句时不一样

3.6 学员管理之添加学生信息

功能实现分析:
1.添加界面有学生姓名和学生班级,学生班级要用下拉框,因为输入时可能班级表里没有这个班级,也因为是外键所以用下拉框
2.所以不能只单单返回一个页面,要提前查询到班级表中的班级名称,用模板标记语言进行渲染再生成页面
3.这里提交的时候进行数据库插入操作,可是页面是插入班级名称,而数据库是插入班级id,所以后台要拿到的是班级id。

3.7 学员管理之编辑学生

功能实现分析:
难点在下拉框默认选择学生原来的班级

注意:添加、编辑需要在views中对用户提交的数据进行判断(django中有个form表单验证组件)
例如添加班级时表单里没有写班级就提交,要有相关处理

3.8 学员管理之基于Ajax添加班级

模态对话框:就是在屏幕上再加上两层,一层遮罩层,一层就是对话框了
下图就是模态对话框
image.png

form表单提交,页面会刷新,则模态对话框会消失

需求:点击提交,对话框不消失,并且可以判断当没有输入班级名称时,显示错误。
如果用form表单提交请求,是无法做到以上需求的。
完成这个需求就要用到Ajax来提交请求了
Ajax应用:网易邮箱登陆界面—当你输入密码或用户名错误时,提示输入错误,此时页面不会刷新,但已经把数据提交到后台了。
使用ajax:引入jquery

  1. <script>
  2. function send(){
  3. $.ajax({
  4. url:'/modal_addClass/', //请求的url
  5. type:'POST',
  6. data:{'title':'kkkkkkkk'}, //发送post请求的请求体
  7. success:function (data){
  8. //当服务端处理完成后,返回数据时,该函数自动调用
  9. //data时服务端返回的值
  10. console.log(data)
  11. }
  12. })
  13. }
  14. </script>
  1. def modal_addClass(request):
  2. """
  3. 处理对话框提交添加班级请求
  4. :param request:
  5. :return:
  6. """
  7. print(request.POST)
  8. # class_name = request.POST.get('class_name')
  9. # if len(class_name) > 0:
  10. # sqlhelper.modify('insert into classes(classname) values(%s)', [class_name])
  11. # return redirect('/classes/')
  12. # else:
  13. # pass
  14. # print(class_name)
  15. return HttpResponse('ok')

服务端返回ok不会直接在前端页面渲染出来,发送给data保存起来,再执行函数内部逻辑,这个过程页面始终没有刷新。
如果用ajax发送请求,用 redirect(‘/classes/‘) 不会看到页面刷新,浏览器没有拿到响应体,会把页面内容放到data中,要想完成页面跳转,自己写js,如下图
image.png
image.png
而用form表单提交数据会看到response有页面内容,如下图
image.png
总结
模态对话框
一般都会和Ajax一起使用
使用场景:
少量输入框
少量数据
新url方式
操作多
大量数据以及输入框

3.9 学员管理之基于Ajax编辑班级

js阻止默认事件的发生

  1. <a href="https://www.baidu.com/" onclick="showEditModal()">模态对话框编辑</a>
  2. <script>
  3. //显示编辑模态对话框
  4. function showEditModal(){
  5. document.getElementById('shadow').classList.remove('hide');
  6. document.getElementById('modalEdit').classList.remove('hide');
  7. }
  8. </script>

这里a标签相当于绑定了两个事件,一个是自己定义的事件、一个是默认事件,会先执行自己定义的事件再去执行默认事件,页面效果就是先弹出对话框然后跳转到百度页面,如果想要用阻止默认事件的发生,修改代码后,如下:

  1. <a href="https://www.baidu.com/" onclick="return showEditModal()">模态对话框编辑</a>
  2. <script>
  3. //显示编辑模态对话框
  4. function showEditModal(){
  5. document.getElementById('shadow').classList.remove('hide');
  6. document.getElementById('modalEdit').classList.remove('hide');
  7. return false
  8. }
  9. </script>

难点:点击模态对话框编辑班级,对话框默认显示原来班级名称,不能像之前form表单那样做了。
解决方案:不再向后台发请求,用前端js完成
1.获取当前点击标签
2.获取当前标签的父标签,再找其上方标签
结构如下:
image.png

  1. var v = $(ths).parent().prevAll();//获取当前标签父标签之前所有同级标签

image.png

3.10 学员管理值基于Ajax添加学生信息

与前面自己用js写事件绑定语法不同,这里基于jquery进行事件绑定

  1. <a id="showAddModal">模态对话框添加</a>
  2. <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
  3. <script>
  4. //基于jquary进行事件绑定
  5. $(function (){
  6. $('#showAddModal').click(function (){
  7. //事件处理
  8. })
  9. })
  10. </script>

同样可以阻止默认事件发生—最后加return false

这里点击模态对话框添加会弹出窗口,要求下拉框显示所有班级,此时的做法和之前请求新url添加学生信息不同,这里没有进行form表单提交,没有进行url请求,有一种做法就是请求students/这个url的时候就已经把班级名称都给拿到,再student函数中加入
image.png
相应前端做修改
image.png

3.11 学员管理之基于Ajax编辑学生信息

需求:对于编辑学生窗口,打开时学生班级默认选择原来的班级

  1. <p>学生班级:
  2. <select id="editClassId">
  3. {% for row in classList %}
  4. <option value="{{ row.id }}">{{ row.classname }}</option>
  5. {% endfor %}
  6. </select>
  7. </p>

image.png
image.png
通过dom操作获取当前下拉框中选中的班级id
因为每一个option都有value值
image.png
通过image.png来改变下拉框内的文本

3.12 学员管理之查看老师以及任课班级列表

涉及多对多的表查询,查询sql语句需要连表操作
查看结果如下
image.png
但是这里有一个缺点:如果同一个老师教了多个班级,这还要自己慢慢找这个老师所教的全部班级,如果让所教授班级显示在同一行就可以了,做法就是在后台数据库查询,拿到数据之后进行一些处理,处理图示
image.png
处理代码:

  1. teacher_class_list = [
  2. {'teacherId': 6, 'name': '孙老师', 'classname': '计算机科学11班'},
  3. {'teacherId': 6, 'name': '孙老师', 'classname': '商务英语1班'},
  4. {'teacherId': 10, 'name': '王老师', 'classname': '商务英语1班'},
  5. {'teacherId': 9, 'name': '刘老师', 'classname': '环境艺术1班'},
  6. {'teacherId': 10, 'name': '王老师', 'classname': '环境艺术1班'},
  7. {'teacherId': 9, 'name': '刘老师', 'classname': '环境艺术2班'}
  8. ]
  9. result = {
  10. # 把teacherId当作字典的键,如果键相同就往该键所对应的值中的classnames里面追加班级
  11. # 6:{'teacherId': 6, 'name': '孙老师', 'classnames': ['计算机科学11班','商务英语1班']},
  12. # 10:{'teacherId': 10, 'name': '王老师', 'classname': ['商务英语1班','环境艺术1班']},
  13. # 9: {'teacherId': 9, 'name': '刘老师', 'classname': ['环境艺术1班',环境艺术2班]},
  14. }
  15. for row in teacher_class_list:
  16. teacherId = row['teacherId']
  17. if teacherId in result:
  18. result[teacherId]['classnames'].append(row['classname'])
  19. else:
  20. result[teacherId] = {'teacherId': row['teacherId'], 'name': row['name'], 'classnames': [row['classname']]}
  21. for row in result.values():
  22. print(row)
  23. """
  24. 处理之后的结果
  25. {'teacherId': 6, 'name': '孙老师', 'classnames': ['计算机科学11班', '商务英语1班']}
  26. {'teacherId': 10, 'name': '王老师', 'classnames': ['商务英语1班', '环境艺术1班']}
  27. {'teacherId': 9, 'name': '刘老师', 'classnames': ['环境艺术1班', '环境艺术2班']}
  28. """

3.13 学员管理之添加老师任课信息

在添加老师任课信息的时候,当前端页面添加老师姓名和选取多个任课班级,数据库要首先在老师表里插入一名新老师,然后在老师班级关系表中插入同一名老师id以及任课多个班级id,这个时候用如下代码可以完成
image.png
但是这会频繁的连接和关闭数据库,效率太低,故不采用!

3.14 学员管理之编辑老师任课信息

难点:点击编辑按钮后,默认选择老师原来任课班级

做法:后台查询出老师当前所任教的班级id,前台用in操作符来完成

  1. <select multiple>
  2. {% for item in class_list %}
  3. {#如果item.id在当前老师任教的班级id中#}
  4. {% if item.id in teacher_class_id %}
  5. <option selected>{{ item.classname }}</option>
  6. {% else %}
  7. <option>{{item.classname }}</option>
  8. {% endif %}
  9. {% endfor %}
  10. </select>
  1. teacher_class_id = obj.get_list('select teacher_class.classId from teacher_class,classes where teacher_class.classId=classes.id and teacherId=%s', [teacherId])
  2. #拿到的格式是[{'classId': 45}, {'classId': 46}],前台用in无法判断,要转换成列表,以下代码就是转换成列表的过程
  3. temp = []
  4. for i in teacher_class_id:
  5. temp.append(i['classId'])
  6. print(temp) #[45,46]
  7. return render(request, 'editteacher_class.html', {'teacher': teacher, 'class_list': class_list, 'teacher_class_id': temp})

4 初始Cookie

cookie:
a.保存在用户浏览器端的一个键值对
b.服务端可以给用户浏览器端写cookie
c.客户端每次发请求时,会携带cookie去

例子:类似于张三取登陆的时候输入用户名和密码,发送给后台进行数据库校验,然后成功登陆,然后过了一会张三要去加入购物车,但是此时因为http请求是无状态的,,后台已经无法确定这次申请加入购物车得请求是谁发的,所以需要张三再登陆一次才能加入,而解决办法就是张三登录成功之后会给张三一个纸条,作为张三登陆过的凭证,张三再次加入购物车的时候就给后台看这张凭证,后台就知道张三已经登陆过了,这个凭证其实就是cookie,张三就是浏览器端,所以说cookie是保存在浏览器端的。

作用:用于用户登录

5 Django程序目录介绍

创建app01:python manage.py startapp app01

project 项目文件夹

  • app01:放业务代码,每个业务放到一个app
    • migrations:数据库更新历史
    • admin.py:django自带后台管理相关配置
    • apps.py: 存放当前应用程序的配置
    • models:写django里面orm用来做映射的类
    • tests.py:单元测试
    • url.py:二级路由文件
    • views.py:视图函数(业务处理)
  • project 与项目名称相同文件夹
    • setting:该项目配置文件
    • urls:一级路由文件
    • wsgi:用于定义django用socket,wsgiref,uwsgi
  • manage:对当前django程序所有操作可以基于python manage.py runserver

6 路由系统

6.1 路由系统之动态路由

以前对某个记录做编辑的时候,url会自动跳转并携带参数
image.png
这种url是不好的,因为权重小,比如百度搜索关键字,这种url会放到搜索内容的下面,而且不美观。

image.png
这里箭头所指实际上是正则表达式,如果不写/那么可以匹配index后面加人以字母的url,加了/只有index/才能匹配到,所以最好用index$这种形式,这样访问的时候不需要加/

以后用这种形式

  1. #url(r'^index$', views.index), # $表示终止符
  2. #url(r'^edit/(\w+).html$', views.edit),
  3. #url(r'^edit/(\w+)/(\w+)', views.edit) # 动态路由,后面的正则,视图函数要用相同数目的用参数接收
  4. #url(r'^edit/(?P<a2>\w+)/(?P<a1>\w+)', views.edit) # 另一种动态路由方式,可以给指定参数传值
  5. def index(requests):
  6. user_list = ['liuxuebin', 'zhangsan', 'lisi']
  7. return render(requests, 'index.html', {'user_list': user_list})
  8. def edit(request, *args,**kwargs):
  9. """
  10. :param request:
  11. :param a1: 正则表达式参数
  12. :param a2: 正则表达式参数
  13. :return:
  14. """
  15. print(args)
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <ul>
  9. {% for i in user_list %}
  10. <li>{{ i }}|<a href="/edit/{{ i }}.html">编辑</a></li>
  11. {% endfor %}
  12. </ul>
  13. </body>
  14. </html>

6.2 路由系统之路由分发

当多个人协同开发的时候,每个人会写一个app文件,里面都有各自的views,每个人都会再urls里面写路由,写的过程中可能会出现相同的路由,所以需要一种机制,让每一个app都有各自的url,这样就不会出现相同的路由了,这种机制就是路由分发

实现方法:
项目同名文件夹下的urls:

  1. from django.conf.urls import url, include
  2. urlpatterns = [
  3. url(r'^app01/', include('app01.urls')),
  4. url(r'^app02/', include('app02.urls')),

app01下的urls:

  1. from django.conf.urls import url
  2. from app01 import views
  3. urlpatterns = [
  4. url(r'^index.html$', views.index)
  5. ]

app02下的urls:

  1. from django.conf.urls import url
  2. from app02 import views
  3. urlpatterns = [
  4. url(r'^index.html$', views.index),
  5. ]

6.3 路由系统之别名URL

后端反生成url
urls.py下

  1. from django.conf.urls import url, include
  2. from django.shortcuts import HttpResponse
  3. from app01 import views
  4. def default(request):
  5. """
  6. 路由匹配失败,返回的页面
  7. :param request:
  8. :return:
  9. """
  10. return HttpResponse('路由匹配失败')
  11. urlpatterns = [
  12. url(r'^index/(\d+)/', views.index, name='n1'), # name='n1' 是根据名字反推url

app01/views.py下

  1. from django.shortcuts import render
  2. from django.shortcuts import HttpResponse
  3. from django.urls import reverse
  4. def index(requests, a1):
  5. user_list = ['liuxuebin', 'zhangsan', 'lisi']
  6. # url.py文件下url用的正则,后台想要反向生成url并重新将url修改,args的参数是多少,就生成多少
  7. v = reverse('n1', args=(1,))
  8. print(v)
  9. return render(requests, 'index.html', {'user_list': user_list})

前端反生成url

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. {#根据名称反生成url#}
  9. <form method="post" action="{% url 'm1' %}">
  10. <input type="text">
  11. </form>
  12. </body>
  13. </html>

urls.py

  1. from django.conf.urls import url
  2. from app01 import views
  3. urlpatterns = [
  4. url(r'^login/', views.login, name='m1')

views.py

  1. def login(request):
  2. return render(request, 'login.html')

index.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <ul>
  9. {% for i in user_list %}
  10. <li>{{ i }}|<a href="{% url 'n2' i %}">编辑</a></li>
  11. {% endfor %}
  12. </ul>
  13. </body>
  14. </html>

模板渲染之后的结果
image.png

好处:不需要自己写url,特别是url特别长的时候,这种方法适用

应用场景:写用户的权限管理会用

7 ORM

ORM操作表:
创建表
修改表
删除表
ORM操作数据行:
增删改查

ORM要利用pymysql第三方工具连接数据库
默认:
连接mysql数据库使用的是mysqlDB(使用django的orm框架时,要修改连接mysql方式)
django默认连接的是sqllite

7.1 ORM操作之准备

1.创建数据库
2.配置文件setting

  1. DATABASES = {
  2. 'default': {
  3. 'ENGINE': 'django.db.backends.mysql',
  4. 'NAME':'testDB',
  5. 'USER': 'root',
  6. 'PASSWORD': '362514',
  7. 'HOST': 'localhost',
  8. 'PORT': '3306',
  9. }
  10. }

3.init.py下

  1. import pymysql
  2. pymysql.version_info = (1, 4, 13, "final", 0)
  3. pymysql.install_as_MySQLdb() # 使用pymysql代替mysqldb连接数据库

7.2 ORM操作之创建数据表

1.app01/models

  1. from django.db import models
  2. class UserInfo(models.Model):
  3. nid = models.BigAutoField(primary_key=True) # 可以不写,会默认生成id这一列且为主键可自增的
  4. userName = models.CharField(max_length=32)
  5. passWord = models.CharField(max_length=64)

2.注册app settings.py下

  1. INSTALLED_APPS = [
  2. 'django.contrib.admin',
  3. 'django.contrib.auth',
  4. 'django.contrib.contenttypes',
  5. 'django.contrib.sessions',
  6. 'django.contrib.messages',
  7. 'django.contrib.staticfiles',
  8. 'app01'
  9. ]

除了app01其他文件下面也有models,models下面也有类,也会映射到表

3.创建数据表命令
python manage.py makemigrations
python manage.py migrate

python manage.py makemigrations
会读取models.py然后自动生成一个配置文件在migrations文件夹下
python manage.py migrate
根据配置文件生成数据库语句
image.png记录了你对数据库操作的所有记录,配置文件自下而上是依赖关系

创建一对多表

  1. from django.db import models
  2. class UserGroup(models.Model):
  3. title = models.CharField(max_length=32)
  4. class UserInfo(models.Model):
  5. nid = models.BigAutoField(primary_key=True)
  6. userName = models.CharField(max_length=32)
  7. passWord = models.CharField(max_length=64)
  8. # age = models.IntegerField(null=True) 设置为空值
  9. age = models.IntegerField(default=1) # 设置默认值1
  10. userGroup = models.ForeignKey('UserGroup', null=True, on_delete=models.CASCADE) # 外键

现创建UserGroup和UserInfo是一对多关系的表,UserInfo里面添加外键,当执行ForeigKey命令时数据库生成的时候自动在外键名后加_id,同时UserGroup表会默认生成id字段

创建多对多表

第一种方式

  1. class Classes(models.Model):
  2. className = models.CharField(max_length=32)
  3. class Teachers(models.Model):
  4. name = models.CharField(max_length=32)
  5. class TeacherClass(models.Model):
  6. teacher = models.ForeignKey('Teachers', null=True, on_delete=models.CASCADE)
  7. classRoom = models.ForeignKey('Classes', null=True, on_delete=models.CASCADE)

第二种方式

  1. class Classes(models.Model):
  2. className = models.CharField(max_length=32)
  3. # 写Classes或者Teachers都可以,会生成一个新表classes_m
  4. m = models.ManyToManyField('Teachers')
  5. class Teachers(models.Model):
  6. name = models.CharField(max_length=32)

第二种方式的增删改查

  1. # obj.m可以是一个ManyRelatedManager对象
  2. obj.m.add(2) # 增
  3. obj.m.remove(4) # 删
  4. obj.m.set([1, ]) # 重置
  5. obj.m.clear() # 清空

老师和班级是多对多关系
第二种方式只能生成3列(主键id,和两个表id),若需要加其他列,只能用第一种方式

7.3 ORM操作之修改表

1.修改字段名
直接在类里面修改属性,然后运行命令即可。

2.增加字段
要给定参数,是空值还是给一个默认值

7.4 ORM操作之单表增删改查

新增

  1. from django.shortcuts import render, HttpResponse
  2. from app01 import models
  3. # 数据库相关操作
  4. def index(request):
  5. # 新增
  6. # models.UserGroup.objects.create(title='销售部')
  7. # models.UserInfo.objects.create(userName='张三', passWord='123', age=18, userGroup_id=1)

查询

无条件查询

  1. from django.shortcuts import render, HttpResponse
  2. from app01 import models
  3. # 数据库相关操作
  4. def index(request):
  5. # 查询
  6. groupList = models.UserType.objects.all() # gropList类型为QuerySet,里面有一些对象
  7. groupList1 = models.UserType.objects.all()[0:2] # 相当于切片,取连续的某几条记录
  8. for row in groupList:
  9. print(row.id, row.title)
  10. # userList = models.UserInfo.objects.all()
  11. print(groupList) # <QuerySet [<UserGroup: UserGroup object (1)>]>

这里groupList是一个QuerySet对象,这个对象其实可以看做是列表,里面又装了一堆UserGroup对象。

条件查询

  1. from django.shortcuts import render, HttpResponse
  2. from app01 import models
  3. # 数据库相关操作
  4. def index(request):
  5. # 条件查询
  6. userList = models.UserInfo.objects.filter(nid=1)
  7. # id>1
  8. # user = models.UserInfo.objects.filter(id__gt=1)
  9. # # id<1
  10. # user = models.UserInfo.objects.filter(id__lt=1)
  11. for row in userList:
  12. print(row.userName)

删除

  1. # 删除
  2. models.UserGroup.objects.filter(id=2).delete()

更新

  1. models.UserGroup.objects.filter(id=1).update(title='研发部')

7.5 多表连接

现在有三张表,且已经建立主外键关系
models.py

  1. from django.db import models
  2. class Foo(models.Model):
  3. caption = models.CharField(max_length=32)
  4. class UserInfo(models.Model):
  5. id = models.BigAutoField(primary_key=True)
  6. userName = models.CharField(max_length=32)
  7. passWord = models.CharField(max_length=64)
  8. # age = models.IntegerField(null=True) 设置为空值
  9. age = models.IntegerField(default=1) # 设置默认值1
  10. userType = models.ForeignKey('UserType', null=True, on_delete=models.CASCADE)
  11. class UserType(models.Model):
  12. title = models.CharField(max_length=32)
  13. foo = models.ForeignKey('Foo', null=True, on_delete=models.CASCADE)

正向操作

views.py

  1. from app01 import models
  2. def test(request):
  3. result = models.UserInfo.objects.all().first()
  4. print(result.userName, result.userType_id, result.userType.title, result.userType.foo.caption)

当前被查询表—UserInfo
关联关系:
UserInfo外键—userType
UserType外键—foo
通过当前被查询表.外键就相当于当前表与关联表做了左连接操作形成了一个新表

  1. from app01 import models
  2. from django.shortcuts import render
  3. def test(request):
  4. # 正向操作 等价于 select app01_userinfo.id,app01_userinfo.userName,app01_usertype.title from app01_userinfo LEFT JOIN app01_usertype on app01_userinfo.userType_id=app01_usertype.id
  5. v1 = models.UserInfo.objects.values('id', 'userName', 'userType__title') # v1是一个Queryset对象,里面是一个个字典
  6. v2 = models.UserInfo.objects.values_list('id', 'userName', 'userType__title') # v2是一个Queryset对象,里面是一个个元组
  7. v3 = models.UserInfo.objects.values('id', 'userName', 'userType') # 不加双下划线默认输出id
  8. v4 = models.U

models.UserInfo.objects.values(‘id’, ‘userName’, ‘外键’)

  1. def test(request):
  2. v4 = models.UserInfo.objects.filter(userType__title='普通用户').values('id', 'userName')
  3. print(v4)

连表之后进行条件筛选,再输出指定字段

反向操作

  1. from app01 import models
  2. # 数据库相关操作
  3. def test(request):
  4. result = models.UserType.objects.all().first() # 查询第一条数据
  5. print('用户类型', result.title)
  6. # result.userinfo_set.all()是一个Queryset对象
  7. for row in result.userinfo_set.all():
  8. print(row.userName, row.age)

当前查询表UserType,与UserInfo有关联,但是外键在UserInfo表中,但是该表具有一个隐含属性,就是关联表的小写加上 _set 再加上.all()就可以获取关联表的所有对象

  1. from app01 import models
  2. from django.shortcuts import render
  3. def test(request):
  4. # 反向操作 等价于 select app01_usertype.id,app01_usertype.title,app01_userinfo.id from app01_usertype LEFT JOIN app01_userinfo on app01_usertype.id=app01_userinfo.userType_id
  5. v2 = models.UserType.objects.values('id', 'title', 'userinfo')

v2 = models.UserType.objects.values(‘id’, ‘title’, ‘要跨的表名(小写)’) 只写表名取到的是这个表的id,要去某个属性,就加双下划线

  1. def test(request):
  2. v2 = models.UserType.objects.filter(userinfo__userType_id=1).values('id', 'title', 'userinfo__userName')
  3. print(v2)

连表之后进行条件筛选,再输出指定字段

7.6 其他操作

排序

  1. def test(request):
  2. v = models.UserInfo.objects.all().order_by('-id') # 根据id降序排列
  3. print(v)

分组

  1. def test(request):
  2. # 等价于SELECT `app01_userinfo`.`userType_id`, COUNT(`app01_userinfo`.`id`) AS `xxxx` FROM `app01_userinfo` GROUP BY `app01_userinfo`.`userType_id` HAVING COUNT(`app01_use
  3. rinfo`.`id`) > 2 ORDER BY NULL
  4. v = models.UserInfo.objects.values('userType_id').annotate(xxxx=Count('id')).filter(xxxx__gt=2)
  5. print(v.query)
  1. def test(request):
  2. v = models.UserInfo.objects.filter(id__gt=2).values('userType_id').annotate(xxxx=Count('id'))
  3. print(v.query)

filter在annotate前面相当于where,在后面相当于having

in betwween and 大于小于

  1. models.UserInfo.objects.filter(id__gt=1) # 大于
  2. models.UserInfo.objects.filter(id__lt=1) # 小于
  3. models.UserInfo.objects.filter(id__lte=1)# 小于等于
  4. models.UserInfo.objects.filter(id__gte=1)# 大于等于
  5. models.UserInfo.objects.filter(id__in=[1, 2, 3])#in
  6. models.UserInfo.objects.filter(id__range=[1, 2])#between and
  7. models.UserInfo.objects.filter(userName__startswith='阿')# like
  8. models.UserInfo.objects.filter(userName__contains='阿')# contains
  9. models.UserInfo.objects.exclude(id=1) # 不等于

7.7 高级操作

  1. # F
  2. # from django.db.models import F
  3. # 让所有年龄加1
  4. # models.UserInfo.objects.update(age=F('age')+1)
  1. # Q 常用于组合查询,构造复杂查询条件
  2. # con = Q() 创建父条件对象
  3. # q1 = Q() 创建子条件对象1
  4. # q1.connector = 'OR'
  5. # q1.children.append(('id', 1))
  6. # q1.children.append(('id', 10))
  7. # q1.children.append(('id', 9))
  8. # 生成子条件1 id=1 or id=10 or id=9
  9. # q2 = Q() 创建条件对象2
  10. # q2.connector = 'OR'
  11. # q2.children.append(('c1', 1))
  12. # q2.children.append(('c1', 10))
  13. # q2.children.append(('c1', 9))
  14. # 生成子条件2 c1=1 or c1=10 or c1=9
  15. # 子条件添加到父条件中
  16. # con.add(q1, 'AND')
  17. # con.add(q2, 'AND')
  18. # 生成父条件 (id=1 or id=10 or id=9) and (c1=1 or c1=10 or c1=9)
  19. # models.Tb1.objects.filter(con)

extra方法
用于额外查询条件以及相关表、排序
image.png

  1. models.UserInfo.objects.values('id', 'userName').extra(
  2. select={'n': 'select count(1) from app01_usertype where id>%s'},
  3. select_params=1,
  4. where=['age>%s'],
  5. params=18,
  6. order_by=['-age'],
  7. tables=['app01_usertype'])
  8. # select id,userName,(select count(1) from app01_usertype where id>1) from app01_userinfo,app01_usertype
  9. # where age>18 order by age desc
  1. models.UserInfo.objects.values('id', 'userName').extra(select={'n': 'select count(1) from app01_usertype where id>%s'},
  2. select_params=1,
  3. where=['age>%s'],
  4. params=18,
  5. order_by=['-age'],
  6. tables=['app01_usertype'])

select和select_params一起使用,用来做字段查询

  1. # select userName,(select count(1) from app01_userinfo) as n from app01_userinfo
  2. v = models.UserInfo.objects.all().extra(select={'n': "select count(1) from app01_userinfo"})
  3. for obj in v:
  4. print(obj.userName, obj.n)
  1. # select userName,(select count(1) from app01_userinfo where id>1 and id<3) as n from app01_userinfo
  2. v = models.UserInfo.objects.all().extra(select={'n': "select count(1) from app01_userinfo where id>%s and id<%s",'m':'select count(1) from app01_userinfo where id>%s and id<%s'},select_params=[1,3,4,7])
  3. for obj in v:
  4. print(obj.userName, obj.n)

where和params一起使用,用来做条件查询

  1. # select userName from app01_userinfo where id=1 or id=2 and name='alex'
  2. models.UserInfo.objects.extra(where=['id=1 or id=%s', 'name=%s'],params=[2, 'alex'])
  1. # select * from app01_userinfo,app01_usertype
  2. models.UserInfo.objects.extra(tables=['app01_usertype'])

7.8 django后台管理页面

EmailField(CharField)
IPAddressField(Field)
GenericIPAddressField(Field)
URLField(CharField)
SlugField(CharField)
CommaSeparatedIntegerField(CharField)
UUIDField(Field)
FilePathField(Field)
FileField(Field)
ImageField(FileField)
以上这些对应数据库都是把字段设置为字符串类型,只影响admin

通过python manage.py createsuperuser命令创建超级用户,然后登录后台管理页面
image.png
通过在admin.py里面写入admin.site.register(models.UserInfo),页面会产生你所注册的表
image.png
添加用户记录,发现email报错,是因为image.png
会在django的admin页面进行验证是否符合正确格式

8 CBV和FBV

原来是一个url对应一个函数,现在是一个url对应一个类,类里面有多个函数,提交的http请求如果是get请求方式,则自动执行get函数,如果请求方式是post,则自动执行post函数

CBV—基于反射实现,根据请求方式不同,执行不同的方法
views

  1. from django.shortcuts import render,HttpResponse
  2. from django.views import View
  3. class Login(View):
  4. def get(self, request):
  5. return render(request, 'login.html')
  6. def post(self, request):
  7. return HttpResponse('post')
  8. def delete(self,request):
  9. return HttpResponse('delete')

url.py

  1. url(r'^student',view.Login.as_view())

原理:a.从路由开始url->view.Login.as_view() as_view函数返回一个view,所以执行view函数—》调用dispach方法,-》反射

9 django自带分页

学员管理
views.py

  1. from app01 import models
  2. from django.core.paginator import Paginator, Page, PageNotAnInteger, EmptyPage
  3. def pagination(request,page):
  4. """
  5. django内置分页
  6. :param request:
  7. :param page: 当前页码
  8. :return:
  9. """
  10. classList = models.Classes.objects.all()
  11. currentPage = page
  12. paginator = Paginator(classList, 10) # 每页显示10条数据
  13. post = paginator.page(currentPage) # 当前显示第几页
  14. return render(request, 'paginator.html', {'post': post})

index.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <ul>
  9. {% for row in post.object_list %}
  10. <li>{{ row.className }}</li>
  11. {% endfor %}
  12. </ul>
  13. <div>
  14. {% if post.has_previous %}
  15. <a href="/app01/pagination/{{ post.previous_page_number }}.html">上一页</a>
  16. {% endif %}
  17. {% for num in post.paginator.page_range %}
  18. <a href="/app01/pagination/{{ num }}.html">{{ num }}</a>
  19. {% endfor %}
  20. {% if post.has_next %}
  21. <a href="/app01/pagination/{{ post.next_page_number }}.html">下一页</a>
  22. {% endif %}
  23. </div>
  24. </body>
  25. </html>

缺点:因为page_range方法,会显示所有页码,所以数据越多,页码越多所以只适合于做上下页。

10 django视图之自定义分页

TODO

11 XSS攻击

例子:有人进行评论的时候写入了非法字符例如,后端接收之后,在前台没有进行过滤而直接渲染出来会造成危险,而django内部对此做出了预防

前端写评论,提交之后后台接收

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <form method="post" action="/comment.html">
  9. <input type="text" name="content">
  10. <input type="submit" value="提交">
  11. </form>
  12. {% for row in msg %}
  13. <p>{{ row }}</p> # 接收后台发过来的数据,若没有加safe则自动注释非法字符
  14. {% endfor %}
  15. </body>
  16. </html>

评论里写的是美女则渲染的时候自动用其他替换,发给前端
image.png

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <form method="post" action="/comment.html">
  9. <input type="text" name="content">
  10. <input type="submit" value="提交">
  11. </form>
  12. {% for row in msg %}
  13. <p>{{ row|safe }}</p> # 加了safe 则不会过滤掉非法字符
  14. {% endfor %}
  15. </body>
  16. </html>

image.png
渲染的时候会将输入不安全的内容渲染出来,发给前端

12 CSRF

下面是我的自己写的网站,如果我已经登陆了招商银行,本地存在Cookie,下面这个是转账的表单,点击就会完成转账

  1. <form action="www.zhaoshangyinhang.com" method="post">
  2. <input type="text" value="987899" name="to">
  3. <input type="text" value="87890" name="money">
  4. <input type="submit" value="点击我">
  5. </form>

django这个中间件表示需要进行csrf验证,也就是说你发送post请求必须还要携带django提供的随机字符串,post请求才能被处理
image.png
比如说我现在注释掉这个中间件,

  1. <form action="/csrf1/" method="post">
  2. <input type="text" value="987899" name="to">
  3. <input type="text" value="87890" name="money">
  4. <input type="submit" value="点击我">
  5. </form>
  1. from django.shortcuts import render,HttpResponse
  2. def csrf1(request):
  3. if request.method == "GET":
  4. return render(request,'csrf1.html')
  5. else:
  6. return HttpResponse('通过csrf验证')

会显示通过csrf验证
如果不注释这个中间件,会进行csrf验证,又因为post没携带随机字符串,所以会报错403!
想要生成随机字符串,就要使用以下代码{% csrf_token %}
再次发送get请求会生成一个隐藏的input框,里面有默认的随机字符串,这样发送post请求,就可以完成csrf验证
image.png
并且cookie也会有csrf_token
image.png
csrf具体讲解

14 Session

保存在服务器端的数据(本质是键值对)
应用:依赖于Cookie
作用:保持会话(web网站)
内部流程:用户登录,登陆成功后,服务器端会生成一个随机字符串自己保存一份作为key,然后返回到前端的cookie里面,每个用户对应一个随机字符串,然后继续发送请求会携带随机字符串过来,在服务器端比对,比对成功就返回相应的value。
优点:敏感信息不会直接给客户端
image.png

14.1 Session基本操作

TODO

14.2 Session配置文件和其它操作

TODO

14.3 Session数据源配置

15 WSGI拾遗

  1. import socket
  2. def handle_request(client):
  3. buf = client.recv(1024) # 获取用户请求
  4. buf = str(buf, encoding='utf-8')
  5. # 自己解析,各种split
  6. hearders, bodys = buf.split('\r\n\r\n')
  7. temp_list = hearders.split('\r\n')
  8. method, url, protocal = temp_list[0].split(' ')
  9. # 请求相关
  10. django
  11. #产出字符串
  12. client.send(产出字符串)
  13. def main():
  14. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  15. # 127.0.0.1 指的是自己的主机 不是公网ip
  16. sock.bind(('127.0.0.1', 8080))
  17. sock.listen(5)
  18. while True:
  19. connection, address = sock.accept()
  20. handle_request(connection)
  21. connection.close()
  22. if __name__ == '__main__':
  23. main()

socket做的主要是给django请求之前的一系列处理(获取用户请求,进行请求的解析等)以及把django产生的字符串发给客户端

wsgiref使用

  1. from wsgiref.simple_server import make_server
  2. # runserver 由wsgi服务器调用、函数对http请求与响应的封装、使得Python专注于处理
  3. # environ http 请求 (dist)
  4. # start_response 响应 (function)
  5. def runserver(environ, start_response):
  6. # django框架开始
  7. # 中间件
  8. # 路由系统
  9. # 视图函数
  10. # 响应请求头
  11. start_response('200 OK', [('Content-Type', 'text/html')])
  12. return [b'<h1>hi, web!</h1>']
  13. # 启动服务器 | 这个服务器负责与 wsgi 接口的 runserver 函数对接数据
  14. httpd = make_server('127.0.0.1', 8000, runserver)
  15. # 监听请求
  16. httpd.serve_forever()

django生命周期:
客户端请求->wsgi->中间件->视图函数->拿到数据库数据和模板渲染->回中间件->wsgi->客户端页面

16 MVC和MTV

  1. from wsgiref.simple_server import make_server
  2. def index():
  3. return [b'index']
  4. def login():
  5. return [b'login']
  6. routers = [
  7. ('/index', index),
  8. ('/login', login)
  9. ]
  10. def runserver(environ, start_response):
  11. func_name = None
  12. routers = [
  13. ('/index', index),
  14. ('/login', login)
  15. ]
  16. url = environ['PATH_INFO']
  17. print(url)
  18. for item in routers:
  19. if item[0] == url:
  20. func_name = item[1]
  21. break
  22. if func_name:
  23. start_response('200 OK', [('Content-Type', 'text/html')])
  24. return func_name()
  25. else:
  26. start_response('404 OK', [('Content-Type', 'text/html')])
  27. return [b'404 not found']
  28. if __name__ == '__main__':
  29. httpd = make_server('127.0.0.1', 8080, runserver)
  30. # 监听请求
  31. httpd.serve_forever()

当视图函数很多时,代码都放在一个py文件会降低可读性,不方便管理,要分开放

MVC三个目录:
-models 数据库操作 模型
-views html模板
-contrller 视图函数

MTV三个目录:
-models 数据库操作 模型
-views 视图函数
-templates html模板

16 中间件

17 Form组件

问题1:
若一个页面进行登陆的时候,需要填写很多表单,现在的情况是你填写时,只要有一个写错了,因为网页无法记住你上次写入的数据,刷新页面之后,数据就消失了
问题2:
现在对用户名和密码提出一些要求:
用户名:不能为空,长度5-18,必须邮箱格式
密码:不能为空,长度5-18,必须包含字母数字下划线
可以采用传统方法—重复对用户数据进行校验:正则、长度、是否为空的判断,但是需要写好多重复代码,效率很低!

form组件功能:
1.数据校验
2.生成html标签

用户提交数据方式:
1.form表单提交(刷新,失去上次内容)
2.ajax提交(不刷新,保留上次内容)

第一版本

  1. def login(request):
  2. if request.method == 'GET':
  3. return render(request, 'login.html')
  4. else:
  5. user = request.POST.get('user')
  6. # 用户名的规则校验
  7. pwd = request.POST.get('pwd')
  8. # 密码的规则校验
  9. if user == 'Alex' and pwd == '123':
  10. return redirect('/index/')
  11. else:
  12. return render(request, 'login.html',{'msg':'用户名或密码错误'})

17.1 Form验证流程

  1. 定义校验规则的类 LoginForm(Form)
  2. 创建对象 obj = LoginForm(用户提交的数据)
  3. obj.is_valid()
  4. obj.cleaned_data
  5. obj.errors

注意:
1.前端传来的数据要和规则个数相同
2.前端name属性值要和规则字段名相同
3.设置错误信息为中文的另一个方法:在setting中设置LANGUAGE_CODE = ‘zh-hans’

form组件—- obj.errors方法
输出结果和类型
image.png
image.png

17.2 Form和ajax提交验证

源码见13Form验证

17.3 ajax提交至显示错误信息

源码见13Form验证

17.4 Form组件之常用字段

https://www.cnblogs.com/wupeiqi/articles/6144178.html武沛奇博客

初始化子类,被传递的参数可以是父类的,原因是如下:

  1. class Person:
  2. def __init__(self, name, age):
  3. self.name = name
  4. self.age = age
  5. def study(self):
  6. print('%s,%s岁 在学习' % (self.name, self.age))
  7. class Worker(Person):
  8. def __init__(self, salary, **kargs):
  9. self.salary = salary
  10. # 调用父类构造方法,初始化worker对象的时候可以给父类的参数传参
  11. super().__init__(**kargs)
  12. def get_salary(self):
  13. print('salart')
  14. obj = Worker(salary=182321,name='xiaoming',age=19)
  15. obj.study()
  16. obj.get_salary()

17.5 Form组件之相关参数

  1. 用于表单验证
  2. required=True, 是否允许为空
  3. error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'}
  1. 用于生成html标签 模板里要写{{对象.as_p}}
  2. widget=None, HTML插件
  3. label=None, 用于生成Label标签或显示内容
  4. initial=None, 初始值
  5. help_text='', 帮助信息(在标签旁边显示)
  6. show_hidden_initial=False, 是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直)
  7. validators=[], 自定义验证规则
  8. localize=False, 是否支持本地化
  9. disabled=False, 是否可以编辑
  10. label_suffix=None Label内容后缀

自动生成html标签代码

  1. class LoginForm(Form):
  2. """
  3. 定义校验规则的类
  4. """
  5. # 长度、是否为空的校验
  6. user = fields.CharField(max_length=18, label='asdasda', min_length=6, required=True, error_messages={
  7. 'required': '用户名不能为空',
  8. 'max_lenth': '长度大于18',
  9. 'min_lenth': '长度小于6'
  10. })
  11. pwd = fields.CharField(max_length=16, required=True)
  12. def login_form(request):
  13. """
  14. 数据校验,提交方式是form
  15. :param request:
  16. :return:
  17. """
  18. if request.method == 'GET':
  19. obj = LoginForm()
  20. # print(obj.base_fields['user'].label) # 前端访问Field实例属性label
  21. return render(request, 'login.html', {'obj': obj})
  1. 默认前端显示input框
  2. <p>
  3. {{ obj.user }}
  4. </p>
  5. <p>
  6. {{ obj.pwd }}
  7. </p>

17.6 Form组件之保留上次输入内容

如果是用form组件自动生成的html标签,进行foorm表单提交的时候,自动保留上次输入内容

17.7 疑问

  • checkbox、select使用Form组件

-默认值
-保留上次输入值

  • 自定义验证规则