Toggling landing page username/password form from Admin Dashboard

I’mt rying to write a feature from my custom CMS for the admin to switch home page login (username / password) form input boxes from available to unavailable. In my web app, I’ve affectionately named the class model attribute the “nuclear” option.

Here is how the page should render by default:

image

Then when the admin switches the ‘nuclear’ Bool to True, the site’s home page should look like this:

image

The above describes what I am trying to accomplish.

The problem I have is that whether I’ve toggled the Bool checkbox in my Admin Dashboard on or off, there is no change to the login page. Here is my Admin interface showing the “nuclear” check box:
image

Here is the relevant code block in my Jinja template (and pay particular attention to the first line):

{% if not controls.nuclear %}

<h1> Site-wide access revoked</h1>

{% else %}

<form method='post' action="{% url 'index' %}">
{% csrf_token %}
<h2>
{{form.username.label_tag}}
{{form.username}}
</h2>
<h2>
{{form.password.label_tag}}
{{form.password}}
<input type="submit" value='login'>
</h2>
<input type="hidden" name='next' value="{{next}}">
 </form> 

{% endif %}

At the first line, {% if not controls.nuclear %}, if I manually remove not, then Django will successfully serve the username/password form and restoring not by adding it back, Django will serve the h1 “Access Revoked” line. So it kind of works. What I am trying to figure out now is how to get the Admin Dashboard checkbox to trigger the change rather than me having to manually enter/remove the negation operator into the template.

I am not sure what I am doing wrong.

Below are some addition snippets from the relevant source code in my project repo.

Here is my views.py. Take note that I am using the LoginView CBV and am over-riding the context_object_name to 'controls' which matches what I am using in my template:

class Gateway(LoginView): 
    model = AuthToggle
    fields = '__all__'
    context_object_name = 'controls'
    template_name = 'registration/login.html'
    redirect_authenticated_user = True

    def get_success_url(self):
        return reverse_lazy('portal')

    def dispatch(self, request, *args, **kwargs):
        # Overiding the dispatch method to add extra functionality to the loginview
        response = super().dispatch(request, *args, **kwargs)

        auth_toggle = AuthToggle.objects.first()
        if self.request.user.is_authenticated and auth_toggle.is_protected and not request.user.is_staff:
            # It is neccessary to store the time in session to set the session expiry + Start session timer
            request.session['session_start_time'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            request.session.set_expiry(auth_toggle.timeout * 60) # I am converting the minutes in secconds
            # Print session start time
            notification.messages_print(
                        'info', 'New session of ' + str(SESSION_TIMEOUT.timeout) + ' minutes has started'
                        )
            print(f"Time session started at: {request.session['session_start_time']}")
        elif not self.request.user.is_authenticated and not auth_toggle.is_protected:
            return redirect('portal')
        return response

Here is my models.py:

class AuthToggle(models.Model):
    is_protected = models.BooleanField(default=False)
    faravahar = models.BooleanField(default=False)
    nuclear = models.BooleanField(default=True)
    timeout = models.IntegerField(default=1)
    # is_time_session = models.BooleanField(default=False)
    # start_time_session = models.DateTimeField(null=True, blank=True)
    email = models.EmailField(max_length=50, default='')

    def __str__(self):
        return "Options"

urls.py:

from django.urls import path,include
from . import views

urlpatterns = [
    path('', views.Gateway.as_view(), name='index'), # former
    path('portal/', views.portal, name='portal'),
    path('logout/', views.EndSession.as_view(), name='logout'),
]

The LoginView does not use an attribute named context_object_name.

If you want additional data put into the context for that page, you would override the get_context_data method to add an object to the context.

1 Like

Thank you for pointing me in the right direction, Ken.

I located the LoginView doc on CCBV specific to get_context_data. There they define the basic default get_context_data method:

def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)
    current_site = get_current_site(self.request)
    context.update(
        {
            self.redirect_field_name: self.get_redirect_url(),
            "site": current_site,
            "site_name": current_site.name,
            **(self.extra_context or {}),
        }
    )
    return context

That’s the relevant strict default code block I need to work with. But that’s all there is. Hungry for more, I turned Django’s LoginView CBV doc. While there is a lot of useful and detailed information for LoginView, I couldn’t find the section specific to the get_context_data method for this CBV. You can see for yourself in that official Django doc, if you do a Ctrl + F for ‘get_context_data’, nothing comes up.

