Django's template doesn't render as expected (template language issue)

Hi everyone, following this topic https://forum.djangoproject.com/t/implementing-a-custom-backend-for-email-authentication/5231/16

I rewrote my app from scratch and decided to move forward with the rest of the training.

That’s why I wanted to tweak my index.html in the sense that when a User Logs In, he will automatically redirected to the home page and the menu will change from “Log In” to “Log Out” :

{% block page_content %}
<div id="page_navbar">
    {% if user.is_authenticated %}
    <a href="{% url 'app_accounts:logoutUser' %}">Log Out</a>
    {% else %}
    <a href="{% url 'app_accounts:loginUser' %}">Log In</a>
    {% endif %}
    <a href="{% url 'app_accounts:signupUser' %}">Sign Up</a>
</div>
{% endblock %}

views.py


def loginUser(request):
    if request.method == "POST":
        form = loginUserForm(request.POST)

        if form.is_valid():
            form_email = form.cleaned_data["email"]
            form_password = form.cleaned_data["password"]

            user = authenticate(request, username=form_email, password=form_password)

            if user is not None:
                login(request, user)
                print(user)
                print(user.is_authenticated)
                return redirect("app_base:index")
    else:
        form = loginUserForm()

    return render(request, "app_accounts/login.html", {"form":form})

def logoutUser(request):
    logout(request)
    return redirect("app_base:index")

Here is the output :

[17/Nov/2020 21:20:33] "GET /favicon.ico HTTP/1.1" 404 2240
[17/Nov/2020 21:20:49] "GET /accounts/login HTTP/1.1" 200 649
test@mail.com
True
[17/Nov/2020 21:20:53] "POST /accounts/login HTTP/1.1" 302 0
[17/Nov/2020 21:20:53] "GET / HTTP/1.1" 200 340

So why don’t I get the “Log Out” menu even so I am authenticated ?

Thank you in advance

Is user present in the template context?

1 Like

To add to and expand on @CodenameTim’s answer, you shared a view with us - but it’s the wrong view.

In the case of a successful login, you are redirecting to a url identified by “app_base:index”. It’s that view that should be rendering the template fragment you supplied above, and that’s the view we would need to see.

Ken

Hi @KenWhitesell, @CodenameTim,

Yes the view you see above is the index.html view (app_base:index).

I’ve tried the following in order to send user to the view but no result :

from django.shortcuts import render

def index(request):
    return render(request, "app_base/index.html", {"user":user})

(Can’t do that because use instance was present inside loginUser view)

When you call render, you need to pass in {“user” : request.user} not just {“user” : user}. user is not defined in that function.

Hi @Berries,

I did what you noticed but no result.

I changed both my login and index views in order to get more debug info :

def loginUser(request):
    if request.method == "POST":
        form = loginUserForm(request.POST)

        if form.is_valid():
            form_email = form.cleaned_data["email"]
            form_password = form.cleaned_data["password"]

            user = authenticate(request, username=form_email, password=form_password)

            if user is not None:
                print("USER is => " + str(user))
                print("USER.IS_AUTHENTICATED => " + str(user.is_authenticated))
                login(request, user)
                return redirect("app_base:index")
    else:
        user = None
        form = loginUserForm()

    return render(request, "app_accounts/login.html", {"form":form})
def index(request):
    print("REQUEST.USER.IS_AUTHENTICATED => " + str(request.user.is_authenticated))
    return render(request, "app_base/index.html", {"user":request.user})

And the ouput :

USER is => test@mail.com
USER.IS_AUTHENTICATED => True
[18/Nov/2020 12:33:39] "POST /accounts/login HTTP/1.1" 302 0
REQUEST.USER.IS_AUTHENTICATED => False
[18/Nov/2020 12:33:39] "GET / HTTP/1.1" 200 340

Just to cover all the bases, do you have the appropriate middleware classes in your settings?

‘django.contrib.sessions.middleware.SessionMiddleware’,
‘django.contrib.auth.middleware.AuthenticationMiddleware’,

and you’re using the system standard login() method, right? (you haven’t replaced that with a custom method?)

Also, what are you currently using for your User model? Is it inheriting from AbstractUser or AbstractBaseUser?

  • Middleware classes :
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',
]
  • Login method : default one
  • models.py :
