使用 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 数据库的 approute_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 Nonedef allow_migrate(self, db, app_label, model_name=None, **hints):"""遗留数据库中的表不允许迁移"""if app_label in self.route_app_labels:return Falsereturn 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 = Falsedb_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 = Falsedb_table = 'city'verbose_name = '城市'verbose_name_plural = verbose_namedef __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 = Truedef delete(self, using=None, keep_parents=False):self.deleted = 1self.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)
