'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:
- I need to first display all the topics available for the quiz on the homepage.
- Clicking on a particular page should redirect us to a new page where we will see the questions.
- Each time one question should be displayed.
- The next button should load the next question and the user cant go back to the previous question.
- After attempting all the questions, the user should be taken to another page that displays the score.
- For each question being attempted, the process should be displayed like 5/10 question, 6/10 question.
- 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 | 
|---|
