Django ORM 知识汇总
模型示例
models.py
# -*- coding: utf-8 -*-
import datetime
from django.db import models
class Article(models.Model):
# id = models.AutoField(primary_key=True) # 默认情况下,django会自动添加一个自增id,也可以自定义
title = models.CharField(u'题目', max_length=64)
body = models.TextField(u'内容')
auth = models.CharField(u'作者', max_length=10)
read = models.IntegerField(default=0)
count = models.IntegerField(default=0)
is_deleted = models.BooleanField(u'是否已删除', default=False)
create_time = models.DateTimeField(u'创建时间', auto_now_add=True)
update_time = models.DateTimeField(u'更新时间', null=True, auto_now=True)
def __str__(self):
# 被其他模型引用时显示为该字段
return f'{self.title}_{self.auth}'
def __unicode__(self):
return f'{self.title}_{self.auth}'
def to_dict(self):
fields = []
for field in self._meta.fields:
fields.append(field.name)
d = {}
for attr in fields:
if isinstance(getattr(self, attr), datetime.datetime):
d[attr] = getattr(self, attr).strftime('%Y-%m-%d %H:%M:%S')
elif isinstance(getattr(self, attr), datetime.date):
d[attr] = getattr(self, attr).strftime('%Y-%m-%d')
else:
d[attr] = getattr(self, attr)
return d
class Meta:
verbose_name = u"Article"
verbose_name_plural = u"Article"
ordering = ("-id",) # 会影响 annotate 的结果,默认添加在 group by 字段中,谨慎使用
# unique_together = (("title", "auth"),)
class Hero(models.Model):
article = models.ForeignKey('Article', related_name='article', on_delete=models.DO_NOTHING,
help_text=u'Destination IP', null=True)
name = models.CharField(max_length=20)
gender = models.BooleanField(default=False)
comment = models.CharField(max_length=200, null=True, blank=False) # 备注
is_deleted = models.BooleanField(u'是否已删除', default=False)
create_time = models.DateTimeField(u'创建时间', auto_now_add=True)
update_time = models.DateTimeField(u'更新时间', null=True, auto_now=True)
def to_dict(self, attrs):
""" 转换为dict格式"""
return {
attr: getattr(self, attr)
for attr in attrs
}
# 控制字段长度,在 admin 中用 short_comment 替代 comment
def short_comment(self):
return f'{self.comment[:10]}...{self.comment[-10:]}'
short_comment.allow_tags = True
short_comment.short_description = 'comment'
class Meta:
verbose_name = u"Hero"
verbose_name_plural = u"Hero"
# 联合主键
unique_together = ("name", "article_id")
ordering = ("-id",)
import datetime
class BaseModel(models.Model):
def to_dict(self):
fields = []
for field in self._meta.fields:
fields.append(field.name)
d = {}
for attr in fields:
if isinstance(getattr(self, attr), datetime.datetime):
d[attr] = getattr(self, attr).strftime('%Y-%m-%d %H:%M:%S')
elif isinstance(getattr(self, attr), datetime.date):
d[attr] = getattr(self, attr).strftime('%Y-%m-%d')
else:
d[attr] = getattr(self, attr)
return d
class Meta:
abstract = True
class Host(BaseModel):
status = models.IntegerField(u'主机运行状态', choices=APP_STATUS_CHOICES, default=0)
role = models.CharField(max_length=128, choices=ROLE_CHOICES, default=RoleType.UNKNOWN)
...
-
字段类型
-
null=True, blank=True
null
是针对数据库而言,如果 null=True
, 表示数据库的该字段可以为空
blank
是针对表单的,如果 blank=True
,表示在 Django admin
表单填写该字段的时候可以不填,该字段非粗体显示
utils.py
from collections import namedtuple
def dict_to_namedtuple(dic):
"""从dict转换到namedtuple"""
return namedtuple('AttrStore', dic.keys())(**dic)
def choices_to_namedtuple(choices):
"""从django-model的choices转换到namedtuple"""
return dict_to_namedtuple(dict(choices))
def tuple_choices(tupl):
"""从django-model的choices转换到namedtuple"""
return [(t, t) for t in tupl]
ROLE_TUPLE = ('Server', 'Agent', 'UNKNOWN',)
ROLE_CHOICES = tuple_choices(ROLE_TUPLE)
RoleType = choices_to_namedtuple(ROLE_CHOICES)
APP_STATUS_CHOICES = [
('RUNNING', 'RUNNING'),
('STOPED', 'STOPED'),
]
APP_STATUS_DICT = OrderedDict(APP_STATUS_CHOICES)
AppStatusType = tuple_to_namedtuple(APP_STATUS_DICT.keys())
APP_STATUS_DICT = {
1: 'RUNNING',
2: 'STOPED',
}
APP_STATUS_CHOICES = dict_to_choices(APP_STATUS_DICT)
AppStatusCode = choices_to_namedtuple(dict_to_choices(APP_STATUS_DICT, True))
语法
增
# 使用 create 创建 1
Article.objects.create(title='abc', body='xyz', auth='xianbin')
# 使用 create 创建 2
obj = {'title': 'abc', 'body': 'xyz', 'auth': 'xianbin'}
Article.objects.create(**obj)
# 使用 save 创建
obj = Article(title='abc', body='xyz', auth='xianbin')
obj.save()
# 使用 update_or_create 创建,object 为新建或者更新的对象,created 为一个布尔值,True为新建
object, created = Article.objects.update_or_create(
title='abc',
auth='xianbin',
defaults={
'body': 'body',
}
)
# 使用 get_or_create 创建,object 为查找或新建的对象,created 为一个布尔值,True为新建。如果使用MySQL,请确保使用 READ COMMITTED 隔离级别而不是默认的 REPEATABLE READ,否则会遇到get_or_create 引发 IntegrityError。该方法不是线程安全的
object, created = Article.objects.get_or_create(
title='abc',
auth='xianbin',
defaults={
'body': 'body',
}
)
删
Article.objects.filter(title='abc').delete()
改
Article.objects.filter(auth='xianbin').update(auth='xiexianbin')
# 或者
obj = Article.objects.get(auth='xianbin')
obj.auth = 'xiexianbin'
obj.save() # 更新所有字段,有覆盖风险
obj.save(update_fields=['auth']) # 更新指定字段
查
查主要使用 Article.objects
获取 querySet
,后面可以跟如下3种过滤器:
- filter() 表示
=
- exclude() 表示
!=
- distinct() 去重
() 里面可以使用如下:
- __exact 精确等于
like 'aaa'
- __iexact 精确等于,忽略大小写
ilike 'aaa'
- __contains 包含
like '%aaa%'
- __icontains 包含,忽略大小写
ilike '%aaa%'
,注意对于sqlite来说,contains
等同于 icontains
- __gt 大于
- __gte 大于等于
- __lt 小于
- __lte 小于等于
- __in 存在于一个list范围内
- __startswith 以…开头
- __istartswith 以…开头,忽略大小写
- __endswith 以…结尾
- __iendswith 以…结尾,忽略大小写
- __range 在…范围内
- __year 日期字段的年份
- __month 日期字段的月份
- __day 日期字段的日
- __isnull=True/False
示例:
$ python manage.py shell
>> Article.objects.filter(title_contains='abc').all()
# Get Article entries with id 1 and 3
>>> Article.objects.filter(pk__in=[1, 3])
# Get all Article entries with id > 3
>>> Article.objects.filter(pk__gt=3)
# null 查询
Article.objects.filter(auth__isnull=True).count()
Article.objects.filter(title__exact='').count()
Article.objects.exclude(auth__isnull=True).exclude(title__exact='').count()
复杂查询
Q 对象
filter
等方法中的关键字参数查询都是一起进行 AND
的。如果你需要执行更复杂的查询(例如 OR
语句),你可以使用Q 对象
from django.db.models import Q
Q 对象
可以使用 &(AND)
和 |(OR)
操作符组合起来。当一个操作符在两个 Q 对象
上使用时,它产生一个新的 Q 对象
Q(title__startswith='Abc') | Q(title__startswith='Xyz')
等同于SQL语句:
WHERE title LIKE 'Abc%' OR title LIKE 'Xyz%'
如果一个查询函数有多个 Q 对象
参数(逗号隔开的),这些参数的逻辑关系为 AND
:
Article.objects.get(
Q(title__startswith='Abc'),
Q(create_time=date(2018, 8, 20)) | Q(create_time=date(2018, 8, 21))
)
等同于下列SQL语句:
SELECT * from Article
WHERE title LIKE 'Abc%'
AND (create_time = '2018-08-20' OR create_time = '2018-08-21')
Q 对象
可以使用 ~
操作符取反,这允许组合正常的查询和取反(NOT) 查询:
Q(title__startswith='Abc') | ~Q(create_time__year=2018)
查询函数可以混合使用 Q 对象
和关键字参数。所有提供给查询函数的参数(关键字参数或Q 对象
)都将AND
在一起。但是,如果出现Q 对象
,它必须位于所有关键字参数的前面。例如
Article.objects.get(
Q(create_time=date(2018, 8, 20)) | Q(create_time=date(2018, 8, 21)),
title__startswith='Abc')
下列是不合法的查询条件:
Article.objects.get(
title__startswith='Abc',
Q(create_time=date(2018, 8, 20)) | Q(create_time=date(2018, 8, 21)))
F 对象
F对象
代表的是 Model 的某一字段的 值
或 聚合出的值
Article.objects.all().update(read=F('read')+1)
阅读量是字数 10 倍的 Article
for a in Article.objects.filter(read__gt=F('count')*10):
print(a.name)
# 根据 read 递增排序,null值在最前面
for a in Article.objects.order_by(F('read').asc(nulls_first=True)):
print(a.name)
# 还可以递减,null值放最后
for a in Article.objects.order_by(F('read').desc(nulls_last=True)):
print(a.name)
# 跨模型排序
Hero.objects.order_by('article__title', 'count')
# 多字段排序,按照 read 降序(负号表示降序),count 升序排列,.all() 可以省略
Article.objects.order_by('-read', 'count').all()
也可以在模型类中定义排序:
class Article(models.Model):
...
class Meta:
ordering = ['-read', 'count']
orm 转 SQL 显示
a = Article.objects.filter(title_contains='abc').all()
# 打印 SQL
print(a.query)
print(a)
聚合查询
annotate
方法可以实现分组 group by
后再聚合:
# select COUNT(auth) FROM article;
print(Article.objects.aggregate(Count('auth'))) # {'auth__count': 4}
# select COUNT(auth) as total FROM article;
print(Article.objects.aggregate(total=Count('auth'))) # {'total': 4}
# select is_deleted, COUNT(auth) as total FROM article GROUP BY status;
sql1 = Article.objects.values('auth').annotate(total=Count('auth')) # 如果 values 在 annotate 前,会导致 group by 错误
# 打印 SQL
print(sql1.query)
print(sql1)
其他高级用法
利用 sql extra
日期格式化 date_format
:
SELECT DATE_FORMAT(create_time, "%Y-%m-%d") as date, COUNT(*) FROM article GROUP BY DATE_FORMAT(create_time, "%Y-%m-%d") order by date desc limit 10;
Article.objects.filter()\
.extra(select={"date": "DATE_FORMAT(create_time, '%%Y-%%m-%%d')"})\
.values('date')\
.annotate(total=Count('auth'))\
.order_by('-date')
raw SQL
from django.db import connection
def my_custom_sql(self):
with connection.cursor() as cursor:
# cursor.execute(sql, [params])
cursor.execute("SELECT * FROM article")
# cursor.execute("SELECT foo FROM bar WHERE baz = '30%%' AND id = %s", [self.id])
# row = cursor.fetchone()
rows = cursor.fetchall()
return rows
数据类型
MultiSelectField
pip install django-multiselectfield
INSTALLED_APPS = [
'multiselectfield',
]
class Auth(models.Model):
language_choices= (('chinese', u'汉语'), ('english', u'英语'))
language = MultiSelectField(u'语言', choices=language_choices)
ModelAdmin
ModelAdmin
通过 django admin 页面,编辑数据库。下面介绍列表页优化和排序,admin.py
如下:
from django.contrib import admin
class ArticleControl(admin.ModelAdmin):
# 显示的字段
list_display = ('title', 'body', 'auth', 'create_time', 'update_time')
# 搜索条件
search_fields = ('title',) # 关联表的字段 auther__name
# 按字段排序 -表示降序
ordering = ('-create_time',)
# 每页显示10条
list_per_page = 10
# 可编辑字段
list_editable = ('auth',)
# 设置哪些字段可以点击进入编辑界面
list_display_links = ('title', 'body')
# 点右侧作者名称,快速找到相关内容
list_filter = ('auth', 'title')
# 时间分层
date_hierarchy = 'create_time'
# 控制actions的下拉框出现在页面的位置
actions_on_bottom = True
actions_on_top = True
# 是否在actions下拉框右侧显示选中的对象的数量
actions_selection_counter = True
# 注册Article表
admin.site.register(models.Article, ArticleControl)
或
from django.contrib import admin
class BaseControl(admin.ModelAdmin):
ordering = ('-id',)
list_per_page = 20
class ArticleControl(BaseControl):
# 显示的字段
list_display = ('title', 'body', 'auth', 'create_time', 'update_time')
# 注册Article表
admin.site.register(models.Article, ArticleControl)
工具
根据模型字段
,生成:"A", "B", "C",
这种格式的字符串。
$ python manage.py shell
from home_application.models import *
def gen_doc(obj):
r = ""
for i in obj.__dict__['_meta'].fields:
r += "\'"
r += i.name
r += "\', "
print(r)
gen_doc(Object)
$ python manage.py shell
from home_application.models import *
def gen_doc(obj):
l = obj.__dict__["__doc__"].replace(" ", "").split("(")[1].split(")")[0].split(",")
print(l)
r = ""
for i in l:
r += "\""
r += i
r += "\", "
print(r)
gen_doc(Object)
运维
# 修改数据库字符集
ALTER DATABASE db1 DEFAULT CHARACTER SET utf8;
# 修改表字符集
ALTER TABLE db1 DEFAULT CHARACTER SET utf8 COLLATE utf8_bin;
# 修改表已有数据为新的字符集
ALTER TABLE db1 CONVERT TO CHARACTER SET utf8 COLLATE utf8_bin;
# 修改主键字段
ALTER TABLE table1 MODIFY id bigint(20) NOT NULL AUTO_INCREMENT;
# 添加新字段
ALTER TABLE table1 ADD COLUMN uid int(11) NOT NULL;