使用 django 做管理后台,数据管理后台相关内部系统数据库与业务后台数据库隔离,是非常实用的高效开发方案。
实践中业务后台并发量高,需要做集群或分布式扩展,而管理后台一般没有这种问题。
管理后台与业务后台分离,往往是单体项目向分布式项目演变的第一步。
创建项目
django-admin startproject xxxx
配置数据库
数据库管理后台不会改变原有线上数据库结构,只对其数据进行增删改查。
管理后台需要权限配置,这部分数据库表可以与线上数据库分开,通过 django 自带的数据库路由实现。
例子中管理后台数据库为 default,线上数据库为 running
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
},
'running': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'xxx',
'USER': 'xxx',
'PASSWORD': 'xxx',
'HOST': '127.0.0.1',
'PORT': '3306',
'OPTIONS': {
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
'charset': 'utf8mb4',
},
},
}
如果需要连接 mysql,需要安装连接驱动
pip install mysqlclient
使用数据库路由
创建数据库路由配置
class DatabaseRouter:
# running 数据库的 app
route_app_labels = {'users', 'order'}
def db_for_read(self, model, **hints):
if model._meta.app_label in self.route_app_labels:
return 'running'
return 'default'
def db_for_write(self, model, **hints):
if model._meta.app_label in self.route_app_labels:
return 'running'
return 'default'
def allow_relation(self, obj1, obj2, **hints):
return None
def allow_migrate(self, db, app_label, model_name=None, **hints):
"""遗留数据库中的表不允许迁移"""
if app_label in self.route_app_labels:
return False
return True
在 settings 中注册 设置创建好的路由配置
DATABASE_ROUTERS = ['fastdb.router.DatabaseRouter']
创建app
django-admin startapp xxx
生成 model 类到 models.py
python manage.py inspectdb 会根据数据库自动生成对应的 model 类
—database=running : 指定数据库
导入到 app/models.py
python manage.py inspectdb --database=running > xxx/models.py
inspectdb —database=running 表名 表名 表名, 可以指定导出具体的表
导出的模型 Meta 信息中包含
class Meta:
managed = False
db_table = 'admin_permission'
managed = False 意思是这个对象实体不再与数据库中的结构保持一致,也就是不会去同步数据库
注册app
INSTALLED_APPS = [
'xxx',
]
注册model 到 admin
admin.site.register(xxx)
如需同步数据库
migrate 管理命令一次只在一个数据库上进行操作。默认情况下,它在 default 数据库上操作,但提供 —database 的话,它可以同步到不同数据库。如果想同步 running 数据库:
python manage.py migrate running --database=running
但是同步会生成额外的 django-migrations 表,
使用 ForeignKey 数据库不做约束
db_constraint 设置为 false 数据库不需要外键约束,但 admin 用起来和有外键一样
db_column 指定 数据库 的字段名
class City(LogicalDeleteModel):
id = models.BigAutoField(primary_key=True)
country = models.ForeignKey(Country, verbose_name='所属国家', on_delete=models.CASCADE,
db_column='country_id', db_constraint=False)
name = models.CharField(max_length=30, verbose_name='城市名')
image = models.ImageField('图片', blank=True, null=True, upload_to=image_upload_to)
latitude = models.DecimalField('纬度', max_digits=10, decimal_places=7, blank=True, null=True)
longitude = models.DecimalField('经度', max_digits=10, decimal_places=7, blank=True, null=True)
sort = models.PositiveIntegerField('排序desc')
published = models.BooleanField('是否上线', default=1)
create_time = models.DateTimeField('创建时间', auto_now_add=True)
update_time = models.DateTimeField('更新时间', auto_now=True)
deleted = models.BooleanField(default=0)
class Meta:
managed = False
db_table = 'city'
verbose_name = '城市'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
处理 BooleanField 与逻辑删除
自定义 model 集成下面的 LogicalDeleteModel
get_queryset 过滤 deleted
class LogicalDeleteManager(Manager):
def get_queryset(self):
return super(LogicalDeleteManager, self).get_queryset().filter(deleted=False)
class LogicalDeleteModel(models.Model):
objects = LogicalDeleteManager()
class Meta:
abstract = True
def delete(self, using=None, keep_parents=False):
self.deleted = 1
self.save()
即使 数据库使用的是 tinyint 字段标识 bool 值,django 这边也可已直接使用 BooleanField,自动映射成 0 1
时间处理
对于 create_time 与 update_time
即使数据库中指定了
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
这里也要加 auto_now_add 与 auto_now,否则会被认为设置为空
create_time = models.DateTimeField('创建时间', auto_now_add=True)
update_time = models.DateTimeField('更新时间', auto_now=True)