Django之模型层
数据准备
models.py
class User(models.Model):
uid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
age = models.IntegerField()
register_time = models.DateField(auto_now_add=True) # 年月日
# auto_now_add 表示数据被创建出来的时间(不变)
op_time = models.DateField(auto_now=True)
# auto_now 表示数被修改的最后时间(会变)
# 对象被执行打印操作自动触发,该代码跟数据库没有关系,无需重新执行迁移命令
def __str__(self):
return '对象:%s' % self.name
配置并连接MySQL数据库,然后执行两条数据库迁移命令
数据库正向迁移命令(将类操作映射到表中)
python3 manage.py makemigrations
python3 manage.py migrate
数据库反向迁移命令(将表映射成类)
python3 manage.py inspectdb
使用专门的环境方便测试,不需要启动整个Django
框架项目
test.py
import os
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_1.settings")
import django
django.setup()
from app01 import models
写入数据
models.User.objects.create(name='kevin', age=22)
models.User.objects.create(name='jerry', age=19)
models.User.objects.create(name='tom', age=35)
models.User.objects.create(name='tony', age=27)
models.User.objects.create(name='jason', age=18)
models.User.objects.create(name='jack', age=32)
ORM操作关键字
all( )
查询所有结果
res = models.User.objects.all() # 查询所有的数据,QuerySet 可以看成列表套套对象
filter(**kwargs)
包含与所给筛选条件相匹配的对象
res = models.User.objects.filter(pk=1) # 括号内为筛选条件,如果不写相当于all(),
- 支持多个筛选条件逗号隔开,默认
and
关系 - 使用
pk
会自动定位当前表的主键字段 - 支持索引取值,但索引取值不存在会报错
- 没有返回数据返回
None
first()
返回第一条记录
res = models.User.objects.filter().first()
last()
返回最后一条记录
res = models.User.objects.filter().first()
values(*field)
返回一个ValueQuerySet
列表套字典,一个特殊的QuerySet
res = models.User.objects.all().values('name')
all()
不写也表示所有从所有数据- 支持多个字段名,逗号隔开
- 可以将
values
看成对结果集进行字段的筛选
values_list(*field)
与values()
非常相似,返回一个元祖套列表
res = models.User.objects.values_list('name')
exclude(**kwargs)
包含了与所给筛选条件不匹配的对象
res = models.User.objects.exclude(pk=1)
exists()
如果QuerySet包含数据,就返回True,否则返回False
res = models.User.objects.filter(name='xxx').exists()
distinct()
从返回结果中剔除重复纪录(如果你查询跨越多个表,可能在计算QuerySet时得到重复的结果。此时可以使用distinct(),注意只有在PostgreSQL中支持按字段去重。)
res = models.User.objects.all().distinct()
- 数据对象如果包含主键,不会去重
order_by(*field)
对查询结果排序
res = models.User.objects.all().order_by('age') # 默认是升序
res = models.User.objects.all().order_by('-age') # 前面加负号是降序
- 也支持多个字段,依次排序
reverse()
对查询结果反向排序,请注意reverse()
通常只能在具有已定义顺序的QuerySet
上调用(在model
类的Meta
中指定ordering
或调用order_by()
方法)
res = models.User.objects.all().order_by('age').reverse() # 只有在order by 排序之后才可以
get(**kwargs)
返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误
res = models.User.objects.get(pk=1)
count()
返回数据库中匹配查询(QuerySet
)的对象数量
res = models.User.objects.all().count()
双下划线查询
value | 含义 | eg |
---|---|---|
__gt | > | |
__gte | >= | |
__lt | < | |
__lte | <= | |
__exact | 精确等于 | |
__iexact | 忽略大小写精确等于 | |
__contains | 包含 | like ‘%aaa%’ |
__icontains | 忽略大小写包含 | ilike ‘%aaa%’ |
__in | 属于 | name__in=[10,20] |
__isnull | 判空 | |
__startswith | 开头 | |
__istartswith | 忽略大小写开头 | |
__endswith | 结尾 | |
__iendswith | 忽略大小写结尾 | |
__range | 范围 | |
__year | 年 | |
__month | 月 | |
__day |
1.查询年龄大于20的用户
关键字:__gt
(大于)、 __lt
(小于)、__gte
(大于等于)、__lte
(小于等于)
res = models.User.objects.filter(age__gt=20)
2.查询年龄是18、22、19的用户
关键字:__in
(成员运算)
res = models.User.objects.filter(age__in=[18, 22, 19])
3.查询年龄在18到26之间的用户
关键字:__range
(范围查询)
res = models.User.objects.filter(age__range=[18, 26]) # 包含18和26
4.查询姓名中包含字母j
的用户
关键字:contains
(区分大小写)、icontains
(不区分大小写)
res = modeles.User.objects.filter(name__icontains='j')
6.查询月份是5月的数据
关键字:__year
(按年份筛选数据)、 __month
(按月份筛选数据)
res = models.User.objects.filter(op_time__month=5)
7.查询年份是22年的数据
res = modles.User.objects.filter(op_time__year=2022)
终端打印SQL语句
方式一
settings.py
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'propagate': True,
'level': 'DEBUG',
},
}
}
配置好之后,再执行任何对数据库进行操作的语句时,会自动将Django
执行的sql
语句打印到pycharm
终端上
方式二
如果结果集对象是QuerySet
那么可以直接点query
查看
数据库表反向生成models类
python manage.py inspectdb > app/models.py
更多了解:https://zhuanlan.zhihu.com/p/361695150
外键字段的创建
先创建图书表、出版社表、作者表和作者详情表,这四张表。图书表与出版社是**一对多关系**,因为一个出版社可以对应多本图书,和MySQL一样**外键建立在多的一方**,使用`**ForeignKey**`关键字。图书表与作者表是**多对多关系**因为书可以有多个作者,而作者也可以对应多本书,关键字`**ManyToManyField**`,不需要创建第三张表,Django会帮自动创建。对应的作者表和作者详情表是**一对一关系**,外键建在查询频率较高的表中,使用`**OneToOneField**`
class Book(models.Model):
"""图书表"""
title = models.CharField(max_length=32, verbose_name='书名')
price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='价格')
# DecimalField表示 固定精度的十进制数,必须有两个参数 max_digits 数中允许的最大数目的数字,decimal_places 存储的小数位数的位数
publish_time = models.DateField(auto_now_add=True, verbose_name='出版日期')
# 书与出版社的外键字段 一对多关系
publish = models.ForeignKey(to='Publish') # 默认关联的就是主键字段
# 书与作者的外键字段 多对多关系
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=64, 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.IntegerField(verbose_name='手机号')
addr = models.CharField(max_length=64, verbose_name='家庭地址')
def __str__(self):
return '作者详细对象:%s' % self.addr
ManyToManyField
不会在表中创建实际的字段,而是告诉django orm自动创建第三张关系表ForeignKey
、OneToOneField
会在字段的后面自动添加_id
后缀,在写的时候不需要了to
用于指定跟和哪一张表有关系会自动关联主键,也可以使用to_field
指定关联字段verbose_name
是给个字段起一个名字
外键字段操作
一对多、一对一字段操作
增
res = models.Book.objects.create(title='水浒传', price=999.99, publish_id=1)
也可以这样
publish_obj = models.Publish.objects.filter(pk=2).first()
res = models.Book.objects.create(title='水浒传', price=999.99, publish=publish_obj)
改
res = models.Book.objects.filter(pk=1).update(publish_id=2)
也可以这样
publish_obj = models.Publish.objects.filter(pk=2).first()
res = models.Book.objects.filter(pk=1).update(publish=publish_obj)
多对多字段操作(针对第三张关系表的操作)
增
book_obj = models.Book.objects.filter(pk=1).first()
book_obj.authors.add(1,2) # 括号内可以放主键值也可以放数据对象 并且都支持多个
修
book_obj = models.Book.objects.filter(pk=1).first()
book_obj = authors.set([2,]) # 括号内必须是一个可迭代对象 元素同样支持主键值或者数据对象
删
book_obj = models.Book.objects.filter(pk=1).first()
book_obj.authors.remove(3) # 括号内可以放主键值也可以放数据对象 并且都支持多个
清
book_obj = models.Book.objects.filter(pk=1).first()
book_obj.authors.clear() # 括号内无需传值,直接清空当前表在第三张关系表中的绑定记录
正向反向
核心在于当前数据对象是否含有外键字段,在当前表中向外查叫正向,不在当前表中,向外查叫反向
查询口诀: 正向查询按外键字段名,反向查询按表名小写
基于对象的跨表查询
本质就是子查询
1.查询python书籍对应的出版社
# 先获取书籍对象
book_obj = models.Book.objects.filter(title='python').first()
# 再使用跨表查询
res = book_obj.publish
print(res)
# 出版社对象: B出版社
2.查询python书籍对应的作者
book_obj = models.Book.objects.filter(title='Python').first()
res = book_obj.authors.all()
print(res)
# <QuerySet [<Author: 作者对象:kevin>]>
3.查询作者kevin的详情信息
author_obj = models.Author.objects.filter(name='kevin').first()
res = author_obj.author_detail
print(res)
# 作者详细对象: 上海
4.查询A出版社出版的书籍
publish_obj = models.Publish.objects.filter(name='A出版社').first()
res = publish_obj.book_set.all()
print(res)
# <QuerySet [<Book: 书籍对象:Linux>]>
5.查询kevin编写的书籍
res = models.Author.objects.filter(name='kevin').values('book__title', 'book__publish_time')
print(res)
# <QuerySet [{'book__title': 'python', 'book__publish_time': datetime.date(2022, 5, 18)}]>
6.查询电话是110的作者
res = models.AuthorDetail.objects.filter(phone=110).values('author__name','author__age')
print(res)
# <QuerySet [{'author__name': 'kevin', 'author__age': 22}]>
基于双下划线的跨表查询
本质就是连表操作
1.查询go书籍对应的出版社名称
res = models.Book.objects.filter(title='go').values('publish__name', 'publish__addr')
print(res)
# <QuerySet [{'publish__name': 'B出版社', 'publish__addr': 'xxx'}]>
2.查询python书籍数据对应的作者姓名和年龄
res = models.Author.objects.filter(book__title='python').values('name', 'age')
print(res)
# <QuerySet [{'name': 'kevin', 'age': 22}]>
3.查询作者jerry的手机号和地址
res = models.Author.objects.filter(name='jerry').values('author_detail__phone','author_detail__addr')
print(res)
# <QuerySet [{'author_detail__phone': 120, 'author_detail__addr': '深圳'}]>
4.查询A出版社出版的书籍名称和价格
res = models.Publish.objects.filter(name='A出版社').values('book__title','book__price')
print(res)
# <QuerySet [{'book__title': 'Linux', 'book__price': Decimal('2688.77')}]>
5.查询kevin编写的书籍名称和日期
res = models.Author.objects.filter(name='kevin').values('book__title', 'book__publish_time')
print(res)
# <QuerySet [{'book__title': 'python', 'book__publish_time': datetime.date(2022, 5, 18)}]>
6.查询电话是110的作者的姓名和年龄
res = models.AuthorDetail.objects.filter(phone=110).values('author__name', 'author__age')
print(res)
# <QuerySet [{'author__name': 'kevin', 'author__age': 22}]>
双下线查询扩展
基于双下划线的跨表查询的结果也可以是完整的数据对象
1.查询go书籍对应的出版社名称
res = models.Publish.objects.filter(book__title='go')
print(res)
# <QuerySet [<Publish: 出版社对象:B出版社>]>
2.查询python对应的作者姓名和年龄
res = models.Author.objects.filter(book__title='python').values('name', 'age')
print(res)
# <QuerySet [{'name': 'kevin', 'age': 22}]>
3.查询作者kevin的手机号和地址
res = models.AuthorDetail.objects.filter(author__name='kevin').values('phone', 'addr')
print(res)
# <QuerySet [{'phone': 110, 'addr': '上海'}]>
4.查询A出版的书籍名称和价格
res = models.Book.objects.filter(publish__name='A出版社').values('title','price')
print(res)
# <QuerySet [{'title': 'Linux', 'price': Decimal('2688.77')}]>
5.查询kevin编写的书籍名称和日期
res = models.Book.objects.filter(authors__name='kevin').values('title','publish_time')
print(res)
# <QuerySet [{'title': 'python', 'publish_time': datetime.date(2022, 5, 18)}]>
6.查询电话是110的作者的姓名和年龄
res = models.Author.objects.filter(author_detail__phone=110).values('name', 'age')
print(res)
# <QuerySet [{'name': 'kevin', 'age': 22}]>
聚合查询
MySQL聚合函数:max
、min
、sum
、count
、avg
关键字:aggregate
做一些统计方面的工作。返回的是聚合后的数据字典
from django.db.models import Max, Min, Sum, Avg, Count
res = models.Book.objects.aggregate(Max('price'),
Min('price'),
Sum('price'),
Avg('price'),
Count('pk')
)
print(res)
# {'price__max': Decimal('2999.88'), 'price__min': Decimal('2345.55'), 'price__sum': Decimal('10511.86'), 'price__avg': 2627.965, 'pk__count': 4}
补充:没有分组也可以使用聚合函数 默认整体就是一组
分组查询
关键字:anmotate
为返回的查询集添加一些额外的数据,返回的依然是查询集。
1.统计每本书的作者个数
from django.db.models import Count
res = models.Book.objects.annotate(author_num=Count('authors__pk')).values('title', 'author_num')
2.统计每个出版社卖的最便宜的书的价格
res = models.Book.objects.annotate(author_num=Count('authors__pk')).values('title', 'author_num')
3.统计不止一个作者的图书
from django.db.models import Count
res = models.Book.objects.annotate(author_num=Count('authors__pk')).filter(author_num__gt=1).values('title','author_num')
4.统计每个作者出的书的总价格
from django.db.models import Sum
res = models.Author.objects.annotate(book_sum_price=Sum('book__price')).values('name','book_sum_price')
5.统计每个出版社主键值对应的书籍个数
from django.db.models import Count
res = models.Book.objects.values('publish_id').annotate(book_num=Count('pk')).values('publish_id','book_num')
print(res)
F查询与Q查询
F查询
当查询条件的左右表的数据都需要表中的数据,可以使用F查询
当表中已经有数据的情况下,添加额外的字段,需要指定默认值或者可以为null
# 中途新增额外字段名
sell = models.IntegerField(verbose_name='销量', null=True)
inventory = models.IntegerField(verbose_name='库存', default=1000)
1.查询库存大于销量的书籍
from django.db.models import F
res = models.Book.objects.filter(inventory__gt=F('sell'))
2.将所有书的价格提升1000块(要修改的字段是int)
res = models.Book.objects.update(price=F('price') + 1000)
3.将所有书的名称后面加上爆款后缀(要修改字段是char,不能用上面的方法)
from django.db.models.functions import Concat
from django.db.models import Value
res = models.Book.objects.update(title=Concat(F('title'), Value('爆款')))
4.查询价格大于20000或者卖出大于1000的书籍
res =modelese.Book.objects.filter()
- 查询价格大于20000或者卖出大于1000的书籍
res = models.Book.objects.filter(price__gt=20000,sell__gt=1000)
Q查询
filter
括号内多个条件默认是and
关系,无法直接修改,使用Q
对象,可以改变filter
括号内多个条件的链接关系
,
是and
关系
from django.db.models import Q
res = models.Book.objects.filter(Q(price__gt=2500),Q(publish__name='A出版社')) # 逗号默认 and
|
是or
关系
res = models.Book.objects.filter(Q(price__gt=2500) | Q(title='go爆款'))
~
是not 操作
res = models.Book.objects.filter(~Q(price__gt=2500))
进阶用法
from django.db.models import Q
q_obj = Q()
q_obj.connector = 'or' # 默认是and 可以改为or
q_obj.children.append(('price__gt', 20000))
q_obj.children.append(('inventory__gt', 1000))
res = models.Book.objects.filter(q_obj)
ORM查询优化
ORM查询默认都是惰性查询,编写ORM语句并不会直接指向SQL语句,只有后续的代码用到了才会执行,ORM查询默认自带分页功能,都是为了减轻数据库压力
要求:单个结果还是以对象的形式展示,可以直接通过句点符操作
only
res = models.Book.objects.only('title', 'price')
for obj in res:
print(obj.title)
print(obj.price)
重要补充:only
会产生对象结果集,对象点括号内出现的字段,不会再走数据库查询,但是如果点了括号内没有的字段,虽然也可以获取到数据,但是每次都会走数据库查询
defer
res = models.Book.objects.only('title', 'price')
for obj in res:
print(obj.publish_time)
重要补充:only
和defer
恰恰相反,对象点括号内出现的字段,会走数据库查询,但是点括号内没有的字段不会走数据库查询
select_related
res = models.Book.objects.select_related('publish')
for obj in res:
print(obj.title)
print(obj.publish.name)
print(obj.publish.addr)
重要补充:select_related
括号内只能传一对一和一对多字段,不能传多对多字段。效果是内部连表(inner join
),然后将连接之后的大表中的所有数据全部封装到数据对象中(一次性封装),后续对象通过正向查询跨表,内部不会再走数据库。
prefetch_related
res = models.Book.objects.prefetch_related('publish')
for obj in res:
print(obj.title)
print(obj.publish.name)
print(obj.publish.addr)
重要补充:将多次查询之后的结果封装到数据对象中(查一点封装一点),后续对象通过正向查询跨表,内部不会再走数据库
ORM常见字段
类型 | 说明 |
---|---|
AutoField | 一个自动增加的整数类型字段。通常你不需要自己编写它,Django会自动帮你添加字段:id = models.AutoField(primary_key=True) ,这是一个自增字段,从1开始计数。如果你非要自己设置主键,那么请务必将字段设置为 primary_key=True 。Django在一个模型中只允许有一个自增字段,并且该字段必须为主键! |
BigAutoField | 64位整数类型自增字段,数字范围更大,从1到9223372036854775807 |
BigIntegerField | 64位整数字段(看清楚,非自增),类似IntegerField ,-9223372036854775808 到9223372036854775807。在Django的模板表单里体现为一个NumberInput 标签。 |
BinaryField | 二进制数据类型。较少使用。 |
BooleanField | 布尔值类型。默认值是None。在HTML表单中体现为CheckboxInput标签。如果设置了参数null=True,则表现为NullBooleanSelect选择框。可以提供default参数值,设置默认值。 |
CharField | 最常用的类型,字符串类型。必须接收一个max_length参数,表示字符串长度不能超过该值。默认的表单标签是text input。 |
DateField | class DateField(auto_now=False, auto_now_add=False, **options) , 日期类型。一个Python中的datetime.date的实例。在HTML中表现为DateInput标签。在admin后台中,Django会帮你自动添加一个JS日历表和一个“Today”快捷方式,以及附加的日期合法性验证。两个重要参数:(参数互斥,不能共存) auto_now :每当对象被保存时将字段设为当前日期,常用于保存最后修改时间。 auto_now_add :每当对象被创建时,设为当前日期,常用于保存创建日期(注意,它是不可修改的)。设置上面两个参数就相当于给field添加了 editable=False 和 blank=True 属性。如果想具有修改属性,请用default参数。例子: pub_time = models.DateField(auto_now_add=True) ,自动添加发布时间。 |
DateTimeField | 日期时间类型。Python的datetime.datetime的实例。与DateField相比就是多了小时、分和秒的显示,其它功能、参数、用法、默认值等等都一样。 |
DecimalField | 固定精度的十进制小数。相当于Python的Decimal实例,必须提供两个指定的参数!参数max_digits :最大的位数,必须大于或等于小数点位数 。 decimal_places :小数点位数,精度。 当 localize=False 时,它在HTML表现为NumberInput标签,否则是textInput类型。例子:储存最大不超过999,带有2位小数位精度的数,定义如下: models.DecimalField(..., max_digits=5, decimal_places=2) 。 |
DurationField | 持续时间类型。存储一定期间的时间长度。类似Python中的timedelta。在不同的数据库实现中有不同的表示方法。常用于进行时间之间的加减运算。但是小心了,这里有坑,PostgreSQL等数据库之间有兼容性问题! |
EmailField | 邮箱类型,默认max_length最大长度254位。使用这个字段的好处是,可以使用Django内置的EmailValidator进行邮箱格式合法性验证。 |
FileField | class FileField(upload_to=None, max_length=100, **options) 上传文件类型,后面单独介绍。 |
FilePathField | 文件路径类型,后面单独介绍 |
FloatField | 浮点数类型,对应Python的float。参考整数类型字段。 |
ImageField | 图像类型,后面单独介绍。 |
IntegerField | 整数类型,最常用的字段之一。取值范围-2147483648到2147483647。在HTML中表现为NumberInput或者TextInput标签。 |
GenericIPAddressField | class GenericIPAddressField(protocol='both', unpack_ipv4=False, **options) ,IPV4或者IPV6地址,字符串形式,例如 192.0.2.30 或者 2a02:42fe::4 。在HTML中表现为TextInput标签。参数 protocol 默认值为‘both’,可选‘IPv4’或者‘IPv6’,表示你的IP地址类型。 |
JSONField | JSON类型字段。Django3.1新增。签名为class JSONField(encoder=None,decoder=None,**options) 。其中的encoder和decoder为可选的编码器和解码器,用于自定义编码和解码方式。如果为该字段提供default值,请务必保证该值是个不可变的对象,比如字符串对象。 |
PositiveBigIntegerField | 正的大整数,0到9223372036854775807 |
PositiveIntegerField | 正整数,从0到2147483647 |
PositiveSmallIntegerField | 较小的正整数,从0到32767 |
SlugField | slug是一个新闻行业的术语。一个slug就是一个某种东西的简短标签,包含字母、数字、下划线或者连接线,通常用于URLs中。可以设置max_length参数,默认为50。 |
SmallAutoField | Django3.0新增。类似AutoField,但是只允许1到32767。 |
SmallIntegerField | 小整数,包含-32768到32767。 |
TextField | 用于储存大量的文本内容,在HTML中表现为Textarea标签,最常用的字段类型之一!如果你为它设置一个max_length参数,那么在前端页面中会受到输入字符数量限制,然而在模型和数据库层面却不受影响。只有CharField才能同时作用于两者。 |
TimeField | 时间字段,Python中datetime.time的实例。接收同DateField一样的参数,只作用于小时、分和秒。 |
URLField | 一个用于保存URL地址的字符串类型,默认最大长度200。 |
UUIDField | 用于保存通用唯一识别码(Universally Unique Identifier)的字段。使用Python的UUID类。在PostgreSQL数据库中保存为uuid类型,其它数据库中为char(32)。这个字段是自增主键的最佳替代品,后面有例子展示。 |
1.FileField
class FileField(upload_to=None, max_length=100, **options)
上传文件字段(不能设置为主键)。默认情况下,该字段在HTML中表现为一个ClearableFileInput
标签。在数据库内,我们实际保存的是一个字符串类型,默认最大长度100,可以通过max_length
参数自定义。真实的文件是保存在服务器的文件系统内的。
重要参数upload_to
用于设置上传地址的目录和文件名。如下例所示:
class MyModel(models.Model):
# 文件被传至`MEDIA_ROOT/uploads`目录,MEDIA_ROOT由你在settings文件中设置
upload = models.FileField(upload_to='uploads/')
# 或者
# 被传到`MEDIA_ROOT/uploads/2015/01/30`目录,增加了一个时间划分
upload = models.FileField(upload_to='uploads/%Y/%m/%d/')
Django很人性化地帮我们实现了根据日期生成目录或文件的方式!
**upload_to**
参数也可以接收一个回调函数,该函数返回具体的路径字符串,如下例:
def user_directory_path(instance, filename):
#文件上传到MEDIA_ROOT/user_<id>/<filename>目录中
return 'user_{0}/{1}'.format(instance.user.id, filename)
class MyModel(models.Model):
upload = models.FileField(upload_to=user_directory_path)
例子中,user_directory_path
这种回调函数,必须接收两个参数,然后返回一个Unix风格的路径字符串。参数instace
代表一个定义了FileField
的模型的实例,说白了就是当前数据记录。filename
是原本的文件名。
从Django3.0开始,支持使用pathlib.Path
处理路径。
当你访问一个模型对象中的文件字段时,Django会自动给我们提供一个 FieldFile实例作为文件的代理,通过这个代理,我们可以进行一些文件操作,主要如下:
- FieldFile.name : 获取文件名
- FieldFile.size: 获取文件大小
- FieldFile.url :用于访问该文件的url
- FieldFile.open(mode=’rb’): 以类似Python文件操作的方式,打开文件
- FieldFile.close(): 关闭文件
- FieldFile.save(name, content, save=True): 保存文件
- FieldFile.delete(save=True): 删除文件
这些代理的API和Python原生的文件读写API非常类似,其实本质上就是进行了一层封装,让我们可以在Django内直接对模型中文件字段进行读写,而不需要绕弯子。
2. ImageField
class ImageField(upload_to=None, height_field=None, width_field=None, max_length=100, **options)
用于保存图像文件的字段。该字段继承了FileField,其用法和特性与FileField基本一样,只不过多了两个属性height和width。默认情况下,该字段在HTML中表现为一个ClearableFileInput标签。在数据库内,我们实际保存的是一个字符串类型,默认最大长度100,可以通过max_length参数自定义。真实的图片是保存在服务器的文件系统内的。
height_field
参数:保存有图片高度信息的模型字段名。 width_field
参数:保存有图片宽度信息的模型字段名。
使用Django的ImageField需要提前安装pillow模块,pip install pillow即可。
3. 使用FileField或者ImageField字段的步骤:
- 在settings文件中,配置
MEDIA_ROOT
,作为你上传文件在服务器中的基本路径(为了性能考虑,这些文件不会被储存在数据库中)。再配置个MEDIA_URL
,作为公用URL,指向上传文件的基本路径。请确保Web服务器的用户账号对该目录具有写的权限。 - 添加FileField或者ImageField字段到你的模型中,定义好
upload_to
参数,文件最终会放在MEDIA_ROOT
目录的“upload_to
”子目录中。 - 所有真正被保存在数据库中的,只是指向你上传文件路径的字符串而已。可以通过url属性,在Django的模板中方便的访问这些文件。例如,假设你有一个ImageField字段,名叫
mug_shot
,那么在Django模板的HTML文件中,可以使用{{ object.mug_shot.url }}
来获取该文件。其中的object用你具体的对象名称代替。 - 可以通过
name
和size
属性,获取文件的名称和大小信息。
安全建议:
无论你如何保存上传的文件,一定要注意他们的内容和格式,避免安全漏洞!务必对所有的上传文件进行安全检查,确保它们不出问题!如果你不加任何检查就盲目的让任何人上传文件到你的服务器文档根目录内,比如上传了一个CGI或者PHP脚本,很可能就会被访问的用户执行,这具有致命的危害。
4. FilePathField
class FilePathField(path='', match=None, recursive=False, allow_files=True, allow_folders=False, max_length=100, **options)
一种用来保存文件路径信息的字段。在数据表内以字符串的形式存在,默认最大长度100,可以通过max_length参数设置。
它包含有下面的一些参数:
path
:必须指定的参数。表示一个系统绝对路径。path通常是个字符串,也可以是个可调用对象,比如函数。match
:可选参数,一个正则表达式,用于过滤文件名。只匹配基本文件名,不匹配路径。例如foo.*\.txt$
,只匹配文件名foo23.txt
,不匹配bar.txt
与foo23.png
。recursive
:可选参数,只能是True或者False。默认为False。决定是否包含子目录,也就是是否递归的意思。allow_files
:可选参数,只能是True或者False。默认为True。决定是否应该将文件名包括在内。它和allow_folders
其中,必须有一个为True。allow_folders
: 可选参数,只能是True或者False。默认为False。决定是否应该将目录名包括在内。
比如:
FilePathField(path="/home/images", match="foo.*", recursive=True)
它只匹配/home/images/foo.png
,但不匹配/home/images/foo/bar.png
,因为默认情况,只匹配文件名,而不管路径是怎么样的。
例子:
import os
from django.conf import settings
from django.db import models
def images_path():
return os.path.join(settings.LOCAL_FILE_DIR, 'images')
class MyModel(models.Model):
file = models.FilePathField(path=images_path)
5. UUIDField
数据库无法自己生成uuid,因此需要如下使用default参数:
import uuid # Python的内置模块
from django.db import models
class MyUUIDModel(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
# 其它字段
注意不要写成default=uuid.uuid4()
自定义字段
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
字段的参数
所有的模型字段都可以接收一定数量的参数,比如CharField至少需要一个max_length参数。下面的这些参数是所有字段都可以使用的,并且是可选的。
null
该值为True时,Django在数据库用NULL保存空值。默认值为False。对于保存字符串类型数据的字段,请尽量避免将此参数设为True,那样会导致两种‘没有数据’的情况,一种是NULL
,另一种是空字符串''
。Django 的惯例是使用空字符串而不是 NULL
。
blank
True时,字段可以为空。默认False。和null参数不同的是,null是纯数据库层面的,而blank是验证相关的,它与表单验证是否允许输入框内为空有关,与数据库无关。所以要小心一个null为False,blank为True的字段接收到一个空值可能会出bug或异常。
choices
用于页面上的选择框标签,需要先提供一个二维的二元元组,第一个元素表示存在数据库内真实的值,第二个表示页面上显示的具体内容。在浏览器页面上将显示第二个元素的值。例如:
YEAR_IN_SCHOOL_CHOICES = (
('FR', 'Freshman'),
('SO', 'Sophomore'),
('JR', 'Junior'),
('SR', 'Senior'),
('GR', 'Graduate'),
)
一般来说,最好将选项定义在类里,并取一个直观的名字,如下所示:
from django.db import models
class Student(models.Model):
FRESHMAN = 'FR'
SOPHOMORE = 'SO'
JUNIOR = 'JR'
SENIOR = 'SR'
YEAR_IN_SCHOOL_CHOICES = (
(FRESHMAN, 'Freshman'),
(SOPHOMORE, 'Sophomore'),
(JUNIOR, 'Junior'),
(SENIOR, 'Senior'),
)
year_in_school = models.CharField(
max_length=2,
choices=YEAR_IN_SCHOOL_CHOICES,
default=FRESHMAN,
)
def is_upperclass(self):
return self.year_in_school in (self.JUNIOR, self.SENIOR)
注意:每当 choices
的顺序变动时将会创建新的迁移。
如果一个模型中有多个字段需要设置choices,可以将这些二维元组组合起来,显得更加整洁优雅,例如下面的做法:
MEDIA_CHOICES = [
('Audio', (
('vinyl', 'Vinyl'),
('cd', 'CD'),
)
),
('Video', (
('vhs', 'VHS Tape'),
('dvd', 'DVD'),
)
),
('unknown', 'Unknown'),
]
反过来,要获取一个choices的第二元素的值,可以使用get_FOO_display()
方法,其中的FOO用字段名代替。对于下面的例子:
from django.db import models
class Person(models.Model):
SHIRT_SIZES = (
('S', 'Small'),
('M', 'Medium'),
('L', 'Large'),
)
name = models.CharField(max_length=60)
shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)
使用方法:
>>> p = Person(name="Fred Flintstone", shirt_size="L")
>>> p.save()
>>> p.shirt_size
'L'
>>> p.get_shirt_size_display()
'Large'
从Django3.0开始,新增了TextChoices、IntegerChoices和Choices三个类,用来达到类似Python的enum枚举库的作用,下面是一个例子:
from django.utils.translation import gettext_lazy as _
class Student(models.Model):
class YearInSchool(models.TextChoices):
FRESHMAN = 'FR', _('Freshman')
SOPHOMORE = 'SO', _('Sophomore')
JUNIOR = 'JR', _('Junior')
SENIOR = 'SR', _('Senior')
GRADUATE = 'GR', _('Graduate')
year_in_school = models.CharField(
max_length=2,
choices=YearInSchool.choices,
default=YearInSchool.FRESHMAN,
)
def is_upperclass(self):
return self.year_in_school in {
self.YearInSchool.JUNIOR,
self.YearInSchool.SENIOR,
}
简要解释一下:
- 第一句导入是废话,搞国际化翻译的,和本例的内容其实没关系
- 核心在Student模型中创建了个内部类YearInSchool
- YearInSchool继承了Django新增的TextChoices类
- TextChoices中没定义别的,只定义了一些类变量,这些类变量看起来和我们前面使用的二维二元元组本质上是一个套路
- Student模型中有一个
year_in_school
字段,其中定义了choices参数,参数的值是YearInSchool.choices
year_in_school
字段还定义了default参数,值是YearInSchool.FRESHMAN
- 从本质上来说,这和我们开始使用choice的方式是一样的,只不过换成了类的方式,而不是二维元组
吐个槽,这么设计除了增加学习成本有什么好处?有多少Choice选项需要你非得用类的形式管理起来封装起来?二维元组它就不香吗?新手学习就不累吗?
吐槽归吐槽,该介绍的还得介绍,否则是不敬业。
如果你不需要人类可读的帮助文本,那么类似的YearInSchool还可以写成下面的方式:
>>> class Vehicle(models.TextChoices):
... CAR = 'C'
... TRUCK = 'T'
... JET_SKI = 'J'
...
>>> Vehicle.JET_SKI.label
'Jet Ski'
哎,我都写内部类了,还差这点吗?
另外,由于使用整数作为选项的场景太常见了,Django除了提供TextChoices还提供了一个IntegerChoices,例子如下:
class Card(models.Model):
class Suit(models.IntegerChoices):
DIAMOND = 1
SPADE = 2
HEART = 3
CLUB = 4
suit = models.IntegerField(choices=Suit.choices)
实际上,Django为这几个类提供了一些属性,典型的有下面的:
- .label
- .choices
- .values
- .name
读者可以多尝试,看看每个的意义。
参考用法:
>>> MedalType = models.TextChoices('MedalType', 'GOLD SILVER BRONZE')
>>> MedalType.choices
[('GOLD', 'Gold'), ('SILVER', 'Silver'), ('BRONZE', 'Bronze')]
>>> Place = models.IntegerChoices('Place', 'FIRST SECOND THIRD')
>>> Place.choices
[(1, 'First'), (2, 'Second'), (3, 'Third')]
如果文本或数字类型不满足你的要求,你也可以继承Choice类,自己写。比如下面就创建了一个时间类型选项的choices类:
class MoonLandings(datetime.date, models.Choices):
APOLLO_11 = 1969, 7, 20, 'Apollo 11 (Eagle)'
APOLLO_12 = 1969, 11, 19, 'Apollo 12 (Intrepid)'
APOLLO_14 = 1971, 2, 5, 'Apollo 14 (Antares)'
APOLLO_15 = 1971, 7, 30, 'Apollo 15 (Falcon)'
APOLLO_16 = 1972, 4, 21, 'Apollo 16 (Orion)'
APOLLO_17 = 1972, 12, 11, 'Apollo 17 (Challenger)'
最后,如果想设置空标签,可以参考下面的做法:
class Answer(models.IntegerChoices):
NO = 0, _('No')
YES = 1, _('Yes')
__empty__ = _('(Unknown)')
db_column
该参数用于定义当前字段在数据表内的列名。如果未指定,Django将使用字段名作为列名。
db_index
该参数接收布尔值。如果为True,数据库将为该字段创建索引。
db_tablespace
用于字段索引的数据库表空间的名字,前提是当前字段设置了索引。默认值为工程的DEFAULT_INDEX_TABLESPACE
设置。如果使用的数据库不支持表空间,该参数会被忽略。
default
字段的默认值,可以是值或者一个可调用对象。如果是可调用对象,那么每次创建新对象时都会调用。设置的默认值不能是一个可变对象,比如列表、集合等等。lambda匿名函数也不可用于default的调用对象,因为匿名函数不能被migrations序列化。
注意:在某种原因不明的情况下将default设置为None,可能会引发intergyerror:not null constraint failed
,即非空约束失败异常,导致python manage.py migrate
失败,此时可将None改为False或其它的值,只要不是None就行。
editable
如果设为False,那么当前字段将不会在admin后台或者其它的ModelForm表单中显示,同时还会被模型验证功能跳过。参数默认值为True。
error_messages
用于自定义错误信息。参数接收字典类型的值。字典的键可以是null
、 blank
、 invalid
、 invalid_choice
、 unique
和unique_for_date
其中的一个。
help_text
额外显示在表单部件上的帮助文本。即便你的字段未用于表单,它对于生成文档也是很有用的。
该帮助文本默认情况下是可以带HTML代码的,具有风险:
help_text="Please use the following format: <em>YYYY-MM-DD</em>."
所以使用时请注意转义为纯文本,防止脚本攻击。
primary_key
如果你没有给模型的任何字段设置这个参数为True,Django将自动创建一个AutoField自增字段,名为‘id’,并设置为主键。也就是id = models.AutoField(primary_key=True)
。
如果你为某个字段设置了primary_key=True,则当前字段变为主键,并关闭Django自动生成id主键的功能。
**primary_key=True**
隐含**null=False**
和**unique=True**
的意思。一个模型中只能有一个主键字段!
另外,主键字段不可修改,如果你给某个对象的主键赋个新值实际上是创建一个新对象,并不会修改原来的对象。
from django.db import models
class Fruit(models.Model):
name = models.CharField(max_length=100, primary_key=True)
###############
>>> fruit = Fruit.objects.create(name='Apple')
>>> fruit.name = 'Pear'
>>> fruit.save()
>>> Fruit.objects.values_list('name', flat=True)
['Apple', 'Pear']
unique
设为True时,在整个数据表内该字段的数据不可重复。
注意:对于ManyToManyField和OneToOneField关系类型,该参数无效。
注意: 当unique=True时,db_index参数无须设置,因为unqiue隐含了索引。
unique_for_date
日期唯一。可能不太好理解。举个栗子,如果你有一个名叫title的字段,并设置了参数unique_for_date="pub_date"
,那么Django将不允许有两个模型对象具备同样的title和pub_date。有点类似联合约束。
unique_for_month
同上,只是月份唯一。
unique_for_year
同上,只是年份唯一。
verbose_name
为字段设置一个人类可读,更加直观的别名。
对于每一个字段类型,除了ForeignKey
、ManyToManyField
和OneToOneField
这三个特殊的关系类型,其第一可选位置参数都是verbose_name
。如果没指定这个参数,Django会利用字段的属性名自动创建它,并将下划线转换为空格。
下面这个例子的verbose name
是”person’s first name”:
first_name = models.CharField("person's first name", max_length=30)
下面这个例子的verbose name
是”first name”:
first_name = models.CharField(max_length=30)
对于外键、多对多和一对一字字段,由于第一个参数需要用来指定关联的模型,因此必须用关键字参数verbose_name
来明确指定。如下:
poll = models.ForeignKey(
Poll,
on_delete=models.CASCADE,
verbose_name="the related poll",
)
sites = models.ManyToManyField(Site, verbose_name="list of sites")
place = models.OneToOneField(
Place,
on_delete=models.CASCADE,
verbose_name="related place",
)
另外,你无须大写verbose_name
的首字母,Django自动为你完成这一工作。
validators
运行在该字段上的验证器的列表
事务操作
Django 默认的事务行为是自动提交,除非事务正在执行,每个查询将会马上自动提交到数据库。
事务的定义:将多个sql语句操作变成原子性操作,要么同时成功,有一个失败则里面回滚到原来的状态,保证数据的完整性和一致性(NoSQL数据库对于事务则是部分支持)
from django.db.models import F
from django.db import transaction
# 开启事务处理
try:
with transaction.atomic():
# 创建一条数据
models.Book.objects.create(title='php', price=2100.99, publish_id=1, inventory=1000)
# 能执行成功
models.Book.objects.filter(title='php').update(inventory=F("inventory") - 1, sell=F("sell") + 1)
except Exception as e:
print(e)
ORM执行原生SQL
方式一
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()
方式二
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']
)
多对多表创建方式
全自动
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 Book_Author(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='Book_Author', # 指定表
through_fields=('book','author') # 指定字段
)
class Author(models.Model):
name = models.CharField(max_length=32)
'''多对多建在任意一方都可以 如果建在作者表 字段顺序互换即可'''
books = models.ManyToManyField(
to='Author',
through='Book_Author', # 指定表
through_fields=('author','book') # 指定字段
)
class Book_Author(models.Model):
book = models.ForeignKey(to='Book')
author = models.ForeignKey(to='Author')