Build a Complete Quiz App using VueJS and Django

Let’s learn how to build an multi topic quiz app using Vue JS in the front-end and Django in the back-end.

I have already build and deployed it. So you can try it live.

https://onlinetool.in/quiz/python

Our Quiz App

Overview of the working of our application

When the quiz app is loaded. our Vue Js application requests the server to send all questions of the quiz topic ‘python’

Note: we only get questions and not answers.

The server gets all the questions of the particular topic, converts it into JSON format and sends it to our Vue Application.

When we click on any option of the question, request is send to the server to get the answer. The server gets the answer of the particular question and sends it. Our Vue application compares the answer to the picked option and then shows the appropriate response.

Back-end logic & Code

Model

There are 3 models. Question, Choice and Answer.

A Question may have 1 or more Choices (OneToMany Relation)

A Question will have only one Answer (OneToOne Relation)

from django.db import models
# Create your models here.

class Question(models.Model):
    category = models.CharField("Category",max_length=100,blank=False, null=True)
    question = models.CharField(max_length=100000)
    description = models.TextField(blank=True, null=True)
    level = models.IntegerField(blank=True, null=True)
    
    def __str__(self):
        return str(self.category) + ' - ' + self.question 

class Choice(models.Model):
    question = models.ForeignKey("Question", related_name="choices")
    choice = models.CharField("Choice", max_length=100000)
    position = models.IntegerField("position",blank=False)
    def __str__(self):
        return self.choice
    class Meta:
        unique_together = [
            # no duplicated choice per question
            ("question", "choice"), 
            # no duplicated position per question
            ("question", "position") 
        ]
        ordering = ("position",)

class Answer(models.Model):
    question = models.OneToOneField("Question", related_name="answers")
    answer = models.IntegerField(blank=False)
    def __str__(self):
        return str(self.answer)

View

This is where we get the Questions and Choices of a particular topic and send it to our front-end application (VueJS).

I have written a separate article about understanding the JSON construction in this code here.

Note: In Django View is more like Controller, if you compare with other MVC frameworks.

def vueData(request):

	data = json.loads(request.body.decode('utf-8')) #quiz topic from frontend

	myList = list(Question.objects.filter(category=data['category']))
	
	shuffle(myList)

	data = []

	for questionObj in myList:
		item = {"id":questionObj.id, "question": questionObj.question, "description":questionObj.description, "category":questionObj.category, "choices":[]}

		for choiceObj in questionObj.choices.all():
			item["choices"].append({"position":choiceObj.position, "choice":choiceObj.choice})
		data.append(item)

	return JsonResponse(data, safe=False)

Front-end logic & Code

data

result will contain the JSON data received from back-end.

step will be used as index to access the next question object from the result.

ans will contain the correct answer of the question from back-end.

picked will contain the selected option value.

disabledDivToggle will be used to disable selecting options, if correct option is selected.

nextQuestionDisabledToggle will be used to disable next button if no more questions are left.

data: {
    results: [],
    step: 0,
    score: 0,
    disabledDivToggle: false,
    nextQuestionDisabledToggle: false,
    picked: '',
    ans: '',
    msg: '',
    correctBool: false,
    wrongBool: false,
},

created Hook

We make a post request using Axios to the server sending the category as python to get us all Python questions along with choices.

We store the JSON response from the backend as soon as Vue js app is created. using created() lifecycle method which is executed at the creation of the application.

created() {
    axios.post(url, {
        "category": "python",

    }).then(response => {
        this.results = response.data;
    })
},

Template

I think you can understand the template code yourself 🙂

<div class="main-box">
    <div v-if="results.length" v-bind:class="{disabledDiv: disabledDivToggle}">
        <h5 v-html="results[step].question"></h5>
        <div style="font-size:15px" v-if="results[step].description" v-html="results[step].description"></div>
        <pre  v-if="results[step].description" v-html="results[step].description"></pre>
        <hr>
        <div v-for="(result, index) in results[step].choices" class="form-check">
            <input v-on:change="getAnswer" v-bind:id="'exampleRadios' + index"
                v-bind:value="result.position" v-model="picked" class="form-check-input" type="radio"
                name="exampleRadios">
            <label v-html="result.choice" v-bind:for="'exampleRadios' + index">
            </label>
        </div>
    </div>
    <br>
    <div v-bind:class="ansColor">{{msg}}</div>
    <br>
    <button v-bind:disabled="nextQuestionDisabledToggle" class="btn btn-primary btn-sm"
        v-on:click="next">Next
        Question</button>
</div>

Methods

getAnswer gets the answer of a particular question from the server. After getting the answer it compares it to the picked option using checkAnswer method.

getAnswer:
    function () {
        axios.post('/check/', {
            key: this.results[this.step].id,
        })
        .then((response) => {
            this.ans = response.data.ans;
            vm.checkAnswer();
        })
        .catch(function (error) {
            console.log(error);
        });
    },
checkAnswer:
    function () {
        if (this.ans == this.picked) {
            this.score += 5;
            this.disabledDivToggle = true;
            vm.addCorrectClass();
        }
        else {
            this.score -= 2;
            vm.addWrongClass();
        }
    },

addWrongClass and addCorrectClass are used to show appropriate message upon selection of the option.

addWrongClass:
    function () {
        this.correctBool = false
        this.wrongBool = true
        this.msg = "Wrong (-2 points), Please try again"
    },
addCorrectClass:
    function () {
        this.wrongBool = false
        this.correctBool = true
        this.msg = "Correct! (+5 points)"
    }

When Next Question button is clicked. step is incremented, picked option is set to empty. It checks if we are on the last index of results.

next:
    function () {
        this.step++;
        this.picked = '';
        this.msg = '';
        this.disabledDivToggle = false;
        if (this.step + 1 == this.results.length) {
            this.nextQuestionDisabledToggle = true
        };
    },

Computed Property

We set green color if choice is correct and red if wrong.

        computed: {
            ansColor: function () {
                return {
                    "correct": this.correctBool,
                    "wrong": this.wrongBool,
                }
            }
        },

Style used

<style>

.disabledDiv {
    pointer-events: none;
    opacity: 0.4;
}

.correct{
    color:green; 
}

.wrong{
    color:red;
}

</style>

The main idea of this tutorial was to teach you the architecture of an end-to-end quiz app. You may try to implement the logic we learnt here in any web framework.

If you have any doubt please comment and let me know.

Leave a Reply

Your email address will not be published. Required fields are marked *