The app is thought as a comunity of books fans where info and opinions on books can be shared.
The user can see information about books, create new book entries, delete or update existing ones (CRUD). In the homepage the guest can search for a book or choose to consult one from the list. The user can also choose in the list by genre or author. Edit/delete option can only be performed with a password that is set when the book is added. In order to test the exisiting books, consider that password is "12"
In the book page the guest will find the basic info about the book and will be able to do the following:
- update the book info (limited to the user that created it)
- delete the book (limited to the user that created it)
- rate the book based on a star rating system (min 1 - max 5)
- leave a review
- go to the store to buy it (this functionality has not been developed)
The user can create a book. The user will be able to choose an existing genre / author or add a new one.
In the STATS page the user can see the following:
- top 10 rated books
- the most voted book
- the book that has been rated the highest today
- interactive chart of rating by author
- interactive chart of rating by genre
User is invited to give his/her contribution to the comunity whenever a request does find any result in the database. This is done through flash messages on the screen with links.
The aim is to create a big database of books but not a big collection of genres/authors not connected to any book. For this reason the option to add genre / author is only available during the submission of a new book.
All books can be edited or deleted by the user that created it (password protected).
I had to consider the possiblity that the user could try to add an entry already existing (book , genre , author). For this reason in the corresponing functions I added some code to avoid this from happening. Guest instead is redirected to the previous page and informed that the entry already exists.
Intially my functions were fed with arguments like book title and author. Crossing those info I could identify the document in the database. I, later, decided to use a more efficient approach and used id instead
When I had to manipulate results from a database search, I had issues handling cursors. In many cases I resolved the problem of not being able to loop through a cursor changing it to a list.
From the beginning I decided that I wanted the ratings to be store in a list. This would allow further manipulation of data in the Stat page. This decision forced me to add an extra function that calculates the mean rating based on the elements of the list. The numeric rating was very precise and useful for stats but I also wanted a more user friendly rapresentation. I then decided to keep the original numeric rating and add an extra function to create a stars-based rating-sistem from 1 to 5. Eventually I decided that having the timestamp associated to a rating would give many more possibilities to work with the data. I, then, decided to store rating data in the form of an array of 2-elements-arrays (rating and autogenerated timestamp). With that kind of setup I was able to identify the highest rated book on the current day.
For the Stats page I have considered different approaches. Initially I was considering using only charts rendered with DC and CROSSFILTER. I then decided that it was not really a good choiche for a project about books. I finally decided to use charts in a more discrete way.
Only a couple of charts were used. Those are visible upon user interaction.
In the Stats page I had to put front end (JS) and back end (Python) in comunication in order to send user choice and check in the DB what was the highest rated book. Initially I have tried to just call my flask method again from the front end. This was definetely not a good idea and I finally undestood I had to make Ajax requests instead. I solved the issue using Fetch API that allowed me to keep client and server side in comunication. In order to transfer the data I had to use JSON format and serialize/ parse methods in both languages. In order to deal with errors (author with no books) I have added a catch() function .
As my code was growing, I had noticed a lot of it was getting repeated . In order to achieve a DRY code, I have decided to make us of few helper functions.
- Python 3 and Flask framework
- Mongo DB Atlas
- Javascript (JQuery)
- Bootstrap 4
The testing of the Python code was done by automatic testing in the file test_app.py. During the development of my Unit Test module I encountered few problems:
Being the app based on a database, it is crucial that the app responds to data modification in the expected way.
For GET functions I was able to achieve this simply using test DB entries and a test client pointed to the correct url.
Testing POST function where the user was actually amending the content of the DB was more complex.
In this case I had to feed a test client with the content of the data posted .
After that, I could verify if the DB content was correct by a simple search.
In some cases I did not only wanted to check that the DB was correctly amended but also that the guest was informed correctly on screen.
This was achieved feeding a test client with the content of the data posted and, after, reviewing the response data.
The following problem was that data response was just showing a redirection message.
This would not allow me to check what guest could seen on screen.
The problem was solved by setting the parameter "follow_redirects" to True.
The testing of the Javascript code was done by manual testing.
I tested the "What is the highest rated book of (author)?" section. I selected "Charles Dickens" and compared the results in the chart with the data in DB.
I tested the "What is the highest rated book of genre(genre)?" section. I selected "historic novel " and compared the results in the chart with the data in DB.
I have tested manually if the stars were highlighted when mouse was entering and the opposite when mouse was leaving.
I have checked that when clicking on it, Delete or Edit button was changing colour and the passsword field was appearing.
The app was deployed on Heroku following these steps:
- A Heroku app was created
- Local Git repository was linked to Heroku
- requirements.txt file was created
- Procfile was created to tell Heroku how to run our project
- Local variables were set in Heroku (IP, PORT and SECRET_KEY)
- I started my project from Gitpod template provided by Code Institute
- Pictures used are taken from stocksnap.io (Creative Commons CC0)
- The layoout of the project is based on Bootstrap4 theme Clean Blog