DEV Community

Chukslord
Chukslord

Posted on • Edited on

Building a Quiz App with Django and Fauna

In this article, we are going to explore building a quiz application using the Python Django web framework and Fauna. If this is your first time hearing about Fauna, please visit my previous article here for a brief introduction.

Creating The Quiz Database

The first step we are going to take is to create a new database in Fauna. To create a Fauna database, you have to first sign up for an account. if you have not done that already. After signing up, you can now create a new database by clicking on the CREATE DATABASE button on the dashboard.

Alt Text

After clicking on the button as shown in the image above, you then need to give your database a name and save it. For this tutorial, we are going to name our database Quiz.

Creating The Collections

You will also need to create four collections (one for Users,Quiz,Question and Answers). A collection could be thought of as a table in a database system. Click on CREATE COLLECTION then fill in the required fields. You will need to enter the name for the collection, the History Days and TTL. The History Days is the number of days you want Fauna to retain a historical record of any data in that collection while the TTL is an expiry date for data in the collection. For example, if the TTL is set to 5, any data stored in the collection will automatically be deleted 5 days after its last modified date.

Alt Text

Creating the Fauna Indexes

We will need to create eight (8) Fauna indexes that will allow us to scroll through data added to our database. The indexes are the; user, quiz_index, quiz_get_index, question_index, question_get_index,question_answer,answer_score_index and the answer_get_index. The user index will have a term for “username” and will be a unique field that will allow querying of the User collection. The quiz_index index will have a term for “name” and will be a unique field that will allow querying and creating new documents in the Quiz collection. The quiz_get_index index will have a term for “status” which will also allow querying of the Quiz collection. The question_index index will have a term for “question_asked” which will allow querying of the Question collection. The question_get_index will have a term for "name" which will allow querying and matching with of the data in the Question collection . The question_answer index will have a term for “correct_answer” which will also allow querying and matching of Question collection. The answer_score_index will have a term for “user” and “quiz” which will allow matching and querying of the Answers collection. The answer_get_index will have a term for “user” and “question” which will also allow matching and querying of the Answers collection.

Alt Text

Creating a Fauna API Key

Before you can begin building a Django app that uses Fauna, you need to create an API key that will allow our Django app to easily interact with the database. To create an API key, go to the security tab on the left side of your screen.

Click New Key to generate a key. You will then be required to provide a database to connect the key to, the role of the key, and an optional name for your API key. After providing the information required, click the SAVE button.

Alt Text

After saving your key, your secret key will be presented as in the image above (hidden here for privacy). Copy your secret key from the dashboard and save it somewhere you can easily retrieve it for later use.

Designing Our Quiz App UI

Prerequisites

From this point onwards, to follow this step by step guide on building a quiz app with Django, you need to have the following installed:

  • Python 3.7 or >3.7
  • Faunadb
  • Django

Installing the requirements

To install Fauna and Django you simply need to run the below command in your command-line interface.

pip install faunadb
Enter fullscreen mode Exit fullscreen mode

Creating the Django Project

To create the Django project you need to run the command below in the command-line interface.

#creating the django project

django-admin startproject Quiz

#changing directory to the django project  
cd Quiz

#creating a django app  
django-admin startapp App
Enter fullscreen mode Exit fullscreen mode

The above command first creates a project Quiz, then changes the directory to the project. In the project directory, the last command creates a new app called App.

The Django Project Structure

The Django project has to be structured in a particular way that follows Django’s rules and is easy for us to work with. Navigate to the App folder in your project, then create a new file urls.py. Next, in the App folder create two new folders templates and static. The template folder will house our HTML files, while the static folder will house our CSS, Javascript, and other static files. To do this, run the command below on your command-line.

# changing directory to our app  
cd App  
# creating the static and templates folders  
mkdir templates  
mkdir static
Enter fullscreen mode Exit fullscreen mode

The “App” folder should now look like the image below.

Alt Text

Now go back to our Quiz directory and edit our settings.py file by adding our app to the list of installed apps as seen below.

INSTALLED_APPS = [  
 'django.contrib.admin',  
 'django.contrib.auth',  
 'django.contrib.contenttypes',  
 'django.contrib.sessions',  
 'django.contrib.messages',  
 'django.contrib.staticfiles',  
 'App',  
]
Enter fullscreen mode Exit fullscreen mode

