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点です。

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

Prev Entry

Next Entry