2010-12-17 11 views
5

Estoy tratando de hacer un django-south migration a una aplicación existente para agregar django-audit-log (para rastrear los cambios iniciados por el usuario de un módulo), pero me estoy encontrando con errores significativos. Específicamente con el campo action_user_id que es un LastUserField (que almacena al usuario que especificó el cambio que se está rastreando).django-south con django-audit-log

Si yo estaba empezando a partir de un modelo en blanco, tan sólo pudiera añadir una audit_log a través de:

from audit_log.models.managers import AuditLog 
... 
class SomeModel(models.Model) 
    ... 
    audit_log = AuditLog() 

La aplicación de este simple cambio y haciendo un schemamigration en django-sur comprensivamente me da un error:

! Cannot freeze field 'myapp.mymodelauditlogentry.action_user' 
! (this field has class audit_log.models.fields.LastUserField) 

! South cannot introspect some fields; this is probably because they are custom 
! fields. If they worked in 0.6 or below, this is because we have removed the 
! models parser (it often broke things). 
! To fix this, read http://south.aeracode.org/wiki/MyFieldsDontWork 

leí el wiki MyFieldsDontWork (y las piezas de campos personalizados/Introspección), pero no es 100% claro lo que tengo que hacer para conseguir los campos para trabajar.

Trato de añadir:

from south.modelsinspector import add_introspection_rules 
add_introspection_rules([], ["^audit_log\.models\.fields\.LastUserField"]) 

a mi models.py que permitió la schemamigration ./manage.py para crear un script de migración con el error anterior desaparece. Sin embargo cuando intento migrar (para aplicar la migración), consigo los siguientes errores:

Running migrations for myapp: 
- Migrating forwards to 0004_auto__add_mymodelauditlogentry. 
> my_app:0004_auto__add_mymodelauditlogentry 
Traceback (most recent call last): 
    File "./manage.py", line 11, in <module> 
    execute_manager(settings) 
     File "/usr/local/lib/python2.6/dist-packages/Django-1.2.3-py2.6.egg/django/core/management/__init__.py", line 438, in execute_manager 
    utility.execute() 
    File "/usr/local/lib/python2.6/dist-packages/Django-1.2.3-py2.6.egg/django/core/management/__init__.py", line 379, in execute 
    self.fetch_command(subcommand).run_from_argv(self.argv) 
    File "/usr/local/lib/python2.6/dist-packages/Django-1.2.3-py2.6.egg/django/core/management/base.py", line 191, in run_from_argv 
    self.execute(*args, **options.__dict__) 
    File "/usr/local/lib/python2.6/dist-packages/Django-1.2.3-py2.6.egg/django/core/management/base.py", line 220, in execute 
    output = self.handle(*args, **options) 
    File "/usr/local/lib/python2.6/dist-packages/South-0.7.3-py2.6.egg/south/management/commands/migrate.py", line 105, in handle 
    ignore_ghosts = ignore_ghosts, 
    File "/usr/local/lib/python2.6/dist-packages/South-0.7.3-py2.6.egg/south/migration/__init__.py", line 191, in migrate_app 
    success = migrator.migrate_many(target, workplan, database) 
    File "/usr/local/lib/python2.6/dist-packages/South-0.7.3-py2.6.egg/south/migration/migrators.py", line 221, in migrate_many 
    result = migrator.__class__.migrate_many(migrator, target, migrations, database) 
    File "/usr/local/lib/python2.6/dist-packages/South-0.7.3-py2.6.egg/south/migration/migrators.py", line 292, in migrate_many 
    result = self.migrate(migration, database) 
    File "/usr/local/lib/python2.6/dist-packages/South-0.7.3-py2.6.egg/south/migration/migrators.py", line 125, in migrate 
    result = self.run(migration) 
    File "/usr/local/lib/python2.6/dist-packages/South-0.7.3-py2.6.egg/south/migration/migrators.py", line 93, in run 
    south.db.db.current_orm = self.orm(migration) 
    File "/usr/local/lib/python2.6/dist-packages/South-0.7.3-py2.6.egg/south/migration/migrators.py", line 246, in orm 
    return migration.orm() 
    File "/usr/local/lib/python2.6/dist-packages/South-0.7.3-py2.6.egg/south/utils.py", line 62, in method 
    value = function(self) 
    File "/usr/local/lib/python2.6/dist-packages/South-0.7.3-py2.6.egg/south/migration/base.py", line 422, in orm 
    return FakeORM(self.migration_class(), self.app_label()) 
    File "/usr/local/lib/python2.6/dist-packages/South-0.7.3-py2.6.egg/south/orm.py", line 46, in FakeORM 
    _orm_cache[args] = _FakeORM(*args) 
    File "/usr/local/lib/python2.6/dist-packages/South-0.7.3-py2.6.egg/south/orm.py", line 125, in __init__ 
    self.models[name] = self.make_model(app_label, model_name, data) 
    File "/usr/local/lib/python2.6/dist-packages/South-0.7.3-py2.6.egg/south/orm.py", line 318, in make_model 
    field = self.eval_in_context(code, app, extra_imports) 
    File "/usr/local/lib/python2.6/dist-packages/South-0.7.3-py2.6.egg/south/orm.py", line 236, in eval_in_context 
    return eval(code, globals(), fake_locals) 
    File "<string>", line 1, in <module> 
    File "/usr/local/lib/python2.6/dist-packages/django_audit_log-0.2.1-py2.6.egg/audit_log/models/fields.py", line 12, in __init__ 
    super(LastUserField, self).__init__(User, null = True, **kwargs) 