Inside the static and templates folders, we are going to create their respective files.

For the templates folder create a new file dashboard.html and add the HTML code below.

{% load static %}  

<html>  
 <head>  
 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">  

 <!-- Optional theme -->  
 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">  

 <!-- Latest compiled and minified JavaScript -->  
 <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>  
 <link href="{% static 'login/style.css' %}" rel="stylesheet"/>  
 <link href="{% static 'https://fonts.googleapis.com/css?family=Ubuntu' %}" rel="stylesheet"/>  
 <meta content="width=device-width, initial-scale=1" name="viewport"/>  
 <link href="{% static 'path/to/font-awesome/css/font-awesome.min.css' %}" rel="stylesheet"/>  
 <title>  
 QUIZ APP  
 </title>  
 </head>  
 <body>  
 <div class="main">  
 <p align="center" class="sign">  
 Welcome {{user}}  
 </p>  
 <center>  
 <form class="form1">  
 {% if messages %}  
<ul class="messages">  
 {% for message in messages %}  
 <li{% if message.tags %} class="{{ message.tags }}"{% endif %} style="color:red;">{{ message }}</li>  
 {% endfor %}  
</ul>  
{% endif %}  
 <a href="quiz"><button type="button" class="btn btn-rounded btn-primary" style="background-color:purple !important;">START QUIZ</button></a>  
 <br>  
 </form>  
 <form class="form1">  
 <a href="create-quiz"><button type="button" class="btn btn-rounded btn-primary" style="background-color:purple !important;">CREATE QUIZ</button></a>  
 <br>  
 </form>  
 <form class="form1">  
 <a href="create-questions"><button type="button" class="btn btn-rounded btn-primary" style="background-color:purple !important;">CREATE QUESTION</button></a>  
 <br>  
</form>  
 <form class="form1">  
 <a href="login"><button type="button" class="btn btn-rounded btn-primary" style="background-color:purple !important;">LOGOUT</button></a>  
 </form>  
 </center>  
 </div>  
 </body>  
</html>
Enter fullscreen mode Exit fullscreen mode

This dashboard.html file is the dashboard page where a user can easily navigate between the site’s functions.

Next, create a login.html file, edit, and add the following HTML code as seen below.

{% load static %}  

<html>  
 <head>  
 <link href="{% static 'login/style.css' %}" rel="stylesheet"/>  
 <link href="{% static 'https://fonts.googleapis.com/css?family=Ubuntu' %}" rel="stylesheet"/>  
 <meta content="width=device-width, initial-scale=1" name="viewport"/>  
 <link href="{% static 'path/to/font-awesome/css/font-awesome.min.css' %}" rel="stylesheet"/>  
 <title>  
 QUIZ APP  
 </title>  
 </head>  
 <body>  
 <div class="main">  
 <p align="center" class="sign">  
 Hello   
 </p>  
 <form class="form1" method="POST">  
 {% csrf_token %}  
 {% if messages %}  
<ul class="messages">  
 {% for message in messages %}  
 <li{% if message.tags %} class="{{ message.tags }}"{% endif %} style="color:red;">{{ message }}</li>  
 {% endfor %}  
</ul>  
{% endif %}  
 <input align="center" class="un" placeholder="Username" name="username" type="text"/>  
 <input align="center" class="pass" placeholder="Password" name="password" type="password"/>  
 <button align="center" type="submit" class="submit">  
 Sign in  
 </button>  
 </form>  
 <p align="center" class="forgot">  
 <a href="register">  
 Don't have an account? Register  
 </a>  
 </p>  
 </div>  
 </body>  
</html>
Enter fullscreen mode Exit fullscreen mode

The login.html page is the page where users can easily be authenticated before accessing the site’s functions. Users are required to input their username and password to login.

The next page we are going to be creating is the register page.

Create a new file register.html, edit and add the HTML code as seen below.

{% load static %}  

