2011-01-19 72 views
5

为我们的Django站点提供动力的MySQL数据库已经发展出一些完整性问题;例如引用不存在的行的外键。我不会深究如何陷入混乱,但我现在正在研究如何解决这个问题。有没有一个工具来检查Django中的数据库完整性?

基本上,我正在寻找一个脚本来扫描Django站点中的所有模型,并检查所有外键和其他约束是否正确。希望问题的数量足够小,以便可以手动修复。

我可以自己编码,但我希望有人在这里有一个更好的主意。

我发现django-check-constraints但它并不完全符合法案:现在,我不需要某些东西来防止这些问题,而是找到它们以便在进行其他步骤之前手动修复它们。

其他制约因素:

  • Django的1.1.1升级已被确定为打破东西
  • 的MySQL 5.0.51(Debian的莱尼),目前正与的MyISAM表
  • 的Python 2.5 ,可能是可升级的,但我宁愿不要现在

(稍后,我们将转换为I nnoDB提供正确的事务支持,并且可能在数据库级别有外键约束,以防止将来出现类似的问题。但这不是这个问题的主题。)

+0

你没有提到只使用PostGreSQL,MySQL是强制性的? – 2011-01-19 12:01:23

+0

不是强制性的,没有。我们以后肯定会考虑PostgreSQL,但目前改变DBMS有点太冒险了。 – Thomas 2011-01-19 12:04:13

回答

7

我自己鞭打了一些东西。以下管理脚本应保存在myapp/management/commands/checkdb.py中。确保中间目录有一个__init__.py文件。

用法:./manage.py checkdb进行全面检查;使用--exclude app.Model-e app.Model排除应用程序app中的模型Model

from django.core.management.base import BaseCommand, CommandError 
from django.core.management.base import NoArgsCommand 
from django.core.exceptions import ObjectDoesNotExist 
from django.db import models 
from optparse import make_option 
from lib.progress import with_progress_meter 

def model_name(model): 
    return '%s.%s' % (model._meta.app_label, model._meta.object_name) 

class Command(BaseCommand): 
    args = '[-e|--exclude app_name.ModelName]' 
    help = 'Checks constraints in the database and reports violations on stdout' 

    option_list = NoArgsCommand.option_list + (
     make_option('-e', '--exclude', action='append', type='string', dest='exclude'), 
    ) 

    def handle(self, *args, **options): 
     # TODO once we're on Django 1.2, write to self.stdout and self.stderr instead of plain print 

     exclude = options.get('exclude', None) or [] 

     failed_instance_count = 0 
     failed_model_count = 0 
     for app in models.get_apps(): 
      for model in models.get_models(app): 
       if model_name(model) in exclude: 
        print 'Skipping model %s' % model_name(model) 
        continue 
       fail_count = self.check_model(app, model) 
       if fail_count > 0: 
        failed_model_count += 1 
        failed_instance_count += fail_count 
     print 'Detected %d errors in %d models' % (failed_instance_count, failed_model_count) 

    def check_model(self, app, model): 
     meta = model._meta 
     if meta.proxy: 
      print 'WARNING: proxy models not currently supported; ignored' 
      return 

     # Define all the checks we can do; they return True if they are ok, 
     # False if not (and print a message to stdout) 
     def check_foreign_key(model, field): 
      foreign_model = field.related.parent_model 
      def check_instance(instance): 
       try: 
        # name: name of the attribute containing the model instance (e.g. 'user') 
        # attname: name of the attribute containing the id (e.g. 'user_id') 
        getattr(instance, field.name) 
        return True 
       except ObjectDoesNotExist: 
        print '%s with pk %s refers via field %s to nonexistent %s with pk %s' % \ 
         (model_name(model), str(instance.pk), field.name, model_name(foreign_model), getattr(instance, field.attname)) 
      return check_instance 

     # Make a list of checks to run on each model instance 
     checks = [] 
     for field in meta.local_fields + meta.local_many_to_many + meta.virtual_fields: 
      if isinstance(field, models.ForeignKey): 
       checks.append(check_foreign_key(model, field)) 

     # Run all checks 
     fail_count = 0 
     if checks: 
      for instance in with_progress_meter(model.objects.all(), model.objects.count(), 'Checking model %s ...' % model_name(model)): 
       for check in checks: 
        if not check(instance): 
         fail_count += 1 
     return fail_count 

我正在使这个社区wiki,因为我欢迎任何和所有改进我的代码!

0

托马斯的回答很棒,但现在已经过时了一点。 我已将它更新为as a gist以支持Django 1.8+。

相关问题