'How can I display data from django model and save at the same time using class based views?

I am creating a quiz app using Django. The requirements are:

  1. I need to first display all the topics available for the quiz on the homepage.
  2. Clicking on a particular page should redirect us to a new page where we will see the questions.
  3. Each time one question should be displayed.
  4. The next button should load the next question and the user cant go back to the previous question.
  5. After attempting all the questions, the user should be taken to another page that displays the score.
  6. For each question being attempted, the process should be displayed like 5/10 question, 6/10 question.
  7. If a user logs out before completing the quiz, he/she must be displayed the same question once he logs in.

I am able to display the topics .

The problem is I am not able to display one question at a time and store the user's answer using the class based view. Also I am confused how will I display progress and store the user record if he/she logs out without completing the quiz and redirect them to the very next question till where they had attempted.

I am new to django and trying to develop this. Please HELP!

Sharing my code:

Models.py

from django.db import models
from django.contrib.auth.models import User
import uuid

class Topic(models.Model):
    t_id = models.UUIDField(
         primary_key = True,
         default = uuid.uuid4,
         editable = False)
    topic = models.CharField(max_length=200)
    time_required = models.IntegerField(help_text="Duration of Quizz in minutes")

    def __str__(self):
        return f"{self.topic}"


class Question(models.Model):
    q_id = models.UUIDField(
         primary_key = True,
         default = uuid.uuid4,
         editable = False)
    question = models.CharField(max_length=200)
    topic = models.ForeignKey(Topic, on_delete=models.CASCADE)

    def __str__(self):
        return self.question

class Answer(models.Model):
    a_id = models.UUIDField(
         primary_key = True,
         default = uuid.uuid4,
         editable = False)
    answer= models.CharField(max_length=100)
    is_correct=models.BooleanField(default=False)
    question=models.ForeignKey(Question, on_delete=models.CASCADE)
    

    def __str__(self):
        return f"{self.answer}"


class UserRecord(models.Model):
    User=models.ForeignKey(User, on_delete=models.CASCADE)
    question=models.CharField(max_length=100)
    answer_choosen=models.CharField(max_length=100)

    def __str__(self):
        return f"{self.User} | {self.question} | {self.answer_choosen}"

views.py

from django.views.generic import ListView
from django.contrib.auth.decorators import login_required
from .models import Topic, Question, Answer, UserRecord
from django.utils.decorators import method_decorator
from django.shortcuts import render,redirect


@method_decorator(login_required, name='dispatch')
class TopicView(ListView):
    model = Topic
    template_name = 'quiz/topic.html'
    context_object_name = 'topics'

    

def nextques(request,t_id):
    
    questions=Question.objects.filter(topic=t_id)
    total_questions=[]
    for question in questions:
        total_questions.append(str(question.q_id))
    user_answered=UserRecord.objects.filter(User=request.user)
    answered_list=[]
    for answered_question in user_answered:
        answered_list.append(str(answered_question.question))
    for question_id in total_questions:
        if question_id not in answered_list:
            questionset=Question.objects.filter(q_id=question_id)
            answerset=Answer.objects.filter(question=question_id)
            break
        else:
            continue
    context={
        "questions":questionset,
        "answers":answerset,
    }
    if request.method=="POST":
        user=request.user
        question_id=request.POST.get('hidden1')     
        question=request.POST.get('hidden')
        answer_id=request.POST.get(question)
        query=UserRecord(User=user,question=question_id,answer_choosen=answer_id)
        query.save()
    if len(user_answered)<len(total_questions):
        return render(request, "quiz/quizz.html", context=context)
    else:
        return render(request, "quiz/quizend.html")

base.html

{% load static %}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <!-- Bootstrap CSS -->

  <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">

  <link rel="stylesheet" type="text/css" href="{% static 'quiz/main.css' %}">

  {% block scripts %}
  {% endblock scripts %}

  <title>Quiz App | {% block title %}{% endblock title %}</title>
</head>