<html>  
 <head>  
 <link href="{% static 'login/style.css' %}" rel="stylesheet"/>  
 <link href="{% static 'https://fonts.googleapis.com/css?family=Ubuntu' %}" rel="stylesheet"/>  
 <meta content="width=device-width, initial-scale=1" name="viewport"/>  
 <link href="{% static 'path/to/font-awesome/css/font-awesome.min.css' %}" rel="stylesheet"/>  
 <title>  
 QUIZ APP  
 </title>  
 </head>  
 <body>  
 <div class="main">  
 <p align="center" class="sign">  
 Create Account  
 </p>  
 <form class="form1" method="POST">  
 {% if messages %}  
<ul class="messages">  
 {% for message in messages %}  
 <li{% if message.tags %} class="{{ message.tags }}"{% endif %} style="color:red;">{{ message }}</li>  
 {% endfor %}  
</ul>  
{% endif %}  
 {% csrf_token %}  
 <input align="center" class="un" placeholder="Username" name="username" type="text"/>  
 <input align="center" class="un" placeholder="email" name="email" type="email"/>  
 <input align="center" class="pass" placeholder="Password" name="password" type="password"/>  
 <button align="center" class="submit" type="submit">  
 Register  
 </button>  
 <center>  
 <a href="login">  
 Already have an account? Login  
 </a>  
 </center>  
 </form>  
 </div>  
 </body>  
</html>
Enter fullscreen mode Exit fullscreen mode

The register.html page is where users can create new accounts to use for login authentication. Users are required to enter their username, email, and password.

The next page is the quiz.html page where users can see all the quizzes created. Create the quiz.html file and add the below code.

{% load static %}  

<html>  
 <head>  
 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">  

 <!-- Optional theme -->  
 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">  

 <!-- Latest compiled and minified JavaScript -->  
 <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>  
 <link href="{% static 'login/style.css' %}" rel="stylesheet"/>  
 <link href="{% static 'https://fonts.googleapis.com/css?family=Ubuntu' %}" rel="stylesheet"/>  
 <meta content="width=device-width, initial-scale=1" name="viewport"/>  
 <link href="{% static 'path/to/font-awesome/css/font-awesome.min.css' %}" rel="stylesheet"/>  
 <title>  
 QUIZ APP  
 </title>  
 </head>  
 <body>  
 <div class="main">  
 <p align="center" class="sign">  
 There {% if count == 1 %} is {{count}} quiz {% else %} are {{count}} quizes {% endif %} available  
 </p>  
 <p align="center" class="sign">  
 Choose a Quiz To Take  
 </p>  
 <center>  
 <div class="card" style="width: 18rem;">  
 <div class="card-body">  
 </div>  
 <br>  
</div>  
<a class="btn btn-primary">{{quiz.name}}</a>  
 <nav aria-label="...">  
 <br>  
 <p><b>About Test</b></p>  
 <p>{{quiz.description}}</p>  
 <br>  
 <p>Total Questions: {{quiz.total_questions}} </p>  
 <br>  
 <a href="answer-quiz/{{quiz.name}}" class="btn btn-danger">Start Test</a>  
 <br>  
 <ul class="pagination">  
 <li class="page-item disabled">  
 <a class="page-link" href="?page={{ prev_page }}" tabindex="-1">Previous</a>  
 </li>  
 <li class="page-item">  
 <a class="page-link" href="?page={{next_page }}">Next</a>  
 </li>  
 </ul>  
</nav>  
</center>  
 </div>  
 </body>  
</html>  
Enter fullscreen mode Exit fullscreen mode

The next page we are going to create is the create_quiz.html page where users can create new quizzes for users to answer. Create the create_quiz.html page and add the code below.

{% load static %}  

<html>  
 <head>  
 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">  

 <!-- Optional theme -->  
 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">  

 <!-- Latest compiled and minified JavaScript -->  
 <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>  
 <link href="{% static 'login/style.css' %}" rel="stylesheet"/>  
 <link href="{% static 'https://fonts.googleapis.com/css?family=Ubuntu' %}" rel="stylesheet"/>  
 <meta content="width=device-width, initial-scale=1" name="viewport"/>  
 <link href="{% static 'path/to/font-awesome/css/font-awesome.min.css' %}" rel="stylesheet"/>  
 <title>  
 QUIZ APP  
 </title>  
 </head>  
 <body>  
 <div class="main">  
 <p align="center" class="sign">  
 Create a Quiz  
 </p>  
 <center>  
 <form class="form1" method="POST">  
 {% csrf_token %}  
 {% if messages %}  
