后端django操作数据库相关

django原生orm的使用步骤,从开头讲起

默认搭建好django基本项目,且测试访问ok后,进入项目目录,假设创建的项目名为lingxi。

新建一个app

python manage.py startapp pingshuo

注册这个app,配置数据库

进入项目设置文件
vim lingxi/settings.py
import pymysql!
pymysql.install_as_MySQLdb()

INSTALLED_APPS = [
前面是别的app
    'pingshuo',
]

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'lingxi',
        'HOST': '数据库ip',
        'USER': '用户名',
        'PASSWORD': '密码',
        'PORT': '端口',
    }
}

顺便配置里面的mysql数据库,数据库需要自己手动先建立好。

CREATE DATABASE mysite CHARACTER SET utf8;

一定要将字符编码设置为utf8,很多错误就是没正确设置编码导致的!

在该app的模型中写数据库模型类

如果直接手动在数据库建了表,则migrate会失败!!

但是只要第一次用migrate产生了表之后,可以额外手动修改表里没的如备注,和编码之类的(如果上面建数据库的试试没有设置编码为utf8,可能这里就能发现创建的表编码不是utf8)。总之就是每次用django的migrate操作了表之后,都去数据库那边看一眼。

vim pingshuo/models.py
from django.db import models

class UserInfo(models.Model):
    id = models.AutoField(db_column = 'id', primary_key = True, null = False)
    openid = models.CharField(db_column = 'openid', unique=True, max_length = 100, null = False, default = '0')
    nick_name = models.CharField(db_column = 'nick_name', max_length = 64, null = False, default = '')
    #  性别 0:未知、1:男、2:女
    gender = models.BooleanField(db_column = 'gender', null = False, default = '0')
    avatar_url = models.CharField(db_column = 'avatar_url', max_length = 255, null = False, default = '0')
    update_time = models.IntegerField(db_column = 'update_time', null = False, default = 0)

    class Meta:
        db_table = 'user_info'

如上创建的表,手动进数据库表中,看不到这里默认值,default是在django内部操作数据库之前,框架带上去的默认值。

《模型和字段》http://www.liujiangblog.com/course/django/95

一个自动增加的整数类型字段。通常你不需要自己编写它,Django会自动帮你添加字段:id = models.AutoField(primary_key=True),这是一个自增字段,从1开始计数。如果你非要自己设置主键,那么请务必将字段设置为primary_key=True。Django在一个模型中只允许有一个自增字段,并且该字段必须为主键!

《模型的元数据Meta》http://www.liujiangblog.com/course/django/99

建立联合索引用Meta中的indexs参数,demo如下,项目中为真正使用这个联合索引

class Meta:
    db_table = 'user_info'
    indexes = [
        models.Index(fields=['openid', 'nick_name'], name='openid_nick_name_idx'),
        # models.Index(fields=['first_name'], name='first_name_idx'),
    ]

联合唯一需要用 unique_together

unique_together = (('name', 'birth_day', 'address'),)

经验

  • 表中字段全部不要允许null,null会有各种坑。
  • 不要用太多复杂类型吧,比如时间直接用数字类型的时间戳存储即可。这样未来换框架,换模型啥的,对接会方便很多。
  • 增加多表表的约束往往也会对后期迭代产生复杂情况。能不用就不用。用代码层面控制。
  • 如果不给default,在某字段没有传入值的时候,会抛异常,所以一般都给default,但是在代码逻辑中,需要对某字段值是否存在做判断。
  • 当 django 用字段 BooleanField 时,mysql 是不支持 bool 类型的,当把一个数据设置成 bool 类型的时候,数据库会自动转换成 tinyint(1) 的数据类型。所以想让数据库里面用 tinyint 类型的时候,也在django模型中用 BooleanField。
  • 不过django用了BooleanField时在orm只能传入True、False,不能和传入别的数字。在需要用多余0,1的数字时候只能用IntegerField了。

makemigrations 和 migrate

如上在models.py中修改模型;
运行python manage.py makemigrations pingshuo为改动创建迁移记录;
运行python manage.py migrate pingshuo,将操作同步到数据库。

