Skip to content
Permalink
468d14b9a3
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
413 lines (320 sloc) 18.6 KB
import os #Used to allow the application to save files locally
from flask import Flask #Used to create app
from flask_sqlalchemy import SQLAlchemy #Used to interface with database.
from flask import render_template #Used to display html templates
from flask import request #Used to get information from the webpage for me to work on with python
from flask import url_for # Used to help redirect to certain pages and functions
from flask import session #Used to create a session cookie that is used to store user information, cart etc
from flask import redirect #Used to redirect to certain webpages
from werkzeug.utils import secure_filename #Used to make sure no malicious filenames are used when admin uploads bookcover images
import datetime #Used to get date from form and turn it into datetime object to be stored in database
from markupsafe import escape #escape() function used to make sure no malicious code is injected into the app via user input fields.
#import sqlite3
app = Flask(__name__) #Creates instance of flask class
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///library.db' #Tells SQL ALCHEMY what datbase to use.
app.secret_key = 'gZeusQBaEuNcCM6ZHKCAhKeWWynsP6fVmrddzpfYx1E' #Secret key used to protect session cookies from being affected by user.
db = SQLAlchemy(app) #Creates sql alchemy database instance
"""Merges two arrays together, 5001CEM 2122 GIVEN CODE
Arguments:
first_array (list, dict or set)
second_array (list, dict or set)
Returns:
if arguments are valid: array, dict or set
False(bool)
"""
#Code used from shopping cart activity from AULA (5001CEM 2122 GIVEN CODE)
def array_merge(first_array, second_array):
if isinstance(first_array, list) and isinstance(second_array, list):
return first_array + second_array
elif isinstance(first_array, dict) and isinstance(second_array, dict):
return dict(list(first_array.items()) + list(second_array.items()))
elif isinstance(first_array, set) and isinstance(second_array, set):
return first_array.union(second_array)
return False
#Used to create database for all of the books.
class Books(db.Model):
#Each class variable is a column in the table. Data types and constraints are specified within these variables
name = db.Column(db.Text, nullable=False)
author = db.Column(db.Text, nullable=False)
pubdate = db.Column(db.Date, nullable=False)
isbn = db.Column(db.Integer, primary_key=True)
description = db.Column(db.Text)
cover = db.Column(db.Text, nullable=False)
tradeprice = db.Column(db.Float, nullable=False)
retailprice = db.Column(db.Float, nullable=False)
quantity = db.Column(db.Integer, nullable=False)
def __repr__(self): #Allows us to determine how an object from the datbase looks when I print it out.
return f"Book('{self.name}', '{self.author}', '{self.pubdate}', '{self.isbn}', '{self.isbn}')"
#Basic user class used to store 3 test users for this application.
class user:
def __init__(self, username, password):
self.username = username
self.password = password
users = [] #Users list used to store 3 test users.
users.append(user("customer1", "p455w0rd"))# Adds three test users into the users list.
users.append(user("customer2", "p455w0rd"))
users.append(user("admin", "p455w0rd"))
@app.route('/')
def index():
"""Redirects user to login page upon first visit
Arguments:
None
Returns:
if no exception: redirection to login page
"""
try:
return redirect(url_for('login'))
except Exception as e:
print(e)
@app.route('/login', methods=['GET', 'POST']) #Allows me to map URL to specific functions.
def login():
"""Checks if user details are valid. If details are valid, they are redirected to the homepage, if not, they are redirected back to login page
Arguments:
None
Returns:
if no exception: Redirection to homepage or login page.
"""
try:
if request.method == 'POST': #Once the submit button is pressed on the login page, it sends a post request, after which the code underneath is executed.
username = escape(request.form['uname']) # Gets username from form
password = escape(request.form['pword']) # Gets password from from
for i in users: #Goes through all users.
if i.username == username and i.password == password: #Gets the stored username and password for each user and checks if both inputted values are valid.
session['username'] = i.username #If details are valid, I store the username in the session cookie for me to use later.
return redirect(url_for('homepage')) # After validation is done, user is redirected to the homepage where they will be able to use the website.
return render_template('login.html', page=url_for('login')) #Renders the login.html file in the event the user first visits the webiste or the user's password and username arent valid.
except Exception as e:
print(e)
@app.route('/homepage', methods=['GET', 'POST'])
def homepage():
"""routes to homepage
Arguments:
None
Returns:
if no exception: redirection to homepage or login page.
"""
try:
if 'username' in session: #If I want to make sure somepages can only be accessed if the user is logged in, I will use this if statement to see if the username has been stored in the session as this indicates that the user has successfully logged in.
return render_template('homepage.html', stocklevels=url_for('stocklevels'), user=session['username'],
books=Books.query.all(), add=url_for('addtocart'), cart=url_for('cart'),
logout=url_for('logout')) #Renders the homepage and also passes in links to the other pages with the 'url_for()' function for use in the nav bar so the user can get around the website.
return redirect(url_for('login'))#If the username is not in the session cookie (user isnt logged in), they will be redirected to the login page.
except Exception as e:
print(e)
@app.route('/stocklevels', methods=['GET', 'POST'])
def stocklevels():
"""Routes stock levels page
Arguments:
None
Returns:
if no exception: redirection to add stocklevels page or homepage
"""
try:
if 'username' in session and session['username'] == "admin": #This performs one more check, it checks if the specific user logged in is the admin, as I only want the admin to be able to view the stock levels.
return render_template('stocklevels.html', addstock=url_for('addstock'), books=Books.query.all(), homepage=url_for('homepage'))
return redirect(url_for('homepage')) #User is redirected to homepage from which they are redirected to the login page if they are not logged in, this is used throughout the code to make sure that the user can't access restricted pages.
except Exception as e:
print(e)
app.config["IMAGE_UPLOADS"] = "/home/codio/workspace/flaskproject/static/uploads" #Configures the path I want to save the uploaded bookcovers to.
@app.route("/addstock", methods=['GET', 'POST'])
def addstock():
"""Routes to add stock page.
Takes in data from add stock form and saves stock to database and saves book thumbnail images to local folder
Arguments:
None
Returns:
if no exception: redirection to add stock page or homepage
"""
try:
if 'username' in session and session['username'] == 'admin':
if request.method == "POST":
name = escape(request.form["name"]) #Gets book name from form.
author = escape(request.form["author"]) #Gets book author from form.
pubdate = datetime.datetime.strptime(request.form["pubdate"], '%Y-%m-%d') #Gets date from form and turns it into a datetime object as SQLAlchemy requires.
isbn = escape(request.form["isbn"]) #Gets isbn 13 number from form.
image = request.files["img"] #Gets the image file object from the form.
description = escape(request.form["description"]) #Gets the description.
retailprice = request.form["retailprice"] #Gets the retail price.
quantity = request.form["quantity"] #Gets the quantity of books to be put into stock.
tradeprice = request.form["tradeprice"] #Gets the trade price.
filename = secure_filename(image.filename) #Before I store the book cover image, I make sure that the filename wont be malicious by using the secure_filename() function.
path = os.path.join(app.config["IMAGE_UPLOADS"], filename) #Defines path for image to be stored in with the filename.
image.save(path) #Saves image in the specified file in the path ('uploads' in the static folder)
book = Books(name=name, author=author, pubdate=pubdate, isbn=isbn, description=description, cover=filename,
tradeprice=tradeprice, retailprice=retailprice, quantity=quantity) #Creates a books object to be stored in the database as a record. All variables passed in are variables retrieved from the form.
db.session.merge(book) #I use the merge function here so that when a book that already exists in the database is added, it simply updates the existing record. And if the book does not exist in the database, a new record is made.
db.session.commit() #Commits changes to the datbase
return render_template('add_stock.html', page=url_for('addstock'), stocklevels=url_for('stocklevels')) #Renders add_stock page.
return redirect(url_for('homepage'))
except Exception as e:
print(e)
@app.route('/logout')
def logout():
"""
Clears the session hence logging the user out. Redirects to login page after logout is completed
Arguments:
None
Return:
if no exception: redirection to login page.
"""
try:
session.clear() #Clears the session cookie so that username and cart data is wiped.
return redirect(url_for('login')) #After clearing the session cookie, user is redirected to the login page again.
except Exception as e:
print(e)
@app.route('/addtocart', methods=["GET", "POST"])
def addtocart():
"""Adds item to shopping cart
Arguments:
None
Returns:
if no exception: redirection to homepage.
"""
try:
if 'username' in session:
isbn = request.form["isbn"] #Gets the isbn number from the book that the user just added to cart.
book = Books.query.get(isbn) #Querys the database to retrieve record with the isbn of the book that the user just added.
quantity = int(request.form["quantity"]) #Gets the quantity of the book that the user added to cart.
if isbn and quantity and request.method == "POST": #isbn and quantity have been retrieved, and the add to cart button has been pressed, code below is executed.
products = {isbn: {'name': book.name, 'price': book.retailprice, 'quantity': quantity, 'cover': book.cover,
'stockstatus': "In Stock"}} #Creates a dictionary object that stores information that is to be displayed in the cart later on.
if 'cart' in session: #If there is a cart in the session, code below is executed.
if isbn in session['cart']: #Checks if the book is already in the cart
item = session['cart'].get(isbn, None) #Gets the dictionary object for the book from the cart.
item["quantity"] += quantity #Adds on the additional quantity that the user specified.
session.modified = True #Indicates that the session cookie has changed, hence it needs to be updated.
else:
session['cart'] = array_merge(session['cart'], products) #If the item is not in the cart, the book is added to the cart using the array_merge function.
else:
session['cart'] = products #Otherwise, if the cart does not exist, it is created with the first book added into the cart by the user.
return redirect(url_for('homepage')) #After the product has been added, the user is redirected to the homepage.
return redirect(url_for('homepage'))
except Exception as e:
print(e)
@app.route('/cart', methods=["GET", "POST"])
def cart():
"""Routes to cart page
Arguments:
None
Returns:
if no exception: redirection to cart or homepage.
"""
try:
if 'username' and 'cart' in session and len(session['cart']) > 0: #Checks if the cart is in session, user is logged in and if there is nothing in the cart.
return render_template('cart.html', homepage=url_for('homepage'), payment=url_for('completeorder'), cancel=url_for('cancel'), total=total()) #Renders html template for the cart.
return redirect(url_for('homepage'))
except Exception as e:
print(e)
@app.route('/cancel')
def cancel():
"""
Cancels order
Arguments:
None
Returns:
if no exception: redirection to homepage
"""
try:
session.pop('cart', None) #Gets rid of the shopping cart, wiping all books stored in it.
return redirect(url_for('homepage'))
except Exception as e:
print(e)
@app.route('/delete/<int:isbn>')
def delete(isbn):
"""
Deletes item from cart specified by isbn
Arguments:
isbn (int): Used to identify each book in database uniquely
Returns:
if no exception: redirection to cart page.
"""
try:
cart = session['cart'].copy() #Creates a copy of the shopping cart for me to iterate over.
session.modified = True
for key, book in cart.items(): #Goes through each book stored in the cart.
if int(key) == isbn:
session['cart'].pop(key, None) #If the isbn passed into the function matches what is stored in the cart (book is found), the book is removed from the cart.
return redirect(url_for('cart')) #Redirects back to the cart after deletion is completed.
except Exception as e:
print(e)
@app.route('/completeorder', methods=['GET', 'POST'])
def completeorder():
"""Routes to complete order screen
Arguments:
None
Returns:
if no exception: redirected to complete order page or homepage
"""
try:
if 'cart' and 'username' in session:
cart = session["cart"].copy()
deduction = 0 #Deduction value is used to determine how much to take off the total cost after determining which book that the user wishes to purchase is out of stock.
for key, book in cart.items():
stockquantity = Books.query.get(key).quantity #For each book in the cart, the database is queried to get the amount in stock for each book.
if stockquantity < book['quantity']: #If the stock quantity is less than the quantity that the user wants to buy the code below is executed.
book["stockstatus"] = "Out of Stock" #If there is not sufficient stock, the stock status is set to 'out of stock' to be displayed to the user.
deduction += book['price'] * book['quantity'] #The price of this book multiplied by the total quantity requested is set as the deduction.
session.modified = True
return render_template('completeorder.html', totalcost=total() - deduction, postage=postage(), payment=url_for('payment')) #the deduction is taken away from the total cost so that the user isnt charged for the books that are out of stock.
return redirect(url_for('homepage'))
except Exception as e:
print(e)
@app.route('/payment', methods=['GET', 'POST'])
def payment():
"""
Routes to payment screen
Arguments:
None
Returns:
if no exception: redirects to payment page or homepage
"""
try:
if 'cart' and 'username' in session:
cart = session['cart'].copy()
for key, book in cart.items():
if book["stockstatus"] == "Out of Stock": #Checks if the book is out of stock
session['cart'].pop(key, None) #Gets rid of the book from the cart.
session.modified = True
if request.method == 'POST': #If the 'pay now' button is clicked, the code below is executed.
for key, book in cart.items():
stock = Books.query.get(key) #Gets the book record in the database for each book in the cart.
stock.quantity -= book["quantity"] #Reduces the stock quantity in the database by however many books of the same type that the user as bought
db.session.commit() #Changes are committed to the database.
session.pop('cart', None) #Cart is cleared after payment.
return redirect(url_for('homepage')) #User is redirected to homepage after payment.
return render_template('payment.html', totalcost=total(), postage=postage(), total=total() + postage(),
cart=url_for('cart')) #Renders payment page template.
return redirect(url_for('homepage'))
except Exception as e:
print(e)
def total(): #Used to calculate total value of books in cart when needed.
"""
Calculates total of all items in cart
Returns:
total(float)
"""
total = 0 #Total value initialised to 0
cart = session['cart'].copy()
for key, book in cart.items():
total = total + (book["price"] * float(book["quantity"])) #For each book in the cart, the price is multiplied by the quantity bought and added onto the total.
return total
def postage():
"""
Calculates postage cost
Arguments:
None
Returns:
postage(float)
"""
postage = 0 #Postage cost is initialised to 0.
orderquantity = 0 #Total quantity of books is initialised to 0.
cart = session["cart"].copy()
for key, book in cart.items():
if book["stockstatus"] == "In Stock": #First checks if the book is in stock.
orderquantity += book["quantity"] #For each book in the cart, the quantity is retrived and added onto the order quantity to get the total quantity of books being bought.
for i in range(orderquantity): #Loop is performed for each book that the user is trying to buy.
if i == 0:
postage += 3.0 #If it is the first book, £3.00 of postage cost is added on.
else:
postage += 1.0 #For the rest of the books, £1.00 of postage cost is added on.
return postage