If you have ever wanted to build an appointment scheduling application with Django and have been looking to explore Fauna serverless database then this article is for you. Personally, I have always had problems with remembering meetings and appointments I have planned out for the day. I decided to build a solution to this problem, with the appointment scheduling application I can easily check appointments I have for the day.
To build this, I made use of the Django Python web framework and Fauna. Django is a very popular web framework, but not so many people know about Fauna and how amazing it can be when managing databases.
What is Fauna?
Fauna is a client-side serverless document database that makes use of GraphQL and the Fauna Query Language (FQL) to support a variety of data types and particularly relational databases in a serverless API. You can learn more about Fauna in their official documentation here.
Creating A Fauna Database
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.
You will also need to create two collections (one for users
and one for events
). 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.
After saving the collection you just created, you will be presented with a screen similar to the image above. A document is relatable to rows of data in a table like a normal database system.
Creating a Fauna Index
We will need to create a Fauna index that allows us to scroll through data added to our database. To do this, go to the DB Overview
tab on the left side of your screen then click on the New Index
button.
After clicking on the New Index
button you will be presented with the above screen. You are required to select the collection you want to connect this particular index to. After choosing the collection, enter a name for your index, terms for your index, and values. Also tick the Unique
checkbox to ensure the data entered for the username is unique. The terms field is used to specify what data you want the index to be able to browse. In this case, we are using a username as the terms. Click on save once you are done filling the required fields. We also have to create three indexes; one with the date and time attributes as unique terms of the Events
collection, the other with user attribute as terms, and the last with user and date as terms. The indexes should be created as seen below.
Integrating Fauna with Python
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 as seen in the image above.
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 Appointment Scheduler UI
Prerequisites
From this point onwards, to follow this step by step guide on building an appointment 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 commands in your command-line interface.
pip install django
pip install faunadb
To check if Django was successfully installed run the command below in the command-line interface. This will provide you with the version of Django installed on your system.
django-admin --version
To check if Fauna installed properly, run the sample python code provided in Fauna’s Python driver documentation.
from faunadb import query as q
from faunadb.objects import Ref
from faunadb.client import FaunaClient
client = FaunaClient(secret="your-secret-here")
indexes = client.query(q.paginate(q.indexes()))
print(indexes)
If your result after running is similar to the image below, then you are good to go.
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 AppointmentScheduler
#changing directory to the django project
cd AppointmentScheduler
#creating a django app
django-admin startapp App
The above command first creates a project AppointmentScheduler
, 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 AppointmentScheduler
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 index.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>
Appointment Scheduler
</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="create-appointment"><button type="button" class="btn btn-rounded btn-primary" style="background-color:purple !important;">Create Appointment</button></a>
</form>
<form class="form1">
<a href="today-appointment"><button type="button" class="btn btn-rounded btn-primary" style="background-color:purple !important;">Appointments Today</button></a>
</form>
<form class="form1">
<a href="all-appointment"><button type="button" class="btn btn-rounded btn-primary" style="background-color:purple !important;">All Appointments</button></a>
</form>
</center>
</div>
</body>
</html>
This index.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>
Appointment Scheduler
</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 log in.
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>
Appointment Scheduler
</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 today-appointment.html
page where users can view appointments they scheduled and are taking place that present day. Users can view all their and click on the “click to complete” button to complete their appointments for the day.
Create the “today-appointment” HTML file, edit, and add the following code as seen 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>
Appointment Scheduler
</title>
</head>
<body>
<div class="main">
<p align="center" class="sign">
You have {{count}} Appointments Today
</p>
<center>
<div class="card" style="width: 18rem;">
<div class="card-body">
<h5 class="card-title">{{appointment.name}}</h5>
<p class="card-text">{{appointment.description}}</p>
<p class="card-text">Date: {{appointment.date}}</p>
<p class="card-text">Time:{{appointment.time}}</p>
</div>
{% if appointment.status == "True" %}
<a class="btn btn-primary">Completed</a>
{% else %}
<a href="?complete=''&page={{page_num}}" class="btn btn-danger">Click to complete</a>
{% endif %}
</div>
<nav aria-label="...">
<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>
<a href="dashboard"> <button class="btn btn-danger btn-rounded" type="button">Back</button></a>
<a href="?delete=''&page={{page_num}}"> <button class="btn btn-danger btn-rounded" type="button">Delete</button></a>
</center>
</center>
</div>
</body>
</html>
The last HTML page we are going to be creating is the all-appointments.html
page. This page is where users can view all the appointments they have created (both past and present). Users can also see the appointments they completed and those they didn’t complete. Create a new file all-appintments.html
, edit and add the following code as seen below to it.
{% 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>
Appointment Scheduler
</title>
</head>
<body>
<div class="main">
<p align="center" class="sign">
You have {{count}} Appointments in Total
</p>
<center>
<div class="card" style="width: 18rem;">
<div class="card-body">
<h5 class="card-title">{{appointment.name}}</h5>
<p class="card-text">{{appointment.description}}</p>
<p class="card-text">Date: {{appointment.date}}</p>
<p class="card-text">Time:{{appointment.time}}</p>
</div>
</div>
{% if appointment.status == "True" %}
<a class="btn btn-primary">Completed</a>
{% else %}
<a class="btn btn-danger">Not Completed</a>
{% endif %}
<nav aria-label="...">
<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>
<a href="dashboard"> <button class="btn btn-danger btn-rounded" type="button">Back</button></a>
<a href="?delete=''&page={{page_num}}"> <button class="btn btn-danger btn-rounded" type="button">Delete</button></a>
</center>
</div>
</body>
</html>
We need to create a page where users can schedule/create appointments. To do this, create a new folder called appoint
. In the appoint
folder, create a new file create-appointment.html
, edit, and add the following HTML code as seen below.
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<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>
<title>
Appointment Scheduler
</title>
<link href="{% static 'appointment/style.css' %}" rel="stylesheet"/>
<meta content="width=device-width, initial-scale=1" name="viewport"/>
</head>
<body>
<!-- partial:index.partial.html -->
<script src="https://code.jquery.com/jquery-2.1.0.min.js">
</script>
<body>
<div id="formWrapper">
<div id="form">
<div class="logo">
<h2>Create an Appointment</h2>
</div>
<form 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 %}
<div class="form-item">
<p class="formLabel">
Appointment Name
</p>
<input autocomplete="off" class="form-style" id="email" name="name" type="text" required/>
</div>
<div class="form-item">
<p class="formLabel">
Appointment Description
</p>
<input autocomplete="off" class="form-style" id="email" name="description" type="text" required/>
</div>
<div class="form-item">
<p class="formLabel">
Appointment Time
</p>
<input autocomplete="off" class="form-style" id="email" name="time" type="time" required/>
</div>
<div class="form-item">
<p class="formLabel">
Appointment Date
</p>
<input autocomplete="off" class="form-style" id="email" name="date" type="date" required/>
</div>
<div class="form-item">
<a href="dashboard"> <button class="btn btn-danger btn-rounded" type="button">Back</button></a>
<input class="login pull-right" type="submit" value="Create"/>
<div class="clear-fix">
</div>
</div>
</div>
</form>
</div>
</body>
</body>
</html>
<!-- partial -->
<script src="{% static 'appointment/script.js' %}">
</script>
We are done creating the HTML files needed. Now we need to create our CSS files. Navigate back to the static
folder, create two new folders in it; appointment
and login
folders.
In the appointment
folder, create a new file script.js
then add the Javascript code as seen in the GitHub gist link here. Create another file style.css
and add the CSS code as seen in the GitHub gist link here. Now navigate to the “login” folder, create a new file style.css
, edit and add the following CSS code to it 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("create-appointment", views.create_appointment, name="create-appointment"),
path("today-appointment", views.today_appointment, name="today-appointment"),
path("all-appointment", views.all_appointment, name="all-appointment"),
path("register", views.register, name="register"),
]
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.
# Create your views here.
from django.shortcuts import render
def login(request):
return render(request,"login.html")
def create_appointment(request):
return render(request,"appoint/create-appointment.html")
def dashboard(request):
return render(request,"index.html")
def today_appointment(request):
return render(request,"today-appointment.html")
def all_appointment(request):
return render(request,"all-appointment.html")
def register(request):
return render(request,"register.html")
In the views.py
file we imported all the required Django modules and defined all the view functions while rendered their respective HTML pages which we are going to be adding functionalities to later in this walkthrough.
Navigate back to the base directory and open the AppointmentScheduler
folder as seen in the image below.
Open and edit the urls.py
file and add the following python code as seen 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 Appointment Scheduler Logic
We would be going through all the steps to build the functionality of our appointment scheduling application.
Importing Required Modules
Edit and import the following modules we will need for our project as seen in the python code below.
# Create your views here.
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
Initializing FQL Client
We will now initialize the FQL client which we will need to access our database from time to time within this project. To initialize the FQL client copy and paste the code below immediately after your imports above.
client = FaunaClient(secret="SECRET_KEY")
indexes = client.query(q.paginate(q.indexes()))
Make sure to replace SECRET_KEY
with your real secret key you stored safely earlier.
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 users
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.
On successful login, you should be redirected to the dashboard page as seen in the image below.
Creating Appointment Functionality
Let’s now give our create-appointment
page the functionality to schedule new appointments. To do this copy the python code below and update the create_appointment
function in your views.py.
def create_appointment(request):
if "user" in request.session:
if request.method=="POST":
name=request.POST.get("name")
description=request.POST.get("description")
time=request.POST.get("time")
date=request.POST.get("date")
try:
user = client.query(q.get(q.match(q.index("events_index"), date,time)))
messages.add_message(request, messages.INFO, 'An Event is already scheduled for the specified time.')
return redirect("App:create-appointment")
except:
user = client.query(q.create(q.collection("Events"), {
"data": {
"name": name,
"description": description,
"time": time,
"date": date,
"user": request.session["user"]["username"],
"status": 'False',
}
}))
messages.add_message(request, messages.INFO, 'Appointment Scheduled Successfully.')
return redirect("App:create-appointment")
return render(request,"appoint/create-appointment.html")
else:
return HttpResponseNotFound("Page not found")
In the create_appointment
view we first checked if the user is logged in by checking if his/her data is stored in the session. If the user is not logged in then this page cannot be accessed. We then check if a POST request was sent. If this is the case, then the data sent along in the POST request (i.e name, description, time, date) is collected. A request with the FQL client is then sent to Fauna to check if the date and time of the event to be scheduled is already existing. If they exist, then an “An Event is already scheduled for the specified time” message is displayed. If the date and time are not existing in the database, then a request with the FQL client is made to create the appointment with provided details.
If the appointment is scheduled successfully you should see an image like the one below.
Also if you check the Events
collection you should see some new data as in the image below.
View Upcoming Appointments Functionality
We need to allow users to see their appointments scheduled for the current day. To do this, in the today_appointment
function update the python code as seen below.
def today_appointment(request):
if "user" in request.session:
appointments=client.query(q.paginate(q.match(q.index("events_today_paginate"), request.session["user"]["username"],str(datetime.date.today()))))["data"]
appointments_count=len(appointments)
page_number = int(request.GET.get('page', 1))
appointment = client.query(q.get(q.ref(q.collection("Events"), appointments[page_number-1].id())))["data"]
context={"count":appointments_count,"appointment":appointment,"page_num":page_number, "next_page": min(appointments_count, page_number + 1), "prev_page": max(1, page_number - 1)}
return render(request,"today-appointment.html",context)
else:
return HttpResponseNotFound("Page not found")
In the today_appointment
view, we first checked if the user is logged in by making sure the data is stored in the session. If the user is not logged in then this page cannot be accessed. We then create a query appointments
by making a request with the paginate
method of the FQL client to Fauna to paginate and match the data to the username of the user and the current date stored in the Events
collection using the events_today_paginate
index we created earlier.
We then create a variable appointments_count
to count the number of documents in the appointments
query above and pass it as context to the frontend. To scroll through all the data, a GET request is sent with the page number. This page number is collected and the default is set to 1 (in case no page number is sent) and used to get the documents in the query. To do this we create a variable appointment
and assign it to a query created by making a request with the get
method of the FQL client to the Events
collection and the id of the current document in the appointments
query we created above. The appointment
, page_num
, next_page
, and prev_page
are all sent to the context to be rendered on the frontend. The prev_page
is created by getting the maximum between 1 and the current page number to avoid going out of the index. While the next_page
is created by getting the minimum between 1 and the current page number.
Completing and Deleting Appointments
To complete an event scheduled for the current day, a GET request is sent by clicking on the complete button
in the template. When this request is sent the query for the current page appointment is retrieved and updated using the update
method of the FQL client to change the status in the document to true
. For deleting appointments, a GET request is sent by clicking on the delete button
in the template. When this request is sent the query for the current page appointment is retrieved and deleted using the delete
method of the FQL client. To add these functionalities, add the following code to the today_appointment
function as seen below.
def today_appointment(request):
if "user" in request.session:
appointments=client.query(q.paginate(q.match(q.index("events_today_paginate"), request.session["user"]["username"],str(datetime.date.today()))))["data"]
appointments_count=len(appointments)
page_number = int(request.GET.get('page', 1))
appointment = client.query(q.get(q.ref(q.collection("Events"), appointments[page_number-1].id())))["data"]
if request.GET.get("complete"):
client.query(q.update(q.ref(q.collection("Events"), appointments[page_number-1].id()),{"data": {"status": "True"}}))["data"]
return redirect("App:today-appointment")
if request.GET.get("delete"):
client.query(q.delete(q.ref(q.collection("Events"), appointments[page_number-1].id())))
return redirect("App:today-appointment")
context={"count":appointments_count,"appointment":appointment,"page_num":page_number, "next_page": min(appointments_count, page_number + 1), "prev_page": max(1, page_number - 1)}
return render(request,"today-appointment.html",context)
else:
return HttpResponseNotFound("Page not found")
The today page should be displayed as seen in the image below.
If you click on the click to complete
button you should have a result as seen in the image below.
Viewing All Appointments
We need to allow users to see all their appointments scheduled for the current day. To do this, in the all_appointment
function update the python code as seen below.
def all_appointment(request):
if "user" in request.session:
appointments=client.query(q.paginate(q.match(q.index("events_index_paginate"), request.session["user"]["username"])))["data"]
appointments_count=len(appointments)
page_number = int(request.GET.get('page', 1))
appointment = client.query(q.get(q.ref(q.collection("Events"), appointments[page_number-1].id())))["data"]
if request.GET.get("delete"):
client.query(q.delete(q.ref(q.collection("Events"), appointments[page_number-1].id())))
return redirect("App:all-appointment")
context={"count":appointments_count,"appointment":appointment, "next_page": min(appointments_count, page_number + 1), "prev_page": max(1, page_number - 1)}
return render(request,"all-appointment.html",context)
else:
return HttpResponseNotFound("Page not found")
In the all_appointment
view, we first checked if the user is logged in by confirming that data is stored in the session. If the user is not logged in then this page cannot be accessed. We then create a query appointments
by making a request with the paginate
method of the FQL client to Fauna to paginate and match the data to the username of the user stored in the Events
collection using events_index_paginate
index we created earlier. We then create a variable appointments_count
to count the number of documents in the appointments
query above and pass it as context to the frontend.
To scroll through all the data, a GET request is sent with the page number. This page number is collected and the default is set to 1 (in case no page number is sent), and used to get the documents in the query. To do this we create a variable appointment
and assign it to a query created by making a request with the get
method of the FQL client to the Events
collection and the id of the current document in the appointments
query we created above.
The appointment
, page_num
, next_page
, and prev_page
are all sent to the context to be rendered on the frontend. The prev_page
is created by getting the maximum between 1 and the current page number to avoid going out of the index. While the next_page
is created by getting the minimum between 1 and the current page number.
Your all-appointment
page should look like the one in the image below.
Conclusion
In this article, we built an appointment scheduler 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 appointment scheduler application is available on Github. If you have any questions, don't hesitate to contact me on Twitter: @LordChuks3.
Top comments (4)
Hi Ochuko,
this is a nice tutorial; i decided to use this as a weekend fun project and so far so good.
The only issue I am having now is with your pagination code, it is throwing an error here when I try to load the "All appointments" and "Today's appointments" page.
I get
this line seems to be the culprit.
This error is a bug that comes up when there is no data added to the events table, therefore no data to be paginated. To resolve this issue simply cache the
all_appointment
andtoday_appointment
views with a try-except as seen below to handle cases when there is no data.Hope this helps, thank you.
Okay, Cool.
I will try this out.
noticed a few bugs it lets users register with no info. didnt show an apt for a test user.
any idea how to fix? also instead of username it says welcome anonymoususer
Some comments may only be visible to logged-in visitors. Sign in to view all comments.