makemigrations 和 migrate 后面可以接app,也可以什么都不加,操作全部app

可去数据库表处修改编码和加备注了

django的orm目前(2018年11月25日)没有找到修改编码和修改备注的设置方法。

此后如果加字段或者修改表字段名等

修改后models.py后还是执行makemigrations 和 migrate 即可。

自定义django默认的admin后台管理

vim pingshuo/admin.py

from django.contrib import admin
from .models import UserInfo

class UserInfoAdmin(admin.ModelAdmin):
    list_display = ('id', 'openid', 'nick_name', 'gender', 'avatar_url','update_time')
    list_filter = ['gender', 'update_time']
admin.site.register(UserInfo, UserInfoAdmin)

每一个新加的model都需要在这里注册才能在django默认管理后台出现。

django的orm增删改查便捷操作

https://www.cnblogs.com/stuqx/p/7127980.html

自测发现:

  • xxx.objects.get() 获取不到的话会抛出异常
  • xxx.objects.filter() 获取到的结果是list,获取不到也不会抛异常
  • 如果save直接带着主键获取,已经有该记录的话,相当于update,但是假设不给某字段,会有变回默认值的风险,所以还是别这么用了。
# 假设自增唯一主键是id
a_user = UserInfo(id=1, openid='aaa', nick_name='nickhaha', gender=2)
a_user.save()  # 相当于更新id为1的记录,但这样假如不给某字段,有变回默认值的风险
a_user = UserInfo(openid='aaa', nick_name='nickhaha', gender=2)
a_user.save()  # 相当于新增一条记录
  • 下面这样能防止有多个元素满足的报错,但是没法防止取不到元素的报错
ret = UserInfo.objects.filter(id=1).get()
ret = [ret.openid, ret.nick_name]
  • 因此取出来的数如果能保证只有一个值,不报错的取法是filter结合if判断。所以建议用values()
    ret = UserInfo.objects.filter(id=3)
    if ret:
        # 取到值取出来用,这样是UserInfo对象
        ret_obj = ret.get()
        # 拿到这条记录的某个字段的值
        ret = ret_obj.nick_name
    else:
        # TODO: 没取到的处理
        ret = '无'

【推荐】values()返回一个包含数据的字典的queryset,而不是模型实例。

该方法接收可选的位置参数*fields,它指定values()应该限制哪些字段。

>>> Blog.objects.values()
<QuerySet [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]>
>>> Blog.objects.values('id', 'name')
<QuerySet [{'id': 1, 'name': 'Beatles Blog'}]>

values()更好用,因为如果取不到元素的时候,get()会报错,且要自己取出键值。而values()可以直接链式调用,取不到值就返回空集<QuerySet []>。如果想命中if判断可以用if not AreaStreet.objects.filter(id=1000).values():

print(AreaStreet.objects.filter(id=1000).get()) # 报错
print(AreaStreet.objects.filter(id=1000).values()) # 输出<QuerySet []>

values()方法还有关键字参数**expressions,这些参数将传递给annotate():

>>> from django.db.models.functions import Lower
>>> Blog.objects.values(lower_name=Lower('name'))
<QuerySet [{'lower_name': 'beatles blog'}]>

在values()子句中的聚合应用于相同values()子句中的其他参数之前。 如果需要按另一个值分组,请将其添加到较早的values()子句中。 像这样:

>>> from django.db.models import Count
>>> Blog.objects.values('author', entries=Count('entry'))
<QuerySet [{'author': 1, 'entries': 20}, {'author': 1, 'entries': 13}]>
>>> Blog.objects.values('author').annotate(entries=Count('entry'))
<QuerySet [{'author': 1, 'entries': 33}]>

demo

# 设置默认请求4条动态链接信息
count = json_dict.get('count') if json_dict.get('count') else 4
ret = DynamicNews.objects.all().values()[:count]
ret = list(ret)
res_dict = {
    'backend_code': 0,
    'msg': '查询动态链接信息成功',
    'dynamic_news_list': ret,
}
res_json_str = json.dumps(res_dict)
return HttpResponse(res_json_str)

分页

# 若未设定每页取10个
page_size = json_dict.get('pageSize') if json_dict.get('pageSize') else 10
offset = (page - 1) * page_size

