REST serializer missing user (and Code for dark and light theme)

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 :white_check_mark:

<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:white_check_mark:

<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:
  1. I use the function getCookie(name) as described in [Django documentation] :white_check_mark:
    (https://docs.djangoproject.com/en/4.0/ref/csrf/ ) including const csrftoken = Cookies.get('csrftoken');

  2. The initialization of the correct mode for a known user :white_check_mark:

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");
            }
     });
}
  1. The function actually making the changes. :white_check_mark:
 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
   }

:exclamation: And number 4 with the possible problem :exclamation:
4. The function to toggle the theme with a button. It does not store. :interrobang:

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 :sweat_smile: 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 :question: (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:

  1. I want to start with the default mode as set in the browser
  2. I want to set the mode of the admin site equal to the mode in the main site
  3. 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 :grinning: 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)