22/:566 Error: SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON 

I am new to Django and am developing a flashcards app that I have included a text to speech functionality however every time I select the text to speech button it does not work. I have been able to debug most of the errors but not this one. The error I get from the network is as above the error I get from the terminal is below

Not Found: /deck/rehearse/22/text_to_speech/5/
[08/Jun/2024 14:33:02] “POST /deck/rehearse/22/text_to_speech/5/ HTTP/1.1” 404 5458

views.py

from django.views.decorators.http import require_POST
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
import random
from io import BytesIO

def text_to_speech(request, deck_id, card_id=None):
    deck = get_object_or_404(Deck, id=deck_id)
    cards = deck.card_set.all()

    if not cards:
        return JsonResponse({'error': 'No cards found for this deck.'}, status=404)

    if card_id:
        card = get_object_or_404(Card, id=card_id)
    else:
        card = random.choice(cards)

    engine = pyttsx3.init()

    # Generate audio data for the question
    question_audio_data = BytesIO()
    engine.save_to_wav(card.question, question_audio_data)
    question_audio_base64 = base64.b64encode(question_audio_data.getvalue()).decode('utf-8')

    # Generate audio data for the answer
    answer_audio_data = BytesIO()
    engine.save_to_wav(card.answer, answer_audio_data)
    answer_audio_base64 = base64.b64encode(answer_audio_data.getvalue()).decode('utf-8')

    engine.stop()

    audio_data = {
        'question_audio': question_audio_base64,
        'answer_audio': answer_audio_base64,
    }

    return JsonResponse(audio_data)


def home(request):
    if request.user.is_authenticated:
        return redirect('all_decks')
    else:
        if request.method == 'POST':
            form = AuthenticationForm(request, data=request.POST)
            if form.is_valid():
                user = authenticate(username=form.cleaned_data['username'], password=form.cleaned_data['password'])
                if user is not None:
                    login(request, user)
                    return redirect('home')
        else:
            form = AuthenticationForm()
        return render(request, 'login.html', {'form': form})


def show_all_cards(request):
    c_list = Card.objects.all()
    context = {'list_of_cards': c_list}
    return render(request, 'show_all_cards.html', context)


def delete_card(request, card_id):
    card_to_delete = Card.objects.get(id=card_id)
    card_to_delete.delete()
    return redirect('show_all_cards')


def show_all_decks(request):
    d_list = Deck.objects.all()
    context = {'list_of_decks': d_list}
    return render(request, 'all_decks.html', context)


def delete_deck(request, deck_id):
    deck_to_delete = Deck.objects.get(id=deck_id)
    deck_to_delete.delete()
    return redirect('all_decks')


class DeckCreate(CreateView):
    form_class = DeckForm
    model = Deck

    def form_valid(self, form):
        f = form.save(commit=False)
        f.creator = self.request.user
        f.save()
        return super().form_valid(form)


def generate_audio(text, output_path):
    engine = pyttsx3.init()
    engine.save_to_file(text, output_path)
    engine.runAndWait()

html

{% extends "base.html" %}
{% load static %}

{% block content %}
<div class="container">
    <div class="row">
        {% for card in list_of_cards %}
        <div class="rehearse_card" id="card-{{ card.id }}">
            <div class="card text-center" style="width: 30rem;">
                <img src="{% static 'images/FlashCardImage.svg' %}" class="card-img-top" alt="...">
                <div class="card-body">
                    <div class="card-front">
                        <h4 class="card-title-question">Q: {{ card.question }}</h4>
                        <button class="btn btn-primary btn-sm" onclick="flipCard('{{ card.id }}')">Flip Card</button>
                        <button class="btn btn-secondary btn-sm tts-button" data-card-id="{{ card.id }}"
                            data-question-audio="{{ card.question_audio.url }}">Text to Speech (Question)</button>
                    </div>
                    <div class="card-back" style="display: none;">
                        <h5 class="card-text-answer">A: {{ card.answer }}</h5>
                        <button class="btn btn-secondary btn-sm tts-button" data-card-id="{{ card.id }}"
                            data-answer-audio="{{ card.answer_audio.url }}">Text to Speech (Answer)</button>
                    </div>
                </div>
                <div class="card-footer">
                    <small class="text-muted">{{ card.id }} {{ card.deck.title }} · {{ card.deck.subject }}</small>
                </div>
            </div>
        </div>
        {% endfor %}
    </div>
    </div>
    </div>
</div>