<ul class="messages">  
 {% for message in messages %}  
 <li{% if message.tags %} class="{{ message.tags }}"{% endif %} style="color:red;">{{ message }}</li>  
 {% endfor %}  
</ul>  
{% endif %}  
 Quiz Name:  
 <input align="center" class="pass" type="text" name="quiz_name" />  
 <br>  
 Quiz Description:  
 <input align="center" class="pass" type="text" name="quiz_description" />  
 <br>  
 Total Questions:  
 <input align="center" class="pass" type="number" name="total_questions" />  
 <br>  
 <a href="dashboard" class="btn btn-primary">Back</a><button class="btn btn-primary" type="submit">Create Quiz </button>  
 </form>  
 </center>  
 </div>  
 </body>  
</html>
Enter fullscreen mode Exit fullscreen mode

We also need to create the answer_quiz.html page where users can answer the quiz that they picked on the quiz.html page we created earlier. Copy and paste the code below in your answer_quiz.html file.

{% load static %}  

<html>  
 <head>  
 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">  

 <!-- Optional theme -->  
 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">  

 <!-- Latest compiled and minified JavaScript -->  
 <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>  
 <link href="{% static 'login/style.css' %}" rel="stylesheet"/>  
 <link href="{% static 'https://fonts.googleapis.com/css?family=Ubuntu' %}" rel="stylesheet"/>  
 <meta content="width=device-width, initial-scale=1" name="viewport"/>  
 <link href="{% static 'path/to/font-awesome/css/font-awesome.min.css' %}" rel="stylesheet"/>  
 <title>  
 QUIZ APP  
 </title>  
 </head>  
 <body>  
 <div class="main">  
 <p align="center" class="sign">  
 Choose Any Option from A to D  
 </p>  
 <center>  
 <div class="card" style="width: 18rem;">  
 <div class="card-body">  
 </div>  
 <br>  
</div>  
<a class="btn btn-primary">SCIENCE TEST</a>  
{% if messages %}  
<ul class="messages">  
{% for message in messages %}  
<li{% if message.tags %} class="{{ message.tags }}"{% endif %} style="color:red;">{{ message }}</li>  
{% endfor %}  
</ul>  
{% endif %}  
 {% if score %}  
 <h1>Total Score: {{score}} out of {{question_count}}<h1>  
 {% else %}  
 <nav aria-label="...">  
 <br>  
 <p><b>Question: {{question.question_asked}}</b></p>  
 <br>  
 <form method="POST" >  
 {% csrf_token %}  
 <p>A. {{question.answer_1}} <input type="radio" name="answer" value="{{question.answer_1}}" /></p>  
 <p>B. {{question.answer_2}} <input type="radio" name="answer" value="{{question.answer_2}}" /></p>  
 <p>C. {{question.answer_3}} <input type="radio" name="answer" value="{{question.answer_3}}" /></p>  
 <p>D. {{question.answer_4}} <input type="radio" name="answer" value="{{question.answer_4}}" /></p>  
 <input type="hidden" name="question" value="{{question.question_asked}}"/>  

 <ul class="pagination">  
 <li class="page-item disabled">  
 <a class="page-link" href="?page={{ prev_page }}" tabindex="-1">Previous</a>  
 </li>  
 <li class="page-item">  
 <a class="page-link" href="?page={{next_page }}">Next</a>  
 </li>  
 </ul>  
</nav>  
<button class="btn btn-danger" type="submit">Save Answer</button>  
</form>  
{% if finish %}  
<a href="?finish=true"><button class="btn btn-danger" >Finish</button></a>  
{% endif %}  
{% endif %}  
 <a href="{% url 'App:dashboard' %}" class="btn btn-primary">Back</a>  
</center>  
 </div>  
 </body>  
</html>
Enter fullscreen mode Exit fullscreen mode

The final page is the create_questions.html page where the user can create the questions that will appear under a quiz. To create copy and paste the code below.

{% load static %}  

