Files
jyls_django/User/management/commands/check_db_models.py
2026-01-13 18:29:01 +08:00

199 lines
7.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
简化的数据库和模型对比检查命令
直接使用 Django 的 inspectdb 逻辑来检查
"""
from django.core.management.base import BaseCommand
from django.db import connection
from django.apps import apps
from django.db import models
from django.core.management.sql import sql_flush
import sys
class Command(BaseCommand):
help = '检查数据库结构和模型类是否一致(简化版)'
def add_arguments(self, parser):
parser.add_argument(
'--app',
type=str,
help='指定要检查的应用名称',
)
parser.add_argument(
'--model',
type=str,
help='指定要检查的模型名称',
)
parser.add_argument(
'--detail',
action='store_true',
help='显示详细信息',
)
def handle(self, *args, **options):
self.stdout.write(self.style.SUCCESS('开始检查数据库结构和模型类...\n'))
app_name = options.get('app')
model_name = options.get('model')
detail = options.get('detail', False)
# 获取所有应用
if app_name:
apps_to_check = [apps.get_app_config(app_name)]
else:
apps_to_check = [
apps.get_app_config('User'),
apps.get_app_config('finance'),
apps.get_app_config('business')
]
all_issues = []
all_warnings = []
with connection.cursor() as cursor:
# 获取数据库名
cursor.execute("SELECT DATABASE()")
db_name = cursor.fetchone()[0]
self.stdout.write(f'数据库: {db_name}\n')
for app_config in apps_to_check:
self.stdout.write(self.style.WARNING(f'\n检查应用: {app_config.name}'))
self.stdout.write('=' * 80)
models_to_check = app_config.get_models()
if model_name:
models_to_check = [m for m in models_to_check if m.__name__ == model_name]
for model in models_to_check:
issues, warnings = self._check_model(cursor, db_name, model, detail)
all_issues.extend(issues)
all_warnings.extend(warnings)
# 输出总结
self.stdout.write('\n' + '=' * 80)
self.stdout.write(self.style.SUCCESS('\n检查完成!\n'))
if all_issues:
self.stdout.write(self.style.ERROR(f'发现 {len(all_issues)} 个问题:\n'))
for i, issue in enumerate(all_issues, 1):
self.stdout.write(f'{i}. [{issue["type"]}] {issue["message"]}')
if detail and 'details' in issue:
for detail_item in issue['details']:
self.stdout.write(f' - {detail_item}')
else:
self.stdout.write(self.style.SUCCESS('✅ 数据库结构和模型类完全一致!'))
if all_warnings:
self.stdout.write(self.style.WARNING(f'\n警告 ({len(all_warnings)} 个)\n'))
for i, warning in enumerate(all_warnings, 1):
self.stdout.write(f'{i}. {warning}')
return len(all_issues)
def _check_model(self, cursor, db_name, model, detail=False):
"""检查单个模型"""
issues = []
warnings = []
db_table = model._meta.db_table
self.stdout.write(f'\n检查模型: {model.__name__} (表: {db_table})')
self.stdout.write('-' * 80)
# 检查表是否存在
cursor.execute("""
SELECT COUNT(*)
FROM information_schema.tables
WHERE table_schema = %s AND table_name = %s
""", [db_name, db_table])
if cursor.fetchone()[0] == 0:
issues.append({
'type': 'missing_table',
'model': model.__name__,
'table': db_table,
'message': f'{db_table} 不存在于数据库中'
})
self.stdout.write(self.style.ERROR(f' ❌ 表 {db_table} 不存在'))
return issues, warnings
self.stdout.write(self.style.SUCCESS(f' ✅ 表 {db_table} 存在'))
# 获取数据库表的字段
cursor.execute("""
SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s
ORDER BY ORDINAL_POSITION
""", [db_name, db_table])
db_columns = {row[0]: {'type': row[1], 'nullable': row[2] == 'YES', 'default': row[3]}
for row in cursor.fetchall()}
# 获取模型字段(只获取数据库字段,排除反向关系)
model_fields = {}
for field in model._meta.get_fields(include_parents=False):
# 跳过多对多关系
if isinstance(field, models.ManyToManyField):
continue
# 跳过反向关系auto_created=True
if getattr(field, 'auto_created', False):
continue
# 只处理有 column 属性的字段
if hasattr(field, 'column'):
model_fields[field.column] = {
'name': field.name,
'type': type(field).__name__,
'null': getattr(field, 'null', False),
}
# 检查模型字段是否在数据库中存在
for field_name, field_info in model_fields.items():
if field_name not in db_columns:
issues.append({
'type': 'missing_field',
'model': model.__name__,
'table': db_table,
'field': field_name,
'message': f'模型字段 {field_info["name"]} ({field_name}) 在数据库表 {db_table} 中不存在'
})
self.stdout.write(self.style.ERROR(f' ❌ 字段 {field_info["name"]} ({field_name}) 在数据库中不存在'))
else:
if detail:
self.stdout.write(self.style.SUCCESS(f' ✅ 字段 {field_info["name"]} ({field_name}) 存在'))
# 检查数据库中是否有模型中没有的字段(排除 id
db_field_names = set(db_columns.keys())
model_field_names = set(model_fields.keys())
extra_fields = db_field_names - model_field_names - {'id'}
if extra_fields:
warnings.append(f'数据库表 {db_table} 中有模型中没有的字段: {", ".join(extra_fields)}')
self.stdout.write(self.style.WARNING(f' ⚠️ 数据库中有额外字段: {", ".join(extra_fields)}'))
# 检查多对多关系表
for field in model._meta.get_fields(include_parents=False):
if isinstance(field, models.ManyToManyField):
m2m_table = field.remote_field.through._meta.db_table
cursor.execute("""
SELECT COUNT(*)
FROM information_schema.tables
WHERE table_schema = %s AND table_name = %s
""", [db_name, m2m_table])
if cursor.fetchone()[0] == 0:
issues.append({
'type': 'missing_m2m_table',
'model': model.__name__,
'table': m2m_table,
'field': field.name,
'message': f'多对多关系表 {m2m_table} 不存在'
})
self.stdout.write(self.style.ERROR(f' ❌ 多对多关系表 {m2m_table} 不存在'))
else:
if detail:
self.stdout.write(self.style.SUCCESS(f' ✅ 多对多关系表 {m2m_table} 存在'))
return issues, warnings