※ 商品のリンクをクリックして何かを購入すると私に少額の報酬が入ることがあります【広告表示】 空前の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を追加してやればいいようですね。