Django ORM 介绍

发布时间: 更新时间: 总字数:2834 阅读时间:6m 作者: IP上海 分享 网址

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",)
  • 模型集成和 Choices 使用
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)
  • 在 filter 中使用 F对象

阅读量是字数 10 倍的 Article

for a in Article.objects.filter(read__gt=F('count')*10):
    print(a.name)
  • 在排序中使用F对象
# 根据 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
  • settings.py
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",这种格式的字符串。

  • django 2.x
$ 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)
  • django 1.x
$ 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;
Home Archives Categories Tags Statistics
本文总阅读量 次 本站总访问量 次 本站总访客数