exams.js File Not Showing In HTML File In Django

I am trying to follow a tutorial to build a quiz app with Django, but have encountered some issues.

When I attempt to access the following url the page only displays the title of the exam and the “Save” button: http://127.0.0.1:8000/exams/2/

I have attached an image showing what I am seeing.

Inside django_project → Exams → urls.py I have the following:

urlpatterns = [
    path('exams/', ExamListView.as_view(), name='main-view'),
    path('exams/<pk>/', exam_view, name='exam-view'),
    path('exams/<pk>/data/', exam_data_view, name='exam-data-view'),
    path('exams/<pk>/save/', save_exam_view, name='save-view'),
]

Inside django_project → Exams → views.py I have the following:

def exam_view(request, pk):
    exam = Exam.objects.get(pk=pk)

    # Assign the exam value to 'object', and then pass it to the
    # exam.html template.
    return render(request, 'Exams/exam.html', {'obj': exam})

Inside django_project → Exams → templates → Exams → exam.html I have the following:

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

{% block scripts %}
    <script src="{% static 'Exams/exam.js' %}" defer></script>
{% endblock scripts %}

{% block title %} 
{{ obj.exam_name }} 
{% endblock title %}

{% block content %} 
{{ obj.exam_name }}

<form id="exam-form" class="mt-3 mb-3">
    {% csrf_token %}
    <div id="exam-box"></div>
    <button type="submit" class="btn btn-primary mt-3">Save</button>
</form>
{% endblock content %}

Inside django_project → Exams → static → Exams → exam.js I have the following:

const url = window.location.href

/* We are wrapping 'exam-box' that was defined inside exam.html as an
   id, and we are assigning it to the 'examBox' element that is being 
   declared here.*/
const examBox = document.getElementById('exam-box')

$.ajax({
    type: 'GET',
    url: `${url}data`,
    success: function(response)
    {
        console.log(response)
        const data = response.data
        /*Each element has a key and a value. The key represents
          the question and the value represents the answer.
          
          Here we are grabbing each individual element*/
        data.forEach(el => {
            for (const [exam_question, exam_answers] of Object.entries(el))
            {
                examBox.innerHTML += `
                    <hr>
                    <div class="mb-2">
                        <b>${exam_question}</b>
                    </div>
                `
                
                /*19:19 Part 3*/
                exam_answers.forEach(answer=> {
                    examBox.innerHTML += `
                        <div>
                            <input type="radio" class="ans" id="${exam_question}-${answer}"
                                   name="${exam_question} value="${answer}">
                                  <label for="${exam_question}">${answer}</label>
                        </div>
                    `
                })
            }
        });
    },
    error: function(error)
    {
        console.log(error)
    }
})
/****************************/
const examForm = document.getElementById('exam-form')
const csrf = document.getElementsByName('csrfmiddlewaretoken')

const sendData = () => {
    const elements = [...document.getElementsByClassName('ans')]
    const data = {}
    /*We have put our csrf middleware token into the data dictionary.*/
    data['csrfmiddlewaretoken'] = csrf[0].value

    elements.forEach(element=> {
        if(element.checked) 
        {
            data[element.name] = element.value
        }else{
            if(!data[element.name])
            {
                data[element.name] = null
            }
        }
    })

    $.ajax({
        type: 'POST',
        url: `${url}save/`,
        data: data,
        success: function(response){
            console.log(response)
        },
        error: function(error){
            console.log(error)
        }
    })
}

examForm.addEventListener('submit', event=>{
    event.preventDefault()

    sendData()
})

Hello @gurbanoglu
The file “exams.js” IS being found and being executed inside the HTML file.
How do I know? The Chrome console is printing “{data: Array(0), time: 5}”, which is the AJAX request where you’re trying to (I assume) fetching the questions and answers of the exam. Up to this point everything seems fine to me.
What you should do? You need to review your code were you’re fetching the answers and questions of the exam, because there is the real issue. I see “time: 5” inside the Chrome console, which indicates that the response has been fetched 5 times, so the Django server is sending back the response, but with no data inside. If you don’t see anything wrong with this, please copy and paste your code here, so maybe we can help you out with this.
Greetins!

Isn’t the following the code inside “exams.js” where we are fetching the answers and questions of the exam?

const url = window.location.href

/* We are wrapping 'exam-box' that was defined inside exam.html as an
   id, and we are assigning it to the 'examBox' element that is being 
   declared here.*/
