Written in connection with the Write with Fauna program.
This article will guide you to build a simple electronic library application using the Django web framework and implement the database with Fauna. This e-library would allow authenticated users to add books to the inventory, search for books, and view book details.
Prerequisites
To fully understand this tutorial, you are required to have the following in place:
- Python 3.7 or newer.
- Basic understanding of Fauna.
- Basic understanding of Django.
- A text editor.
Introduction to Fauna
If this is the first time you hear about Fauna, visit my previous article here for a brief introduction.
Creating The E-Library Database
To create a Fauna database, you have to first sign up for an account. Suppose you have not done that already. After signing up, you can 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 need to give your database a name and save it.
Creating The Collections
You need to create two collections in your database; the Books
collection and the Users
collection. The Books
collection will store the documents for all the books added to the e-library. While the Users
collection will store users data registered on the platform. For the History days
and TTL
, use the default values provided and save.
Creating The Indexes
You will create three indexes for your collections; user
,book_index
, and book_index_all
. The user
index will allow you to scroll through data in the User
collection. It has one term, which is the username
field. This term will enable you to match data with the username for easy querying. The username field is unique.
Both book_index
and book_index_all
allow you to scroll through data in the Books
collection. The book_index
index has one term, which is the title
field. The book_index_all
index also has one term, which is the availability
field.
Creating an API key
To create an API key, go to the security tab on the left side of your dashboard, then click New Key
to generate a key. You will then be required to provide a database to connect to the key. After providing the information required, click the SAVE
button.
.
Your secret key will be presented as in the image above (hidden here for privacy) after saving your key. Copy your secret key from the dashboard and save it somewhere you can easily retrieve it for later use.
Building the Django App
Now you will be learning how to implement the various functionalities of the e-library app.
Cloning the App
Run the code below in your command line to clone the repo containing the Django app on Github.
git clone https://github.com/Chukslord1/FAUNA_LIBRARY.git
Importing the Required Libraries
from django.shortcuts import render,redirect
from django.contrib import messages
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 Fauna Client
To initialize the Fauna client, copy the code below and replace secret_key
with the API key you created earlier.
client = FaunaClient(secret="secret_key")
The Index Page
def index(request):
return render(request,"index.html")
This page contains all the navigation for the e-library application.
The 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("user"), username)))
if hashlib.sha512(password.encode()).hexdigest() == user["data"]["password"]:
request.session["user"] = {
"id": user["ref"].id(),
"username": user["data"]["username"]
}
return redirect("App:index")
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 code above, you collected the username and password passed by the user in the POST request. You then made a query to the FQL(Fauna Query Language) client using the username as a match to check if the user exists in the database. If the user exists and the password in the database matches the one passed, you redirect the user to the index page.
The Register Functionality
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("user"), 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 code above, you collected the username, email, and password passed by the user in the POST request. You then check if the username exists in the database by making a query to the FQL client and matching it with the username. If the user does not exist, then an account is created using the fields passed.
The Add Book Functionality
def add_book(request):
if request.method=="POST":
title= request.POST.get("title")
genres=request.POST.get("genres")
summary=request.POST.get("summary")
pages=request.POST.get("pages")
copies=request.POST.get("copies")
author= request.POST.get("author")
about=request.POST.get("about")
try:
book = client.query(q.get(q.match(q.index("book_index"), title)))
messages.add_message(request, messages.INFO, 'Book Already Exists')
return redirect("App:add_book")
except:
book = client.query(q.create(q.collection("Books"), {
"data": {
"title":title,
"genres": genres,
"summary": summary,
"pages":pages,
"copies":copies,
"author":author,
"about":about,
"availability":"True"
}
}))
messages.add_message(request, messages.INFO, 'Book added Successfully')
return redirect("App:add_book")
return render(request,"add-book.html")
In the code above, you collected the title and other book details passed in the POST request. You then queried the FQL client to check if the book matching the title passed exists in the database using the book_index
index. If the book doesn’t exist, then a book is created in the Books
collection using the details.
The Search Book Functionality
def search_books(request):
if request.GET.get("search"):
try:
search=request.GET.get("search")
book= client.query(q.get(q.match(q.index("book_index"), search)))["data"]
context={"book":book}
return render(request,"search-books.html",context)
except:
messages.add_message(request, messages.INFO, 'No book found.')
return render(request,"search-books.html")
else:
try:
books= client.query(q.paginate(q.match(q.index("book_index_all"), "True")))["data"]
book_count=len(books)
page_number = int(request.GET.get('page', 1))
book = client.query(q.get(q.ref(q.collection("Books"), books[page_number-1].id())))["data"]
context={"count":book_count,"book":book, "next_page": min(book_count, page_number + 1), "prev_page": max(1, page_number - 1)}
return render(request,"search-books.html",context)
except:
messages.add_message(request, messages.INFO, 'No book found.')
return render(request,"search-books.html")
In the code above, you checked if the user passed a search in the get request. If yes, you then query the FQL client for books whose title matches the search value using the book_index
index. However, if the user didn’t create a search, you make a query to the FQL client for books whose availability matches the value “True” using the book_index_all
index. You then paginated the data retrieved using the pagination method, thus getting the data where the page number passed matches the id of the book in the database.
The Details Functionality
def detail(request,slug):
try:
book= client.query(q.get(q.match(q.index("book_index"), slug)))["data"]
context={"book":book}
return render(request,"detail.html",context)
except:
return render(request,"detail.html")
In the code above, you passed slug as an argument that the function expects to pass from the url. You then query the FQL client using the book_index
index to get the book whose title matches the slug passed. You then render the particular book to the front-end as context.
The App URLs
In the App/urls.py file, you will find the code 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("register", views.register, name="register"),
path("login", views.login, name="login"),
path("index", views.index, name="index"),
path("search-books", views.search_books, name="search_books"),
path("detail/<slug>", views.detail, name="detail"),
path("add-book", views.add_book, name="add_book"),
]
In the code above, you defined all the required urls and connected them to their respective view functions.
Conclusion
In this article, you learned how to build an electronic library 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 electronic library application is available on Github. If you have any questions, don't hesitate to contact me on Twitter: @LordChuks3.
Top comments (1)
I've been trying to get ahold of you on Twitter bro but I can't find your account