<body>
  <header class="site-header">
    <nav class="navbar navbar-expand-md navbar-dark bg-steel fixed-top">
      <div class="container">
        <a class="navbar-brand mr-4" href="#">Quiz App</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarToggle"
          aria-controls="navbarToggle" aria-expanded="false" aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarToggle">
          <div class="navbar-nav mr-auto">
            <a class="nav-item nav-link" href="{% url 'topic' %}">Home</a>
          </div>
          <!-- Navbar Right Side -->
          <div class="collapse navbar-collapse justify-content-end" id="navbarCollapse">
            <ul class="navbar-nav">
              <li class="nav-item">
                <a class="nav-link" href="#">Profile</a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="{% url 'logout' %}">Logout</a>
              </li>
            </ul>
          </div>
          
        </div>
      </div>
    </nav>
  </header>
  <div class="row">
    <div class="col-md-8">
      {% if messages %}
      {% for message in messages %}
      <div class="alert alert-{{ message.tags }}">
        {{ message }}
      </div>
      {% endfor %}
      {% endif %}
      {% block content %}{% endblock %}
    </div>
  </div>
  <footer class="text-center text-white fixed-bottom" style="background-color: #f1f1f1;">
    <div class="text-center text-dark p-3" style="background-color: rgba(0, 0, 0, 0.2);">
      © 2022:QuizApp
    </div>
  </footer>
  <script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js" integrity="sha384-7+zCNj/IqJ95wo16oMtfsKbZ9ccEh31eOz1HGyDuCQ6wgnyJNSYdrPa03rtR1zdB" crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-QJHtvGhmr9XOIpI6YVutG+2QOK9T+ZnN4kzFN1RtK3zEFEIsxhlmWl5/YESvpZ13" crossorigin="anonymous"></script>
</body>

topic.html

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

{% block title %}
Quiz
{% endblock %}

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

{% block content %}

<div class="modal fade" id="quizModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1"
aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog">
    <div class="modal-content">
        <div class="modal-header">
            <h5 class="modal-title" id="staticBackdropLabel">Start Quiz ?</h5>
            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
        </div>
        <div class="modal-body" id="modalbody">
        </div>
        <div class="modal-footer">
            <button type="button" class="btn btn-danger" data-bs-dismiss="modal">No</button>
            <button id="start-button" type="button" class="btn btn-success">Yes, Start</button>
        </div>
    </div>
</div>
</div>

    <div class="container mr-4">
        <div class="h1">Topics</div>
        <hr />

        {% for obj in topics %}
            <button class="btn btn-secondary modal-button" 
            data-bs-pk="{{obj.t_id}}"
            data-bs-topic="{{obj.topic}}" 
             data-bs-time="{{obj.time_required}}"
             data-bs-toggle="modal"
             data-bs-target="#quizModal">
                {{obj.topic}}
            </button><br /><br />
        {% endfor %}
    </div>
{% endblock %}

topic.js

const modalBtns = [...document.getElementsByClassName('modal-button')]
const modalBody = document.getElementById('modalbody')
const startBtn = document.getElementById('start-button')

const url = window.location.href

modalBtns.forEach(modalBtn=> modalBtn.addEventListener('click', ()=>{
    const id = modalBtn.getAttribute('data-bs-pk')
    const topic = modalBtn.getAttribute('data-bs-topic')
    const time = modalBtn.getAttribute('data-bs-time')

    modalBody.innerHTML = `
        <div class="h5 mb-3">Are you sure you want to begin "<b>${topic}</b>"?</div>
        <div class="text-muted">
                Time: <b>${time} mins</b>
        </div>
    `
    startBtn.addEventListener('click', ()=>{
        window.location.href = url + id
    })
}))

quizend.html

{% extends "quiz/base.html" %}

{% block content %}
    <h1>This is QuizEnd</h1>
{% endblock content %}

quiz.html

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

{% block title %}

{% endblock title %}

{% block scripts %}
    <script>
        const url=window.location.href
    </script>
{% endblock scripts %}
{% block content %}
    <div class="container">
        <form id="quiz-form" action="" method="POST" class="mt-3 mb-3">
            {% csrf_token %}
            <div id="quiz-box">
                {% for question in questions %}
                        {{question}}<br />
                        {% for answer in answers %}
                            <div class="form-check">
                                <input type="hidden" name="hidden1" value="{{question.q_id}}">
                                <input type="hidden" name="hidden" value="{{question}}">
                                <input type="radio" class="ans" id="{{question}}-{{answer}}" name="{{question}}" value="{{answer.a_id}}" required>
                                <label for="{{question}}">{{answer}}</label>
                            </div>
                        {% endfor %}<br />
                    {% endfor %}
                <br />
            </div> 
            <button type="submit" class="btn btn-primary mt-3">Next</button>
        </form>
    </div>
{% endblock content %}

quiz/urls.py

from django.urls import path
from .import views
from .views import TopicView #QuestionListView,


urlpatterns = [
    path('', TopicView.as_view(), name='topic'),
    path('<t_id>', views.nextques, name='quiz'),
]

urls.py

"""quiz_proj URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/4.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from users import views as user_views
from django.contrib.auth import views as auth_views
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('quiz.urls')),
    path('register/', user_views.register, name= 'register'),
    path('login/', auth_views.LoginView.as_view(template_name='users/login.html'), name = 'login'),
    path('logout/', auth_views.LogoutView.as_view(template_name='users/logout.html'), name = 'logout'),
]


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source