Django-Юг с Django-Audit-Log - PullRequest
       37

Django-Юг с Django-Audit-Log

5 голосов
/ 18 декабря 2010

Я пытаюсь выполнить миграцию 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 (и части 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'

РЕДАКТИРОВАТЬ (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, которое ссылается на «auth_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, если база данных не требует, чтобы длинное имя было обрезано).Я не уверен, имеют ли значение имена индекса;но хотел быть в безопасности.

Ответы [ 2 ]

8 голосов
/ 25 декабря 2010

Вот, наконец, ответ (и объяснение).

При миграции на юг сохраняются не только имена полей в ваших моделях, но также тип и аргументы, которые ему передаются.Результатом этого является то, что Юг должен понимать, какие параметры задаются полем, а какие должны быть сохранены.

Поэтому, когда вы создаете правило, подобное этому:

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 все еще здесь, но параметры to и null исчезли.Теперь мы можем безопасно применить миграцию без конфликтов.

3 голосов
/ 12 января 2011

Несмотря на использование шагов из ответа @ 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'])

Любые предложения о том, что я могу сделать, чтобы избежать этой хакерской атаки?

...