Django
目录
Django
django简介
python三大主流框架
django
特点:大而全 自带的功能特别特别特别的多 类似于航空母舰
不足之处:有时候过于笨重
flask
特点:小而精 自带的功能特别特别特别的少 类似于游骑兵
第三方的模块特别特别特别的多,如果将flask第三方的模块加起来完全可以盖过django
并且也越来越像django
不足之处:比较依赖于第三方的开发者
tornado
特点:异步非阻塞 支持高并发 牛逼到甚至可以开发游戏服务器
不足之处:暂时你不会
注意事项
#保证django项目正常启动
1.计算机的名称不能有中文
2.一个pycharm窗口只开一个项目
3.项目里面所有的文件也尽量不要出现中文
4.python解释器尽量使用3.4~3.6之间的版本
(如果你的项目报错 你点击最后一个报错信息去源码中把逗号删掉)
#gjango版本问题
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基本操作
# 命令行操作
1.创建django项目
"""
你可以先切换到文件夹下,再创建
"""
django-admin startproject 项目名
项目结构:
first(项目名)
-manage.py
-first(项目名)
-wsgi.py
-urls.py
-settings.py
-__init__.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 应用名
应用名应该做到见名知意
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....
应用
***********************创建的应用一定要去配置文件中注册**********************
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并且自动注册
***********************************************************************
主要文件介绍
first(项目名)
db.sqlite3(django自带的sqlite3数据库,小型数据库 功能不是很多还有bug)
manage.py(django的入口文件)
app01(应用名)
migrations(数据库迁移记录)
__init__.py
admin.py(django后台管理)
apps.py(注册使用)
models.py(数据库相关,模型类(orm))
tests.py(测试文件)
views.py(视图函数(视图层))
first(项目名)
__init__.py(包自带文件)
settings.py(配置文件)
urls.py(路由文件)
wsgi.py(wsgiref模块,不考虑)
命令行与pycharm创建的区别
# 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')]
"""
三板斧
HttpResponse
返回字符串类型数据
render
返回html文件
redirect
重定向
return redirect('https://www.mzitu.com/')
return redirect('/home/')
静态文件配置
#登录功能
html文件默认放在templates下
静态文件默认放在static下
静态文件,拿来就可以直接使用的
网站写好的js文件
网站写好的css文件
网站用到的图片文件
第三方前端框架
等等...
# 需要自己手动创建static文件夹,按功能区分
-static
--js
--css
--img
···其他第三方文件
#浏览器中输入url能够看到对应的资源,是因为后端提前开设了该资源的借口,如问不到资源 说明后端没有开设该资源的借口
#静态文件介绍
****************************************************************
如果出现后端代码修改了, 但是前端页面没有变化的情况
1.你在同一个端口开了好几个django项目
一直在跑的其实是第一个django项目
2.浏览器缓存的问题
settings
network
disable cache 勾选上 #禁用缓存
*****************************************************************
STATIC_URL = '/ooo/' # 类似于访问静态文件的令牌
如:atic/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请求的时候 需要取配置文件中注释掉一行代码,禁用csrf跨站请求伪造
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对象初识
request.method # 返回请求方式 并且是全大写的字符串形式 <class 'str'>
request.POST # 获取用户post请求提交的普通数据不包含文件
request.POST.get() # 只获取列表最后一个元素
request.POST.getlist() # 直接将列表取出
request.GET # 获取用户提交的get请求数据
request.GET.get() # 只获取列表最后一个元素
request.GET.getlist() # 直接将列表取出
get请求携带的数据是有大小限制的 大概好像只有4KB左右
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链接数据库
#三个位置查找数据库相关
右侧上方database
左下方database
配置里面的plugins插件搜索安装
再没有卸载pycharm重新装
#pycharm可以充当很多款数据库软件的客户端
#参考截图和视频操作即可,需要提前创建好库
django链接MySql
# 默认用的是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
#ORM 对象关系映射
作用:不需要使用sql语句通过python面向对象操作数据库
不足之处:封装程度太高,有时候sql语句的效率偏低,需要你自己写SQL语句
# 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字段,没有自己定义会自动创建
# username varchar(32)
username = models.CharField(max_length=32)
# password int
password = models.IntegerField()
字段的增删查改
# 字段的增加
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的时候一定要细心
千万不要注释一些字段
执行迁移命令之前最好先检查一下自己写的代码
"""
# 人生教训:当你离开你的计算机之后一定要锁屏
数据的增删改查(简单使用)
# 查
res = models.User.objects.filter(username=username).first()
"""
返回值你先看成是列表套数据对象的格式
它也支持索引取值 切片操作 但是不支持负数索引
它也不推荐你使用索引的方式取值
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.save()
user_obj = models.User(username=username,password=password)
user_obj.save() # 保存数据
数据的增删查改(完整使用)
# 增
from app01 import models
res = models.User.objects.create(username=username,password=password)
# 返回值就是当前被创建的对象本身
# 第二种增加,记得执行user_obj.save()
user_obj = models.User(username=username,password=password)
user_obj.save() # 保存数据
# 数据库中的数据展示到前端,可编辑,可删除
# 查看
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/')
# 将数据对象展示到页面上locals()
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中创建表关系
#表与表之间的关系
一对多
多对多
一对一
没有关系
#判断表关系的方法:换位思考
假如有:
图书表
出版社表
作者表
作者详情表
图书和出版社是一对多的关系 外键字段建在多的那一方 book
图书和作者是多对多的关系 需要创建第三张表来专门存储
作者与作者详情表是一对一
# 创建表关系,先建基表,然后建外键字段
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=32)
price = models.DecimalField(max_digits=8,decimal_places=2)# 总共八位 小数点后面占两位
publish = models.ForeignKey(to='Publish') # 默认和出版社表的主键做外键关联
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')#一对一字段建在查询频率高的一方
class AuthorDetail(models.Model):
phone = models.BigIntegerField() # 或者直接字符类型
addr = models.CharField(max_length=32)
### 重点,在django1.X版本中外键默认都是级联更新删除的
django请求生命周期流程图(重点)
路由层
路由匹配
# 路由匹配
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),#上面的匹配不道就会走这里
]
无名分组
#分组,就是给某一段正则表达式用小括号扩起来
url(r'^test/(\d+)/',views.test) #匹配test/数字
def test(request,xx):
print(xx)
return HttpResponse('test')
# 无名分组就是把路由匹配到的内容传递给视图层的函数做参数
有名分组
#可以给正则表达式起一个别名,视图层就可以接受这个别名,如果名字不一样则会报错
url(r'^testadd/(?P<year>\d+)',views.testadd)
def testadd(request,year):
print(year)
return HttpResponse('testadd')
# 有名分组就是将括号内正则表达式匹配到的内容当作关键字参数传递给后面的视图函数
#有名、无名分组不能混合使用,单个分组可以使用多次
url(r'^index/(\d+)/(\d+)/(\d+)/',views.index),
url(r'^index/(?P<year>\d+)/(?P<age>\d+)/(?P<month>\d+)/',views.index),
反向解析
# 通过一些方法得到一个结果 该结果可以直接访问对应的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>
无名有名分组反向解析
# 无名分组反向解析
url(r'^index/(\d+)/',views.index,name='xxx')
前端
{% url 'xxx' 123 %}
后端
reverse('xxx', args=(1,))
# 有名分组反向解析
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,)))
路由分发
# 总路由
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)), #分发到app01下的urls.py
url(r'^app02/',include(app02_urls)) #分发到app02下的urls.py
# 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)
]
了解
名称空间(了解)
# 当多个应用出现了相同的别名 我们研究反向解析会不会自动识别应用前缀
"""
正常情况下的反向解析是没有办法自动识别前缀的
"""
# 名称空间
# 总路由
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')
]
伪静态
"""
静态网页
数据是写死的 万年不变
伪静态
将一个动态网页伪装成静态网页
为什么要伪装呢?
https://www.cnblogs.com/Dominic-Ji/p/9234099.html
伪装的目的在于增大本网站的seo查询力度
并且增加搜索引擎收藏本网上的概率
搜索引擎本质上就是一个巨大的爬虫程序
总结:
无论你怎么优化 怎么处理
始终还是干不过RMB玩家
"""
urlpatterns = [
url(r'^reg.html',views.reg,name='app02_reg')
]
虚拟环境
"""
在正常开发中 我们会给每一个项目配备一个该项目独有的解释器环境
该环境内只有该项目用到的模块 用不到一概不装
linux:缺什么才装什么
虚拟环境
你每创建一个虚拟环境就类似于重新下载了一个纯净的python解释器
但是虚拟环境不要创建太多,是需要消耗硬盘空间的
扩展:
每一个项目都需要用到很多模块 并且每个模块版本可能还不一样
那我该如何安装呢? 一个个看一个个装???
开发当中我们会给每一个项目配备一个requirements.txt文件
里面书写了该项目所有的模块即版本
你只需要直接输入一条命令即可一键安装所有模块即版本
"""
django版本之间的区别
1.django1.X路由层使用的是url方法
django2.Xhe3.X版本中路由层使用的是path方法
url()第一个参数支持正则
path()不支持正则 写什么就匹配什么
如果你习惯使用path或url那么也给你提供了另外一个方法
from django.urls import path, re_path
from django.conf.urls import url
re_path(r'^index/',index),
url(r'^login/',login)
#django2.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)#on_delete=models.CASCADE级联更新
视图层
三板斧
HttpResponse
返回字符串类型
return HttpResponse('ok')
render
返回html页面 并且在返回给浏览器之前还可以给html文件传值
return render(request, 'login.html')
redirect
重定向
return redirect('www.baidu.com')
# 视图函数必须要返回一个HttpResponse对象
JsonResponse对象
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(l,safe=False)
# 默认只能序列化字典 序列化其他类型需要加safe参数
form表单上传文件
form表单上传文件类型的数据
1.method必须指定post
2.enctype必须是formdata
def ab_file(request):
if request.method == 'POST':
# print(request.POST) # 只能获取普通的键值对数据
# print(request.FILES) # 获取文件数据>
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对象方法
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
# 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,现在阶段我们用fbv,是在视图中对应一个函数
路由
url(r'^reg/',views.Reg)
视图层
def Reg(request):
return HttpResponse('ok')
CBV源码剖析
# 你自己不要修改源码 除了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的内部执行流程(******)
模板语法
模板语法传值
{{}}:变量相关
{%%}:逻辑相关
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()) #全部传给html页面
#前端渲染值
<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>
# 即可以点属性也可以点索引 还可以两者混用
过滤器
# 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>移除特定的字符:{{ 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>
标签
# for循环
{% for foo in l %}
<p>{{ forloop }}</p>
<p>{{ foo }}</p> #一个个元素
{% endfor %}
# 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
1.在app下创建一个templatetags文件夹
2.在该文件夹创建“任意”名称的py文件如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
@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 %}
模板的继承
# 模版的继承,先选好一个想要继承的模版页面
{% 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代码
模板导入
将页面的某一个局部当成模块的形式
哪个地方需要就可以直接导入使用即可
{% include 'wasai.html' %}
表操作
# 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)#结果是影响的行数
# user_obj = models.User.objects.filter(pk=1).first()#.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则不会
"""
# user_obj.name = 'egonPPP'
# user_obj.save() #修改之后保存
必知必会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() 根据对应字段的值取数据,结果是列表套字典
res = models.User.objects.values('name','age')
print(res)
7.values_list() 结果是列表套元祖
res = models.User.objects.values_list('name','age')
print(res)
8.distinct() 去重
res = models.User.objects.values('name','age').distinct()
print(res)
"""
去重一定要是一模一样的数据
如果带有主键那么肯定不一样 你在往后的查询中一定不要忽略主键
"""
9.order_by() #排序
res = models.User.objects.order_by('age') # 默认升序
res1 = 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)
测试脚本(重点)
# 测试脚本可以写在应用下test.py,或者创建其他py文件内
# 配置django环境,下面就可以写测试代码了
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day64.settings")
import django
django.setup()
查看内部sql语句
# 方式1,得到对象.query
res = models.User.objects.values_list('name','age')
print(res.query)
queryset对象才能够点击query查看内部的sql语句
# 方式2:所有的sql语句都能查看,配置文件中配置,然后对象.query
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console':{
'level':'DEBUG',
'class':'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'propagate': True,
'level':'DEBUG',
},
}
}
神奇的双下划线
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)
# 是否区分大小写 查询出名字里面含有p的数据 忽略大小写
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')
一对多外键增、删、改
# 增
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)
多对多外键增、删、改
# 操作第三张表,给书籍添加作者
add添加id,支持多个
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)
add添加对象,支持多个
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)
# 删,也支持删id、删对象
remove删id,支持多个
book_obj.authors.remove(2)
book_obj.authors.remove(1,3)
remove删对象,支持多个
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)
# 修改,必须传的是可迭代对象,可以是表id(主键)也可以是对象
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]) # 括号内必须给一个可迭代对象
# 清空
# 在第三张关系表中清空某个书籍与作者的绑定关系,不需要传参
book_obj.authors.clear()
正反向概念
# 正向
外键字段在我手上那么,我查你就是正向
# 反向
外键字段如果不在自己手上,我查你就是反向
一对一和多对多正反向的判断也是一样*
#正向查询按字段,反向查询按表名小写*
多表操作
子查询(对象的跨表查询)
#正向查,按字段
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()
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)
# 反向查询,按表名小写
4.查询出版社是东方出版社出版的书
publish_obj = models.Publish.objects.filter(name='东方出版社').first()
# 出版社查书 反向
res = publish_obj.book_set # 结果有多个加.all(),否则为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 # 结果有多个加.all(),否则为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)
联表查询(双下划线的跨表查询)
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)
#你只要掌握了正反向的概念,以及双下划线,那么你就可以无限制的跨表
聚合查询
#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)
分组查询
#annotate,分组查询特点,分组之后只能获取到分组的依据,无法获取组内其他字段
#严格模式,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)
# 2.统计每个出版社卖的最便宜的书的价格
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')
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()
#如果 出现分组报错,需要修改数据库严格模式
F查询
#f查询,能够直接获取表中某个字段对应的数据
1.查询卖出数大于库存数的书籍
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') + '爆款') # 所有名称都变成空白
Q查询
1.查询卖出数大于100或者价格小于600的书籍,filter括号内and关系
res = models.Book.objects.filter(maichu__gt=100,price__lt=600)
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)
# 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-model进阶
事务
#特性
将多个sql语句操作变成原子性操作,要么同时成功,有一个失败则里面回滚到原来的状态,保证数据的完整性和一致性(NoSQL数据库对于事务则是部分支持)
ACID
原子性
不可分割的最小单位
一致性
跟原子性是相辅相成
隔离性
事务之间互相不干扰
持久性
事务一旦确认永久生效
#事务的回滚,rollback,事务的确认,commit
# django开启事务
from django.db import transaction
try:
with transaction.atomic():
# 写操作数据库的orm语句1
# 写操作数据库的orm语句2
...
# with代码快内书写的所有orm操作都是属于同一个事务
except Exception as e:
print(e)
print('执行其他操作')
orm常用字段及参数
AutoField 主键字段 #数据库对应primary_key
primary_key=True
CharField 字符串字段 #数据库对应varchar
verbose_name 字段的注释
max_length 长度
IntegerField 整数字段 #数据库对应int
BigIntegerField 长整形字段 #数据库对应bigint
DecimalField 浮点型字段
max_digits=8 最大8位
decimal_places=2 保留小数点后2位
EmailFiled 字符串类型,Django Admin以及ModelForm中提供验证机制
DateField 日期字段 #数据库对应date
DateTimeField 日期时间字段 #数据库对应datetime
auto_now=True:每次修改数据的时候都会自动更新当前时间
auto_now_add=True:只在创建数据的时候记录创建时间后续不会自动修改了
BooleanField(Field) 布尔值字段,该字段传入True,False,
TextField(Field) 大文本字段,没有字段限制
FileField(Field) 文件字段
upload_to = "/data"
给该字段传一个文件对象,会自动将文件保存到/data目录下然后将文件路径保存到数据库中
/data/a.txt
# 更多字段
直接参考博客:https://www.cnblogs.com/Dominic-Ji/p/9203990.html
#自定义字段
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):
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
当删除关联表中的数据时,当前表与其关联的行的行为。
choices参数(字段设计常见)
class User(models.Model):
username = models.CharField(max_length=32)
age = models.IntegerField()
# 性别
gender_choices = (
(1,'男'),
(2,'女'),
(3,'其他'),
)
gender = models.IntegerField(choices=gender_choices)
score_choices = (
('A','优秀'),
('B','良好'),
('C','及格'),
('D','不合格'),
)
# 保证字段类型跟列举出来的元祖第一个数据类型一致即可
score = models.CharField(choices=score_choices,null=True)
from app01 import models
models.User.objects.create(username='jason',age=18,gender=1)
models.User.objects.create(username='egon',age=85,gender=2)
models.User.objects.create(username='tank',age=40,gender=3)
# 存的时候 没有列举出来的数字也能存(范围还是按照字段类型决定)
models.User.objects.create(username='tony',age=45,gender=4)
# 取
user_obj = models.User.objects.filter(pk=1).first()
print(user_obj.get_gender_display()) #get_字段名_display()
user_obj = models.User.objects.filter(pk=4).first()
print(user_obj.get_gender_display()) #4,没有对应关系,是什么还是展示什么
# CRM相关内部表
class School(models.Model): #校区表
title = models.CharField(verbose_name='校区名称', max_length=32)
def __str__(self):
return self.title
class Course(models.Model): #课程表
name = models.CharField(verbose_name='课程名称', max_length=32)
def __str__(self):
return self.name
class Department(models.Model): #部门表
title = models.CharField(verbose_name='部门名称', max_length=16)
code = models.IntegerField(verbose_name='部门编号', unique=True, null=False)
def __str__(self):
return self.title
class UserInfo(models.Model): #员工表
name = models.CharField(verbose_name='员工姓名', max_length=16)
email = models.EmailField(verbose_name='邮箱', max_length=64)
depart = models.ForeignKey(verbose_name='部门', to="Department",to_field="code")
user=models.OneToOneField("User",default=1)
def __str__(self):
return self.name
class ClassList(models.Model): #班级表
school = models.ForeignKey(verbose_name='校区', to='School')
course = models.ForeignKey(verbose_name='课程名称', to='Course')
semester = models.IntegerField(verbose_name="班级(期)")
price = models.IntegerField(verbose_name="学费")
start_date = models.DateField(verbose_name="开班日期")
graduate_date = models.DateField(verbose_name="结业日期", null=True, blank=True)
memo = models.CharField(verbose_name='说明', max_length=256, blank=True, null=True, )
teachers = models.ManyToManyField(verbose_name='任课老师', to='UserInfo',limit_choices_to={'depart':1002})
tutor = models.ForeignKey(verbose_name='班主任', to='UserInfo',related_name="class_list",limit_choices_to={'depart':1006})
def __str__(self):
return "{0}({1}期)".format(self.course.name, self.semester)
class Customer(models.Model): #客户表
qq = models.CharField(verbose_name='qq', max_length=64, unique=True, help_text='QQ号必须唯一')
name = models.CharField(verbose_name='学生姓名', max_length=16)
gender_choices = ((1, '男'), (2, '女'))
gender = models.SmallIntegerField(verbose_name='性别', choices=gender_choices)
education_choices = (
(1, '重点大学'),
(2, '普通本科'),
(3, '独立院校'),
(4, '民办本科'),
(5, '大专'),
(6, '民办专科'),
(7, '高中'),
(8, '其他')
)
education = models.IntegerField(verbose_name='学历', choices=education_choices, blank=True, null=True, )
graduation_school = models.CharField(verbose_name='毕业学校', max_length=64, blank=True, null=True)
major = models.CharField(verbose_name='所学专业', max_length=64, blank=True, null=True)
experience_choices = [
(1, '在校生'),
(2, '应届毕业'),
(3, '半年以内'),
(4, '半年至一年'),
(5, '一年至三年'),
(6, '三年至五年'),
(7, '五年以上'),
]
experience = models.IntegerField(verbose_name='工作经验', blank=True, null=True, choices=experience_choices)
work_status_choices = [
(1, '在职'),
(2, '无业')
]
work_status = models.IntegerField(verbose_name="职业状态", choices=work_status_choices, default=1, blank=True,
null=True)
company = models.CharField(verbose_name="目前就职公司", max_length=64, blank=True, null=True)
salary = models.CharField(verbose_name="当前薪资", max_length=64, blank=True, null=True)
source_choices = [
(1, "qq群"),
(2, "内部转介绍"),
(3, "官方网站"),
(4, "百度推广"),
(5, "360推广"),
(6, "搜狗推广"),
(7, "腾讯课堂"),
(8, "广点通"),
(9, "高校宣讲"),
(10, "渠道代理"),
(11, "51cto"),
(12, "智汇推"),
(13, "网盟"),
(14, "DSP"),
(15, "SEO"),
(16, "其它"),
]
source = models.SmallIntegerField('客户来源', choices=source_choices, default=1)
referral_from = models.ForeignKey(
'self',
blank=True,
null=True,
verbose_name="转介绍自学员",
help_text="若此客户是转介绍自内部学员,请在此处选择内部学员姓名",
related_name="internal_referral"
)
course = models.ManyToManyField(verbose_name="咨询课程", to="Course")
status_choices = [
(1, "已报名"),
(2, "未报名")
]
status = models.IntegerField(
verbose_name="状态",
choices=status_choices,
default=2,
help_text=u"选择客户此时的状态"
)
consultant = models.ForeignKey(verbose_name="课程顾问", to='UserInfo', related_name='consultanter',limit_choices_to={'depart':1001})
date = models.DateField(verbose_name="咨询日期", auto_now_add=True)
recv_date = models.DateField(verbose_name="当前课程顾问的接单日期", null=True)
last_consult_date = models.DateField(verbose_name="最后跟进日期", )
def __str__(self):
return self.name
class ConsultRecord(models.Model): #客户跟进记录
customer = models.ForeignKey(verbose_name="所咨询客户", to='Customer')
consultant = models.ForeignKey(verbose_name="跟踪人", to='UserInfo',limit_choices_to={'depart':1001})
date = models.DateField(verbose_name="跟进日期", auto_now_add=True)
note = models.TextField(verbose_name="跟进内容...")
def __str__(self):
return self.customer.name + ":" + self.consultant.name
class Student(models.Model): #学生表(已报名)
customer = models.OneToOneField(verbose_name='客户信息', to='Customer')
class_list = models.ManyToManyField(verbose_name="已报班级", to='ClassList', blank=True)
emergency_contract = models.CharField(max_length=32, blank=True, null=True, verbose_name='紧急联系人')
company = models.CharField(verbose_name='公司', max_length=128, blank=True, null=True)
location = models.CharField(max_length=64, verbose_name='所在区域', blank=True, null=True)
position = models.CharField(verbose_name='岗位', max_length=64, blank=True, null=True)
salary = models.IntegerField(verbose_name='薪资', blank=True, null=True)
welfare = models.CharField(verbose_name='福利', max_length=256, blank=True, null=True)
date = models.DateField(verbose_name='入职时间', help_text='格式yyyy-mm-dd', blank=True, null=True)
memo = models.CharField(verbose_name='备注', max_length=256, blank=True, null=True)
def __str__(self):
return self.customer.name
class ClassStudyRecord(models.Model): #上课记录表 (班级记录)
class_obj = models.ForeignKey(verbose_name="班级", to="ClassList")
day_num = models.IntegerField(verbose_name="节次", help_text=u"此处填写第几节课或第几天课程...,必须为数字")
teacher = models.ForeignKey(verbose_name="讲师", to='UserInfo',limit_choices_to={'depart':1002})
date = models.DateField(verbose_name="上课日期", auto_now_add=True)
course_title = models.CharField(verbose_name='本节课程标题', max_length=64, blank=True, null=True)
course_memo = models.TextField(verbose_name='本节课程内容概要', blank=True, null=True)
has_homework = models.BooleanField(default=True, verbose_name="本节有作业")
homework_title = models.CharField(verbose_name='本节作业标题', max_length=64, blank=True, null=True)
homework_memo = models.TextField(verbose_name='作业描述', max_length=500, blank=True, null=True)
exam = models.TextField(verbose_name='踩分点', max_length=300, blank=True, null=True)
def __str__(self):
return "{0} day{1}".format(self.class_obj, self.day_num)
class StudentStudyRecord(models.Model): #学生学习记录
classstudyrecord = models.ForeignKey(verbose_name="第几天课程", to="ClassStudyRecord")
student = models.ForeignKey(verbose_name="学员", to='Student')
record_choices = (('checked', "已签到"),
('vacate', "请假"),
('late', "迟到"),
('noshow', "缺勤"),
('leave_early', "早退"),
)
record = models.CharField("上课纪录", choices=record_choices, default="checked", max_length=64)
score_choices = ((100, 'A+'),
(90, 'A'),
(85, 'B+'),
(80, 'B'),
(70, 'B-'),
(60, 'C+'),
(50, 'C'),
(40, 'C-'),
(0, ' D'),
(-1, 'N/A'),
(-100, 'COPY'),
(-1000, 'FAIL'),
)
score = models.IntegerField("本节成绩", choices=score_choices, default=-1)
homework_note = models.CharField(verbose_name='作业评语', max_length=255, blank=True, null=True)
note = models.CharField(verbose_name="备注", max_length=255, blank=True, null=True)
homework = models.FileField(verbose_name='作业文件', blank=True, null=True, default=None)
stu_memo = models.TextField(verbose_name='学员备注', blank=True, null=True)
date = models.DateTimeField(verbose_name='提交作业日期', auto_now_add=True)
def __str__(self):
return "{0}-{1}".format(self.classstudyrecord, self.student)
#chocies参数使用场景非常广泛
数据库查询优化
#only与defer
#想要获取书籍表中所有数的名字
res = models.Book.objects.values('title')
for d in res:
print(d.get('title'))
# 你给我实现获取到的是一个数据对象 然后点title就能够拿到书名 并且没有其他字段
res = models.Book.objects.only('title')
print(res)
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)
# select_related与prefetch_related,跟跨表操作有关
res = models.Book.objects.select_related('publish') # 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)
MTV、MVC模型
MTV:Django号称是MTV模型
M:model
T:templates
V:views
MVC:django本质也是MVC
M:models
V:views
C:controller
vue框架:MVVM模型
第三张表(多对多字段)创建方式
#全自动
class Book(models.Model):
name = models.CharField(max_length=32)
authors = models.ManyToManyField(to='Author')
class Author(models.Model):
name = models.CharField(max_length=32)
优点:不需要自己写代码
缺点:扩展性差,不能添加额外字段
#纯手动
class Book(models.Model):
name = models.CharField(max_length=32)
class Author(models.Model):
name = models.CharField(max_length=32)
class Book2Author(models.Model):
book_id = models.ForeignKey(to='Book')
author_id = models.ForeignKey(to='Author')
优点:随意扩展字段
缺点:不能使用orm提供的的方法操作数据库,不介意使用这种方式
#半自动*,只能使用orm的正反向查询,不支持add(), set(), remove(), clear()
class Book(models.Model):
name = models.CharField(max_length=32)
authors = models.ManyToManyField(to='Author',
through='Book2Author',
through_fields=('book','author')
) #多对多字段放在使用频率高的一方
class Author(models.Model):
name = models.CharField(max_length=32)
class Book2Author(models.Model):
book = models.ForeignKey(to='Book')
author = models.ForeignKey(to='Author')
info = models.CharField(max_length=32) #额外字段
优点:后续可以添加字段
Ajax
介绍
#特点
异步提交
局部刷新
如:Github注册,动态获取用户名实时跟后端确认并实时展示的前端(局部刷新)
#朝后端请求的方式
1 浏览器输入url GET请求
2 a标签href属性 GET请求
3 form表单 GET请求/POST请求
4 ajax GET请求/POST请求
#Ajax我们学习jQuery封装之后的版本(不学原生的,原生的复杂并且在实际项目中也一般不用),使用注意,前端页面使用记得导入JQuery
小例子
$('#btn').click(function () {
// 朝后端发送ajax请求
$.ajax({
// 1.指定朝哪个后端发送ajax请求
url:'', // 不写就是朝当前地址提交
// 2.请求方式
type:'post', // 不指定默认就是get 都是小写
// 3.数据
{#data:{'username':'jason','password':123},#}
data:{'i1':$('#d1').val(),'i2':$('#d2').val()},
// 4.回调函数:当后端给你返回结果的时候会自动触发 args接受后端的返回结果
success:function (args) {
{#alert(args) // 通过DOM操作动态渲染到第三个input里面#}
{#$('#d3').val(args)#}
console.log(typeof args)
}
})
})
JsonResponse可以
#后端用c返回数据,回调函数不会自动反序列化,需要自己反序列化
1.自己在前端利用JSON.parse()
2.在ajax里面配置一个参数 ???没找到
前后端传输数据的编码格式(contentType)
#前后端传输数据的编码格式
urlencoded
formdata #可以携带文件
json
#能向后端提交post请求的方式
form表单
ajax请求
#from表单
默认的数据编码格式是urlencoded
django后端针对urlencoded格式的数据,会放到request.POST中
formdata格式,普通的键值对会放到request.POST中,文件在request.File中
不能发送json格式数据
#ajax请求
默认编码格式urlencoded
formdata格式,普通的键值对会放到request.POST中,文件在request.File中
可以发送json格式数据
ajax发送json格式数据
#后端需要判断
if request.is_ajax(): #判断是ajax请求再操作数据
...
#前端
<script>
$('#d1').click(function () {
$.ajax({
url:'',
type:'post',
data:JSON.stringify({'username':'jason','age':25}), #序列化数据
contentType:'application/json', // 指定编码格式
success:function () {
//成功的回调函数
}
})
})
</script>
#ajax发送json格式数据需要注意
1.contentType参数指定成:application/json
2.数据是真正的json格式数据
3.django后端去request.body获取json数据
ajax发送文件
#文件需要借助js内置对象FormData
<script>
// 点击按钮朝后端发送普通键值对和文件数据
$('#d4').on('click',function () {
// 1 需要先利用FormData内置对象
let formDateObj = new FormData();
// 2 添加普通的键值对
formDateObj.append('username',$('#d1').val());
formDateObj.append('password',$('#d2').val());
// 3 添加文件对象
formDateObj.append('myfile',$('#d3')[0].files[0])
// 4 将对象基于ajax发送给后端
$.ajax({
url:'',
type:'post',
data:formDateObj, // 直接将对象放在data后面即可
// ajax发送文件必须要指定的两个参数
contentType:false, // 不需使用任何编码 django后端能够自动识别formdata对象
processData:false, // 告诉你的浏览器不要对你的数据进行任何处理
success:function (args) {
}
})
})
</script>
#后端
def ab_file(request):
if request.is_ajax():
if request.method == 'POST':
print(request.POST) #数据
print(request.FILES) #文件对象
return render(request,'ab_file.html')
django自带的序列化组件
#前端需要获取到数据是列表套字典
import json
from django.http import JsonResponse
from django.core import serializers
def ab_ser(request):
user_queryset = models.User.objects.all()
# 序列化
res = serializers.serialize('json',user_queryset)
return HttpResponse(res)
ajax结合sweetalert
<script>
$('.del').on('click',function () {
// 先将当前标签对象存储起来
let currentBtn = $(this);
// 二次确认弹框
swal({
title: "你确定要删吗?",
text: "你可要考虑清除哦,可能需要拎包跑路哦!",
type: "warning",
showCancelButton: true,
confirmButtonClass: "btn-danger",
confirmButtonText: "是的,老子就要删!",
cancelButtonText: "算了,算了!",
closeOnConfirm: false,
closeOnCancel: false,
showLoaderOnConfirm: true
},
function(isConfirm) {
if (isConfirm) {
// 朝后端发送ajax请求删除数据之后 再弹下面的提示框
$.ajax({
{#url:'/delete/user/' + currentBtn.attr('delete_id'), // 1 传递主键值方式1#}
url:'/delete/user/', // 2 放在请求体里面
type:'post',
data:{'delete_id':currentBtn.attr('delete_id')},
success:function (args) { // args = {'code':'','msg':''}
// 判断响应状态码 然后做不同的处理
if(args.code === 1000){
swal("删了!", args.msg, "success");
// 1.lowb版本 直接刷新当前页面
{#window.location.reload()#}
// 2.利用DOM操作 动态刷新
currentBtn.parent().parent().remove()
}else{
swal('完了','出现了位置的错误','info')
}
}
})
} else {
swal("怂逼", "不要说我认识你", "error");
}
});
})
</script>
批量创建
def ab_pl(request):
#传统方式
for i in range(10000):
models.Book.objects.create(title='第%s本书'%i)
# 再将所有的数据查询并展示到前端页面
book_queryset = models.Book.objects.all()
return render(request,'ab_pl.html',locals())
#批量创建
book_list = []
for i in range(100000):
book_obj = models.Book(title='第%s本书'%i)
book_list.append(book_obj)
models.Book.objects.bulk_create(book_list) #能够大幅度减少操作时间
return render(request,'ab_pl.html',locals())
自定义分页器
基本思路
#django有自带的分页器模块,不太好用,我们自定义一个
book_list = models.Book.objects.all()
# 想访问哪一页
current_page = request.GET.get('page',1) # 如果获取不到当前页码 就展示第一页
# 数据类型转换
try:
current_page = int(current_page)
except Exception:
current_page = 1
# 每页展示多少条
per_page_num = 10
# 起始位置
start_page = (current_page - 1) * per_page_num
# 终止位置
end_page = current_page * per_page_num
# 计算出到底需要多少页
all_count = book_list.count()
page_count, more = divmod(all_count, per_page_num)
if more:
page_count += 1
page_html = ''
xxx = current_page
if current_page < 6:
current_page = 6
for i in range(current_page-5,current_page+6):
if xxx == i:
page_html += '<li class="active"><a href="?page=%s">%s</a></li>'%(i,i)
else:
page_html += '<li><a href="?page=%s">%s</a></li>'%(i,i)
book_queryset = book_list[start_page:end_page]
实现
#自定义分页器基于bootstrao样式,需要提前导入bootstrap v3版本,jQuery v3版本
#项目名/utils/pagination.py
class Pagination(object):
def __init__(self, current_page, all_count, per_page_num=2, pager_count=11):
"""
封装分页相关数据
:param current_page: 当前页
:param all_count: 数据库中的数据总条数
:param per_page_num: 每页显示的数据条数
:param pager_count: 最多显示的页码个数
"""
try:
current_page = int(current_page)
except Exception as e:
current_page = 1
if current_page < 1:
current_page = 1
self.current_page = current_page
self.all_count = all_count
self.per_page_num = per_page_num
# 总页码
all_pager, tmp = divmod(all_count, per_page_num)
if tmp:
all_pager += 1
self.all_pager = all_pager
self.pager_count = pager_count
self.pager_count_half = int((pager_count - 1) / 2)
@property
def start(self):
return (self.current_page - 1) * self.per_page_num
@property
def end(self):
return self.current_page * self.per_page_num
def page_html(self):
# 如果总页码 < 11个:
if self.all_pager <= self.pager_count:
pager_start = 1
pager_end = self.all_pager + 1
# 总页码 > 11
else:
# 当前页如果<=页面上最多显示11/2个页码
if self.current_page <= self.pager_count_half:
pager_start = 1
pager_end = self.pager_count + 1
# 当前页大于5
else:
# 页码翻到最后
if (self.current_page + self.pager_count_half) > self.all_pager:
pager_end = self.all_pager + 1
pager_start = self.all_pager - self.pager_count + 1
else:
pager_start = self.current_page - self.pager_count_half
pager_end = self.current_page + self.pager_count_half + 1
page_html_list = []
# 添加前面的nav和ul标签
page_html_list.append('''
<nav aria-label='Page navigation>'
<ul class='pagination'>
''')
first_page = '<li><a href="?page=%s">首页</a></li>' % (1)
page_html_list.append(first_page)
if self.current_page <= 1:
prev_page = '<li class="disabled"><a href="#">上一页</a></li>'
else:
prev_page = '<li><a href="?page=%s">上一页</a></li>' % (self.current_page - 1,)
page_html_list.append(prev_page)
for i in range(pager_start, pager_end):
if i == self.current_page:
temp = '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i,)
else:
temp = '<li><a href="?page=%s">%s</a></li>' % (i, i,)
page_html_list.append(temp)
if self.current_page >= self.all_pager:
next_page = '<li class="disabled"><a href="#">下一页</a></li>'
else:
next_page = '<li><a href="?page=%s">下一页</a></li>' % (self.current_page + 1,)
page_html_list.append(next_page)
last_page = '<li><a href="?page=%s">尾页</a></li>' % (self.all_pager,)
page_html_list.append(last_page)
# 尾部添加标签
page_html_list.append('''
</nav>
</ul>
''')
return ''.join(page_html_list)
# 后端
book_queryset = models.Book.objects.all()
current_page = request.GET.get('page',1)
all_count = book_queryset.count()
# 1 传值生成对象
page_obj = Pagination(current_page=current_page,all_count=all_count)
# 2 直接对总数据进行切片操作
page_queryset = book_queryset[page_obj.start:page_obj.end]
# 3 将page_queryset传递到页面 替换之前的book_queryset
# 前端
{% for book_obj in page_queryset %}
<p>{{ book_obj.title }}</p>
<nav aria-label="Page navigation">
</nav>
{% endfor %}
{#利用自定义分页器直接显示分页器样式#}
{{ page_obj.page_html|safe }}
Forms组件
#能做什么
1.渲染html代码
2.校验数据
3.展示提示信息
#基本使用
from django import forms
class MyForm(forms.Form):
# username字符串类型最小3位最大8位
username = forms.CharField(min_length=3,max_length=8)
# password字符串类型最小3位最大8位
password = forms.CharField(min_length=3,max_length=8)
# email字段必须符合邮箱格式 xxx@xx.com
email = forms.EmailField()
#校检数据
from app01 import views
# 1 将带校验的数据组织成字典的形式传入即可
form_obj = views.MyForm({'username':'jason','password':'123','email':'123'})
# 2 判断数据是否合法,全部合法才返回True
form_obj.is_valid()
# 3 查看所有校验通过的数据
form_obj.cleaned_data
# 4 查看所有不符合校验规则以及不符合的原因
form_obj.errors
# 5 校验数据只校验类中出现的字段,多传不影响,少传报错
form_obj = views.MyForm({'username':'jason','password':'123','email':'123@qq.com','hobby':'study'})
form_obj.is_valid()
# 6 校验数据 默认情况下 类里面所有的字段都必须传值
form_obj = views.MyForm({'username':'jason','password':'123'})
form_obj.is_valid()
#渲染标签,forms组件只能自动渲染用户输入的标签input,select,radio,checkbox
#后端
def index(request):
# 1 先产生一个空对象
form_obj = MyForm()
# 2 直接将该空对象传递给html页面
return render(request,'index.html',locals())
#前端
<p>第一种渲染方式:代码书写极少,封装程度太高 不便于后续的扩展 一般情况下只在本地测试使用</p>
{{ form_obj.as_p }}
{{ form_obj.as_ul }}
{{ form_obj.as_table }}
<p>第二种渲染方式:可扩展性很强 但是需要书写的代码太多 一般情况下不用</p>
<p>{{ form_obj.username.label }}:{{ form_obj.username }}</p>
<p>{{ form_obj.password.label }}:{{ form_obj.password }}</p>
<p>{{ form_obj.email.label }}:{{ form_obj.email }}</p>
<p>第三种渲染方式(推荐使用):代码书写简单 并且扩展性也高</p>
{% for form in form_obj %}
<p>{{ form.label }}:{{ form }}</p>
{% endfor %}
#label属性默认展示的是类中定义的字段首字母大写的形式也可以自己修改,直接给字段对象加label属性即可,username = forms.CharField(min_length=3,max_length=8,label='用户名')
#展示提示信息
#浏览器会自动校验数据,前端的校验弱不禁风,禁用浏览器校检<form action="" method="post" novalidate>
#后端
def index(request):
# 1 先产生一个空对象
form_obj = MyForm()
if request.method == 'POST':
# 获取用户数据并且校验
"""
1.数据获取繁琐
2.校验数据需要构造成字典的格式传入才行
ps:但是request.POST可以看成就是一个字典
"""
# 3.校验数据
form_obj = MyForm(request.POST)
# 4.判断数据是否合法
if form_obj.is_valid():
# 5.如果合法 操作数据库存储数据
return HttpResponse('OK')
# 5.不合法 有错误
# 2 直接将该空对象传递给html页面
return render(request,'index.html',locals())
#前端
{% for form in form_obj %}
<p>
{{ form.label }}:{{ form }}
<span style="color: red">{{ form.errors.0 }}</span>
</p>
{% endfor %}
# 自定义错误提示信息
class MyForm(forms.Form):
# username字符串类型最小3位最大8位
username = forms.CharField(min_length=3,max_length=8,label='用户名',
error_messages={
'min_length':'用户名最少3位',
'max_length':'用户名最大8位',
'required':"用户名不能为空"
}
)
# password字符串类型最小3位最大8位
password = forms.CharField(min_length=3,max_length=8,label='密码',
error_messages={
'min_length': '密码最少3位',
'max_length': '密码最大8位',
'required': "密码不能为空"
}
)
# email字段必须符合邮箱格式 xxx@xx.com
email = forms.EmailField(label='邮箱',
error_messages={
'invalid':'邮箱格式不正确',
'required': "邮箱不能为空"
}
)
钩子函数
#局部钩子
def clean_username(self):
# 获取到用户名
username = self.cleaned_data.get('username')
if '666' in username:
# 提示前端展示错误信息
self.add_error('username','光喊666是不行滴~')
# 将钩子函数钩去出来数据再放回去
return username
#全局钩子
def clean(self):
password = self.cleaned_data.get('password')
confirm_password = self.cleaned_data.get('confirm_password')
if not confirm_password == password:
self.add_error('confirm_password','两次密码不一致')
# 将钩子函数钩出来数据再放回去
return self.cleaned_data
forms组件其他参数补充
label 字段名
error_messages 自定义报错信息
initial 默认值
required 控制字段是否必填
1.字段没有样式
2.针对不同类型的input如何修改
text
password
date
radio
checkbox
...
widget=forms.widgets.PasswordInput(attrs={'class':'form-control c1 c2'})# 多个属性值的话 直接空格隔开即可
# 第一道关卡里面还支持正则校验
validators=[
RegexValidator(r'^[0-9]+$', '请输入数字'),
RegexValidator(r'^159[0-9]+$', '数字必须以159开头')
]
其他类型渲染
# radio
gender = forms.ChoiceField(
choices=((1, "男"), (2, "女"), (3, "保密")),
label="性别",
initial=3,
widget=forms.widgets.RadioSelect()
)
# select
hobby = forms.ChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=3,
widget=forms.widgets.Select()
)
# 多选
hobby1 = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=[1, 3],
widget=forms.widgets.SelectMultiple()
)
# 单选checkbox
keep = forms.ChoiceField(
label="是否记住密码",
initial="checked",
widget=forms.widgets.CheckboxInput()
)
# 多选checkbox
hobby2 = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=[1, 3],
widget=forms.widgets.CheckboxSelectMultiple()
)
forms组件源码
"""
切入点:
form_obj.is_valid()
"""
def is_valid(self):
"""
Returns True if the form has no errors. Otherwise, False. If errors are
being ignored, returns False.
"""
return self.is_bound and not self.errors
# 如果is_valid要返回True的话 那么self.is_bound要为True self.errors要为Flase
self.is_bound = data is not None or files is not None # 只要你传值了肯定为True
@property
def errors(self):
"Returns an ErrorDict for the data provided for the form"
if self._errors is None:
self.full_clean()
return self._errors
# forms组件所有的功能基本都出自于该方法
def full_clean(self):
self._clean_fields() # 校验字段 + 局部钩子
self._clean_form() # 全局钩子
self._post_clean()
cookie、session
cookie
服务端保存在客户端浏览器上的信息都可以称之为cookie
它的表现形式一般都是k:v键值对(可以有多个)
session
数据是保存在服务端的并且它的表现形式一般也是k:v键值对(可以有多个)
cookie操作
#登录操作
def login_auth(func):
def inner(request,*args,**kwargs):
# print(request.path_info)
# print(request.get_full_path()) # 能够获取到用户上一次想要访问的url
target_url = request.get_full_path()
if request.COOKIES.get('username'):
return func(request,*args,**kwargs)
else:
return redirect('/login/?next=%s'%target_url)
return inner
def login(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
if username == 'jason' and password == '123':
# 获取用户上一次想要访问的url
target_url = request.GET.get('next') # 这个结果可能是None
if target_url:
obj = redirect(target_url)
else:
# 保存用户登陆状态
obj = redirect('/home/')
# 让浏览器记录cookie数据
obj.set_cookie('username', 'jason666')
"""
浏览器不单单会帮你存
而且后面每次访问你的时候还会带着它过来
"""
# 跳转到一个需要用户登陆之后才能看的页面
return obj
return render(request,'login.html')
@login_auth
def home(request):
# 获取cookie信息 判断你有没有
# if request.COOKIES.get('username') == 'jason666':
# return HttpResponse("我是home页面,只有登陆的用户才能进来哟~")
# # 没有登陆应该跳转到登陆页面
# return redirect('/login/')
return HttpResponse("我是home页面,只有登陆的用户才能进来哟~")
seeion操作
#django默认session的过期时间是14天
#设置session
request.session['key'] = value
#获取session
request.session.get('key')
#设置过期时间
request.session.set_expiry()
#括号内可以放四种类型的参数
1.整数 多少秒
2.日期对象 到指定日期就失效
3.0 旦当前浏览器窗口关闭立刻失效
4.不写 失效时间就取决于django内部全局session默认的失效时间
#清除session
request.session.delete() # 只删服务端的 客户端的不删
request.session.flush() # 浏览器和服务端都清空(推荐使用)
#session是保存在服务端的 但是session的保存位置可以有多种选择
1.MySQL
2.文件
3.redis
4.memcache
#django_session表中的数据条数是取决于浏览器的
同一个计算机上(IP地址)同一个浏览器只会有一条数据生效
(当session过期的时候可能会出现多条数据对应一个浏览器,但是该现象不会持续很久,内部会自动识别过期的数据清除 你也可以通过代码清除)
CBV添加装饰器
#CBV中django不建议你直接给类的方法加装饰器
from django.views import View
from django.utils.decorators import method_decorator
# 方式2(可以添加多个针对不同的方法加不同的装饰器)
# @method_decorator(login_auth,name='get')
# @method_decorator(login_auth,name='post')
class MyLogin(View):
@method_decorator(login_auth)
# 方式3:它会直接作用于当前类里面的所有的方法
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request,*args,**kwargs)
# @method_decorator(login_auth)
# 方式1:指名道姓
def get(self,request):
return HttpResponse("get请求")
def post(self,request):
return HttpResponse('post请求')
django中间件
介绍
#中间件是django的门户
1 请求来的时候需要经过中间件才能到达django后端
2 响应走的时候也需要经过中间件才能发送出去
#djnago自带7个中间件
'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',
自定义中间件
#utils\mymiddleware.py 根目录下建utils包
from django.utils.deprecation import MiddlewareMixin
class Md1(MiddlewareMixin):
def process_request(self, request):
print("Md1请求")
def process_response(self, request, response):
print("Md1返回")
return response
class Md2(MiddlewareMixin):
def process_request(self, request):
print("Md2请求")
def process_response(self, request, response):
print("Md2返回")
return response
#django复习\settings.py 注册中间件
MIDDLEWARE = [
'utils.mymiddleware.Md1',
'utils.mymiddleware.Md2',
]
#自定义中间件暴露给程序员的5个方法
1 必须掌握
process_request
1.请求来的时候会经过每一个中间件的process_request方法,执行顺序是从上往下
2.如果没有定义process_request方法,会直接跳过到下一个中间件
3.如果返回HttpResponse对象,那么请求将不再继续往后执行,而是直接原路返回(校验失败不允许访问...)*
4 作用:process_request方法是用来做全局相关的所有限制功能
process_response
1.响应走的时候需要经过每一个中间件里面的process_response方法
该方法有两个额外参数request,response
2.该方法必须返回一个HttpResponse对象
1.默认返回的就是形参response
2.你也可以自己返回自己的
3.执行顺序是从下往上,如果没有该方法,直接跳过到上一个中间件
#flask框架也有一个中间件但是它的规律
只要返回数据了就必须经过所有中间件里面的类似于process_reponse方法
csfr跨站请求伪造
#案例
钓鱼网站
我搭建一个跟正规网站一模一样的界面(中国银行)
用户不小心进入到了我们的网站,用户给某个人打钱
打钱的操作确确实实是提交给了中国银行的系统,用户的钱也确确实实减少了
但是唯一不同的时候打钱的账户不适用户想要打的账户变成了一个莫名其妙的账户
本质
我们在钓鱼网站的页面 针对对方账户 只给用户提供一个没有name属性的普通input框
然后我们在内部隐藏一个已经写好name和value的input框
#如何避免上述问题
csrf跨站请求伪造校验
网站在给用户返回一个具有提交数据功能页面的时候会给这个页面加一个唯一标识
当这个页面朝后端发送post请求的时候 我的后端会先校验唯一标识,如果唯一标识不对直接拒绝(403 forbbiden)如果成功则正常执行
如何符合效验
#form表单如何符合效验,form表单内加{% csrf_token %}
<form action="" method="post">
{% csrf_token %}
<p>username:<input type="text" name="username"></p>
<p>target_user:<input type="text" name="target_user"></p>
<p>money:<input type="text" name="money"></p>
<input type="submit">
</form>
#ajax任何人符合效验
// 第一种 利用标签查找获取页面上的随机字符串
{#data:{"username":'jason','csrfmiddlewaretoken':$('[name=csrfmiddlewaretoken]').val()},#}
// 第二种 利用模版语法提供的快捷书写
{#data:{"username":'jason','csrfmiddlewaretoken':'{{ csrf_token }}'},#}
// 第三种 通用方式直接拷贝js代码并应用到自己的html页面上即可
data:{"username":'jason'}
csrf相关装饰器
1.网站整体都不校验csrf,就单单几个视图函数需要校验
2.网站整体都校验csrf,就单单几个视图函数不校验
from django.views.decorators.csrf import csrf_protect,csrf_exempt
from django.utils.decorators import method_decorator
"""
csrf_protect 需要校验
针对csrf_protect符合我们之前所学的装饰器的三种玩法
csrf_exempt 忽视校验
针对csrf_exempt只能给dispatch方法加才有效
"""
# @csrf_exempt
# @csrf_protect
def transfer(request):
if request.method == 'POST':
username = request.POST.get('username')
target_user = request.POST.get('target_user')
money = request.POST.get('money')
print('%s给%s转了%s元'%(username,target_user,money))
return render(request,'transfer.html')
from django.views import View
# @method_decorator(csrf_protect,name='post') # 针对csrf_protect 第二种方式可以
# @method_decorator(csrf_exempt,name='post') # 针对csrf_exempt 第二种方式不可以
@method_decorator(csrf_exempt,name='dispatch')
class MyCsrfToken(View):
# @method_decorator(csrf_protect) # 针对csrf_protect 第三种方式可以
# @method_decorator(csrf_exempt) # 针对csrf_exempt 第三种方式可以
def dispatch(self, request, *args, **kwargs):
return super(MyCsrfToken, self).dispatch(request,*args,**kwargs)
def get(self,request):
return HttpResponse('get')
# @method_decorator(csrf_protect) # 针对csrf_protect 第一种方式可以
# @method_decorator(csrf_exempt) # 针对csrf_exempt 第一种方式不可以
def post(self,request):
return HttpResponse('post')
补充知识点
# 模块importlib,不使用from myfile import b获取b.py的name值
#myfile\b.py
name = 'jason'
#a.py
import importlib
res = 'myfile.b'
ret = importlib.import_module(res) # from myfile import b
print(ret.name) #jason
#重要思想
import settings
import importlib
def send_all(content):
for path_str in settings.NOTIFY_LIST: #'notify.email.Email'
module_path,class_name = path_str.rsplit('.',maxsplit=1)
# module_path = 'notify.email' class_name = 'Email'
# 1 利用字符串导入模块
module = importlib.import_module(module_path) # from notify import email
# 2 利用反射获取类名
cls = getattr(module,class_name) # Email、QQ、Wechat
# 3 生成类的对象
obj = cls()
# 4 利用鸭子类型直接调用send方法
obj.send(content)
Auth模块
#在django项目执行数据迁移命令会生成很多表,django_session,auth_user
#djangi项目启动之后访问damin,需要用户名和密码,数据参考的是auth_user表,并且是管理员才行
#用户认证系统--auth,它默认使用 auth_user 表来存储用户数据。
创建超级管理员
python3 manage.py createsuperuser
auth模块方法
# 1.比对用户名和密码是否正确
user_obj = auth.authenticate(request,username=username,password=password)
print(user_obj) #返回值是对象或none
print(user_obj.username) # jason
print(user_obj.password) # 密文
# 2.保存用户状态
auth.login(request,user_obj) # 类似于request.session[key] = user_obj,就可以在任何地方request.user获取当前登录的对象
# 3.判断当前用户是否登陆
request.user.is_authenticated()
# 4.获取当前登陆用户
request.user
# 5.校验用户是否登陆装饰器
from django.contrib.auth.decorators import login_required
# 局部配置
@login_required(login_url='/login/')
# 全局配置
LOGIN_URL = '/login/'
1.如果局部和全局都有 该听谁的?
局部 > 全局
2.局部和全局哪个好呢?
全局的好处在于无需重复写代码 但是跳转的页面却很单一
局部的好处在于不同的视图函数在用户没有登陆的情况下可以跳转到不同的页面
# 6.比对原密码
request.user.check_password(old_password)
# 7.修改密码
request.user.set_password(new_password) # 仅仅是在修改对象的属性
request.user.save() # 这一步才是真正的操作数据库
# 8.注销
auth.logout(request)
# 9.注册
# 操作auth_user表写入数据
User.objects.create(username=username,password=password) # 写入数据 不能用create 密码没有加密处理
# 创建普通用户
User.objects.create_user(username=username,password=password)
# 创建超级用户(了解):使用代码创建超级用户 邮箱是必填的 而用命令创建则可以不填
User.objects.create_superuser(username=username,email='123@qq.com',password=password)
扩展auth_user表
from django.db import models
from django.contrib.auth.models import User,AbstractUser
# 第一种:一对一关系 不推荐
class UserDetail(models.Model):
phone = models.BigIntegerField()
user = models.OneToOneField(to='User')
# 第二种:面向对象的继承
class UserInfo(AbstractUser):
"""
如果继承了AbstractUser
那么在执行数据库迁移命令的时候auth_user表就不会再创建出来了
而UserInfo表中会出现auth_user所有的字段外加自己扩展的字段
这么做的好处在于你能够直接点击你自己的表更加快速的完成操作及扩展
前提:
1.在继承之前没有执行过数据库迁移命令
auth_user没有被创建,如果当前库已经创建了那么你就重新换一个库
2.继承的类里面不要覆盖AbstractUser里面的字段名
表里面有的字段都不要动,只扩展额外字段即可
3.需要在配置文件中告诉django你要用UserInfo替代auth_user(******)
AUTH_USER_MODEL = 'app01.UserInfo'
'应用名.表名'
"""
phone = models.BigIntegerField()