<html>  
 <head>  
 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">  

 <!-- Optional theme -->  
 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">  

 <!-- Latest compiled and minified JavaScript -->  
 <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>  
 <link href="{% static 'login/style.css' %}" rel="stylesheet"/>  
 <link href="{% static 'https://fonts.googleapis.com/css?family=Ubuntu' %}" rel="stylesheet"/>  
 <meta content="width=device-width, initial-scale=1" name="viewport"/>  
 <link href="{% static 'path/to/font-awesome/css/font-awesome.min.css' %}" rel="stylesheet"/>  
 <title>  
 QUIZ APP  
 </title>  
 </head>  
 <body>  
 <div class="main">  
 <p align="center" class="sign">  
 Create a Question  
 </p>  
 <center>  
 <form class="form1" method="POST">  
 {% csrf_token %}  
 {% if messages %}  
<ul class="messages">  
 {% for message in messages %}  
 <li{% if message.tags %} class="{{ message.tags }}"{% endif %} style="color:red;">{{ message }}</li>  
 {% endfor %}  
</ul>  
{% endif %}  
 Quiz Name:  
 <select align="center" class="pass" name="quiz_name">  
 {% for quiz in quiz_all %}  
 <option>{{quiz.data.name}}</option>  
 {% endfor %}  
 </select>  
 <br>  
 Question:  
 <input align="center" class="pass" type="text" name="question" />  
 <br>  
 Answer 1:  
 <input align="center" class="pass" type="text" name="answer_1" />  
 <br>  
 Answer 2:  
 <input align="center" class="pass" type="text" name="answer_2" />  
 <br>  
 Answer 3:  
 <input align="center" class="pass" type="text" name="answer_3" />  
 <br>  
 Answer 4:  
 <input align="center" class="pass" type="text" name="answer_4" />  
 <br>  
 Correct Answer:  
 <input align="center" class="pass" type="text" name="correct_answer" />  
 <br>  
 <a href="dashboard" class="btn btn-primary">Back</a><button class="btn btn-primary" type="submit">Create Question </button>  
 </form>  
 </center>  
 </div>  
 </body>  
</html>
Enter fullscreen mode Exit fullscreen mode

We are done creating the HTML files needed. Now we need to create our CSS files. Navigate back to the static folder, create a new folder in it; Login folder.

In the Login folder, create a new file style.css and add the CSS code as seen in the GitHub gist link here.

We are done with the static and templates folders. Let’s now edit the urls.py and the views.py file. In the urls.py file, copy and add the following python code as seen below.

from django.conf import settings  
from django.conf.urls.static import static  
from django.urls import path, include  
from . import views  
app_name = "App"  

urlpatterns = [  
 path("", views.login, name="login"),  
 path("login", views.login, name="login"),  
 path("dashboard", views.dashboard, name="dashboard"),  
 path("quiz", views.quiz, name="quiz"),  
 path("register", views.register, name="register"),  
 path("create-quiz", views.create_quiz, name="create-quiz"),  
 path("create-questions", views.create_question, name="create-question"),  
 path("answer-quiz/<slug>", views.answer_quiz, name="answer-quiz"),  
]
Enter fullscreen mode Exit fullscreen mode

In the urls.py file we imported the required Django modules needed and defined all the URLs we are going to be making use of in this project and connected them with the required view function needed for them to run.

from django.shortcuts import render,redirect  
from django.contrib import messages  
from django.core.paginator import Paginator  
from django.http import HttpResponseNotFound  
from faunadb import query as q  
import pytz  
from faunadb.objects import Ref  
from faunadb.client import FaunaClient  
import hashlib  
import datetime  




client = FaunaClient(secret="SECRET_KEY")  
indexes = client.query(q.paginate(q.indexes()))
Enter fullscreen mode Exit fullscreen mode

In the views.py file we imported all the required Django modules and initialized the Fauna client using our secret key which we saved earlier.

Navigate back to the base directory and open the Quiz folder as seen in the image below.

Alt Text

from django.contrib import admin  
from django.urls import path, include  
from django.conf import settings  
from django.contrib.staticfiles.urls import staticfiles_urlpatterns  
from django.contrib.staticfiles.urls import static  

urlpatterns = [  
 path('admin/', admin.site.urls),  
 path('', include("App.urls")),  

]  
Enter fullscreen mode Exit fullscreen mode

