diff --git a/app.py b/app.py index 1edbc3c..d261a78 100644 --- a/app.py +++ b/app.py @@ -7,6 +7,8 @@ from controllers.web.endpoints import blueprint Initialises any components that are needed at runtime such as the Database manager... ''' + + def main(): app = Flask(__name__) @@ -19,9 +21,10 @@ def main(): else: app.secret_key = secret_key - # Register a blueprint + # Register a blueprint app.register_blueprint(blueprint) app.run(debug=True, host="0.0.0.0") - + + if __name__ == "__main__": main() diff --git a/controllers/database/category.py b/controllers/database/category.py index 3c183e8..eeb8525 100644 --- a/controllers/database/category.py +++ b/controllers/database/category.py @@ -1,6 +1,7 @@ from .database import DatabaseController from models.category import Category + class CategoryController(DatabaseController): FIELDS = ['id', 'name'] @@ -18,7 +19,6 @@ class CategoryController(DatabaseController): ) self._conn.commit() - def read(self, id: int = 0) -> Category | None: params = [ id @@ -32,12 +32,11 @@ class CategoryController(DatabaseController): if row == None: return None - + params = dict(zip(self.FIELDS, row)) obj = self.new_instance(Category, params) - - return obj + return obj def read_all(self) -> list[Category] | None: cursor = self._conn.execute( @@ -47,18 +46,18 @@ class CategoryController(DatabaseController): if rows == None: return None - + categories = list() for category in rows: params = dict(zip(self.FIELDS, category)) obj = self.new_instance(Category, params) categories.append(obj) - + return categories def update(self): print("Doing work") - + def delete(self): - print("Doing work") \ No newline at end of file + print("Doing work") diff --git a/controllers/database/database.py b/controllers/database/database.py index 0772f72..aa873e0 100644 --- a/controllers/database/database.py +++ b/controllers/database/database.py @@ -1,8 +1,9 @@ from abc import ABC, abstractmethod -from typing import Mapping, Any +from typing import Mapping, Any import sqlite3 import os + class DatabaseController(ABC): __data_dir = "./data/" __db_name = "wmgzon.db" @@ -13,13 +14,13 @@ class DatabaseController(ABC): __sqlitefile = __data_dir + __db_name - def __init__(self): self._conn = None try: # Creates a connection and specifies a flag to parse all types back down into # Python declared types e.g. date & time - self._conn = sqlite3.connect(self.__sqlitefile, detect_types=sqlite3.PARSE_DECLTYPES) + self._conn = sqlite3.connect( + self.__sqlitefile, detect_types=sqlite3.PARSE_DECLTYPES) except sqlite3.Error as e: # Close the connection if still open if self._conn: @@ -27,17 +28,18 @@ class DatabaseController(ABC): print(e) def __del__(self): - if self._conn != None: + if self._conn != None: self._conn.close() """ Takes a dictionary of fields and returns the object with those fields populated """ + def new_instance(self, of: type, with_fields: Mapping[str, Any]): obj = of.__new__(of) for attr, value in with_fields.items(): setattr(obj, attr, value) return obj - + """ Set of CRUD methods to allow for Data manipulation on the backend """ @@ -53,7 +55,7 @@ class DatabaseController(ABC): @abstractmethod def update(self): pass - + @abstractmethod def delete(self): - pass \ No newline at end of file + pass diff --git a/controllers/database/product.py b/controllers/database/product.py index 08943d0..96bbdc3 100644 --- a/controllers/database/product.py +++ b/controllers/database/product.py @@ -1,8 +1,10 @@ from .database import DatabaseController from models.products.product import Product + class ProductController(DatabaseController): - FIELDS = ['id', 'name', 'image', 'description', 'cost', 'category', 'sellerID', 'postedDate', 'quantityAvailable'] + FIELDS = ['id', 'name', 'image', 'description', 'cost', + 'category', 'sellerID', 'postedDate', 'quantityAvailable'] def __init__(self): super().__init__() @@ -25,7 +27,6 @@ class ProductController(DatabaseController): ) self._conn.commit() - def read(self, name: str = "") -> list[Product] | None: params = [ "%" + name + "%" @@ -39,24 +40,23 @@ class ProductController(DatabaseController): if rows == None: return None - + products = list() - + # Create an object for each row for product in rows: params = dict(zip(self.FIELDS, product)) obj = self.new_instance(Product, params) products.append(obj) - - return products + return products def read_all(self, category: str = "", search_term: str = "") -> list[Product] | None: params = [ "%" + category + "%", "%" + search_term + "%" ] - + cursor = self._conn.execute( """SELECT * FROM Products INNER JOIN Categories ON Products.categoryID = Categories.id @@ -69,19 +69,19 @@ class ProductController(DatabaseController): if len(rows) == 0: return None - + products = list() - + # Create an object for each row for product in rows: params = dict(zip(self.FIELDS, product)) obj = self.new_instance(Product, params) products.append(obj) - + return products def update(self): print("Doing work") - + def delete(self): - print("Doing work") \ No newline at end of file + print("Doing work") diff --git a/controllers/database/user.py b/controllers/database/user.py index 9a8ad09..8534a48 100644 --- a/controllers/database/user.py +++ b/controllers/database/user.py @@ -3,8 +3,10 @@ from models.users.user import User from models.users.customer import Customer from models.users.seller import Seller + class UserController(DatabaseController): - FIELDS = ['id', 'username', 'password', 'firstName', 'lastName', 'email', 'phone', 'role'] + FIELDS = ['id', 'username', 'password', 'firstName', + 'lastName', 'email', 'phone', 'role'] def __init__(self): super().__init__() @@ -44,13 +46,12 @@ class UserController(DatabaseController): type = Customer if row[7] == "Seller": type = Seller - + obj = self.new_instance(type, params) return obj - + return None - def read_id(self, id: int) -> User | None: params = [ id @@ -69,13 +70,14 @@ class UserController(DatabaseController): type = Customer if row[7] == "Seller": type = Seller - + obj = self.new_instance(type, params) return obj - + return None + def update(self): print("Doing work") - + def delete(self): - print("Doing work") \ No newline at end of file + print("Doing work") diff --git a/controllers/web/endpoints.py b/controllers/web/endpoints.py index 4171df3..533b3d7 100644 --- a/controllers/web/endpoints.py +++ b/controllers/web/endpoints.py @@ -16,15 +16,15 @@ blueprint.register_blueprint(product.blueprint) # Function that returns a given user class based on the ID in the session @blueprint.context_processor -def get_user() -> dict[User|None]: +def get_user() -> dict[User | None]: # Get the user based on the user ID user_id = session.get('user_id') user = None - + if user_id != None: db = UserController() user = db.read_id(user_id) - + return dict(user=user) diff --git a/controllers/web/product.py b/controllers/web/product.py index c68ea69..2c4e317 100644 --- a/controllers/web/product.py +++ b/controllers/web/product.py @@ -15,14 +15,18 @@ import pathlib ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'} + def allowed_file(filename): - return '.' in filename and \ - filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS + return '.' in filename and \ + filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS + blueprint = Blueprint("products", __name__, url_prefix="/products") -# Global context to enable the categories to be accessed +# Global context to enable the categories to be accessed # from any view + + @blueprint.context_processor def category_list(): database = CategoryController() @@ -30,6 +34,8 @@ def category_list(): return dict(categories=categories) # Loads the front product page + + @blueprint.route('/') def index(): database = ProductController() @@ -38,14 +44,16 @@ def index(): # No Products visible if products == None: flash("No Products available") - - return render_template('index.html', content="content.html", products = products) + + return render_template('index.html', content="content.html", products=products) # Loads a given product category page + + @blueprint.route('/') def category(category: str): database = ProductController() - + # Check to see if there is a custome search term search_term = request.args.get("search", type=str) if search_term != None: @@ -57,10 +65,12 @@ def category(category: str): # No Products visible if products == None: flash(f"No Products available in {category}") - - return render_template('index.html', content="content.html", products = products, category = category) + + return render_template('index.html', content="content.html", products=products, category=category) # Loads a given product based on ID + + @blueprint.route('/') def id(id: int): return "ID: " + str(id) @@ -70,12 +80,12 @@ def id(id: int): @blueprint.route('/add') def display_add_product(): user_id = session.get('user_id') - + # User must be logged in to view this page if user_id == None: flash("Please Login to view this page") return redirect('/login') - + db = UserController() user = db.read_id(user_id) if user == None or user.role != "Seller": @@ -89,12 +99,12 @@ def display_add_product(): @blueprint.post('/add') def add_product(): user_id = session.get('user_id') - + # User must be logged in to view this page if user_id == None: flash("Please Login to view this page") return redirect('/login', code=302) - + db = UserController() user = db.read_id(user_id) if user == None or user.role != "Seller": @@ -102,7 +112,7 @@ def add_product(): return redirect('/', code=302) file = request.files.get('image') - + # Ensure that the correct file type is uploaded if file == None or not allowed_file(file.filename): flash("Invalid File Uploaded") diff --git a/controllers/web/user.py b/controllers/web/user.py index de5cd43..721e7a3 100644 --- a/controllers/web/user.py +++ b/controllers/web/user.py @@ -10,13 +10,17 @@ from hashlib import sha512 # Blueprint to append user endpoints to blueprint = Blueprint("users", __name__) -### LOGIN FUNCTIONALITY +# LOGIN FUNCTIONALITY # Function responsible for delivering the Login page for the site + + @blueprint.route('/login') def display_login(): return render_template('index.html', content="login.html") # Function responsible for handling logins to the site + + @blueprint.post('/login') def login(): database = UserController() @@ -28,7 +32,7 @@ def login(): error = "No user found with the username " + request.form['username'] flash(error) return redirect("/login") - + # Incorrect Password if sha512(request.form['password'].encode()).hexdigest() != user.password: error = "Incorrect Password" @@ -39,13 +43,15 @@ def login(): return redirect("/") -### SIGNUP FUNCTIONALITY +# SIGNUP FUNCTIONALITY # Function responsible for delivering the Signup page for the site @blueprint.route('/signup') def display_signup(): return render_template('index.html', content="signup.html") # Function responsible for handling signups to the site + + @blueprint.post('/signup') def signup(): database = UserController() @@ -60,7 +66,8 @@ def signup(): if request.form.get('seller'): user = Seller( request.form['username'], - sha512(request.form['password'].encode()).hexdigest(), # Hashed as soon as it is recieved on the backend + # Hashed as soon as it is recieved on the backend + sha512(request.form['password'].encode()).hexdigest(), request.form['firstname'], request.form['lastname'], request.form['email'], @@ -69,23 +76,24 @@ def signup(): else: user = Customer( request.form['username'], - sha512(request.form['password'].encode()).hexdigest(), # Hashed as soon as it is recieved on the backend + # Hashed as soon as it is recieved on the backend + sha512(request.form['password'].encode()).hexdigest(), request.form['firstname'], request.form['lastname'], request.form['email'], "123" ) - + database.create(user) # Code 307 Preserves the original request (POST) return redirect("/login", code=307) -### SIGN OUT FUNCTIONALITY +# SIGN OUT FUNCTIONALITY # Function responsible for handling logouts from the site @blueprint.route('/logout') def logout(): # Clear the current user from the session if they are logged in session.pop('user_id', None) - return redirect("/") \ No newline at end of file + return redirect("/") diff --git a/models/category.py b/models/category.py index 91c0360..67e0820 100644 --- a/models/category.py +++ b/models/category.py @@ -2,6 +2,7 @@ class Category: ''' Constructor for a category object ''' + def __init__(self): self.id = 0 - self.name = "" \ No newline at end of file + self.name = "" diff --git a/models/order.py b/models/order.py index 3abc6cb..d5b6486 100644 --- a/models/order.py +++ b/models/order.py @@ -1,13 +1,15 @@ from datetime import datetime + class Order: ''' Constructor for an order object ''' + def __init__(self): self.id = 0 self.sellerID = 0 self.customerID = 0 self.products = list() self.totalCost = 0.0 - self.orderDate = datetime.now() \ No newline at end of file + self.orderDate = datetime.now() diff --git a/models/products/carpart.py b/models/products/carpart.py index 3603f82..75b652a 100644 --- a/models/products/carpart.py +++ b/models/products/carpart.py @@ -1,13 +1,14 @@ from product import Product + class CarPart(Product): ''' Constructor for a car part Contains additional information that is only relevant for car parts ''' + def __init__(self): super().__init__() self.make = "" self.compatibleVehicles = list() - \ No newline at end of file diff --git a/models/products/product.py b/models/products/product.py index ed65af6..6229182 100644 --- a/models/products/product.py +++ b/models/products/product.py @@ -1,9 +1,11 @@ from datetime import datetime + class Product: ''' Base class for a product ''' + def __init__(self): self.id = 0 self.name = "" @@ -20,7 +22,8 @@ class Product: No additional properties are assigned to the customer ''' - def __init__(self, name: str, image: str, description: str, cost: float, category: int, + + def __init__(self, name: str, image: str, description: str, cost: float, category: int, seller_id: int, posted_date: datetime, quantity_available: int): self.id = 0 self.name = name @@ -31,4 +34,3 @@ class Product: self.sellerID = seller_id self.postedDate = posted_date self.quantityAvailable = quantity_available - \ No newline at end of file diff --git a/models/users/admin.py b/models/users/admin.py index 946a53e..08a9715 100644 --- a/models/users/admin.py +++ b/models/users/admin.py @@ -1,12 +1,14 @@ from .user import User + class Admin(User): ''' Class constructor to instatiate an admin object No additional properties are assigned to the admin ''' - def __init__(self, username: str, password: str, firstname: str, + + def __init__(self, username: str, password: str, firstname: str, lastname: str, email: str, phone: str): super().__init__( username, password, firstname, lastname, email, phone, "Admin" diff --git a/models/users/customer.py b/models/users/customer.py index 0c571ad..786090d 100644 --- a/models/users/customer.py +++ b/models/users/customer.py @@ -1,14 +1,15 @@ from .user import User + class Customer(User): ''' Class constructor to instatiate a customer object No additional properties are assigned to the customer ''' - def __init__(self, username: str, password: str, firstname: str, + + def __init__(self, username: str, password: str, firstname: str, lastname: str, email: str, phone: str): super().__init__( username, password, firstname, lastname, email, phone, "Customer" ) - diff --git a/models/users/seller.py b/models/users/seller.py index 5b1ca80..5a3622a 100644 --- a/models/users/seller.py +++ b/models/users/seller.py @@ -1,12 +1,14 @@ from .user import User + class Seller(User): ''' Class constructor to instatiate a customer object No additional properties are assigned to the customer ''' - def __init__(self, username: str, password: str, firstname: str, + + def __init__(self, username: str, password: str, firstname: str, lastname: str, email: str, phone: str): super().__init__( username, password, firstname, lastname, email, phone, "Seller" diff --git a/models/users/user.py b/models/users/user.py index 95d3206..146bed7 100644 --- a/models/users/user.py +++ b/models/users/user.py @@ -1,9 +1,11 @@ from abc import ABC + class User(ABC): """ Functional Class constructor to initialise all properties in the base object with a value """ - def __init__(self, username: str, password: str, firstname: str, + + def __init__(self, username: str, password: str, firstname: str, lastname: str, email: str, phone: str, role: str): self.id = 0 self.username = username @@ -12,4 +14,4 @@ class User(ABC): self.lastName = lastname self.email = email self.phone = phone - self.role= role + self.role = role diff --git a/scripts/create_database.py b/scripts/create_database.py index 442c5d5..ba3e11e 100644 --- a/scripts/create_database.py +++ b/scripts/create_database.py @@ -14,14 +14,14 @@ def create_connection(path: str, filename: str): print("Database file open") # Execute creation scripts - sql = open("scripts/create_tables.sql", "r"); + sql = open("scripts/create_tables.sql", "r") conn.executescript(sql.read()) print("Table creation complete") # Populate with test data if we are in Test Mode if os.environ.get("ENVIRON") == "test": - sql = open("scripts/test_data.sql", "r"); + sql = open("scripts/test_data.sql", "r") conn.executescript(sql.read()) except sqlite3.Error as e: @@ -31,12 +31,15 @@ def create_connection(path: str, filename: str): conn.close() # Ensure a directory is created given a path to it + + def create_directory(dir: str): try: os.makedirs(dir) except FileExistsError: pass + def remove_file(dir: str): try: os.remove(dir) @@ -44,7 +47,6 @@ def remove_file(dir: str): pass - dir = r"./data/" db_name = r"wmgzon.db" diff --git a/tests/__init__.py b/tests/__init__.py index 8b13789..e69de29 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1 +0,0 @@ - diff --git a/tests/database/__init__.py b/tests/database/__init__.py index 3c3cf69..19487b8 100644 --- a/tests/database/__init__.py +++ b/tests/database/__init__.py @@ -1,8 +1,8 @@ # Ensure test environment is set before using +import scripts.create_database import os # Setup test environment variables os.environ["ENVIRON"] = "test" # Runs the database creation scripts -import scripts.create_database \ No newline at end of file diff --git a/tests/database/test_products.py b/tests/database/test_products.py index a805966..79482cd 100644 --- a/tests/database/test_products.py +++ b/tests/database/test_products.py @@ -5,22 +5,26 @@ from controllers.database.product import ProductController from models.products.product import Product product = Product( - "product", - "image.png", - "description", - 10.00, - 1, - 1, - datetime.now(), - 1 + "product", + "image.png", + "description", + 10.00, + 1, + 1, + datetime.now(), + 1 ) # Tests a new product can be created + + def test_create_product(): db = ProductController() db.create(product) # Tests the database maintains integrity when we try and add a product with the same details + + @pytest.mark.skip def test_duplicate_product(): db = ProductController() @@ -28,11 +32,13 @@ def test_duplicate_product(): db.create(product) # Tests that products can be refined by category + + def test_search_category(): db = ProductController() - # Check each category for correct amount of test products - assert len(db.read_all("Car Parts")) == 9 + 1 # Added in previous test + # Check each category for correct amount of test products + assert len(db.read_all("Car Parts")) == 9 + 1 # Added in previous test assert len(db.read_all("Books")) == 9 assert db.read_all("Phones") == None @@ -48,6 +54,8 @@ def test_search_term(): assert db.read_all(search_term="not_test") == None # Test we the same product details get returned from the database + + def test_read_product(): db = ProductController() diff --git a/tests/database/test_users.py b/tests/database/test_users.py index f0cfae7..1d3ac98 100644 --- a/tests/database/test_users.py +++ b/tests/database/test_users.py @@ -5,35 +5,41 @@ from models.users.customer import Customer from models.users.seller import Seller customer = Customer( - "testcustomer", - "Password1", - "firstname", - "lastname", - "test@test", - "123456789" + "testcustomer", + "Password1", + "firstname", + "lastname", + "test@test", + "123456789" ) seller = Seller( - "testseller", - "Password1", - "firstname", - "lastname", - "seller@seller", - "987654321" + "testseller", + "Password1", + "firstname", + "lastname", + "seller@seller", + "987654321" ) # Tests a new user can be created + + def test_create_user(): db = UserController() db.create(customer) # Tests the database maintains integrity when we try and add a user with the same details + + def test_duplicate_user(): db = UserController() with pytest.raises(sqlite3.IntegrityError): db.create(customer) # Test we the same user details get returned from the database + + def test_read_user(): db = UserController() @@ -52,6 +58,8 @@ def test_create_seller(): db.create(seller) # Test that the same seller details get returned from the database + + def test_read_seller(): db = UserController() diff --git a/tests/general/test_pep8.py b/tests/general/test_pep8.py index 495618c..fa2c8ea 100644 --- a/tests/general/test_pep8.py +++ b/tests/general/test_pep8.py @@ -1,9 +1,11 @@ import pycodestyle # Tests files to ensure they conform to pep8 standards + + def test_pep8_conformance(): """Test that we conform to PEP8.""" pep8style = pycodestyle.StyleGuide() dirs = ["./controllers", "./models", "./scripts", "./tests"] result = pep8style.check_files(dirs) - assert result.total_errors == 0 \ No newline at end of file + assert result.total_errors == 0