TypeError: __init__() got multiple values for keyword argument 'null' 

EDITAR (12/20 mediodía): Puedo aplicar el schemamigration si añado las líneas de models.py

from south.modelsinspector import add_introspection_rules, add_ignored_fields 
add_ignored_fields(["^audit_log\.models\.fields\.LastUserField"]) 

excepto entonces el middleware audit_log no funciona ya que no hay campo entero action_user_id en myapp_mymodelauditlogentry que las referencias "aUTH_USER" por "id". Luego aplico manualmente el (sintaxis sqlite, obtenida mediante el uso de Sqliteman en la base de datos recién creada.) SQL

ALTER TABLE "myapp_mymodelauditlogentry" ADD "action_user_id" integer REFERENCES "auth_user" ("id"); 

y funciona. Todavía voy a dar la recompensa si alguien explica cómo se supone que debo hacer esto en el contexto de django-Sur con las migraciones/introspección, sin necesidad de ir a la base de datos de SQL depende cruda y estar agradecidos.

Además, he creado un índice para action_user_id. Me he dado cuenta que la creación de los modelos normales con cables a un índice llamado

CREATE INDEX "myapp_mymodelauditlogentry_26679921" ON "myapp_mymodelauditlogentry" ("action_user_id") 

Cacé abajo que el hash 26679921 se crea basándose en el nombre del campo con '%x' % (abs(hash(('action_user_id',))) % 4294967296L,) y no se basa en ninguna otra cosa (por lo que debe ser siempre _26679921 a menos que la base de datos requiera el nombre largo para ser truncado). No estoy seguro si los nombres del índice alguna vez importan; pero quería estar seguro.

+1

El problema es que South no sabe cómo migrar sus campos. Para los campos personalizados, debe agregar sus propias reglas de introspección, donde le indica al sur qué parámetros son importantes y cuáles pueden ignorarse. Si tengo tiempo esta noche, escribiré un ejemplo sobre cómo hacer que la introspección funcione correctamente. – Wolph

+0

@WoLpH: Sí, lograr que la introspección funcione es el problema, pero afortunadamente hubo documentación para la introspección. No estaba claro para mí, un novato en schema-migrations/django-south (así como django-audit-log). Las primeras cosas que probé no funcionaban con LastUserField. Estaría agradecido si se puede trabajar en introspección (para no tener que insertar SQL manualmente), pero para ser honesto, dejé de intentarlo y pasé a otros temas. –

+1

He estado bastante ocupado, así que no he tenido tiempo de darle una respuesta adecuada todavía. Por favor, tengan paciencia conmigo un poco más :) (o alguien más puede explicarlo).Para resumir, no funciona porque no le dijiste al sur acerca de los parámetros, por lo que lo ignora lo que hace que pase dos veces. – Wolph

Respuesta

3

A pesar de seguir los pasos en la respuesta de @ WoLpH, aún no pude crear la migración. Tuve que modificar el archivo audit_log/models/fields.py. Así es como mi campo LastUserField parece:

class LastUserField(models.ForeignKey): 
    """ 
    A field that keeps the last user that saved an instance 
    of a model. None will be the value for AnonymousUser. 
    """ 

    def __init__(self, **kwargs): 
     kwargs.pop('null', None) 
     kwargs.pop('to', None) 
     super(LastUserField, self).__init__(User, null = True, **kwargs) 

    def contribute_to_class(self, cls, name): 
     super(LastUserField, self).contribute_to_class(cls, name) 
     registry = registration.FieldRegistry(self.__class__) 
     registry.add_field(cls, self) 

se añade el siguiente texto a mi archivo models.py (que no funcionaba) antes de que tuviera que recurrir a hacer esto:

rules = [((fields.LastUserField,), 
    [],  
    { 
     'to': ['rel.to', {'default': User}], 
     'null': ['null', {'default': True}], 
    },)] 

# Add the rules for the `LastUserField` 
add_introspection_rules(rules, ['^audit_log\.models\.fields\.LastUserField']) 

Cualquier sugerencia ¿Qué podría hacer para evitar este pirateo?

+0

Soy el autor de django-audit-log. La página GitHub del proyecto está aquí: https://github.com/Atomidata/django-audit-log. Sería beneficioso para el paquete si la gente informa problemas y parches como este. No he usado la aplicación junto con el sur, pero veré el problema. He creado y emitido al respecto y consideraré la mejor manera de solucionarlo. – Vasil

8

Aquí es finalmente la respuesta (y la explicación).

Al migrar del Sur no sólo almacena los nombres de los campos en sus modelos, sino también el tipo y los argumentos que se pasan a la misma. El resultado de esto es que South tiene que entender qué parámetros son dados por el campo y cuáles deben almacenarse.

Así que cuando se crea una regla como la siguiente:

add_introspection_rules([], ["^audit_log\.models\.fields\.LastUserField"]) 

que el sur va a crear una tabla con una columna de la siguiente manera:

(
    'action_user', 
    self.gf('audit_log.models.fields.LastUserField')(
    related_name='_somemodel_audit_log_entry', 
    null=True, 
    to=orm['auth.User'], 
) 
), 

que tiene, como se puede ver, un parámetro related_name , un parámetro null y un parámetro to. Ahora echemos un vistazo a la definición de campo:

class LastUserField(models.ForeignKey):          
    """                  
    A field that keeps the last user that saved an instance     
    of a model. None will be the value for AnonymousUser.      
    """                  

    def __init__(self, **kwargs):            
     models.ForeignKey.__init__(self, User, null=True, **kwargs)   
     #print kwargs               
     #super(LastUserField, self).__init__(User, null = True, **kwargs)  

    def contribute_to_class(self, cls, name):         
     super(LastUserField, self).contribute_to_class(cls, name)    
     registry = registration.FieldRegistry(self.__class__)     
     registry.add_field(cls, self)           

¿Qué es lo que vemos aquí? El primer argumento para ForeignKey es user (el primer argumento es el atributo to). El segundo argumento (también codificado) es el parámetro null. El resultado, al aplicar la migración, tanto South como su campo intentarán establecer estos parámetros.

Y se obtiene el error:

TypeError: __init__() got multiple values for keyword argument 'null' 

¿Cómo podemos solucionar este problema?

Bueno, podemos decir al sur que estamos pasando estos argumentos por defecto para que pueda ignorarlos con seguridad.

Por lo tanto, crear un conjunto de reglas como esto:

rules = [(           
    (fields.LastUserField,),       
    [],            
    {            
     'to': ['rel.to', {'default': User}],   
     'null': ['null', {'default': True}],   
    },            
)] 
add_introspection_rules(       
    rules,           
    ['^audit_log\.models\.fields\.LastUserField'], 
)  

Debido a eso, Sur ahora entiende cómo almacenar los parámetros y que deben ser ignorados parámetros. Por lo que la nueva definición de campo será la siguiente:

(
    'action_user', 
    self.gf('audit_log.models.fields.LastUserField')(
    related_name='_somemodel_audit_log_entry' 
) 
), 

Como podemos ver, la related_name es todavía aquí, pero los parámetros to y null han desaparecido. Entonces ahora podemos aplicar la migración de forma segura sin tener conflictos.

+0

Muchas gracias. –