ret = AreaStreetComment.objects.filter(is_show=1).order_by('-time')[offset : offset + page_size].values()

不返回QuerySets的API

http://www.liujiangblog.com/course/django/131

update_or_create(defaults=None, **kwargs) 该方法返回一个由(object, created)组成的元组,元组中的object是一个创建的或者是被更新的对象, created是一个标示是否创建了新的对象的布尔值。
update_or_create方法尝试通过给出的kwargs 去从数据库中获取匹配的对象。 如果找到匹配的对象,它将会依据defaults 字典给出的值更新字段。

eg:

    obj, created = UserInfo.objects.update_or_create(
        openid = json_dict.get('openid'),
        defaults = {
            'openid': json_dict.get('openid'),
            'nick_name': json_dict.get('nick_name'),
            'gender': json_dict.get('gender'),
            'avatar_url': json_dict.get('avatar_url'),
            'update_time': time.time()
        },
    )

orm中的聚合函数,算均值等

如下面语句是求AreaStreetComment表中area_street_id=1的所有记录的score字段的平均值

ret = AreaStreetComment.objects.filter(area_street_id=1).aggregate(Avg('score'))
# 若取不到记录则返回None,特殊情况需要判断
# 若能求出平均值,存到街区表里更新平均分数
if ret and ret.get('score__avg'):
    score_str = "{:.1f}".format(ret.get('score__avg'))
    AreaStreet.objects.filter(id=area_street_id).update(score=score_str)

本app和跨app调用模型或者函数

django 的跨app引用文件是通过import 来实现的,但是import 的路径查找和标准的import 不太一样。

在luohu这个app的views.py中如:

from .models import Luohu, setUserLuohuInfo
from pingshuo.models import UserInfo

调用本app里面的模型,用from .models import Luohu, setUserLuohuInfo 其中Luohu为对接数据库的类模型,setUserLuohuInfo 为封装的函数,其实现如:

def setUserLuohuInfo(user_id, detail):
   # return Luohu.insert(we_id=we_id, test=test).execute() 测试发现用insert在跨模块调用时,会报找不到insert
    a_user = Luohu(user_id=user_id, detail=detail)
    return a_user.save()

跨app调用,如调用pingshuo这个app里面的模型,直接用【app名】.【模型类或者函数】这样的形式。

参考:https://www.cnblogs.com/JiangLe/p/6911667.html

借助db.connect用原生sql语句操作

from django.db import connection

def index(request):
    SQL_str = "select * from user_info where openid like '%aa%'"
    cursor = connection.cursor()
    cursor.execute(SQL_str)
    ret = cursor.fetchall()

又如插入操作:

    SQL_str = "INSERT INTO `user_info` (`openid`, `gender`, `nick_name`, `first_visit_time`) VALUES ('cba', 0, '', 12345);"
    cursor = connection.cursor()
    ret = cursor.execute(SQL_str)

上面语句均可改为使用with做上下文管理

with connection.cursor() as cursor:

如果不习惯用orm,或者orm找不到想要的操作,自己直接上原生sql,可以在操作前,过滤一下字符串,防止sql注入。

raw() 原始的SQL查询

raw(raw_query, params=None, translations=None)

接收一个原始的SQL查询,执行它并返回一个django.db.models.query.RawQuerySet实例。

这个RawQuerySet实例可以迭代,就像普通的QuerySet一样。

脚本中单独使用django的ORM模型的方法

测试或者调试都有可能用到。

https://blog.csdn.net/kkevinyang/article/details/79446744

https://blog.csdn.net/qq_33547169/article/details/78408819

上面两个博客单独弄还是不对,结合之,实现如下。

假设需要单独运行的脚本在 lingxi/script/shanghai-area.py

import sys
import os

BASE_DIR = os.path.dirname(os.path.abspath(__file__))  # 定位到你的django根目录
sys.path.append(os.path.abspath(os.path.join(BASE_DIR, os.pardir)))
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "lingxi.settings")
import django
django.setup()

from luohu.models import AreaStreet
原文地址:https://www.cnblogs.com/xrszff/p/11847143.html