2010-12-17 15 views
5

Ich versuche, eine django-south migration zu einer vorhandenen Anwendung zu tun, um django-audit-log hinzuzufügen (um von Benutzern initiierte Änderungen eines Moduls zu verfolgen), aber es treten erhebliche Fehler auf. Insbesondere mit dem Feld action_user_id, das ein LastUserField ist (das den Benutzer speichert, der die Änderung angegeben hat, die verfolgt wird).django-south mit django-audit-log

Wenn ich von einem leeren Modell ausgehend war, konnte ich nur ein audit_log über hinzufügen:

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

Anwendung diese einfache Änderung und eine schemamigration in django-Süd zu tun gibt mir verstehend einen Fehler:

! 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 

Ich lese das MyFieldsDontWork Wiki (und die Custom Fields/Introspection Teile), aber es ist nicht 100% klar, was ich tun muss, um die Felder zum Funktionieren zu bringen.

Ich versuche, fügte hinzu:

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

meiner models.py, die die ./manage.py schemamigration ein Migrationsskript zu erstellen erlaubt mit der vorherigen Fehler weggeht. Allerdings, wenn ich versuche zu migrieren (die Migration zu rechnen), erhalte ich folgende Fehler:

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' 

EDIT (20.12 Uhr): Ich kann die schemamigration bewerben, wenn ich die Zeilen in dem models.py

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

außer dann die audit_log Middleware funktioniert nicht, da es kein Feld in myapp_mymodelauditlogentry, die Verweise „auth_user“ von „id“ action_user_id ganze Zahl ist. Dann wende ich manuell die SQL (SQLite Syntax, erhalten durch sqliteman auf neu erstellte Datenbank.)

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

und es funktioniert. Ich werde immer noch das Kopfgeld geben, wenn jemand erklärt, wie ich das im Kontext von Django-South mit Migrationen/Introspektion tun soll, ohne auf rohen datenbankabhängigen SQL umsteigen zu müssen und dankbar zu sein.

Außerdem habe ich einen Index für action_user_id erstellt. Ich stelle fest, dass die normale Erstellung von Modellen mit führt zu einem Index

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

ich erlegt, dass der Hash-26679921 mit '%x' % (abs(hash(('action_user_id',))) % 4294967296L,) basierend auf dem Feldnamen erstellt wird und auf etwas anderes (so sollte nicht auf der Grundlage immer _26679921, es sei denn, die Datenbank erfordert, dass der lange Name abgeschnitten wird. Ich bin mir nicht sicher, ob die Namen des Index jemals wichtig sind; aber wollte in Sicherheit sein.

+1

Das Problem ist nur, dass Süden nicht wissen, wie Sie Ihre Felder migrieren. Für benutzerdefinierte Felder müssen Sie eigene Introspektionsregeln hinzufügen, in denen Sie angeben, welche Parameter wichtig sind und welche ignoriert werden können. Wenn ich heute Abend Zeit habe, werde ich ein Beispiel schreiben, wie man die Introspektion richtig machen kann. – Wolph

+0

@WoLpH: Ja, es ist das Problem, die Introspektion zur Arbeit zu bringen, aber währenddessen gab es glücklicherweise Dokumentation für Introspektion. Es war mir nicht klar, ein Anfänger in Schema-Migrationen/Django-South (sowie Django-Audit-Log). Die ersten paar Dinge, die ich versuchte, funktionierten nicht mit dem LastUserField. Ich wäre Ihnen dankbar, wenn Sie in sich selbst arbeiten können (also muss ich SQL nicht manuell injizieren), aber um ehrlich zu sein, habe ich aufgehört, mich selbst zu testen und habe mich anderen Problemen zugewandt. –

+1

Ich war ziemlich beschäftigt, also hatte ich noch keine Zeit, Ihnen eine richtige Antwort zu geben. Bitte ertragen Sie mich etwas länger :) (oder jemand anders kann es erklären).Lange Rede, kurzer Sinn, es funktioniert nicht, weil Sie den Süden nicht über die Parameter informiert haben, so dass es ignoriert wird, was dazu führt, dass es zweimal übergeben wird. – Wolph

Antwort

3

Trotz der Schritte in @ WoLpH Antwort konnte ich die Migration noch nicht erstellen. Ich musste die Datei audit_log/models/fields.py modifizieren. Hier ist, wie mein LastUserField Feld wie folgt aussieht:

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) 

Folgendes wurde hinzugefügt, um meine models.py Datei (was nicht funktioniert hat), bevor ich zu tun, dies zu greifen hatte:

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']) 

Irgendwelche Vorschläge Was könnte ich tun, um diesen Hacker zu vermeiden?

+0

Ich bin der Autor von django-audit-log. Die GitHub-Seite des Projekts befindet sich hier: https://github.com/Atomidata/django-audit-log. Es würde dem Paket nützen, wenn Leute dort Probleme und Patches melden. Ich habe die App nicht in Verbindung mit Süden verwendet, aber ich werde das Problem betrachten. Ich habe diesbezüglich erstellt und herausgegeben und werde den besten Weg in Betracht ziehen, dies zu beheben. – Vasil

8

Hier ist endlich die Antwort (und Erklärung).

Bei der Migration nach Süden werden nicht nur die Namen der Felder in Ihren Modellen gespeichert, sondern auch der Typ und die Argumente, die an sie übergeben werden. Dies hat zur Folge, dass South verstehen muss, welche Parameter durch das Feld gegeben sind und welche gespeichert werden sollen.

Also, wenn Sie erstellen eine Regel wie folgt aus:

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

als Süden wird eine Tabelle mit einer Spalte wie folgt erstellen:

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

Welche hat, wie man sieht, eine related_name Parameter , ein null Parameter und ein to Parameter. Werfen wir nun einen Blick auf die Felddefinition:

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)           

Was sehen wir hier? Das erste Argument für ForeignKey ist user (das erste Argument ist das to Attribut). Das zweite Argument (auch fest codiert) ist der null Parameter. Das Ergebnis, wenn Sie die Migration anwenden South und Ihr Feld wird versuchen, diese Parameter festzulegen.

Und Sie den Fehler:

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

Wie wir dieses Problem beheben?

Nun, wir können South sagen, dass wir diese Argumente als Standard übergeben, so dass es sie ignorieren kann.

So schaffen wir eine Reihe von Regeln wie folgt aus:

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

davon Denn Süd jetzt versteht, wie die Parameter zu speichern und die Parameter ignoriert werden müssen. So ist die neue Felddefinition wird dies:

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

Wie wir sehen können, die related_name ist immer noch hier, aber die to und null Parameter verschwunden sind. Jetzt können wir die Migration sicher anwenden, ohne Konflikte zu bekommen.

+0

Vielen Dank. –