Search code examples
pythondjangohttp-redirectdjango-viewsdjango-urls

Automatically route logout/ URL to Login/ in Django


I'm following a tutorial to build a To Do list in Python and Django, I've finished implementing authentication and User-based access using built-in views,

Upon clicking the "logout" button I'm correctly redirected to the login page. However, if I type http://localhost:8000/logout directly in the URL it shows a 405 error. My goal is to redirect the user if someone directly types the URL to the login page. How do I do that?

Below is my url.py file

from django.urls import path, reverse_lazy
#from . import views                 # For Functions, thus but now it's class so 
from .views import TaskList, TaskDetail, TaskCreate, TaskUpdate, TaskDelete, CustomLoginView, RegisterPage         # CLASSES FROM VIEWS FILES 
from django.contrib.auth.views import LogoutView

urlpatterns = [
    path('login/',CustomLoginView.as_view(),name='login'),
    path('logout/',LogoutView.as_view(next_page=reverse_lazy('login')),name='logout'),
    path('register/', RegisterPage.as_view(),name="register"),


    path('', TaskList.as_view(), name='tasks'),                             # root URL - #base url thus empty string, view name
    path('task/<int:pk>', TaskDetail.as_view(), name='task'),               # when clicked on list item - sub url <int:pk> - pk - primary key  
    path('create-task/', TaskCreate.as_view(), name='task-create'),         # url accessed to create the task
    path('update-task/<int:pk>', TaskUpdate.as_view(), name='task-update'),         # url accessed to update the task
    path('delete-task/<int:pk>', TaskDelete.as_view(), name='task-delete'),         # url accessed to delete the task
]

Below are my relevent view.py functions

from django.shortcuts import render, redirect
from django.http import HttpResponse
from django.views.generic.list import ListView
from django.views.generic.detail import DetailView  #description pane
from django.views.generic.edit  import CreateView, UpdateView, DeleteView, FormView   # create form; update  prefill and modify the data
from django.urls import reverse_lazy                # redirects user to certain part or page

from django.contrib.auth.views import LoginView                 
from django.contrib.auth.mixins import LoginRequiredMixin       #add this before builtin view to prevent unauthorised user accessing data, 
from django.contrib.auth.forms import UserCreationForm          #built in user creation
from django.contrib.auth import login                           #to login user automatically once registered

from .models import Task

# Create your views here.
#if not logged in then restrict people from seeing it 
#gatekeeper thus on top - login optionality 

class CustomLoginView(LoginView):
    template_name = 'base/login.html'
    fields = '__all__'
    redirect_authenticated_user = True                     #--- unauthenticated users to be redirected 
    def get_success_url(self):
        return reverse_lazy('tasks')


class RegisterPage(FormView):
    template_name = 'base/register.html'                         
    form_class = UserCreationForm                           #--- passing prebuilt form
    redirect_authenticated_user = True
    success_url = reverse_lazy('tasks')                     #--- redirect on success when registered

    def form_valid(self, form):                         #-- the form submission is valid by rules
        user = form.save()                              #-- save the user
        if user is not None:                            #-- is user is suceesfully created
            login(self.request, user)                   #-- redirect control to login function for autologin
        return super().form_valid(form)
    
    def get(self,*args, **kwargs):
        if self.request.user.is_authenticated:          
            return redirect('tasks')                    
        return super().get(*args,**kwargs)              

Below is where the logout button is placed in the whole application

task_list.html

<!-- homepage tasks listing  -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>To Do</title>
</head>
<body>
    
    {% if request.user.is_authenticated %}
    <p>{{ request.user }}</p>

    <!-- <a href="{% url 'logout' %}">Log Out</a>   Won't work error 405, need POst and csrf token by django -->

    <form action="{% url 'logout' %}" method="post">
        {% csrf_token %}
        <button type="submit">Logout</button>
    </form>
    {% else %}
    <a href="{% url 'login' %}">Log In</a>
    {% endif %}

    <hr>
    <h1>My To Do List</h1>
    <a href="{% url 'task-create' %}"> Add Items</a>
    <table>
        <tr>
            <th>Items</th>              <!-- th acts as cell header -->
            <th> </th>                  <!--   #td only shows if we've a th for each td-->
            <th> </th>
            <th> </th>
        </tr>
        <!-- loop through all the items  -->
        {% for task in tasks %}   <!-- this si django templating syntax-->
        <tr>
            <td>{{task.title}}</td>                                     <!-- td acts as cell value -->
            <td><a href="{% url 'task' task.id %}">View</a></td>
            <td><a href="{% url 'task-update' task.id %}">Edit</a></td>
            <td><a href="{% url 'task-delete' task.id %}">Delete</a></td>
        </tr>
        {% empty %}
        <h3>No Items in List</h3>
        {% endfor %}
    </table>
</body>
</html>

I've tried searching but everywhere only the below solution is mentioned and only for a dedicated button. no solution on how to handle the localhot:8000/logout URL

<form action="{% url 'logout' %}" method="post">
        {% csrf_token %}
        <button type="submit">Logout</button>
</form>

Solution

  • based on the Django's source code for LogoutView

    
    class LogoutView(RedirectURLMixin, TemplateView):
        """
        Log out the user and display the 'You are logged out' message.
        """
    
        http_method_names = ["post", "options"]
        template_name = "registration/logged_out.html"
        extra_context = None
    
        @method_decorator(csrf_protect)
        @method_decorator(never_cache)
        def dispatch(self, request, *args, **kwargs):
            return super().dispatch(request, *args, **kwargs)
    
        def post(self, request, *args, **kwargs):
            """Logout may be done via POST."""
            auth_logout(request)
            redirect_to = self.get_success_url()
            if redirect_to != request.get_full_path():
                # Redirect to target page once the session has been cleared.
                return HttpResponseRedirect(redirect_to)
            return super().get(request, *args, **kwargs)
    
    

    linked here: https://github.com/django/django/blob/main/django/contrib/auth/views.py

    This view will not handle GET requests and when you call it, will return 405 Method Not Allowed.

    So if you really need to do this, you have to write your own Logout view.