2011-01-19 32 views
5

La base de datos MySQL que alimenta nuestro sitio Django ha desarrollado algunos problemas de integridad; p.ej. claves externas que hacen referencia a filas inexistentes. No entraré en cómo nos metimos en este lío, pero ahora estoy buscando cómo solucionarlo.¿Hay alguna herramienta para verificar la integridad de la base de datos en Django?

Básicamente, Estoy buscando un script que analiza todos los modelos en el sitio de Django, y comprueba si todas las claves externas y otras limitaciones son correctos. Con suerte, la cantidad de problemas será lo suficientemente pequeña para que puedan corregirse a mano.

Podría codificar esto yo mismo, pero espero que alguien aquí tenga una idea mejor.

Encontré django-check-constraints pero no encaja del todo bien: en este momento, no necesito algo para evitar estos problemas, sino que los encuentre para que puedan repararse manualmente antes de tomar otras medidas.

Otras restricciones:

  • Django 1.1.1 y modernización se ha determinado a romper cosas
  • MySQL 5.0.51 (Debian Lenny), actualmente con MyISAM tablas
  • Python 2.5 , podría ser actualizable pero prefiero no ahora

(más tarde, vamos a convertir a I nnoDB para soporte de transacción adecuado, y tal vez restricciones de clave externa en el nivel de la base de datos, para evitar problemas similares en el futuro. Pero ese no es el tema de esta pregunta.)

+0

¿No mencionó simplemente usar PostGreSQL, MySQL es obligatorio? –

+0

No obligatorio, no. Definitivamente consideraremos PostgreSQL más tarde, pero cambiar el DBMS es un poco arriesgado en este momento. – Thomas

Respuesta

7

Arreglé algo yo mismo. La secuencia de comandos de gestión a continuación debe guardarse en myapp/management/commands/checkdb.py. Asegúrese de que los directorios intermedios tengan un archivo __init__.py.

Uso: ./manage.py checkdb para un control completo; use --exclude app.Model o -e app.Model para excluir el modelo Model en la aplicación app.

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 

¡Estoy haciendo esto una wiki comunitaria porque doy la bienvenida a todas y cada una de las mejoras a mi código!

+1

Esto se ve genial. Debería ponerse en contacto con los desarrolladores principales de Django y preguntarles si considerarían algo así en Django. Creo que es una gran característica que falta. – slacy

+0

¿dónde se encuentra lib.progress with_progress_meter? CHeers –

+0

En mi disco duro. Y ahora también aquí: http://pastebin.com/Bdd75Rkk – Thomas

0

La respuesta de Thomas es genial, pero ahora está un poco desactualizada. Lo he actualizado as a gist para admitir Django 1.8+.

Cuestiones relacionadas