Django 数据库操作

2021-11-30  本文已影响0人  李霖弢

通过ORM(Object-Relation Mapping,对象关系映射),把一个类对应到一个表,类的每个实例对应表中的一条记录,类的每个属性对应表中的每个字段,开发人员只需像操作对象一样从数据库操作数据。

注意,自定义的mysql_client.pyredis_client.py中应使用单例模式,保证同一进程下只有一个数据库实例。

数据库配置

settings.pyDATABASES中可以配置数据库,默认使用SQLite3。
如使用MySQL,则需要安装驱动模块:mysqlclient,这是MySQLdb的分支,用于兼容Python3,安装时也会附带MySQLdb包。Django进一步封装了该驱动模块以实现内置的ORM模型(见django.db.backends.mysql.baseDatabaseWrapper类),也可以创建其同名子类并进一步自定义封装:

# Linux
pip3 install mysqlclient -i https://pypi.tuna.tsinghua.edu.cn/simple
# Windows
pip3 install mysqlclient @ file:///D:/ChinaDuanCai/DRF/static/mysqlclient-2.0.3-cp37-cp37m-win_amd64.whl 

# settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',  # 也可以自定义为封装后的模块路径
        'NAME': 'drf',  # 连接的数据库  #一定要存在的数据库名
        'HOST': '127.0.0.1',  # mysql的ip地址
        'PORT': 3306,  # mysql的端口
        'USER': 'root',  # mysql的用户名
        'PASSWORD': 'vl1qaz@WSX'  # mysql的密码
    }
}

关于为什么不用Pymysql,以及通过连接池解决并发问题,详见https://www.jianshu.com/p/e51dc4f6356a

设计模型

# models.py
from django.db import models
from DRF.settings import MEDIA_ROOT

class Type(models.Model):
    id = models.AutoField(primary_key=True, unique=True)
    name = models.CharField(verbose_name="类型名", max_length=10) # verbose_name用于自带的admin页面中显示名称

def cb():
    return None

def upload_to(instance, filename):
    return "/".join([MEDIA_ROOT, "upload", filename])

class Blog(models.Model):
    sex_choices = (
        (0, '男'),
        (1, '女'),
    )

    # 自增int + 主健 + 唯一值,model中无主键列时会自动创建该id字段
    id = models.AutoField(primary_key=True, unique=True)

    # 建立索引
    code = models.CharField(max_length=64, null=True, verbose_name="编号", db_index=True)

    name = models.CharField(verbose_name="姓名", max_length=32)
    age = models.IntegerField("年龄")
    sex = models.SmallIntegerField('性别', choices=sex_choices, default=0)
    has_hair = models.BooleanField()
    phone = models.CharField("手机号", max_length=14, null=True)  # 该字段可以为null

    # 超过254个字符应使用TextField,对应MySQL中的longtext
    description = models.TextField("简介", blank=True)  # 可以为空字符串

    register_date = models.DateField('注册日期', auto_now_add=True)  # 首次添加时使用当前时间
    update_time = models.DateTimeField('注册时间', auto_now=True)  # 每次保存时使用当前时间
    is_delete = models.BooleanField(default=False)
    type_id = models.ForeignKey(  # 外键
        Type,  # 主表
        # related_name="blog_set",  # 默认即为 子表的小写名称_set
        on_delete=models.SET(cb),  # 主表对应行删除时的子表行为
        # on_delete=models.CASCADE,
        null=True)

    # models.FileField 用于接收上传的文件,其在数据库里只是一个路径字符串
    # upload_to用于指定MEDIA_ROOT下的相对路径,也可以传入一个方法用于自定义保存逻辑。
    # upload_file = models.FileField(upload_to="upload/") #指向 settings.MEDIA_ROOT 下的 upload 子目录
    # upload_file = models.FileField(upload_to="upload/%Y/%m/%d")
    upload_file = models.FileField(upload_to=upload_to, default="")

    # 自定义实例print输出内容
    def __str__(self):
        return f"name={self.name}"

    class Meta:
        db_table = 'blog'  # 通过db_table自定义数据表名

应用模型

python manage.py makemigrations
python manage.py makemigrations app_name
python manage.py migrate
python manage.py migrate app_name

QuerySet

查询集,也称查询结果集,表示ORM从数据库中获取的对象(model实例)集合。
可迭代,可转为列表,也可以直接索引或切片,支持链式调用:

items = Blog.objects.all()
# 迭代
for item in items: 
    print(item)
# 转为列表
[item for item in items]
list(items)
# 索引、切片
items[0]
items[1:3]
model 实例对象
query_set = Blog.objects.all()
query_set[0].name="new" # 无效
query_set.first().name="new" # 无效
new_item = query_set[0]
new_item.name="new" 
new_item.save()
query_set = Blog.objects.all()
query_set[0].delete()
from django.forms.models import model_to_dict
model_to_dict(Blog.objects.first(), exclude=['create_time', 'update_time'])
惰性查询

创建查询集本身不进行数据库访问,仅读取其值时才真正运行:

query_set = Book.objects.all() # 不会查询数据库
print(query_set) # 查询数据库
缓存机制

每个查询集都包含一个缓存来最小化对数据库的访问。
对原本的查询集执行filter等操作会创建一个新的查询集,拥有独立的缓存空间。
首次对整个查询集求值时,发生数据库访问,其查询结果会存入缓存,后续对查询集的取值直接从缓存中读取。

bool(query_set)
if query_set:
    ...

可使用exist方法节省性能,此时本质上只查了一条看是否存在,因此不会加入缓存:

if query_set.exists():# SELECT (1) AS `a` FROM `app01_book` LIMIT 1
    ...
[book for book in query_set]
bool(query_set)
any_obj in query_set
list(query_set)

