web-dev-qa-db-fra.com

Django: remplir l'ID utilisateur lors de l'enregistrement d'un modèle

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?

40
seanf

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
33
Daniel Roseman

La méthode la moins contraignante consiste à utiliser une variable CurrentUserMiddleware pour stocker l'utilisateur actuel dans un objet local de thread:

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

Il ne vous reste plus qu'à ajouter ce middleware à votre middleware d'authentification MIDDLEWARE_CLASSES after .

settings.py

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.

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)

Allusion:

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.

24
bikeshedder

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()
22
Tim Fletcher

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
13
mindlace

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 ()

7
Monster

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)
6
yretuta

Sur la base de la réponse de bikeshedder , j’ai trouvé une solution car la sienne ne fonctionnait pas pour moi.

  1. 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
    
  2. 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',
    ]
    
  3. 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

3
leila

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:

forms.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
1
jgadelange

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.

1
simple_human

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.

1
Florentin

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.

0
durdenk