const examBox = document.getElementById('exam-box')

$.ajax({
    type: 'GET',
    url: `${url}data`,
    success: function(response)
    {
        console.log(response)
        const data = response.data
        /*Each element has a key and a value. The key represents
          the question and the value represents the answer.
          
          Here we are grabbing each individual element*/
        data.forEach(el => {
            for (const [exam_question, exam_answers] of Object.entries(el))
            {
                examBox.innerHTML += `
                    <hr>
                    <div class="mb-2">
                        <b>${exam_question}</b>
                    </div>
                `
                
                /*19:19 Part 3*/
                exam_answers.forEach(answer=> {
                    examBox.innerHTML += `
                        <div>
                            <input type="radio" class="ans" id="${exam_question}-${answer}"
                                   name="${exam_question} value="${answer}">
                                  <label for="${exam_question}">${answer}</label>
                        </div>
                    `
                })
            }
        });
    },
    error: function(error)
    {
        console.log(error)
    }
})
/****************************/
const examForm = document.getElementById('exam-form')
const csrf = document.getElementsByName('csrfmiddlewaretoken')

const sendData = () => {
    const elements = [...document.getElementsByClassName('ans')]
    const data = {}
    /*We have put our csrf middleware token into the data dictionary.*/
    data['csrfmiddlewaretoken'] = csrf[0].value

    elements.forEach(element=> {
        if(element.checked) 
        {
            data[element.name] = element.value
        }else{
            if(!data[element.name])
            {
                data[element.name] = null
            }
        }
    })

    $.ajax({
        type: 'POST',
        url: `${url}save/`,
        data: data,
        success: function(response){
            console.log(response)
        },
        error: function(error){
            console.log(error)
        }
    })
}

examForm.addEventListener('submit', event=>{
    event.preventDefault()

    sendData()
})

Yes, within that JS file you’re fetching the data from your server.
I meant you need to review your code inside Django server, on the url

exams/<pk>/data/

or the function inside your views called

exam_data_view

Or you can paste here your view file here, so we can take a look at it.
That “exam_data_view” funcion is the one who’s not returning any data (neither questions neither answers), because as I said, the JS fetch you’re executing does get called and the Django server does response, but with no data in it.

Thank you for explaining. The following file is the views file inside the “Exams” application:

from django.shortcuts import render
from .models import Exam
from django.views.generic import ListView
from django.http import JsonResponse
from ExamQuestions.models import ExamQuestion, ExamAnswer
from ExamResults.models import ExamResult

# Create your views here.


class ExamListView(ListView):
    model = Exam
    template_name = 'Exams/main.html'


def exam_view(request, pk):
    exam = Exam.objects.get(pk=pk)

    # Assign the exam value to 'object', and then pass it to the
    # exam.html template.
    return render(request, 'Exams/exam.html', {'obj': exam})


def exam_data_view(request, pk):
    exam = Exam.objects.get(pk=pk)
    questions = []

    for question in exam.get_exam_questions():
        answers = []

        for answer in question.get_exam_answers():
            answers.append(answer.text)

        questions.append({str(question): answers})

    return JsonResponse({
        'data': questions,
        'time': exam.time_limit,
    })


# 'pk' always stands for the primary key. The primary key represents
# the exam that is currently being accepted as an argument.


def save_exam_view(request, pk):
    if request.is_ajax():
        questions = []
        data = request.POST
        data_ = dict(data.lists())
        
        data_.pop('csrfmiddlewaretoken')
        
        # The keys are the questions.
        for k in data_.keys():
            print('key:', k)
            question = ExamQuestion.objects.get(exam_text=k)
            questions.append(question)

        print(questions)

        user = request.user
        exam = Exam.objects.get(pk=pk)

        score = 0
        multiplier = 100 / exam.question_count
        results = []
        correct_answer = None

        # Nested for loop where we loop through each question, and then within
        # the for loop we loop through the possible answers for that particular question.
        for q in questions:
            a_selected = request.POST.get(q.text)
            
            if a_selected != "":
                question_answers = ExamAnswer.objects.filter(question=q)

                for a in question_answers:
                    if a_selected == a.text:
                        # If the question was answered correctly, increase the score.
                        if a.correct:
                            score += 1
                            correct_answer = a.text
                    else:
                        if a.correct:
                            correct_answer = a.text
                results.append({str(q): {'correct_answer': correct_answer, 'answered': a_selected}})
            else:
                results.append({str(q): 'not answered'})

        score_ = score * multiplier
        ExamResult.objects.create(exam=exam, user=user, score=score_)

        if score_ >= exam.score_required_to_pass:
            return JsonResponse({'passed': True, 'score': score_, 'results': results})
        else:
            return JsonResponse({'passed': False, 'score': score_, 'results': results})