当数据量过大时,为防止占用过多内存,可用iterator迭代器。迭代器也会在第一次被调用时完成完整的查询,只是不会进入缓存:

objs = Book.objects.all().iterator()
for obj in objs:
    print(obj.name)
# 强调:再次遍历没有打印,因为迭代器已经在上一次遍历(next)到最后一次了,没得遍历了
for obj in books:
  print(obj.name)
res = Book.objects.only('name')

for obj in res:
    print(obj.name) # .name不走数据库 .其他字段会重新走数据库查询
objects 与 链式方法

models中的类均继承models.Model,也继承了其 objects 属性用于管理模型。
其具有如下链式方法,如返回值是QuerySet则可以继续调用:

def db_retrieve2(request):
    vvs = Blog.objects.all().aggregate(Avg('age'), Sum('age'), count=Count('*'),
                                       max_age=Max('age'), min_age=Min('age'))
    return JsonResponse(vvs, json_dumps_params={'ensure_ascii': False})
vvs = Blog.objects.values("age").annotate(Min('id'))
# SELECT `blog`.`age`, MIN(`blog`.`id`) AS `id__max` FROM `blog` GROUP BY `blog`.`age`
## select提供简单数据
# SELECT age, (age > 18) as is_adult FROM myapp_person ORDER BY is_adult;
Person.objects.all().extra(select={'is_adult': "age > 18"}, order_by=['is_adult'])  # 加在select后面

## where提供查询条件
# SELECT * FROM myapp_person WHERE first||last ILIKE 'jeffrey%';
Person.objects.all().extra(where=["first||last ILIKE 'jeffrey%'"])  # 加一个where条件

## table连接其它表
# SELECT * FROM myapp_book, myapp_person WHERE last = author_last
Book.objects.all().extra(table=['myapp_person'], where=['last = author_last']) # 加from后面

## params添参数
# !! 错误的方式 !!
first_name = 'Joe'  # 如果first_name中有SQL特定字符就会出现漏洞
Person.objects.all().extra(where=["first = '%s'" % first_name])
# 正确方式
Person.objects.all().extra(where=["first = '%s'"], params=[first_name])
双下划线过滤参数

__exact 精确等于 (缺省值)
__iexact 精确等于 忽略大小写
__contains 包含(sqlite中,等同于icontains)
__icontains 包含 忽略大小写
__regex 正则
__iregex 正则 忽略大小写
__gt 大于
__gte 大于等于
__lt 小于
__lte 小于等于
__in 存在于一个 list 内
__startswith 以...开头
__istartswith 以...开头 忽略大小写
__endswith 以...结尾
__iendswith 以...结尾,忽略大小写
__range 在...范围内
__year 日期字段的年份
__month 日期字段的月份
__day 日期字段的日
__isnull 取 True 或 False,对应 IS NULL 和 IS NOT NULL 的 SQL 查询

Q 查询

使用符号&表示与(或直接隔开),|表示或,~表示非,将多个Q对象进行组合,合并成一个新的Q对象。
Q对象和普通关键词参数共同使用时,应放在普通关键词参数前面

from django.db.models imports Q
News.objects.get(
    ~Q(question__startswith='Who'),
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
    is_delete=1)
)
F 查询

用于引用该model下的另一个字段

from django.db.models imports F
News.objects.filter(aaa__gt=F('bbb')*2)
跨表查询优化
res = Book.objects.select_related('publish')

for obj in res:
    print(obj.publish.name)

article= Article.objects.select_related("blog__user").get(nid=1)
print(article.blog.user.username)

可以直接调用create方法新增,也可以实例化model并在赋值后save。
两种方法返回值均为model的实例。

Blog.objects.create(name="VV", age=8, has_hair=False)

vv = {"name": "VV2", "age": 6, "has_hair": False}
Blog.objects.create(**vv)

instance = Blog(name = "VV", age=7)
instance.has_hair= False
instance.save()

queryset实例 都具有 delete 方法
注意该方式是硬删除

Blog.objects.all().delete()
Blog.objects.first().delete()
r = Blog.objects.create(name="VV", age=8, has_hair=False)
r.delete()

queryset 具有 update 方法
实例 可以修改后调用 save 方法

Blog.objects.all().update(age=18)
Blog.objects.first().update(age=18)
r = Blog.objects.create(name="VV", age=8, has_hair=False)
r.age = 18
r.save()

通过上述QuerySet语法进行查询,注意缓存的使用即可。

Entry.objects.filter(is_delete=0,headline__startswith="What")
.exclude(pub_date__gte=datetime.date.today())
.order_by('-title', 'pub_date')

外键

创建

其中on_delete可选的值包括:CASCADE、PROTECT、SET_NULL、SET_DEFAULT、SET()

# models.py
    type_id = models.ForeignKey(  # 外键
        Type,  # 主表
        # related_name="blog_set",  # 默认即为 子表的小写名称_set
        on_delete=models.SET(cb),  # 主表对应行删除时的子表行为
        # on_delete=models.CASCADE,
        null=True)
赋值

Django中,外键属性赋值时,需直接赋值所在行的实例对象:

def db_update_2(request):
    vv = Blog.objects.first()
    vv.type_id = Type.objects.get(id=1)
    vv.save()
    return JsonResponse(model_to_dict(vv), json_dumps_params={'ensure_ascii': False})
主表实例查看关联的子表QuerySet

默认可以通过主表实例.子表小写名称_set.all()获取。
也可以在子表类中声明外键字段时,通过related_name自定义名称。

type = Type.objects.first()
type.blog_set.all()

查看Django实际的数据库语句

from django.db import connection
print(connection.queries[-1]['sql'])
上一篇 下一篇

猜你喜欢

热点阅读