related to: Dark and light theme implementation
@PerOve123 here is the code you might need… and hopefully the answer for the last part if you need that too. I added some extra comments for anybody who wants to implement this.
All works except storing the changed mode using an ajax call. The problem I can identify is that I don’t pass the user in the call…and dont know how to solve that. (and some small things)
For the dark and light mode I created the following:
HTML main template:
In the head
<input type="hidden" id="update_mode" url_ref="{% url 'update_mode' %}" />
<input type="hidden" id="get_mode" url_ref="{% url 'get_mode' %}" />
<link id="mode__light" rel="stylesheet" type="text/css" href="{% static 'css/light.css' %}" disabled="disabled"/>
<link id="mode__dark" rel="stylesheet" type="text/css" href="{% static 'css/dark.css' %}" disabled="disabled"/>
There are many solutions to implementing the light and dark theme (css options) and I chose to use two stylesheets. I need both stylesheets disabled when creating the page to prevent multiple changes at load. In the body tag I added “onload=“init_mode()” After loading the page this will activate the correct mode.
And the switch mode button
<button id="mode-button" class="mode-switch" onclick="switch_mode()"> <i id="mode__icon" class="bi bi-moon"></i></button>
Changing the icon and the active css file (see javascript)
I have 4 javascript functions including AJAX:
-
I use the function getCookie(name) as described in [Django documentation]
(https://docs.djangoproject.com/en/4.0/ref/csrf/ ) including const csrftoken = Cookies.get('csrftoken');
-
The initialization of the correct mode for a known user
function init_mode() {
var get_url = $("#get_mode").attr("url_ref");
$.ajax({
url: get_url,
type: "GET",
beforeSend: function (xhr) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
},
success: function (data) {
setMode(data.mode);
},
error: function (error) {
console.log(error);
setMode("L");
}
});
}
- The function actually making the changes.
function setMode(mode){
var dark__sheet_link = document.getElementById("mode__dark"),
light__sheet_link = document.getElementById("mode__light"),
mode__icon = document.getElementById("mode__icon");
if(mode == 'D'){
dark__sheet_link.removeAttribute("disabled");
light__sheet_link.disabled = "disabled";
mode__icon.classList.remove('bi-moon');
mode__icon.classList.add('bi-sun');
}
else{
//The opposite of the above
}
And number 4 with the possible problem
4. The function to toggle the theme with a button. It does not store.
function switch_mode() {
var post_url = $("#update_mode").attr("url_ref");
var dark__sheet_link = document.getElementById("mode__dark"),
light__sheet_link = document.getElementById("mode__light");
if( dark__sheet_link.disabled == true ) {
$.ajax({
url: post_url ,
type: "PUT",
data: {"mode":"D"},
beforeSend: function (xhr) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
},
success: function (data) {
setMode("D");
},
error: function (error) {
//Cannot store mode but change it anyway
setMode("D");
console.log(error);
}
});
}
else {
$.ajax({
//same code as above but now for L
});
}
}
The model
class VisualConfiguration(models.Model):
"""The model for the theme (color) and the mode (dark or light) of the website"""
can_delete = False
MODE = [
('D', _('dark')),
('L', _('light')),
]
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
mode = models.CharField(_('mode'), max_length=1, default='L', choices=MODE)
#Future color theme
#theme = models.???(_('theme')) #probably links to multiple possible color themes
class Meta:
constraints = [
models.UniqueConstraint(fields=['user'], name='One Entry Per User')
]
def __str__(self):
return "VisualConfiguration"
serializers.py
from rest_framework import serializers
from .models import VisualConfiguration
class VisualModeSerializer(serializers.ModelSerializer):
"""Serializer for the dark or light mode of the website"""
class Meta:
model = VisualConfiguration
fields = ('user', 'mode')
Context_processors.py
from .models import VisualConfiguration
def mode(request):
if request.user.is_authenticated:
_mode = VisualConfiguration.objects.filter(user=request.user).last()
else:
_mode = None
return {
'mode': _mode,
}
I forgot why I created this context processor so you might not need this
Views.py
from rest_framework import status
from rest_framework.response import Response
from rest_framework.decorators import api_view
from .serializers import VisualModeSerializer
@api_view(['GET'])
def get_mode(request):
if request.method == 'GET':
user = request.user
if user.is_authenticated:
visual_mode, created = VisualConfiguration.objects.get_or_create(user=user) # default light
serializer = VisualModeSerializer(visual_mode, many=False)
logger.info('get_mode[ serializer data: '+serializer.data.__str__() + "]")
return Response(serializer.data)
else:
logger.error('get_mode with wrong method: ' + request.method.__str__())
@api_view(['PUT'])
def update_mode(request):
logger.info('test' + request.data.__str__())
if request.method == 'PUT':
user = request.user
if user.is_authenticated:
serializer = VisualModeSerializer(data=request.data)
logger.info('PUT: Update_mode for authenticated user: ' + user.id.__str__() + '; --> data: ' + request.data.__str__())
if serializer.is_valid(): #raise_exception=True
serializer.save()
logger.info('PUT: Response(serializer.data): ' + Response(serializer.data).__str__())
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
logger.error('Invalid serializer: ' + serializer.errors.__str__())
return Response(serializer.data, status=status.HTTP_400_BAD_REQUEST)
else:
logger.info('User not authenticated')
else:
logger.info('Update_mode wrong method: '+ request.method.__str__())
I get the following log in the terminal:
PUT: Update_mode for authenticated user: 1; --> data: <QueryDict: {'mode': ['D']}>
Invalid serializer: {'user': [ErrorDetail(string='Required field', code='required')]}
Bad Request: /account/update_mode/
[12/Mar/2022 16:02:08] "PUT /account/update_mode/ HTTP/1.1" 400 12
I realize I don’t send the user with it but cant find out how to get that. I tried to add the user in the update_mode view but either that is not possible or I do it wrong. Or I need to create a save function that uses the current user but that did not work either.
So how can I add the user so I can store it (I hope that is the only problem.)
For anybody who wants to use this code.... changes / problems I am still facing:
I have some problems I still have to research:
- I want to start with the default mode as set in the browser
- I want to set the mode of the admin site equal to the mode in the main site
- The code:
<input type="hidden" id="update_mode" url_ref="{% url 'update_mode' %}" />
in the head is incorrect html in many ways but it is the only way I could pass the two url’s for the update and the getmode So I hope to find a better way ….some day and I will add it here if I remember
The things I also need but probably can solve:
- You can store the mode in cookies or local storage when ajax returns an error because you cant store it in the database (see first part of this for a solution)