Django 是一个开放源代码的 Web 应用框架,由 Python 写成。其几乎囊括了 Web 应用的方方面面,可以用于快速搭建高性能、优雅的网站,Django 提供了许多网站后台开发经常用到的模块,使开发者能够专注于业务部分
版本介绍
django3.X: 默认支持异步功能
django2.X: 默认不支持异步
django1.X: 默认不支持异步
不同版本的解释器可能会报错,找到报错信息的最后一行提示对应的代码,删除源文件逗号即可
Django 版本对应的 Python 版本:
Django 版本 | Python 版本 |
---|---|
1.8 | 2.7, 3.2 , 3.3, 3.4, 3.5 |
1.9, 1.10 | 2.7, 3.4, 3.5 |
1.11 | 2.7, 3.4, 3.5, 3.6 |
2.0 | 3.4, 3.5, 3.6, 3.7 |
2.1, 2.2 | 3.5, 3.6, 3.7 |
命令: pip install django==1.11.11
网址: https://www.djangoproject.com/download/
Django 项目与应用
Django 项目类似于一个空壳子,里面包含着诸多应用,此应用不是日常所说的app软件,Django应用就相当于一个功能模
块,用于实现功能。
1. 验证是否下载成功
cmd窗口直接输入django-admin后如下图就是成功了
2.创建django项目
命令方式:django-admin startproject 项目名(mysite)
进入到指定的文件目录下执行命令
pycharm方式:如下图示例
3.启动django项目
命令方式:cd 项目名(mysite) python3 manage.py runserver IP:PORT
pychar方式:直接点击运行即可
4.创建app应用
命令方式:python3 manage.py startapp 应用名(app01)
'''需要切到项目根目录下创建,且命令行无法自动创建模板文件夹,需要自己执行命令创建'''
pycharm方式:打开 Tools 菜单栏,选择 Run manage.py Task... ,在框中输入下面的命令即可
startapp 应用名
创建应用之后。一定要去配置文件中注册才能生效
语法
'app01.apps.App01Config' 完整语法
'app01' 简写语法
加在settings文件中的INSTALLED_APPS
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01.apps.App01Config',
]
5. pycharm修改端口
找到Edit Configurations...然后找到Configuration下的port修改即可
我们也可以通过虚拟环境来创建Django项目,创建的方式如下所示
使用虚拟环境可以创建不同版本的Django项目,就不需要一个个重新下载了。
使用虚拟环境创建的项目会有一个venv文件夹,如下所示
--mysite项目文件夹
--mysite文件夹
---settings.py 配置文件
---urls.py 路由与视图函数对应关系(路由层)
---wsgi.py wsgiref模块(不考虑)
--manage.py django的入口文件
--db.sqlite3 django自带的sqlite3数据库(小型数据库功能不是很多还有bug)
--app01文件夹
---admin.py django后台管理
---apps.py 注册使用
---migrations文件夹 数据库迁移记录
1. 外层的mysite/目录与Django无关,只是你项目的容器,可以任意重命名。
2. manage.py:一个命令行工具,管理Django的交互脚本。
3. 内层的mysite/目录是真正的项目文件包裹目录,它的名字是你引用内部文件的Python包名
4. mysite/__init__.py:一个定义包的空文件。
5. mysite/settings.py:项目的配置文件。
6. mysite/urls.py:路由文件,所有的任务都是从这里开始分配,相当于Django驱动站点的目录。
7. mysite/wsgi.py:一个基于WSGI的web服务器进入点,提供底层的网络通信功能,通常不用关心。
8. mysite/asgi.py:一个基于ASGI的web服务器进入点,提供异步的网络通信功能,通常不用关心。
Django其实也是一个MTV 的设计模式。MTV是Model、Template、View三个单词的简写,分别代表模型、模版、视图。但是在Django中,控制器接受用户输入的部分由框架自行处理,所以 Django 里更关注的是模型(Model)、模板(Template)和视图(Views),称为MTV模式
。
层次 | 作用 |
---|---|
模型(Model),即数据存取层 | 处理与数据相关的所有事务,也就是和数据库打交道 |
模板(Template),即表现层 | 存放用于展示的模板文件,也就是HTML文档 |
视图(View),即业务逻辑层 | 存取模型及调取恰当模板的相关逻辑。模型与模板的桥梁。 |
模型层主要是对数据库的一些操作
在pycharm中连接
流程如下图所示
1. django默认自带一个sqlite3数据库,但是功能很少,仅用于本地测试
2. 首先找到settings配置文件中的'DATABASES',其默认配置如下所示
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
3. 我们要对其进行修改,如下示例
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'd3', # 库名
'HOST': '127.0.0.1', # 数据库IP地址
'PORT': 3306, # 端口
'USER': 'root', # 用户名
'PASSWORD': '123', # 密码
}
}
修改的具体内容可以参照以下网址
https://docs.djangoproject.com/en/1.11/ref/settings/#databases
4. 指定模块
在项目同名的文件夹内的'__init__.py'或者应用文件夹内的__init__.py中添加下面内容即可
import pymysql
pymysql.install_as_MySQLdb()
Django 模型使用自带的 ORM(对象关系映射),用于实现面向对象编程语言里不同类型系统的数据之间的转换。也就是说可以将SQL语句和python代码互相转换
python | 数据库 |
---|---|
类 | 表 |
实例对象 | 记录 |
类属性 | 字段 |
ORM 解析过程:
1、ORM 会将 Python 代码转成为 SQL 语句。
2、SQL 语句通过 pymysql 传送到数据库服务端。
3、在数据库中执行 SQL 语句并将结果返回。
使用 ORM 的好处:
提高开发效率。
不同数据库可以平滑切换。
使用 ORM 的缺点:
ORM 代码转换为 SQL 语句时,需要花费一定的时间,执行效率会有所降低。
'models.py文件'
from django.db import models
class Users(models.Model):
# 等价于 uid int primary key auto_increment
uid = models.AutoField(primary_key=True)
# 等价于 name varchar(32)
name = models.CharField(max_length=32)
# 等价于 pwd int
pwd = models.IntegerField()
1. 在models.py文件中编写类,该类还需要继承'models.Model'类,也就是说每个类都是django.db.models.Model
的子类。类对应的是表,在类中创建属性用于表示表中的字段。
2. 虽然已经有了字段,但是数据库中并没有增加该表,是因为还没有使用数据库迁移命令。
'主要字段'
'AutoField(Field)'
- int自增列,必须填入参数 primary_key=True
'BigAutoField(AutoField)'
- bigint自增列,必须填入参数 primary_key=True
注:当model中如果没有自增列,则自动会创建一个列名为id的列
'SmallIntegerField(IntegerField)':
- 小整数 -32768 ~ 32767
'PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)'
- 正小整数 0 ~ 32767
'IntegerField(Field)'
- 整数列(有符号的) -2147483648 ~ 2147483647
'PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)'
- 正整数 0 ~ 2147483647
'BigIntegerField(IntegerField):'
- 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807
'BooleanField(Field)'
- 布尔值类型
'NullBooleanField(Field):'
- 可以为空的布尔值
'CharField(Field)'
- 字符类型
- 必须提供max_length参数, max_length表示字符长度
- verbose_name 相当于注释
'TextField(Field)'
- 文本类型
'EmailField(CharField):'
- 字符串类型,Django Admin以及ModelForm中提供验证机制
'IPAddressField(Field)'
- 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制
'GenericIPAddressField(Field)'
- 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6
- 参数:
protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6"
unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启此功能
需要protocol="both"
'URLField(CharField)'
- 字符串类型,Django Admin以及ModelForm中提供验证 URL
'SlugField(CharField)'
- 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号)
'CommaSeparatedIntegerField(CharField)'
- 字符串类型,格式必须为逗号分割的数字
'UUIDField(Field)'
- 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证
'FilePathField(Field)'
- 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能
- 参数:
path, 文件夹路径
match=None, 正则匹配
recursive=False, 递归下面的文件夹
allow_files=True, 允许文件
allow_folders=False, 允许文件夹
'FileField(Field)'
- 字符串,路径保存在数据库,文件上传到指定目录
- 参数:
upload_to = "" 上传文件的保存路径
storage = None 存储组件,默认django.core.files.storage.FileSystemStorage
'ImageField(FileField)'
- 字符串,路径保存在数据库,文件上传到指定目录
- 参数:
upload_to = "" 上传文件的保存路径
storage = None 存储组件,默认django.core.files.storage.FileSystemStorage
width_field=None, 上传图片的高度保存的数据库字段名(字符串)
height_field=None 上传图片的宽度保存的数据库字段名(字符串)
'DateTimeField(DateField)'
- 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]
'DateField(DateTimeCheckMixin, Field)'
- 日期格式 YYYY-MM-DD
auto_now: 每次修改数据的时候都会自动更新当前时间
auto_now_add: 在数据被创建出来的时候会自动记录当前时间,之后不人为修改的情况下保持不变
'TimeField(DateTimeCheckMixin, Field)'
- 时间格式 HH:MM[:ss[.uuuuuu]]
'DurationField(Field)'
- 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型
'FloatField(Field)'
- 浮点型
'DecimalField(Field)'
- 10进制小数
- 参数:
max_digits,小数总长度
decimal_places,小数位长度
'BinaryField(Field)'
- 二进制类型
对应关系:
'AutoField': 'integer AUTO_INCREMENT',
'BigAutoField': 'bigint AUTO_INCREMENT',
'BinaryField': 'longblob',
'BooleanField': 'bool',
'CharField': 'varchar(%(max_length)s)',
'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
'DateField': 'date',
'DateTimeField': 'datetime',
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
'DurationField': 'bigint',
'FileField': 'varchar(%(max_length)s)',
'FilePathField': 'varchar(%(max_length)s)',
'FloatField': 'double precision',
'IntegerField': 'integer',
'BigIntegerField': 'bigint',
'IPAddressField': 'char(15)',
'GenericIPAddressField': 'char(39)',
'NullBooleanField': 'bool',
'OneToOneField': 'integer',
'PositiveIntegerField': 'integer UNSIGNED',
'PositiveSmallIntegerField': 'smallint UNSIGNED',
'SlugField': 'varchar(%(max_length)s)',
'SmallIntegerField': 'smallint',
'TextField': 'longtext',
'TimeField': 'time',
'UUIDField': 'char(32)',
重要参数
1. primary_key
2. max_length
3. verbose_name
4. null
5. default
6. max_digits
7. decimal_places
8. unique
9. db_index
10. auto_now
11. auto_now_add
12. 'choices'
用于可以被列举完全的数据 eg:性别 学历 工作经验 工作状态
创建表
class User(models.Model):
username = models.CharField(max_length=32)
password = models.IntegerField()
# 提供对应关系,类似于枚举
gender_choice = (
(1,'男性'),
(2,'女性'),
(3,'变性')
)
# 设置字段
gender = models.IntegerField(choices=gender_choice)
查询性别
res = models.User.objects.filter()
for obj in res:
print(obj.gender) # 返回的是数字
print(obj.get_gender_display()) # 返回的是准备好的性别,若是不存在则显示原来的数字
13. to
14. to_field
15. db_constraint
16. 外键字段中可能还会遇到related_name参数,外键字段中使用related_name参数可以修改正向查询的字段名
自定义字段类型
class MyCharField(models.Field):
def __init__(self, max_length, *args, **kwargs):
self.max_length = max_length
super().__init__(max_length=max_length, *args, **kwargs)
def db_type(self, connection):
return 'char(%s)' % self.max_length
正向迁移命令
'数据库迁移命令'
# 在pycharm的Terminal终端中输入
python3 manage.py makemigrations # 记录操作
python3 manage.py migrate # 将操作迁移到数据库
# 或者在Tools下拉菜单中的 Run manage.py task... 下输入以下关键字
makemigrations
migrate
1. 执行记录操作后会在应用的 migrations文件夹下创建一个 0001_initial.py 文件,该文件类似于日志文件,其记
录了即将要执行的操作
2. 迁移到数据库后,会在数据库生成刚创建出的表,并且表名会自动加上应用的名称,因为不同的应用可能会出现同名的表
。如果是第一次迁移,django还会自动创建一些默认需要使用到的表,如下结构
+----------------------------+
| Tables_in_userinfo |
+----------------------------+
| app01_users |
| auth_group |
| auth_group_permissions |
| auth_permission |
| auth_user |
| auth_user_groups |
| auth_user_user_permissions |
| django_admin_log |
| django_content_type |
| django_migrations |
| django_session |
+----------------------------+
3. 每次修改了跟数据库相关的python代码,都需要重新执行迁移命令。
反向迁移命令
数据库反向迁移命令,其作用是将已存在的表映射成类
python3 manage.py inspectdb
# 也可以在Run manage.py task... 下输入以下关键字
inspectdb
注意事项
当表中已经存在数据的时候要添加其他字段,不能直接使用迁移命令,需要给新添加的字段设置默认值或者设置允许为空
示例
book_sale = models.IntegerField(verbose_name='销量', null=True)
book_stock = models.IntegerField(verbose_name='库存', default=1000)
然后再使用 makemigrations记录操作、migrate数据库迁移
基础操作
'views.py文件'
# 在视图函数中操作
def create_data(request):
# 导入models文件
from app01 import models
# 给Users表中插入数据,等价于 insert into app01_users(name,pwd) values('xwx',123);
user_obj = models.Users.objects.create(name='xwx', pwd='123')
print(user_obj) # Users object
print(user_obj.uid) # 1
print(user_obj.name) # xwx
print(user_obj.pwd) # 123
# 在Users表中查询数据,等价于 select * from app01_users where name='xwx';
res = models.Users.objects.filter(name='xwx')
print(res) # ]>
print(res[0]) # Users object
print(res[0].uid) # 1
print(res[0].name) # xwx
print(res[0].pwd) # 123
print(res.first()) # Users object
print(res.first().uid) # 1
print(res.first().name) # xwx
print(res.first().pwd) # 123
# 在Users表中更新数据,等价于 update app01_users set name='XWenXiang' where uid=1
models.Users.objects.filter(uid=1).update(name='XWenXiang')
# 在Users表中删除数据,等价于 delete from app01_users where uid=1
models.Users.objects.filter(uid=1).delete()
其他操作
'aggregate: 聚合函数,获取字典类型聚合结果'
from django.db.models import Count, Avg, Max, Min, Sum
models.UserInfo.objects.aggregate(k=Count('u_id', distinct=True), n=Count('nid'))
'all: 查询所有结果'
res = models.User.objects.all() # 查询所有的数据,QuerySet 可以看成是列表套对象
'annotate: 用于实现聚合group by查询'
from django.db.models import Count, Avg, Max, Min, Sum
v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id'))
# SELECT u_id, COUNT(u_id) AS `uid` FROM UserInfo GROUP BY u_id
v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id')).filter(uid__gt=1)
# SELECT u_id, COUNT(u_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1
v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id',distinct=True)).filter(uid__gt=1)
# SELECT u_id, COUNT( DISTINCT u_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1
'count(): 返回数据库中匹配查询(QuerySet)的对象数量。'
res = models.User.objects.count() # 统计结果集的个数
'create: 给表中插入数据'
'defer: 映射中排除某列数据'
models.UserInfo.objects.defer('username','id')
'dates: 根据时间进行某一部分进行去重查找并截取指定内容'
models.DatePlus.objects.dates('ctime','day','DESC')
'datetimes: 根据时间进行某一部分进行去重查找并截取指定内容,将时间转换为指定时区时间'
models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.timezone('Asia/Shanghai'))
'delete: 删除'
'distinct(): 从返回结果中剔除重复纪录'
res = models.User.objects.all().distinct() # 数据对象中如果包含主键,不可能去重
res = models.User.objects.values('name').distinct()
'exclude: 取反'
res = models.User.objects.exclude(name='xwx') # 取反操作
'extra: 构造额外的查询条件或者映射'
Entry.objects.extra(select={'new_id': "select col from sometable where othercol > %s"}, select_params=(1,))
Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"])
Entry.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['-nid'])
'exists(): 如果QuerySet包含数据,就返回True,否则返回False'
res = models.User.objects.exists()
res = models.User.objects.filter(name='jasonNB').exists() # 判断结果集中是否有数据 有返回True 没有返回False
'first(): 返回第一条记录'
'filter(**kwargs): 它包含了与所给筛选条件相匹配的对象,但是不支持大于小于逻辑判断'
res = models.User.objects.filter() # 括号内填写筛选条件,不写相当于all(),QuerySet 可以看成是列表套对象
res = models.User.objects.filter(pk=1) # 想通过主键筛选数据,可以直接写pk,会自动定位到当前表的主键字段
res = models.User.objects.filter(pk=1)[0] # 直接获取数据对象,QuerySet支持索引取值,但是django不推荐使用,因为索引不存在会直接报错
res = models.User.objects.filter(pk=1).first() # 获取结果集中第一个对象,推荐使用封装的方法,不会出现索引超出范围报错的情况
res = models.User.objects.filter(pk=1, name='xwx').first() # 括号内支持填写多个筛选条件,默认是and关系
res = models.User.objects.filter().filter().filter().filter().filter() # 只要是QuerySet对象就可以继续点对象方法(类似于jQuery链式操作)
res = models.User.objects.filter().last() # 获取结果集中最后一个对象
'get(**kwargs): 返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误。'
res = models.User.objects.get(pk=1) # 直接获取数据对象 但是不推荐使用
res = models.User.objects.get(pk=100) # 条件不存在 直接报错
res = models.User.objects.filter(pk=100) # 条件不存在 返回空
'last(): 返回最后一条记录'
'only: 仅取某个表中的数据'
models.UserInfo.objects.only('username','id')
'order_by'
res = models.User.objects.order_by('age') # 默认是升序
res = models.User.objects.order_by('-age') # 该为降序
res = models.User.objects.order_by('age', 'pk') # 也支持多个字段依次排序
'prefetch_related: 执行多次子查询在Python代码中实现连表操作'
'reverse'
res = models.User.objects.reverse() # 不起作用
res1 = models.User.objects.order_by('age').reverse() # 只有在order_by排序之后才可以
'raw: 执行原生SQL'
models.UserInfo.objects.raw('select * from userinfo')
# 为原生SQL设置参数
models.UserInfo.objects.raw('select id as nid from userinfo where nid>%s', params=[12,])
'select_related: 表之间进行join连表操作,一次性获取关联的数据,不能对多对多使用'
'values(*field): 返回一个ValueQuerySet(一个特殊的QuerySet)'
res = models.User.objects.all().values('name','age') # QuerySet 可以看成是列表套字典
res = models.User.objects.values('name','age') # 同上
res = models.User.objects.filter(pk=2).values('name') # 可以看成是对结果集进行字段的筛选
res = models.User.objects.all().values_list('name', 'age') # QuerySet 可以看成是列表套元组
'update: 更新'
双下方法
常见双下方法
__gt 大于
__lt 小于
__gte 大于等于
__lte 小于等于
__in 成员运算
__range 范围查询
__contains 筛选含有指定字符的内容并区分大小写
__icontains 筛选含有指定字符的内容且不区分大小写
__startswith 筛选指定字符开头
__endswith 筛选指定字符结尾
__regex 正则筛选
__year 按照年份筛选数据
__month 按照月份筛选数据
__day 按天筛选
print(models.User.objects.filter(age__gt=17).values())
print(models.User.objects.filter(age__lt=13).values())
print(models.User.objects.filter(age__lte=12).values())
print(models.User.objects.filter(age__gte=11).values())
print(models.User.objects.filter(age__range=[12, 19]).values())
print(models.User.objects.filter(time__range=['2000-05-01', '2090-01-01']).values())
print(models.User.objects.filter(name__contains='x').values())
print(models.User.objects.filter(name__icontains='X').values())
print(models.User.objects.filter(name__startswith='x').values())
print(models.User.objects.filter(name__endswith='x').values())
print(models.User.objects.filter(name__in=['x']).values())
print(models.User.objects.filter(age__in=[11,12]).values())
print(models.User.objects.filter(name__regex='x').values())
print(models.User.objects.filter(time__year=2022).values())
print(models.User.objects.filter(time__month=5).values())
print(models.User.objects.filter(time__day=5).values())
ForeignKey
字段用来表示外键的一对多关系,一般把该字段设置在一对多关系中多的一方。
ManyToManyField
字段用于表示外键关系中的多对多关系,该字段不会在表中创建实际的字段,而是让ORM自动创建第三张关系表
OneToOneField
字段用于表示外键关系中的一对一关系,外键建在使用频率较高那边
2.x、3.x版本需要手动设置级联更新级联删除
'''
on_delete 用于表示删除后的操作
on_delete可以选择的请情况:
-models.CASCADE 级联删除
-models.SET_NULL 关联字段置为空 null=True
-models.SET_DEFAULT 关联字段设为默认值 default=0
-models.DO_NOTHING 由于数据库有约束会报错,去掉外键关系(公司都不建立外键)
-on_delete=models.SET(值,函数内存地址) 设置上某个值,或者运行某个函数
'''
外键字段有以下属性
to: 设置要关联的表
to_field\to_fields: 设置要关联的表的字段
on_delete: 当删除关联表中的数据时,当前表与其关联的行的行为。
models.CASCADE: 删除关联数据,与之关联也删除
多对多三种创建方式
# 全自动(常见)
ORM 自动创建第三张表,但是无法扩展第三张表的字段
authors = models.ManyToManyField(to='Author')
# 全手动(使用频率最低)
优势在于第三张表完全自定义扩展性高,劣势在于无法使用外键方法和正反向
class Book(models.Model):
title = 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')
# 半自动(常见)
正反向还可以使用,并且第三张表可以扩展,唯一的缺陷是不能用add\set\remove\clear四个方法
class Book(models.Model):
title = models.CharField(max_length=32)
authors = models.ManyToManyField(
to='Author', # 绑定的表
through='Book2Author', # 指定关系表,如果不写,ORM会自动创建第三张表
through_fields=('book','author') # 指定自定义关系表中的字段
)
class Author(models.Model):
name = models.CharField(max_length=32)
# '''多对多建在任意一方都可以,如果建在作者表,字段顺序互换即可'''
#books = models.ManyToManyField(
# to='Author',
# through='Book2Author', # 指定表
# through_fields=('author','book') # 指定字段
)
# 创建第三张关系表
class Book2Author(models.Model):
book = models.ForeignKey(to='Book')
author = models.ForeignKey(to='Author')
接下来使用四张表来表示外键的关系,分别是图书表、出版社表、作者表、作者详情表。
'四表关系'
图书表和出版社表是一对多关系,一本书只能有一个出版社,出版社可以有很多书。即出版社是一、书是多。
图书表和作者表是多对多关系,一本书可以有多个作者,一个作者有多本书。即俩者都为多。
作者表和作者详情表是一对一关系,一个作者只能对应一个作者详情,即俩者都为一。
'在models.py中创建四张表'
from django.db import models
class Book(models.Model):
"""图书表"""
title = models.CharField(max_length=32, verbose_name='书名')
# verbose_name 的作用相当于注释
price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='价格')
publish_time = models.DateField(auto_now_add=True, verbose_name='出版日期')
# 书与出版社之间的一对多外键字段publish,默认关联出版社表的主键,也可以使用to_field指定关联的字段
publish = models.ForeignKey(to='Publish')
# 创建表的时候会自动在外键字段后加_id后缀
# 书与作者的多对多外键字段authors,Django会自动创建第三张表来储存关系
authors = models.ManyToManyField(to='Author')
def __str__(self):
return '书籍对象: %s' % self.title
class Publish(models.Model):
"""出版社表"""
name = models.CharField(max_length=32, verbose_name='出版社名称')
addr = models.CharField(max_length=32, verbose_name='出版社地址')
def __str__(self):
return '出版社对象: %s' % self.name
class Author(models.Model):
"""作者表"""
name = models.CharField(max_length=32, verbose_name='姓名')
age = models.IntegerField(verbose_name='年龄')
# 一对一外键关系字段
author_detail = models.OneToOneField(to='AuthorDetail')
def __str__(self):
return '作者对象: %s' % self.name
class AuthorDetail(models.Model):
"""作者详情表"""
phone = models.BigIntegerField(verbose_name='手机号')
addr = models.CharField(max_length=64, verbose_name='家庭地址')
def __str__(self):
return '作者详情对象: %s' % self.phone
ForeignKey、OneToOneField会在字段的后面自动添加_id后缀,所以不要自己加_id后缀
操作外键字段
使用的方法:add()、set()、remove()、clear()
'一对多、一对一外键字段操作'
新增数据
# 1. 直接填写关联数据的键值
models.Book.objects.create(title='聊斋志异', price=167.22, publish_id=1)
# 2. 使用对象的形式添加外键值
publish_obj = models.Publish.objects.filter(pk=2).first()
models.Book.objects.create(title='资本论', price=5677.98, publish=publish_obj)
修改数据
# 1. 直接修改
models.Book.objects.filter(pk=1).update(publish_id=3)
# 2. 使用出版社对象修改
publish_obj = models.Publish.objects.filter(pk=2).first()
models.Book.objects.filter(pk=1).update(publish=publish_obj)
'多对多字段操作'
作者表和图书表存在多对多的关系
添加数据
# 1. 使用书本对象点其虚拟的authors字段来操作虚拟的关系表
book_obj = models.Book.objects.filter(pk=1).first()
# 可以在括号里直接填写主键值来添加关系
book_obj.authors.add(1)
book_obj.authors.add(1, 2)
# 2. 使用作者对象的方式添加
book_obj = models.Book.objects.filter(pk=1).first()
author_obj1 = models.Author.objects.filter(pk=1).first()
author_obj2 = models.Author.objects.filter(pk=2).first()
# 使用作者对象代替主键值
book_obj.authors.add(author_obj1)
book_obj.authors.add(author_obj1,author_obj2)
修改数据
# 1. 书本对象点虚拟字段authors并使用set方法,方法内使用作者表的主键值
book_obj = models.Book.objects.filter(pk=1).first()
# 设置会将原有的所有关系清除,并添加新关系,需要注意的是需要使用可迭代的例如列表的形式
book_obj.authors.set([1, ])
# 可以添加多个
book_obj.authors.set([2, 3])
# 2. 使用作者对象设置,同样需要使用可迭代的例如列表的形式
author_obj1 = models.Author.objects.filter(pk=3).first()
author_obj2 = models.Author.objects.filter(pk=4).first()
book_obj.authors.set([author_obj1, ])
book_obj.authors.set([author_obj1, author_obj2])
移除关系
# 1. 移除指定作者表主键值,可以同时移除多个
book_obj = models.Book.objects.filter(pk=1).first()
book_obj.authors.remove(3)
book_obj.authors.remove(3,4)
# 2. 移除指定作者对象
book_obj = models.Book.objects.filter(pk=1).first()
author_obj1 = models.Author.objects.filter(pk=1).first()
author_obj2 = models.Author.objects.filter(pk=2).first()
book_obj.authors.remove(author_obj1)
book_obj.authors.remove(author_obj1,author_obj2)
清空关系
# 此方法可以情况指定图书对象的所有关系
book_obj = models.Book.objects.filter(pk=1).first()
book_obj.authors.clear()
有外键的一方查询其相关联的一方的方式叫做正向查询,例如上面表中图书表查询其出版社。反向查询与其相反
"""
查询口诀
正向查询按外键字段名
反向查询按表名小写
"""
基于对象的正反向跨表查询
1.获取书籍对应的出版社(正向)
# 先获取书籍对象
book_obj = models.Book.objects.filter(title='数据分析').first()
# 再使用跨表查询,点字段名的形式
res = book_obj.publish
print(res)
2.根据书籍获取对应的作者(正向)
# 先获取书籍对象
book_obj = models.Book.objects.filter(title='python全栈开发').first()
# 再使用跨表查询,由于是多对多的关系,需要使用all()方法
print(book_obj.authors) # app1.Author.None
res = book_obj.authors.all()
print(res)
3.查询作者1的详情信息(正向)
# 先获取作者对象
author_obj = models.Author.objects.filter(name='作者1').first()
# 再使用跨表查询,点字段名
res = author_obj.author_detail
print(res)
4.查询东方出版社出版的书籍(反向)
# 获得出版社对象
publish_obj = models.Publish.objects.filter(name='东方出版社').first()
# 点表名小写需要使用set()方法,并且由于数量多,使用all()方法
print(publish_obj.book_set) # app01.Book.None
res = publish_obj.book_set.all()
print(res)
5.查询作者1编写的书籍(反向,因为创建表时作者表没有虚拟外键字段)
# 获得作者对象
author_obj = models.Author.objects.filter(name='作者1').first()
print(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) # 作者对象:jason
基于双下划线的正反向跨表查询
1.查询数据分析书籍对应的出版社名称和地址(正向)
# 先获取图书对象,使用 外键名__字段名 的形式可以直接获取对应的值,不需要first()方法
res = models.Book.objects.filter(title='数据分析').values('publish__name', 'publish__addr')
print(res) #
2.查询python全栈开发对应的作者姓名和年龄(正向)
# 先获取图书对象,使用 外键名__字段名 的形式可以直接获取对应的值
res = models.Book.objects.filter(title='python全栈开发').values('authors__name','authors__age')
print(res) #
3.查询作者1的手机号和地址(正向)
# 先获取作者对象,使用 外键名__字段名 的形式可以直接获取对应的值
res = models.Author.objects.filter(name='作者1').values('author_detail__phone','author_detail__addr')
print(res)
4.查询东方出版社出版的书籍名称和价格(反向)
# 先获取出版社对象,使用 表名__字段名 的形式可以直接获取对应的值
res = models.Publish.objects.filter(name='东方出版社').values('book__title','book__price')
print(res)
5.查询作者1编写的书籍名称和日期(反向,作者表中没有虚拟外键字段)
# 先获取作者对象,使用 表名__字段名 的形式可以直接获取对应的值
res = models.Author.objects.filter(name='作者1').values('book__title', 'book__publish_time')
print(res)
6.查询电话是110的作者的姓名和年龄(反向)
# 先获取作者对象,使用 表名__字段名 的形式可以直接获取对应的值
res = models.AuthorDetail.objects.filter(phone=110).values('author__name','author__age')
print(res) #
正向查询是外键名__字段名,反向是表名小写__字段名
基于双下划线的查询拓展
除了前面的将已知的条件作为表对象,也可以将未知数值所在的表作为对象
1.查询数据分析书籍对应的出版社名称(正向)
# 获取出版社对象,使用 表名__字段名 的形式来表示已知条件
res = models.Publish.objects.filter(book__title='数据分析')
print(res) # ]>
res = models.Publish.objects.filter(book__title='数据分析').values('name')
print(res) #
2.查询python全栈开发对应的作者姓名和年龄(正向)
# 获取作者表对象
res = models.Author.objects.filter(book__title='python全栈开发').values('name','age')
print(res) #
3.查询作者1的手机号和地址(正向)
# 获取作者详情表对象
res = models.AuthorDetail.objects.filter(author__name='jason').values('phone','addr')
print(res) #
4.查询东方出版社出版的书籍名称和价格(反向)
# 获取图书对象,使用的是 外键名__字段名 的形式表示已知条件进行筛选
res = models.Book.objects.filter(publish__name='东方出版社').values('title','price')
print(res)
5.查询作者1编写的书籍名称和日期(反向)
# 获取图书对象
res = models.Book.objects.filter(authors__name='作者1').values('title','publish_time')
print(res)
6.查询电话是110的作者的姓名和年龄(反向)
# 获取作者对象
res = models.Author.objects.filter(author_detail__phone=110).values('name','age')
print(res)
'多个表连接查询'
1. 查询python全栈开发对应的作者的手机号
# 获取图书对象,使用的形式是 外键名__外键名__字段名
res = models.Book.objects.filter(title='python全栈开发').values('authors__author_detail__phone')
print(res) #
# 以 表名__表名__字段名 的形式作为筛选条件
res1 = models.AuthorDetail.objects.filter(author__book__title='python全栈开发').values('phone')
print(res1) #
方式1:如果结果集对象是queryset,那么可以直接点'query'查看
方式2:配置文件固定配置,适用面更广,只要执行了orm操作 都会打印内部SQL语句
方式1
obj = models.Book.objects.filter(title='书本3').values('publish__addr')
print(obj.query)
方式2
在settings配置文件中添加如下配置,添加完每次执行都会打印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',
},
}
}
聚合查询就类似于SQL语言中的聚合函数例如max,在ORM中使用需要导入django.db.models中的类,然后在使用aggregate方法,aggregate(别名 = 聚合函数名("属性名称"))
导入: from django.db.models import Sum, Min, Count, Max, Avg
继续使用上面的4张表做举例。
1. 查询图书表中价格最高的书籍
print(models.Book.objects.aggregate(max_price=Max('price')))
等价于:SELECT MAX(`app1_book`.`price`) AS `max_price` FROM `app1_book`; args=()
2. 查询最小年纪的作者
print(models.Author.objects.aggregate(min_age=Min('age')))
等价于:SELECT MIN(`app1_author`.`age`) AS `min_age` FROM `app1_author`; args=()
3. 查询所有书籍总价
print(models.Book.objects.aggregate(sum_price=Sum('price')))
等价于:SELECT SUM(`app1_book`.`price`) AS `sum_price` FROM `app1_book`; args=()
4. 查询所有书籍平均价格
print(models.Book.objects.aggregate(avg_price=Avg('price')))
等价于:SELECT AVG(`app1_book`.`price`) AS `avg_price` FROM `app1_book`; args=()
5. 查询出版社的个数
print(models.Publish.objects.aggregate(count_num=Count('pk')))
等价于:SELECT COUNT(`app1_publish`.`id`) AS `count_num` FROM `app1_publish`; args=()
上述都是在未分组的情况下直接查询,一般聚合查询都是配合分组查询一起使用
分组查询类似于SQL中的 group by 分组,使用的方法是annotate。
values 放在 annotate 前面: 用于声明以什么字段分组,annotate 执行分组。
values 放在 annotate 后面: annotate 表示直接以当前表的pk执行分组,values 表示查询哪些字段,并且要
将 annotate 里的聚合函数起别名,在 values 或者 values_list 里写其别名。
1. 统计每本书的作者个数(先对图书表分组,正向查询使用外键字段,并使用count()计数)
print(models.Book.objects.annotate(count_author=Count('authors')).values('title', 'count_author'))
# 等价于:
SELECT
`app1_book`.`title`,
COUNT( `app1_book_authors`.`author_id` ) AS `count_author`
FROM
`app1_book`
LEFT OUTER JOIN `app1_book_authors` ON ( `app1_book`.`id` = `app1_book_authors`.`book_id` )
GROUP BY
`app1_book`.`id`
ORDER BY
NULL
LIMIT 21;
args =()
2. 统计每本书的作者个数,并筛选作者数大于1的图书(第一题的基础上使用filter进行筛选)
print(models.Book.objects.annotate(count_author=Count('authors')).filter(count_author__gt=1).values('title', 'count_author'))
# 等价于:
SELECT
`app1_book`.`title`,
COUNT( `app1_book_authors`.`author_id` ) AS `count_author`
FROM
`app1_book`
LEFT OUTER JOIN `app1_book_authors` ON ( `app1_book`.`id` = `app1_book_authors`.`book_id` )
GROUP BY
`app1_book`.`id`
HAVING
COUNT( `app1_book_authors`.`author_id` ) > 1
ORDER BY
NULL
LIMIT 21;
args =(
1,)
3. 统计每个作者编写书本个数
print(models.Author.objects.annotate(book_num=Count('book__pk')).values('name', 'book_num'))
# 等价于
SELECT
`app1_author`.`name`,
COUNT( `app1_book_authors`.`book_id` ) AS `book_num`
FROM
`app1_author`
LEFT OUTER JOIN `app1_book_authors` ON ( `app1_author`.`id` = `app1_book_authors`.`author_id` )
GROUP BY
`app1_author`.`id`
ORDER BY
NULL
LIMIT 21;
args =()
4. 统计每个出版社卖的最便宜的书的价格
res = models.Publish.objects.annotate(min_price=Min('book__price')).values('name', 'book__title', 'min_price')
for i in res:print(i)
5. 统计每个作者出的书的总价格
print(models.Author.objects.annotate(book_sum=Sum('book__price')).values('name', 'book_sum'))
# 等价于:
SELECT
`app1_author`.`name`,
SUM( `app1_book`.`price` ) AS `book_sum`
FROM
`app1_author`
LEFT OUTER JOIN `app1_book_authors` ON ( `app1_author`.`id` = `app1_book_authors`.`author_id` )
LEFT OUTER JOIN `app1_book` ON ( `app1_book_authors`.`book_id` = `app1_book`.`id` )
GROUP BY
`app1_author`.`id`
ORDER BY
NULL
LIMIT 21;
args =()
6. 统计每个出版社出版的书籍个数
print(models.Publish.objects.annotate(c=Count('book')).values('name', 'c'))
'除了直接对表进行分组,还可以对表的指定字段进行分组,只要将 values 或者 values_list 放在 annotate 前面'
'对表指定字段分组'
1. 统计每本书的作者个数
print(models.Author.objects.values('book').annotate(c=Count('name')).values('book__title', 'c'))
2. 统计每本书的作者个数,并筛选作者数大于1的图书
print(models.Author.objects.values('book').annotate(c=Count('name')).filter(c__gt=1).values('book__title', 'c'))
3. 统计每个作者编写的书本个数
print(models.Book.objects.values('authors').annotate(c=Count('pk')).values('authors__name', 'c'))
4. 统计每个出版社卖的最便宜的书的价格
print(models.Book.objects.values('publish').annotate(m=Min('price')).values('publish__name', 'm'))
5. 统计每个作者出的书的总价格
print(models.Book.objects.values('authors').annotate(s=Sum('price')).values('authors__name', 's'))
6. 获取每个出版社出版的书籍(对书本出版社字段分组,求出分组后每个出版社组别包含的书本个数)
print(models.Book.objects.values('publish').annotate(c=Count('title')).values('publish__name', 'c'))
F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。
之前构造的过滤器都只是将字段值与某个常量做比较,如果想要对两个字段的值做比较,就需要用到 F()。
例如比较图书表中的销售量和库存量,以下方式是不能直接比较的
print(models.Book.objects.filter(book_sale__gt=book_stock))
print(models.Book.objects.filter(book_sale > book_stock))
'F 动态获取对象字段的值,可以进行运算。'
Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取余的操作
修改操作(update)也可以使用 F() 函数
F 在使用前需要导入,导入语句:from django.db.models import F
例子
1. 比较数据,销售量大于库存量
print(models.Book.objects.filter(book_sale__gt=F('book_stock')).values('title', 'book_sale', 'book_stock'))
2. 修改数字字段,所有书本价格增加1000
models.Book.objects.update(price=F('price') + 1000)
3. 修改字符字段,添加字符内容
# 先导入额外方法
from django.db.models.functions import Concat
from django.db.models import Value
# 括号里使用 字段名=Concat(F('字段名'), Value('添加的内容'))
models.Book.objects.update(title=Concat(F('title'), Value('爆款')))
之前构造的过滤器里的多个条件的关系都是 and,如果需要执行更复杂的查询(例如 or 语句),就可以使用 Q。Q 对象可以使用 &(与)
、|(或)
、 ~ (非)
操作符进行组合
使用前要先从 django.db.models 引入 Q
引入语句:from django.db.models import Q
用法:
Q(条件判断)
1. 逗号也表示与的意思
print(models.Book.objects.filter(Q(price__gt=1200), Q(price__lt=1500)).values('title', 'price'))
2. & 表示与
print(models.Book.objects.filter(Q(price__gt=1200) & Q(price__lt=1500)).values('title', 'price'))
3. | 表示或的意思
print(models.Book.objects.filter(Q(price__gt=1400) | Q(price__lt=1200)).values('title', 'price'))
4. 在前面使用 ~ 表示非,取反的意思
print(models.Book.objects.filter(~Q(price__gt=1200)).values('title', 'price'))
'Q 的进阶用法'
适用于当需要编写一个搜索功能,并且条件是由用户指定,这个时候条件数据就是一个字符串
# Q 其实是一个类
q_obj = Q()
# 修改连接方式
q_obj.connector = 'or' # 默认是and 可以改为or
q_obj.children.append(('price__gt', 1400)) # append参数只能为一个
q_obj.children.append(('price__lt', 1200))
# 将Q对象当作条件来筛选
res = models.Book.objects.filter(q_obj)
print(res.values('title', 'price'))
print(res.query)
1. ORM查询默认都是惰性查询(能不消耗数据库资源就不消耗),光编写orm语句并不会直接执行SQL语句,只有后续的代码
用到了才会执行
2. ORM查询默认自带分页功能(尽量减轻单次查询数据的压力)
only会产生对象结果集,对象点括号内出现的字段不会再走数据库查询,也就是提前查询好了,但是如果点击了括号内没有的字段也可以获取到数据,但是每次都会走数据库查询
示例
res = models.Book.objects.only('title', 'price')
for obj in res:
print(obj.title) # 不执行查询语句
print(obj.price) # 不执行查询语句
print(obj.publish_time) # 执行查询语句
defer与only刚好相反,对象点括号内出现的字段会走数据库查询,如果点击了括号内没有的字段也可以获取到数据,且每次都不会走数据库查询
示例
res = models.Book.objects.defer('title')
for obj in res:
print(obj.title) # 执行查询语句
print(obj.price) # 不执行查询语句
print(obj.publish_time) # 不执行查询语句
'select_related':表之间进行join连表操作,一次性获取关联的数据,后续对象通过正反向查询跨表,内部不会再走
数据库查询
1. select_related主要针一对一和多对一关系进行优化,'多对多关系不可以'
2. select_related使用SQL的JOIN语句进行优化,通过减少SQL查询的次数来进行优化、提高性能。
示例
# 通过publish字段将book表和publish表相连,多对多关系字段会报错
res = models.Book.objects.select_related('publish')
for obj in res:
print(obj.title)
# 也可以直接点publish表的字段
print(obj.publish.name)
print(obj.publish.addr)
'prefetch_related':子查询的方式,使用其执行多次SQL查询在Python代码中实现连表操作。
1. 对于多对多字段(ManyToManyField)和一对多字段,可以使用prefetch_related()来进行优化。
2. prefetch_related()的优化方式是分别查询每个表,然后用Python处理他们之间的关系。
示例
# 使用多对多外键字段 authors 进行子查询连接
res = models.Book.objects.prefetch_related('authors')
for obj in res:
print(obj.title)
print(obj.authors) # app1.Author.None
print(obj.authors.all()) # ]>
print(obj.authors.all().values('name', 'age'))
同样使用ORM也可以进行事务的操作,也具有原子性、一致性、独立性、持久性
在 SQL 中事务的操作关键语句
start transcation; # 开启事务
rollback; # 事务回滚
commit; # 事务提交确认
ORM 操作事务
# 导入方法
from django.db import transaction
try:
# 开启事务
with transaction.atomic():
pass
except Exception:
pass
'ORM操作事务不需要自己确认,事务出错会自动回滚,正确则自动确认。'
# 方式1
from django.db import connection, connections
cursor = connection.cursor()
# cursor = connections['default'].cursor() 配置
cursor.execute("""SELECT * from auth_user where id = %s""", [1])
cursor.fetchone()
# 方式2
models.UserInfo.objects.extra(
select={'newid':'select count(1) from app01_usertype where id>%s'},
select_params=[1,],
where = ['age>%s'],
params=[18,],
order_by=['-age'],
tables=['app01_usertype']
)
由于在Django中不能单独执行某一个py文件,所以如果只是想测试 ORM 操作且不基于网络的话,可以搭建一个测试环境。
'手动搭建'
from django.test import TestCase
import os
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangoProject_group.settings")
import django
django.setup()
# 测试内容
from app01 import models
data = models.Teacher.objects.filter()
print(data)
'pycharm'
在 pycharm 提供的 python console 中测试
每一个Web框架都需要一种便利的方法用于动态渲染HTML页面。 最常见的做法是使用模板系统。模板包含所需HTML页面的静态部分,以及一些特殊的模版语法,用于将动态内容插入其中。
'注释补充'
<!----> 是HTML的注释语法
{##} 是django模板语法的注释
HTML的注释可以在前端浏览器页面上直接查看到
模板语法的注释只能在后端查看 前端浏览器查看不了
Django自带 DTL(Django Template Language )的模板语言,以及另外流行的 Jinja2 语言
文件介绍
在Django中使用'templates'文件用于存放模板文件,使用'static'文件夹存放静态文件。
1. 需要注意的是,用pycharm创建的Django项目会自动添加templates文件夹,但是使用命令行的方式来创建的Django
项目是没有templates文件夹的需要手动创建。
2. static文件夹一般储存不经常改动的静态文件,例如css文件、js文件、图片文件、第三方框架文件(bootstrap)等等
所以在static文件夹中可以继续创建例如css、js、img等等的文件夹分类存储。
配置
创建完templates文件夹后需要在settings配置文件中进行配置,在settings文件中以下代码修改
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
# 修改的地方,拼接了templates路径
'DIRS': [BASE_DIR, 'templates'],
#或者使用此语句形式: 'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
1. 如果是pycharm创建的Django项目是已经设置好了的,但是有可能会出错,是路径连接的错误,其会将/作为路径连接符
只需要修改成上面的语句样式即可。使用命令行创建的项目需要手动设置。
还需要在settings文件中给static文件夹开设一个接口,在settings文件中以下代码修改
STATIC_URL = '/static/'
# 修改位置
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
os.path.join(BASE_DIR, 'static_a')
]
1. STATIC_URL 中的'/static/'代表的是接口前缀,表示具备访问静态文件资源的权限。而STATICFILES_DIRS列表内
部存放的是static文件夹等,若是前缀匹配上了就可以去该文件夹中比对静态文件。列表内文件可以有多个,匹配的顺
序是从上到下。
2. 例如在HTML文件中有这样一个link:
<link rel="stylesheet" href="/static/bootstrap-3.4.1-dist/css/bootstrap.min.css">
虽然我们引用是的本地文件,但是浏览器需要根据网络请求该文件,也就是说其发送了类似以下请求:
http://127.0.0.1:8000/static/bootstrap-3.4.1-dist/css/bootstrap.min.css
link中的static就是路由,若是和STATIC_URL匹配上,即可查找静态文件
接口前缀动态绑定
假如说我们修改了接口前缀,如果有很多个HTML文件中那么修改link标签会很困难,需要如下示例动态的绑定link标签了
{% load static %}
<link rel="stylesheet" href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.min.css' %}">
现在修改STATIC_URL也不会出问题。
模板语法一
{{}}:主要用于变量相关操作(引用)
{{}}语法可以传递基本数据类型但是不支持有参函数,文件显示io对象,类自动加括号实例化成对象
传入方式一
后端传入: {'自定义名称': 变量} # 将单个变量指名道姓的形式传递给html页面,适合数据较少时使用
前端接收: {{ 自定义名称 }}
传入方式二
后端传入: locals() # 将当前名称空间中所有的变量名传递给html页面,适合数据较多时使用
前端接收: {{ 真实变量名 }} # 若有类似于变量嵌套的情况也可以使用点的方式获取,可以点名称或索引
示例
return render(request, 'Mylogin.html', {'name': name})
return render(request, 'Mylogin.html', locals())
过滤器的作用就像是内置函数一样,有其独特的功能,语法结构如下所示
{{ 数据对象|过滤器名称:参数 }}
需要注意的是过滤器一次只能使用一个参数
过滤器 | 说明 |
---|---|
add | 加法 |
addslashes | 添加斜杠 |
capfirst | 首字母大写 |
center | 文本居中 |
cut | 切除字符 |
date | 日期格式化 |
default | 设置默认值 |
default_if_none | 为None设置默认值 |
dictsort | 字典排序 |
dictsortreversed | 字典反向排序 |
divisibleby | 整除判断 |
escape | 转义 |
escapejs | 转义js代码 |
filesizeformat | 文件计量单位人性化显示 |
first | 第一个元素 |
floatformat | 浮点数格式化 |
force_escape | 强制立刻转义 |
get_digit | 获取数字 |
iriencode | 转换IRI |
join | 字符列表链接 |
json_script | 生成script标签,带json数据 |
last | 最后一个 |
length | 长度 |
length_is | 长度等于 |
linebreaks | 行转换 |
linebreaksbr | 行转换 |
linenumbers | 行号 |
ljust | 左对齐 |
lower | 小写 |
make_list | 分割成字符列表 |
phone2numeric | 电话号码 |
pluralize | 复数形式 |
pprint | 调试 |
random | 随机获取 |
rjust | 右对齐 |
safe | 安全确认 |
safeseq | 列表安全确认 |
slice | 切片 |
slugify | 转换成ASCII |
stringformat | 字符串格式化 |
striptags | 去除HTML中的标签 |
time | 时间格式化 |
timesince | 从何时开始 |
timeuntil | 到何时多久 |
title | 所有单词首字母大写 |
truncatechars | 截断字符 |
truncatechars_html | 截断字符 |
truncatewords | 截断单词 |
truncatewords_html | 截断单词 |
unordered_list | 无序列表 |
upper | 大写 |
urlencode | 转义url |
urlize | url转成可点击的链接 |
urlizetrunc | urlize的截断方式 |
wordcount | 单词计数 |
wordwrap | 单词包裹 |
yesno | 将True,False和None,映射成字符串‘yes’,‘no’,‘maybe’ |
'length'
str1 = 'xwx'
<p>{{ str1|length }}</p>
获取数据的长度
'add'
str2 = 'xxx'
<p>{{ str2|add:'yyy' }}</p>
将指定的值添加到变量,自动判断类型
'filesizeformat'
int1 = 12345
<p>{{ int1|filesizeformat }}</p>
将数字转成合适的文件计量单位,例如12345会转成12.1 KB
'default'
bool1 = False
<p>{{ bool1|default:'此变量为Fasle' }}</p>
设置默认值,只有当变量为False时值显示默认的值
'date'
time1 = datetime.datetime.today()
<p>{{ time1|date:"Y-m-d H:i:s"}}</p>
将传递的时间数据对象转为指定格式,详细格式如下表格
格式化字符 | 描述 | 示例输出 |
---|---|---|
a | 'a.m.‘或’p.m.’(请注意,这与PHP的输出略有不同,因为这包括符合Associated Press风格的期间) | ‘a.m.’ |
A | ‘AM’或’PM’。 | ‘AM’ |
b | 月,文字,3个字母,小写。 | ‘jan’ |
B | 未实现。 | |
c | ISO 8601格式。 | ‘2022-05-16T15:36:22.637334’ |
d | 月的日子,带前导零的2位数字。 | ‘01’到’31’ |
D | 一周中的文字,3个字母。 | “星期五” |
e | 时区名称 可能是任何格式,或者可能返回一个空字符串,具体取决于datetime。 | ‘’、‘GMT’、'US/Eastern’等 |
E | 月份,特定地区的替代表示通常用于长日期表示。 | ‘listopada’(对于波兰语区域,而不是’Listopad’) |
f | 时间,在12小时的小时和分钟内,如果它们为零,则分钟停留。 专有扩展。 | ‘1’,‘1:30’ |
F | 月,文,长。 | ‘一月’ |
g | 小时,12小时格式,无前导零。 | ‘1’到’12’ |
G | 小时,24小时格式,无前导零。 | ‘0’到’23’ |
h | 小时,12小时格式。 | ‘01’到’12’ |
H | 小时,24小时格式。 | ‘00’到’23’ |
i | 分钟。 | ‘00’到’59’ |
I | 夏令时间,无论是否生效。 | ‘1’或’0’ |
j | 没有前导零的月份的日子。 | ‘1’到’31’ |
l | 星期几,文字长。 | ‘星期五’ |
L | 布尔值是否是一个闰年。 | True或False |
m | 月,2位数字带前导零。 | ‘01’到’12’ |
M | 月,文字,3个字母。 | “扬” |
n | 月无前导零。 | ‘1’到’12’ |
N | 美联社风格的月份缩写。 专有扩展。 | ‘Jan.’,‘Feb.’,‘March’,‘May’ |
o | ISO-8601周编号,对应于使用闰年的ISO-8601周数(W)。 对于更常见的年份格式,请参见Y。 | ‘1999年’ |
O | 与格林威治时间的差异在几小时内。 | ‘+0200’ |
P | 时间为12小时,分钟和’a.m。'/‘p.m。’,如果为零,分钟停留,特殊情况下的字符串“午夜”和“中午”。 专有扩展。 | ‘1 am’,‘1:30 pm’ |
r | RFC 5322格式化日期。 | ‘Thu, 21 Dec 2000 16:01:07 +0200’ |
s | 秒,带前导零的2位数字。 | ‘00’到’59’ |
S | 一个月的英文序数后缀,2个字符。 | ‘st’,‘nd’,‘rd’或’th’ |
t | 给定月份的天数。 | 28 to 31 |
T | 本机的时区。 | ‘EST’,‘MDT’ |
u | 微秒。 | 000000 to 999999 |
U | 自Unix Epoch以来的二分之一(1970年1月1日00:00:00 UTC)。 | |
w | 星期几,数字无前导零。 | ‘0’(星期日)至’6’(星期六) |
W | ISO-8601周数,周数从星期一开始。 | 1,53 |
y | 年份,2位数字。 | ‘99’ |
Y | 年,4位数。 | ‘1999年’ |
z | 一年中的日子 | 0到365 |
Z | 时区偏移量,单位为秒。 UTC以西时区的偏移量总是为负数,对于UTC以东时,它们总是为正。 | -43200到43200 |
'slice'
str3 = 'XWenXiang'
<p>{{ str3|slice:'0:5' }}</p>
对数据进行索引切片
'truncatewords'
str4 = '今 天的天 气 真好, 适合 去公 园散 步'
<p>{{ str4|truncatewords:5 }}</p>
按照空格截取指定个数的文本,例如例子中就是第五个空格后的文本以省略号代替,即:今 天的天 气 真好, 适合 ...
'truncatechars'
str5 = '今天的天气真好,适合去公园散步'
<p>{{ str5|truncatechars:5 }}</p>
按照字符个数截取文本,也是以省略号代替,需要注意的是省略号占据3个位置,例如示例中的结果为:今天...
'cut'
str6 = '今|天|的|天|气|真|好,适|合|去|公|园|散|步'
<p>{{ str6|cut:'|' }}</p>
对数据按照指定的字符进行切割,上面的结果是:今天的天气真好,适合去公园散步
'safe'
str7 = "这是一个链接"
<p>{{ str7|safe }}</p>
用于安全确认,例如在模板传递HTML代码时,是不会直接显示其效果,需要对传入的内容进行安全确认,也就是将值标记
为不应自动转义的字符串,如上示例
# views.py文件
str8 = "这是一个链接"
from django.utils.safestring import mark_safe
str8 = mark_safe(str8)
# html文件
<p>{{ str8 }}</p>
或者在后端中确认,如上示例
模板语法之标签
标签语法
{%%}:主要用于逻辑相关操作(循环、判断)
标签 | 说明 |
---|---|
autoescape | 自动转义开关 |
block | 块引用 |
comment | 注释 |
csrf_token | CSRF令牌 |
cycle | 循环对象的值 |
debug | 调试模式 |
extends | 继承模版 |
filter | 过滤功能 |
firstof | 输出第一个不为False的参数 |
for | 循环对象 |
for … empty | 带empty说明的循环 |
if | 条件判断 |
ifchanged | 如果有变化,则… |
include | 导入子模版的内容 |
load | 加载标签和过滤器 |
lorem | 生成无用的废话 |
now | 当前时间 |
regroup | 根据对象重组集合 |
resetcycle | 重置循环 |
static | 用于链接保存在STATIC_ROOT中的静态文件 |
spaceless | 去除空白 |
templatetag | 转义模版标签符号 |
url | 获取url字符串 |
verbatim | 禁用模版引擎 |
widthratio | 宽度比例 |
with | 上下文变量管理器 |
if标签结构
{% if condition1 %}
... display 1
{% elif condition2 %}
... display 2
{% else %}
... display 3
{% endif %}
for标签结构
{% for i in list %}
{{ i }}
{% empty %}
空空如也~
{% endfor %}
可选的 {% empty %} 从句:在循环为空的时候执行(即 in 后面的参数布尔值为 False )。
for标签还提供了一个'forloop'关键字,其结构如下
{'parentloop': {}, 'counter0': 0, 'counter': 1, 'revcounter': 5, 'revcounter0': 4, 'first': True, 'last': False}
我们可以在for中使用其first、last方法,如下示例
{% for i in list %}
{% if forloop.first %}
<p>这是第一次循环</p>
{% elif forloop.last %}
<p>这是最后一次循环</p>
{% else %}
<p>中间循环</p>
{% endif %}
{% empty %}
<p>for循环对象为空 自动执行</p>
{% endfor %}
Variable | Description |
---|---|
forloop.counter | 当前循环的索引值(从1开始) |
forloop.counter0 | 当前循环的索引值(从0开始) |
forloop.revcounter | 当前循环的倒序索引值(从1开始) |
forloop.revcounter0 | 当前循环的倒序索引值(从0开始) |
forloop.first | 当前循环是不是第一次循环(布尔值) |
forloop.last | 当前循环是不是最后一次循环(布尔值) |
forloop.parentloop | 本层循环的外层循环 |
'include'
{% include 'two.html' %}
{% include %} 标签允许在模板中包含其它的模板的内容,我们可以将比较常用的页面代码例如导航条等做成独立的模板,
然后在其他模板中使用include标签引入即可,并且还可以多次引入
准备操作
1. 第一步,在app中新建一个'templatetags'文件夹,名字固定只能是这个
2. 在该文件夹内创建一个任意名称的py文件,此时的文件结构类似于
app01/
__init__.py
models.py
templatetags/
# 可有可无
__init__.py
custom.py
views.p
3. 在该py文件内需要先提前编写两行固定的代码,如下所示
from django import template
register = template.Library()
编写自定义过滤器
需要使用到装饰器 '@register.filter',按照上面的文件结构编写自定义过滤器,如下所示
custom.py文件
# 固定语句
from django import template
register = template.Library()
# 在装饰器中加入参数is_safe=True,也可以起别名 name='xxx',起了别名过滤器的名字也会变
@register.filter(is_safe=True)
def index(a, b):
return a + b
html文件
# 需要引入该文件
{% load custom %}
# 使用自定义筛选器
{{ str|index:666 }}
'注意:使用了装饰器的函数参数最多只能有 2 个'
自定义标签的准备工作也是和自定义筛选器一样,使用的方法如下所示
custom.py文件
from django import template
register = template.Library()
@register.simple_tag(name='my_tag')
def index(a, b, c, d):
return a + b + c + d
html文件
# 需要引入该文件
{% load custom %}
# 使用自定义标签
{% my_tag 1 2 3 5 %}
自定义标签可以传入多个参数,空格隔开即可
这个和自定义标签不一样的地方是其通过渲染一个模板来显示一些数据,如下示例
custom.py文件
from django import template
register = template.Library()
# 装饰器指向 index.html
@register.inclusion_tag('index.html')
def func(n):
l1 = []
for i in range(1, n + 1):
l1.append(f'第{i}页')
return locals()
渲染用的模板文件 index.html
<ul>
{% for i in l1 %}
<li>{{ i }}</li>
{% endfor %}
</ul>
使用自定义inclusion_tag的模板文件
{% load custom %}
{% func 5 %}
流程类似于下面的图片
类似于面向对象的继承,继承了某个页面就可以使用该页面上所有的资源,使用的标签是 {% extends %}
,{% block %}
简略使用方式
'母版内容'
# 指定块区域
{% block center %}
<p>这是母版的内容</p>
{% endblock %}
'子版内容'
# 继承母版所有内容
{% extends 'home.html' %}
# 修改指定块区域内容
{% block center %}
<p>这是子版内容</p>
# 重新调用母版内容,可重复使用
{{ block.super }}
{{ block.super }}
{{ block.super }}
{% endblock %}
'''
模板上最少应该有三个区域
css区域、内容区域、js区域
这样子页面就可以有自己独立的css、js、内容
'''
Django 中的ulers.py文件又被称之为路由层,用于表示视图函数和路由的关系
'Django1.1.x 版本的ulrs.py文件'
from django.conf.urls import url
from django.contrib import admin
from . import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^login/', views.login),
]
1. Django2.1.x 以上的ulrs.py文件使用的是path,其不需要自己手动添加正则首位限制符号
2. 上面的admin和login代表的是路由,在浏览器输入地址和端口号后在输入admin或login会有不同的页面内容,其中
admin指向的是Django自带的后台管理。而views.login代表的就是视图函数了,当用户输入的路由被匹配上,那么
执行该视图函数。
3. 例如 http://127.0.0.1:8000/index/ 匹配的路由就是index
我们发现是通过正则表达式来匹配路由,那么正则表达式写了什么就能匹配什么路由。
正则表达式 作用
1. test 只写test的话虽然能匹配到test,但是也能匹配到例如testet等等,不可取
2. test/ 加了/后能匹配到的是test/,并且test也能被匹配到,因为Django有二次匹配的机制,其默认在浏览
器后缀自动添加/并再一次匹配。也可以设置默认不添加斜杠,再配置中加入APPEND_SLASH = False
3. ^test/ 此时只能匹配以test/开头的路由
4. ^test/$ 只能匹配test/
5. ^$ 默认展示,没有后缀也能访问
6. .* 不管是什么路由都可以访问
'路由匹配成功之后就会调用视图函数默认情况下会自动给视图函数传递一个request位置参数'
在编写正则表达式的时候可以添加括号来进行分组,并根据其有没有别名分成无名或有名分组。
'无名分组'
1. 无名分组是在给正则表达式分组时不加别名,如下所示
# urls.py文件中内容
urlpatterns = [
url(r'^test/(\d+)', views.test),
]
# views.py文件中内容
def test(request, res):
# 打印参数
print('res:', res)
return HttpResponse('from test')
按照正则表达式我们应该能访问后缀类似于test/123的内容,但是浏览器此时会报错,并提示此时给出了俩个参数,当
然其中一个参数是默认的request位置参数,另外一个参数我们可以在视图函数中接收并打印查看其内容,发现此参数的
值就是后缀中的组内匹配的内容,例如示例中test/123后缀中的123。
2. 也可以分多个组,如下示例
# urls.py文件中内容
urlpatterns = [
url(r'^test/([a-z])([1-9])', views.test),
]
# views.py文件中内容
def test(request, *args):
print('args:', args)
return HttpResponse('from test')
多个组别,就会传递多个参数,可以直接使用 *args 来接收所有的位置参数
'有名分组'
有名分组就是在使用括号分组的时候添加别名,添加别名的方式是 (?P<别名>正则表达式)
1. 如下所示
# urls.py文件中内容
urlpatterns = [
url(r'^test/(?P\d+)' , views.test),
]
# views.py文件中内容
def test(request, tname):
print('tname:', tname)
return HttpResponse('from test')
同样的,分组之后也会传入参数,并且这个参数是用组别名作为关键字参数的形式传入。
2. 多个有名分组情况
# urls.py文件中内容
urlpatterns = [
url(r'^test/(?P[a-z])/(?P[0-9])' , views.test),
]
# views.py文件中内容
def test(request, **kwargs):
print('kwargs:', kwargs)
return HttpResponse('from test')
多个有名分组会传递多个关键字参数,可以使用 **kwargs 接收
'有名分组和无名分组不能混用,只能单种同时使用'
我们知道了正则表达式表示的是路由的匹配规则,假如说将正则表达式修改了呢,也就是说修改完之后不能通过原本的路由访问和执行视图函数了。比较常见的就是HTML的链接失效,原本是写死的路由链接,改了正则就会失效。而使用反向解析可以动态的获取到一个路由,无需考虑正则的变化
1. 使用的方法就是在urls.py文件中绑定视图函数和路由正则表达式的时候起一个别名,如下示例
# urls.py文件中内容
urlpatterns = [
url(r'^regist/', views.register, name='reg'),
]
使用别名可以在前端和后端均可动态的获取到正则匹配的路由
2. HTML反向解析路由
使用的方式是 {% url '别名' %} ,如下示例
# HTML内容
<a href="{% url 'reg' %}">注册</a>
3. 视图函数动态获取路由
需要导入一个模块,导入语句: from django.shortcuts import reverse
# reverse('别名')
print(reverse('reg'))
该方法就可以动态返回出该别名对应的路由
无名分组反向解析
无名分组也可以反向解析,在前面说,进行无名分组后会多出位置参数,而这个位置参数需要我们人为指定才能反向解析
1. HTML反向解析
# {% url 别名 手动添加内容 %}
<a href="{% url 'reg' 123 %}">注册</a>
数字123就是我们指定的内容,此时路由就是 .../register/123,不过别忘了分组之后视图函数需要额外的形参
2. 视图函数反向解析
def register(request, *args):
print(*args)
print(reverse('reg', args=(123,)))
return HttpResponse('from register')
或者不使用*args,定义多个位置形参,将其放入args=()括号中也行
有名分组反向解析
有名分组之后视图函数需要接收关键字参数才行,同样的反向解析也需要手动添加内容
1. HTML反向解析
# {% url '关系别名' 正则组名='指定的后缀' %}
<a href="{% url 'reg' group_name='register' %}">注册</a>
2. 视图函数反向解析
def register(request, **kwargs):
print(kwargs)
print(reverse('reg', kwargs={'group_name': 'register', }))
return HttpResponse('from register')
使用**kwargs接收所有关键字参数,并在reverse方法中kwargs=({})的括号里添加键值对即可。
或者使用args()的方式也可以,不过形参还得是kwaegs。
由于整个项目比较大,因此路由层中会有很多内容,不方便管理。其实django支持每个应用都可以有自己独立的路由层、模
板层、静态文件、视图层(默认)、模型层(默认)。这样分组开发会更加清晰,但是每个应用都有一个urls文件,就需要用到
路由分发的方式整合了。
'总路由文件'
# 路由分发
from django.conf.urls import url, include
from django.contrib import admin
from app01 import urls as app01_urls
from app02 import urls as app02_urls
"""总路由只负责分发,不负责视图函数对应,子路由负责匹配对应"""
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^app01/', include(app01_urls)),
url(r'^app02/', include(app02_urls)),
]
# 简写方法
from django.conf.urls import url, include
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^app01/', include('app01.urls')),
url(r'^app02/', include('app02.urls')),
]
'''总路由的正则表达式不能使用 $ 结尾'''
但是还有一种情况就是不同的应用下的路由关系有着相同的别名,也就是别名冲突了,别名原本是为了反向解析用的,如果
冲突了就失去了原有的作用,我们可以使用名称空间的方法解决。
'名称空间解决方法'
在总路由中添加名称空间,如下示例
'总路由'
from django.conf.urls import url, include
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
# namespace用于指定名称空间,每个应用都有名称空间
url(r'^app01/', include('app01.urls', namespace='app01')),
url(r'^app02/', include('app02.urls', namespace='app02')),
]
'app01 子路由'
from django.conf.urls import url
from app01 import views
urlpatterns = [
# 相同的别名app
url(r'^index/', views.index, name='app'),
]
'app02 子路由'
from django.conf.urls import url
from app02 import views
urlpatterns = [
# 相同的别名app
url(r'^index/', views.index, name='app'),
]
'app01 views'
from django.shortcuts import render, HttpResponse, reverse
def index(request):
# 使用了名称空间之后会自动提示名称空间中的别名
print('from app01', reverse('app01:app'))
return HttpResponse('from app01 views')
'app02 views'
from django.shortcuts import render, HttpResponse, reverse
def index(request):
print('from app02', reverse('app02:app'))
return HttpResponse('from app02 views')
但其实最好的解决方式就是让别名不要冲突。
django2.x、3.x版本和1.x版本的路由层有些许区别
1. 路由匹配的方法不一样,新版本使用的是path方法,其不支持正则,使用方式如下
urlpatterns = [
path('admin/', admin.site.urls),
]
2. path也是一个路径转换器,他可以表示不同的数据类型,有5种转换类型
'str': 匹配任何非空字符串,但不含斜杠/,如果你没有专门指定转换器,默认使用该转换器
'int': 匹配0和正整数,返回一个int类型
'slug':可理解为注释、后缀、附属等概念,是url拖在最后的一部分解释性字符。该转换器匹配任何ASCII字符以及连接符和下划线,比如building-your-1st-django-site;
'uuid':匹配一个uuid格式的对象。为了防止冲突,规定必须使用破折号,所有字母必须小写,例如075194d3-6885-417e-a8a8-6c931e272f00。返回一个UUID对象;
'path':匹配任何非空字符串,重点是可以包含路径分隔符’/‘。这个转换器可以帮助你匹配整个url而不是一段一段的url字符串。
使用示例
urlpatterns = [
path('login/' , views.login),
path('register/' , views.register),
]
会匹配对应位置的数据并且自动转换类型,且会传入关键字参数
3. 新版本也能兼容旧版本的正则表达式,不过使用的是re_path,该方法相当于url(),如下示例
urlpatterns = [
path('login/', views.login),
re_path(r'^index/([0-9]{2})', views.index),
re_path(r'^register/(?P\d+)/$' , views.register),
]
视图层是Django处理请求的核心代码层,其可以调用模型层并根据需求对数据进行处理,并调用模板层将处理好的数据和模板结合展示给用户。
在Django项目中在'views.py'文件里编写视图函数,在' urls.py '文件中表示路由和视图函数之间的关系
'views.py文件'
from django.shortcuts import render, HttpResponse, redirect
# 创建视图函数
def login(request):
return render(request, 'login.html')
1. 视图函数必须有一个返回值,其返回的内容就是浏览器展示的内容
2. 视图函数必须要有一个request位置参数,用于接收请求相关数据。
常见视图函数返回值
3. HttpResponse
主要用于返回字符串类型的数据
示例: return HttpResponse('hello')
其本身属于一个类,结构类似于:class HttpResponse(...):pass
4. render
主要用于返回html文件 并且支持模板语法(django自己写的)
示例: return render(request, 'login.html')
其本身是一个函数,但是最后返回的还是HttpResponse类,大致结构如下:
def render(...):
return HttpResponse(...)
5. redirect
主要用于重定向,括号内可以写其他网站的全称,也可以自己网站的后缀
示例: return redirect('http://baidu.com')
return redirect('/register/')
其是一个类,但是通过继承多个类,最终继承的也是HttpResponse类。
HttpRequest 对象简称为 request 对象,每当一个用户请求发送过来,Django将HTTP数据包中的相关内容,打包成为一个HttpRequest对象,并传递给视图函数作为第一位置参数,也就是request
'request'方法
1. request.method 获得请求方法的大写字符串
2. request.path 表示当前请求页面的完整路径,但是不包括协议名和域名。例如:'/login/'。
3. request.path_info 获取当前路径
4. request.get_full_path() 返回包含完整参数列表的path。例如:/music/bands/the_beatles/?print=true
5. request.scheme 表示请求的协议种类,'http'或'https'
6. request.GET 一个类似于字典的对象,包含GET请求中的所有参数
7. request.POST 包含所有POST表单数据的键值对字典
8. request.body bytes类型,表示原始HTTP请求的正文。GET、POST可以看作是从bodey中拿数据
9. request.FILES 一个类似于字典的对象,包含所有上传的文件数据
10. request.META['REMOTE_ADDR'] 用户访问的IP
'''
form表单上传的数据中如果含有文件,有以下注意事项
1.method必须是post
2.enctype必须修改为 multipart/form-data(默认是 application/x-www-form-urlencoded)
3.后端需要使用request.FILES获取(django会根据数据类型的不同自动帮你封装到不同的方法中)
'''
CBV:基于类的视图
FBV:基于函数的视图
除了上面使用的函数的形式,还可以使用类来实现功能
使用方式
'views.py'
# 定义一个继承views.View的类
class Mylogin(views.View):
# 创建方法,方法名为get、post等等且必须有request参数
def get(self, request):
return HttpResponse('from get')
'ulrs.py'
urlpatterns = [
# 以类名.as_view()绑定匹配的路由
url(r'^MyLogin/', views.Mylogin.as_view()),
]
在类中定义的get方法会在用户以get方式访问路由时候执行。
'CBV源码结构简易解析'
1. 首先在绑定关系的时候是用类名.as_views()的形式,很明显在我们自己定义的类中并没有as_views方法,按照名称的
查找顺序,接下来会去其继承的类中查找,查看源码发现继承的View类中有as_views方法,并且在as_views方法中使用了
闭包嵌套了一个方法view,如下简易结构:
class View(object):
# 略
@classonlymethod
def as_view(cls, **initkwargs):
# 略
def view(request, *args, **kwargs):
# 略
return view
2. 在上面的结构简介中可以看到as_views方法的返回值是view方法名。由于在绑定关系的时候在as_views方法后加括号
调用了,由于调用优先级较高,所以实际上绑定的关系是: url(r'^MyLogin/', views.view)。所以CBV与FBV在路由匹
配本质是一样的都是绑定的函数。此时访问该路由时执行的时执行的是view方法,其源码内容如下:
def view(request, *args, **kwargs):
# self 是自己写的类实例化对象
self = cls(**initkwargs)
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.request = request
self.args = args
self.kwargs = kwargs
# 返回的值是dispatch方法的执行结果
return self.dispatch(request, *args, **kwargs)
3. 主要看其返回的值,是dispatch方法的结果,我们可以点击查看dispatch方法的源码,如下所示:
def dispatch(self, request, *args, **kwargs):
# 判断其请求的方式是否存在,http_method_names是View类中的列表,包含各种请求方式的小写
if request.method.lower() in self.http_method_names:
# getattr(类对象,获取的属性,指定属性不存在时使用的参数)
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
# 如果不存在报405警告
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
4. 假设此时是get请求,已经符合了if判断,此时handler的值就是类中的get方法,且dispatch的返回值是handler()
也就是将自己写的类中的get方法加括号调用了。所以实现了get的请求执行类中get方法。
用来对象dump成json字符串,然后返回将json字符串封装成Response对象返回给浏览器。并且他的Content-Type是application/json。
示例
# 转换字典类型
def json_view(request):
info_dict = {'name': 'xxx', 'age': 21}
# json_str = json.dumps(info_dict)
# resp = HttpResponse(json_str, content_type='application/json')
resp = JsonResponse(info_dict)
return resp
# 转换非字典类型时,需要添加 safe=False
def json_view(request):
info_list = ['name', 'age']
resp = JsonResponse(info_list, safe=False)
return resp
# 有中文情况下需要添加 json_dumps_params={'ensure_ascii': False}
def json_view(request):
info_dict = {'name': '你好', 'age': 21}
# json_str = json.dumps(info_dict, ensure_ascii=False)
# resp = HttpResponse(json_str, content_type='application/json')
resp = JsonResponse(info_dict, json_dumps_params={'ensure_ascii': False})
return resp
class JsonResponse(HttpResponse):
def __init__(self, data,json_dumps_params=None, **kwargs):
data = json.dumps(data, **json_dumps_params)
"""为什么使用JsonResponse还不是原始的json模块"""
django对json序列化的数据类型的范围做了扩充