后端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