django-south 与 django-audit-log

发布于 2024-10-08 16:03:51 字数 5717 浏览 5 评论 0原文

我正在尝试对现有应用程序进行 django-south 迁移 以添加 django-audit-log 到它(跟踪用户发起的模块更改),但我遇到了重大错误。具体来说,action_user_id 字段是 LastUserField(它存储指定正在跟踪的更改的用户)。

如果我从一个空白模型开始,我可以通过以下方式添加一个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 中,我可以应用 schemamigration,

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

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

I'm trying to do a django-south migration to an existing application to add django-audit-log to it (to track user-initiated changes of a module), but am running into significant errors. Specifically with the action_user_id field that is a LastUserField (which stores the user who specified the change that is being tracked).

If I was starting from a blank model, I could just add an audit_log via:

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

Applying this simple change and doing a schemamigration in django-south understandingly gives me an 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

I read the MyFieldsDontWork wiki (and the Custom Fields/Introspection parts), but its not 100% clear what I need to do to get the fields to work.

I try adding:

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

to my models.py which allowed the ./manage.py schemamigration to create a migration script with the previous error goes away. However when I try to migrate (to apply the migration), I get the following errors:

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 noon): I can apply the schemamigration if I add the lines to models.py

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

except then the audit_log middleware doesn't work as there is no action_user_id integer field in myapp_mymodelauditlogentry that references "auth_user" by "id". Then I manually apply the SQL (sqlite syntax; obtained by using sqliteman on newly created database.)

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

and it works. I'll still give the bounty if someone explains how I'm supposed to do this in the context of django-south with migrations/introspection, without necessitating going to raw database dependent SQL and be grateful.

Also, I created an index for action_user_id. I notice that the normal creation of models with leads to an index called

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

I hunted down that the hash 26679921 is created based on the field name with '%x' % (abs(hash(('action_user_id',))) % 4294967296L,) and isn't based on anything else (so should always be _26679921 unless the database requires the long name to be trunctated). I'm not sure if the names of the index ever matter; but wanted to be safe.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

旧城烟雨 2024-10-15 16:03:51

最后是答案(和解释)。

向南迁移时,不仅会存储模型中的字段名称,还会存储传递给它的类型和参数。这样做的结果是 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'],
  )
),

如您所见,其中包含一个 lated_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 的第一个参数是 user(第一个参数是 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'
  )
),

正如我们所看到的,lated_name 仍然在这里,但是 tonull 参数已经消失了。所以现在我们可以安全地应用迁移而不会发生冲突。

Here's finally the answer (and explanation).

When migrating South not only stores the names of the fields in your models, but also the type and the arguments that are passed to it. The result of this is that South has to understand which parameters are given by the field and which should be stored.

So when you create a rule like this:

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

Than South will create a table with a column like this:

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

Which has, as you can see, a related_name parameter, a null parameter and a to parameter. Now let's take a look at the field definition:

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)                                        

What do we see here? The first argument to ForeignKey is user (the first argument is the to attribute). The second argument (also hardcoded) is the null parameter. The result, when applying the migration both South and your field will try to set these parameters.

And you get the error:

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

How do we fix this?

Well, we can tell South that we are passing these arguments as defaults so it can safely ignore them.

So we create a set of rules like this:

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

Because of that, South now understands how to store the parameters and which parameters need to be ignored. So the new field definition will be this:

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

As we can see, the related_name is still here, but the to and null parameters have disappeared. So now we can safely apply the migration without getting conflicts.

み零 2024-10-15 16:03:51

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

关于我可以采取哪些措施来避免这种黑客行为的任何建议?

Despite using the steps in @WoLpH's answer, I still couldn't create the migration. I had to modify the audit_log/models/fields.py file. Here's how my LastUserField field looks like:

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)

The following was added to my models.py file (which didn't work) before I had to resort to doing this:

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

Any suggestions on what I could do to avoid this hackery?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文