loaddataでもLogを保存する

2008年08月23日(土) 03:08 この記事をクリップ!

空前のwassrブームの中、wassrのdjangoチャンネルにこんな発言がありました。

ジャンゴ管理画面から、インポートを行った際のログってとれるのかな〜


管理画面にインポートがついた様子はないので、django.core.managementのloaddataかinitial_dataのことかな?だったらログは記録されないから見られないな。と思ったわけです。svn 8466(2008/08/23)


Djangoの管理画面にあるログは、あくまでも管理画面を使ってのデータ操作についてのみ記録されます。

実際に記録をとっているのは、django/contrib/admin/options.pyのModelAdmin.log_addition(change,deletion)です。

コードは単純で、adminのLogEntryモデルをインサートしているだけです。

from django.contrib.admin.models import LogEntry, ADDITION
LogEntry.objects.log_action(
    user_id         = request.user.pk, 
    content_type_id = ContentType.objects.get_for_model(object).pk,
    object_id       = object.pk,
    object_repr     = force_unicode(object), 
    action_flag     = ADDITION
)


せっかくなので、loaddataを拡張してログをとれる仕組みを構築してみました。


まずは、loaddataでログがとれていないことを確認してみましょう。


次の設定でdjangoのプロジェクトとアプリケーションを作成しました。

プロジェクト名: load_data_admin_log

アプリケーション名: sample


settings.pyとurls.pyを修正して、django.contrib.adminが動作するように、またload_data_admin_log.sampleもINSTALLED_APPSに追加して動作するようにしておいてください。


モデルは超単純にしました。admin.site.registerが怪しいのはご愛敬 :)


モデル(load_data_admin_log/sample/models.py)

from django.db import models
from django.contrib.admin.sites import AlreadyRegistered
class TestModel(models.Model):
    sample_data = models.CharField(max_length=100)
    
    def __unicode__(self):
        return self.sample_data[:20]

from django.contrib import admin

try:
    admin.site.register(TestModel)
except AlreadyRegistered:
    pass


アプリケーションディレクトリの中に、fixturesというディレクトリを作成し、そのなかにfixture用のファイルを作成します。今回はtarget_data.jsonというjsonフォーマットのfixtureを作りました。


fixture(load_data_admin_log/sample/fixtures/target_data.json)

[
    {
        "pk": "1",
        "model": "sample.TestModel",
        "fields": {
            "sample_data": "sample data 1"
        }
    },
    {
        "pk": "2",
        "model": "sample.TestModel",
        "fields": {
            "sample_data": "sample data 2"
        }
    },
    {
        "pk": "3",
        "model": "sample.TestModel",
        "fields": {
            "sample_data": "sample data 3"
        }
    }
]


fixtureを取り込むには、managementのloadコマンドを利用します。今回の場合はプロジェクトディレクトリで次のように実行します(syncdbを済ませた後ですよ)。

./manage.py loaddata target_data


すると、3件取り込んだというメッセージが出るはずです。

管理画面を開いてデータが登録されているか、ログが出ているかを確認してみましょう。

データは3件あるはずですが、ログは出ていませんね?


では、manage.pyのコマンドを作成します。

コマンドとして認識させるためには、アプリケーションの中にmanagement/commandsパッケージを作成します。パッケージとして認識させるためには、managementディレクトリとcommandsディレクトリの中に__init__.pyを作成します(内容は空でかまわない)。


次に、commandsディレクトリの中にコマンド.pyという名前のファイルを作成します。今回は、loaddata_with_log.pyというファイルで作成しました。


コマンド(load_data_admin_log/sample/management/commands/loaddata_with_log.py)

from django.core.management.base import BaseCommand
from django.core.management.color import no_style
from django.core import management
from optparse import make_option

from django.contrib.admin.models import LogEntry, ADDITION, CHANGE
from django.contrib.contenttypes.models import ContentType
from django.utils.encoding import force_unicode

class Command(BaseCommand):
    option_list = BaseCommand.option_list + (
        make_option('--username', action='store', dest='username', default=None,
            help='username.'),
        make_option('--password', action='store', dest='password', default=None,
            help='password.'),
    )
    help = 'Installs the named fixture(s) in the database with logging.'
    args = "fixture [fixture ...] --username xxx --password xxx"

    def handle(self, *fixture_labels, **options):
        from django.contrib.auth import authenticate
        user = authenticate(username=options.get('username'), password=options.get('password'))
        if user is not None and user.is_authenticated():
            from django.db.models import signals
            from sample.models import TestModel
            def add_log_data(instance, raw, created, **kwargs):
                LogEntry.objects.log_action(
                    user_id         = user.pk, 
                    content_type_id = ContentType.objects.get_for_model(instance).pk,
                    object_id       = instance.pk,
                    object_repr     = force_unicode(instance), 
                    action_flag     = ADDITION if created else CHANGE
                )
            signals.post_save.connect(add_log_data, sender=TestModel)
            management.call_command('loaddata', *fixture_labels, **options)
        else:
            print 'Authenticate failed.'


非常にやっつけで汚いコードですが、求めるログの追加はできているはずです。

対象のモデルが固定なので、このままでは実用にはほど遠いのですが、管理画面を開いてみるとログは追加されていますよね?


肝は次の3点です。

  • 管理画面の操作ログはLogEntry型のデータを追加するだけ

  • manage.pyで呼び出すコマンドはプラグイン型になっている

  • モデルの保存後のイベントにコールバックを追加できる


その後のwassrへの書き込みをみると、独自に作ったインポート機能でログをとりたいとのことですから、単にLogEntryを追加してやればいいようですね。


 
ponybadge

Powered by

Feedbacks

Tweets

Tags

Calendar