Where can I find more information about how to customize get_context_data? Perhaps viewing a project or web app with LoginView implemented with custom values might help.

Elsewhere on the web I encountered a recommended get_context_data code block as follows:

def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        # Add custom context data here
        context['custom_data'] = 'Custom value'
        return context

That more succinct than CCBV’s quoted originally.

It’s also worth pointing out that I realize that context when defined in a Django web app’s view is conventionally a Python dictionary. That much should be incredibly obvious to anyone with a little bit of experience with Python and Django and that goes without saying. Therefore, when specifying a context in general, it needs to be in one of two formats: context['key'] = 'value' or {"key":"value",}. I believe in my case the value I want to create is ‘controls’. So adding that to the most recent code snippet above, I need:

context['custom_data'] = 'controls'

What I struggle with now is what I should use instead of the ['custom_data'] placeholder. I believe this is the missing puzzle piece as I set out in my original post.

I agree that finding some of these details regarding the Django CBVs can be challenging at times. Sometimes you just need to know where to find it, or do some digging around. That’s why I always try to refer people to the CCBV and CBV diagrams pages along with the docs.

If you use the search box on the docs site for get_context_data, you’ll get a couple of page references that would get you to the right place eventually. The first two pages returned by the search, Generic display views | Django documentation | Django and Built-in class-based generic views | Django documentation | Django pretty much cover the topic. (Note, I’m saying this both for future readers of this thread, and as a general tip for the future. When it comes to the CBV methods, the search box is often a great help.)

More specifically, the context, when referring to the rendering process, is a dict that is passed to the rendering engine to provide data to be injected into a template. (See Templates | Django documentation | Django and Django shortcut functions | Django documentation | Django among other references.)

For clarity - the first format is used when you already have a dict named context, and you wish to add or update an existing key in that dict. The second format creates a new dict with the key/value pairs specified within the braces.

In your template, when you write something like {{ data_value }}, data_value is a reference to the key in the context dict. What is rendered within that template is the value from that dict key.

For example, if you have:
context['custom_data'] = 'controls'
as you wrote above,

then in your template, if you have:
<div>some text, {{ custom_data }}, some more text<div>

Then rendering that template with the context named context will generate:
<div>some text, controls, some more text<div>

1 Like

One more topic on this I’d like to expand upon:

The purpose of get_context_data is to provide the hook for building the context to be used by the rendering engine.

What LoginView defines as get_context_data is what LoginView needs to function. Other views have different needs, and so their implementations of get_context_data are going to differ.

This sample identifies the type of operation you might perform to augment the default. First, it calls the default method (super().get_context_data(...)) to get the initial dict, then adds a new key (custom_data). This allows the template being rendered within that view to use {{custom_data}} as a variable within the template.

Two different methods performing two different functions - working together.

My go-to is Google for searching in general but I will add using the Django project’s built-in search feature to my repetoire when I am in Django documentation ‘discovery mode’. Noted.

To your point, I subsequently first experimented with {"key":"value",} format which immediately broke the LoginView and my Form view functionality. My form fields disappeared. After a little playing around I then realized what you also mentioned about how {“key”:“value”,} would over-ride the context entirely, like it began to interfere with contexts declared and defined likely elsewhere in the vast expanse abstraction that is the CBV hierarchy that composes the LoginView. So I next resolved to add to (or update) the existing context dictionary.

To that end, I went back to the context['key'] = 'value' format and it worked. Here is my complete end product and working custom method:

def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)
    nuclear = AuthToggle.objects.filter(nuclear=True)
    faravahar = AuthToggle.objects.filter(faravahar=True)
        
    context['nuclear'] = nuclear
    context['faravahar'] = faravahar
                
    return context

Eureka! It works. I have achieved what I set out to accommplish.

It’s also worth mentioning that when I first started working with Django, when course instructors would define/update context dicionaries (as I have fashioned above), I used to get flustered and confused with dictionary key names and value names that looked the same just with the value around quotes. The rendundancy kind of trivialized context variables for me. One way of making it more clear would be to use this instead:

def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)      
    context['nuclear'] = AuthToggle.objects.filter(nuclear=True)
    context['faravahar'] = AuthToggle.objects.filter(faravahar=True)
    return context

Thanks @KenWhitesell for your patience and for sharing your insight.