2011-01-19 29 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+。

相關問題