In the base project’s urls.py we imported the modules required and linked the app URLs to our base path URLs.

Building Our Quiz App Logic

We would be going through all the steps to build the functionality of our quiz application.

Register Functionality

The first action the user takes when opening the web app is to register if they haven't already. Update the register function in the views.py with the python code below.

def register(request):
   if request.method == "POST":
       username = request.POST.get("username").strip().lower()
       email = request.POST.get("email").strip().lower()
       password = request.POST.get("password")

       try:
           user = client.query(q.get(q.match(q.index("users_index"), username)))
           messages.add_message(request, messages.INFO, 'User already exists with that username.')
           return redirect("App:register")
       except:
           user = client.query(q.create(q.collection("users"), {
               "data": {
                   "username": username,
                   "email": email,
                   "password": hashlib.sha512(password.encode()).hexdigest(),
                   "date": datetime.datetime.now(pytz.UTC)
               }
           }))
           messages.add_message(request, messages.INFO, 'Registration successful.')
           return redirect("App:login")
   return render(request,"register.html")
Enter fullscreen mode Exit fullscreen mode

In the register view function, we first checked if the request sent from the register page is a POST request else we simply render the register page. If this was the case, then we are to expect some values (i.e the username, email, and password) in the POST request. We then make a request to Fauna with the get method of the FQL client and the users_index we created earlier to check the user's collection if a user with the match of the username already exists. If the user exists then a “User already exists with that username” message is displayed. If the user does not exist, a request is made with the FQL client to Fauna to create a document with provided details along with the date of registration. The user is now redirected to the login page to log in with his/her newly created login details. If you check the users collection you should see the data added as seen below.

Alt Text

On unsuccessful registration, a message as seen in the image below should be displayed.

Alt Text

Login Functionality

The second action the user takes when he opens the web app is to provide his login details for authentication. Update the login function in the views.py with the following python code to implement login functionality.

def login(request):
   if request.method == "POST":
       username = request.POST.get("username").strip().lower()
       password = request.POST.get("password")

       try:
           user = client.query(q.get(q.match(q.index("users_index"), username)))
           if hashlib.sha512(password.encode()).hexdigest() == user["data"]["password"]:
               request.session["user"] = {
                   "id": user["ref"].id(),
                   "username": user["data"]["username"]
               }
               return redirect("App:dashboard")
           else:
               raise Exception()
       except:
           messages.add_message(request, messages.INFO,"You have supplied invalid login credentials, please try again!", "danger")
           return redirect("App:login")
   return render(request,"login.html")
Enter fullscreen mode Exit fullscreen mode

In the login views function, we first check if the request sent from the login page is a POST request else we simply render the login page. If this was the case, then we are to expect some values (i.e the username and password) in the POST request. After collecting the username and password, we then make use of the get method of the FQL client and the users_index we created earlier to make a request to Fauna to check if the user exists using the username as a match. If this user exists, we then check if the password passed is correct by hashing it and comparing it to the hashed password on Fauna. If the password is correct we then store the user’s data in the session and redirect him/her to the dashboard. If the user does not exist or the password doesn’t match, then we pass a “you have supplied invalid login credentials” message to the login page then redirect the user back to the login page. We then cached our function with a try-except to handle any errors that may come from querying the Fauna.

If the login was unsuccessful you should get a result as seen in the image below.

Alt Text

The Dashboard

Update your views.py file with the code below to create the dashboard view and render the logged in users data on the frontend.

def dashboard(request):
   if "user" in request.session:
       user=request.session["user"]["username"]
       context={"user":user}
       return render(request,"dashboard.html",context)
   else:
       return HttpResponseNotFound("Page not found")
Enter fullscreen mode Exit fullscreen mode

On successful login, you should be redirected to the dashboard page as seen in the image below.

Alt Text

Create Quiz Functionality

We need to allow users to create new quizzes to participate in. To do this update your views.py file with the following code:

