標準の Django User モデルにリンクされた created_by フィールドを持つモデルがあります。モデルを保存するときに、現在のユーザーの ID を自動的に入力する必要があります。サイトのほとんどの部分は組み込みの管理者を使用しないため、管理者レイヤーでこれを行うことはできません。これについてどうすればよいか、誰にもアドバイスできますか?
13 に答える
UPDATE 2020-01-02
⚠ 次の回答は、最新の Python および Django バージョンに更新されていません。数年前にこれを書いて以来、この問題を解決するパッケージがリリースされました。現在django-crum
、同じ手法を実装していますが、テストがあり、定期的に更新されているものを使用することを強くお勧めします: https://pypi.org/project/django-crum/
最も邪魔にならない方法は、 a を使用しCurrentUserMiddleware
て現在のユーザーをスレッド ローカル オブジェクトに格納することです。
current_user.py
from threading import local
_user = local()
class CurrentUserMiddleware(object):
def process_request(self, request):
_user.value = request.user
def get_current_user():
return _user.value
認証ミドルウェアの後に、このミドルウェアを MIDDLEWARE_CLASSES に追加するだけです。
設定.py
MIDDLEWARE_CLASSES = (
...
'django.contrib.auth.middleware.AuthenticationMiddleware',
...
'current_user.CurrentUserMiddleware',
...
)
モデルは、get_current_user
関数を使用して、リクエスト オブジェクトを渡すことなくユーザーにアクセスできるようになりました。
models.py
from django.db import models
from current_user import get_current_user
class MyModel(models.Model):
created_by = models.ForeignKey('auth.User', default=get_current_user)
ヒント:
Django CMS を使用している場合は、独自の CurrentUserMiddleware を定義する必要さえありませんがcms.middleware.user.CurrentUserMiddleware
、cms.utils.permissions.get_current_user
関数を使用して現在のユーザーを取得できます。
管理画面と他の場所の両方で機能するものが必要な場合は、カスタム モデルフォームを使用する必要があります。基本的な考え方は、__init__
メソッドをオーバーライドして追加のパラメーター (リクエスト) を取得し、それをフォームの属性として保存し、save メソッドをオーバーライドしてユーザー ID を設定してからデータベースに保存することです。
class MyModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
return super(MyModelForm, self).__init__(*args, **kwargs)
def save(self, *args, **kwargs):
kwargs['commit']=False
obj = super(MyModelForm, self).save(*args, **kwargs)
if self.request:
obj.user = self.request.user
obj.save()
return obj
リクエストオブジェクトを渡す必要があるため、ダニエルの答えは管理者には直接機能しません。get_form
クラスでメソッドをオーバーライドすることでこれを行うことができるかもしれませんがModelAdmin
、フォームのカスタマイズを避けsave_model
てModelAdmin
.
def save_model(self, request, obj, form, change):
"""When creating a new object, set the creator field.
"""
if not change:
obj.creator = request.user
obj.save()
この全体的なアプローチは、私を悩ませました。一度だけ言いたかったので、ミドルウェアに実装しました。認証ミドルウェアの後に WhodidMiddleware を追加するだけです。
created_by および modified_by フィールドが に設定されている場合editable = False
、フォームをまったく変更する必要はありません。
"""Add user created_by and modified_by foreign key refs to any model automatically.
Almost entirely taken from https://github.com/Atomidata/django-audit-log/blob/master/audit_log/middleware.py"""
from django.db.models import signals
from django.utils.functional import curry
class WhodidMiddleware(object):
def process_request(self, request):
if not request.method in ('GET', 'HEAD', 'OPTIONS', 'TRACE'):
if hasattr(request, 'user') and request.user.is_authenticated():
user = request.user
else:
user = None
mark_whodid = curry(self.mark_whodid, user)
signals.pre_save.connect(mark_whodid, dispatch_uid = (self.__class__, request,), weak = False)
def process_response(self, request, response):
signals.pre_save.disconnect(dispatch_uid = (self.__class__, request,))
return response
def mark_whodid(self, user, sender, instance, **kwargs):
if 'created_by' in instance._meta.fields and not instance.created_by:
instance.created_by = user
if 'modified_by' in instance._meta.fields:
instance.modified_by = user
一般的なビューでそれを行う方法は次のとおりです。
class MyView(CreateView):
model = MyModel
def form_valid(self, form):
object = form.save(commit=False)
object.owner = self.request.user
object.save()
return super(MyView, self).form_valid(form)
クラスベースのビューを使用している場合、ダニエルの答えにはさらに多くのことが必要です。以下を追加して、リクエスト オブジェクトが ModelForm オブジェクトで使用できるようにします。
class BaseCreateView(CreateView):
def get_form_kwargs(self):
"""
Returns the keyword arguments for instanciating the form.
"""
kwargs = {'initial': self.get_initial()}
if self.request.method in ('POST', 'PUT'):
kwargs.update({
'data': self.request.POST,
'files': self.request.FILES,
'request': self.request})
return kwargs
また、すでに述べたように、ModelForm.save() の最後に obj を返す必要があります。
forms.ModelForm の「save」メソッドは、保存されたインスタンスを返します。
MyModelForm に最後の 1 行を追加する必要があります:
...
return obj
この変更は、create_object または update_object ジェネリック ビューを使用している場合に必要です。
保存されたオブジェクトを使用してリダイレクトを行います。
オブジェクトを常に保存することでモデルフォームのデフォルトの動作を変更するため、ダニエルの答えが最善であるとは思いません。
私が使用するコード:
フォーム.py
from django import forms
class MyModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super(MyModelForm, self).__init__(*args, **kwargs)
def save(self, commit=True):
obj = super(MyModelForm, self).save(commit=False)
if obj.created_by_id is None:
obj.created_by = self.user
if commit:
obj.save()
return obj