2010-12-17 74 views
5

我試圖對現有應用程序執行django-south migration以將django-audit-log添加到它(以跟蹤用戶啓動的模塊更改),但遇到了重大錯誤。特別是使用LastUserField(存儲指定正在跟蹤的更改的用戶)的action_user_id字段。django-south with django-audit-log

如果我從空白模型開始,我可以通過添加一個AUDIT_LOG:

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

運用這一簡單的變化,並在Django南做了schemamigration會心給我一個錯誤:

! 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 

我讀了MyFieldsDontWork wiki(和Custom Fields/Introspection部分),但它並沒有100%清楚我需要做些什麼才能使這些字段正常工作。

我嘗試添加:

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

我的models.py其允許./manage.py schemamigration與以前的錯誤消失創建一個遷移腳本。然而,當我嘗試遷移(應用的遷移),我收到以下錯誤:

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(12/20中午):我可以將schemamigration如果我添加了線的models.py

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

除了那麼AUDIT_LOG中間件不起作用,因爲在myapp_mymodelauditlogentry引用「AUTH_USER」的「ID」不action_user_id整型字段。然後我手動應用的SQL(SQLite的語法;通過使用新創建的數據庫上獲得sqliteman)

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

和它的作品。如果有人解釋我應該如何在遷移/自省的django-south的背景下做到這一點,而不必依賴原始的依賴於數據庫的SQL並且很感激,我仍然會給予賞金。

另外,我爲action_user_id創建了一個索引。我注意到,在正常的創建模式與導致的指數稱爲

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

我追殺的哈希26679921基於字段名創建了'%x' % (abs(hash(('action_user_id',))) % 4294967296L,)而不是基於任何東西(所以應始終_26679921除非數據庫要求長名稱被截斷)。我不確定索引的名稱是否重要;但想要安全。

+1

問題是南方不知道如何遷移你的領域。對於自定義字段,需要添加自己的內省規則,告訴南方哪些參數很重要,哪些參數可以忽略。如果我今晚有時間,我會寫一個關於如何使內省工作正常的例子。 – Wolph 2010-12-20 17:30:17

+0

@WoLpH:是的,讓內省工作是問題,但在那裏幸運的是內省的文件。我不清楚,我是模式遷移/ django-south(以及django-audit-log)的新手。我嘗試的前幾件事不是用LastUserField工作的。如果你能夠進行自省工作(所以我不需要手動注入SQL),我會很感激,但說實話,我停止嘗試自己並轉移到其他問題上。 – 2010-12-22 21:54:10

+1

我一直很忙,所以我沒有時間給你一個正確的答案。請忍耐一會兒:)(或其他人可以解釋它)。長話短說,它不起作用,因爲你沒有告訴南方有關參數,所以它忽略了它導致它通過兩次。 – Wolph 2010-12-23 01:09:02

回答

3

儘管使用@ WoLpH的答案中的步驟,我仍然無法創建遷移。我不得不修改audit_log/models/fields.py文件。這裏是我的LastUserField場看起來像:

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) 

下加入到我的models.py文件(沒有工作)之前,我不得不求助於這樣做:

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

任何建議我能做些什麼來避免這種駭人聽聞?

+0

我是django-audit-log的作者。該項目的GitHub頁面位於:https://github.com/Atomidata/django-audit-log。如果人們在那裏報告問題和補丁,它將有益於這個軟件包。我沒有與南方一起使用該應用程序,但我會看看這個問題。我已經創建併發布了關於此問題並將考慮解決此問題的最佳方法。 – Vasil 2011-04-04 16:32:38

8

這裏是最後的答案(和解釋)。

當遷移南不僅存儲在您的模型中的字段的名稱,而且存儲傳遞給它的類型和參數。這樣做的結果是,南必須瞭解哪些參數是由該字段給出的以及應該存儲哪些參數。

所以,當你創建一個這樣的規則:

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

比南方將創建一個表像這樣的列:

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

其中有,你可以看到,一個related_name參數,null參數和to參數。現在讓我們來看看現場定義:

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)           

我們在這裏看到什麼? ForeignKey的第一個參數是用戶(第一個參數是to屬性)。第二個參數(也是硬編碼)是null參數。結果,當應用遷移South和您的字段將嘗試設置這些參數。

,你會得到錯誤:

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

我們如何解決這個問題?

那麼,我們可以告訴南方,我們將這些參數作爲默認值傳遞,以便安全地忽略它們。

所以我們創建了一套規則是這樣的:

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

正因爲如此,南方現懂得如何存儲的參數和參數需要被忽略。因此,新的字段定義將是這樣的:

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

正如我們所看到的,related_name還在這裏,但tonull參數已經消失。所以現在我們可以安全地應用遷移而不會發生衝突。

+0

非常感謝。 – 2010-12-28 04:47:11