def create_quiz(request):
   if request.method=="POST":
       name=request.POST.get("quiz_name")
       description=request.POST.get("quiz_description")
       total_questions=request.POST.get("total_questions")
       try:
           quiz = client.query(q.get(q.match(q.index("quiz_index"), name)))
           messages.add_message(request, messages.INFO, 'A Quiz with that name already exists.')
           return redirect("App:create-quiz")
       except:
           quiz = client.query(q.create(q.collection("Quiz"), {
               "data": {
                   "status":"active",
                   "name": name,
                   "description": description,
                   "total_questions": total_questions,
               }
           }))
           messages.add_message(request, messages.INFO, 'Quiz Created Successfully.')
           return redirect("App:create-quiz")
   return render(request,"create_quiz.html")
Enter fullscreen mode Exit fullscreen mode

In the code above, we checked if there a POST request was received. If it was, we collected the “name”,”description” and “total_questions” which are fields required to create a quiz. WE then used a “try-except” to cache querying our Quiz collection with our quiz_index index which checks if any other quiz in the database matches the name provided for the quiz to be created. If the query produces no results, we then allow the quiz to be created.

Alt Text

Quiz Page Functionality

The quiz page carries all the quizzes created and allows users to scroll through them and choose anyone to participate in. Update your views.py file with the following code:

def quiz(request):
   try:
       all_quiz=client.query(q.paginate(q.match(q.index("quiz_get_index"), "active")))["data"]
       quiz_count=len(all_quiz)
       page_number = int(request.GET.get('page', 1))
       quiz = client.query(q.get(q.ref(q.collection("Quiz"), all_quiz[page_number-1].id())))["data"]
       context={"count":quiz_count,"quiz":quiz, "next_page": min(quiz_count, page_number + 1), "prev_page": max(1, page_number - 1)}
       return render(request,"quiz.html",context)
   except:
       return render(request,"quiz.html")
Enter fullscreen mode Exit fullscreen mode

In the code above, we made a query to the FQL client and created the query all_quiz using the quiz_get_index index to check for quizzes that are active. We then created a pagination quiz to query all data in the all_quiz query we created earlier using the page number requested by the user and rendered all the pagination and other required data using the context.’

Alt Text

As seen in the image above, the user can view all the quizzes on every page and click on Start Test to answer a quiz of his/her choice.

Create Questions Functionality

This functionality allows users to create questions that would appear in the different quizzes created. Update your views.py with the code below.

def create_question(request):
   quiz_all=client.query(q.paginate(q.match(q.index("quiz_get_index"), "active")))
   all_quiz=[]
   for i in quiz_all["data"]:
       all_quiz.append(q.get(q.ref(q.collection("Quiz"),i.id())))
   context = {"quiz_all":client.query(all_quiz)}
   if request.method=="POST":
       quiz_name=request.POST.get("quiz_name")
       question_asked=request.POST.get("question")
       answer_1=request.POST.get("answer_1")
       answer_2=request.POST.get("answer_2")
       answer_3=request.POST.get("answer_3")
       answer_4=request.POST.get("answer_4")
       correct_answer=request.POST.get("correct_answer")
       try:
           question_create = client.query(q.get(q.match(q.index("question_index"), question_asked)))
           messages.add_message(request, messages.INFO, 'This question already exists')
           return redirect("App:create-question")
       except:
           question_create = client.query(q.create(q.collection("Question"), {
               "data": {
                   "quiz_name": quiz_name,
                   "question_asked": question_asked,
                   "answer_1": answer_1,
                   "answer_2": answer_2,
                   "answer_3": answer_3,
                   "answer_4": answer_4,
                   "correct_answer": correct_answer,
               }
           }))
           messages.add_message(request, messages.INFO, 'Question Created Successfully.')
           return redirect("App:create-question")
   return render(request,"create-questions.html",context)
Enter fullscreen mode Exit fullscreen mode

In the code above, we made a query to the FQL client using the quiz_get_index to check query all the data in the Quiz collection to be rendered on the create-questions.html page. We then appended all the data in the query above into the all_quiz list. At this point, we checked if a POST request was received. If it was, we collected all the data provided in the POST request required to create a question and made a query using the question_index index to check if there are any documents in our Question collection with the same data provided for the question to be created. If there was no match, we proceeded to create the question and rendered a “Question created successfully” message to the frontend while redirecting back to the create-questions.html page to avoid duplicate data on reload of the page.

Alt Text

The Answer Quiz Functionality

