Updated line endings to be in line with UNIX style

This commit is contained in:
Luke Else 2024-01-28 11:59:04 +00:00
parent 3eea1d946a
commit 5a20a8d7c0
46 changed files with 2536 additions and 2536 deletions

578
.gitignore vendored
View File

@ -1,289 +1,289 @@
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,python,flask,web
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,python,flask,web
### SQLite 3 Database ###
data/
static/assets/img/products/
### CICD Registration files ###
cicd/runner-data
### Flask ###
instance/*
!instance/.gitignore
.webassets-cache
.env
### Flask.Python Stack ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
### Python ###
# Byte-compiled / optimized / DLL files
# C extensions
# Distribution / packaging
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
# Installer logs
# Unit test / coverage reports
# Translations
# Django stuff:
# Flask stuff:
# Scrapy stuff:
# Sphinx documentation
# PyBuilder
# Jupyter Notebook
# IPython
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
# Celery stuff
# SageMath parsed files
# Environments
# Spyder project settings
# Rope project settings
# mkdocs documentation
# mypy
# Pyre type checker
# pytype static type analyzer
# Cython debug symbols
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
### Python Patch ###
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
poetry.toml
# ruff
.ruff_cache/
# LSP config files
pyrightconfig.json
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,python,flask,web
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,python,flask,web
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,python,flask,web
### SQLite 3 Database ###
data/
static/assets/img/products/
### CICD Registration files ###
cicd/runner-data
### Flask ###
instance/*
!instance/.gitignore
.webassets-cache
.env
### Flask.Python Stack ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
### Python ###
# Byte-compiled / optimized / DLL files
# C extensions
# Distribution / packaging
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
# Installer logs
# Unit test / coverage reports
# Translations
# Django stuff:
# Flask stuff:
# Scrapy stuff:
# Sphinx documentation
# PyBuilder
# Jupyter Notebook
# IPython
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
# Celery stuff
# SageMath parsed files
# Environments
# Spyder project settings
# Rope project settings
# mkdocs documentation
# mypy
# Pyre type checker
# pytype static type analyzer
# Cython debug symbols
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
### Python Patch ###
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
poetry.toml
# ruff
.ruff_cache/
# LSP config files
pyrightconfig.json
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,python,flask,web

View File

@ -1,11 +1,11 @@
variables:
ENVIRON: "test"
pytest:
stage: test
script:
- cd /builds/u5500327/wmgzon
# - python -m venv .venv
# - source /bin/activate
- pip install -r requirements.txt
variables:
ENVIRON: "test"
pytest:
stage: test
script:
- cd /builds/u5500327/wmgzon
# - python -m venv .venv
# - source /bin/activate
- pip install -r requirements.txt
- pytest --disable-warnings

View File

@ -1,43 +1,43 @@
---
runme:
id: 01HK0BF4BTBSKR9VWAP1KGD2S7
version: v2.0
---
# WMGZON
`Flask web application serving WMGZON and its relevant backend services.`
## Initialisation
To start you need to create a virtual environment and load in the required dependencies for the project
```sh {"closeTerminalOnSuccess":"false","id":"01HK0BJCK9BR05J127F1X0RZP9"}
python -m venv .venv
./.venv/Scripts/Activate.ps1
pip install -r requirements.txt
```
## Testing
To run the full suite of unit tests for the webapp simply run the following command in the venv
```sh
pytest
```
## Running
### Pre-Requisites
- Docker daemon is installed and running.
- Docker compose is installed.
### Instructions
In order to run the web app, simply use the command
```sh {"id":"01HKD0VRADDYQ92W22JN1FHA03"}
docker-compose up -d
```
---
runme:
id: 01HK0BF4BTBSKR9VWAP1KGD2S7
version: v2.0
---
# WMGZON
`Flask web application serving WMGZON and its relevant backend services.`
## Initialisation
To start you need to create a virtual environment and load in the required dependencies for the project
```sh {"closeTerminalOnSuccess":"false","id":"01HK0BJCK9BR05J127F1X0RZP9"}
python -m venv .venv
./.venv/Scripts/Activate.ps1
pip install -r requirements.txt
```
## Testing
To run the full suite of unit tests for the webapp simply run the following command in the venv
```sh
pytest
```
## Running
### Pre-Requisites
- Docker daemon is installed and running.
- Docker compose is installed.
### Instructions
In order to run the web app, simply use the command
```sh {"id":"01HKD0VRADDYQ92W22JN1FHA03"}
docker-compose up -d
```
to run the container in a detatched mode.

54
app.py
View File

@ -1,27 +1,27 @@
from flask import Flask
from os import environ
from controllers.web.endpoints import blueprint
'''
Main entrypoint for Flask application.
Initialises any components that are needed at runtime such as the
Database manager...
'''
app = Flask(__name__)
# Set app secret key to sign session cookies
secret_key = environ.get("APPSECRET")
if secret_key is None:
# NO Secret Key set!
print("No app secret set, please set one before deploying in production")
app.secret_key = "DEFAULTKEY"
else:
app.secret_key = secret_key
# Register a blueprint
app.register_blueprint(blueprint)
if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0", port=8080)
from flask import Flask
from os import environ
from controllers.web.endpoints import blueprint
'''
Main entrypoint for Flask application.
Initialises any components that are needed at runtime such as the
Database manager...
'''
app = Flask(__name__)
# Set app secret key to sign session cookies
secret_key = environ.get("APPSECRET")
if secret_key is None:
# NO Secret Key set!
print("No app secret set, please set one before deploying in production")
app.secret_key = "DEFAULTKEY"
else:
app.secret_key = secret_key
# Register a blueprint
app.register_blueprint(blueprint)
if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0", port=8080)

View File

@ -1,8 +1,8 @@
version: '3.8'
services:
gitlab-runner:
image: gitlab/gitlab-runner:latest
volumes:
- ./runner-data:/etc/gitlab-runner
- /var/run/docker.sock:/var/run/docker.sock
version: '3.8'
services:
gitlab-runner:
image: gitlab/gitlab-runner:latest
volumes:
- ./runner-data:/etc/gitlab-runner
- /var/run/docker.sock:/var/run/docker.sock
restart: unless-stopped

View File

@ -1,63 +1,63 @@
from .database import DatabaseController
from models.category import Category
class CategoryController(DatabaseController):
FIELDS = ['id', 'name']
def __init__(self):
super().__init__()
def create(self, category: Category):
params = [
category.name,
]
self._conn.execute(
"INSERT INTO Categories (name) VALUES (?)",
params
)
self._conn.commit()
def read(self, id: int = 0) -> Category | None:
params = [
id
]
cursor = self._conn.execute(
"SELECT * FROM Categories WHERE id = ?",
params
)
row = cursor.fetchone()
if row is None:
return None
params = dict(zip(self.FIELDS, row))
obj = self.new_instance(Category, params)
return obj
def read_all(self) -> list[Category] | None:
cursor = self._conn.execute(
"SELECT * FROM Categories",
)
rows = cursor.fetchall()
if rows is 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")
from .database import DatabaseController
from models.category import Category
class CategoryController(DatabaseController):
FIELDS = ['id', 'name']
def __init__(self):
super().__init__()
def create(self, category: Category):
params = [
category.name,
]
self._conn.execute(
"INSERT INTO Categories (name) VALUES (?)",
params
)
self._conn.commit()
def read(self, id: int = 0) -> Category | None:
params = [
id
]
cursor = self._conn.execute(
"SELECT * FROM Categories WHERE id = ?",
params
)
row = cursor.fetchone()
if row is None:
return None
params = dict(zip(self.FIELDS, row))
obj = self.new_instance(Category, params)
return obj
def read_all(self) -> list[Category] | None:
cursor = self._conn.execute(
"SELECT * FROM Categories",
)
rows = cursor.fetchall()
if rows is 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")

View File

@ -1,81 +1,81 @@
from abc import ABC, abstractmethod
from typing import Mapping, Any
import sqlite3
import os
class DatabaseController(ABC):
""" Abstract Base Class to handle database access for each component
in the web app
"""
__data_dir = "./data/"
__db_name = "wmgzon.db"
# Use test file if necessary
if os.environ.get("ENVIRON") == "test":
__db_name = "test_" + __db_name
__sqlitefile = __data_dir + __db_name
def __init__(self):
""" Initialises the object and creates a connection to the local
DB on the server
"""
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)
except sqlite3.Error as e:
# Close the connection if still open
if self._conn:
self._conn.close()
print(e)
def __del__(self):
""" Object Destructor which kills the connection to the database """
if self._conn is not None:
self._conn.close()
def new_instance(self, of: type, with_fields: Mapping[str, Any]):
""" Takes a dictionary of fields and returns the object
with those fields populated
"""
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
"""
@abstractmethod
def create(self):
""" Abstract method used to create a new record of a given
type within the database
"""
pass
@abstractmethod
def read(self):
""" Abstract method used to read a record of a given
type from the database
"""
pass
@abstractmethod
def update(self):
""" Abstract method used to update a record of a given
type within the database
"""
pass
@abstractmethod
def delete(self):
""" Abstract method used to delete record of a given
type from the database
"""
pass
from abc import ABC, abstractmethod
from typing import Mapping, Any
import sqlite3
import os
class DatabaseController(ABC):
""" Abstract Base Class to handle database access for each component
in the web app
"""
__data_dir = "./data/"
__db_name = "wmgzon.db"
# Use test file if necessary
if os.environ.get("ENVIRON") == "test":
__db_name = "test_" + __db_name
__sqlitefile = __data_dir + __db_name
def __init__(self):
""" Initialises the object and creates a connection to the local
DB on the server
"""
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)
except sqlite3.Error as e:
# Close the connection if still open
if self._conn:
self._conn.close()
print(e)
def __del__(self):
""" Object Destructor which kills the connection to the database """
if self._conn is not None:
self._conn.close()
def new_instance(self, of: type, with_fields: Mapping[str, Any]):
""" Takes a dictionary of fields and returns the object
with those fields populated
"""
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
"""
@abstractmethod
def create(self):
""" Abstract method used to create a new record of a given
type within the database
"""
pass
@abstractmethod
def read(self):
""" Abstract method used to read a record of a given
type from the database
"""
pass
@abstractmethod
def update(self):
""" Abstract method used to update a record of a given
type within the database
"""
pass
@abstractmethod
def delete(self):
""" Abstract method used to delete record of a given
type from the database
"""
pass

View File

@ -1,160 +1,160 @@
from .database import DatabaseController
from models.products.product import Product
class ProductController(DatabaseController):
FIELDS = ['id', 'name', 'image', 'description', 'cost',
'sellerID', 'category', 'postedDate', 'quantityAvailable']
def __init__(self):
super().__init__()
def create(self, product: Product):
params = [
product.name,
product.image,
product.description,
product.cost,
product.category,
product.sellerID,
product.postedDate,
product.quantityAvailable
]
self._conn.execute(
"""
INSERT INTO Products
(name, image, description, cost, categoryID,
sellerID, postedDate, quantityAvailable)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
params
)
self._conn.commit()
def read(self, name: str = "") -> list[Product] | None:
params = [
"%" + name + "%"
]
cursor = self._conn.execute(
"SELECT * FROM Products WHERE name like ?",
params
)
rows = cursor.fetchmany()
if rows is 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
def read_id(self, id: int) -> Product | None:
params = [
id
]
cursor = self._conn.execute(
"SELECT * FROM Products WHERE id == ?",
params
)
row = cursor.fetchone()
if row is None:
return None
# Create an object with the row
params = dict(zip(self.FIELDS, row))
obj = self.new_instance(Product, params)
return obj
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
WHERE Categories.name LIKE ?
AND Products.name LIKE ?
""",
params
)
rows = cursor.fetchall()
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 read_user(self, user_id: int) -> list[Product] | None:
params = [
user_id
]
cursor = self._conn.execute(
"""SELECT * FROM Products
WHERE sellerID = ?
""",
params
)
rows = cursor.fetchall()
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, product: Product):
params = [
product.name,
product.description,
product.image,
product.cost,
product.quantityAvailable,
product.category,
product.id
]
cursor = self._conn.execute(
"""UPDATE Products
SET name = ?,
description = ?,
image = ?,
cost = ?,
quantityAvailable = ?,
categoryID = ?
WHERE id = ?
""",
params
)
self._conn.commit()
def delete(self):
print("Doing work")
from .database import DatabaseController
from models.products.product import Product
class ProductController(DatabaseController):
FIELDS = ['id', 'name', 'image', 'description', 'cost',
'sellerID', 'category', 'postedDate', 'quantityAvailable']
def __init__(self):
super().__init__()
def create(self, product: Product):
params = [
product.name,
product.image,
product.description,
product.cost,
product.category,
product.sellerID,
product.postedDate,
product.quantityAvailable
]
self._conn.execute(
"""
INSERT INTO Products
(name, image, description, cost, categoryID,
sellerID, postedDate, quantityAvailable)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
params
)
self._conn.commit()
def read(self, name: str = "") -> list[Product] | None:
params = [
"%" + name + "%"
]
cursor = self._conn.execute(
"SELECT * FROM Products WHERE name like ?",
params
)
rows = cursor.fetchmany()
if rows is 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
def read_id(self, id: int) -> Product | None:
params = [
id
]
cursor = self._conn.execute(
"SELECT * FROM Products WHERE id == ?",
params
)
row = cursor.fetchone()
if row is None:
return None
# Create an object with the row
params = dict(zip(self.FIELDS, row))
obj = self.new_instance(Product, params)
return obj
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
WHERE Categories.name LIKE ?
AND Products.name LIKE ?
""",
params
)
rows = cursor.fetchall()
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 read_user(self, user_id: int) -> list[Product] | None:
params = [
user_id
]
cursor = self._conn.execute(
"""SELECT * FROM Products
WHERE sellerID = ?
""",
params
)
rows = cursor.fetchall()
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, product: Product):
params = [
product.name,
product.description,
product.image,
product.cost,
product.quantityAvailable,
product.category,
product.id
]
cursor = self._conn.execute(
"""UPDATE Products
SET name = ?,
description = ?,
image = ?,
cost = ?,
quantityAvailable = ?,
categoryID = ?
WHERE id = ?
""",
params
)
self._conn.commit()
def delete(self):
print("Doing work")

View File

@ -1,85 +1,85 @@
from .database import DatabaseController
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']
def __init__(self):
super().__init__()
def create(self, user: User):
params = [
user.username,
user.password,
user.firstName,
user.lastName,
user.email,
user.phone,
user.role
]
self._conn.execute(
"""INSERT INTO Users
(username, password, first_name, last_name, email, phone, role)
VALUES (?, ?, ?, ?, ?, ?, ?)""",
params
)
self._conn.commit()
def read(self, username: str) -> User | None:
params = [
username
]
cursor = self._conn.execute(
"SELECT * FROM Users WHERE Username = ?",
params
)
row = cursor.fetchone()
if row is not None:
params = dict(zip(self.FIELDS, row))
# Is user a seller
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
]
cursor = self._conn.execute(
"SELECT * FROM Users WHERE id = ?",
params
)
row = cursor.fetchone()
if row is not None:
params = dict(zip(self.FIELDS, row))
# Is user a seller
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")
from .database import DatabaseController
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']
def __init__(self):
super().__init__()
def create(self, user: User):
params = [
user.username,
user.password,
user.firstName,
user.lastName,
user.email,
user.phone,
user.role
]
self._conn.execute(
"""INSERT INTO Users
(username, password, first_name, last_name, email, phone, role)
VALUES (?, ?, ?, ?, ?, ?, ?)""",
params
)
self._conn.commit()
def read(self, username: str) -> User | None:
params = [
username
]
cursor = self._conn.execute(
"SELECT * FROM Users WHERE Username = ?",
params
)
row = cursor.fetchone()
if row is not None:
params = dict(zip(self.FIELDS, row))
# Is user a seller
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
]
cursor = self._conn.execute(
"SELECT * FROM Users WHERE id = ?",
params
)
row = cursor.fetchone()
if row is not None:
params = dict(zip(self.FIELDS, row))
# Is user a seller
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")

View File

@ -1,34 +1,34 @@
from models.users.user import User
from controllers.database.user import UserController
from flask import redirect, Blueprint, session
from . import user
from . import product
blueprint = Blueprint('main', __name__)
blueprint.register_blueprint(user.blueprint)
blueprint.register_blueprint(product.blueprint)
# CONTEXTS #
# Function that returns a given user class based on the ID in the session
@blueprint.context_processor
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 is not None:
db = UserController()
user = db.read_id(user_id)
return dict(user=user)
# Function responsible for displaying the main landing page of the site
@blueprint.route('/')
def index():
return redirect("/products")
from models.users.user import User
from controllers.database.user import UserController
from flask import redirect, Blueprint, session
from . import user
from . import product
blueprint = Blueprint('main', __name__)
blueprint.register_blueprint(user.blueprint)
blueprint.register_blueprint(product.blueprint)
# CONTEXTS #
# Function that returns a given user class based on the ID in the session
@blueprint.context_processor
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 is not None:
db = UserController()
user = db.read_id(user_id)
return dict(user=user)
# Function responsible for displaying the main landing page of the site
@blueprint.route('/')
def index():
return redirect("/products")

View File

@ -1,188 +1,188 @@
"""
Product related endpoints. Included contexts for principles such as
categories and image processing.
"""
from flask import render_template, session, flash, request, redirect, Blueprint
from models.products.product import Product
from controllers.database.product import ProductController
from controllers.database.category import CategoryController
from controllers.database.user import UserController
from datetime import datetime
from utils.file_utils import allowed_file, save_image, remove_file
from utils.user_utils import is_role
import pathlib
import os
blueprint = Blueprint("products", __name__, url_prefix="/products")
@blueprint.context_processor
def category_list():
""" Places a list of all categories in the products context """
database = CategoryController()
categories = database.read_all()
return dict(categories=categories)
@blueprint.route('/')
def index():
""" The front product page """
# Returning an empty category acts the same
# as a generic home page
return category("")
@blueprint.route('/<string:category>')
def category(category: str):
""" Loads a given categories page """
database = ProductController()
# Check to see if there is a custome search term
search_term = request.args.get("search", type=str)
if search_term is not None:
products = database.read_all(category, search_term)
else:
products = database.read_all(category)
# No Products visible
if products is None:
flash(
f"No Products available. Try expanding your search criteria.",
"warning"
)
return render_template(
'index.html',
content="content.html",
products=products,
category=category
)
@blueprint.route('/<int:id>')
def id(id: int):
""" Loads a given product based on ID """
db = ProductController()
product = db.read_id(id)
# Check that a valid product was returned
if product is None:
flash(f"No Product available with id {id}", "warning")
return redirect("/")
print(product.name)
return render_template(
'index.html',
content='product.html',
product=product
)
@blueprint.route('/add')
def display_add_product():
""" Launches the page to add a new product to the site """
user_id = session.get('user_id')
# User needs to be logged in as a seller to view this page
if not is_role("Seller"):
flash("You must be logged in as a seller to view this page!", "error")
return redirect("/")
return render_template('index.html', content='new_product.html')
@blueprint.post('/add')
def add_product():
""" Server site processing to handle a request to add a
new product to the site
"""
user_id = session.get('user_id')
# User needs to be logged in as a seller to view this page
if not is_role("Seller"):
flash("You must be logged in as a seller to view this page!", "error")
return redirect("/")
file = request.files.get('image')
image_filename = save_image(file)
product = Product(
request.form.get('name'),
image_filename if image_filename is not None else "",
request.form.get('description'),
request.form.get('cost'),
request.form.get('category'),
user_id,
datetime.now(),
request.form.get('quantity')
)
db = ProductController()
db.create(product)
return redirect('/products/ownproducts')
@blueprint.post('/update/<int:id>')
def update_product(id: int):
""" Processes a request to update a product in place on the site """
# Ensure that the product belongs to the current user
user_id = session.get('user_id')
# User needs to be logged in as a seller to view this page
if not is_role("Seller"):
flash("You must be logged in as a seller to view this page!", "error")
return redirect("/")
db = ProductController()
product = db.read_id(id)
if product.sellerID != user_id:
flash("This product does not belong to you!", "error")
return redirect("/ownproducts")
# Save new image file
file = request.files.get('image')
new_image = save_image(file)
if new_image is not None:
remove_file(os.path.join(os.environ.get('FILESTORE'), product.image))
product.image = new_image
# Update product details
product.name = request.form.get('name')
product.description = request.form.get('description')
product.category = request.form.get('category')
product.cost = request.form.get('cost')
product.quantityAvailable = request.form.get('quantity')
db.update(product)
flash("Product successfully updated", 'notice')
return redirect(f"/products/{product.id}")
@blueprint.route('/ownproducts')
def display_own_products():
""" Display products owned by the currently logged in seller """
user_id = session.get('user_id')
# User must be logged in as seller to view page
if not is_role("Seller"):
flash("You must be logged in as a seller to view this page!", "error")
return redirect("/")
db = ProductController()
products = db.read_user(user_id)
if products is None:
flash("You don't currently have any products for sale.", "info")
return render_template(
'index.html',
content='content.html',
products=products
)
"""
Product related endpoints. Included contexts for principles such as
categories and image processing.
"""
from flask import render_template, session, flash, request, redirect, Blueprint
from models.products.product import Product
from controllers.database.product import ProductController
from controllers.database.category import CategoryController
from controllers.database.user import UserController
from datetime import datetime
from utils.file_utils import allowed_file, save_image, remove_file
from utils.user_utils import is_role
import pathlib
import os
blueprint = Blueprint("products", __name__, url_prefix="/products")
@blueprint.context_processor
def category_list():
""" Places a list of all categories in the products context """
database = CategoryController()
categories = database.read_all()
return dict(categories=categories)
@blueprint.route('/')
def index():
""" The front product page """
# Returning an empty category acts the same
# as a generic home page
return category("")
@blueprint.route('/<string:category>')
def category(category: str):
""" Loads a given categories page """
database = ProductController()
# Check to see if there is a custome search term
search_term = request.args.get("search", type=str)
if search_term is not None:
products = database.read_all(category, search_term)
else:
products = database.read_all(category)
# No Products visible
if products is None:
flash(
f"No Products available. Try expanding your search criteria.",
"warning"
)
return render_template(
'index.html',
content="content.html",
products=products,
category=category
)
@blueprint.route('/<int:id>')
def id(id: int):
""" Loads a given product based on ID """
db = ProductController()
product = db.read_id(id)
# Check that a valid product was returned
if product is None:
flash(f"No Product available with id {id}", "warning")
return redirect("/")
print(product.name)
return render_template(
'index.html',
content='product.html',
product=product
)
@blueprint.route('/add')
def display_add_product():
""" Launches the page to add a new product to the site """
user_id = session.get('user_id')
# User needs to be logged in as a seller to view this page
if not is_role("Seller"):
flash("You must be logged in as a seller to view this page!", "error")
return redirect("/")
return render_template('index.html', content='new_product.html')
@blueprint.post('/add')
def add_product():
""" Server site processing to handle a request to add a
new product to the site
"""
user_id = session.get('user_id')
# User needs to be logged in as a seller to view this page
if not is_role("Seller"):
flash("You must be logged in as a seller to view this page!", "error")
return redirect("/")
file = request.files.get('image')
image_filename = save_image(file)
product = Product(
request.form.get('name'),
image_filename if image_filename is not None else "",
request.form.get('description'),
request.form.get('cost'),
request.form.get('category'),
user_id,
datetime.now(),
request.form.get('quantity')
)
db = ProductController()
db.create(product)
return redirect('/products/ownproducts')
@blueprint.post('/update/<int:id>')
def update_product(id: int):
""" Processes a request to update a product in place on the site """
# Ensure that the product belongs to the current user
user_id = session.get('user_id')
# User needs to be logged in as a seller to view this page
if not is_role("Seller"):
flash("You must be logged in as a seller to view this page!", "error")
return redirect("/")
db = ProductController()
product = db.read_id(id)
if product.sellerID != user_id:
flash("This product does not belong to you!", "error")
return redirect("/ownproducts")
# Save new image file
file = request.files.get('image')
new_image = save_image(file)
if new_image is not None:
remove_file(os.path.join(os.environ.get('FILESTORE'), product.image))
product.image = new_image
# Update product details
product.name = request.form.get('name')
product.description = request.form.get('description')
product.category = request.form.get('category')
product.cost = request.form.get('cost')
product.quantityAvailable = request.form.get('quantity')
db.update(product)
flash("Product successfully updated", 'notice')
return redirect(f"/products/{product.id}")
@blueprint.route('/ownproducts')
def display_own_products():
""" Display products owned by the currently logged in seller """
user_id = session.get('user_id')
# User must be logged in as seller to view page
if not is_role("Seller"):
flash("You must be logged in as a seller to view this page!", "error")
return redirect("/")
db = ProductController()
products = db.read_user(user_id)
if products is None:
flash("You don't currently have any products for sale.", "info")
return render_template(
'index.html',
content='content.html',
products=products
)

View File

@ -1,99 +1,99 @@
""" The user controller to manage all of the user related endpoints
in the web app
"""
from flask import Blueprint
from flask import render_template, redirect, request, session, flash
from controllers.database.user import UserController
from models.users.user import User
from models.users.customer import Customer
from models.users.seller import Seller
from hashlib import sha512
# Blueprint to append user endpoints to
blueprint = Blueprint("users", __name__)
# LOGIN FUNCTIONALITY
@blueprint.route('/login')
def display_login():
""" Function responsible for delivering the Login page for the site """
return render_template('index.html', content="login.html")
@blueprint.post('/login')
def login():
""" Function to handle the backend processing of a login request """
database = UserController()
user = database.read(request.form['username'])
error = None
# No user found
if user is None:
error = "No user found with the username " + request.form['username']
flash(error, 'warning')
return redirect("/login")
# Incorrect Password
if sha512(request.form['password'].encode()).hexdigest() != user.password:
error = "Incorrect Password"
flash(error, 'warning')
return redirect("/login")
session['user_id'] = user.id
return redirect("/")
# SIGNUP FUNCTIONALITY
@blueprint.route('/signup')
def display_signup():
""" Function responsible for delivering the Signup page for the site """
return render_template('index.html', content="signup.html")
@blueprint.post('/signup')
def signup():
""" Function to handle the backend processing of a signup request """
database = UserController()
# User already exists
if database.read(request.form['username']) is not None:
error = "User, " + request.form['username'] + " already exists"
flash(error, 'warning')
return redirect("/signup")
# Signup as Seller or Customer
if request.form.get('seller'):
user = Seller(
request.form['username'],
# 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"
)
else:
user = Customer(
request.form['username'],
# 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
@blueprint.route('/logout')
def logout():
""" Function responsible for handling logouts from the site """
# Clear the current user from the session if they are logged in
session.pop('user_id', None)
return redirect("/")
""" The user controller to manage all of the user related endpoints
in the web app
"""
from flask import Blueprint
from flask import render_template, redirect, request, session, flash
from controllers.database.user import UserController
from models.users.user import User
from models.users.customer import Customer
from models.users.seller import Seller
from hashlib import sha512
# Blueprint to append user endpoints to
blueprint = Blueprint("users", __name__)
# LOGIN FUNCTIONALITY
@blueprint.route('/login')
def display_login():
""" Function responsible for delivering the Login page for the site """
return render_template('index.html', content="login.html")
@blueprint.post('/login')
def login():
""" Function to handle the backend processing of a login request """
database = UserController()
user = database.read(request.form['username'])
error = None
# No user found
if user is None:
error = "No user found with the username " + request.form['username']
flash(error, 'warning')
return redirect("/login")
# Incorrect Password
if sha512(request.form['password'].encode()).hexdigest() != user.password:
error = "Incorrect Password"
flash(error, 'warning')
return redirect("/login")
session['user_id'] = user.id
return redirect("/")
# SIGNUP FUNCTIONALITY
@blueprint.route('/signup')
def display_signup():
""" Function responsible for delivering the Signup page for the site """
return render_template('index.html', content="signup.html")
@blueprint.post('/signup')
def signup():
""" Function to handle the backend processing of a signup request """
database = UserController()
# User already exists
if database.read(request.form['username']) is not None:
error = "User, " + request.form['username'] + " already exists"
flash(error, 'warning')
return redirect("/signup")
# Signup as Seller or Customer
if request.form.get('seller'):
user = Seller(
request.form['username'],
# 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"
)
else:
user = Customer(
request.form['username'],
# 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
@blueprint.route('/logout')
def logout():
""" Function responsible for handling logouts from the site """
# Clear the current user from the session if they are logged in
session.pop('user_id', None)
return redirect("/")

View File

@ -1,18 +1,18 @@
version: '3.8'
services:
wmgzon:
container_name: "wmgzon"
build: .
environment:
- APPSECRET=test
- ENVIRON=test
#- ENVIRON=prod
- FILESTORE=static/assets/img/products/
tty: true
ports:
- "8080:8080"
volumes:
- .:/app
restart: unless-stopped
version: '3.8'
services:
wmgzon:
container_name: "wmgzon"
build: .
environment:
- APPSECRET=test
- ENVIRON=test
#- ENVIRON=prod
- FILESTORE=static/assets/img/products/
tty: true
ports:
- "8080:8080"
volumes:
- .:/app
restart: unless-stopped

View File

@ -1,7 +1,7 @@
FROM python:latest
COPY ./requirements.txt /app/requirements.txt
WORKDIR /app
RUN pip install -r requirements.txt
COPY . /app
RUN chmod +x scripts/run.bash
FROM python:latest
COPY ./requirements.txt /app/requirements.txt
WORKDIR /app
RUN pip install -r requirements.txt
COPY . /app
RUN chmod +x scripts/run.bash
CMD ["bash", "scripts/run.bash"]

View File

@ -1,8 +1,8 @@
class Category:
'''
Constructor for a category object
'''
def __init__(self):
self.id = 0
self.name = ""
class Category:
'''
Constructor for a category object
'''
def __init__(self):
self.id = 0
self.name = ""

View File

@ -1,15 +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()
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()

View File

@ -1,14 +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()
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()

View File

@ -1,38 +1,38 @@
from datetime import datetime
class Product:
'''
Base class for a product
'''
def __init__(self):
self.id = 0
self.name = ""
self.image = "/static/assets/wmgzon.png"
self.description = ""
self.cost = 0.0
self.category = 0
self.sellerID = 0
self.postedDate = datetime.now()
self.quantityAvailable = 0
'''
Class constructor to instatiate a customer object
No additional properties are assigned to the customer
'''
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
self.image = image
self.description = description
self.cost = cost
self.category = category
self.sellerID = seller_id
self.postedDate = posted_date
self.quantityAvailable = quantity_available
from datetime import datetime
class Product:
'''
Base class for a product
'''
def __init__(self):
self.id = 0
self.name = ""
self.image = "/static/assets/wmgzon.png"
self.description = ""
self.cost = 0.0
self.category = 0
self.sellerID = 0
self.postedDate = datetime.now()
self.quantityAvailable = 0
'''
Class constructor to instatiate a customer object
No additional properties are assigned to the customer
'''
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
self.image = image
self.description = description
self.cost = cost
self.category = category
self.sellerID = seller_id
self.postedDate = posted_date
self.quantityAvailable = quantity_available

View File

@ -1,15 +1,15 @@
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,
lastname: str, email: str, phone: str):
super().__init__(
username, password, firstname, lastname, email, phone, "Admin"
)
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,
lastname: str, email: str, phone: str):
super().__init__(
username, password, firstname, lastname, email, phone, "Admin"
)

View File

@ -1,15 +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,
lastname: str, email: str, phone: str):
super().__init__(
username, password, firstname, lastname, email, phone, "Customer"
)
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,
lastname: str, email: str, phone: str):
super().__init__(
username, password, firstname, lastname, email, phone, "Customer"
)

View File

@ -1,16 +1,16 @@
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,
lastname: str, email: str, phone: str):
super().__init__(
username, password, firstname, lastname, email, phone, "Seller"
)
self.store = ""
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,
lastname: str, email: str, phone: str):
super().__init__(
username, password, firstname, lastname, email, phone, "Seller"
)
self.store = ""

View File

@ -1,20 +1,20 @@
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, lastname: str,
email: str, phone: str, role: str
):
self.id = 0
self.username = username
self.password = password
self.firstName = firstname
self.lastName = lastname
self.email = email
self.phone = phone
self.role = role
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, lastname: str,
email: str, phone: str, role: str
):
self.id = 0
self.username = username
self.password = password
self.firstName = firstname
self.lastName = lastname
self.email = email
self.phone = phone
self.role = role

View File

@ -1,26 +1,26 @@
blinker==1.7.0
click==8.1.7
colorama==0.4.6
distlib==0.3.7
dnspython==2.4.2
docstring-to-markdown==0.13
filelock==3.13.1
Flask==3.0.0
iniconfig==2.0.0
itsdangerous==2.1.2
jedi==0.19.1
Jinja2==3.1.2
MarkupSafe==2.1.3
packaging==23.2
parso==0.8.3
platformdirs==3.11.0
pluggy==1.3.0
pymongo==4.6.0
pytest==7.4.3
python-lsp-jsonrpc==1.1.2
python-lsp-server==1.9.0
ujson==5.8.0
virtualenv==20.24.6
waitress==2.1.2
Werkzeug==3.0.1
blinker==1.7.0
click==8.1.7
colorama==0.4.6
distlib==0.3.7
dnspython==2.4.2
docstring-to-markdown==0.13
filelock==3.13.1
Flask==3.0.0
iniconfig==2.0.0
itsdangerous==2.1.2
jedi==0.19.1
Jinja2==3.1.2
MarkupSafe==2.1.3
packaging==23.2
parso==0.8.3
platformdirs==3.11.0
pluggy==1.3.0
pymongo==4.6.0
pytest==7.4.3
python-lsp-jsonrpc==1.1.2
python-lsp-server==1.9.0
ujson==5.8.0
virtualenv==20.24.6
waitress==2.1.2
Werkzeug==3.0.1
pycodestyle==2.11.1

View File

@ -1,51 +1,51 @@
import sqlite3
import os
import sys
if __name__ == "__main__":
sys.path.append(os.getcwd())
from utils.file_utils import create_directory, remove_file
def create_connection(path: str, filename: str):
""" create a database connection to a SQLite database """
conn = None
try:
# Make the directory for the file to go into
create_directory(path)
print("Opening Database file and ensuring table integrity")
conn = sqlite3.connect(path + filename)
print("Database file open")
# Execute creation scripts
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")
conn.executescript(sql.read())
except sqlite3.Error as e:
print(e)
finally:
if conn:
conn.close()
# Ensure a directory is created given a path to it
dir = r"./data/"
db_name = r"wmgzon.db"
# Check for test environ
if os.environ.get("ENVIRON") == "test":
# Remove the original test database
print("TEST ENVIRONMENT ACTIVE")
db_name = "test_" + db_name
remove_file(dir + db_name)
create_connection(dir, db_name)
import sqlite3
import os
import sys
if __name__ == "__main__":
sys.path.append(os.getcwd())
from utils.file_utils import create_directory, remove_file
def create_connection(path: str, filename: str):
""" create a database connection to a SQLite database """
conn = None
try:
# Make the directory for the file to go into
create_directory(path)
print("Opening Database file and ensuring table integrity")
conn = sqlite3.connect(path + filename)
print("Database file open")
# Execute creation scripts
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")
conn.executescript(sql.read())
except sqlite3.Error as e:
print(e)
finally:
if conn:
conn.close()
# Ensure a directory is created given a path to it
dir = r"./data/"
db_name = r"wmgzon.db"
# Check for test environ
if os.environ.get("ENVIRON") == "test":
# Remove the original test database
print("TEST ENVIRONMENT ACTIVE")
db_name = "test_" + db_name
remove_file(dir + db_name)
create_connection(dir, db_name)

View File

@ -1,56 +1,56 @@
CREATE TABLE IF NOT EXISTS Users (
id INTEGER PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
password TEXT NOT NULL,
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
phone TEXT NOT NULL,
role TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS Categories (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE
);
INSERT INTO Categories (name) VALUES ("Car Parts");
INSERT INTO Categories (name) VALUES ("Animals");
INSERT INTO Categories (name) VALUES ("Sports");
INSERT INTO Categories (name) VALUES ("Books");
INSERT INTO Categories (name) VALUES ("Phones");
INSERT INTO Categories (name) VALUES ("Music");
CREATE TABLE IF NOT EXISTS Products (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
image TEXT NOT NULL,
description TEXT NOT NULL,
cost DECIMAL NOT NULL,
sellerID INTEGER NOT NULL
REFERENCES Users (id)
ON DELETE CASCADE
ON UPDATE NO ACTION,
categoryID INTEGER NOT NULL
REFERENCES Categories (id)
ON DELETE CASCADE
ON UPDATE NO ACTION,
postedDate TIMESTAMP,
quantityAvailable INTEGER DEFAULT 0
);
CREATE TABLE IF NOT EXISTS Orders (
id INTEGER PRIMARY KEY,
sellerID TEXT NOT NULL
REFERENCES Users (id)
ON DELETE NO ACTION
ON UPDATE NO ACTION,
total DECIMAL NOT NULL,
buyerID INTEGER NOT NULL
REFERENCES Users (id)
ON DELETE CASCADE
ON UPDATE NO ACTION,
orderDate DATE NOT NULL
);
CREATE TABLE IF NOT EXISTS Users (
id INTEGER PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
password TEXT NOT NULL,
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
phone TEXT NOT NULL,
role TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS Categories (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE
);
INSERT INTO Categories (name) VALUES ("Car Parts");
INSERT INTO Categories (name) VALUES ("Animals");
INSERT INTO Categories (name) VALUES ("Sports");
INSERT INTO Categories (name) VALUES ("Books");
INSERT INTO Categories (name) VALUES ("Phones");
INSERT INTO Categories (name) VALUES ("Music");
CREATE TABLE IF NOT EXISTS Products (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
image TEXT NOT NULL,
description TEXT NOT NULL,
cost DECIMAL NOT NULL,
sellerID INTEGER NOT NULL
REFERENCES Users (id)
ON DELETE CASCADE
ON UPDATE NO ACTION,
categoryID INTEGER NOT NULL
REFERENCES Categories (id)
ON DELETE CASCADE
ON UPDATE NO ACTION,
postedDate TIMESTAMP,
quantityAvailable INTEGER DEFAULT 0
);
CREATE TABLE IF NOT EXISTS Orders (
id INTEGER PRIMARY KEY,
sellerID TEXT NOT NULL
REFERENCES Users (id)
ON DELETE NO ACTION
ON UPDATE NO ACTION,
total DECIMAL NOT NULL,
buyerID INTEGER NOT NULL
REFERENCES Users (id)
ON DELETE CASCADE
ON UPDATE NO ACTION,
orderDate DATE NOT NULL
);

View File

@ -1,37 +1,37 @@
INSERT INTO Users (first_name, last_name, username, email, phone, password, role) VALUES ("Luke", "Else", "lukejelse04", "test@test.com", "07498 289321", "test213", "Customer");
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 2);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 2);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 3);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 4);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 4);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 4);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 6);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 6);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 2);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 2);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 3);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 4);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 4);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 4);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 6);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 6);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 2);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 2);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 3);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 4);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 4);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 4);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 6);
INSERT INTO Users (first_name, last_name, username, email, phone, password, role) VALUES ("Luke", "Else", "lukejelse04", "test@test.com", "07498 289321", "test213", "Customer");
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 2);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 2);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 3);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 4);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 4);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 4);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 6);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 6);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 2);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 2);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 3);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 4);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 4);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 4);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 6);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 6);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 2);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 2);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 3);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 4);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 4);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 4);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 6);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 6);

View File

@ -1,73 +1,73 @@
.alert {
position: fixed;
bottom: 3em;
left: 50%;
transform: translate(-50%, 50%);
z-index: 1;
width: auto;
height: auto;
padding: 10px;
margin: 10px;
line-height: 1.8;
border-radius: 5px;
cursor: hand;
cursor: pointer;
font-family: sans-serif;
font-weight: 400;
}
.alertCheckbox {
display: none;
}
:checked + .alert {
display: none;
}
.alertText {
display: table;
margin: 0 auto;
padding: 0 5px 0 0;
text-align: center;
font-size: 20px;
}
.alertClose {
float: right;
padding-top: 5px;
font-size: 15px;
}
.clear {
clear: both;
}
.info {
background-color: #EEE;
border: 1px solid #DDD;
color: #999;
}
.success {
background-color: #EFE;
border: 1px solid #DED;
color: #9A9;
}
.notice {
background-color: #EFF;
border: 1px solid #DEE;
color: #9AA;
}
.warning {
background-color: #FDF7DF;
border: 1px solid #FEEC6F;
color: #C9971C;
}
.error {
background-color: #FEE;
border: 1px solid #EDD;
color: #A66;
.alert {
position: fixed;
bottom: 3em;
left: 50%;
transform: translate(-50%, 50%);
z-index: 1;
width: auto;
height: auto;
padding: 10px;
margin: 10px;
line-height: 1.8;
border-radius: 5px;
cursor: hand;
cursor: pointer;
font-family: sans-serif;
font-weight: 400;
}
.alertCheckbox {
display: none;
}
:checked + .alert {
display: none;
}
.alertText {
display: table;
margin: 0 auto;
padding: 0 5px 0 0;
text-align: center;
font-size: 20px;
}
.alertClose {
float: right;
padding-top: 5px;
font-size: 15px;
}
.clear {
clear: both;
}
.info {
background-color: #EEE;
border: 1px solid #DDD;
color: #999;
}
.success {
background-color: #EFE;
border: 1px solid #DED;
color: #9A9;
}
.notice {
background-color: #EFF;
border: 1px solid #DEE;
color: #9AA;
}
.warning {
background-color: #FDF7DF;
border: 1px solid #FEEC6F;
color: #C9971C;
}
.error {
background-color: #FEE;
border: 1px solid #EDD;
color: #A66;
}

View File

@ -1,69 +1,69 @@
.filter-pane {
flex: 0 1 auto;
width: 60%;
padding: .5rem 2rem;
background-color: var(--bg-grad-3);
border-radius: 1rem;
}
.filter-items {
display: flex;
align-items: center;
justify-content: space-between;
gap: 2rem;
}
.product-filter {
width: 100%;
padding: 16px 20px;
border: none;
border-radius: 4px;
background-color: var(--bg-grad-2);
color: var(--fg);
font-size: .75rem;
}
/* Number Plate*/
.number-plate {
display: flex;
align-items: center;
}
.country-identifier {
width: auto;
display: flex;
flex-direction: column;
align-items: center;
color: #ffffff;
font-weight: bold;
height: 60px;
justify-content: center;
}
.country-identifier img {
border-radius: 8px 0 0 8px;
}
.vrn {
width: 10rem;
height: 63px;
border: 1px solid #ead809;
background-color: #ead809;
border-radius: 0 8px 8px 0;
}
.vrn .vrn-text {
width: -webkit-fill-available;
height: -webkit-fill-available;
border-radius: 0;
border: 0;
font-family: "UKNumberPlate", sans-serif;
font-size: 180%;
text-transform: uppercase;
text-align: center;
outline: 0;
background-color: transparent;
}
.vrn .vrn-text:focus {
outline: 0;
.filter-pane {
flex: 0 1 auto;
width: 60%;
padding: .5rem 2rem;
background-color: var(--bg-grad-3);
border-radius: 1rem;
}
.filter-items {
display: flex;
align-items: center;
justify-content: space-between;
gap: 2rem;
}
.product-filter {
width: 100%;
padding: 16px 20px;
border: none;
border-radius: 4px;
background-color: var(--bg-grad-2);
color: var(--fg);
font-size: .75rem;
}
/* Number Plate*/
.number-plate {
display: flex;
align-items: center;
}
.country-identifier {
width: auto;
display: flex;
flex-direction: column;
align-items: center;
color: #ffffff;
font-weight: bold;
height: 60px;
justify-content: center;
}
.country-identifier img {
border-radius: 8px 0 0 8px;
}
.vrn {
width: 10rem;
height: 63px;
border: 1px solid #ead809;
background-color: #ead809;
border-radius: 0 8px 8px 0;
}
.vrn .vrn-text {
width: -webkit-fill-available;
height: -webkit-fill-available;
border-radius: 0;
border: 0;
font-family: "UKNumberPlate", sans-serif;
font-size: 180%;
text-transform: uppercase;
text-align: center;
outline: 0;
background-color: transparent;
}
.vrn .vrn-text:focus {
outline: 0;
}

View File

@ -1,155 +1,155 @@
h2 {
font-weight:300;
text-align:center;
}
#input-form-wrap {
background-color: rgba(255, 255, 255, .15);
backdrop-filter: blur(200px);
width: 35%;
text-align: center;
padding: 1em 0 0 0;
border-radius: 4px;
box-shadow: 0px 30px 50px 0px rgba(0, 0, 0, 0.2);
}
.input-form {
padding: 1em 2em;
display: flex;
flex-direction: column;
justify-items: center;
gap: 1em;
}
.input-form-row {
display: flex;
flex-direction: row;
gap: 1em 1em;
}
.input-form input, .input-form select, .input-form option, .input-form textarea {
width: 100%;
padding: 0 0 0 10px;
margin: 0;
color: var(--fg);
border: 1px solid var(--fg);
box-sizing: border-box;
outline: none;
height: 60px;
line-height: 60px;
border-radius: 4px;
font-style: normal;
font-size: 16px;
appearance: none;
position: relative;
display: inline-block;
background: none;
&:focus {
&:invalid {
color: var(--red);
border-color: var(--red);
}
}
&:valid {
border-color: var(--green);
}
}
.input-form textarea {
min-height: 120px;
max-width: 100%;
}
.input-form input[type="submit"] {
border: none;
display:block;
background-color: rgba(255, 255, 255, .10);
color: var(--fg);
font-weight: bold;
text-transform:uppercase;
cursor: pointer;
transition: all 0.2s ease;
font-size: 18px;
position: relative;
display: inline-block;
cursor: pointer;
text-align: center;
&:hover {
background-color: rgba(255, 255, 255, .20);
transition: all 0.2s ease;
}
}
#create-account-wrap {
background-color: rgba(255, 255, 255, .15);
color:#dfdfdf;
font-size:14px;
width:100%;
padding:10px 0;
border-radius: 0 0 4px 4px;
}
.checkbox {
display: block;
position: relative;
padding-left: 35px;
margin-bottom: 12px;
cursor: pointer;
font-size: 22px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
/* Hide the browser's default checkbox */
.checkbox input {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
/* Create a custom checkbox */
.checkmark {
position: absolute;
height: 25px;
width: 25px;
background-color: #808080;
}
/* On mouse-over, add a grey background color */
.checkbox:hover input ~ .checkmark {
background-color: #ccc;
}
/* When the checkbox is checked, add a blue background */
.checkbox input:checked ~ .checkmark {
background-color: #2196F3;
}
/* Create the checkmark/indicator (hidden when not checked) */
.checkmark:after {
content: "";
position: absolute;
display: none;
}
/* Show the checkmark when checked */
.checkbox input:checked ~ .checkmark:after {
display: block;
}
/* Style the checkmark/indicator */
.checkbox .checkmark:after {
left: 9px;
top: 5px;
width: 5px;
height: 10px;
border: solid white;
border-width: 0 3px 3px 0;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
h2 {
font-weight:300;
text-align:center;
}
#input-form-wrap {
background-color: rgba(255, 255, 255, .15);
backdrop-filter: blur(200px);
width: 35%;
text-align: center;
padding: 1em 0 0 0;
border-radius: 4px;
box-shadow: 0px 30px 50px 0px rgba(0, 0, 0, 0.2);
}
.input-form {
padding: 1em 2em;
display: flex;
flex-direction: column;
justify-items: center;
gap: 1em;
}
.input-form-row {
display: flex;
flex-direction: row;
gap: 1em 1em;
}
.input-form input, .input-form select, .input-form option, .input-form textarea {
width: 100%;
padding: 0 0 0 10px;
margin: 0;
color: var(--fg);
border: 1px solid var(--fg);
box-sizing: border-box;
outline: none;
height: 60px;
line-height: 60px;
border-radius: 4px;
font-style: normal;
font-size: 16px;
appearance: none;
position: relative;
display: inline-block;
background: none;
&:focus {
&:invalid {
color: var(--red);
border-color: var(--red);
}
}
&:valid {
border-color: var(--green);
}
}
.input-form textarea {
min-height: 120px;
max-width: 100%;
}
.input-form input[type="submit"] {
border: none;
display:block;
background-color: rgba(255, 255, 255, .10);
color: var(--fg);
font-weight: bold;
text-transform:uppercase;
cursor: pointer;
transition: all 0.2s ease;
font-size: 18px;
position: relative;
display: inline-block;
cursor: pointer;
text-align: center;
&:hover {
background-color: rgba(255, 255, 255, .20);
transition: all 0.2s ease;
}
}
#create-account-wrap {
background-color: rgba(255, 255, 255, .15);
color:#dfdfdf;
font-size:14px;
width:100%;
padding:10px 0;
border-radius: 0 0 4px 4px;
}
.checkbox {
display: block;
position: relative;
padding-left: 35px;
margin-bottom: 12px;
cursor: pointer;
font-size: 22px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
/* Hide the browser's default checkbox */
.checkbox input {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
/* Create a custom checkbox */
.checkmark {
position: absolute;
height: 25px;
width: 25px;
background-color: #808080;
}
/* On mouse-over, add a grey background color */
.checkbox:hover input ~ .checkmark {
background-color: #ccc;
}
/* When the checkbox is checked, add a blue background */
.checkbox input:checked ~ .checkmark {
background-color: #2196F3;
}
/* Create the checkmark/indicator (hidden when not checked) */
.checkmark:after {
content: "";
position: absolute;
display: none;
}
/* Show the checkmark when checked */
.checkbox input:checked ~ .checkmark:after {
display: block;
}
/* Style the checkmark/indicator */
.checkbox .checkmark:after {
left: 9px;
top: 5px;
width: 5px;
height: 10px;
border: solid white;
border-width: 0 3px 3px 0;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}

View File

@ -1,163 +1,163 @@
/* Product Information*/
.product-link:after {
content: '';
position: absolute;
bottom: 0;
left: 15%;
width: 0%;
border-bottom: 2px solid var(--fg);
transition: 0.4s;
}
.product-link:hover:after {
width: 70%;
color: var(--hover);
}
.product-link:hover {
color: var(--hover);
}
.product-link:active {
color: var(--header);
}
.product-container {
width: 95%;
background-color: var(--bg-grad-3);
border-radius: 1rem 1rem 0rem 0rem;
display: flex;
flex-wrap: wrap;
flex-direction: row;
gap: 2rem 1.5rem;
padding: 1rem;
transition: all 0.2s;
overflow-y: scroll;
}
.product {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex: 4 0 1rem;
max-width: 40%;
gap: .5em .5em;
padding: .5rem 0.2rem;
background: var(--bg-secondary);
border-radius: .5rem .5rem;
border: .1em solid rgba(255, 255, 255, 15%);
transition: all 0.2s;
}
.product-fs {
height: 80%;
width: 80%;
font-size: 150%;
display: flex;
flex-direction: row;
justify-content: space-between;
gap: 2em 2em;
padding: .5rem 1rem 2rem 2rem;
/* background: var(--bg-secondary); */
border-radius: .5rem .5rem;
transition: all 0.2s;
}
.product-content-container {
display: flex;
flex-direction: row;
gap: 1em 1em;
padding: 0rem 1rem;
}
.product-image-preview {
max-height: 150px;
max-width: 150px;
}
.product-image {
max-height: 500px;
max-width: 500px;
}
.product-title {
font-size: 120%;
font-weight: bold;
}
.product-details {
display: flex;
flex-direction: column;
padding: 5% 0;
/* justify-content: center; */
/* align-items: center; */
gap: 1rem 1rem;
}
.product-description {
font-size: 70%;
}
.hide-overflow {
inline-size: 200px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.product-acquisition-pane {
font-size: 80%;
width: 100rem;
max-width: 20%;
background-color: var(--bg-secondary);
border: .3em solid rgba(255, 255, 255, 15%);
border-radius: .5rem;
padding: 1rem 1rem;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 1rem 1rem;
}
.product-price {
font-weight: bold;
}
.product-delivery {
font-size: 70%;
}
.product-postage {
font-size: 70%;
}
.product-stock {
display: flex;
flex-direction: row;
font-weight: bold;
justify-content: space-around;
flex-wrap: wrap;
align-items: center;
}
.product-quantity {
font-size: 50%;
}
.product-instock {
color: var(--green);
}
.product-nostock {
color: var(--red);
}
.product-add-to-cart {
padding: .25rem .5rem;
margin-top: .25rem;
margin-right: .5rem;
font-size: 1rem;
border: none;
background-color: gray;
/* Product Information*/
.product-link:after {
content: '';
position: absolute;
bottom: 0;
left: 15%;
width: 0%;
border-bottom: 2px solid var(--fg);
transition: 0.4s;
}
.product-link:hover:after {
width: 70%;
color: var(--hover);
}
.product-link:hover {
color: var(--hover);
}
.product-link:active {
color: var(--header);
}
.product-container {
width: 95%;
background-color: var(--bg-grad-3);
border-radius: 1rem 1rem 0rem 0rem;
display: flex;
flex-wrap: wrap;
flex-direction: row;
gap: 2rem 1.5rem;
padding: 1rem;
transition: all 0.2s;
overflow-y: scroll;
}
.product {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex: 4 0 1rem;
max-width: 40%;
gap: .5em .5em;
padding: .5rem 0.2rem;
background: var(--bg-secondary);
border-radius: .5rem .5rem;
border: .1em solid rgba(255, 255, 255, 15%);
transition: all 0.2s;
}
.product-fs {
height: 80%;
width: 80%;
font-size: 150%;
display: flex;
flex-direction: row;
justify-content: space-between;
gap: 2em 2em;
padding: .5rem 1rem 2rem 2rem;
/* background: var(--bg-secondary); */
border-radius: .5rem .5rem;
transition: all 0.2s;
}
.product-content-container {
display: flex;
flex-direction: row;
gap: 1em 1em;
padding: 0rem 1rem;
}
.product-image-preview {
max-height: 150px;
max-width: 150px;
}
.product-image {
max-height: 500px;
max-width: 500px;
}
.product-title {
font-size: 120%;
font-weight: bold;
}
.product-details {
display: flex;
flex-direction: column;
padding: 5% 0;
/* justify-content: center; */
/* align-items: center; */
gap: 1rem 1rem;
}
.product-description {
font-size: 70%;
}
.hide-overflow {
inline-size: 200px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.product-acquisition-pane {
font-size: 80%;
width: 100rem;
max-width: 20%;
background-color: var(--bg-secondary);
border: .3em solid rgba(255, 255, 255, 15%);
border-radius: .5rem;
padding: 1rem 1rem;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 1rem 1rem;
}
.product-price {
font-weight: bold;
}
.product-delivery {
font-size: 70%;
}
.product-postage {
font-size: 70%;
}
.product-stock {
display: flex;
flex-direction: row;
font-weight: bold;
justify-content: space-around;
flex-wrap: wrap;
align-items: center;
}
.product-quantity {
font-size: 50%;
}
.product-instock {
color: var(--green);
}
.product-nostock {
color: var(--red);
}
.product-add-to-cart {
padding: .25rem .5rem;
margin-top: .25rem;
margin-right: .5rem;
font-size: 1rem;
border: none;
background-color: gray;
}

View File

@ -1,174 +1,174 @@
:root {
--bg: #282c34;
--bg-secondary: #474d57;
--bg-grad-1: #484e58;
--bg-grad-2: #4e5560;
--bg-grad-3: #59616d;
--bg-grad-4: #606a7b;
--bg-grad-5: #606978;
--input: #4e5560;
--fg: #ABB2BF;
--header: #E06C75;
--link: #FFF;
--hover: #888;
--green: #98C379;
--red: #E06C75;
}
html {
background-color: var(--bg);
color: var(--fg);
font-size: 160%;
font-family: 'Courier New', Courier, 'Inter';
height: 100%;
}
body {
display: flex;
flex-direction: column;
gap: 1rem;
margin: 0;
height: 100%;
}
.navbar {
flex: 0 1 auto;
padding: 30px;
display: flex;
flex-direction: column;
gap: 1rem;
}
/* Navbar styling*/
nav {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 120%;
}
nav form {
display: flex;
justify-content: center;
align-items: center;
}
.search-bar {
padding: .25rem;
margin-top: .25rem;
font-size: 1rem;
width: 15rem;
border: none;
transition: width 0.4s ease-in-out;
background-color: var(--fg);
}
@media (min-width:1500px) {
.search-bar:focus {
width: 30rem;
}
}
.search-button {
padding: .25rem .5rem;
margin-top: .25rem;
margin-right: .5rem;
background: #ddd;
font-size: 1rem;
border: none;
background-color: orange;
}
.sticky {
position: -webkit-sticky;
position: sticky;
top: -2rem;
}
#logo {
width: 8rem;
height: auto;
}
*::-webkit-scrollbar,
*::-webkit-scrollbar-thumb {
width: 26px;
border-radius: 13px;
background-clip: padding-box;
border: 10px solid transparent;
color: var(--fg);
}
*::-webkit-scrollbar-thumb:hover{
color: var(--link);
}
*::-webkit-scrollbar-thumb {
box-shadow: inset 0 0 0 10px;
}
@media (max-width:1000px) {
.not-required {
display: none;
}
}
a {
text-decoration: none;
position: relative;
color: var(--link);
white-space: nowrap;
transition: 0.4s;
align-content: center;
}
a:after {
content: '';
position: absolute;
bottom: 0;
left: 15%;
width: 0%;
border-bottom: 2px solid var(--fg);
transition: 0.4s;
}
a:hover:after {
width: 70%;
color: var(--hover);
}
a:hover {
color: var(--hover);
}
a:active {
color: var(--header);
}
.categories {
background-color: var(--bg);
display: flex;
justify-content: center;
}
.category {
margin: .5rem 0rem;
padding: 0rem 1rem;
border-left: .125rem solid var(--fg);
}
.category:first-child {
border-left: none;
}
.container {
flex: 1 1 auto;
display: flex;
justify-content: flex-start;
align-items: center;
width: 100%;
gap: 1rem;
flex-direction: column;
flex-grow: 1;
height: 1%;
}
:root {
--bg: #282c34;
--bg-secondary: #474d57;
--bg-grad-1: #484e58;
--bg-grad-2: #4e5560;
--bg-grad-3: #59616d;
--bg-grad-4: #606a7b;
--bg-grad-5: #606978;
--input: #4e5560;
--fg: #ABB2BF;
--header: #E06C75;
--link: #FFF;
--hover: #888;
--green: #98C379;
--red: #E06C75;
}
html {
background-color: var(--bg);
color: var(--fg);
font-size: 160%;
font-family: 'Courier New', Courier, 'Inter';
height: 100%;
}
body {
display: flex;
flex-direction: column;
gap: 1rem;
margin: 0;
height: 100%;
}
.navbar {
flex: 0 1 auto;
padding: 30px;
display: flex;
flex-direction: column;
gap: 1rem;
}
/* Navbar styling*/
nav {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 120%;
}
nav form {
display: flex;
justify-content: center;
align-items: center;
}
.search-bar {
padding: .25rem;
margin-top: .25rem;
font-size: 1rem;
width: 15rem;
border: none;
transition: width 0.4s ease-in-out;
background-color: var(--fg);
}
@media (min-width:1500px) {
.search-bar:focus {
width: 30rem;
}
}
.search-button {
padding: .25rem .5rem;
margin-top: .25rem;
margin-right: .5rem;
background: #ddd;
font-size: 1rem;
border: none;
background-color: orange;
}
.sticky {
position: -webkit-sticky;
position: sticky;
top: -2rem;
}
#logo {
width: 8rem;
height: auto;
}
*::-webkit-scrollbar,
*::-webkit-scrollbar-thumb {
width: 26px;
border-radius: 13px;
background-clip: padding-box;
border: 10px solid transparent;
color: var(--fg);
}
*::-webkit-scrollbar-thumb:hover{
color: var(--link);
}
*::-webkit-scrollbar-thumb {
box-shadow: inset 0 0 0 10px;
}
@media (max-width:1000px) {
.not-required {
display: none;
}
}
a {
text-decoration: none;
position: relative;
color: var(--link);
white-space: nowrap;
transition: 0.4s;
align-content: center;
}
a:after {
content: '';
position: absolute;
bottom: 0;
left: 15%;
width: 0%;
border-bottom: 2px solid var(--fg);
transition: 0.4s;
}
a:hover:after {
width: 70%;
color: var(--hover);
}
a:hover {
color: var(--hover);
}
a:active {
color: var(--header);
}
.categories {
background-color: var(--bg);
display: flex;
justify-content: center;
}
.category {
margin: .5rem 0rem;
padding: 0rem 1rem;
border-left: .125rem solid var(--fg);
}
.category:first-child {
border-left: none;
}
.container {
flex: 1 1 auto;
display: flex;
justify-content: flex-start;
align-items: center;
width: 100%;
gap: 1rem;
flex-direction: column;
flex-grow: 1;
height: 1%;
}

View File

@ -1,22 +1,22 @@
<link rel="stylesheet" href="{{ url_for('static', filename='css/carparts.css') }}">
<div class="filter-pane">
<form action="" method="get" class="filter-items">
<div class="number-plate">
<span class="country-identifier">
<img src="https://mycarneedsa.com/assets/flint/img/flag_europe_gb.png" alt="">
</span>
<span class="vrn">
<input type="text" class="vrn-text" placeholder="YOUR REG" name="vrn">
</span>
</div>
<select class="product-filter not-required" name="filter">
<option value="relevance">Most Relevant</option>
<option value="price-lh">Price: Low -> High</option>
<option value="price-hl">Price: High -> Low</option>
</select>
<input type="submit" class="search-button" value="Filter">
</form>
</div>
<link rel="stylesheet" href="{{ url_for('static', filename='css/carparts.css') }}">
<div class="filter-pane">
<form action="" method="get" class="filter-items">
<div class="number-plate">
<span class="country-identifier">
<img src="https://mycarneedsa.com/assets/flint/img/flag_europe_gb.png" alt="">
</span>
<span class="vrn">
<input type="text" class="vrn-text" placeholder="YOUR REG" name="vrn">
</span>
</div>
<select class="product-filter not-required" name="filter">
<option value="relevance">Most Relevant</option>
<option value="price-lh">Price: Low -> High</option>
<option value="price-hl">Price: High -> Low</option>
</select>
<input type="submit" class="search-button" value="Filter">
</form>
</div>

View File

@ -1,20 +1,20 @@
<link rel="stylesheet" href="{{ url_for('static', filename='css/products.css') }}">
<div class="product-container">
{% if products != None %}
{% for product in products %}
<a href="/products/{{product.id}}" class="product product-link">
<div class="product-title">{{product.name}}</div>
<div class="product-content-container">
<img class="product-image-preview" src="{{url_for('static', filename='assets/img/products/' + product.image)}}" alt="Brake Disks" />
<div class="product-details">
<div class="product-price">£{{product.cost}}</div>
<div class="product-description hide-overflow ">{{product.description}}</div>
</div>
</div>
<input type="submit" class="product-add-to-cart" value="Add to Cart" />
</a>
{% endfor %}
{% endif %}
</div>
<link rel="stylesheet" href="{{ url_for('static', filename='css/products.css') }}">
<div class="product-container">
{% if products != None %}
{% for product in products %}
<a href="/products/{{product.id}}" class="product product-link">
<div class="product-title">{{product.name}}</div>
<div class="product-content-container">
<img class="product-image-preview" src="{{url_for('static', filename='assets/img/products/' + product.image)}}" alt="Brake Disks" />
<div class="product-details">
<div class="product-price">£{{product.cost}}</div>
<div class="product-description hide-overflow ">{{product.description}}</div>
</div>
</div>
<input type="submit" class="product-add-to-cart" value="Add to Cart" />
</a>
{% endfor %}
{% endif %}
</div>

View File

@ -1,51 +1,51 @@
<div class="navbar">
<nav>
<a href="/"><img src="{{url_for('static', filename='assets/img/wmgzon.png')}}" id="logo" class="not-required" alt="WMGZON Logo"></a>
<form action="" method="get">
<input type="text" name="search" placeholder="Find your favourite products" class="search-bar">
<input type="submit" class="search-button">
</form>
{% if user != None: %}
<a href="/logout">Welcome, {{ user.username }}</a>
{% else %}
<a href="/login">Login/Signup</a>
{% endif %}
</nav>
<centre>
{% if user != None and user.role == "Seller" %}
<div class="categories">
{# List all available seller tools #}
<a href="/products/add" class="category">Create Products</a>
<a href="/products/ownproducts" class="category">View My Products</a>
<a href="/products/stats" class="category">View Seller Stats</a>
</div>
{% endif %}
<div class="categories">
{# List all categories and ensure the selected one is highlighted #}
{% for c in categories %}
{% if category == c.name %}
<a style="color: cyan" href="/products/{{c.name}}" class="category">{{c.name}}</a>
{% else %}
<a href="/products/{{c.name}}" class="category">{{c.name}}</a>
{% endif %}
{% endfor %}
</div>
</centre>
</div>
{% with messages = get_flashed_messages(with_categories=true)%}
{% if messages %}
{% for category, message in messages %}
<label>
<input type="checkbox" class="alertCheckbox" autocomplete="off" />
<div class="alert {{category}}">
<span class="alertClose">X</span>
<span class="alertText">{{message}}
<br class="clear"/></span>
</div>
</label>
{% endfor%}
{% endif %}
{% endwith %}
<div class="navbar">
<nav>
<a href="/"><img src="{{url_for('static', filename='assets/img/wmgzon.png')}}" id="logo" class="not-required" alt="WMGZON Logo"></a>
<form action="" method="get">
<input type="text" name="search" placeholder="Find your favourite products" class="search-bar">
<input type="submit" class="search-button">
</form>
{% if user != None: %}
<a href="/logout">Welcome, {{ user.username }}</a>
{% else %}
<a href="/login">Login/Signup</a>
{% endif %}
</nav>
<centre>
{% if user != None and user.role == "Seller" %}
<div class="categories">
{# List all available seller tools #}
<a href="/products/add" class="category">Create Products</a>
<a href="/products/ownproducts" class="category">View My Products</a>
<a href="/products/stats" class="category">View Seller Stats</a>
</div>
{% endif %}
<div class="categories">
{# List all categories and ensure the selected one is highlighted #}
{% for c in categories %}
{% if category == c.name %}
<a style="color: cyan" href="/products/{{c.name}}" class="category">{{c.name}}</a>
{% else %}
<a href="/products/{{c.name}}" class="category">{{c.name}}</a>
{% endif %}
{% endfor %}
</div>
</centre>
</div>
{% with messages = get_flashed_messages(with_categories=true)%}
{% if messages %}
{% for category, message in messages %}
<label>
<input type="checkbox" class="alertCheckbox" autocomplete="off" />
<div class="alert {{category}}">
<span class="alertClose">X</span>
<span class="alertText">{{message}}
<br class="clear"/></span>
</div>
</label>
{% endfor%}
{% endif %}
{% endwith %}

View File

@ -1,28 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{{url_for('static', filename='css/style.css')}}" />
<link rel="stylesheet" href="{{url_for('static', filename='css/alerts.css')}}" />
<link href="http://fonts.cdnfonts.com/css/uk-number-plate" rel="stylesheet">
<!-- Inter font set used across the whole page -->
<link rel="preconnect" href="https://rsms.me/">
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
<title>WMGZON</title>
</head>
<body>
{% include 'header.html' %}
<div class="container">
{% if category is defined %}
{% set include_file = category+".html" %}
{% include include_file ignore missing %}
{% endif %}
{% include content %}
</div>
</body>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{{url_for('static', filename='css/style.css')}}" />
<link rel="stylesheet" href="{{url_for('static', filename='css/alerts.css')}}" />
<link href="http://fonts.cdnfonts.com/css/uk-number-plate" rel="stylesheet">
<!-- Inter font set used across the whole page -->
<link rel="preconnect" href="https://rsms.me/">
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
<title>WMGZON</title>
</head>
<body>
{% include 'header.html' %}
<div class="container">
{% if category is defined %}
{% set include_file = category+".html" %}
{% include include_file ignore missing %}
{% endif %}
{% include content %}
</div>
</body>
</html>

View File

@ -1,14 +1,14 @@
<link rel="stylesheet" href="{{url_for('static', filename='css/loginform.css')}}" />
<div id="input-form-wrap">
<h2>Login</h2>
<form class="input-form" method="POST">
<input type="text" id="username" name="username" placeholder="Username" required>
<input type="password" id="password" name="password" placeholder="Password" required>
<input type="submit" id="login" value="Login">
</form>
<div id="create-account-wrap">
<p>Not a member? <a href="signup">Create Account</a><p>
</div>
</div>
<link rel="stylesheet" href="{{url_for('static', filename='css/loginform.css')}}" />
<div id="input-form-wrap">
<h2>Login</h2>
<form class="input-form" method="POST">
<input type="text" id="username" name="username" placeholder="Username" required>
<input type="password" id="password" name="password" placeholder="Password" required>
<input type="submit" id="login" value="Login">
</form>
<div id="create-account-wrap">
<p>Not a member? <a href="signup">Create Account</a><p>
</div>
</div>

View File

@ -1,29 +1,29 @@
<link rel="stylesheet" href="{{url_for('static', filename='css/loginform.css')}}" />
<div id="input-form-wrap">
<h2>Create New Product</h2>
<form class="input-form" method="POST" enctype="multipart/form-data">
<div class="input-form-row">
<input type="text" id="name" name="name" placeholder="Product Name" required>
<input type="file" id="image" name="image" accept="image/x" required>
</div>
<textarea id="description" name="description" placeholder="Product Description" required></textarea>
<select name="category" id="category">
{% for category in categories %}
<option value="{{category.id}}">{{category.name}}</option>
{% endfor %}
</select>
<div class="input-form-row">
<input type="number" id="cost" name="cost" placeholder=10.99 min=0 step=any required>
<input type="number" id="quantity" name="quantity" placeholder=0 min=0 required>
</div>
<input type="submit" id="Create Product" value="Create Product">
</form>
<div id="create-account-wrap">
<p>Want to view all of your products? <a href="">Click Here</a><p>
</div>
</div>
<link rel="stylesheet" href="{{url_for('static', filename='css/loginform.css')}}" />
<div id="input-form-wrap">
<h2>Create New Product</h2>
<form class="input-form" method="POST" enctype="multipart/form-data">
<div class="input-form-row">
<input type="text" id="name" name="name" placeholder="Product Name" required>
<input type="file" id="image" name="image" accept="image/x" required>
</div>
<textarea id="description" name="description" placeholder="Product Description" required></textarea>
<select name="category" id="category">
{% for category in categories %}
<option value="{{category.id}}">{{category.name}}</option>
{% endfor %}
</select>
<div class="input-form-row">
<input type="number" id="cost" name="cost" placeholder=10.99 min=0 step=any required>
<input type="number" id="quantity" name="quantity" placeholder=0 min=0 required>
</div>
<input type="submit" id="Create Product" value="Create Product">
</form>
<div id="create-account-wrap">
<p>Want to view all of your products? <a href="">Click Here</a><p>
</div>
</div>

View File

@ -1,65 +1,65 @@
<link rel="stylesheet" href="{{ url_for('static', filename='css/products.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/loginform.css') }}">
<div class="product-fs">
{% if product != None %}
{% if user.id == product.sellerID %}
<form class="input-form" method="POST" action="/products/update/{{product.id}}" enctype="multipart/form-data">
<div class="product-title">
<input type="text" id="name" name="name" placeholder="Product Name" value="{{product.name}}" required>
</div>
<div class="product-information">
<div class="product-image">
<img src="{{url_for('static', filename='assets/img/products/' + product.image)}}" alt="Brake Disks" height="auto" width="150px" />
<input type="file" id="image" name="image" accept="image/x">
</div>
<div>
<div class="input-form-row">
<input type="number" id="cost" name="cost" placeholder=10.99 min=0 step=any value="{{product.cost}}"required>
<input type="textarea" id="description" name="description" placeholder="Product Description" value="{{product.description}}" required>
</div>
<div class="input-form-row">
<select name="category" id="category">
{% for category in categories %}
{% if category.id == product.category %}
<option value="{{category.id}}" selected>{{category.name}}</option>
{% else %}
<option value="{{category.id}}">{{category.name}}</option>
{% endif%}
{% endfor %}
</select>
</div>
<div class="input-form-row">
<input type="number" id="quantity" name="quantity" placeholder=0 min=0 value="{{product.quantityAvailable}}" required>
</div>
</div>
</div>
<input type="submit" id="Create Product" value="Create Product">
</form>
<div class="product-add-to-cart"></div>
{% else %}
<img class="product-image" src="{{url_for('static', filename='assets/img/products/' + product.image)}}" alt="Brake Disks"/>
<div class="product-details">
<div class="product-title">{{product.name}}</div>
<div class="product-description">{{product.description}}</div>
</div>
<div class="product-acquisition-pane">
<div class="product-price">£{{product.cost}}</div>
<div class="product-delivery">Earliest Delivery Friday 24th December</div>
<div class="product-postage">+£{{product.cost}} P&P</div>
<div class="product-stock">
{% if product.quantityAvailable > 0 %}
<div class="product-instock">In Stock</div>
<div class="product-quantity">{{product.quantityAvailable}} Available</div>
<input type="submit" class="product-add-to-cart" value="Add to Cart" />
{% else %}
<div class="product-nostock">Out of Stock</div>
{% endif %}
</div>
</div>
{% endif %}
{% endif %}
</div>
<link rel="stylesheet" href="{{ url_for('static', filename='css/products.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/loginform.css') }}">
<div class="product-fs">
{% if product != None %}
{% if user.id == product.sellerID %}
<form class="input-form" method="POST" action="/products/update/{{product.id}}" enctype="multipart/form-data">
<div class="product-title">
<input type="text" id="name" name="name" placeholder="Product Name" value="{{product.name}}" required>
</div>
<div class="product-information">
<div class="product-image">
<img src="{{url_for('static', filename='assets/img/products/' + product.image)}}" alt="Brake Disks" height="auto" width="150px" />
<input type="file" id="image" name="image" accept="image/x">
</div>
<div>
<div class="input-form-row">
<input type="number" id="cost" name="cost" placeholder=10.99 min=0 step=any value="{{product.cost}}"required>
<input type="textarea" id="description" name="description" placeholder="Product Description" value="{{product.description}}" required>
</div>
<div class="input-form-row">
<select name="category" id="category">
{% for category in categories %}
{% if category.id == product.category %}
<option value="{{category.id}}" selected>{{category.name}}</option>
{% else %}
<option value="{{category.id}}">{{category.name}}</option>
{% endif%}
{% endfor %}
</select>
</div>
<div class="input-form-row">
<input type="number" id="quantity" name="quantity" placeholder=0 min=0 value="{{product.quantityAvailable}}" required>
</div>
</div>
</div>
<input type="submit" id="Create Product" value="Create Product">
</form>
<div class="product-add-to-cart"></div>
{% else %}
<img class="product-image" src="{{url_for('static', filename='assets/img/products/' + product.image)}}" alt="Brake Disks"/>
<div class="product-details">
<div class="product-title">{{product.name}}</div>
<div class="product-description">{{product.description}}</div>
</div>
<div class="product-acquisition-pane">
<div class="product-price">£{{product.cost}}</div>
<div class="product-delivery">Earliest Delivery Friday 24th December</div>
<div class="product-postage">+£{{product.cost}} P&P</div>
<div class="product-stock">
{% if product.quantityAvailable > 0 %}
<div class="product-instock">In Stock</div>
<div class="product-quantity">{{product.quantityAvailable}} Available</div>
<input type="submit" class="product-add-to-cart" value="Add to Cart" />
{% else %}
<div class="product-nostock">Out of Stock</div>
{% endif %}
</div>
</div>
{% endif %}
{% endif %}
</div>

View File

@ -1,27 +1,27 @@
<link rel="stylesheet" href="{{url_for('static', filename='css/loginform.css')}}" />
<div id="input-form-wrap">
<h2>Sign Up</h2>
<form class="input-form" method="POST">
<div class="input-form-row">
<input type="text" id="firstname" name="firstname" placeholder="First Name" required>
<input type="text" id="lastname" name="lastname" placeholder="Last Name" required>
</div>
<div class="input-form-row">
<input type="text" id="username" name="username" placeholder="Username" required>
<input type="email" id="email" name="email" placeholder="Email Address" required>
</div>
<input type="password" id="password" name="password" minlength=8 placeholder="Password" required>
<label class="checkbox">Signup as a Seller?
<input type="checkbox" id="seller" name="seller"/>
<span class="checkmark"></span>
</label>
<input type="submit" id="Sign Up" value="Sign Up">
</form>
<div id="create-account-wrap">
<p>Already have an account? <a href="login">Login</a><p>
</div>
</div>
<link rel="stylesheet" href="{{url_for('static', filename='css/loginform.css')}}" />
<div id="input-form-wrap">
<h2>Sign Up</h2>
<form class="input-form" method="POST">
<div class="input-form-row">
<input type="text" id="firstname" name="firstname" placeholder="First Name" required>
<input type="text" id="lastname" name="lastname" placeholder="Last Name" required>
</div>
<div class="input-form-row">
<input type="text" id="username" name="username" placeholder="Username" required>
<input type="email" id="email" name="email" placeholder="Email Address" required>
</div>
<input type="password" id="password" name="password" minlength=8 placeholder="Password" required>
<label class="checkbox">Signup as a Seller?
<input type="checkbox" id="seller" name="seller"/>
<span class="checkmark"></span>
</label>
<input type="submit" id="Sign Up" value="Sign Up">
</form>
<div id="create-account-wrap">
<p>Already have an account? <a href="login">Login</a><p>
</div>
</div>

View File

@ -1,4 +1,4 @@
import os
# Setup test environment variables
os.environ["ENVIRON"] = "test"
import os
# Setup test environment variables
os.environ["ENVIRON"] = "test"

View File

@ -1,3 +1,3 @@
# Ensure test environment is set before using
# Runs the database creation scripts
import scripts.create_database
# Ensure test environment is set before using
# Runs the database creation scripts
import scripts.create_database

View File

@ -1,70 +1,70 @@
import pytest
import sqlite3
from datetime import datetime
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
)
# 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()
with pytest.raises(sqlite3.IntegrityError):
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
assert len(db.read_all("Books")) == 9
assert db.read_all("Phones") is None
# Tests that products can be refined by search term
def test_search_term():
db = ProductController()
# Check each search term for correct amount of test products
assert len(db.read_all(search_term="test")) == 33
assert len(db.read_all("Car Parts", "test")) == 9
assert len(db.read_all(search_term="product")) == 1
assert db.read_all(search_term="not_test") is None
# Test we the same product details get returned from the database
def test_read_product():
db = ProductController()
# Test the same product is returned
new_product = db.read("product")
assert isinstance(new_product, list)
assert isinstance(new_product[0], Product)
# Update the ID on the item as database assigns new id
product.id = new_product[0].id
assert new_product[0].__dict__ == product.__dict__
import pytest
import sqlite3
from datetime import datetime
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
)
# 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()
with pytest.raises(sqlite3.IntegrityError):
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
assert len(db.read_all("Books")) == 9
assert db.read_all("Phones") is None
# Tests that products can be refined by search term
def test_search_term():
db = ProductController()
# Check each search term for correct amount of test products
assert len(db.read_all(search_term="test")) == 33
assert len(db.read_all("Car Parts", "test")) == 9
assert len(db.read_all(search_term="product")) == 1
assert db.read_all(search_term="not_test") is None
# Test we the same product details get returned from the database
def test_read_product():
db = ProductController()
# Test the same product is returned
new_product = db.read("product")
assert isinstance(new_product, list)
assert isinstance(new_product[0], Product)
# Update the ID on the item as database assigns new id
product.id = new_product[0].id
assert new_product[0].__dict__ == product.__dict__

View File

@ -1,74 +1,74 @@
import pytest
import sqlite3
from controllers.database.user import UserController
from models.users.customer import Customer
from models.users.seller import Seller
customer = Customer(
"testcustomer",
"Password1",
"firstname",
"lastname",
"test@test",
"123456789"
)
seller = Seller(
"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()
# Test the same user is returned
user = db.read(customer.username)
assert isinstance(user, Customer)
# Update the ID on the item as database assigns new id
customer.id = user.id
assert user.__dict__ == customer.__dict__
# Tests a new seller can be created
def test_create_seller():
db = UserController()
db.create(seller)
# Test that the same seller details get returned from the database
def test_read_seller():
db = UserController()
# Test the same user is returned
user = db.read(seller.username)
assert isinstance(user, Seller)
# Update the ID on the item as database assigns new id
seller.id = user.id
user.store = ""
assert user.__dict__ == seller.__dict__
import pytest
import sqlite3
from controllers.database.user import UserController
from models.users.customer import Customer
from models.users.seller import Seller
customer = Customer(
"testcustomer",
"Password1",
"firstname",
"lastname",
"test@test",
"123456789"
)
seller = Seller(
"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()
# Test the same user is returned
user = db.read(customer.username)
assert isinstance(user, Customer)
# Update the ID on the item as database assigns new id
customer.id = user.id
assert user.__dict__ == customer.__dict__
# Tests a new seller can be created
def test_create_seller():
db = UserController()
db.create(seller)
# Test that the same seller details get returned from the database
def test_read_seller():
db = UserController()
# Test the same user is returned
user = db.read(seller.username)
assert isinstance(user, Seller)
# Update the ID on the item as database assigns new id
seller.id = user.id
user.store = ""
assert user.__dict__ == seller.__dict__

View File

@ -1,11 +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", "./utils"]
result = pep8style.check_files(dirs)
assert result.total_errors == 0
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", "./utils"]
result = pep8style.check_files(dirs)
assert result.total_errors == 0

View File

@ -1,46 +1,46 @@
import os
import uuid
import pathlib
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
def allowed_file(filename) -> bool:
""" Ensures only filenames ending with the correct extension are allowed.
Note: This does not verify that the content inside of the file
matches the type specified
"""
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
def save_image(file) -> str | None:
""" Saves a given file to disk with a random UUID4 generated
filename. Returns the filename as a string.
"""
# Ensure that the correct file type is uploaded
if file is None or not allowed_file(file.filename):
return None
# Create the product object and push to database
filename = str(uuid.uuid4()) + pathlib.Path(file.filename).suffix
path = os.environ.get('FILESTORE')
file.save(os.path.join(path, filename))
return filename
def create_directory(dir: str):
""" Creates the given directory string is not alreay made """
try:
os.makedirs(dir)
except FileExistsError:
pass
def remove_file(dir: str):
""" Removes a given file if it is present at the given dir """
try:
os.remove(dir)
except FileNotFoundError:
pass
import os
import uuid
import pathlib
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
def allowed_file(filename) -> bool:
""" Ensures only filenames ending with the correct extension are allowed.
Note: This does not verify that the content inside of the file
matches the type specified
"""
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
def save_image(file) -> str | None:
""" Saves a given file to disk with a random UUID4 generated
filename. Returns the filename as a string.
"""
# Ensure that the correct file type is uploaded
if file is None or not allowed_file(file.filename):
return None
# Create the product object and push to database
filename = str(uuid.uuid4()) + pathlib.Path(file.filename).suffix
path = os.environ.get('FILESTORE')
file.save(os.path.join(path, filename))
return filename
def create_directory(dir: str):
""" Creates the given directory string is not alreay made """
try:
os.makedirs(dir)
except FileExistsError:
pass
def remove_file(dir: str):
""" Removes a given file if it is present at the given dir """
try:
os.remove(dir)
except FileNotFoundError:
pass

View File

@ -1,26 +1,26 @@
from flask import session
from models.users.user import User
from controllers.database.user import UserController
def is_logged_in() -> User | None:
""" Returns the user object if the user is logged in
Otherwise returns a None type
"""
user_id = session.get('user_id')
if user_id is not None:
db = UserController()
return db.read_id(user_id)
return None
def is_role(role: str) -> bool:
""" Function that returns true if the user is logged in as"""
user = is_logged_in()
if user is not None:
return user.role == role
# User isn't logged in
return False
from flask import session
from models.users.user import User
from controllers.database.user import UserController
def is_logged_in() -> User | None:
""" Returns the user object if the user is logged in
Otherwise returns a None type
"""
user_id = session.get('user_id')
if user_id is not None:
db = UserController()
return db.read_id(user_id)
return None
def is_role(role: str) -> bool:
""" Function that returns true if the user is logged in as"""
user = is_logged_in()
if user is not None:
return user.role == role
# User isn't logged in
return False