使用 django 做管理后台,数据管理后台相关内部系统数据库与业务后台数据库隔离,是非常实用的高效开发方案。

实践中业务后台并发量高,需要做集群或分布式扩展,而管理后台一般没有这种问题。
管理后台与业务后台分离,往往是单体项目向分布式项目演变的第一步。

创建项目

  1. django-admin startproject xxxx

配置数据库

数据库管理后台不会改变原有线上数据库结构,只对其数据进行增删改查。
管理后台需要权限配置,这部分数据库表可以与线上数据库分开,通过 django 自带的数据库路由实现。
例子中管理后台数据库为 default,线上数据库为 running

  1. DATABASES = {
  2. 'default': {
  3. 'ENGINE': 'django.db.backends.sqlite3',
  4. 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
  5. },
  6. 'running': {
  7. 'ENGINE': 'django.db.backends.mysql',
  8. 'NAME': 'xxx',
  9. 'USER': 'xxx',
  10. 'PASSWORD': 'xxx',
  11. 'HOST': '127.0.0.1',
  12. 'PORT': '3306',
  13. 'OPTIONS': {
  14. 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
  15. 'charset': 'utf8mb4',
  16. },
  17. },
  18. }

如果需要连接 mysql,需要安装连接驱动

  1. pip install mysqlclient

使用数据库路由

创建数据库路由配置

  1. class DatabaseRouter:
  2. # running 数据库的 app
  3. route_app_labels = {'users', 'order'}
  4. def db_for_read(self, model, **hints):
  5. if model._meta.app_label in self.route_app_labels:
  6. return 'running'
  7. return 'default'
  8. def db_for_write(self, model, **hints):
  9. if model._meta.app_label in self.route_app_labels:
  10. return 'running'
  11. return 'default'
  12. def allow_relation(self, obj1, obj2, **hints):
  13. return None
  14. def allow_migrate(self, db, app_label, model_name=None, **hints):
  15. """遗留数据库中的表不允许迁移"""
  16. if app_label in self.route_app_labels:
  17. return False
  18. return True

在 settings 中注册 设置创建好的路由配置

  1. DATABASE_ROUTERS = ['fastdb.router.DatabaseRouter']

创建app

  1. django-admin startapp xxx

生成 model 类到 models.py

python manage.py inspectdb 会根据数据库自动生成对应的 model 类
—database=running : 指定数据库

导入到 app/models.py

  1. python manage.py inspectdb --database=running > xxx/models.py

inspectdb —database=running 表名 表名 表名, 可以指定导出具体的表

导出的模型 Meta 信息中包含

  1. class Meta:
  2. managed = False
  3. db_table = 'admin_permission'

managed = False 意思是这个对象实体不再与数据库中的结构保持一致,也就是不会去同步数据库

注册app

  1. INSTALLED_APPS = [
  2. 'xxx',
  3. ]

注册model 到 admin

  1. admin.site.register(xxx)

如需同步数据库

migrate 管理命令一次只在一个数据库上进行操作。默认情况下,它在 default 数据库上操作,但提供 —database 的话,它可以同步到不同数据库。如果想同步 running 数据库:

  1. python manage.py migrate running --database=running

但是同步会生成额外的 django-migrations 表,

使用 ForeignKey 数据库不做约束

db_constraint 设置为 false 数据库不需要外键约束,但 admin 用起来和有外键一样
db_column 指定 数据库 的字段名

  1. class City(LogicalDeleteModel):
  2. id = models.BigAutoField(primary_key=True)
  3. country = models.ForeignKey(Country, verbose_name='所属国家', on_delete=models.CASCADE,
  4. db_column='country_id', db_constraint=False)
  5. name = models.CharField(max_length=30, verbose_name='城市名')
  6. image = models.ImageField('图片', blank=True, null=True, upload_to=image_upload_to)
  7. latitude = models.DecimalField('纬度', max_digits=10, decimal_places=7, blank=True, null=True)
  8. longitude = models.DecimalField('经度', max_digits=10, decimal_places=7, blank=True, null=True)
  9. sort = models.PositiveIntegerField('排序desc')
  10. published = models.BooleanField('是否上线', default=1)
  11. create_time = models.DateTimeField('创建时间', auto_now_add=True)
  12. update_time = models.DateTimeField('更新时间', auto_now=True)
  13. deleted = models.BooleanField(default=0)
  14. class Meta:
  15. managed = False
  16. db_table = 'city'
  17. verbose_name = '城市'
  18. verbose_name_plural = verbose_name
  19. def __str__(self):
  20. return self.name

处理 BooleanField 与逻辑删除

自定义 model 集成下面的 LogicalDeleteModel
get_queryset 过滤 deleted

  1. class LogicalDeleteManager(Manager):
  2. def get_queryset(self):
  3. return super(LogicalDeleteManager, self).get_queryset().filter(deleted=False)
  4. class LogicalDeleteModel(models.Model):
  5. objects = LogicalDeleteManager()
  6. class Meta:
  7. abstract = True
  8. def delete(self, using=None, keep_parents=False):
  9. self.deleted = 1
  10. self.save()

即使 数据库使用的是 tinyint 字段标识 bool 值,django 这边也可已直接使用 BooleanField,自动映射成 0 1

时间处理

对于 create_time 与 update_time
即使数据库中指定了

  1. `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  2. `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',

这里也要加 auto_now_add 与 auto_now,否则会被认为设置为空

  1. create_time = models.DateTimeField('创建时间', auto_now_add=True)
  2. update_time = models.DateTimeField('更新时间', auto_now=True)