To allow users to answer questions and get scored on a quiz, update your views.py with the following code:

def answer_quiz(request,slug):
   question_all=client.query(q.paginate(q.match(q.index("question_get_index"), slug)))["data"]
   question_count=len(question_all)
   page_number = int(request.GET.get('page', 1))
   question = client.query(q.get(q.ref(q.collection("Question"), question_all[page_number-1].id())))["data"]
   if page_number==question_count:
       context={"question":question,"next_page": min(question_count, page_number + 1), "prev_page": max(1, page_number - 1),"finish":"true"}
   else:
       context={"question":question,"next_page": min(question_count, page_number + 1), "prev_page": max(1, page_number - 1)}
   if request.method=="POST":
       answer=request.POST.get("answer")
       question=request.POST.get("question")
       try:
           check_answer=client.query(q.get(q.match(q.index("answer_get_index"), request.session["user"]["username"],question)))["data"]
           messages.add_message(request, messages.INFO, 'You already answered this question')
       except Exception:
           answer_create = client.query(q.create(q.collection("Answers"), {
               "data": {
                   "user": request.session["user"]["username"],
                   "quiz": slug,
                   "question": question,
                   "answer": answer,
               }
           }))
           messages.add_message(request, messages.INFO, 'Answer Saved')
   if request.GET.get("finish")=="true":
       score=0
       check_answer=client.query(q.paginate(q.match(q.index("answer_score_index"), request.session["user"]["username"],slug)))
       all_answer=[]
       for i in check_answer["data"]:
           all_answer.append(q.get(q.ref(q.collection("Answers"),i.id())))
       answers=client.query(all_answer)
       for i in answers:
           try:
               mark_answer=client.query(q.get(q.match(q.index("question_answer"),i["data"]["answer"])))
               score=score+1
           except:
               score=score
       context={"score":score,"question_count":question_count}
   return render(request,"answer_quiz.html",context)
Enter fullscreen mode Exit fullscreen mode

In the code above, we made a query question_all to get the quiz matching the slug passed for the particular page. We then counted the number of questions in the query question_count and created pagination of the Question collection by matching the id of the page of the question_all query requested by the user. We then checked if the current page number is equal to the last page in our query. If this is the case, we render “finish” to the context which will allow the user to click on the finish button and end the quiz. We also checked if a POST request was received to make an answer submission. If a POST was received, we check if the user already answered that particular question by making a query to the FQL client using the answer_get_index index to match the username of the current user and the question answered in the Answers. If the user hasn’t answered this question, we create a query to create this answer in the Answers collection. The user selects the answer for the current question and clicks on the save answer button before proceeding to the next question. Lastly, we checked if the user clicked on the finish button by checking if “finish==true”. We set the original score in the test and created a query check_answer to the FQL client to get the answers matching the particular quiz answered using the answer_score_index index created earlier. We then ran a for loop to append all the data in check_answer into the all_answer list. At this point, we created a query using all_answer. We then looped through all_answer to check if the answers saved by the users match the correct_answer field for each data in the Question collection using the question_answer index. We cached this checking query with a “try-except” so that for each time both answers match without any errors the score increase by one (1). Otherwise, the score remains the same. This loop runs matches all answers provided by the user till the final score is obtained. We then render this score to the frontend using context for the user to see.

Alt Text
After picking an option for each question the user has to click on THE Save Answer button to save the answer to the database and click on Next to proceed to the next question.

Alt Text

As seen in the image above, after answering the last question, the user can save his answer and click on the Finish button to end the quiz and see his total score for that quiz.

The score will be displayed similar to the one in the image below.

Alt Text

Conclusion

In this article, we built a quiz application with Fauna's serverless database and Django. We saw how easy it is to integrate Fauna into a Python application and got the chance to explore some of its core features and functionalities.

The source code of our quiz application is available on Github. If you have any questions, don't hesitate to contact me on Twitter: @LordChuks3.

Top comments (2)

Collapse
 
aurisbut profile image
Aurimas Butvilauskas

What terms, uniqueness and collection for question_get_index ?

Collapse
 
chukslord1 profile image
Chukslord

The question_get_index will have a term for "name" which will allow querying and matching with of the data in the Question collection .