How to leverage CBVs with User authentication properly with Mixins

I’m exploring Class Based Views for the first time and I have a rudimentary Django CMS that is working quite well to my delight. Next I am exploring how to implement Django’s authentication and sessions feature which I can’t seem to get working as intended.

What works:

  1. Django is serving a template facilitated by a ListView CBV on the landing page at an address like this: http://127.0.0.1:8000/
  2. The list of hyperlinks presented on that page take the web visitor to the corresponding detail page as indicated by a DetailView and identified by a unique slug.
  3. I’ve already got a basic custom login page: http://127.0.0.1:8000/accounts/login/
    and a logout button which then redirects the user to this location: http://127.0.0.1:8000/accounts/logout/
  4. I have created non-Admin user credentials which Django will accept. I can add permissions in the Admin Dashboard.

You can see the above listed features by checking out this mirror of my local dev server: https://8ff1-2607-fea8-87e3-3200-00-a0f5.ngrok.io/
However please be advised that Chrome may alert you to a potential phishing attack. To access the site, you may need to bypass the phishing warning.

What doesn’t work:
The problem with the above is that with or without valid credentials, any web visitor can still view all the content.

  1. When a user is signed out, the web visitor is still able to traverse and descend down into the other content.
  2. I’m trying to get the home page to present the web visitor with the login page requiring every web visitor to enter their credentials. But right now, Django serves all the other content.

My question: How do I get the User authentication Mixins to properly require valid login credentials? Right now everything is open to the public, even though I have added Mixins and tried implementing a custom LoginView, as described below

What I have tried:

I tried adding this path to my top-level project urls.py:

from contents.views import CustomLoginView
...
path('', CustomLoginView.as_view(),name='home'),

I accompanied the inclusion of the above path entry with this code in my views.py:

class CustomLoginView(LoginView):
   #template_name = 'registration/login.html'
   fields = '__all__'
   redirect_authenticated_user = True

   # def get_success_url(self):
   #    return reverse_lazy('/')

As you can see above, there are some lines which are commented out. I have tried running my server with those lines commented in and commented out. No dice.

It’s also worth pointing out that at the top of my views.py I included: from django.contrib.auth.mixins import LoginRequiredMixin and also added LoginRequiredMixin as an argument inside every single CBV, like in this code snippet:

class InductionDetailView(DetailView, LoginRequiredMixin):
   model = Induction
   context_object_name = 'inductions'

I did that for all the other models.

I created these (non-Admin) credentials for web visitor to test but given that Chrome shows it is a non-secure site, CSRF token mechanism kicks in, preventing you from continuing. But I thought I would include this anyway just in case:

  • Username: superdave
  • Password: osbourne12
    https://8ff1-2607-fea8-87e3-3200-00-a0f5.ngrok.io/accounts/logout/
    https://8ff1-2607-fea8-87e3-3200-00-a0f5.ngrok.io/accounts/login/

My code base:

hypnos_juicer/urls.py (project level urls):

from django.contrib import admin
from django.urls import path, include
# from contents.views import CustomLoginView
 
urlpatterns = [
   path('admin/', admin.site.urls),
   # path('', CustomLoginView.as_view(),name='home'),
   path('accounts/',include('django.contrib.auth.urls')),
   path('', include('contents.urls')),
   # path('classroom/', include('classroom.urls')),
]
 

contents/urls.py (app urls):

from django.urls import path, include
from .views import ContentListView, InductionDetailView,ResearchDetailView, PreambleDetailView, ScriptSuggestionDetailView,StockScriptDetailView, index
 
app_name = 'contents'
 
urlpatterns = [
   path('', ContentListView.as_view(),name='home'),
   path('preambling/<str:slug>', PreambleDetailView.as_view(),name='preamble_details'),
   path('inductioning/<str:slug>', InductionDetailView.as_view(),name='induction_details'),
   path('scripting/<str:slug>', ScriptSuggestionDetailView.as_view(),name='scriptsuggestion_detail'),
   path('stock_scripting/<str:slug>', StockScriptDetailView.as_view(),name='stockscript_detail'),
   path('researching/<str:slug>', ResearchDetailView.as_view(),name='research_detail'),
]

views.py (app):

from django.shortcuts import render
from django.urls import reverse_lazy
from django.views.generic import ListView,DetailView
from .models import Preamble, Induction, Research, ScriptSuggestion,StockScript,Content
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.views import LoginView
from django.contrib.auth.decorators import login_required
''' class CustomLoginView(LoginView):
   #template_name = 'registration/login.html'
   fields = '__all__'
   redirect_authenticated_user = True
 
   #def get_success_url(self):
   #    return reverse_lazy('/')
'''
def index(request):
   return render(request,'contents/index.html')
 
class ContentListView(ListView, LoginRequiredMixin):
   # model_list.html
   # model = Induction
   model = Content
   # template_name = 'home.html'
  
   # fields =['author','title',]
  
   def get_context_data(self, **kwargs):
       # Call the base implementation first to get a context
       context = super(ContentListView, self).get_context_data(**kwargs)
       # Add in a QuerySet of all the Baklawa
       context['inductions'] = Induction.objects.all()
       context['preambles'] = Preamble.objects.all()
       context['research'] = Research.objects.all()
       context['scriptsuggestions'] = ScriptSuggestion.objects.all()
       context['stockscripts'] = StockScript.objects.all()
       return context
 
class PreambleDetailView(DetailView, LoginRequiredMixin):
   model = Preamble
   context_object_name = 'preambles'
 
class InductionDetailView(DetailView, LoginRequiredMixin):
   model = Induction
   context_object_name = 'inductions'
  
