第七章——模型与数据库
自定义ORM框架
class Field(object):
'''模型字段的基本类'''
def __init__(self, name, column_type):
self.name = name
self.column_type = column_type
def __str__(self):
return "<%s:%s>" % (self.__class__.__name__, self.name)
class StringField(Field):
'''模型字段的字符类型'''
def __init__(self, name):
super(StringField, self).__init__(name, 'varchar(100)')
class IntegerField(Field):
'''模型字段的整数类型'''
def __init__(self, name):
super(IntegerField, self).__init__(name, 'bigint')
class ModelMetaClass(type):
'''定义元类,控制Modle对象的创建'''
def __new__(cls, name, bases, attrs):
mappings = dict()
for k, v in attrs.items():
# 保存类属性和列的映射关系到mappings字典
if isinstance(v, Field):
print('Found mapping: %s==>%s' % (k, v))
mappings[k] = v
for k in mappings.keys():
# 将类属性移除, 使定义的字段不污染USER类属性
attrs.pop(k)
# 创建类的时候添加一个__table__属性
attrs['__table__'] = name.lower()
# 保存属性和类的映射关系,创建类时添加一个__mappings__类属性
attrs['__mappings__'] = mappings
return super().__new__(cls, name, bases, attrs)
# 定义model类
class Model(metaclass=ModelMetaClass):
def __init__(self, *args, **kwargs):
self.kwargs = kwargs
super().__init__()
def save(self):
fields, params = [], []
for k, v in self.__mappings__.items():
fields.append(v.name)
params.append(str(self.kwargs[k]))
sql = 'insert into %s (%s) values (%s)'
sql = sql % (self.__table__, ','.join(fields), ','.join(params))
print("SQL: %s" % sql)
class User(Model):
id = IntegerField('id')
name = StringField('username')
email = StringField('email')
password = StringField('password')
u = User(id=12, name='dj', email='dj@qq.com', password='123')
u.save()
Django 中的几个常用命令
python manage.py sqlmigrate index(应用名) 0001——initial(数据名) # 查看其SQL 语句
python manage.py dumpdata > data.json # 将整个项目的数据从数据库中导出并保存为json格式的数据
python manage.py dumpdata index(应用名) > data.json # 导出index应用的数据
python manage.py dumpdata index.Somemodels > data.json # 导出index应用下某个模型的数据
python manage.py loaddata data.json # 将data.json 数据导入到数据库中
Django 中的数据表操作(多使用,多练习)原书172页面
Django 执行SQL语句
extra:结果集修改器,一种提供额外查询参数的机制
raw: 执行原始SQL并返回模型实例对象
execute: 直接执行自定义SQL,需要先连接数据库
数据库事务
from django.db import transaction
# transaction.atomic() # 在视图类或者视图函数中使用事务
# transaction.savepoint() # 开启事务
# transaction.savepoint_commit() # 提交事务
# transaction.savepoint_rollback() # 回滚事务
from django.db.models import F
@transaction.atomic # 或者使用 with transaction.atomic()
def index(request):
# 开启事务保护
sid = transaction.savepoint()
try:
id = request.GET.get('id', '')
if id:
t = TimeInfo.objects.filter(id=id)
t.update(payment=F('payment') + 1)
print('Done')
# 提交事务
# 如不设置,程序执行完会自动提交
transaction.savepoint_commit(sid)
else:
TimeInfo.objects.update(payment=F('payment') - 1)
transaction.savepoint_rollback(sid)
except Exception as e:
transaction.savepoint_rollback(sid)
return render(request, 'index.html', locals())
说明一下 F 的用法
from django.db.models import F
F() 的执行不经过 python解释器,不经过本机内存,是生成 SQL语句的执行。
# Tintin filed a news story!
reporter = Reporters.objects.get(name='Tintin')
reporter.stories_filed += 1
reporter.save()
# 等于
from django.db.models import F
reporter = Reporters.objects.get(name='Tintin')
reporter.stories_filed = F('stories_filed') + 1
reporter.save()
简写了代码:
Reporter.objects.all().update(stories_filed=F('stories_filed') + 1)
多数据库的连接与使用
# settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
},
'db1': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'db1', # mysql中创建对应的数据库
'USER': 'root',
'PASSWORD': '123456',
'HOST': '192.168.241.128',
'PORT': '3306',
},
'db2': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'db2', # mysql中创建对应的数据库
'USER': 'root',
'PASSWORD': '123456',
'HOST': '192.168.241.128',
'PORT': '3306',
},
}
DATABASE_ROUTERS = ['web_test_01.dbRouter.DbAppsRouter']
# 设置每个app模型使用的数据库
DATABASE_APPS_MAPPINGS = {
# {'app_name': 'database_name'}
'admin': 'default',
'app1': 'db1',
'app2': 'db2',
}
定义数据库读写、数据表关系和数据迁移的方法
# settings.py 同目录创建dbRounter.py
from django.conf import settings
DATABASE_MAPPING = settings.DATABASE_APPS_MAPPINGS
class DbAppsRouter(object):
def db_for_read(self, model, **kwargs):
if model._meta.app_label in DATABASE_MAPPING:
return DATABASE_MAPPING[model._meta.app_label]
return None
def db_for_write(self, model, **kwargs):
if model._meta.app_label in DATABASE_MAPPING:
return DATABASE_MAPPING[model._meta.app_label]
return None
def allow_relation(self, obj1, obj2, **kwargs):
db_obj1 = DATABASE_MAPPING.get(obj1._meta.app_label)
db_obj2 = DATABASE_MAPPING.get(obj2._meta.app_label)
if db_obj1 and db_obj2:
if db_obj1 == db_obj2:
return True
else:
return False
return None
def allow_migrate(self, db, app_label, model_name=None, **kwargs):
if db in DATABASE_MAPPING.values():
return DATABASE_MAPPING.get(app_label) == db
elif app_label in DATABASE_MAPPING:
return False
return None
多数据库的使用
# 创建多个应用,在其对应的modles.py 下创建模型
# app1.models.py
from django.db import models
# Create your models here.
class City(models.Model):
name = models.CharField(max_length=20)
def __str__(self):
return self.name
class Meta:
app_label = 'app1'
db_table = 'city'
verbose_name = '城市信息表'
# app2.models.py
from django.db import models
# Create your models here.
class PersonInfo(models.Model):
name = models.CharField(max_length=20)
age = models.CharField(max_length=20)
live = models.CharField(max_length=20)
def __str__(self):
return self.name
class Meta:
app_label = 'app2'
db_table = 'personinfo'
verbose_name = '个人信息表'
创建数据表
python manage.py makemigrations
python manage.py migrate # 在数据库default(db.sqlite3)中创建内置功能的数据表
python manage.py migrate --database=db1 # 在数据库db1(应用app1)中创建数据表
python manage.py migrate --database=db2 # 在数据库db1(应用app2)中创建数据表
动态创建模型和数据表
例如:一张储存商品信息的数据表,商品销量每天更新,如果将商品每天的销量存储在商品信息表,就需要365个字段。显然不仅合适。
为解决这类需求,将商品每天的销量用新的数据表来存储。
Django 并没有为我们提供动态创建模型和数据库的方法,需要自己在ORM的基础上定义
# 可在models.py 下添加如下函数
def createModel(name, fields, app_label, options=None):
'''
动态定义模型对象
:param name: 模型的命名
:param fields: 模型字段
:param app_label: 模型所属的项目应用
:param options: 模型Meta类的设置属性
:return: 返回模型对象
'''
class Meta:
pass
setattr(Meta, 'app_label', app_label) # 用于设置属性值,该属性不一定是存在的
# 设置模型Meta类的属性
if options is not None:
for key, value in options.items():
setattr(Meta, key, value)
# 添加模型属性和模型字段
attrs = {'__module__':f'{app_label}.models', 'Meta': Meta}
attrs.update(fields)
# 使用type动态创建类
return type(name, (models.Model,), attrs)
def createDB(model):
'''
使用ORM数据迁移创建数据表
:param model: 模型对象
:return:
'''
from django.db import connection, ConnectionHandler # connection 默认使用default数据库
from django.utils.connection import ConnectionProxy
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
# 创建数据表必须使用try except 因为数据表已经存在的情况下会显示异常
connections = ConnectionHandler()
connection = ConnectionProxy(connections, alias='db1') # 多数据下 可自定义连接的数据库
try:
with BaseDatabaseSchemaEditor(connection) as editor:
editor.create_model(model=model) # 在连接的数据库中生成数据表
except Exception as e:
print(f"error: {e}")
def createNewTab(model_name):
'''
定义模型对象和创建相应的数据表
:param model_name: 模型名称(数据表名称)
:return: 返回模型对象,便于视图执行增删改查
'''
fields = {
'id': models.AutoField(primary_key=True),
'product': models.CharField(max_length=20),
'sales': models.IntegerField(),
'__str__':lambda self:str(self.id),
}
options = {
'verbose_name': model_name,
'db_table': model_name,
}
m = createModel(name=model_name, fields=fields, app_label='app1', options=options)
print('mm', m) # mm <class 'app1.models.sales2024-02-29'>
print('type', type(m)) # type <class 'django.db.models.base.ModelBase'>
createDB(m)
return m
# views.py
from .models import createNewTab
import time
# Create your views here.
def indexView(request):
today = time.localtime(time.time())
model_name = f"sales{time.strftime('%Y-%m-%d', today)}" # 每天更新模型名
model_name = createNewTab(model_name) # 创建对应的数据表
model_name.objects.create( # 填充数据
product='Django',
sales = 666,
)
return HttpResponse('done')
MySQL分表功能
当数据表的数据量过大的时候,数据读写速度会越来越慢,从而增加了网站的响应时间,不利于用户体验。为了提高性能,可以对数据量比较大的数据表进行分表处理,MySQL数据库内置了分表功能,分表功能使用数据表引擎MyISAM实现。
# settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
},
'db1': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'db1', # mysql中创建对应的数据库
'USER': 'root',
'PASSWORD': '123456',
'HOST': '192.168.241.128',
'PORT': '3306',
'OPTIONS': {'init_command': 'SET default_storage_engine=MyISAM',}, # 添加分表引擎
},
'db2': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'db2', # mysql中创建对应的数据库
'USER': 'root',
'PASSWORD': '123456',
'HOST': '192.168.241.128',
'PORT': '3306',
},
}
定义数据表
def create_table(sql):
'''创建数据表必须使用try except 因为数据表已经存在的情况下会显示异常'''
from django.db import ConnectionHandler # connection 默认使用default数据库
from django.utils.connection import ConnectionProxy
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
# 创建数据表必须使用try except 因为数据表已经存在的情况下会显示异常
connections = ConnectionHandler()
connection = ConnectionProxy(connections, alias='db1') # 多数据下 可自定义连接的数据库
try:
with BaseDatabaseSchemaEditor(connection) as editor:
editor.execute(sql=sql) # 在连接的数据库中生成数据表
except Exception as e:
print(f"error: {e}")
# 创建分表 # 建议分表的主键ID设为字符型
tb_list = []
for i in range(3):
sql = f'''create table if not exists person{i} (
id int primary key auto_increment ,
name varchar(20),
sex tinyint not null default '0')
ENGINE=MyISAM DEFAULT CHARSET=utf8
AUTO_INCREMENT=1 ;'''
create_table(sql=sql)
tb_list.append(f"person{i}")
# 创建总表
# tb_str 将分表联合到总表
tb_str = ','.join(tb_list)
sql_all = f"""create table if not exists allperson(
id int primary key auto_increment ,
name varchar(20),
sex tinyint not null default '0')
ENGINE=MERGE UNION=({tb_str}) INSERT_METHOD=LAST
CHARSET=utf8 AUTO_INCREMENT=1 ;"""
create_table(sql=sql_all)
class PerInfo(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=20)
sex = models.IntegerField()
def __str__(self):
return self.name
class Meta:
verbose_name = '人员信息'
managed = False # 代表在数据迁移过程中不会创建数据表
db_table = 'allperson' # 模型映射数据库中特定的数据表
Django——MySQL 分表功能, 将数据写入到数据量最小的分表中
import pymysql
# 连接 MySQL 数据库
conn = pymysql.connect(
host='192.168.***.128', # 主机名
port=3306, # 端口号,MySQL默认为3306
user='root', # 用户名
password='123456', # 密码
database='db1', # 数据库名称
)
# 查询数据表的数据量
def func1(table_name, connection=conn):
# try:
with connection.cursor() as cursor:
# 编写SQL查询语句,查询数据表中的数据量
sql = f"SELECT COUNT(*) FROM {table_name}"
cursor.execute(sql)
# 获取查询结果
result = cursor.fetchone()
# 打印数据表中的数据量
if result:
count = result[0]
print(f"数据表 {table_name} 中的数据量为: {count}")
return count
else:
print(f"数据表 {table_name} 是空的")
# finally:
# # 关闭数据库连接
# connection.close()
# 找出数据量最小的数据表
def func2():
table_list = ['person0', 'person1', 'person2']
tables = {}
for table_name in table_list:
c = func1(table_name)
tables[table_name] = c
sorted_items = sorted(tables.items(), key=lambda item: item[1])
if sorted_items:
return sorted_items[0] # 返回元组(表名,数据量)
# 数据表中插入数据
def func3(table_name, connection=conn):
try:
with connection.cursor() as cursor:
# 编写SQL插入语句
sql = """
INSERT INTO {table_name} (id, name, sex) VALUES (%s, %s, %s)
""".format(table_name=table_name)
# 要插入的数据
data = ('8', 'hello8', '38')
# 执行SQL语句
cursor.execute(sql, data)
# 提交事务
connection.commit()
# 打印插入成功的消息
print("数据插入成功!")
finally:
# 关闭数据库连接
connection.close()
Django——MySQL 分表功能, 将数据随机写入到各个分表中
import pymysql
# 连接 MySQL 数据库
conn = pymysql.connect(
host='192.168.***.128', # 主机名
port=3306, # 端口号,MySQL默认为3306
user='root', # 用户名
password='123456', # 密码
database='db1', # 数据库名称
)
# 随机向数据表中插入数据
def func3(connection=conn):
import random
table_list = ['person0', 'person1', 'person2'] # 数据表
table_name = random.choice(table_list)
try:
with connection.cursor() as cursor:
# 编写SQL插入语句
sql = """
INSERT INTO {table_name} (id, name, sex) VALUES (%s, %s, %s)
""".format(table_name=table_name)
# 要插入的数据
data = ('8', 'hello8', '38')
# 执行SQL语句
cursor.execute(sql, data)
# 提交事务
connection.commit()
# 打印插入成功的消息
print("数据插入成功!")
finally:
# 关闭数据库连接
connection.close()