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.
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.
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.
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.
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
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
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
The “App” folder should now look like the image below.
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',
]
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>
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>
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>
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>
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>
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>
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>
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"),
]
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()))
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.
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")),
]
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")
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.
On unsuccessful registration, a message as seen in the image below should be displayed.
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")
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.
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")
On successful login, you should be redirected to the dashboard page as seen in the image below.
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")
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.
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")
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.’
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)
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.
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)
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.
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.
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.
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)
What terms, uniqueness and collection for question_get_index ?
The question_get_index will have a term for "name" which will allow querying and matching with of the data in the Question collection .