from django.db import models
from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    username = None
    email = models.EmailField(max_length=40, unique=True, blank=False)
    password = models.EmailField(max_length=40, blank=False)

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = []

Yep, I’m stumped at the moment - which means I need to try to recreate the issue locally.

I’m really glad you included that print in index, that’s illuminating.

I’d be really curious to see what is in request.session at that point too - and what request.user is. I’m beginning to believe that there’s something in the session middleware that isn’t pulling the user correctly - but no ideas yet what that might be.

As mainly an aside, did you make a mistake coping the model here? password should be a CharField.

Ignore

In your other post your backend’s authenticate signature was:

def authenticate(self, username=None, password=None):

It should be:

def authenticate(self, request, username=None, password=None):

Also be sure to define the get_user method on your backend:

def get_user(self, user_id):
    try:
        return User.objects.get(pk=user_id)
    except User.DoesNotExist:
        return None

The above was pulled from the example at https://docs.djangoproject.com/en/3.1/topics/auth/customizing/#writing-an-authentication-backend

You are right there was a mistake in my model.

Yet, even after adding the Manager, it doesn’t change the bug :

from django.db import models
from django.contrib.auth.models import AbstractUser

class UserManager(models.Manager):
    def get_user(self, id):
        try:
            user = User.objects.get(pk=id)
        except:
            return None

class User(AbstractUser):
    username = None
    email = models.EmailField(max_length=40, unique=True, blank=False)
    password = models.CharField(max_length=40, blank=False)
    objects = UserManager()

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = []

output :

[18/Nov/2020 18:03:24] "GET /accounts/login HTTP/1.1" 200 649
USER is => test@mail.com
USER.IS_AUTHENTICATED => True
[18/Nov/2020 18:03:29] "POST /accounts/login HTTP/1.1" 302 0
REQUEST.USER.IS_AUTHENTICATED => False

Whoops, def get_user(self, id): belongs on your custom backend class.

Well I’ve tried different solutions but I still get the same output.

That’s weird

[19/Nov/2020 06:39:01] "GET /accounts/login HTTP/1.1" 200 649
USER is => test@mail.com
USER.IS_AUTHENTICATED => True
[19/Nov/2020 06:39:05] "POST /accounts/login HTTP/1.1" 302 0
REQUEST.USER.IS_AUTHENTICATED => False
[19/Nov/2020 06:39:05] "GET / HTTP/1.1" 200 340

@KenWhitesell, @Berries, @CodenameTim :

Guys, I found a workaround, I just used “ModelBackend” instead of “BaseBackend” in my custom backends.py file :

from django.contrib.auth.backends import ModelBackend
from .models import User

class EmailBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None):
        try:
            user = User.objects.get(email=username)
            return user
        except User.DoesNotExist:
            return None
[20/Nov/2020 06:30:33] "GET /accounts/login HTTP/1.1" 200 649
USER is => test@mail.com
USER.IS_AUTHENTICATED => True
[20/Nov/2020 06:30:39] "POST /accounts/login HTTP/1.1" 302 0
REQUEST.USER.IS_AUTHENTICATED => True
[20/Nov/2020 06:30:39] "GET / HTTP/1.1" 200 342

Accorting to this link : https://stackoverflow.com/questions/37332190/django-login-with-email, it is due to the fact that ModelBackend already implements get_user like methods.

So, my guess here is that the issue was coming from my custom UserManager, I don’t really understand why but I’d be grateful if someone would explain it :wink:

Without seeing the complete then-current versions of your custom manager, backend, and user model, it would be tough to explain. Browsing through the various classes show some tight interrelationships among them.

Just as one example, ModelBackend implements the following get_user method:

    def get_user(self, user_id):
        try:
            user = UserModel._default_manager.get(pk=user_id)
        except UserModel.DoesNotExist:
            return None
        return user if self.user_can_authenticate(user) else None

note how it’s not looking for objects as a specific manager, it’s looking for whatever the user model defines as the default manager.
(I’m not saying that this is in any way related to the issues you were facing. I’m only pointing out one example of the interrelationships existing between the backends and user model classes.)

So I think the only way to get the explanations you’re looking for would be to see the classes involved and follow the flow through the various components.

1 Like