【问题标题】:django-south with django-audit-logdjango-south 与 django-audit-log
【发布时间】:2011-05-27 09:33:51
【问题描述】:

我正在尝试对现有应用程序执行django-south migration 以将django-audit-log 添加到其中(以跟踪用户启动的模块更改),但遇到了重大错误。特别是作为 LastUserField 的 action_user_id 字段(存储指定正在跟踪的更改的用户)。

如果我从一个空白模型开始,我可以通过以下方式添加一个 audit_log:

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

应用这个简单的更改并在 django-south 中进行架构迁移会理解地给我一个错误:

 ! 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(以及自定义字段/自省部分),但它并不是 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'

编辑(中午 12 月 20 日):如果我将行添加到 models.py,我可以应用架构迁移

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

除了 audit_log 中间件不起作用,因为 myapp_mymodelauditlogentry 中没有 action_user_id 整数字段通过“id”引用“auth_user”。然后我手动应用 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,除非数据库需要截断长名称)。我不确定索引的名称是否重要;但想要安全。

【问题讨论】:

  • 问题是南方不知道如何迁移你的田地。对于自定义字段,您需要添加自己的自省规则,告诉南哪些参数很重要,哪些可以忽略。如果今晚有时间,我会写一个关于如何使自省正常工作的示例。
  • @WoLpH:是的,让内省发挥作用是问题所在,但幸运的是,有内省的文档。我不清楚,架构迁移/django-south(以及 django-audit-log)的新手。我尝试的前几件事不适用于 LastUserField。如果您可以进行自省,我将不胜感激(因此我不需要手动注入 SQL),但老实说,我停止尝试自己并转向其他问题。
  • 我最近很忙,所以还没有时间给你一个正确的答案。请多忍耐一下:)(或者其他人可以解释一下)。长话短说,它不起作用,因为您没有告诉南有关参数的信息,所以它忽略了它,导致它被传递了两次。

标签: python django django-south


【解决方案1】:

这就是答案(和解释)。

向南迁移时,不仅会存储模型中的字段名称,还会存储传递给它的类型和参数。这样做的结果是,South 必须了解哪些参数由该字段给出,哪些应该存储。

所以当你创建这样的规则时:

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

Than South 将创建一个包含如下列的表:

(
  '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'

我们如何解决这个问题?

好吧,我们可以告诉 South 我们将这些参数作为默认值传递,因此它可以安全地忽略它们。

所以我们创建了一组这样的规则:

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

因此,South 现在了解如何存储参数以及需要忽略哪些参数。所以新的字段定义是这样的:

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

正如我们所见,related_name 仍然存在,但 tonull 参数已经消失。所以现在我们可以安全地应用迁移而不会发生冲突。

【讨论】:

    【解决方案2】:

    尽管使用了@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'])
    

    关于我可以做些什么来避免这种黑客行为的任何建议?

    【讨论】:

    • 我是 django-audit-log 的作者。该项目的 GitHub 页面在这里:github.com/Atomidata/django-audit-log。如果人们在那里报告诸如此类的问题和补丁,它将使该软件包受益。我没有将应用程序与南一起使用,但我会看看这个问题。我已经创建并发布了有关此问题的问题,并将考虑解决此问题的最佳方法。
    猜你喜欢
    • 2014-02-11
    • 1970-01-01
    • 2012-12-23
    • 2013-03-28
    • 1970-01-01
    • 1970-01-01
    • 2018-04-17
    • 2014-01-12
    • 2014-01-30
    相关资源
    最近更新 更多