Building an E-Library with  Django and Fauna

chukslord1

Chukslord

Posted on April 22, 2021

Building an E-Library with  Django and Fauna

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

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

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

The Index Page

def index(request):
   return render(request,"index.html")
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

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

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

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

Enter fullscreen mode Exit fullscreen mode

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


]
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
chukslord1
Chukslord

Posted on April 22, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related