DEV Community

Chukslord
Chukslord

Posted on

Building an Appointment Scheduler App with Django and Fauna

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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',  
]
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 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>
Enter fullscreen mode Exit fullscreen mode

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>
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 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>
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 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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
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 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"),  

]
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.

# 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")
Enter fullscreen mode Exit fullscreen mode

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")),  

]  
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 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
Enter fullscreen mode Exit fullscreen mode

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()))
Enter fullscreen mode Exit fullscreen mode

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")

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 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")

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.

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")
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
josylad profile image
Joseph Adediji • Edited

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

IndexError at /today-appointment
list index out of range
Enter fullscreen mode Exit fullscreen mode

this line seems to be the culprit.

appointment = client.query(q.get(q.ref(q.collection("appointments"), appointments[page_number-1].id())))["data"] 
Enter fullscreen mode Exit fullscreen mode
Collapse
 
chukslord1 profile image
Chukslord • Edited

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 and today_appointment views with a try-except as seen below to handle cases when there is no data.

def today_appointment(request):
    if "user" in request.session:
        try:
            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)
        except:
            return render(request,"today-appointment.html")
    else:
        return HttpResponseNotFound("Page not found")

def all_appointment(request):
    if "user" in request.session:
        try:
            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)
        except:
            return render(request,"all-appointment.html")
    else:
        return HttpResponseNotFound("Page not found")
Enter fullscreen mode Exit fullscreen mode

Hope this helps, thank you.

Collapse
 
josylad profile image
Joseph Adediji

Okay, Cool.
I will try this out.

Collapse
 
ninekind profile image
ninekind • Edited

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.