写一个简易版本的web框架
### 软件开发架构
```python
cs架构
bs架构
# 本质bs也是cs
```
### 纯手撸web框架
```python
# HTTP协议
"""
网络协议
HTTP协议 数据传输是明文
HTTPS协议 数据传输是密文
websocket协议 数据传输是密文
四大特性
1.基于请求响应
2.基于TCP、IP作用于应用层之上的协议
3.无状态
4.短/无链接
数据格式
请求首行
请求头
请求体
响应状态码
1XX
2XX 200
3XX
4XX 403 404
5XX 500
"""
# 如何做到后缀的不同返回不同的内容
# 拿到用户输入的后缀 做判断
# 不足之处
1.代码重复(服务端代码所有人都要重复写)
2.手动处理http格式的数据 并且只能拿到url后缀 其他数据获取繁琐(数据格式一样处理的代码其实也大致一样 重复写)
3.并发的问题
```
### 借助于wsgiref模块
```python
"""
urls.py 路由与视图函数对应关系
views.py 视图函数(后端业务逻辑)
templates文件夹 专门用来存储html文件
"""
# 按照功能的不同拆分之后 后续添加功能只需要在urls.py书写对应关系然后取views.py书写业务逻辑即可
```
### 动静态网页
```python
"""
静态网页
页面上的数据是直接写死的 万年不变
动态网页
数据是实时获取的
eg:
1.后端获取当前时间展示到html页面上
2.数据是从数据库中获取的展示到html页面上
"""
# 动态网页制作
import datetime
def get_time(env):
current_time = datetime.datetime.now().strftime('%Y-%m-%d %X')
# 如何将后端获取到的数据"传递"给html文件?
with open(r'templates/03 mytime.html','r',encoding='utf-8') as f:
data = f.read()
# data就是一堆字符串
data = data.replace('dwadasdsadsadasdas',current_time) # 在后端将html页面处理好之后再返回给前端
return data
# 将一个字典传递给html文件 并且可以在文件上方便快捷的操作字典数据
from jinja2 import Template
def get_dict(env):
user_dic = {'username':'jason','age':18,'hobby':'read'}
with open(r'templates/04 get_dict.html','r',encoding='utf-8') as f:
data = f.read()
tmp = Template(data)
res = tmp.render(user=user_dic)
# 给get_dict.html传递了一个值 页面上通过变量名user就能够拿到user_dict
return res
# 后端获取数据库中数据展示到前端页面
```
### 模版语法之Jinja2模块
```python
pip3 install jinja2
"""模版语法是在后端起作用的"""
# 模版语法(非常贴近python语法)
{{ user }}
{{ user.get('username')}}
{{ user.age }}
{{ user['hobby'] }}
{% for user_dict in user_list %}
<tr>
<td>{{ user_dict.id}}</td>
<td>{{ user_dict.username}}</td>
<td>{{ user_dict.password}}</td>
<td>{{ user_dict.hobby}}</td>
</tr>
{% endfor%}
```
### 自定义简易版本web框架请求流程图
```python
"""
wsgiref模块
1.请求来的时候解析http格式的数据 封装成大字典
2.响应走的时候给数据打包成符合http格式 再返回给浏览器
"""
```
### python三大主流web框架
```python
"""
django
特点:大而全 自带的功能特别特别特别的多 类似于航空母舰
不足之处:
有时候过于笨重
flask
特点:小而精 自带的功能特别特别特别的少 类似于游骑兵
第三方的模块特别特别特别的多,如果将flask第三方的模块加起来完全可以盖过django
并且也越来越像django
不足之处:
比较依赖于第三方的开发者
tornado
特点:异步非阻塞 支持高并发
牛逼到甚至可以开发游戏服务器
不足之处:
暂时你不会
"""
A:socket部分
B:路由与视图函数对应关系(路由匹配)
C:模版语法
django
A用的是别人的 wsgiref模块
B用的是自己的
C用的是自己的(没有jinja2好用 但是也很方便)
flask
A用的是别人的 werkzeug(内部还是wsgiref模块)
B自己写的
C用的别人的(jinja2)
tornado
A,B,C都是自己写的
```
### 注意事项
```python
# 如何让你的计算机能够正常的启动django项目
1.计算机的名称不能有中文
2.一个pycharm窗口只开一个项目
3.项目里面所有的文件也尽量不要出现中文
4.python解释器尽量使用3.4~3.6之间的版本
(如果你的项目报错 你点击最后一个报错信息
去源码中把逗号删掉)
# django版本问题
1.X 2.X 3.X(直接忽略)
1.X和2.X本身差距也不大 我们讲解主要以1.X为例 会讲解2.X区别
公司之前用的1.8 满满过渡到了1.11版本 有一些项目用的2.0
# django安装
pip3 install django==1.11.11
如果已经安装了其他版本 无需自己卸载
直接重新装 会自动卸载安装新的
如果报错 看看是不是timeout 如果是 那么只是网速波动
重新安装即可
验证是否安装成功的方式1
终端输入django-admin看看有没有反应
```
### django基本操作
```python
# 命令行操作
# 1.创建django项目
"""
你可以先切换到对应的D盘 然后再创建
"""
django-admin startproject mysite
mysite文件夹
manage.py
mysite文件夹
__init__.py
settings.py
urls.py
wsgi.py
# 2.启动django项目
"""
一定要先切换到项目目录下
cd /mysite
"""
python3 manage.py runserver
# http://127.0.0.1:8000/
# 3.创建应用
"""
Next, start your first app by running python manage.py startapp [app_label].
"""
python manage.py startapp app01
应用名应该做到见名知意
user
order
web
...
但是我们教学统一就用app01/02/03/04
有很多文件
# pycharm操作
# 1 new project 选择左侧第二个django即可
# 2 启动
1.还是用命令行启动
2.点击绿色小箭头即可
# 3 创建应用
1.pycharm提供的终端直接输入完整命令
2.pycharm
tools
run manage.py task提示(前期不要用 给我背完整命令)
# 4 修改端口号以及创建server
edit confi....
```
### 应用
```python
"""
django是一款专门用来开发app的web框架
django框架就类似于是一所大学(空壳子)
app就类似于大学里面各个学院(具体功能的app)
比如开发淘宝
订单相关
用户相关
投诉相关
创建不同的app对应不同的功能
选课系统
学生功能
老师功能
一个app就是一个独立的功能模块
"""
***********************创建的应用一定要去配置文件中注册**********************
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01.apps.App01Config', # 全写
'app01', # 简写
]
# 创建出来的的应用第一步先去配置文件中注册 其他的先不要给我干
ps:你在用pycharm创建项目的时候 pycharm可以帮你创建一个app并且自动注册
***********************************************************************
```
### 主要文件介绍
```python
-mysite项目文件夹
--mysite文件夹
---settings.py 配置文件
---urls.py 路由与视图函数对应关系(路由层)
---wsgi.py wsgiref模块(不考虑)
--manage.py django的入口文件
--db.sqlite3 django自带的sqlite3数据库(小型数据库 功能不是很多还有bug)
--app01文件夹
---admin.py django后台管理
---apps.py 注册使用
---migrations文件夹 数据库迁移记录
---models.py 数据库相关的 模型类(orm)
---tests.py 测试文件
---views.py 视图函数(视图层)
```
### 命令行与pycharm创建的区别
```python
# 1 命令行创建不会自动有templatew文件夹 需要你自己手动创建而pycharm会自动帮你创建并且还会自动在配置文件中配置对应的路径
# pycharm创建
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')]
]
# 命令行创建
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
]
"""
也就意味着你在用命令创建django项目的时候不单单需要创建templates文件夹还需要去配置文件中配置路径
'DIRS': [os.path.join(BASE_DIR, 'templates')]
"""
```
### django小白必会三板斧
```python
"""
HttpResponse
返回字符串类型的数据
render
返回html文件的
redirect
重定向
return redirect('https://www.mzitu.com/')
return redirect('/home/')
"""
```
### 作业
```python
"""
1.整理web框架推导思路
2.安装django并正常启动访问,测试三板斧
3.整理今日日考题,django内容
选做题
1.结合前端,django,MySQL,pymysql模块实现数据库数据动态展示到前端
2.尝试着摸索django模版语法
"""
```
### 静态文件配置
```python
# 登陆功能
"""
我们将html文件默认都放在templates文件夹下
我们将网站所使用的静态文件默认都放在static文件夹下
静态文件
前端已经写好了的 能够直接调用使用的文件
网站写好的js文件
网站写好的css文件
网站用到的图片文件
第三方前端框架
...
拿来就可以直接使用的
"""
# django默认是不会自动帮你创建static文件夹 需要你自己手动创建
一般情况下我们在static文件夹内还会做进一步的划分处理
-static
--js
--css
--img
其他第三方文件
"""
在浏览器中输入url能够看到对应的资源
是因为后端提前开设了该资源的借口
如果访问不到资源 说明后端没有开设该资源的借口
http://127.0.0.1:8000/static/bootstrap-3.3.7-dist/css/bootstrap.min.css
"""
# 静态文件配置
"""
****************************************************************
当你在写django项目的时候 可能会出现后端代码修改了但是前端页面没有变化的情况
1.你在同一个端口开了好几个django项目
一直在跑的其实是第一个django项目
2.浏览器缓存的问题
settings
network
disable cache 勾选上
*****************************************************************
"""
STATIC_URL = '/ooo/' # 类似于访问静态文件的令牌
"""如果你想要访问静态文件 你就必须以static开头"""
"""
/static/bootstrap-3.3.7-dist/js/bootstrap.min.js
/static/令牌
取列表里面从上往下依次查找
bootstrap-3.3.7-dist/js/bootstrap.min.js
都没有才会报错
"""
# 静态文件配置
STATICFILES_DIRS = [
os.path.join(BASE_DIR,'static'),
os.path.join(BASE_DIR,'static1'),
os.path.join(BASE_DIR,'static2'),
]
# 静态文件动态解析
{% load static %}
<link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
<script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
# form表单默认是get请求数据
http://127.0.0.1:8000/login/?username=jason&password=123
"""
form表单action参数
1.不写 默认朝当前所在的url提交数据
2.全写 指名道姓
3.只写后缀 /login/
"""
# 在前期我们使用django提交post请求的时候 需要取配置文件中注释掉一行代码
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
```
### request对象方法初识
```python
request.method # 返回请求方式 并且是全大写的字符串形式 <class 'str'>
request.POST # 获取用户post请求提交的普通数据不包含文件
request.POST.get() # 只获取列表最后一个元素
request.POST.getlist() # 直接将列表取出
request.GET # 获取用户提交的get请求数据
request.GET.get() # 只获取列表最后一个元素
request.GET.getlist() # 直接将列表取出
"""
get请求携带的数据是有大小限制的 大概好像只有4KB左右
而post请求则没有限制
"""
def login(request):
# 返回一个登陆界面
"""
get请求和post请求应该有不同的处理机制
:param request: 请求相关的数据对象 里面有很多简易的方法
:return:
"""
# print(type(request.method)) # 返回请求方式 并且是全大写的字符串形式 <class 'str'>
# if request.method == 'GET':
# print('来了 老弟')
# return render(request,'login.html')
# elif request.method == 'POST':
# return HttpResponse("收到了 宝贝")
if request.method == 'POST':
return HttpResponse("收到了 宝贝")
return render(request, 'login.html')
```
### pycharm链接数据库(MySQL)
```python
"""
三个位置查找数据库相关
右侧上方database
左下方database
配置里面的plugins插件搜索安装
再没有卸载pycharm重新装
pycharm可以充当很多款数据库软件的客户端
参考截图和视频操作即可
需要提前创建好库
"""
```
### django链接数据库(MySQL)
```python
# 默认用的是sqkite3
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# django链接MySQL
1.第一步配置文件中配置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'day60',
'USER':'root',
'PASSWORD':'admin123',
'HOST':'127.0.0.1',
'PORT':3306,
'CHARSET':'utf8'
}
}
2.代码声明
django默认用的是mysqldb模块链接MySQL
但是该模块的兼容性不好 需要手动改为用pymysql链接
你需要告诉django不要用默认的mysqldb还是用pymysql
# 在项目名下的init或者任意的应用名下的init文件中书写以下代码都可以
import pymysql
pymysql.install_as_MySQLdb()
```
### Django ORM
```python
"""
ORM. 对象关系映射
作用:能够让一个不用sql语句的小白也能够通过python 面向对象的代码简单快捷的操作数据库
不足之处:封装程度太高 有时候sql语句的效率偏低 需要你自己写SQL语句
类 表
对象 记录
对象属性 记录某个字段对应的值
应用下面的models.py文件
"""
# 1 先去models.py中书写一个类
class User(models.Model):
# id int primary_key auto_increment
id = models.AutoField(primary_key=True)
# username varchar(32)
username = models.CharField(max_length=32)
# password int
password = models.IntegerField()
*************************# 2 数据库迁移命令*************************
python3 manage.py makemigrations 将操作记录记录到小本本上(migrations文件夹)
python3 manage.py migrate 将操作真正的同步到数据库中
# 只要你修改了models.py中跟数据库相关的代码 就必须重新执行上述的两条命令
******************************************************************
class User(models.Model):
# id int primary_key auto_increment
id = models.AutoField(primary_key=True,verbose_name='主键')
# username varchar(32)
username = models.CharField(max_length=32,verbose_name='用户名')
"""
CharField必须要指定max_length参数 不指定会直接报错
verbose_name该参数是所有字段都有的 就是用来对字段的解释
"""
# password int
password = models.IntegerField(verbose_name='密码')
class Author(models.Model):
# 由于一张表中必须要有一个主键字段 并且一般情况下都叫id字段
# 所以orm当你不定义主键字段的时候 orm会自动帮你创建一个名为id主键字段
# 也就意味着 后续我们在创建模型表的时候如果主键字段名没有额外的叫法 那么主键字段可以省略不写
# username varchar(32)
username = models.CharField(max_length=32)
# password int
password = models.IntegerField()
```
### 字段的增删改查
```python
# 字段的增加
1.可以在终端内直接给出默认值
2.该字段可以为空
info = models.CharField(max_length=32,verbose_name='个人简介',null=True)
3.直接给字段设置默认值
hobby = models.CharField(max_length=32,verbose_name='兴趣爱好',default='study')
# 字段的修改
直接修改代码然后执行数据库迁移的两条命令即可!
# 字段的删
直接注释对应的字段然后执行数据库迁移的两条命令即可!
执行完毕之后字段对应的数据也都没有了
"""
在操作models.py的时候一定要细心
千万不要注释一些字段
执行迁移命令之前最好先检查一下自己写的代码
"""
# 个人建议:当你离开你的计算机之后一定要锁屏
```
### 数据的增删改查
```python
# 今天只会介绍一点点 后面会详细的介绍
# 查
res = models.User.objects.filter(username=username)
"""
返回值你先看成是列表套数据对象的格式
它也支持索引取值 切片操作 但是不支持负数索引
它也不推荐你使用索引的方式取值
user_obj = models.User.objects.filter(username=username).first()
"""
filter括号内可以携带多个参数 参数与参数之间默认是and关系
你可以把filter联想成where记忆
# 增
from app01 import models
res = models.User.objects.create(username=username,password=password)
# 返回值就是当前被创建的对象本身
# 第二种增加
user_obj = models.User(username=username,password=password)
user_obj.save() # 保存数据
```
### 今日作业
```python
今日作业
必做题
1.整理今日考题,今日笔记,今日内容
2.熟练掌握django静态文件配置、数据库链接配置,注意事项
3.熟练掌握django orm大体功能
4.利用django在前端页面上完成注册登陆功能
选做题
1.编写图书管管理系统(周作业)
提示:主要表(图书表,出版社表,作者表),尝试利用orm建立表关系
不会写没关系,后面会讲
```
### 数据的查,改,删
```python
# 先讲数据库中的数据全部展示到前端 然后给每一个数据两个按钮 一个编辑一个删除
# 查看
def userlist(request):
# 查询出用户表里面所有的数据
# 方式1
# data = models.User.objects.filter()
# print(data)
# 方式2
user_queryset = models.User.objects.all()
# return render(request,'userlist.html',{'user_queryset':user_queryset})
return render(request,'userlist.html',locals())
# 编辑功能
# 点击编辑按钮朝后端发送编辑数据的请求
"""
如何告诉后端用户想要编辑哪条数据?
将编辑按钮所在的那一行数据的主键值发送给后端
利用url问号后面携带参数的方式
{% for user_obj in user_queryset %}
<tr>
<td>{{ user_obj.id }}</td>
<td>{{ user_obj.username }}</td>
<td>{{ user_obj.password }}</td>
<td>
<a href="/edit_user/?user_id={{ user_obj.id }}" class="btn btn-primary btn-xs">编辑</a>
<a href="" class="btn btn-danger btn-xs">删除</a>
</td>
</tr>
{% endfor %}
"""
# 后端查询出用户想要编辑的数据对象 展示到前端页面供用户查看和编辑
def edit_user(request):
# 获取url问号后面的参数
edit_id = request.GET.get('user_id')
# 查询当前用户想要编辑的数据对象
edit_obj = models.User.objects.filter(id=edit_id).first()
if request.method == "POST":
username = request.POST.get('username')
password = request.POST.get('password')
# 去数据库中修改对应的数据内容
# 修改数据方式1
# models.User.objects.filter(id=edit_id).update(username=username,password=password)
"""
将filter查询出来的列表中所有的对象全部更新 批量更新操作
只修改被修改的字段
"""
# 修改数据方式2
edit_obj.username = username
edit_obj.password= password
edit_obj.save()
"""
上述方法当字段特别多的时候效率会非常的低
从头到尾将数据的所有字段全部更新一边 无论该字段是否被修改
"""
# 跳转到数据的展示页面
return redirect('/userlist/')
# 将数据对象展示到页面上
return render(request,'edit_user.html',locals())
# 删除功能
"""
跟编辑功能逻辑类似
def delete_user(request):
# 获取用户想要删除的数据id值
delete_id = request.GET.get('user_id')
# 直接去数据库中找到对应的数据删除即可
models.User.objects.filter(id=delete_id).delete()
"""
批量删除
"""
# 跳转到展示页面
return redirect('/userlist/')
"""
# 真正的删除功能应该需要二次确认 我们这里先不做后面会讲
# 删除数据内部其实并不是真正的删除 我们会给数据添加一个标识字段用来表示当前数据是否被删除了,如果数据被删了仅仅只是讲字段修改一个状态
username password is_delete
jason 123 0
egon 123 1
```
### django orm中如何创建表关系
```python
"""
表与表之间的关系
一对多
多对多
一对一
没有关系
判断表关系的方法:换位思考
"""
图书表
出版社表
作者表
作者详情表
"""
图书和出版社是一对多的关系 外键字段建在多的那一方 book
图书和作者是多对多的关系 需要创建第三张表来专门存储
作者与作者详情表是一对一
"""
from django.db import models
# Create your models here.
# 创建表关系 先将基表创建出来 然后再添加外键字段
class Book(models.Model):
title = models.CharField(max_length=32)
price = models.DecimalField(max_digits=8,decimal_places=2)
# 总共八位 小数点后面占两位
"""
图书和出版社是一对多 并且书是多的一方 所以外键字段放在书表里面
"""
publish = models.ForeignKey(to='Publish') # 默认就是与出版社表的主键字段做外键关联
"""
如果字段对应的是ForeignKey 那么会orm会自动在字段的后面加_id
如果你自作聪明的加了_id那么orm还是会在后面继续加_id
后面在定义ForeignKey的时候就不要自己加_id
"""
"""
图书和作者是多对多的关系 外键字段建在任意一方均可 但是推荐你建在查询频率较高的一方
"""
authors = models.ManyToManyField(to='Author')
"""
authors是一个虚拟字段 主要是用来告诉orm 书籍表和作者表是多对多关系
让orm自动帮你创建第三张关系表
"""
class Publish(models.Model):
name = models.CharField(max_length=32)
addr = models.CharField(max_length=32)
class Author(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
"""
作者与作者详情是一对一的关系 外键字段建在任意一方都可以 但是推荐你建在查询频率较高的表中
"""
author_detail = models.OneToOneField(to='AuthorDetail')
"""
OneToOneField也会自动给字段加_id后缀
所以你也不要自作聪明的自己加_id
"""
class AuthorDetail(models.Model):
phone = models.BigIntegerField() # 或者直接字符类型
addr = models.CharField(max_length=32)
"""
orm中如何定义三种关系
publish = models.ForeignKey(to='Publish') # 默认就是与出版社表的主键字段做外键关联
authors = models.ManyToManyField(to='Author')
author_detail = models.OneToOneField(to='AuthorDetail')
ForeignKey
OneToOneField
会自动在字段后面加_id后缀
"""
# 在django1.X版本中外键默认都是级联更新删除的
# 多对多的表关系可以有好几种创建方式 这里暂且先介绍一种
# 针对外键字段里面的其他参数 暂时不要考虑 如果感兴趣自己可以百度试试看
```
### django请求生命周期流程图(必会)
```python
# 每个人都要会画 这个图是你们后期复习django最好的一个梳理方式
# 扩展知识点
"""
缓存数据库
提前已经将你想要的数据准备好了 你来直接拿就可以
提高效率和响应时间
当你在修改你的数据的时候 你会发现数据并不是立刻修改完成的
而是需要经过一段时间才会修改
博客园
了解即可
"""
```
# 路由层
### 路由匹配
```python
# 路由匹配
url(r'test',views.test),
url(r'testadd',views.testadd)
"""
url方法第一个参数是正则表达式
只要第一个参数正则表达式能够匹配到内容 那么就会立刻停止往下匹配
直接执行对应的视图函数
你在输入url的时候会默认加斜杠
django内部帮你做到重定向
一次匹配不行
url后面加斜杠再来一次
"""
# 取消自动加斜杠
APPEND_SLASH = False/True # 默认是自动加斜杠的
urlpatterns = [
url(r'^admin/', admin.site.urls),
# 首页
url(r'^$',views.home),
# 路由匹配
url(r'^test/$',views.test),
url(r'^testadd/$',views.testadd),
# 尾页(了解)
url(r'',views.error),
]
```
### 无名分组
```python
"""
分组:就是给某一段正则表达式用小括号扩起来
"""
url(r'^test/(\d+)/',views.test)
def test(request,xx):
print(xx)
return HttpResponse('test')
# 无名分组就是将括号内正则表达式匹配到的内容当作位置参数传递给后面的视图函数
```
### 有名分组
```python
"""
可以给正则表达式起一个别名
"""
url(r'^testadd/(?P<year>\d+)',views.testadd)
def testadd(request,year):
print(year)
return HttpResponse('testadd')
# 有名分组就是将括号内正则表达式匹配到的内容当作关键字参数传递给后面的视图函数
```
### 无名有名是否可以混合使用
```python
"""
不能混用
但是同一个分组可以使用N多次
"""
# 单个的分组可以使用多次
url(r'^index/(\d+)/(\d+)/(\d+)/',views.index),
url(r'^index/(?P<year>\d+)/(?P<age>\d+)/(?P<month>\d+)/',views.index),
```
### 反向解析
```python
# 通过一些方法得到一个结果 该结果可以直接访问对应的url触发视图函数
# 先给路由与视图函数起一个别名
url(r'^func_kkk/',views.func,name='ooo')
# 反向解析
# 后端反向解析
from django.shortcuts import render,HttpResponse,redirect,reverse
reverse('ooo')
# 前端反向解析
<a href="{% url 'ooo' %}">111</a>
```
# 作业
```python
"""
必做题:
1.整理今日内容到博客
2.自己完成单表的增删改查功能(结合前端鼠标点点操作)
3.自己找几张表练习如何创建表与表之间的关系
选做题:
1.尝试着利用路由匹配和反向解析完成数据的编辑和删除功能
2.构思图书管理系统(后面会讲,自己先尝试着看看)
## 无名有名分组反向解析
```python
# 无名分组反向解析
url(r'^index/(\d+)/',views.index,name='xxx')
# 前端
{% url 'xxx' 123 %}
# 后端
reverse('xxx', args=(1,))
"""
这个数字写代码的时候应该放什么
数字一般情况下放的是数据的主键值 数据的编辑和删除
url(r'^edit/(\d+)/',views.edit,name='xxx')
def edit(request,edit_id):
reverse('xxx',args=(edit_id,))
{%for user_obj in user_queryset%}
<a href="{% url 'xxx' user_obj.id %}">编辑</a>
{%endfor%}
今天每个人都必须完成的作业(*******)
利用无名有名 反向解析 完成数据的增删改查
"""
# 有名分组反向解析
url(r'^func/(?P<year>\d+)/',views.func,name='ooo')
# 前端
<a href="{% url 'ooo' year=123 %}">111</a> 了解
<a href="{% url 'ooo' 123 %}">222</a> 记忆
# 后端
# 有名分组反向解析 写法1 了解
print(reverse('ooo',kwargs={'year':123}))
# 简便的写法 减少你的脑容量消耗 记跟无名一样的操作即可
print(reverse('ooo',args=(111,)))
```
### 路由分发
```python
"""
django的每一个应用都可以有自己的templates文件夹 urls.py static文件夹
正是基于上述的特点 django能够非常好的做到分组开发(每个人只写自己的app)
作为组长 只需要将手下书写的app全部拷贝到一个新的django项目中 然后在配置文件里面注册所有的app再利用路由分发的特点将所有的app整合起来
当一个django项目中的url特别多的时候 总路由urls.py代码非常冗余不好维护
这个时候也可以利用路由分发来减轻总路由的压力
利用路由分发之后 总路由不再干路由与视图函数的直接对应关系
而是做一个分发处理
识别当前url是属于哪个应用下的 直接分发给对应的应用去处理
"""
# 总路由
from app01 import urls as app01_urls
from app02 import urls as app02_urls
urlpatterns = [
url(r'^admin/', admin.site.urls),
# 1.路由分发
url(r'^app01/',include(app01_urls)), # 只要url前缀是app01开头 全部交给app01处理
url(r'^app02/',include(app02_urls)) # 只要url前缀是app02开头 全部交给app02处理
# 2.终极写法 推荐使用
url(r'^app01/',include('app01.urls')),
url(r'^app02/',include('app02.urls'))
# 注意事项:总路由里面的url千万不能加$结尾
]
# 子路由
# app01 urls.py
from django.conf.urls import url
from app01 import views
urlpatterns = [
url(r'^reg/',views.reg)
]
# app02 urls.py
from django.conf.urls import url
from app02 import views
urlpatterns = [
url(r'^reg/',views.reg)
]
```
### 名称空间(了解)
```python
# 当多个应用出现了相同的别名 我们研究反向解析会不会自动识别应用前缀
"""
正常情况下的反向解析是没有办法自动识别前缀的
"""
# 名称空间
# 总路由
url(r'^app01/',include('app01.urls',namespace='app01')),
url(r'^app02/',include('app02.urls',namespace='app02'))
# 解析的时候
# app01
urlpatterns = [
url(r'^reg/',views.reg,name='reg')
]
# app02
urlpatterns = [
url(r'^reg/',views.reg,name='reg')
]
reverse('app01:reg')
reverse('app02:reg')
{% url 'app01:reg' %}
{% url 'app02:reg' %}
# 其实只要保证名字不冲突 就没有必要使用名称空间
"""
一般情况下 有多个app的时候我们在起别名的时候会加上app的前缀
这样的话就能够确保多个app之间名字不冲突的问题
"""
urlpatterns = [
url(r'^reg/',views.reg,name='app01_reg')
]
urlpatterns = [
url(r'^reg/',views.reg,name='app02_reg')
]
```
### 伪静态(了解)
```python
"""
静态网页
数据是写死的 万年不变
伪静态
将一个动态网页伪装成静态网页
为什么要伪装呢?
https://www.cnblogs.com/Dominic-Ji/p/9234099.html
伪装的目的在于增大本网站的seo查询力度
并且增加搜索引擎收藏本网上的概率
搜索引擎本质上就是一个巨大的爬虫程序
总结:
无论你怎么优化 怎么处理
始终还是干不过RMB玩家
"""
urlpatterns = [
url(r'^reg.html',views.reg,name='app02_reg')
]
```
### 虚拟环境(了解)
```python
"""
在正常开发中 我们会给每一个项目配备一个该项目独有的解释器环境
该环境内只有该项目用到的模块 用不到一概不装
linux:缺什么才装什么
虚拟环境
你每创建一个虚拟环境就类似于重新下载了一个纯净的python解释器
但是虚拟环境不要创建太多,是需要消耗硬盘空间的
扩展:
每一个项目都需要用到很多模块 并且每个模块版本可能还不一样
那我该如何安装呢? 一个个看一个个装???
开发当中我们会给每一个项目配备一个requirements.txt文件
里面书写了该项目所有的模块即版本
你只需要直接输入一条命令即可一键安装所有模块即版本
"""
```
### django版本区别
```python
"""
1.django1.X路由层使用的是url方法
而在django2.Xhe3.X版本中路由层使用的是path方法
url()第一个参数支持正则
path()第一个参数是不支持正则的 写什么就匹配什么
如果你习惯使用path那么也给你提供了另外一个方法
from django.urls import path, re_path
from django.conf.urls import url
re_path(r'^index/',index),
url(r'^login/',login)
2.X和3.X里面的re_path就等价于1.X里面的url
2.虽然path不支持正则 但是它的内部支持五种转换器
path('index/<int:id>/',index)
# 将第二个路由里面的内容先转成整型然后以关键字的形式传递给后面的视图函数
def index(request,id):
print(id,type(id))
return HttpResponse('index')
str,匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式
int,匹配正整数,包含0。
slug,匹配字母、数字以及横杠、下划线组成的字符串。
uuid,匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00。
path,匹配任何非空字符串,包含了路径分隔符(/)(不能用?)
3.除了有默认的五个转换器之外 还支持自定义转换器(了解)
class MonthConverter:
regex='\d{2}' # 属性名必须为regex
def to_python(self, value):
return int(value)
def to_url(self, value):
return value # 匹配的regex是两个数字,返回的结果也必须是两个数字
from django.urls import path,register_converter
from app01.path_converts import MonthConverter
# 先注册转换器
register_converter(MonthConverter,'mon')
from app01 import views
urlpatterns = [
path('articles/<int:year>/<mon:month>/<slug:other>/', views.article_detail, name='aaa'),
]
4.模型层里面1.X外键默认都是级联更新删除的
但是到了2.X和3.X中需要你自己手动配置参数
models.ForeignKey(to='Publish')
models.ForeignKey(to='Publish',on_delete=models.CASCADE...)
"""
```
# 视图层
### 三板斧
```python
"""
HttpResponse
返回字符串类型
render
返回html页面 并且在返回给浏览器之前还可以给html文件传值
redirect
重定向
"""
# 视图函数必须要返回一个HttpResponse对象 正确 研究三者的源码即可得处结论
The view app01.views.index didn't return an HttpResponse object. It returned None instead.
# render简单内部原理
from django.template import Template,Context
res = Template('<h1>{{ user }}</h1>')
con = Context({'user':{'username':'jason','password':123}})
ret = res.render(con)
print(ret)
return HttpResponse(ret)
```
### JsonResponse对象
```python
"""
json格式的数据有什么用?
前后端数据交互需要使用到json作为过渡 实现跨语言传输数据
前端序列化
JSON.stringify() json.dumps()
JSON.parse() json.loads()
"""
import json
from django.http import JsonResponse
def ab_json(request):
user_dict = {'username':'jason好帅哦,我好喜欢!','password':'123','hobby':'girl'}
l = [111,222,333,444,555]
# 先转成json格式字符串
# json_str = json.dumps(user_dict,ensure_ascii=False)
# 将该字符串返回
# return HttpResponse(json_str)
# 读源码掌握用法
# return JsonResponse(user_dict,json_dumps_params={'ensure_ascii':False})
# In order to allow non-dict objects to be serialized set the safe parameter to False.
# return JsonResponse(l,safe=False)
# 默认只能序列化字典 序列化其他需要加safe参数
```
### form表单上传文件及后端如何操作
```python
"""
form表单上传文件类型的数据
1.method必须指定成post
2.enctype必须换成formdata
"""
def ab_file(request):
if request.method == 'POST':
# print(request.POST) # 只能获取普通的简直对数据 文件不行
print(request.FILES) # 获取文件数据
# <MultiValueDict: {'file': [<InMemoryUploadedFile: u=1288812541,1979816195&fm=26&gp=0.jpg (image/jpeg)>]}>
file_obj = request.FILES.get('file') # 文件对象
print(file_obj.name)
with open(file_obj.name,'wb') as f:
for line in file_obj.chunks(): # 推荐加上chunks方法 其实跟不加是一样的都是一行行的读取
f.write(line)
return render(request,'form.html')
```
### request对象方法
```python
"""
request.method
request.POST
request.GET
request.FILES
request.body # 原生的浏览器发过来的二进制数据 后面详细的讲
request.path
request.path_info
request.get_full_path() 能过获取完整的url及问号后面的参数
"""
print(request.path) # /app01/ab_file/
print(request.path_info) # /app01/ab_file/
print(request.get_full_path()) # /app01/ab_file/?username=jason
```
### FBV与CBV
```python
# 视图函数既可以是函数也可以是类
def index(request):
return HttpResponse('index')
# CBV
# CBV路由
url(r'^login/',views.MyLogin.as_view())
from django.views import View
class MyLogin(View):
def get(self,request):
return render(request,'form.html')
def post(self,request):
return HttpResponse('post方法')
"""
FBV和CBV各有千秋
CBV特点
能够直接根据请求方式的不同直接匹配到对应的方法执行
内部到底是怎么实现的?
CBV内部源码(******)
"""
```
# 作业
```python
1.整理今日内容到博客
2.利用无名有名 反向解析 完成数据的增删改查
选做:
研究FBV内部原理
CBV源码剖析
```python
# 你自己不要修改源码 除了bug很难找
# 突破口在urls.py
url(r'^login/',views.MyLogin.as_view())
# url(r'^login/',views.view) FBV一模一样
# CBV与FBV在路由匹配上本质是一样的 都是路由 对应 函数内存地址
"""
函数名/方法名 加括号执行优先级最高
猜测
as_view()
要么是被@staicmethod修饰的静态方法
要么是被@classmethod修饰的类方法 正确
@classonlymethod
def as_view(cls, **initkwargs):
pass
"""
@classonlymethod
def as_view(cls, **initkwargs):
"""
cls就是我们自己写的类 MyCBV
Main entry point for a request-response process.
"""
def view(request, *args, **kwargs):
self = cls(**initkwargs) # cls是我们自己写的类
# self = MyLogin(**initkwargs) 产生一个我们自己写的类的对象
return self.dispatch(request, *args, **kwargs)
"""
以后你们会经常需要看源码 但是在看python源码的时候 一定要时刻提醒自己面向对象属性方法查找顺序
先从对象自己找
再去产生对象的类里面找
之后再去父类找
...
总结:看源码只要看到了self点一个东西 一定要问你自己当前这个self到底是谁
"""
return view
# CBV的精髓
def dispatch(self, request, *args, **kwargs):
# 获取当前请求的小写格式 然后比对当前请求方式是否合法
# get请求为例
# post请求
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
"""
反射:通过字符串来操作对象的属性或者方法
handler = getattr(自己写的类产生的对象,'get',当找不到get属性或者方法的时候就会用第三个参数)
handler = 我们自己写的类里面的get方法
"""
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
"""
自动调用get方法
"""
# 要求掌握到不看源码也能够描述出CBV的内部执行流程(******)
```
### 模版语法传值
{{}}:变量相关
{%%}:逻辑相关
```python
def index(request):
# 模版语法可以传递的后端python数据类型
n = 123
f = 11.11
s = '我也想奔现'
b = True
l = ['小红','姗姗','花花','茹茹']
t = (111,222,333,444)
d = {'username':'jason','age':18,'info':'这个人有点意思'}
se = {'晶晶','洋洋','嘤嘤'}
def func():
print('我被执行了')
return '你的另一半在等你'
class MyClass(object):
def get_self(self):
return 'self'
@staticmethod
def get_func():
return 'func'
@classmethod
def get_class(cls):
return 'cls'
# 对象被展示到html页面上 就类似于执行了打印操作也会触发__str__方法
def __str__(self):
return '到底会不会?'
obj = MyClass()
# return render(request,'index.html',{}) # 一个个传
return render(request,'index.html',locals())
<p>{{ n }}</p>
<p>{{ f }}</p>
<p>{{ s }}</p>
<p>{{ b }}</p>
<p>{{ l }}</p>
<p>{{ d }}</p>
<p>{{ t }}</p>
<p>{{ se }}</p>
<p>传递函数名会自动加括号调用 但是模版语法不支持给函数传额外的参数:{{ func }}</p>
<p>传类名的时候也会自动加括号调用(实例化){{ MyClass }}</p>
<p>内部能够自动判断出当前的变量名是否可以加括号调用 如果可以就会自动执行 针对的是函数名和类名</p>
<p>{{ obj }}</p>
<p>{{ obj.get_self }}</p>
<p>{{ obj.get_func }}</p>
<p>{{ obj.get_class }}</p>
# django模版语法的取值 是固定的格式 只能采用“句点符” .
<p>{{ d.username }}</p>
<p>{{ l.0 }}</p>
<p>{{ d.hobby.3.info }}</p>
# 即可以点键也可以点索引 还可以两者混用
```
### 过滤器(过滤器只能最多有两个参数)
```python
# 过滤器就类似于是模版语法内置的 内置方法
# django内置有60多个过滤器 我们不需要学这么多 了解10个左右就差不多了 后面碰到了再去记忆
# 基本语法
{{数据|过滤器:参数}}
# 转义
# 前端
|safe
# 后端
from django.utils.safestring import mark_safe
res = mark_safe('<h1>新新</h1>')
"""
以后你在全栈项目的时候 前端代码不一定非要在前端页面书写
也可以现在先在后端写好 然后传递给前端页面
"""
<h1>过滤器</h1>
<p>统计长度:{{ s|length }}</p>
<p>默认值(第一个参数布尔值是True就展示第一个参数的值否在展示冒号后面的值):{{ b|default:'啥也不是' }}</p>
<p>文件大小:{{ file_size|filesizeformat }}</p>
<p>日期格式化:{{ current_time|date:'Y-m-d H:i:s' }}</p>
<p>切片操作(支持步长):{{ l|slice:'0:4:2' }}</p>
<p>切取字符(包含三个点):{{ info|truncatechars:9 }}</p>
<p>切取单词(不包含三个点 按照空格切):{{ egl|truncatewords:9 }}</p>
<p>切取单词(不包含三个点 按照空格切):{{ info|truncatewords:9 }}</p>
<p>移除特定的字符:{{ msg|cut:' ' }}</p>
<p>拼接操作:{{ l|join:'$' }}</p>
<p>拼接操作(加法):{{ n|add:10 }}</p>
<p>拼接操作(加法):{{ s|add:msg }}</p>
<p>转义:{{ hhh|safe }}</p>
<p>转义:{{ sss|safe }}</p>
<p>转义:{{ res }}</p>
```
### 标签
```python
# for循环
{% for foo in l %}
<p>{{ forloop }}</p>
<p>{{ foo }}</p> 一个个元素
{% endfor %}
{'parentloop': {}, 'counter0': 0, 'counter': 1, 'revcounter': 6, 'revcounter0': 5, 'first': True, 'last': False}
# if判断
{% if b %}
<p>baby</p>
{% elif s%}
<p>都来把</p>
{% else %}
<p>老baby</p>
{% endif %}
# for与if混合使用
{% for foo in lll %}
{% if forloop.first %}
<p>这是我的第一次</p>
{% elif forloop.last %}
<p>这是最后一次啊</p>
{% else %}
<p>{{ foo }}</p>
{% endif %}
{% empty %}
<p>for循环的可迭代对象内部没有元素 根本没法循环</p>
{% endfor %}
# 处理字典其他方法
{% for foo in d.keys %}
<p>{{ foo }}</p>
{% endfor %}
{% for foo in d.values %}
<p>{{ foo }}</p>
{% endfor %}
{% for foo in d.items %}
<p>{{ foo }}</p>
{% endfor %}
# with起别名
{% with d.hobby.3.info as nb %}
<p>{{ nb }}</p>
在with语法内就可以通过as后面的别名快速的使用到前面非常复杂获取数据的方式
<p>{{ d.hobby.3.info }}</p>
{% endwith %}
```
### 自定义过滤器、标签、inclusion_tag
```python
"""
先三步走
1.在应用下创建一个名字”必须“叫templatetags文件夹
2.在该文件夹内创建“任意”名称的py文件 eg:mytag.py
3.在该py文件内"必须"先书写下面两句话(单词一个都不能错)
from django import template
register = template.Library()
"""
# 自定义过滤器
@register.filter(name='baby')
def my_sum(v1, v2):
return v1 + v2
# 使用
{% load mytag %}
<p>{{ n|baby:666 }}</p>
# 自定义标签(参数可以有多个) 类似于自定义函数
@register.simple_tag(name='plus')
def index(a,b,c,d):
return '%s-%s-%s-%s'%(a,b,c,d)
# 使用
标签多个参数彼此之间空格隔开
<p>{% plus 'jason' 123 123 123 %}</p>
# 自定义inclusion_tag
"""
内部原理
先定义一个方法
在页面上调用该方法 并且可以传值
该方法会生成一些数据然后传递给一个html页面
之后将渲染好的结果放到调用的位置
"""
@register.inclusion_tag('left_menu.html')
def left(n):
data = ['第{}项'.format(i) for i in range(n)]
# 第一种
# return {'data':data} # 将data传递给left_menu.html
# 第二种
return locals() # 将data传递给left_menu.html
{% left 5 %}
# 总结:当html页面某一个地方的页面需要传参数才能够动态的渲染出来,并且在多个页面上都需要使用到该局部 那么就考虑将该局部页面做成inclusion_tag形式
(在讲bbs的时候会使用到)
```
### 模版的继承
```python
"""
你们有没有见过一些网站
这些网站页面整体都大差不差 只是某一些局部在做变化
"""
# 模版的继承 你自己先选好一个你要想继承的模版页面
{% extends 'home.html' %}
# 继承了之后子页面跟模版页面长的是一模一样的 你需要在模版页面上提前划定可以被修改的区域
{% block content %}
模版内容
{% endblock %}
# 子页面就可以声明想要修改哪块划定了的区域
{% block content %}
子页面内容
{% endblock %}
# 一般情况下模版页面上应该至少有三块可以被修改的区域
1.css区域
2.html区域
3.js区域
{% block css %}
{% endblock %}
{% block content %}
{% endblock %}
{% block js %}
{% endblock %}
# 每一个子页面就都可以有自己独有的css代码 html代码 js代码
"""
一般情况下 模版的页面上划定的区域越多 那么该模版的扩展性就越高
但是如果太多 那还不如自己直接写
"""
```
### 模版的导入
```python
"""
将页面的某一个局部当成模块的形式
哪个地方需要就可以直接导入使用即可
"""
{% include 'wasai.html' %}
```
# 作业
```python
"""
今日作业
必做题
1.整理今日内容,用自己的话术和思路整理到个人博客中
2.阅读CBV源码,截图加注释到手机一份,要求做到脱稿说出流程
3.利用模版的继承及CBV完成网站首页 登陆 注册页面搭建 实现三者相互交互
选做题
1.尝试着做一做图书管理系统(将所学的知识点全部用进去)
无名有名+反向解析+模版的继承
"""
django框架查询
django单表操作
```python
# django自带的sqlite3数据库对日期格式不是很敏感 处理的时候容易出错
# 增
# res = models.User.objects.create(name='jason',age=18,register_time='2002-1-21')
# print(res)
# import datetime
# ctime = datetime.datetime.now()
# user_obj = models.User(name='egon',age=84,register_time=ctime)
# user_obj.save()
# 删
# res = models.User.objects.filter(pk=2).delete()
# print(res)
"""
pk会自动查找到当前表的主键字段 指代的就是当前表的主键字段
用了pk之后 你就不需要指代当前表的主键字段到底叫什么了
uid
pid
sid
...
"""
# user_obj = models.User.objects.filter(pk=1).first()
# user_obj.delete()
# 修改
# models.User.objects.filter(pk=4).update(name='egonDSB')
# user_obj = models.User.objects.get(pk=4)
# user_obj = models.User.objects.filter(pk=6)
"""
get方法返回的直接就是当前数据对象
但是该方法不推荐使用
一旦数据不存在该方法会直接报错
而filter则不会
所以我们还是用filter
"""
# user_obj.name = 'egonPPP'
# user_obj.save()
```
### 必知必会13条
```python
# 必知必会13条
# 1.all() 查询所有数据
# 2.filter() 带有过滤条件的查询
# 3.get() 直接拿数据对象 但是条件不存在直接报错
# 4.first() 拿queryset里面第一个元素
# res = models.User.objects.all().first()
# print(res)
# 5.last()
# res = models.User.objects.all().last()
# print(res)
# 6.values() 可以指定获取的数据字段 select name,age from ... 列表套字典
# res = models.User.objects.values('name','age') # <QuerySet [{'name': 'jason', 'age': 18}, {'name': 'egonPPP', 'age': 84}]>
# print(res)
# 7.values_list() 列表套元祖
# res = models.User.objects.values_list('name','age') # <QuerySet [('jason', 18), ('egonPPP', 84)]>
# print(res)
# """
# # 查看内部封装的sql语句
# 上述查看sql语句的方式 只能用于queryset对象
# 只有queryset对象才能够点击query查看内部的sql语句
#
# """
# 8.distinct() 去重
# res = models.User.objects.values('name','age').distinct()
# print(res)
"""
去重一定要是一模一样的数据
如果带有主键那么肯定不一样 你在往后的查询中一定不要忽略主键
"""
# 9.order_by()
# res = models.User.objects.order_by('age') # 默认升序
# res = models.User.objects.order_by('-age') # 降序
#
# print(res)
# 10.reverse() 反转的前提是 数据已经排过序了 order_by()
# res = models.User.objects.all()
# res1 = models.User.objects.order_by('age').reverse()
# print(res,res1)
# 11.count() 统计当前数据的个数
# res = models.User.objects.count()
# print(res)
# 12.exclude() 排除在外
# res = models.User.objects.exclude(name='jason')
# print(res)
# 13.exists() 基本用不到因为数据本身就自带布尔值 返回的是布尔值
# res = models.User.objects.filter(pk=10).exists()
# print(res)
```
### 测试脚本
```python
"""
当你只是想测试django中的某一个py文件内容 那么你可以不用书写前后端交互的形式
而是直接写一个测试脚本即可
脚本代码无论是写在应用下的tests.py还是自己单独开设py文件都可以
"""
# 测试环境的准备 去manage.py中拷贝前四行代码 然后自己写两行
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day64.settings")
import django
django.setup()
# 在这个代码块的下面就可以测试django里面的单个py文件了
```
### 查看内部sql语句的方式
```python
# 方式1
res = models.User.objects.values_list('name','age') # <QuerySet [('jason', 18), ('egonPPP', 84)]>
print(res.query)
queryset对象才能够点击query查看内部的sql语句
# 方式2:所有的sql语句都能查看
# 去配置文件中配置一下即可
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console':{
'level':'DEBUG',
'class':'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'propagate': True,
'level':'DEBUG',
},
}
}
```
### 神奇的双下划线查询
```python
# 神奇的双下划线查询
# 1 年龄大于35岁的数据
# res = models.User.objects.filter(age__gt=35)
# print(res)
# 2 年龄小于35岁的数据
# res = models.User.objects.filter(age__lt=35)
# print(res)
# 大于等于 小于等于
# res = models.User.objects.filter(age__gte=32)
# print(res)
# res = models.User.objects.filter(age__lte=32)
# print(res)
# 年龄是18 或者 32 或者40
# res = models.User.objects.filter(age__in=[18,32,40])
# print(res)
# 年龄在18到40岁之间的 首尾都要
# res = models.User.objects.filter(age__range=[18,40])
# print(res)
# 查询出名字里面含有s的数据 模糊查询
# res = models.User.objects.filter(name__contains='s')
# print(res)
#
# 是否区分大小写 查询出名字里面含有p的数据 区分大小写
# res = models.User.objects.filter(name__contains='p')
# print(res)
# 忽略大小写
# res = models.User.objects.filter(name__icontains='p')
# print(res)
# res = models.User.objects.filter(name__startswith='j')
# res1 = models.User.objects.filter(name__endswith='j')
#
# print(res,res1)
# 查询出注册时间是 2020 1月
# res = models.User.objects.filter(register_time__month='1')
# res = models.User.objects.filter(register_time__year='2020')
```
### 一对多外键增删改查
```python
# 一对多外键增删改查
# 增
# 1 直接写实际字段 id
# models.Book.objects.create(title='论语',price=899.23,publish_id=1)
# models.Book.objects.create(title='聊斋',price=444.23,publish_id=2)
# models.Book.objects.create(title='老子',price=333.66,publish_id=1)
# 2 虚拟字段 对象
# publish_obj = models.Publish.objects.filter(pk=2).first()
# models.Book.objects.create(title='红楼梦',price=666.23,publish=publish_obj)
# 删
# models.Publish.objects.filter(pk=1).delete() # 级联删除
# 修改
# models.Book.objects.filter(pk=1).update(publish_id=2)
# publish_obj = models.Publish.objects.filter(pk=1).first()
# models.Book.objects.filter(pk=1).update(publish=publish_obj)
```
### 多对多外键增删改查
```python
# 如何给书籍添加作者?
book_obj = models.Book.objects.filter(pk=1).first()
# print(book_obj.authors) # 就类似于你已经到了第三张关系表了
# book_obj.authors.add(1) # 书籍id为1的书籍绑定一个主键为1 的作者
# book_obj.authors.add(2,3)
# author_obj = models.Author.objects.filter(pk=1).first()
# author_obj1 = models.Author.objects.filter(pk=2).first()
# author_obj2 = models.Author.objects.filter(pk=3).first()
# book_obj.authors.add(author_obj)
# book_obj.authors.add(author_obj1,author_obj2)
"""
add给第三张关系表添加数据
括号内既可以传数字也可以传对象 并且都支持多个
"""
# 删
# book_obj.authors.remove(2)
# book_obj.authors.remove(1,3)
# author_obj = models.Author.objects.filter(pk=2).first()
# author_obj1 = models.Author.objects.filter(pk=3).first()
# book_obj.authors.remove(author_obj,author_obj1)
"""
remove
括号内既可以传数字也可以传对象 并且都支持多个
"""
# 修改
# book_obj.authors.set([1,2]) # 括号内必须给一个可迭代对象
# book_obj.authors.set([3]) # 括号内必须给一个可迭代对象
# author_obj = models.Author.objects.filter(pk=2).first()
# author_obj1 = models.Author.objects.filter(pk=3).first()
# book_obj.authors.set([author_obj,author_obj1]) # 括号内必须给一个可迭代对象
"""
set
括号内必须传一个可迭代对象,该对象内既可以数字也可以对象 并且都支持多个
"""
# 清空
# 在第三张关系表中清空某个书籍与作者的绑定关系
book_obj.authors.clear()
"""
clear
括号内不要加任何参数
"""
```
### 正反向的概念
```python
# 正向
# 反向
外键字段在我手上那么,我查你就是正向
外键字段如果不在手上,我查你就是反向
book >>>外键字段在书那儿(正向)>>> publish
publish >>>外键字段在书那儿(反向)>>>book
一对一和多对多正反向的判断也是如此
"""
正向查询按字段
反向查询按表名小写
_set
...
"""
```
# 多表查询
### 子查询(基于对象的跨表查询)
```python
# 1.查询书籍主键为1的出版社
# book_obj = models.Book.objects.filter(pk=1).first()
# # 书查出版社 正向
# res = book_obj.publish
# print(res)
# print(res.name)
# print(res.addr)
# 2.查询书籍主键为2的作者
# book_obj = models.Book.objects.filter(pk=2).first()
# # 书查作者 正向
# # res = book_obj.authors # app01.Author.None
# res = book_obj.authors.all() # <QuerySet [<Author: Author object>, <Author: Author object>]>
#
# print(res)
# 3.查询作者jason的电话号码
# author_obj = models.Author.objects.filter(name='jason').first()
# res = author_obj.author_detail
# print(res)
# print(res.phone)
# print(res.addr)
"""
在书写orm语句的时候跟写sql语句一样的
不要企图一次性将orm语句写完 如果比较复杂 就写一点看一点
正向什么时候需要加.all()
当你的结果可能有多个的时候就需要加.all()
如果是一个则直接拿到数据对象
book_obj.publish
book_obj.authors.all()
author_obj.author_detail
"""
# 4.查询出版社是东方出版社出版的书
# publish_obj = models.Publish.objects.filter(name='东方出版社').first()
# 出版社查书 反向
# res = publish_obj.book_set # app01.Book.None
# res = publish_obj.book_set.all()
# print(res)
# 5.查询作者是jason写过的书
# author_obj = models.Author.objects.filter(name='jason').first()
# 作者查书 反向
# res = author_obj.book_set # app01.Book.None
# res = author_obj.book_set.all()
# print(res)
# 6.查询手机号是110的作者姓名
# author_detail_obj = models.AuthorDetail.objects.filter(phone=110).first()
# res = author_detail_obj.author
# print(res.name)
"""
基于对象
反向查询的时候
当你的查询结果可以有多个的时候 就必须加_set.all()
当你的结果只有一个的时候 不需要加_set.all()
自己总结出 自己方便记忆的即可 每个人都可以不一样
"""
```
### 联表查询(基于双下划线的跨表查询)
```python
# 基于双下划线的跨表查询
# 1.查询jason的手机号和作者姓名
# res = models.Author.objects.filter(name='jason').values('author_detail__phone','name')
# print(res)
# 反向
# res = models.AuthorDetail.objects.filter(author__name='jason') # 拿作者姓名是jason的作者详情
# res = models.AuthorDetail.objects.filter(author__name='jason').values('phone','author__name')
# print(res)
# 2.查询书籍主键为1的出版社名称和书的名称
# res = models.Book.objects.filter(pk=1).values('title','publish__name')
# print(res)
# 反向
# res = models.Publish.objects.filter(book__id=1).values('name','book__title')
# print(res)
# 3.查询书籍主键为1的作者姓名
# res = models.Book.objects.filter(pk=1).values('authors__name')
# print(res)
# 反向
# res = models.Author.objects.filter(book__id=1).values('name')
# print(res)
# 查询书籍主键是1的作者的手机号
# book author authordetail
# res = models.Book.objects.filter(pk=1).values('authors__author_detail__phone')
# print(res)
"""
你只要掌握了正反向的概念
以及双下划线
那么你就可以无限制的跨表
"""
```
### 周末作业
```python
"""
今日作业
必做题
1.整理今日内容 用自己的话术整理到博客中(切勿直接复制粘贴)
独立完成以下任务
2.自己手动创建图书管理系统表及数据录入
3.独立完成单表查询N条方法,双下划线方法
4.将课上orm题目摘出来,自己完成orm语句书写,体会orm简便之处
选做题
1.图书管理系统 图书表的增删改查
(只需要完成图书表的就可以)
"""
### 聚合查询
```python
# 聚合查询 aggregate
"""
聚合查询通常情况下都是配合分组一起使用的
只要是跟数据库相关的模块
基本上都在django.db.models里面
如果上述没有那么应该在django.db里面
"""
from app01 import models
from django.db.models import Max,Min,Sum,Count,Avg
# 1 所有书的平均价格
# res = models.Book.objects.aggregate(Avg('price'))
# print(res)
# 2.上述方法一次性使用
res = models.Book.objects.aggregate(Max('price'),Min('price'),Sum('price'),Count('pk'),Avg('price'))
print(res)
```
### 分组查询
```python
# 分组查询 annotate
"""
MySQL分组查询都有哪些特点
分组之后默认只能获取到分组的依据 组内其他字段都无法直接获取了
严格模式
ONLY_FULL_GROUP_BY
"""
from django.db.models import Max, Min, Sum, Count, Avg
# 1.统计每一本书的作者个数
# res = models.Book.objects.annotate() # models后面点什么 就是按什么分组
# res = models.Book.objects.annotate(author_num=Count('authors')).values('title','author_num')
"""
author_num是我们自己定义的字段 用来存储统计出来的每本书对应的作者个数
"""
# res1 = models.Book.objects.annotate(author_num=Count('authors__id')).values('title','author_num')
# print(res,res1)
"""
代码没有补全 不要怕 正常写
补全给你是pycharm给你的 到后面在服务器上直接书写代码 什么补全都没有 颜色提示也没有
"""
# 2.统计每个出版社卖的最便宜的书的价格(作业:复习原生SQL语句 写出来)
# res = models.Publish.objects.annotate(min_price=Min('book__price')).values('name','min_price')
# print(res)
# 3.统计不止一个作者的图书
# 1.先按照图书分组 求每一本书对应的作者个数
# 2.过滤出不止一个作者的图书
# res = models.Book.objects.annotate(author_num=Count('authors')).filter(author_num__gt=1).values('title','author_num')
# """
# 只要你的orm语句得出的结果还是一个queryset对象
# 那么它就可以继续无限制的点queryset对象封装的方法
#
# """
# print(res)
# 4.查询每个作者出的书的总价格
# res = models.Author.objects.annotate(sum_price=Sum('book__price')).values('name','sum_price')
# print(res)
"""
如果我想按照指定的字段分组该如何处理呢?
models.Book.objects.values('price').annotate()
后续BBS作业会使用
你们的机器上如果出现分组查询报错的情况
你需要修改数据库严格模式
"""
```
### F与Q查询
```python
# F查询
# 1.查询卖出数大于库存数的书籍
# F查询
"""
能够帮助你直接获取到表中某个字段对应的数据
"""
from django.db.models import F
# res = models.Book.objects.filter(maichu__gt=F('kucun'))
# print(res)
# 2.将所有书籍的价格提升500块
# models.Book.objects.update(price=F('price') + 500)
# 3.将所有书的名称后面加上爆款两个字
"""
在操作字符类型的数据的时候 F不能够直接做到字符串的拼接
"""
from django.db.models.functions import Concat
from django.db.models import Value
models.Book.objects.update(title=Concat(F('title'), Value('爆款')))
# models.Book.objects.update(title=F('title') + '爆款') # 所有的名称会全部变成空白
```
```python
# Q查询
# 1.查询卖出数大于100或者价格小于600的书籍
# res = models.Book.objects.filter(maichu__gt=100,price__lt=600)
"""filter括号内多个参数是and关系"""
from django.db.models import Q
# res = models.Book.objects.filter(Q(maichu__gt=100),Q(price__lt=600)) # Q包裹逗号分割 还是and关系
# res = models.Book.objects.filter(Q(maichu__gt=100)|Q(price__lt=600)) # | or关系
# res = models.Book.objects.filter(~Q(maichu__gt=100)|Q(price__lt=600)) # ~ not关系
# print(res) # <QuerySet []>
# Q的高阶用法 能够将查询条件的左边也变成字符串的形式
q = Q()
q.connector = 'or'
q.children.append(('maichu__gt',100))
q.children.append(('price__lt',600))
res = models.Book.objects.filter(q) # 默认还是and关系
print(res)
```
### django中如何开启事务
```python
"""
事务
ACID
原子性
不可分割的最小单位
一致性
跟原子性是相辅相成
隔离性
事务之间互相不干扰
持久性
事务一旦确认永久生效
事务的回滚
rollback
事务的确认
commit
"""
# 目前你只需要掌握Django中如何简单的开启事务
# 事务
from django.db import transaction
try:
with transaction.atomic():
# sql1
# sql2
...
# 在with代码快内书写的所有orm操作都是属于同一个事务
except Exception as e:
print(e)
print('执行其他操作')
```
### orm中常用字段及参数
```python
AutoField
主键字段 primary_key=True
CharField varchar
verbose_name 字段的注释
max_length 长度
IntegerField int
BigIntegerField bigint
DecimalField
max_digits=8
decimal_places=2
EmailFiled varchar(254)
DateField date
DateTimeField datetime
auto_now:每次修改数据的时候都会自动更新当前时间
auto_now_add:只在创建数据的时候记录创建时间后续不会自动修改了
BooleanField(Field) - 布尔值类型
该字段传布尔值(False/True) 数据库里面存0/1
TextField(Field) - 文本类型
该字段可以用来存大段内容(文章、博客...) 没有字数限制
后面的bbs作业 文章字段用的就是TextField
FileField(Field) - 字符类型
upload_to = "/data"
给该字段传一个文件对象,会自动将文件保存到/data目录下然后将文件路径保存到数据库中
/data/a.txt
后面bbs作业也会涉及
# 更多字段
直接参考博客:https://www.cnblogs.com/Dominic-Ji/p/9203990.html
# django除了给你提供了很多字段类型之外 还支持你自定义字段
class MyCharField(models.Field):
def __init__(self,max_length,*args,**kwargs):
self.max_length = max_length
# 调用父类的init方法
super().__init__(max_length=max_length,*args,**kwargs) # 一定要是关键字的形式传入
def db_type(self, connection):
"""
返回真正的数据类型及各种约束条件
:param connection:
:return:
"""
return 'char(%s)'%self.max_length
# 自定义字段使用
myfield = MyCharField(max_length=16,null=True)
# 外键字段及参数
unique=True
ForeignKey(unique=True) === OneToOneField()
# 你在用前面字段创建一对一 orm会有一个提示信息 orm推荐你使用后者但是前者也能用
db_index
如果db_index=True 则代表着为此字段设置索引
(复习索引是什么)
to_field
设置要关联的表的字段 默认不写关联的就是另外一张的主键字段
on_delete
当删除关联表中的数据时,当前表与其关联的行的行为。
"""
django2.X及以上版本 需要你自己指定外键字段的级联更新级联删除
"""
```
### 数据库查询优化
```python
only与defer
select_related与prefetch_related
"""
orm语句的特点:
惰性查询
如果你仅仅只是书写了orm语句 在后面根本没有用到该语句所查询出来的参数
那么orm会自动识别 直接不执行
"""
# only与defer
# res = models.Book.objects.all()
# print(res) # 要用数据了才会走数据库
# 想要获取书籍表中所有数的名字
# res = models.Book.objects.values('title')
# for d in res:
# print(d.get('title'))
# 你给我实现获取到的是一个数据对象 然后点title就能够拿到书名 并且没有其他字段
# res = models.Book.objects.only('title')
# res = models.Book.objects.all()
# print(res) # <QuerySet [<Book: 三国演义爆款>, <Book: 红楼梦爆款>, <Book: 论语爆款>, <Book: 聊斋爆款>, <Book: 老子爆款>]>
# for i in res:
# print(i.title) # 点击only括号内的字段 不会走数据库
# print(i.price) # 点击only括号内没有的字段 会重新走数据库查询而all不需要走了
res = models.Book.objects.defer('title') # 对象除了没有title属性之外其他的都有
for i in res:
print(i.price)
"""
defer与only刚好相反
defer括号内放的字段不在查询出来的对象里面 查询该字段需要重新走数据
而如果查询的是非括号内的字段 则不需要走数据库了
"""
# select_related与prefetch_related
# select_related与prefetch_related 跟跨表操作有关
# res = models.Book.objects.all()
# for i in res:
# print(i.publish.name) # 每循环一次就要走一次数据库查询
# res = models.Book.objects.select_related('authors') # INNER JOIN
"""
select_related内部直接先将book与publish连起来 然后一次性将大表里面的所有数据
全部封装给查询出来的对象
这个时候对象无论是点击book表的数据还是publish的数据都无需再走数据库查询了
select_related括号内只能放外键字段 一对多 一对一
多对多也不行
"""
# for i in res:
# print(i.publish.name) # 每循环一次就要走一次数据库查询
res = models.Book.objects.prefetch_related('publish') # 子查询
"""
prefetch_related该方法内部其实就是子查询
将子查询查询出来的所有结果也给你封装到对象中
给你的感觉好像也是一次性搞定的
"""
for i in res:
print(i.publish.name)
```
### 图书管理系统
### 作业
```python
"""
1.整理今日内容到博客
2.熟练掌握数据库常用字段及参数以及应用场景
3.总结数据库查询优化的两组方法,尝试用自己的语言表述
4.完成图书管理系统
书籍表的增删改查(尽量将之前的知识点全部融入进去)
"""
```