class ScriptSuggestionDetailView(DetailView, LoginRequiredMixin):
   model = ScriptSuggestion
   context_object_name = 'scriptsuggestions'
 
class StockScriptDetailView(DetailView, LoginRequiredMixin):
   model = StockScript
   context_object_name = 'stockscripts'
 
 
class ResearchDetailView(DetailView, LoginRequiredMixin):
   model = Research
   context_object_name = 'research'
 

models.py (app):

from django.db import models
 
class Content(models.Model):
   pass
 
# Create your models here.
class Preamble(models.Model):
   title = models.CharField(max_length=300,blank=True)
   body = models.TextField(max_length=300000,blank=True)
   author = models.CharField(max_length=30,blank=True)
   slug = models.SlugField(unique=True,blank=True)
   # posting_date = models.DateField(auto_now=False, auto_now_add=False, **options), https://docs.djangoproject.com/en/4.1/ref/models/fields/#django.db.models.DateField
  
   def __str__(self):
       return f'{self.title}'
  
class Induction(models.Model):
   title = models.CharField(max_length=300,blank=True)
   body = models.TextField(max_length=300000,blank=True)
   author = models.CharField(max_length=30,blank=True)
   slug = models.SlugField(unique=True,blank=True)
  
   def __str__(self):
       return f'{self.title}'
 
class ScriptSuggestion(models.Model):
   title = models.CharField(max_length=300,blank=True)
   body = models.TextField(max_length=300000,blank=True)
   author = models.CharField(max_length=300,blank=True)
   slug = models.SlugField(unique=True,blank=True)
 
   def __str__(self):
       return f'{self.title}'
 
class Research(models.Model):
   title = models.CharField(max_length=300,blank=True)
   body = models.TextField(max_length=300000,blank=True)
   author = models.CharField(max_length=300,blank=True)
   slug = models.SlugField(unique=True,blank=True)
 
   def __str__(self):
       return f'{self.title}'
 
class StockScript(models.Model):
   title = models.CharField(max_length=300,blank=True)
   body = models.TextField(max_length=300000,blank=True)
   author = models.CharField(max_length=300,blank=True)
   slug = models.SlugField(unique=True,blank=True)
 
   def __str__(self):
       return f'{self.title}'

models.py:

from django.db import models
 
class Content(models.Model):
   pass
 
# Create your models here.
class Preamble(models.Model):
   title = models.CharField(max_length=300,blank=True)
   body = models.TextField(max_length=300000,blank=True)
   author = models.CharField(max_length=30,blank=True)
   slug = models.SlugField(unique=True,blank=True)
   # posting_date = models.DateField(auto_now=False, auto_now_add=False, **options), https://docs.djangoproject.com/en/4.1/ref/models/fields/#django.db.models.DateField
  
   def __str__(self):
       return f'{self.title}'
  
class Induction(models.Model):
   title = models.CharField(max_length=300,blank=True)
   body = models.TextField(max_length=300000,blank=True)
   author = models.CharField(max_length=30,blank=True)
   slug = models.SlugField(unique=True,blank=True)
  
   def __str__(self):
       return f'{self.title}'
 
class ScriptSuggestion(models.Model):
   title = models.CharField(max_length=300,blank=True)
   body = models.TextField(max_length=300000,blank=True)
   author = models.CharField(max_length=300,blank=True)
   slug = models.SlugField(unique=True,blank=True)
 
   def __str__(self):
       return f'{self.title}'
 
class Research(models.Model):
   title = models.CharField(max_length=300,blank=True)
   body = models.TextField(max_length=300000,blank=True)
   author = models.CharField(max_length=300,blank=True)
   slug = models.SlugField(unique=True,blank=True)
 
   def __str__(self):
       return f'{self.title}'
 
class StockScript(models.Model):
   title = models.CharField(max_length=300,blank=True)
   body = models.TextField(max_length=300000,blank=True)
   author = models.CharField(max_length=300,blank=True)
   slug = models.SlugField(unique=True,blank=True)
 
   def __str__(self):
       return f'{self.title}'

Resources I have used so far:

You are oh-so-close.

Unfortunately, the only reference you missed listing is the one with the specific answer to your issue.

See The LoginRequiredMixin

Specifically, the very first paragraph in which it says:

This mixin should be at the leftmost position in the inheritance list.

I’ll point out here that LoginRequiredMixin is not an “argument” to the class. These are class definitions, so this mixin is a base class being used in the construction of your view class.

If this distinction isn’t clear, or to just enhance your understanding of CBVs and topics related to them, I would suggest you spend some time learning about Python classes, class inheritance, and the MRO. (This becomes more important when you look at extending or enhancing the functionality of these CBVs.)

1 Like

Hi @KenWhitesell! Thanks for the reply. This answers my question. For all my CBV’s in my app’s views.py I swapped the position of the LoginRequiredMixin in the class constructor. That achieves what I have set out to accompish for now. My website is now protected and the user login credentials work beautifully.

I’ve got big plans to extend this web app. When I come up with more questions I will be sure to report back here.

Noted. My project so far is a rudimentary Django CMS like a blog. I am able to get away with using basic CBVs. No need yet to build advanced features. One day maybe I will get there.

Improving my overall Python chops in general is a great idea which will then enable me to write my own Django middleware and take my Django views to the next-level.

In 2020 I completed 32 PyBites on: https://codechalleng.es/
Earlier this year I completed 14 exercises on https://exercism.org/

These two destinations on the web are excellent resources for learning Python. Continuing to work on those Python challenges, specifically with classes, will be my next step to expand my ability to build Django projects.