web-dev-qa-db-fra.com

Accès à la demande de l'utilisateur dans un signal post_save

J'ai fait le signal post_save ci-dessous dans mon projet.

from Django.db.models.signals import post_save
from Django.contrib.auth.models import User

# CORE - SIGNALS
# Core Signals will operate based on post

def after_save_handler_attr_audit_obj(sender, **kwargs):
    print User.get_profile()

    if hasattr(kwargs['instance'], 'audit_obj'):
        if kwargs['created']:
            kwargs['instance'].audit_obj.create(operation="INSERT", operation_by=**USER.ID**).save()
        else:
            kwargs['instance'].audit_obj.create(operation="UPDATE").save()


# Connect the handler with the post save signal - Django 1.2
post_save.connect(after_save_handler_attr_audit_obj, dispatch_uid="core.models.audit.new")

La colonne operation_by, je veux obtenir le user_id et le stocker. Une idée comment faire ça?

38
Mo J. Mughrabi

Impossible. L'utilisateur actuel est uniquement disponible via la demande, qui n'est pas disponible lors de l'utilisation de fonctionnalités purement de modèle. Accédez à l'utilisateur dans la vue d'une manière ou d'une autre.

27

J'ai pu le faire en inspectant la pile et en recherchant la vue, puis en regardant les variables locales pour que la vue reçoive la demande. Cela ressemble à un peu de piratage, mais cela a fonctionné.

import inspect, os

@receiver(post_save, sender=MyModel)
def get_user_in_signal(sender, **kwargs):
    for entry in reversed(inspect.stack()):
        if os.path.dirname(__file__) + '/views.py' == entry[1]:
            try:
                user = entry[0].f_locals['request'].user
            except:
                user = None
            break
    if user:
        # do stuff with the user variable
13
PaulR

Ignacio a raison. Les signaux du modèle de Django sont destinés à informer les autres composants du système des événements associés aux instances et de leurs données respectées, donc je suppose qu'il est valide que vous ne pouvez pas, par exemple, accéder aux données de demande à partir d'un modèle post_save signal, sauf si les données de la demande ont été stockées ou associées à l'instance.

Je suppose qu'il existe de nombreuses façons de le gérer, allant du pire au meilleur, mais je dirais que c'est un exemple prime pour créer des vues génériques basées sur des classes/fonctions qui géreront automatiquement cela pour vous.

Ayez vos vues qui héritent de CreateView, UpdateView ou DeleteView héritent en outre de votre classe AuditMixin si elles gèrent des verbes qui fonctionnent sur des modèles qui doivent être audités . Le AuditMixin peut alors se connecter aux vues qui ont réussi à créer des objets\update\delete et à créer une entrée dans la base de données.

Est parfaitement logique, très propre, facilement enfichable et donne naissance à des poneys heureux. Côté opposé? Vous devrez soit être sur la prochaine version Django version 1.3 ou vous devrez passer un peu de temps à plier les vues génériques basées sur les fonctions et à en fournir de nouvelles pour chacune opération d'audit.

7
Filip Dupanović

Pour la traçabilité, ajoutez deux attributs à votre modèle (created_by et updated_by), dans "updated_by", enregistrez le dernier utilisateur qui a modifié l'enregistrement. Ensuite, dans votre signal, vous avez l'utilisateur:

models.py:

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')
    created_by = models. (max_length=100)
    updated_by = models. (max_length=100)

views.py

    p = Question.objects.get(pk=1)
    p.question_text = 'some new text'
    p.updated_by = request.user
    p.save()

signaux.py

@receiver(pre_save, sender=Question)
def do_something(sender, instance, **kwargs):
    try:
        obj = Question.objects.get(pk=instance.pk)
    except sender.DoesNotExist:
        pass
    else:
        if not obj.user == instance.user: # Field has changed
            # do something
            print('change: user, old=%s new=%s' % (obj.user, instance.user))
5

Vous pouvez également utiliser Django-reversion à cette fin, par exemple.

from reversion.signals import post_revision_commit
import reversion

@receiver(post_save)
def post_revision_commit(sender, **kwargs):
    if reversion.is_active():
        print(reversion.get_user())

En savoir plus sur leur API https://Django-reversion.readthedocs.io/en/stable/api.html#revision-api

5
bjorn

Pourquoi ne pas ajouter un middleware avec quelque chose comme ceci:

class RequestMiddleware(object):

    thread_local = threading.local()

    def process_request(self, request):
        RequestMiddleware.thread_local.current_user = request.user

et plus tard dans votre code (spécialement dans un signal dans cette rubrique):

thread_local = RequestMiddleware.thread_local
if hasattr(thread_local, 'current_user'):
    user = thread_local.current_user
else:
    user = None
4
firebird631

J'imagine que vous auriez compris cela, mais j'ai eu le même problème et j'ai réalisé que toutes les instances que je crée avaient une référence à l'utilisateur qui les crée (c'est ce que vous recherchez)

1
kiril
context_processors.py

from Django.core.cache import cache

def global_variables(request):
    cache.set('user', request.user)

----------------------------------
in you model

from Django.db.models.signals import pre_delete
from Django.dispatch import receiver
from Django.core.cache import cache
from news.models import News

@receiver(pre_delete, sender=News)
def news_delete(sender, instance, **kwargs):
    user = cache.get('user')

in settings.py

TEMPLATE_CONTEXT_PROCESSORS = (
    'web.context_processors.global_variables',
)