J'ai un modèle avec un champ created_by lié au modèle d'utilisateur Django standard. Je dois renseigner automatiquement cela avec l'ID de l'utilisateur actuel lorsque le modèle est enregistré. Je ne peux pas faire cela au niveau de la couche Admin, car la plupart des parties du site n'utiliseront pas l'Admin intégré. Quelqu'un peut-il me conseiller sur la façon dont je devrais m'y prendre?
Si vous voulez quelque chose qui fonctionnera à la fois dans l’administrateur et ailleurs, vous devez utiliser un modèle personnalisé. L'idée de base est de remplacer la méthode __init__
pour prendre un paramètre supplémentaire - request - et le stocker en tant qu'attribut du formulaire, puis de surcharger la méthode de sauvegarde pour définir l'ID utilisateur avant de sauvegarder dans la base de données.
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
La méthode la moins contraignante consiste à utiliser une variable CurrentUserMiddleware
pour stocker l'utilisateur actuel dans un objet local de thread:
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
Il ne vous reste plus qu'à ajouter ce middleware à votre middleware d'authentification MIDDLEWARE_CLASSES after .
MIDDLEWARE_CLASSES = (
...
'Django.contrib.auth.middleware.AuthenticationMiddleware',
...
'current_user.CurrentUserMiddleware',
...
)
Votre modèle peut maintenant utiliser la fonction get_current_user
pour accéder à l'utilisateur sans avoir à transmettre l'objet de requête.
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)
Si vous utilisez le système de gestion de contenu Django, vous n'avez même pas besoin de définir votre propre CurrentUserMiddleware, mais vous pouvez utiliser cms.middleware.user.CurrentUserMiddleware
et la fonction cms.utils.permissions.get_current_user
pour récupérer l'utilisateur actuel.
La réponse de Daniel ne fonctionnera pas directement pour l'administrateur car vous devez transmettre l'objet de requête. Vous pourrez peut-être le faire en remplaçant la méthode get_form
dans votre classe ModelAdmin
, mais il est probablement plus facile d'éviter la personnalisation du formulaire et de simplement remplacer save_model
dans votre 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()
Toute cette approche m'a dérangé. Je voulais le dire exactement une fois, je l'ai donc implémenté dans le middleware. Ajoutez simplement WhodidMiddleware après votre middleware d’authentification.
Si vos champs created_by & modified_by sont définis sur editable = False
, vous ne devrez modifier aucun de vos formulaires.
"""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
Si vous utilisez des vues basées sur les classes, la réponse de Daniel nécessite davantage d'informations. Ajoutez ce qui suit pour vous assurer que l'objet de requête est disponible pour nous dans votre objet 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
En outre, comme déjà mentionné, vous devez renvoyer l'obj à la fin de ModelForm.save ()
voici comment je le fais avec des vues génériques:
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)
Sur la base de la réponse de bikeshedder , j’ai trouvé une solution car la sienne ne fonctionnait pas pour moi.
app/middleware/current_user.py
from threading import local
_user = local()
class CurrentUserMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
_user.value = request.user
return self.get_response(request)
def get_current_user():
return _user.value
settings.py
MIDDLEWARE = [
'Django.middleware.security.SecurityMiddleware',
'Django.contrib.sessions.middleware.SessionMiddleware',
'Django.middleware.common.CommonMiddleware',
'Django.middleware.csrf.CsrfViewMiddleware',
'Django.contrib.auth.middleware.AuthenticationMiddleware',
'Django.contrib.messages.middleware.MessageMiddleware',
'Django.middleware.clickjacking.XFrameOptionsMiddleware',
'common.middleware.current_user.CurrentUserMiddleware',
]
model.py
from common.middleware import current_user
created_by = models.ForeignKey(User, blank=False, related_name='created_by', editable=False, default=current_user.get_current_user)
J'utilise Python 3.5 et Django 1.11.3
Je ne crois pas que la réponse de Daniel soit la meilleure, car elle modifie le comportement par défaut d'un formulaire de modèle en enregistrant toujours l'objet.
Le code que j'utiliserais:
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
quel est le problème avec l'utilisation de quelque chose comme:
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
exclude = ['created_by']
def save(self, user):
obj = super().save(commit = False)
obj.created_by = user
obj.save()
return obj
Appelez-le maintenant comme myform.save(request.user)
dans views.
voici (fonction de sauvegarde de ModelForm) , qui n'a qu'un paramètre commit
.
La méthode 'save' de forms.ModelForm renvoie l'instance sauvegardée.
Vous devriez ajouter une dernière ligne à MyModelForm:
...
retour obj
Cette modification est nécessaire si vous utilisez des vues génériques create_object ou update_object.
Ils utilisent l'objet enregistré pour faire la redirection.
Pour les références futures, la meilleure solution que j'ai trouvée à ce sujet:
https://pypi.python.org/pypi/Django-crum/0.6.1
Cette bibliothèque est composée de middleware . Après avoir configuré cette bibliothèque, remplacez simplement la méthode save du modèle et procédez comme suit:
from crum import get_current_user
def save(self, *args, **kwargs):
user = get_current_user()
if not self.pk:
self.created_by = user
else:
self.changed_by = user
super(Foomodel, self).save(*args, **kwargs)
si vous créez un modèle abstrait et en héritez pour tous vos modèles, vous obtenez vos champs automatiquement renseignés created_by et modified_by.