<script>
    function flipCard(cardId) {
        const cardFront = document.querySelector(`#card-${cardId} .card-front`);
        const cardBack = document.querySelector(`#card-${cardId} .card-back`);

        if (cardFront && cardBack) {
            cardFront.style.display = cardFront.style.display === 'none' ? 'block' : 'none';
            cardBack.style.display = cardBack.style.display === 'none' ? 'block' : 'none';
        } else {
            console.error('Card elements not found.');
        }
    }

    function toggleTextToSpeech(button) {
        const cardId = button.dataset.cardId;
        const deckId = window.location.pathname.split('/')[3]; // Assuming the deck_id is the 4th part of the URL path
        const questionAudioUrl = button.dataset.questionAudio;
        const answerAudioUrl = button.dataset.answerAudio;

        const url = `/deck/rehearse/${deckId}/text_to_speech/${cardId}/`;

        fetch(url, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRFToken': '{{ csrf_token }}',
            },
            body: JSON.stringify({}),
        })
        .then(response => response.json())
        .then(data => {
            const questionAudio = new Audio(`data:audio/mpeg;base64,${data.question_audio}`);
            const answerAudio = new Audio(`data:audio/mpeg;base64,${data.answer_audio}`);

            questionAudio.play();
            questionAudio.addEventListener('ended', () => {
                answerAudio.play();
            });
        })
        .catch(error => {
            console.error('Error:', error);
        });
    }

    const ttsButtons = document.querySelectorAll('.tts-button');
    ttsButtons.forEach(button => {
        button.addEventListener('click', () => toggleTextToSpeech(button));
    });
</script>
{% endblock %}

java-script

var visibleCard = 0;
var cardSide = "QudestionSide";
var audioPlayer = null;

function showCard() {
    $(".rehearse_card").hide();
    $(".rehearse_card:eq(" + visibleCard + ")").show();
    $(".rehearse_card:eq(" + visibleCard + ")").find(".card-title-question").show();
    $(".rehearse_card:eq(" + visdibleCard + ")").find(".card-answer-audio").show();
}

function flipCard() {
    var currentCard = $(".rehearse_card:eq(" + visibleCard + ")");
    if (cardSide == "QuestionSide") {
        cardSide = "AnswerSide";
        currentCard.find(".card-title-question").hide();
        currentCard.find(".card-text-answer").show();
    } else {
        currentCard.find(".card-title-question").show();
        currentCard.find(".card-text-answer").hide();
        cardSdide = "QuestionSide";
    }
}

function showNextCard() {
    if (visibleCard == $(".rehearse_card").length - 1) {
        visibleCard = 0;
    } else {
        visibleCard++;
    }
    showCard();
}

function showPrevCard() {
    if (visibleCard == 0) {
        visibleCard = $(".rehearse_card").length - 1;
    } else {
        visibleCard--;
    }
    showCard();
}

function toggleTextToSpeech(button, questionAudioUrl, answerAudioUrl) {
    if (audioPlayer === null) {
        audioPlayer = new Audio(questionAudioUrl);
        audioPlayer.play();
        button.textContent = "Stop Text to Speech";
        audioPlayer.addEventListener('ended', function() {
            audioPlayer.src = answerAudioUrl;
            audioPlayer.play();
        });
    } else {
        audioPlayer.pause();
        audioPlayer.currentTime = 0;
        audioPlayer = null;
        button.textContent = "Text to Speech";
    }
}

$(document).ready(function() {
    showCard();

    $(".tts-button").click(function() {
        var questionAudioUrl = $(this).data("question-audio");
        var answerAudioUrl = $(this).data("answer-audio");
        toggleTextToSpeech(this, questionAudioUrl, answerAudioUrl);
    });
});

You can understand the problem easily from the error logging. Of course the part of the client code that tries to understand a JSON response will see an “unexpected token” right at the start, if the response starts with <!DOCTYPE. Because that isn’t valid JSON. But it is exactly what you’d expect to see at the start of HTML. In the terminal, meanwhile, you can clearly see that the request received a 404 response.

get_object_or_404 works by raising an exception when the object is not found, and then that exception is handled somewhere else - by default, producing an HTTP response. Your client code expects to receive JSON in every circumstance, but it receives HTTP in almost every case that something goes wrong in text_to_speech: if there is no deck, or if the cards don’t include the card that was specified by ID. It will only get a JSON error response when the deck has no cards in it (and you return JsonResponse(...) explicitly).

Of course, I have no idea why your server app can’t find card ID 5 within deck ID 22. But presumably what you really want to do is make sure you get a JSON-formatted error in this case as well. To find out how to do that, I tried putting get_object_or_404 json response into a search engine. We find out pretty directly that this isn’t built in, but get_object_or_404 is a very simple wrapper, so we should just re-implement the logic ourselves:

This is something that I already know however, what I need to know is what exactly is causing the error and how to fix it?

Did you try reading the link and following the advice there?

(Also, it’s a good idea to try to write questions in a way that reflects what you actually know. It’s ordinary for people to throw up their hands at an error and post anywhere they think they can get help, so the default assumption is that the OP doesn’t know anything about what it means.)