Ok, I see nothing wrong with your code.
Can you post also your Models code please?
Do you have already data in your DB?

The following is the source code for the Exams → models.py file:

from django.db import models
import random

# Create your models here.

DIFF_CHOICES = (
    ('easy', 'easy'),
    ('medium', 'medium'),
    ('challenging', 'challenging'),
)


class Exam(models.Model):
    exam_name = models.CharField(max_length=120)
    material = models.CharField(max_length=120)
    question_count = models.IntegerField()
    time_limit = models.IntegerField(
        help_text="duration of the quiz in minutes")
    score_required_to_pass = models.IntegerField(
        help_text="required score in %")
    difficulty = models.CharField(max_length=11, choices=DIFF_CHOICES)

    def __str__(self):
        return f"{self.exam_name}-{self.material}"

    def get_exam_questions(self):
        questions = list(self.examquestion_set.all())
        # random.shuffle(questions) will re-order the questions for
        # each exam, so that they don't appear in the same order every
        # time.
        random.shuffle(questions)
        # [:self.question_count] ensures that the amount of questions
        # we get will be limited to the number of question there are for
        # an exam.
        return questions[:self.question_count]

    class Meta:
        verbose_name_plural = 'Exams'

I do have two exams already created and have executed “python manage.py makemigrations” as well as “python manage.py migrate” if that is what you mean.

There are two other applications that go with this project called “ExamQuestions” and “ExamResults”.

Ok, thanks.

I do have two exams already created and have executed “python manage.py makemigrations” as well as “python manage.py migrate” if that is what you mean.

Nop, I meant that there are already some Exams, questions and answers saved in your DB. The command “makemigrations” just kind of create the instructions the DB will have to take to modify the large, type, etc of the fields and tables within your DB. And the command “migrate” just applies those changes in your DB.

There are two other applications that go with this project called “ExamQuestions” and “ExamResults”.

Could you also paste here the models of those Apps please?

Another question: what do you want to achieve with this line?

        questions = list(self.examquestion_set.all())

What is self.examquestion_set.all()? Because I don’t see that property or field inside the “Exam” model.

Maybe that’s where the issue is.

Yes, I already have two “Exam” objects saved in the database. The following is an image showing what I mean:

This can be seen in the python shell as well.
examsShell

There are some questions that can be see inside the python shell as well. They are objects of the “ExamQuestion” class.

The following file is for ExamQuestions → models.py:

from django.db import models
from Exams.models import Exam

# Create your models here.


class ExamQuestion(models.Model):
    exam_text = models.CharField(max_length=220)
    exam = models.ForeignKey(Exam, on_delete=models.CASCADE)
    created = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return str(self.exam_text)

    def get_exam_answers(self):
        return self.examanswer_set.all()


class ExamAnswer(models.Model):
    exam_text = models.CharField(max_length=240)
    correct = models.BooleanField(default=False)
    question = models.ForeignKey(ExamQuestion, on_delete=models.CASCADE)
    created = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f"question: {self.question.exam_text}, answer: {self.exam_text}, correct: {self.correct}"

The following is the file for ExamResults → models.py:

from django.db import models

# Create your models here.

from django.db import models
from Exams.models import Exam
from django.contrib.auth.models import User

class ExamResult(models.Model):
    exam = models.ForeignKey(Exam, on_delete=models.CASCADE)
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    exam_score = models.FloatField()

    def __str__(self):
        return str(self.pk)

Regarding the following line: questions = list(self.examquestion_set.all())

This is inside the Exams → models.py file.

def get_exam_questions(self):
    "examquestion" refers to the model name inside the ExamQuestions -> models.py file.
    However, as you can the actual model name inside ExamQuestions -> models.py was "ExamQuestion".
    questions = list(self.examquestion_set.all())

    return questions[:self.question_count]

After seeing my latest response, please let me know what you think the problem could be.
Thanks!