Résolue

Django : @user_passes_test

# Fonctions # Listes # Django

Gabriel Trouvé

Mentor

Bonjour,


j'ai un blocage sur le user_passes_test.

Je précise que c'est la première fois que j'utilise la fonction lambda et que je la vois.

Donc lambda argument: expression si j'ai bien compris.

Comment Django est capable de récupérer les groupes en faisant u.groups.all() ?

Il y a sûrement un petit truc que je ne dois pas comprendre...


Et le fait de mettre "Modérateurs" in ... c'est comme faire :

if "Modérateurs" in ?


Merci d'avance


@user_passes_test(lambda u: "Modérateurs" in [group.name for group in u.groups.all()])
def view_login_cond(request):
    return HttpResponse("<h1>Restreindre avec une condition</h1>")

Salut Gabriel,

Effectivement, les fonctions lambda (fonctions anonymes) sont des fonctions sans nom que tu déclare sur une seule ligne, pratique quand tu veux juste effectuer une opération sans avoir besoin de déclarer une fonction spécifique pour cela.


La fonction anonyme suivante :


lambda u: "Modérateurs" in [group.name for group in u.groups.all()]


Est donc l'équivalent de :


def foo(u):
    return "Modérateurs" in [group.name for group in u.groups.all()]


groups c'est un attribut disponible sur les utilisateurs, qui te retourne un queryset avec tous les groupes auquel l'utilisateur appartient.


Par exemple sur Docstring :


>>> u = User.objects.get(first_name="Thibault", last_name="Houdon")
>>> u.groups.all()
<QuerySet [<Group: Aide Formateur>, <Group: rockstar>, <Group: Mentor>]>


Le décorateur user_passes_test permet l'accès à la vue si la fonction que tu lui passes retourne True (comme l'indique la docstring de la fonction) :

None


Si on reprend la fonction anonyme, tu as donc une compréhension de liste qui permet de récupérer le nom de tous les groupes de l'utilisateur :

[group.name for group in u.groups.all)]


☝️ Ici on boucle sur tous les groupes de l'utilisateur : u.groups.all() et on récupère leur nom (group.name).


Si je reprends l'exemple de Docstring, ça donnerait :

>>> [group.name for group in u.groups.all()]
['Aide Formateur', 'rockstar', 'Mentor']


Ensuite avec l'opérateur in, on vérifie si la chaîne de caractères Modérateurs est dans la liste :

>>> "Modérateurs" in ['Aide Formateur', 'rockstar', 'Mentor']
False


Dans ce cas-ci ce n'est pas le cas, et je n'aurais donc pas accès à la vue.

Dans ce cas-ci je ne comprends pas comment Python sait que u correspond à une instance de User et que du coup on peut faire u.groups. (?)

lol désolé mais je ne sais pas pk j'ai eu un blocage avec la fonction anonyme :s


Edit : c'est la fonction elle même qui faut que l'argument est un "user object"?

Effectivement, il faut aller fouiller un peu plus dans le code de la fonction user_passes_test et avec le mécanisme de décorateur ça fait un peu peur je te l'accorde ^^



Tu vois ici que le premier paramètre test_func récupère ta fonction anonyme.


Et ce test_func est ensuite exécuté et Django lui passe l'utilisateur connecté avec request.user.


Du coup de ton côté du déclare une fonction anonyme, avec par exemple :

@user_passes_test(lambda u: u.is_superuser)


Qui, je le rappelle, revient exactement au même que de faire :


def foo(user):
    return user.is_superuser

@user_passes_test(lambda u: u.is_superuser)
def index(request):
    return render(request, 'index.html')


Quand un utilisateur veut accéder à ta vue, il passe par le décorateur (user_passes_test).


Ce décorateur exécute ta fonction foo (car elle est passée au paramètre test_func) :


# Le code du décorateur de Django dans user_passes_test 👇
def _wrapped_view(request, *args, **kwargs):
    if test_func(request.user):
        return view_func(request, *args, **kwargs)

# Ça revient à 👇
def _wrapped_view(request, *args, **kwargs):
    if foo(request.user):
        return view_func(request, *args, **kwargs)


Tu vois donc bien ici que request.user est passé à foo.


Et dans foo, tu vérifie si l'utilisateur qui est "superuser" et tu retourne cette condition (tu vas donc retourner True ou False).


Si ta fonction foo retourne True, Django va retourner la vue d'origine. Dans le cas contraire, il redirige vers la page de login :


Donc tu vois que c'est Django qui passe request.user à ta fonction qui sert de vérification.


Après, tu pourrais très bien ne pas l'utiliser et vérifier totalement autre chose !


Exemple avec une fonction qui retourne True si la date d'aujourd'hui est le 23 janvier 2023 :

def foo(user):
    return datetime.now().date() == date(2023, 1, 22)

@user_passes_test(foo)
def home(request):
    return render(request, 'index.html')


Avec une fonction anonyme :


@user_passes_test(lambda _: datetime.now().date() == date(2023, 1, 22))
def home(request):
    return render(request, 'index.html')


Tu remarques que j'ai mis un tiret du bas / underscore. Si tu l'enlèves, tu obtiens une erreur :

TypeError: <lambda>() takes 0 positional arguments but 1 was given


Logique ! Car le décorateur user_passes_test passe request.user à ta fonction anonyme. Si tu ne mets pas de paramètre pour récupérer cet utilisateur envoyé par Django, tu as une erreur (0 arguments but 1 was given).


Dans ce cas-ci, je n'utilise pas l'argument récupéré (l'utilisateur), du coup par convention, je mets un tiret du bas pour indiquer que le paramètre n'est pas utilisé.


Mais j'aurais très bien pu mettre Patrick et ça fonctionnerait aussi ;)


@user_passes_test(lambda Patrick: datetime.now().date() == date(2023, 1, 22))
def home(request):
    return render(request, 'index.html')


Je pense que je vais faire une vidéo sur le sujet, beaucoup de concepts intéressants sont en jeu ici :)

Gabriel Trouvé

Mentor

Merci Thibault ! J'ai tout compris. Et j'ai posé la question trop vite car je ne suis pas encore allé dans le code user passes test je ne me connecte que ce soir. J'aurais du regarder avant. Le ctrl+B est vraiment pratique sur pycharm. Mais avec ton explication je sais comment fonctionne user passes test ! 😁

Inscris-toi

(c'est gratuit !)

Inscris-toi

Tu dois créer un compte pour participer aux discussions.

Créer un compte

Rechercher sur le site

Formulaire de contact

Inscris-toi à Docstring

Pour commencer ton apprentissage.

Tu as déjà un compte ? Connecte-toi.