Updated line endings to be in line with UNIX style
This commit is contained in:
parent
3eea1d946a
commit
5a20a8d7c0
578
.gitignore
vendored
578
.gitignore
vendored
@ -1,289 +1,289 @@
|
|||||||
# Created by 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
|
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,python,flask,web
|
||||||
|
|
||||||
|
|
||||||
### SQLite 3 Database ###
|
### SQLite 3 Database ###
|
||||||
data/
|
data/
|
||||||
static/assets/img/products/
|
static/assets/img/products/
|
||||||
|
|
||||||
### CICD Registration files ###
|
### CICD Registration files ###
|
||||||
cicd/runner-data
|
cicd/runner-data
|
||||||
|
|
||||||
### Flask ###
|
### Flask ###
|
||||||
instance/*
|
instance/*
|
||||||
!instance/.gitignore
|
!instance/.gitignore
|
||||||
.webassets-cache
|
.webassets-cache
|
||||||
.env
|
.env
|
||||||
|
|
||||||
### Flask.Python Stack ###
|
### Flask.Python Stack ###
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
*$py.class
|
*$py.class
|
||||||
|
|
||||||
# C extensions
|
# C extensions
|
||||||
*.so
|
*.so
|
||||||
|
|
||||||
# Distribution / packaging
|
# Distribution / packaging
|
||||||
.Python
|
.Python
|
||||||
build/
|
build/
|
||||||
develop-eggs/
|
develop-eggs/
|
||||||
dist/
|
dist/
|
||||||
downloads/
|
downloads/
|
||||||
eggs/
|
eggs/
|
||||||
.eggs/
|
.eggs/
|
||||||
lib/
|
lib/
|
||||||
lib64/
|
lib64/
|
||||||
parts/
|
parts/
|
||||||
sdist/
|
sdist/
|
||||||
var/
|
var/
|
||||||
wheels/
|
wheels/
|
||||||
share/python-wheels/
|
share/python-wheels/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
.installed.cfg
|
.installed.cfg
|
||||||
*.egg
|
*.egg
|
||||||
MANIFEST
|
MANIFEST
|
||||||
|
|
||||||
# PyInstaller
|
# PyInstaller
|
||||||
# Usually these files are written by a python script from a template
|
# 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.
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
*.manifest
|
*.manifest
|
||||||
*.spec
|
*.spec
|
||||||
|
|
||||||
# Installer logs
|
# Installer logs
|
||||||
pip-log.txt
|
pip-log.txt
|
||||||
pip-delete-this-directory.txt
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
# Unit test / coverage reports
|
# Unit test / coverage reports
|
||||||
htmlcov/
|
htmlcov/
|
||||||
.tox/
|
.tox/
|
||||||
.nox/
|
.nox/
|
||||||
.coverage
|
.coverage
|
||||||
.coverage.*
|
.coverage.*
|
||||||
.cache
|
.cache
|
||||||
nosetests.xml
|
nosetests.xml
|
||||||
coverage.xml
|
coverage.xml
|
||||||
*.cover
|
*.cover
|
||||||
*.py,cover
|
*.py,cover
|
||||||
.hypothesis/
|
.hypothesis/
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
cover/
|
cover/
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
*.mo
|
*.mo
|
||||||
*.pot
|
*.pot
|
||||||
|
|
||||||
# Django stuff:
|
# Django stuff:
|
||||||
*.log
|
*.log
|
||||||
local_settings.py
|
local_settings.py
|
||||||
db.sqlite3
|
db.sqlite3
|
||||||
db.sqlite3-journal
|
db.sqlite3-journal
|
||||||
|
|
||||||
# Flask stuff:
|
# Flask stuff:
|
||||||
instance/
|
instance/
|
||||||
|
|
||||||
# Scrapy stuff:
|
# Scrapy stuff:
|
||||||
.scrapy
|
.scrapy
|
||||||
|
|
||||||
# Sphinx documentation
|
# Sphinx documentation
|
||||||
docs/_build/
|
docs/_build/
|
||||||
|
|
||||||
# PyBuilder
|
# PyBuilder
|
||||||
.pybuilder/
|
.pybuilder/
|
||||||
target/
|
target/
|
||||||
|
|
||||||
# Jupyter Notebook
|
# Jupyter Notebook
|
||||||
.ipynb_checkpoints
|
.ipynb_checkpoints
|
||||||
|
|
||||||
# IPython
|
# IPython
|
||||||
profile_default/
|
profile_default/
|
||||||
ipython_config.py
|
ipython_config.py
|
||||||
|
|
||||||
# pyenv
|
# pyenv
|
||||||
# For a library or package, you might want to ignore these files since the code is
|
# 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:
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
# .python-version
|
# .python-version
|
||||||
|
|
||||||
# pipenv
|
# pipenv
|
||||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
# 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
|
# 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
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
# install all needed dependencies.
|
# install all needed dependencies.
|
||||||
#Pipfile.lock
|
#Pipfile.lock
|
||||||
|
|
||||||
# poetry
|
# poetry
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
# 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
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
# commonly ignored for libraries.
|
# commonly ignored for libraries.
|
||||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
#poetry.lock
|
#poetry.lock
|
||||||
|
|
||||||
# pdm
|
# pdm
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
#pdm.lock
|
#pdm.lock
|
||||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||||
# in version control.
|
# in version control.
|
||||||
# https://pdm.fming.dev/#use-with-ide
|
# https://pdm.fming.dev/#use-with-ide
|
||||||
.pdm.toml
|
.pdm.toml
|
||||||
|
|
||||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
__pypackages__/
|
__pypackages__/
|
||||||
|
|
||||||
# Celery stuff
|
# Celery stuff
|
||||||
celerybeat-schedule
|
celerybeat-schedule
|
||||||
celerybeat.pid
|
celerybeat.pid
|
||||||
|
|
||||||
# SageMath parsed files
|
# SageMath parsed files
|
||||||
*.sage.py
|
*.sage.py
|
||||||
|
|
||||||
# Environments
|
# Environments
|
||||||
.venv
|
.venv
|
||||||
env/
|
env/
|
||||||
venv/
|
venv/
|
||||||
ENV/
|
ENV/
|
||||||
env.bak/
|
env.bak/
|
||||||
venv.bak/
|
venv.bak/
|
||||||
|
|
||||||
# Spyder project settings
|
# Spyder project settings
|
||||||
.spyderproject
|
.spyderproject
|
||||||
.spyproject
|
.spyproject
|
||||||
|
|
||||||
# Rope project settings
|
# Rope project settings
|
||||||
.ropeproject
|
.ropeproject
|
||||||
|
|
||||||
# mkdocs documentation
|
# mkdocs documentation
|
||||||
/site
|
/site
|
||||||
|
|
||||||
# mypy
|
# mypy
|
||||||
.mypy_cache/
|
.mypy_cache/
|
||||||
.dmypy.json
|
.dmypy.json
|
||||||
dmypy.json
|
dmypy.json
|
||||||
|
|
||||||
# Pyre type checker
|
# Pyre type checker
|
||||||
.pyre/
|
.pyre/
|
||||||
|
|
||||||
# pytype static type analyzer
|
# pytype static type analyzer
|
||||||
.pytype/
|
.pytype/
|
||||||
|
|
||||||
# Cython debug symbols
|
# Cython debug symbols
|
||||||
cython_debug/
|
cython_debug/
|
||||||
|
|
||||||
# PyCharm
|
# PyCharm
|
||||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
# 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
|
# 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
|
# 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.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
#.idea/
|
||||||
|
|
||||||
### Python ###
|
### Python ###
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
|
|
||||||
# C extensions
|
# C extensions
|
||||||
|
|
||||||
# Distribution / packaging
|
# Distribution / packaging
|
||||||
|
|
||||||
# PyInstaller
|
# PyInstaller
|
||||||
# Usually these files are written by a python script from a template
|
# 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.
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
|
||||||
# Installer logs
|
# Installer logs
|
||||||
|
|
||||||
# Unit test / coverage reports
|
# Unit test / coverage reports
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
|
|
||||||
# Django stuff:
|
# Django stuff:
|
||||||
|
|
||||||
# Flask stuff:
|
# Flask stuff:
|
||||||
|
|
||||||
# Scrapy stuff:
|
# Scrapy stuff:
|
||||||
|
|
||||||
# Sphinx documentation
|
# Sphinx documentation
|
||||||
|
|
||||||
# PyBuilder
|
# PyBuilder
|
||||||
|
|
||||||
# Jupyter Notebook
|
# Jupyter Notebook
|
||||||
|
|
||||||
# IPython
|
# IPython
|
||||||
|
|
||||||
# pyenv
|
# pyenv
|
||||||
# For a library or package, you might want to ignore these files since the code is
|
# 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:
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
# .python-version
|
# .python-version
|
||||||
|
|
||||||
# pipenv
|
# pipenv
|
||||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
# 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
|
# 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
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
# install all needed dependencies.
|
# install all needed dependencies.
|
||||||
|
|
||||||
# poetry
|
# poetry
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
# 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
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
# commonly ignored for libraries.
|
# commonly ignored for libraries.
|
||||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
|
|
||||||
# pdm
|
# pdm
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
# 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
|
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||||
# in version control.
|
# in version control.
|
||||||
# https://pdm.fming.dev/#use-with-ide
|
# https://pdm.fming.dev/#use-with-ide
|
||||||
|
|
||||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
|
|
||||||
# Celery stuff
|
# Celery stuff
|
||||||
|
|
||||||
# SageMath parsed files
|
# SageMath parsed files
|
||||||
|
|
||||||
# Environments
|
# Environments
|
||||||
|
|
||||||
# Spyder project settings
|
# Spyder project settings
|
||||||
|
|
||||||
# Rope project settings
|
# Rope project settings
|
||||||
|
|
||||||
# mkdocs documentation
|
# mkdocs documentation
|
||||||
|
|
||||||
# mypy
|
# mypy
|
||||||
|
|
||||||
# Pyre type checker
|
# Pyre type checker
|
||||||
|
|
||||||
# pytype static type analyzer
|
# pytype static type analyzer
|
||||||
|
|
||||||
# Cython debug symbols
|
# Cython debug symbols
|
||||||
|
|
||||||
# PyCharm
|
# PyCharm
|
||||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
# 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
|
# 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
|
# 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.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
|
|
||||||
### Python Patch ###
|
### Python Patch ###
|
||||||
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
|
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
|
||||||
poetry.toml
|
poetry.toml
|
||||||
|
|
||||||
# ruff
|
# ruff
|
||||||
.ruff_cache/
|
.ruff_cache/
|
||||||
|
|
||||||
# LSP config files
|
# LSP config files
|
||||||
pyrightconfig.json
|
pyrightconfig.json
|
||||||
|
|
||||||
### VisualStudioCode ###
|
### VisualStudioCode ###
|
||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/settings.json
|
!.vscode/settings.json
|
||||||
!.vscode/tasks.json
|
!.vscode/tasks.json
|
||||||
!.vscode/launch.json
|
!.vscode/launch.json
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
!.vscode/*.code-snippets
|
!.vscode/*.code-snippets
|
||||||
|
|
||||||
# Local History for Visual Studio Code
|
# Local History for Visual Studio Code
|
||||||
.history/
|
.history/
|
||||||
|
|
||||||
# Built Visual Studio Code Extensions
|
# Built Visual Studio Code Extensions
|
||||||
*.vsix
|
*.vsix
|
||||||
|
|
||||||
### VisualStudioCode Patch ###
|
### VisualStudioCode Patch ###
|
||||||
# Ignore all local history of files
|
# Ignore all local history of files
|
||||||
.history
|
.history
|
||||||
.ionide
|
.ionide
|
||||||
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,python,flask,web
|
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,python,flask,web
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
variables:
|
variables:
|
||||||
ENVIRON: "test"
|
ENVIRON: "test"
|
||||||
|
|
||||||
pytest:
|
pytest:
|
||||||
stage: test
|
stage: test
|
||||||
script:
|
script:
|
||||||
- cd /builds/u5500327/wmgzon
|
- cd /builds/u5500327/wmgzon
|
||||||
# - python -m venv .venv
|
# - python -m venv .venv
|
||||||
# - source /bin/activate
|
# - source /bin/activate
|
||||||
- pip install -r requirements.txt
|
- pip install -r requirements.txt
|
||||||
- pytest --disable-warnings
|
- pytest --disable-warnings
|
84
README.md
84
README.md
@ -1,43 +1,43 @@
|
|||||||
---
|
---
|
||||||
runme:
|
runme:
|
||||||
id: 01HK0BF4BTBSKR9VWAP1KGD2S7
|
id: 01HK0BF4BTBSKR9VWAP1KGD2S7
|
||||||
version: v2.0
|
version: v2.0
|
||||||
---
|
---
|
||||||
|
|
||||||
# WMGZON
|
# WMGZON
|
||||||
|
|
||||||
`Flask web application serving WMGZON and its relevant backend services.`
|
`Flask web application serving WMGZON and its relevant backend services.`
|
||||||
|
|
||||||
## Initialisation
|
## Initialisation
|
||||||
|
|
||||||
To start you need to create a virtual environment and load in the required dependencies for the project
|
To start you need to create a virtual environment and load in the required dependencies for the project
|
||||||
|
|
||||||
```sh {"closeTerminalOnSuccess":"false","id":"01HK0BJCK9BR05J127F1X0RZP9"}
|
```sh {"closeTerminalOnSuccess":"false","id":"01HK0BJCK9BR05J127F1X0RZP9"}
|
||||||
python -m venv .venv
|
python -m venv .venv
|
||||||
./.venv/Scripts/Activate.ps1
|
./.venv/Scripts/Activate.ps1
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
To run the full suite of unit tests for the webapp simply run the following command in the venv
|
To run the full suite of unit tests for the webapp simply run the following command in the venv
|
||||||
```sh
|
```sh
|
||||||
pytest
|
pytest
|
||||||
```
|
```
|
||||||
|
|
||||||
## Running
|
## Running
|
||||||
|
|
||||||
### Pre-Requisites
|
### Pre-Requisites
|
||||||
|
|
||||||
- Docker daemon is installed and running.
|
- Docker daemon is installed and running.
|
||||||
- Docker compose is installed.
|
- Docker compose is installed.
|
||||||
|
|
||||||
### Instructions
|
### Instructions
|
||||||
|
|
||||||
In order to run the web app, simply use the command
|
In order to run the web app, simply use the command
|
||||||
|
|
||||||
```sh {"id":"01HKD0VRADDYQ92W22JN1FHA03"}
|
```sh {"id":"01HKD0VRADDYQ92W22JN1FHA03"}
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
to run the container in a detatched mode.
|
to run the container in a detatched mode.
|
54
app.py
54
app.py
@ -1,27 +1,27 @@
|
|||||||
from flask import Flask
|
from flask import Flask
|
||||||
from os import environ
|
from os import environ
|
||||||
from controllers.web.endpoints import blueprint
|
from controllers.web.endpoints import blueprint
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Main entrypoint for Flask application.
|
Main entrypoint for Flask application.
|
||||||
Initialises any components that are needed at runtime such as the
|
Initialises any components that are needed at runtime such as the
|
||||||
Database manager...
|
Database manager...
|
||||||
'''
|
'''
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
# Set app secret key to sign session cookies
|
# Set app secret key to sign session cookies
|
||||||
secret_key = environ.get("APPSECRET")
|
secret_key = environ.get("APPSECRET")
|
||||||
if secret_key is None:
|
if secret_key is None:
|
||||||
# NO Secret Key set!
|
# NO Secret Key set!
|
||||||
print("No app secret set, please set one before deploying in production")
|
print("No app secret set, please set one before deploying in production")
|
||||||
app.secret_key = "DEFAULTKEY"
|
app.secret_key = "DEFAULTKEY"
|
||||||
else:
|
else:
|
||||||
app.secret_key = secret_key
|
app.secret_key = secret_key
|
||||||
|
|
||||||
# Register a blueprint
|
# Register a blueprint
|
||||||
app.register_blueprint(blueprint)
|
app.register_blueprint(blueprint)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(debug=True, host="0.0.0.0", port=8080)
|
app.run(debug=True, host="0.0.0.0", port=8080)
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
version: '3.8'
|
version: '3.8'
|
||||||
services:
|
services:
|
||||||
gitlab-runner:
|
gitlab-runner:
|
||||||
image: gitlab/gitlab-runner:latest
|
image: gitlab/gitlab-runner:latest
|
||||||
volumes:
|
volumes:
|
||||||
- ./runner-data:/etc/gitlab-runner
|
- ./runner-data:/etc/gitlab-runner
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
@ -1,63 +1,63 @@
|
|||||||
from .database import DatabaseController
|
from .database import DatabaseController
|
||||||
from models.category import Category
|
from models.category import Category
|
||||||
|
|
||||||
|
|
||||||
class CategoryController(DatabaseController):
|
class CategoryController(DatabaseController):
|
||||||
FIELDS = ['id', 'name']
|
FIELDS = ['id', 'name']
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
def create(self, category: Category):
|
def create(self, category: Category):
|
||||||
params = [
|
params = [
|
||||||
category.name,
|
category.name,
|
||||||
]
|
]
|
||||||
|
|
||||||
self._conn.execute(
|
self._conn.execute(
|
||||||
"INSERT INTO Categories (name) VALUES (?)",
|
"INSERT INTO Categories (name) VALUES (?)",
|
||||||
params
|
params
|
||||||
)
|
)
|
||||||
self._conn.commit()
|
self._conn.commit()
|
||||||
|
|
||||||
def read(self, id: int = 0) -> Category | None:
|
def read(self, id: int = 0) -> Category | None:
|
||||||
params = [
|
params = [
|
||||||
id
|
id
|
||||||
]
|
]
|
||||||
|
|
||||||
cursor = self._conn.execute(
|
cursor = self._conn.execute(
|
||||||
"SELECT * FROM Categories WHERE id = ?",
|
"SELECT * FROM Categories WHERE id = ?",
|
||||||
params
|
params
|
||||||
)
|
)
|
||||||
row = cursor.fetchone()
|
row = cursor.fetchone()
|
||||||
|
|
||||||
if row is None:
|
if row is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
params = dict(zip(self.FIELDS, row))
|
params = dict(zip(self.FIELDS, row))
|
||||||
obj = self.new_instance(Category, params)
|
obj = self.new_instance(Category, params)
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def read_all(self) -> list[Category] | None:
|
def read_all(self) -> list[Category] | None:
|
||||||
cursor = self._conn.execute(
|
cursor = self._conn.execute(
|
||||||
"SELECT * FROM Categories",
|
"SELECT * FROM Categories",
|
||||||
)
|
)
|
||||||
rows = cursor.fetchall()
|
rows = cursor.fetchall()
|
||||||
|
|
||||||
if rows is None:
|
if rows is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
categories = list()
|
categories = list()
|
||||||
|
|
||||||
for category in rows:
|
for category in rows:
|
||||||
params = dict(zip(self.FIELDS, category))
|
params = dict(zip(self.FIELDS, category))
|
||||||
obj = self.new_instance(Category, params)
|
obj = self.new_instance(Category, params)
|
||||||
categories.append(obj)
|
categories.append(obj)
|
||||||
|
|
||||||
return categories
|
return categories
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
print("Doing work")
|
print("Doing work")
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
print("Doing work")
|
print("Doing work")
|
||||||
|
@ -1,81 +1,81 @@
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Mapping, Any
|
from typing import Mapping, Any
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
class DatabaseController(ABC):
|
class DatabaseController(ABC):
|
||||||
""" Abstract Base Class to handle database access for each component
|
""" Abstract Base Class to handle database access for each component
|
||||||
in the web app
|
in the web app
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__data_dir = "./data/"
|
__data_dir = "./data/"
|
||||||
__db_name = "wmgzon.db"
|
__db_name = "wmgzon.db"
|
||||||
|
|
||||||
# Use test file if necessary
|
# Use test file if necessary
|
||||||
if os.environ.get("ENVIRON") == "test":
|
if os.environ.get("ENVIRON") == "test":
|
||||||
__db_name = "test_" + __db_name
|
__db_name = "test_" + __db_name
|
||||||
|
|
||||||
__sqlitefile = __data_dir + __db_name
|
__sqlitefile = __data_dir + __db_name
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
""" Initialises the object and creates a connection to the local
|
""" Initialises the object and creates a connection to the local
|
||||||
DB on the server
|
DB on the server
|
||||||
"""
|
"""
|
||||||
self._conn = None
|
self._conn = None
|
||||||
try:
|
try:
|
||||||
# Creates a connection and specifies a flag to parse all types
|
# Creates a connection and specifies a flag to parse all types
|
||||||
# back down into Python declared types e.g. date & time
|
# back down into Python declared types e.g. date & time
|
||||||
self._conn = sqlite3.connect(
|
self._conn = sqlite3.connect(
|
||||||
self.__sqlitefile, detect_types=sqlite3.PARSE_DECLTYPES)
|
self.__sqlitefile, detect_types=sqlite3.PARSE_DECLTYPES)
|
||||||
except sqlite3.Error as e:
|
except sqlite3.Error as e:
|
||||||
# Close the connection if still open
|
# Close the connection if still open
|
||||||
if self._conn:
|
if self._conn:
|
||||||
self._conn.close()
|
self._conn.close()
|
||||||
print(e)
|
print(e)
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
""" Object Destructor which kills the connection to the database """
|
""" Object Destructor which kills the connection to the database """
|
||||||
if self._conn is not None:
|
if self._conn is not None:
|
||||||
self._conn.close()
|
self._conn.close()
|
||||||
|
|
||||||
def new_instance(self, of: type, with_fields: Mapping[str, Any]):
|
def new_instance(self, of: type, with_fields: Mapping[str, Any]):
|
||||||
""" Takes a dictionary of fields and returns the object
|
""" Takes a dictionary of fields and returns the object
|
||||||
with those fields populated
|
with those fields populated
|
||||||
"""
|
"""
|
||||||
obj = of.__new__(of)
|
obj = of.__new__(of)
|
||||||
for attr, value in with_fields.items():
|
for attr, value in with_fields.items():
|
||||||
setattr(obj, attr, value)
|
setattr(obj, attr, value)
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Set of CRUD methods to allow for Data manipulation on the backend
|
Set of CRUD methods to allow for Data manipulation on the backend
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def create(self):
|
def create(self):
|
||||||
""" Abstract method used to create a new record of a given
|
""" Abstract method used to create a new record of a given
|
||||||
type within the database
|
type within the database
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def read(self):
|
def read(self):
|
||||||
""" Abstract method used to read a record of a given
|
""" Abstract method used to read a record of a given
|
||||||
type from the database
|
type from the database
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def update(self):
|
def update(self):
|
||||||
""" Abstract method used to update a record of a given
|
""" Abstract method used to update a record of a given
|
||||||
type within the database
|
type within the database
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def delete(self):
|
def delete(self):
|
||||||
""" Abstract method used to delete record of a given
|
""" Abstract method used to delete record of a given
|
||||||
type from the database
|
type from the database
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
@ -1,160 +1,160 @@
|
|||||||
from .database import DatabaseController
|
from .database import DatabaseController
|
||||||
from models.products.product import Product
|
from models.products.product import Product
|
||||||
|
|
||||||
|
|
||||||
class ProductController(DatabaseController):
|
class ProductController(DatabaseController):
|
||||||
FIELDS = ['id', 'name', 'image', 'description', 'cost',
|
FIELDS = ['id', 'name', 'image', 'description', 'cost',
|
||||||
'sellerID', 'category', 'postedDate', 'quantityAvailable']
|
'sellerID', 'category', 'postedDate', 'quantityAvailable']
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
def create(self, product: Product):
|
def create(self, product: Product):
|
||||||
params = [
|
params = [
|
||||||
product.name,
|
product.name,
|
||||||
product.image,
|
product.image,
|
||||||
product.description,
|
product.description,
|
||||||
product.cost,
|
product.cost,
|
||||||
product.category,
|
product.category,
|
||||||
product.sellerID,
|
product.sellerID,
|
||||||
product.postedDate,
|
product.postedDate,
|
||||||
product.quantityAvailable
|
product.quantityAvailable
|
||||||
]
|
]
|
||||||
|
|
||||||
self._conn.execute(
|
self._conn.execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO Products
|
INSERT INTO Products
|
||||||
(name, image, description, cost, categoryID,
|
(name, image, description, cost, categoryID,
|
||||||
sellerID, postedDate, quantityAvailable)
|
sellerID, postedDate, quantityAvailable)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||||
params
|
params
|
||||||
)
|
)
|
||||||
self._conn.commit()
|
self._conn.commit()
|
||||||
|
|
||||||
def read(self, name: str = "") -> list[Product] | None:
|
def read(self, name: str = "") -> list[Product] | None:
|
||||||
params = [
|
params = [
|
||||||
"%" + name + "%"
|
"%" + name + "%"
|
||||||
]
|
]
|
||||||
|
|
||||||
cursor = self._conn.execute(
|
cursor = self._conn.execute(
|
||||||
"SELECT * FROM Products WHERE name like ?",
|
"SELECT * FROM Products WHERE name like ?",
|
||||||
params
|
params
|
||||||
)
|
)
|
||||||
rows = cursor.fetchmany()
|
rows = cursor.fetchmany()
|
||||||
|
|
||||||
if rows is None:
|
if rows is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
products = list()
|
products = list()
|
||||||
|
|
||||||
# Create an object for each row
|
# Create an object for each row
|
||||||
for product in rows:
|
for product in rows:
|
||||||
params = dict(zip(self.FIELDS, product))
|
params = dict(zip(self.FIELDS, product))
|
||||||
obj = self.new_instance(Product, params)
|
obj = self.new_instance(Product, params)
|
||||||
products.append(obj)
|
products.append(obj)
|
||||||
|
|
||||||
return products
|
return products
|
||||||
|
|
||||||
def read_id(self, id: int) -> Product | None:
|
def read_id(self, id: int) -> Product | None:
|
||||||
params = [
|
params = [
|
||||||
id
|
id
|
||||||
]
|
]
|
||||||
|
|
||||||
cursor = self._conn.execute(
|
cursor = self._conn.execute(
|
||||||
"SELECT * FROM Products WHERE id == ?",
|
"SELECT * FROM Products WHERE id == ?",
|
||||||
params
|
params
|
||||||
)
|
)
|
||||||
row = cursor.fetchone()
|
row = cursor.fetchone()
|
||||||
|
|
||||||
if row is None:
|
if row is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Create an object with the row
|
# Create an object with the row
|
||||||
params = dict(zip(self.FIELDS, row))
|
params = dict(zip(self.FIELDS, row))
|
||||||
obj = self.new_instance(Product, params)
|
obj = self.new_instance(Product, params)
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def read_all(self, category: str = "",
|
def read_all(self, category: str = "",
|
||||||
search_term: str = "") -> list[Product] | None:
|
search_term: str = "") -> list[Product] | None:
|
||||||
params = [
|
params = [
|
||||||
"%" + category + "%",
|
"%" + category + "%",
|
||||||
"%" + search_term + "%"
|
"%" + search_term + "%"
|
||||||
]
|
]
|
||||||
|
|
||||||
cursor = self._conn.execute(
|
cursor = self._conn.execute(
|
||||||
"""SELECT * FROM Products
|
"""SELECT * FROM Products
|
||||||
INNER JOIN Categories ON Products.categoryID = Categories.id
|
INNER JOIN Categories ON Products.categoryID = Categories.id
|
||||||
WHERE Categories.name LIKE ?
|
WHERE Categories.name LIKE ?
|
||||||
AND Products.name LIKE ?
|
AND Products.name LIKE ?
|
||||||
""",
|
""",
|
||||||
params
|
params
|
||||||
)
|
)
|
||||||
rows = cursor.fetchall()
|
rows = cursor.fetchall()
|
||||||
|
|
||||||
if len(rows) == 0:
|
if len(rows) == 0:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
products = list()
|
products = list()
|
||||||
|
|
||||||
# Create an object for each row
|
# Create an object for each row
|
||||||
for product in rows:
|
for product in rows:
|
||||||
params = dict(zip(self.FIELDS, product))
|
params = dict(zip(self.FIELDS, product))
|
||||||
obj = self.new_instance(Product, params)
|
obj = self.new_instance(Product, params)
|
||||||
products.append(obj)
|
products.append(obj)
|
||||||
|
|
||||||
return products
|
return products
|
||||||
|
|
||||||
def read_user(self, user_id: int) -> list[Product] | None:
|
def read_user(self, user_id: int) -> list[Product] | None:
|
||||||
params = [
|
params = [
|
||||||
user_id
|
user_id
|
||||||
]
|
]
|
||||||
|
|
||||||
cursor = self._conn.execute(
|
cursor = self._conn.execute(
|
||||||
"""SELECT * FROM Products
|
"""SELECT * FROM Products
|
||||||
WHERE sellerID = ?
|
WHERE sellerID = ?
|
||||||
""",
|
""",
|
||||||
params
|
params
|
||||||
)
|
)
|
||||||
rows = cursor.fetchall()
|
rows = cursor.fetchall()
|
||||||
|
|
||||||
if len(rows) == 0:
|
if len(rows) == 0:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
products = list()
|
products = list()
|
||||||
|
|
||||||
# Create an object for each row
|
# Create an object for each row
|
||||||
for product in rows:
|
for product in rows:
|
||||||
params = dict(zip(self.FIELDS, product))
|
params = dict(zip(self.FIELDS, product))
|
||||||
obj = self.new_instance(Product, params)
|
obj = self.new_instance(Product, params)
|
||||||
products.append(obj)
|
products.append(obj)
|
||||||
|
|
||||||
return products
|
return products
|
||||||
|
|
||||||
def update(self, product: Product):
|
def update(self, product: Product):
|
||||||
params = [
|
params = [
|
||||||
product.name,
|
product.name,
|
||||||
product.description,
|
product.description,
|
||||||
product.image,
|
product.image,
|
||||||
product.cost,
|
product.cost,
|
||||||
product.quantityAvailable,
|
product.quantityAvailable,
|
||||||
product.category,
|
product.category,
|
||||||
product.id
|
product.id
|
||||||
]
|
]
|
||||||
|
|
||||||
cursor = self._conn.execute(
|
cursor = self._conn.execute(
|
||||||
"""UPDATE Products
|
"""UPDATE Products
|
||||||
SET name = ?,
|
SET name = ?,
|
||||||
description = ?,
|
description = ?,
|
||||||
image = ?,
|
image = ?,
|
||||||
cost = ?,
|
cost = ?,
|
||||||
quantityAvailable = ?,
|
quantityAvailable = ?,
|
||||||
categoryID = ?
|
categoryID = ?
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
""",
|
""",
|
||||||
params
|
params
|
||||||
)
|
)
|
||||||
self._conn.commit()
|
self._conn.commit()
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
print("Doing work")
|
print("Doing work")
|
||||||
|
@ -1,85 +1,85 @@
|
|||||||
from .database import DatabaseController
|
from .database import DatabaseController
|
||||||
from models.users.user import User
|
from models.users.user import User
|
||||||
from models.users.customer import Customer
|
from models.users.customer import Customer
|
||||||
from models.users.seller import Seller
|
from models.users.seller import Seller
|
||||||
|
|
||||||
|
|
||||||
class UserController(DatabaseController):
|
class UserController(DatabaseController):
|
||||||
FIELDS = ['id', 'username', 'password', 'firstName',
|
FIELDS = ['id', 'username', 'password', 'firstName',
|
||||||
'lastName', 'email', 'phone', 'role']
|
'lastName', 'email', 'phone', 'role']
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
def create(self, user: User):
|
def create(self, user: User):
|
||||||
params = [
|
params = [
|
||||||
user.username,
|
user.username,
|
||||||
user.password,
|
user.password,
|
||||||
user.firstName,
|
user.firstName,
|
||||||
user.lastName,
|
user.lastName,
|
||||||
user.email,
|
user.email,
|
||||||
user.phone,
|
user.phone,
|
||||||
user.role
|
user.role
|
||||||
]
|
]
|
||||||
|
|
||||||
self._conn.execute(
|
self._conn.execute(
|
||||||
"""INSERT INTO Users
|
"""INSERT INTO Users
|
||||||
(username, password, first_name, last_name, email, phone, role)
|
(username, password, first_name, last_name, email, phone, role)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)""",
|
VALUES (?, ?, ?, ?, ?, ?, ?)""",
|
||||||
params
|
params
|
||||||
)
|
)
|
||||||
self._conn.commit()
|
self._conn.commit()
|
||||||
|
|
||||||
def read(self, username: str) -> User | None:
|
def read(self, username: str) -> User | None:
|
||||||
params = [
|
params = [
|
||||||
username
|
username
|
||||||
]
|
]
|
||||||
|
|
||||||
cursor = self._conn.execute(
|
cursor = self._conn.execute(
|
||||||
"SELECT * FROM Users WHERE Username = ?",
|
"SELECT * FROM Users WHERE Username = ?",
|
||||||
params
|
params
|
||||||
)
|
)
|
||||||
row = cursor.fetchone()
|
row = cursor.fetchone()
|
||||||
|
|
||||||
if row is not None:
|
if row is not None:
|
||||||
params = dict(zip(self.FIELDS, row))
|
params = dict(zip(self.FIELDS, row))
|
||||||
|
|
||||||
# Is user a seller
|
# Is user a seller
|
||||||
type = Customer
|
type = Customer
|
||||||
if row[7] == "Seller":
|
if row[7] == "Seller":
|
||||||
type = Seller
|
type = Seller
|
||||||
|
|
||||||
obj = self.new_instance(type, params)
|
obj = self.new_instance(type, params)
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def read_id(self, id: int) -> User | None:
|
def read_id(self, id: int) -> User | None:
|
||||||
params = [
|
params = [
|
||||||
id
|
id
|
||||||
]
|
]
|
||||||
|
|
||||||
cursor = self._conn.execute(
|
cursor = self._conn.execute(
|
||||||
"SELECT * FROM Users WHERE id = ?",
|
"SELECT * FROM Users WHERE id = ?",
|
||||||
params
|
params
|
||||||
)
|
)
|
||||||
row = cursor.fetchone()
|
row = cursor.fetchone()
|
||||||
|
|
||||||
if row is not None:
|
if row is not None:
|
||||||
params = dict(zip(self.FIELDS, row))
|
params = dict(zip(self.FIELDS, row))
|
||||||
|
|
||||||
# Is user a seller
|
# Is user a seller
|
||||||
type = Customer
|
type = Customer
|
||||||
if row[7] == "Seller":
|
if row[7] == "Seller":
|
||||||
type = Seller
|
type = Seller
|
||||||
|
|
||||||
obj = self.new_instance(type, params)
|
obj = self.new_instance(type, params)
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
print("Doing work")
|
print("Doing work")
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
print("Doing work")
|
print("Doing work")
|
||||||
|
@ -1,34 +1,34 @@
|
|||||||
from models.users.user import User
|
from models.users.user import User
|
||||||
from controllers.database.user import UserController
|
from controllers.database.user import UserController
|
||||||
|
|
||||||
from flask import redirect, Blueprint, session
|
from flask import redirect, Blueprint, session
|
||||||
|
|
||||||
from . import user
|
from . import user
|
||||||
from . import product
|
from . import product
|
||||||
|
|
||||||
blueprint = Blueprint('main', __name__)
|
blueprint = Blueprint('main', __name__)
|
||||||
|
|
||||||
blueprint.register_blueprint(user.blueprint)
|
blueprint.register_blueprint(user.blueprint)
|
||||||
blueprint.register_blueprint(product.blueprint)
|
blueprint.register_blueprint(product.blueprint)
|
||||||
|
|
||||||
|
|
||||||
# CONTEXTS #
|
# CONTEXTS #
|
||||||
|
|
||||||
# Function that returns a given user class based on the ID in the session
|
# Function that returns a given user class based on the ID in the session
|
||||||
@blueprint.context_processor
|
@blueprint.context_processor
|
||||||
def get_user() -> dict[User | None]:
|
def get_user() -> dict[User | None]:
|
||||||
# Get the user based on the user ID
|
# Get the user based on the user ID
|
||||||
user_id = session.get('user_id')
|
user_id = session.get('user_id')
|
||||||
user = None
|
user = None
|
||||||
|
|
||||||
if user_id is not None:
|
if user_id is not None:
|
||||||
db = UserController()
|
db = UserController()
|
||||||
user = db.read_id(user_id)
|
user = db.read_id(user_id)
|
||||||
|
|
||||||
return dict(user=user)
|
return dict(user=user)
|
||||||
|
|
||||||
|
|
||||||
# Function responsible for displaying the main landing page of the site
|
# Function responsible for displaying the main landing page of the site
|
||||||
@blueprint.route('/')
|
@blueprint.route('/')
|
||||||
def index():
|
def index():
|
||||||
return redirect("/products")
|
return redirect("/products")
|
||||||
|
@ -1,188 +1,188 @@
|
|||||||
"""
|
"""
|
||||||
Product related endpoints. Included contexts for principles such as
|
Product related endpoints. Included contexts for principles such as
|
||||||
categories and image processing.
|
categories and image processing.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from flask import render_template, session, flash, request, redirect, Blueprint
|
from flask import render_template, session, flash, request, redirect, Blueprint
|
||||||
|
|
||||||
from models.products.product import Product
|
from models.products.product import Product
|
||||||
from controllers.database.product import ProductController
|
from controllers.database.product import ProductController
|
||||||
from controllers.database.category import CategoryController
|
from controllers.database.category import CategoryController
|
||||||
from controllers.database.user import UserController
|
from controllers.database.user import UserController
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from utils.file_utils import allowed_file, save_image, remove_file
|
from utils.file_utils import allowed_file, save_image, remove_file
|
||||||
from utils.user_utils import is_role
|
from utils.user_utils import is_role
|
||||||
|
|
||||||
import pathlib
|
import pathlib
|
||||||
import os
|
import os
|
||||||
|
|
||||||
blueprint = Blueprint("products", __name__, url_prefix="/products")
|
blueprint = Blueprint("products", __name__, url_prefix="/products")
|
||||||
|
|
||||||
|
|
||||||
@blueprint.context_processor
|
@blueprint.context_processor
|
||||||
def category_list():
|
def category_list():
|
||||||
""" Places a list of all categories in the products context """
|
""" Places a list of all categories in the products context """
|
||||||
database = CategoryController()
|
database = CategoryController()
|
||||||
categories = database.read_all()
|
categories = database.read_all()
|
||||||
return dict(categories=categories)
|
return dict(categories=categories)
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/')
|
@blueprint.route('/')
|
||||||
def index():
|
def index():
|
||||||
""" The front product page """
|
""" The front product page """
|
||||||
# Returning an empty category acts the same
|
# Returning an empty category acts the same
|
||||||
# as a generic home page
|
# as a generic home page
|
||||||
return category("")
|
return category("")
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/<string:category>')
|
@blueprint.route('/<string:category>')
|
||||||
def category(category: str):
|
def category(category: str):
|
||||||
""" Loads a given categories page """
|
""" Loads a given categories page """
|
||||||
database = ProductController()
|
database = ProductController()
|
||||||
|
|
||||||
# Check to see if there is a custome search term
|
# Check to see if there is a custome search term
|
||||||
search_term = request.args.get("search", type=str)
|
search_term = request.args.get("search", type=str)
|
||||||
if search_term is not None:
|
if search_term is not None:
|
||||||
products = database.read_all(category, search_term)
|
products = database.read_all(category, search_term)
|
||||||
else:
|
else:
|
||||||
products = database.read_all(category)
|
products = database.read_all(category)
|
||||||
|
|
||||||
# No Products visible
|
# No Products visible
|
||||||
if products is None:
|
if products is None:
|
||||||
flash(
|
flash(
|
||||||
f"No Products available. Try expanding your search criteria.",
|
f"No Products available. Try expanding your search criteria.",
|
||||||
"warning"
|
"warning"
|
||||||
)
|
)
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
'index.html',
|
'index.html',
|
||||||
content="content.html",
|
content="content.html",
|
||||||
products=products,
|
products=products,
|
||||||
category=category
|
category=category
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/<int:id>')
|
@blueprint.route('/<int:id>')
|
||||||
def id(id: int):
|
def id(id: int):
|
||||||
""" Loads a given product based on ID """
|
""" Loads a given product based on ID """
|
||||||
db = ProductController()
|
db = ProductController()
|
||||||
product = db.read_id(id)
|
product = db.read_id(id)
|
||||||
|
|
||||||
# Check that a valid product was returned
|
# Check that a valid product was returned
|
||||||
if product is None:
|
if product is None:
|
||||||
flash(f"No Product available with id {id}", "warning")
|
flash(f"No Product available with id {id}", "warning")
|
||||||
return redirect("/")
|
return redirect("/")
|
||||||
|
|
||||||
print(product.name)
|
print(product.name)
|
||||||
return render_template(
|
return render_template(
|
||||||
'index.html',
|
'index.html',
|
||||||
content='product.html',
|
content='product.html',
|
||||||
product=product
|
product=product
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/add')
|
@blueprint.route('/add')
|
||||||
def display_add_product():
|
def display_add_product():
|
||||||
""" Launches the page to add a new product to the site """
|
""" Launches the page to add a new product to the site """
|
||||||
user_id = session.get('user_id')
|
user_id = session.get('user_id')
|
||||||
|
|
||||||
# User needs to be logged in as a seller to view this page
|
# User needs to be logged in as a seller to view this page
|
||||||
if not is_role("Seller"):
|
if not is_role("Seller"):
|
||||||
flash("You must be logged in as a seller to view this page!", "error")
|
flash("You must be logged in as a seller to view this page!", "error")
|
||||||
return redirect("/")
|
return redirect("/")
|
||||||
|
|
||||||
return render_template('index.html', content='new_product.html')
|
return render_template('index.html', content='new_product.html')
|
||||||
|
|
||||||
|
|
||||||
@blueprint.post('/add')
|
@blueprint.post('/add')
|
||||||
def add_product():
|
def add_product():
|
||||||
""" Server site processing to handle a request to add a
|
""" Server site processing to handle a request to add a
|
||||||
new product to the site
|
new product to the site
|
||||||
"""
|
"""
|
||||||
user_id = session.get('user_id')
|
user_id = session.get('user_id')
|
||||||
|
|
||||||
# User needs to be logged in as a seller to view this page
|
# User needs to be logged in as a seller to view this page
|
||||||
if not is_role("Seller"):
|
if not is_role("Seller"):
|
||||||
flash("You must be logged in as a seller to view this page!", "error")
|
flash("You must be logged in as a seller to view this page!", "error")
|
||||||
return redirect("/")
|
return redirect("/")
|
||||||
|
|
||||||
file = request.files.get('image')
|
file = request.files.get('image')
|
||||||
image_filename = save_image(file)
|
image_filename = save_image(file)
|
||||||
|
|
||||||
product = Product(
|
product = Product(
|
||||||
request.form.get('name'),
|
request.form.get('name'),
|
||||||
image_filename if image_filename is not None else "",
|
image_filename if image_filename is not None else "",
|
||||||
request.form.get('description'),
|
request.form.get('description'),
|
||||||
request.form.get('cost'),
|
request.form.get('cost'),
|
||||||
request.form.get('category'),
|
request.form.get('category'),
|
||||||
user_id,
|
user_id,
|
||||||
datetime.now(),
|
datetime.now(),
|
||||||
request.form.get('quantity')
|
request.form.get('quantity')
|
||||||
)
|
)
|
||||||
|
|
||||||
db = ProductController()
|
db = ProductController()
|
||||||
db.create(product)
|
db.create(product)
|
||||||
|
|
||||||
return redirect('/products/ownproducts')
|
return redirect('/products/ownproducts')
|
||||||
|
|
||||||
|
|
||||||
@blueprint.post('/update/<int:id>')
|
@blueprint.post('/update/<int:id>')
|
||||||
def update_product(id: int):
|
def update_product(id: int):
|
||||||
""" Processes a request to update a product in place on the site """
|
""" Processes a request to update a product in place on the site """
|
||||||
# Ensure that the product belongs to the current user
|
# Ensure that the product belongs to the current user
|
||||||
user_id = session.get('user_id')
|
user_id = session.get('user_id')
|
||||||
|
|
||||||
# User needs to be logged in as a seller to view this page
|
# User needs to be logged in as a seller to view this page
|
||||||
if not is_role("Seller"):
|
if not is_role("Seller"):
|
||||||
flash("You must be logged in as a seller to view this page!", "error")
|
flash("You must be logged in as a seller to view this page!", "error")
|
||||||
return redirect("/")
|
return redirect("/")
|
||||||
|
|
||||||
db = ProductController()
|
db = ProductController()
|
||||||
product = db.read_id(id)
|
product = db.read_id(id)
|
||||||
|
|
||||||
if product.sellerID != user_id:
|
if product.sellerID != user_id:
|
||||||
flash("This product does not belong to you!", "error")
|
flash("This product does not belong to you!", "error")
|
||||||
return redirect("/ownproducts")
|
return redirect("/ownproducts")
|
||||||
|
|
||||||
# Save new image file
|
# Save new image file
|
||||||
file = request.files.get('image')
|
file = request.files.get('image')
|
||||||
new_image = save_image(file)
|
new_image = save_image(file)
|
||||||
|
|
||||||
if new_image is not None:
|
if new_image is not None:
|
||||||
remove_file(os.path.join(os.environ.get('FILESTORE'), product.image))
|
remove_file(os.path.join(os.environ.get('FILESTORE'), product.image))
|
||||||
product.image = new_image
|
product.image = new_image
|
||||||
|
|
||||||
# Update product details
|
# Update product details
|
||||||
product.name = request.form.get('name')
|
product.name = request.form.get('name')
|
||||||
product.description = request.form.get('description')
|
product.description = request.form.get('description')
|
||||||
product.category = request.form.get('category')
|
product.category = request.form.get('category')
|
||||||
product.cost = request.form.get('cost')
|
product.cost = request.form.get('cost')
|
||||||
product.quantityAvailable = request.form.get('quantity')
|
product.quantityAvailable = request.form.get('quantity')
|
||||||
|
|
||||||
db.update(product)
|
db.update(product)
|
||||||
flash("Product successfully updated", 'notice')
|
flash("Product successfully updated", 'notice')
|
||||||
return redirect(f"/products/{product.id}")
|
return redirect(f"/products/{product.id}")
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/ownproducts')
|
@blueprint.route('/ownproducts')
|
||||||
def display_own_products():
|
def display_own_products():
|
||||||
""" Display products owned by the currently logged in seller """
|
""" Display products owned by the currently logged in seller """
|
||||||
user_id = session.get('user_id')
|
user_id = session.get('user_id')
|
||||||
|
|
||||||
# User must be logged in as seller to view page
|
# User must be logged in as seller to view page
|
||||||
if not is_role("Seller"):
|
if not is_role("Seller"):
|
||||||
flash("You must be logged in as a seller to view this page!", "error")
|
flash("You must be logged in as a seller to view this page!", "error")
|
||||||
return redirect("/")
|
return redirect("/")
|
||||||
|
|
||||||
db = ProductController()
|
db = ProductController()
|
||||||
products = db.read_user(user_id)
|
products = db.read_user(user_id)
|
||||||
|
|
||||||
if products is None:
|
if products is None:
|
||||||
flash("You don't currently have any products for sale.", "info")
|
flash("You don't currently have any products for sale.", "info")
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
'index.html',
|
'index.html',
|
||||||
content='content.html',
|
content='content.html',
|
||||||
products=products
|
products=products
|
||||||
)
|
)
|
||||||
|
@ -1,99 +1,99 @@
|
|||||||
""" The user controller to manage all of the user related endpoints
|
""" The user controller to manage all of the user related endpoints
|
||||||
in the web app
|
in the web app
|
||||||
"""
|
"""
|
||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
|
|
||||||
from flask import render_template, redirect, request, session, flash
|
from flask import render_template, redirect, request, session, flash
|
||||||
from controllers.database.user import UserController
|
from controllers.database.user import UserController
|
||||||
from models.users.user import User
|
from models.users.user import User
|
||||||
from models.users.customer import Customer
|
from models.users.customer import Customer
|
||||||
from models.users.seller import Seller
|
from models.users.seller import Seller
|
||||||
from hashlib import sha512
|
from hashlib import sha512
|
||||||
|
|
||||||
# Blueprint to append user endpoints to
|
# Blueprint to append user endpoints to
|
||||||
blueprint = Blueprint("users", __name__)
|
blueprint = Blueprint("users", __name__)
|
||||||
|
|
||||||
|
|
||||||
# LOGIN FUNCTIONALITY
|
# LOGIN FUNCTIONALITY
|
||||||
@blueprint.route('/login')
|
@blueprint.route('/login')
|
||||||
def display_login():
|
def display_login():
|
||||||
""" Function responsible for delivering the Login page for the site """
|
""" Function responsible for delivering the Login page for the site """
|
||||||
return render_template('index.html', content="login.html")
|
return render_template('index.html', content="login.html")
|
||||||
|
|
||||||
|
|
||||||
@blueprint.post('/login')
|
@blueprint.post('/login')
|
||||||
def login():
|
def login():
|
||||||
""" Function to handle the backend processing of a login request """
|
""" Function to handle the backend processing of a login request """
|
||||||
database = UserController()
|
database = UserController()
|
||||||
user = database.read(request.form['username'])
|
user = database.read(request.form['username'])
|
||||||
error = None
|
error = None
|
||||||
|
|
||||||
# No user found
|
# No user found
|
||||||
if user is None:
|
if user is None:
|
||||||
error = "No user found with the username " + request.form['username']
|
error = "No user found with the username " + request.form['username']
|
||||||
flash(error, 'warning')
|
flash(error, 'warning')
|
||||||
return redirect("/login")
|
return redirect("/login")
|
||||||
|
|
||||||
# Incorrect Password
|
# Incorrect Password
|
||||||
if sha512(request.form['password'].encode()).hexdigest() != user.password:
|
if sha512(request.form['password'].encode()).hexdigest() != user.password:
|
||||||
error = "Incorrect Password"
|
error = "Incorrect Password"
|
||||||
flash(error, 'warning')
|
flash(error, 'warning')
|
||||||
return redirect("/login")
|
return redirect("/login")
|
||||||
|
|
||||||
session['user_id'] = user.id
|
session['user_id'] = user.id
|
||||||
return redirect("/")
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
# SIGNUP FUNCTIONALITY
|
# SIGNUP FUNCTIONALITY
|
||||||
@blueprint.route('/signup')
|
@blueprint.route('/signup')
|
||||||
def display_signup():
|
def display_signup():
|
||||||
""" Function responsible for delivering the Signup page for the site """
|
""" Function responsible for delivering the Signup page for the site """
|
||||||
return render_template('index.html', content="signup.html")
|
return render_template('index.html', content="signup.html")
|
||||||
|
|
||||||
|
|
||||||
@blueprint.post('/signup')
|
@blueprint.post('/signup')
|
||||||
def signup():
|
def signup():
|
||||||
""" Function to handle the backend processing of a signup request """
|
""" Function to handle the backend processing of a signup request """
|
||||||
database = UserController()
|
database = UserController()
|
||||||
|
|
||||||
# User already exists
|
# User already exists
|
||||||
if database.read(request.form['username']) is not None:
|
if database.read(request.form['username']) is not None:
|
||||||
error = "User, " + request.form['username'] + " already exists"
|
error = "User, " + request.form['username'] + " already exists"
|
||||||
flash(error, 'warning')
|
flash(error, 'warning')
|
||||||
return redirect("/signup")
|
return redirect("/signup")
|
||||||
|
|
||||||
# Signup as Seller or Customer
|
# Signup as Seller or Customer
|
||||||
if request.form.get('seller'):
|
if request.form.get('seller'):
|
||||||
user = Seller(
|
user = Seller(
|
||||||
request.form['username'],
|
request.form['username'],
|
||||||
# Hashed as soon as it is recieved on the backend
|
# Hashed as soon as it is recieved on the backend
|
||||||
sha512(request.form['password'].encode()).hexdigest(),
|
sha512(request.form['password'].encode()).hexdigest(),
|
||||||
request.form['firstname'],
|
request.form['firstname'],
|
||||||
request.form['lastname'],
|
request.form['lastname'],
|
||||||
request.form['email'],
|
request.form['email'],
|
||||||
"123"
|
"123"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
user = Customer(
|
user = Customer(
|
||||||
request.form['username'],
|
request.form['username'],
|
||||||
# Hashed as soon as it is recieved on the backend
|
# Hashed as soon as it is recieved on the backend
|
||||||
sha512(request.form['password'].encode()).hexdigest(),
|
sha512(request.form['password'].encode()).hexdigest(),
|
||||||
request.form['firstname'],
|
request.form['firstname'],
|
||||||
request.form['lastname'],
|
request.form['lastname'],
|
||||||
request.form['email'],
|
request.form['email'],
|
||||||
"123"
|
"123"
|
||||||
)
|
)
|
||||||
|
|
||||||
database.create(user)
|
database.create(user)
|
||||||
|
|
||||||
# Code 307 Preserves the original request (POST)
|
# Code 307 Preserves the original request (POST)
|
||||||
return redirect("/login", code=307)
|
return redirect("/login", code=307)
|
||||||
|
|
||||||
|
|
||||||
# SIGN OUT FUNCTIONALITY
|
# SIGN OUT FUNCTIONALITY
|
||||||
@blueprint.route('/logout')
|
@blueprint.route('/logout')
|
||||||
def logout():
|
def logout():
|
||||||
""" Function responsible for handling logouts from the site """
|
""" Function responsible for handling logouts from the site """
|
||||||
# Clear the current user from the session if they are logged in
|
# Clear the current user from the session if they are logged in
|
||||||
session.pop('user_id', None)
|
session.pop('user_id', None)
|
||||||
return redirect("/")
|
return redirect("/")
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
version: '3.8'
|
version: '3.8'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
wmgzon:
|
wmgzon:
|
||||||
container_name: "wmgzon"
|
container_name: "wmgzon"
|
||||||
build: .
|
build: .
|
||||||
environment:
|
environment:
|
||||||
- APPSECRET=test
|
- APPSECRET=test
|
||||||
- ENVIRON=test
|
- ENVIRON=test
|
||||||
#- ENVIRON=prod
|
#- ENVIRON=prod
|
||||||
- FILESTORE=static/assets/img/products/
|
- FILESTORE=static/assets/img/products/
|
||||||
tty: true
|
tty: true
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
volumes:
|
volumes:
|
||||||
- .:/app
|
- .:/app
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
12
dockerfile
12
dockerfile
@ -1,7 +1,7 @@
|
|||||||
FROM python:latest
|
FROM python:latest
|
||||||
COPY ./requirements.txt /app/requirements.txt
|
COPY ./requirements.txt /app/requirements.txt
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN pip install -r requirements.txt
|
RUN pip install -r requirements.txt
|
||||||
COPY . /app
|
COPY . /app
|
||||||
RUN chmod +x scripts/run.bash
|
RUN chmod +x scripts/run.bash
|
||||||
CMD ["bash", "scripts/run.bash"]
|
CMD ["bash", "scripts/run.bash"]
|
@ -1,8 +1,8 @@
|
|||||||
class Category:
|
class Category:
|
||||||
'''
|
'''
|
||||||
Constructor for a category object
|
Constructor for a category object
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.id = 0
|
self.id = 0
|
||||||
self.name = ""
|
self.name = ""
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
class Order:
|
class Order:
|
||||||
'''
|
'''
|
||||||
Constructor for an order object
|
Constructor for an order object
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.id = 0
|
self.id = 0
|
||||||
self.sellerID = 0
|
self.sellerID = 0
|
||||||
self.customerID = 0
|
self.customerID = 0
|
||||||
self.products = list()
|
self.products = list()
|
||||||
self.totalCost = 0.0
|
self.totalCost = 0.0
|
||||||
self.orderDate = datetime.now()
|
self.orderDate = datetime.now()
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
from product import Product
|
from product import Product
|
||||||
|
|
||||||
|
|
||||||
class CarPart(Product):
|
class CarPart(Product):
|
||||||
'''
|
'''
|
||||||
Constructor for a car part
|
Constructor for a car part
|
||||||
|
|
||||||
Contains additional information that is only relevant for car parts
|
Contains additional information that is only relevant for car parts
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.make = ""
|
self.make = ""
|
||||||
self.compatibleVehicles = list()
|
self.compatibleVehicles = list()
|
||||||
|
@ -1,38 +1,38 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
class Product:
|
class Product:
|
||||||
'''
|
'''
|
||||||
Base class for a product
|
Base class for a product
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.id = 0
|
self.id = 0
|
||||||
self.name = ""
|
self.name = ""
|
||||||
self.image = "/static/assets/wmgzon.png"
|
self.image = "/static/assets/wmgzon.png"
|
||||||
self.description = ""
|
self.description = ""
|
||||||
self.cost = 0.0
|
self.cost = 0.0
|
||||||
self.category = 0
|
self.category = 0
|
||||||
self.sellerID = 0
|
self.sellerID = 0
|
||||||
self.postedDate = datetime.now()
|
self.postedDate = datetime.now()
|
||||||
self.quantityAvailable = 0
|
self.quantityAvailable = 0
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Class constructor to instatiate a customer object
|
Class constructor to instatiate a customer object
|
||||||
|
|
||||||
No additional properties are assigned to the customer
|
No additional properties are assigned to the customer
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, name: str, image: str, description: str,
|
def __init__(self, name: str, image: str, description: str,
|
||||||
cost: float, category: int, seller_id: int,
|
cost: float, category: int, seller_id: int,
|
||||||
posted_date: datetime, quantity_available: int
|
posted_date: datetime, quantity_available: int
|
||||||
):
|
):
|
||||||
self.id = 0
|
self.id = 0
|
||||||
self.name = name
|
self.name = name
|
||||||
self.image = image
|
self.image = image
|
||||||
self.description = description
|
self.description = description
|
||||||
self.cost = cost
|
self.cost = cost
|
||||||
self.category = category
|
self.category = category
|
||||||
self.sellerID = seller_id
|
self.sellerID = seller_id
|
||||||
self.postedDate = posted_date
|
self.postedDate = posted_date
|
||||||
self.quantityAvailable = quantity_available
|
self.quantityAvailable = quantity_available
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
from .user import User
|
from .user import User
|
||||||
|
|
||||||
|
|
||||||
class Admin(User):
|
class Admin(User):
|
||||||
'''
|
'''
|
||||||
Class constructor to instatiate an admin object
|
Class constructor to instatiate an admin object
|
||||||
|
|
||||||
No additional properties are assigned to the admin
|
No additional properties are assigned to the admin
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, username: str, password: str, firstname: str,
|
def __init__(self, username: str, password: str, firstname: str,
|
||||||
lastname: str, email: str, phone: str):
|
lastname: str, email: str, phone: str):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
username, password, firstname, lastname, email, phone, "Admin"
|
username, password, firstname, lastname, email, phone, "Admin"
|
||||||
)
|
)
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
from .user import User
|
from .user import User
|
||||||
|
|
||||||
|
|
||||||
class Customer(User):
|
class Customer(User):
|
||||||
'''
|
'''
|
||||||
Class constructor to instatiate a customer object
|
Class constructor to instatiate a customer object
|
||||||
|
|
||||||
No additional properties are assigned to the customer
|
No additional properties are assigned to the customer
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, username: str, password: str, firstname: str,
|
def __init__(self, username: str, password: str, firstname: str,
|
||||||
lastname: str, email: str, phone: str):
|
lastname: str, email: str, phone: str):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
username, password, firstname, lastname, email, phone, "Customer"
|
username, password, firstname, lastname, email, phone, "Customer"
|
||||||
)
|
)
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
from .user import User
|
from .user import User
|
||||||
|
|
||||||
|
|
||||||
class Seller(User):
|
class Seller(User):
|
||||||
'''
|
'''
|
||||||
Class constructor to instatiate a customer object
|
Class constructor to instatiate a customer object
|
||||||
|
|
||||||
No additional properties are assigned to the customer
|
No additional properties are assigned to the customer
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, username: str, password: str, firstname: str,
|
def __init__(self, username: str, password: str, firstname: str,
|
||||||
lastname: str, email: str, phone: str):
|
lastname: str, email: str, phone: str):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
username, password, firstname, lastname, email, phone, "Seller"
|
username, password, firstname, lastname, email, phone, "Seller"
|
||||||
)
|
)
|
||||||
self.store = ""
|
self.store = ""
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
from abc import ABC
|
from abc import ABC
|
||||||
|
|
||||||
|
|
||||||
class User(ABC):
|
class User(ABC):
|
||||||
""" Functional Class constructor to initialise all properties in
|
""" Functional Class constructor to initialise all properties in
|
||||||
the base object with a value
|
the base object with a value
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, username: str, password: str,
|
def __init__(self, username: str, password: str,
|
||||||
firstname: str, lastname: str,
|
firstname: str, lastname: str,
|
||||||
email: str, phone: str, role: str
|
email: str, phone: str, role: str
|
||||||
):
|
):
|
||||||
self.id = 0
|
self.id = 0
|
||||||
self.username = username
|
self.username = username
|
||||||
self.password = password
|
self.password = password
|
||||||
self.firstName = firstname
|
self.firstName = firstname
|
||||||
self.lastName = lastname
|
self.lastName = lastname
|
||||||
self.email = email
|
self.email = email
|
||||||
self.phone = phone
|
self.phone = phone
|
||||||
self.role = role
|
self.role = role
|
||||||
|
@ -1,26 +1,26 @@
|
|||||||
blinker==1.7.0
|
blinker==1.7.0
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
colorama==0.4.6
|
colorama==0.4.6
|
||||||
distlib==0.3.7
|
distlib==0.3.7
|
||||||
dnspython==2.4.2
|
dnspython==2.4.2
|
||||||
docstring-to-markdown==0.13
|
docstring-to-markdown==0.13
|
||||||
filelock==3.13.1
|
filelock==3.13.1
|
||||||
Flask==3.0.0
|
Flask==3.0.0
|
||||||
iniconfig==2.0.0
|
iniconfig==2.0.0
|
||||||
itsdangerous==2.1.2
|
itsdangerous==2.1.2
|
||||||
jedi==0.19.1
|
jedi==0.19.1
|
||||||
Jinja2==3.1.2
|
Jinja2==3.1.2
|
||||||
MarkupSafe==2.1.3
|
MarkupSafe==2.1.3
|
||||||
packaging==23.2
|
packaging==23.2
|
||||||
parso==0.8.3
|
parso==0.8.3
|
||||||
platformdirs==3.11.0
|
platformdirs==3.11.0
|
||||||
pluggy==1.3.0
|
pluggy==1.3.0
|
||||||
pymongo==4.6.0
|
pymongo==4.6.0
|
||||||
pytest==7.4.3
|
pytest==7.4.3
|
||||||
python-lsp-jsonrpc==1.1.2
|
python-lsp-jsonrpc==1.1.2
|
||||||
python-lsp-server==1.9.0
|
python-lsp-server==1.9.0
|
||||||
ujson==5.8.0
|
ujson==5.8.0
|
||||||
virtualenv==20.24.6
|
virtualenv==20.24.6
|
||||||
waitress==2.1.2
|
waitress==2.1.2
|
||||||
Werkzeug==3.0.1
|
Werkzeug==3.0.1
|
||||||
pycodestyle==2.11.1
|
pycodestyle==2.11.1
|
@ -1,51 +1,51 @@
|
|||||||
import sqlite3
|
import sqlite3
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
sys.path.append(os.getcwd())
|
sys.path.append(os.getcwd())
|
||||||
|
|
||||||
from utils.file_utils import create_directory, remove_file
|
from utils.file_utils import create_directory, remove_file
|
||||||
|
|
||||||
|
|
||||||
def create_connection(path: str, filename: str):
|
def create_connection(path: str, filename: str):
|
||||||
""" create a database connection to a SQLite database """
|
""" create a database connection to a SQLite database """
|
||||||
conn = None
|
conn = None
|
||||||
try:
|
try:
|
||||||
# Make the directory for the file to go into
|
# Make the directory for the file to go into
|
||||||
create_directory(path)
|
create_directory(path)
|
||||||
|
|
||||||
print("Opening Database file and ensuring table integrity")
|
print("Opening Database file and ensuring table integrity")
|
||||||
conn = sqlite3.connect(path + filename)
|
conn = sqlite3.connect(path + filename)
|
||||||
|
|
||||||
print("Database file open")
|
print("Database file open")
|
||||||
# Execute creation scripts
|
# Execute creation scripts
|
||||||
sql = open("scripts/create_tables.sql", "r")
|
sql = open("scripts/create_tables.sql", "r")
|
||||||
conn.executescript(sql.read())
|
conn.executescript(sql.read())
|
||||||
|
|
||||||
print("Table creation complete")
|
print("Table creation complete")
|
||||||
|
|
||||||
# Populate with test data if we are in Test Mode
|
# Populate with test data if we are in Test Mode
|
||||||
if os.environ.get("ENVIRON") == "test":
|
if os.environ.get("ENVIRON") == "test":
|
||||||
sql = open("scripts/test_data.sql", "r")
|
sql = open("scripts/test_data.sql", "r")
|
||||||
conn.executescript(sql.read())
|
conn.executescript(sql.read())
|
||||||
|
|
||||||
except sqlite3.Error as e:
|
except sqlite3.Error as e:
|
||||||
print(e)
|
print(e)
|
||||||
finally:
|
finally:
|
||||||
if conn:
|
if conn:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
# Ensure a directory is created given a path to it
|
# Ensure a directory is created given a path to it
|
||||||
dir = r"./data/"
|
dir = r"./data/"
|
||||||
db_name = r"wmgzon.db"
|
db_name = r"wmgzon.db"
|
||||||
|
|
||||||
# Check for test environ
|
# Check for test environ
|
||||||
if os.environ.get("ENVIRON") == "test":
|
if os.environ.get("ENVIRON") == "test":
|
||||||
# Remove the original test database
|
# Remove the original test database
|
||||||
print("TEST ENVIRONMENT ACTIVE")
|
print("TEST ENVIRONMENT ACTIVE")
|
||||||
db_name = "test_" + db_name
|
db_name = "test_" + db_name
|
||||||
remove_file(dir + db_name)
|
remove_file(dir + db_name)
|
||||||
|
|
||||||
create_connection(dir, db_name)
|
create_connection(dir, db_name)
|
||||||
|
@ -1,56 +1,56 @@
|
|||||||
CREATE TABLE IF NOT EXISTS Users (
|
CREATE TABLE IF NOT EXISTS Users (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
username TEXT NOT NULL UNIQUE,
|
username TEXT NOT NULL UNIQUE,
|
||||||
password TEXT NOT NULL,
|
password TEXT NOT NULL,
|
||||||
first_name TEXT NOT NULL,
|
first_name TEXT NOT NULL,
|
||||||
last_name TEXT NOT NULL,
|
last_name TEXT NOT NULL,
|
||||||
email TEXT NOT NULL UNIQUE,
|
email TEXT NOT NULL UNIQUE,
|
||||||
phone TEXT NOT NULL,
|
phone TEXT NOT NULL,
|
||||||
role TEXT NOT NULL
|
role TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS Categories (
|
CREATE TABLE IF NOT EXISTS Categories (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
name TEXT NOT NULL UNIQUE
|
name TEXT NOT NULL UNIQUE
|
||||||
);
|
);
|
||||||
|
|
||||||
INSERT INTO Categories (name) VALUES ("Car Parts");
|
INSERT INTO Categories (name) VALUES ("Car Parts");
|
||||||
INSERT INTO Categories (name) VALUES ("Animals");
|
INSERT INTO Categories (name) VALUES ("Animals");
|
||||||
INSERT INTO Categories (name) VALUES ("Sports");
|
INSERT INTO Categories (name) VALUES ("Sports");
|
||||||
INSERT INTO Categories (name) VALUES ("Books");
|
INSERT INTO Categories (name) VALUES ("Books");
|
||||||
INSERT INTO Categories (name) VALUES ("Phones");
|
INSERT INTO Categories (name) VALUES ("Phones");
|
||||||
INSERT INTO Categories (name) VALUES ("Music");
|
INSERT INTO Categories (name) VALUES ("Music");
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS Products (
|
CREATE TABLE IF NOT EXISTS Products (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
image TEXT NOT NULL,
|
image TEXT NOT NULL,
|
||||||
description TEXT NOT NULL,
|
description TEXT NOT NULL,
|
||||||
cost DECIMAL NOT NULL,
|
cost DECIMAL NOT NULL,
|
||||||
sellerID INTEGER NOT NULL
|
sellerID INTEGER NOT NULL
|
||||||
REFERENCES Users (id)
|
REFERENCES Users (id)
|
||||||
ON DELETE CASCADE
|
ON DELETE CASCADE
|
||||||
ON UPDATE NO ACTION,
|
ON UPDATE NO ACTION,
|
||||||
categoryID INTEGER NOT NULL
|
categoryID INTEGER NOT NULL
|
||||||
REFERENCES Categories (id)
|
REFERENCES Categories (id)
|
||||||
ON DELETE CASCADE
|
ON DELETE CASCADE
|
||||||
ON UPDATE NO ACTION,
|
ON UPDATE NO ACTION,
|
||||||
postedDate TIMESTAMP,
|
postedDate TIMESTAMP,
|
||||||
quantityAvailable INTEGER DEFAULT 0
|
quantityAvailable INTEGER DEFAULT 0
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS Orders (
|
CREATE TABLE IF NOT EXISTS Orders (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
sellerID TEXT NOT NULL
|
sellerID TEXT NOT NULL
|
||||||
REFERENCES Users (id)
|
REFERENCES Users (id)
|
||||||
ON DELETE NO ACTION
|
ON DELETE NO ACTION
|
||||||
ON UPDATE NO ACTION,
|
ON UPDATE NO ACTION,
|
||||||
total DECIMAL NOT NULL,
|
total DECIMAL NOT NULL,
|
||||||
buyerID INTEGER NOT NULL
|
buyerID INTEGER NOT NULL
|
||||||
REFERENCES Users (id)
|
REFERENCES Users (id)
|
||||||
ON DELETE CASCADE
|
ON DELETE CASCADE
|
||||||
ON UPDATE NO ACTION,
|
ON UPDATE NO ACTION,
|
||||||
orderDate DATE NOT NULL
|
orderDate DATE NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
@ -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 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 6);
|
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "brake-disks.png", "this is a product", 20.99, 1, 6);
|
@ -1,73 +1,73 @@
|
|||||||
.alert {
|
.alert {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 3em;
|
bottom: 3em;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, 50%);
|
transform: translate(-50%, 50%);
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
line-height: 1.8;
|
line-height: 1.8;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
cursor: hand;
|
cursor: hand;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alertCheckbox {
|
.alertCheckbox {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
:checked + .alert {
|
:checked + .alert {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alertText {
|
.alertText {
|
||||||
display: table;
|
display: table;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 0 5px 0 0;
|
padding: 0 5px 0 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alertClose {
|
.alertClose {
|
||||||
float: right;
|
float: right;
|
||||||
padding-top: 5px;
|
padding-top: 5px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clear {
|
.clear {
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
background-color: #EEE;
|
background-color: #EEE;
|
||||||
border: 1px solid #DDD;
|
border: 1px solid #DDD;
|
||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.success {
|
.success {
|
||||||
background-color: #EFE;
|
background-color: #EFE;
|
||||||
border: 1px solid #DED;
|
border: 1px solid #DED;
|
||||||
color: #9A9;
|
color: #9A9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice {
|
.notice {
|
||||||
background-color: #EFF;
|
background-color: #EFF;
|
||||||
border: 1px solid #DEE;
|
border: 1px solid #DEE;
|
||||||
color: #9AA;
|
color: #9AA;
|
||||||
}
|
}
|
||||||
|
|
||||||
.warning {
|
.warning {
|
||||||
background-color: #FDF7DF;
|
background-color: #FDF7DF;
|
||||||
border: 1px solid #FEEC6F;
|
border: 1px solid #FEEC6F;
|
||||||
color: #C9971C;
|
color: #C9971C;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
background-color: #FEE;
|
background-color: #FEE;
|
||||||
border: 1px solid #EDD;
|
border: 1px solid #EDD;
|
||||||
color: #A66;
|
color: #A66;
|
||||||
}
|
}
|
@ -1,69 +1,69 @@
|
|||||||
.filter-pane {
|
.filter-pane {
|
||||||
flex: 0 1 auto;
|
flex: 0 1 auto;
|
||||||
width: 60%;
|
width: 60%;
|
||||||
padding: .5rem 2rem;
|
padding: .5rem 2rem;
|
||||||
background-color: var(--bg-grad-3);
|
background-color: var(--bg-grad-3);
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-items {
|
.filter-items {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 2rem;
|
gap: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-filter {
|
.product-filter {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: var(--bg-grad-2);
|
background-color: var(--bg-grad-2);
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
font-size: .75rem;
|
font-size: .75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Number Plate*/
|
/* Number Plate*/
|
||||||
.number-plate {
|
.number-plate {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.country-identifier {
|
.country-identifier {
|
||||||
width: auto;
|
width: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
.country-identifier img {
|
.country-identifier img {
|
||||||
border-radius: 8px 0 0 8px;
|
border-radius: 8px 0 0 8px;
|
||||||
}
|
}
|
||||||
.vrn {
|
.vrn {
|
||||||
width: 10rem;
|
width: 10rem;
|
||||||
height: 63px;
|
height: 63px;
|
||||||
border: 1px solid #ead809;
|
border: 1px solid #ead809;
|
||||||
background-color: #ead809;
|
background-color: #ead809;
|
||||||
border-radius: 0 8px 8px 0;
|
border-radius: 0 8px 8px 0;
|
||||||
}
|
}
|
||||||
.vrn .vrn-text {
|
.vrn .vrn-text {
|
||||||
width: -webkit-fill-available;
|
width: -webkit-fill-available;
|
||||||
height: -webkit-fill-available;
|
height: -webkit-fill-available;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
font-family: "UKNumberPlate", sans-serif;
|
font-family: "UKNumberPlate", sans-serif;
|
||||||
font-size: 180%;
|
font-size: 180%;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
.vrn .vrn-text:focus {
|
.vrn .vrn-text:focus {
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
@ -1,155 +1,155 @@
|
|||||||
h2 {
|
h2 {
|
||||||
font-weight:300;
|
font-weight:300;
|
||||||
text-align:center;
|
text-align:center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#input-form-wrap {
|
#input-form-wrap {
|
||||||
background-color: rgba(255, 255, 255, .15);
|
background-color: rgba(255, 255, 255, .15);
|
||||||
backdrop-filter: blur(200px);
|
backdrop-filter: blur(200px);
|
||||||
width: 35%;
|
width: 35%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 1em 0 0 0;
|
padding: 1em 0 0 0;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: 0px 30px 50px 0px rgba(0, 0, 0, 0.2);
|
box-shadow: 0px 30px 50px 0px rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-form {
|
.input-form {
|
||||||
padding: 1em 2em;
|
padding: 1em 2em;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-items: center;
|
justify-items: center;
|
||||||
gap: 1em;
|
gap: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-form-row {
|
.input-form-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 1em 1em;
|
gap: 1em 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-form input, .input-form select, .input-form option, .input-form textarea {
|
.input-form input, .input-form select, .input-form option, .input-form textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0 0 0 10px;
|
padding: 0 0 0 10px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
border: 1px solid var(--fg);
|
border: 1px solid var(--fg);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
outline: none;
|
outline: none;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
line-height: 60px;
|
line-height: 60px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background: none;
|
background: none;
|
||||||
&:focus {
|
&:focus {
|
||||||
&:invalid {
|
&:invalid {
|
||||||
color: var(--red);
|
color: var(--red);
|
||||||
border-color: var(--red);
|
border-color: var(--red);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:valid {
|
&:valid {
|
||||||
border-color: var(--green);
|
border-color: var(--green);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-form textarea {
|
.input-form textarea {
|
||||||
min-height: 120px;
|
min-height: 120px;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-form input[type="submit"] {
|
.input-form input[type="submit"] {
|
||||||
border: none;
|
border: none;
|
||||||
display:block;
|
display:block;
|
||||||
background-color: rgba(255, 255, 255, .10);
|
background-color: rgba(255, 255, 255, .10);
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-transform:uppercase;
|
text-transform:uppercase;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgba(255, 255, 255, .20);
|
background-color: rgba(255, 255, 255, .20);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#create-account-wrap {
|
#create-account-wrap {
|
||||||
background-color: rgba(255, 255, 255, .15);
|
background-color: rgba(255, 255, 255, .15);
|
||||||
color:#dfdfdf;
|
color:#dfdfdf;
|
||||||
font-size:14px;
|
font-size:14px;
|
||||||
width:100%;
|
width:100%;
|
||||||
padding:10px 0;
|
padding:10px 0;
|
||||||
border-radius: 0 0 4px 4px;
|
border-radius: 0 0 4px 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox {
|
.checkbox {
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-left: 35px;
|
padding-left: 35px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
-ms-user-select: none;
|
-ms-user-select: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide the browser's default checkbox */
|
/* Hide the browser's default checkbox */
|
||||||
.checkbox input {
|
.checkbox input {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
height: 0;
|
height: 0;
|
||||||
width: 0;
|
width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Create a custom checkbox */
|
/* Create a custom checkbox */
|
||||||
.checkmark {
|
.checkmark {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
width: 25px;
|
width: 25px;
|
||||||
background-color: #808080;
|
background-color: #808080;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* On mouse-over, add a grey background color */
|
/* On mouse-over, add a grey background color */
|
||||||
.checkbox:hover input ~ .checkmark {
|
.checkbox:hover input ~ .checkmark {
|
||||||
background-color: #ccc;
|
background-color: #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* When the checkbox is checked, add a blue background */
|
/* When the checkbox is checked, add a blue background */
|
||||||
.checkbox input:checked ~ .checkmark {
|
.checkbox input:checked ~ .checkmark {
|
||||||
background-color: #2196F3;
|
background-color: #2196F3;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Create the checkmark/indicator (hidden when not checked) */
|
/* Create the checkmark/indicator (hidden when not checked) */
|
||||||
.checkmark:after {
|
.checkmark:after {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Show the checkmark when checked */
|
/* Show the checkmark when checked */
|
||||||
.checkbox input:checked ~ .checkmark:after {
|
.checkbox input:checked ~ .checkmark:after {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Style the checkmark/indicator */
|
/* Style the checkmark/indicator */
|
||||||
.checkbox .checkmark:after {
|
.checkbox .checkmark:after {
|
||||||
left: 9px;
|
left: 9px;
|
||||||
top: 5px;
|
top: 5px;
|
||||||
width: 5px;
|
width: 5px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
border: solid white;
|
border: solid white;
|
||||||
border-width: 0 3px 3px 0;
|
border-width: 0 3px 3px 0;
|
||||||
-webkit-transform: rotate(45deg);
|
-webkit-transform: rotate(45deg);
|
||||||
-ms-transform: rotate(45deg);
|
-ms-transform: rotate(45deg);
|
||||||
transform: rotate(45deg);
|
transform: rotate(45deg);
|
||||||
}
|
}
|
@ -1,163 +1,163 @@
|
|||||||
/* Product Information*/
|
/* Product Information*/
|
||||||
|
|
||||||
.product-link:after {
|
.product-link:after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 15%;
|
left: 15%;
|
||||||
width: 0%;
|
width: 0%;
|
||||||
border-bottom: 2px solid var(--fg);
|
border-bottom: 2px solid var(--fg);
|
||||||
transition: 0.4s;
|
transition: 0.4s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-link:hover:after {
|
.product-link:hover:after {
|
||||||
width: 70%;
|
width: 70%;
|
||||||
color: var(--hover);
|
color: var(--hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-link:hover {
|
.product-link:hover {
|
||||||
color: var(--hover);
|
color: var(--hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-link:active {
|
.product-link:active {
|
||||||
color: var(--header);
|
color: var(--header);
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-container {
|
.product-container {
|
||||||
width: 95%;
|
width: 95%;
|
||||||
background-color: var(--bg-grad-3);
|
background-color: var(--bg-grad-3);
|
||||||
border-radius: 1rem 1rem 0rem 0rem;
|
border-radius: 1rem 1rem 0rem 0rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 2rem 1.5rem;
|
gap: 2rem 1.5rem;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product {
|
.product {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex: 4 0 1rem;
|
flex: 4 0 1rem;
|
||||||
max-width: 40%;
|
max-width: 40%;
|
||||||
gap: .5em .5em;
|
gap: .5em .5em;
|
||||||
padding: .5rem 0.2rem;
|
padding: .5rem 0.2rem;
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
border-radius: .5rem .5rem;
|
border-radius: .5rem .5rem;
|
||||||
border: .1em solid rgba(255, 255, 255, 15%);
|
border: .1em solid rgba(255, 255, 255, 15%);
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-fs {
|
.product-fs {
|
||||||
height: 80%;
|
height: 80%;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
font-size: 150%;
|
font-size: 150%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 2em 2em;
|
gap: 2em 2em;
|
||||||
padding: .5rem 1rem 2rem 2rem;
|
padding: .5rem 1rem 2rem 2rem;
|
||||||
/* background: var(--bg-secondary); */
|
/* background: var(--bg-secondary); */
|
||||||
border-radius: .5rem .5rem;
|
border-radius: .5rem .5rem;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-content-container {
|
.product-content-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 1em 1em;
|
gap: 1em 1em;
|
||||||
padding: 0rem 1rem;
|
padding: 0rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-image-preview {
|
.product-image-preview {
|
||||||
max-height: 150px;
|
max-height: 150px;
|
||||||
max-width: 150px;
|
max-width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-image {
|
.product-image {
|
||||||
max-height: 500px;
|
max-height: 500px;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-title {
|
.product-title {
|
||||||
font-size: 120%;
|
font-size: 120%;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-details {
|
.product-details {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 5% 0;
|
padding: 5% 0;
|
||||||
/* justify-content: center; */
|
/* justify-content: center; */
|
||||||
/* align-items: center; */
|
/* align-items: center; */
|
||||||
gap: 1rem 1rem;
|
gap: 1rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-description {
|
.product-description {
|
||||||
font-size: 70%;
|
font-size: 70%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hide-overflow {
|
.hide-overflow {
|
||||||
inline-size: 200px;
|
inline-size: 200px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-acquisition-pane {
|
.product-acquisition-pane {
|
||||||
font-size: 80%;
|
font-size: 80%;
|
||||||
width: 100rem;
|
width: 100rem;
|
||||||
max-width: 20%;
|
max-width: 20%;
|
||||||
background-color: var(--bg-secondary);
|
background-color: var(--bg-secondary);
|
||||||
border: .3em solid rgba(255, 255, 255, 15%);
|
border: .3em solid rgba(255, 255, 255, 15%);
|
||||||
border-radius: .5rem;
|
border-radius: .5rem;
|
||||||
padding: 1rem 1rem;
|
padding: 1rem 1rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 1rem 1rem;
|
gap: 1rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-price {
|
.product-price {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-delivery {
|
.product-delivery {
|
||||||
font-size: 70%;
|
font-size: 70%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-postage {
|
.product-postage {
|
||||||
font-size: 70%;
|
font-size: 70%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-stock {
|
.product-stock {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.product-quantity {
|
.product-quantity {
|
||||||
font-size: 50%;
|
font-size: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-instock {
|
.product-instock {
|
||||||
color: var(--green);
|
color: var(--green);
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-nostock {
|
.product-nostock {
|
||||||
color: var(--red);
|
color: var(--red);
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-add-to-cart {
|
.product-add-to-cart {
|
||||||
padding: .25rem .5rem;
|
padding: .25rem .5rem;
|
||||||
margin-top: .25rem;
|
margin-top: .25rem;
|
||||||
margin-right: .5rem;
|
margin-right: .5rem;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
border: none;
|
border: none;
|
||||||
background-color: gray;
|
background-color: gray;
|
||||||
}
|
}
|
@ -1,174 +1,174 @@
|
|||||||
:root {
|
:root {
|
||||||
--bg: #282c34;
|
--bg: #282c34;
|
||||||
--bg-secondary: #474d57;
|
--bg-secondary: #474d57;
|
||||||
--bg-grad-1: #484e58;
|
--bg-grad-1: #484e58;
|
||||||
--bg-grad-2: #4e5560;
|
--bg-grad-2: #4e5560;
|
||||||
--bg-grad-3: #59616d;
|
--bg-grad-3: #59616d;
|
||||||
--bg-grad-4: #606a7b;
|
--bg-grad-4: #606a7b;
|
||||||
--bg-grad-5: #606978;
|
--bg-grad-5: #606978;
|
||||||
--input: #4e5560;
|
--input: #4e5560;
|
||||||
--fg: #ABB2BF;
|
--fg: #ABB2BF;
|
||||||
--header: #E06C75;
|
--header: #E06C75;
|
||||||
--link: #FFF;
|
--link: #FFF;
|
||||||
--hover: #888;
|
--hover: #888;
|
||||||
|
|
||||||
--green: #98C379;
|
--green: #98C379;
|
||||||
--red: #E06C75;
|
--red: #E06C75;
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
background-color: var(--bg);
|
background-color: var(--bg);
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
font-size: 160%;
|
font-size: 160%;
|
||||||
font-family: 'Courier New', Courier, 'Inter';
|
font-family: 'Courier New', Courier, 'Inter';
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar {
|
.navbar {
|
||||||
flex: 0 1 auto;
|
flex: 0 1 auto;
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Navbar styling*/
|
/* Navbar styling*/
|
||||||
nav {
|
nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 120%;
|
font-size: 120%;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav form {
|
nav form {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-bar {
|
.search-bar {
|
||||||
padding: .25rem;
|
padding: .25rem;
|
||||||
margin-top: .25rem;
|
margin-top: .25rem;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
width: 15rem;
|
width: 15rem;
|
||||||
border: none;
|
border: none;
|
||||||
transition: width 0.4s ease-in-out;
|
transition: width 0.4s ease-in-out;
|
||||||
background-color: var(--fg);
|
background-color: var(--fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width:1500px) {
|
@media (min-width:1500px) {
|
||||||
.search-bar:focus {
|
.search-bar:focus {
|
||||||
width: 30rem;
|
width: 30rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-button {
|
.search-button {
|
||||||
padding: .25rem .5rem;
|
padding: .25rem .5rem;
|
||||||
margin-top: .25rem;
|
margin-top: .25rem;
|
||||||
margin-right: .5rem;
|
margin-right: .5rem;
|
||||||
background: #ddd;
|
background: #ddd;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
border: none;
|
border: none;
|
||||||
background-color: orange;
|
background-color: orange;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sticky {
|
.sticky {
|
||||||
position: -webkit-sticky;
|
position: -webkit-sticky;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: -2rem;
|
top: -2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#logo {
|
#logo {
|
||||||
width: 8rem;
|
width: 8rem;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
*::-webkit-scrollbar,
|
*::-webkit-scrollbar,
|
||||||
*::-webkit-scrollbar-thumb {
|
*::-webkit-scrollbar-thumb {
|
||||||
width: 26px;
|
width: 26px;
|
||||||
border-radius: 13px;
|
border-radius: 13px;
|
||||||
background-clip: padding-box;
|
background-clip: padding-box;
|
||||||
border: 10px solid transparent;
|
border: 10px solid transparent;
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
}
|
}
|
||||||
*::-webkit-scrollbar-thumb:hover{
|
*::-webkit-scrollbar-thumb:hover{
|
||||||
color: var(--link);
|
color: var(--link);
|
||||||
}
|
}
|
||||||
|
|
||||||
*::-webkit-scrollbar-thumb {
|
*::-webkit-scrollbar-thumb {
|
||||||
box-shadow: inset 0 0 0 10px;
|
box-shadow: inset 0 0 0 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width:1000px) {
|
@media (max-width:1000px) {
|
||||||
.not-required {
|
.not-required {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
color: var(--link);
|
color: var(--link);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
transition: 0.4s;
|
transition: 0.4s;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:after {
|
a:after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 15%;
|
left: 15%;
|
||||||
width: 0%;
|
width: 0%;
|
||||||
border-bottom: 2px solid var(--fg);
|
border-bottom: 2px solid var(--fg);
|
||||||
transition: 0.4s;
|
transition: 0.4s;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover:after {
|
a:hover:after {
|
||||||
width: 70%;
|
width: 70%;
|
||||||
color: var(--hover);
|
color: var(--hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
color: var(--hover);
|
color: var(--hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
a:active {
|
a:active {
|
||||||
color: var(--header);
|
color: var(--header);
|
||||||
}
|
}
|
||||||
|
|
||||||
.categories {
|
.categories {
|
||||||
background-color: var(--bg);
|
background-color: var(--bg);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.category {
|
.category {
|
||||||
margin: .5rem 0rem;
|
margin: .5rem 0rem;
|
||||||
padding: 0rem 1rem;
|
padding: 0rem 1rem;
|
||||||
border-left: .125rem solid var(--fg);
|
border-left: .125rem solid var(--fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.category:first-child {
|
.category:first-child {
|
||||||
border-left: none;
|
border-left: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
height: 1%;
|
height: 1%;
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/carparts.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/carparts.css') }}">
|
||||||
|
|
||||||
<div class="filter-pane">
|
<div class="filter-pane">
|
||||||
<form action="" method="get" class="filter-items">
|
<form action="" method="get" class="filter-items">
|
||||||
<div class="number-plate">
|
<div class="number-plate">
|
||||||
<span class="country-identifier">
|
<span class="country-identifier">
|
||||||
<img src="https://mycarneedsa.com/assets/flint/img/flag_europe_gb.png" alt="">
|
<img src="https://mycarneedsa.com/assets/flint/img/flag_europe_gb.png" alt="">
|
||||||
</span>
|
</span>
|
||||||
<span class="vrn">
|
<span class="vrn">
|
||||||
<input type="text" class="vrn-text" placeholder="YOUR REG" name="vrn">
|
<input type="text" class="vrn-text" placeholder="YOUR REG" name="vrn">
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<select class="product-filter not-required" name="filter">
|
<select class="product-filter not-required" name="filter">
|
||||||
<option value="relevance">Most Relevant</option>
|
<option value="relevance">Most Relevant</option>
|
||||||
<option value="price-lh">Price: Low -> High</option>
|
<option value="price-lh">Price: Low -> High</option>
|
||||||
<option value="price-hl">Price: High -> Low</option>
|
<option value="price-hl">Price: High -> Low</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<input type="submit" class="search-button" value="Filter">
|
<input type="submit" class="search-button" value="Filter">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/products.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/products.css') }}">
|
||||||
|
|
||||||
<div class="product-container">
|
<div class="product-container">
|
||||||
{% if products != None %}
|
{% if products != None %}
|
||||||
{% for product in products %}
|
{% for product in products %}
|
||||||
<a href="/products/{{product.id}}" class="product product-link">
|
<a href="/products/{{product.id}}" class="product product-link">
|
||||||
<div class="product-title">{{product.name}}</div>
|
<div class="product-title">{{product.name}}</div>
|
||||||
<div class="product-content-container">
|
<div class="product-content-container">
|
||||||
<img class="product-image-preview" src="{{url_for('static', filename='assets/img/products/' + product.image)}}" alt="Brake Disks" />
|
<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-details">
|
||||||
<div class="product-price">£{{product.cost}}</div>
|
<div class="product-price">£{{product.cost}}</div>
|
||||||
<div class="product-description hide-overflow ">{{product.description}}</div>
|
<div class="product-description hide-overflow ">{{product.description}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input type="submit" class="product-add-to-cart" value="Add to Cart" />
|
<input type="submit" class="product-add-to-cart" value="Add to Cart" />
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,51 +1,51 @@
|
|||||||
<div class="navbar">
|
<div class="navbar">
|
||||||
<nav>
|
<nav>
|
||||||
<a href="/"><img src="{{url_for('static', filename='assets/img/wmgzon.png')}}" id="logo" class="not-required" alt="WMGZON Logo"></a>
|
<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">
|
<form action="" method="get">
|
||||||
<input type="text" name="search" placeholder="Find your favourite products" class="search-bar">
|
<input type="text" name="search" placeholder="Find your favourite products" class="search-bar">
|
||||||
<input type="submit" class="search-button">
|
<input type="submit" class="search-button">
|
||||||
</form>
|
</form>
|
||||||
{% if user != None: %}
|
{% if user != None: %}
|
||||||
<a href="/logout">Welcome, {{ user.username }}</a>
|
<a href="/logout">Welcome, {{ user.username }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="/login">Login/Signup</a>
|
<a href="/login">Login/Signup</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<centre>
|
<centre>
|
||||||
{% if user != None and user.role == "Seller" %}
|
{% if user != None and user.role == "Seller" %}
|
||||||
<div class="categories">
|
<div class="categories">
|
||||||
{# List all available seller tools #}
|
{# List all available seller tools #}
|
||||||
<a href="/products/add" class="category">Create Products</a>
|
<a href="/products/add" class="category">Create Products</a>
|
||||||
<a href="/products/ownproducts" class="category">View My Products</a>
|
<a href="/products/ownproducts" class="category">View My Products</a>
|
||||||
<a href="/products/stats" class="category">View Seller Stats</a>
|
<a href="/products/stats" class="category">View Seller Stats</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="categories">
|
<div class="categories">
|
||||||
{# List all categories and ensure the selected one is highlighted #}
|
{# List all categories and ensure the selected one is highlighted #}
|
||||||
{% for c in categories %}
|
{% for c in categories %}
|
||||||
{% if category == c.name %}
|
{% if category == c.name %}
|
||||||
<a style="color: cyan" href="/products/{{c.name}}" class="category">{{c.name}}</a>
|
<a style="color: cyan" href="/products/{{c.name}}" class="category">{{c.name}}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="/products/{{c.name}}" class="category">{{c.name}}</a>
|
<a href="/products/{{c.name}}" class="category">{{c.name}}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</centre>
|
</centre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% with messages = get_flashed_messages(with_categories=true)%}
|
{% with messages = get_flashed_messages(with_categories=true)%}
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
{% for category, message in messages %}
|
{% for category, message in messages %}
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" class="alertCheckbox" autocomplete="off" />
|
<input type="checkbox" class="alertCheckbox" autocomplete="off" />
|
||||||
<div class="alert {{category}}">
|
<div class="alert {{category}}">
|
||||||
<span class="alertClose">X</span>
|
<span class="alertClose">X</span>
|
||||||
<span class="alertText">{{message}}
|
<span class="alertText">{{message}}
|
||||||
<br class="clear"/></span>
|
<br class="clear"/></span>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
{% endfor%}
|
{% endfor%}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
@ -1,28 +1,28 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<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/style.css')}}" />
|
||||||
<link rel="stylesheet" href="{{url_for('static', filename='css/alerts.css')}}" />
|
<link rel="stylesheet" href="{{url_for('static', filename='css/alerts.css')}}" />
|
||||||
<link href="http://fonts.cdnfonts.com/css/uk-number-plate" rel="stylesheet">
|
<link href="http://fonts.cdnfonts.com/css/uk-number-plate" rel="stylesheet">
|
||||||
|
|
||||||
<!-- Inter font set used across the whole page -->
|
<!-- Inter font set used across the whole page -->
|
||||||
<link rel="preconnect" href="https://rsms.me/">
|
<link rel="preconnect" href="https://rsms.me/">
|
||||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
|
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
|
||||||
<title>WMGZON</title>
|
<title>WMGZON</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{% include 'header.html' %}
|
{% include 'header.html' %}
|
||||||
|
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{% if category is defined %}
|
{% if category is defined %}
|
||||||
{% set include_file = category+".html" %}
|
{% set include_file = category+".html" %}
|
||||||
{% include include_file ignore missing %}
|
{% include include_file ignore missing %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% include content %}
|
{% include content %}
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@ -1,14 +1,14 @@
|
|||||||
<link rel="stylesheet" href="{{url_for('static', filename='css/loginform.css')}}" />
|
<link rel="stylesheet" href="{{url_for('static', filename='css/loginform.css')}}" />
|
||||||
|
|
||||||
<div id="input-form-wrap">
|
<div id="input-form-wrap">
|
||||||
<h2>Login</h2>
|
<h2>Login</h2>
|
||||||
<form class="input-form" method="POST">
|
<form class="input-form" method="POST">
|
||||||
<input type="text" id="username" name="username" placeholder="Username" required>
|
<input type="text" id="username" name="username" placeholder="Username" required>
|
||||||
<input type="password" id="password" name="password" placeholder="Password" required>
|
<input type="password" id="password" name="password" placeholder="Password" required>
|
||||||
<input type="submit" id="login" value="Login">
|
<input type="submit" id="login" value="Login">
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div id="create-account-wrap">
|
<div id="create-account-wrap">
|
||||||
<p>Not a member? <a href="signup">Create Account</a><p>
|
<p>Not a member? <a href="signup">Create Account</a><p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,29 +1,29 @@
|
|||||||
<link rel="stylesheet" href="{{url_for('static', filename='css/loginform.css')}}" />
|
<link rel="stylesheet" href="{{url_for('static', filename='css/loginform.css')}}" />
|
||||||
|
|
||||||
<div id="input-form-wrap">
|
<div id="input-form-wrap">
|
||||||
<h2>Create New Product</h2>
|
<h2>Create New Product</h2>
|
||||||
<form class="input-form" method="POST" enctype="multipart/form-data">
|
<form class="input-form" method="POST" enctype="multipart/form-data">
|
||||||
<div class="input-form-row">
|
<div class="input-form-row">
|
||||||
<input type="text" id="name" name="name" placeholder="Product Name" required>
|
<input type="text" id="name" name="name" placeholder="Product Name" required>
|
||||||
<input type="file" id="image" name="image" accept="image/x" required>
|
<input type="file" id="image" name="image" accept="image/x" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<textarea id="description" name="description" placeholder="Product Description" required></textarea>
|
<textarea id="description" name="description" placeholder="Product Description" required></textarea>
|
||||||
|
|
||||||
<select name="category" id="category">
|
<select name="category" id="category">
|
||||||
{% for category in categories %}
|
{% for category in categories %}
|
||||||
<option value="{{category.id}}">{{category.name}}</option>
|
<option value="{{category.id}}">{{category.name}}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<div class="input-form-row">
|
<div class="input-form-row">
|
||||||
<input type="number" id="cost" name="cost" placeholder=10.99 min=0 step=any required>
|
<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>
|
<input type="number" id="quantity" name="quantity" placeholder=0 min=0 required>
|
||||||
</div>
|
</div>
|
||||||
<input type="submit" id="Create Product" value="Create Product">
|
<input type="submit" id="Create Product" value="Create Product">
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div id="create-account-wrap">
|
<div id="create-account-wrap">
|
||||||
<p>Want to view all of your products? <a href="">Click Here</a><p>
|
<p>Want to view all of your products? <a href="">Click Here</a><p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,65 +1,65 @@
|
|||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/products.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/products.css') }}">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/loginform.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/loginform.css') }}">
|
||||||
|
|
||||||
<div class="product-fs">
|
<div class="product-fs">
|
||||||
{% if product != None %}
|
{% if product != None %}
|
||||||
{% if user.id == product.sellerID %}
|
{% if user.id == product.sellerID %}
|
||||||
<form class="input-form" method="POST" action="/products/update/{{product.id}}" enctype="multipart/form-data">
|
<form class="input-form" method="POST" action="/products/update/{{product.id}}" enctype="multipart/form-data">
|
||||||
<div class="product-title">
|
<div class="product-title">
|
||||||
<input type="text" id="name" name="name" placeholder="Product Name" value="{{product.name}}" required>
|
<input type="text" id="name" name="name" placeholder="Product Name" value="{{product.name}}" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="product-information">
|
<div class="product-information">
|
||||||
<div class="product-image">
|
<div class="product-image">
|
||||||
<img src="{{url_for('static', filename='assets/img/products/' + product.image)}}" alt="Brake Disks" height="auto" width="150px" />
|
<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">
|
<input type="file" id="image" name="image" accept="image/x">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
<div class="input-form-row">
|
<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="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>
|
<input type="textarea" id="description" name="description" placeholder="Product Description" value="{{product.description}}" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="input-form-row">
|
<div class="input-form-row">
|
||||||
<select name="category" id="category">
|
<select name="category" id="category">
|
||||||
{% for category in categories %}
|
{% for category in categories %}
|
||||||
{% if category.id == product.category %}
|
{% if category.id == product.category %}
|
||||||
<option value="{{category.id}}" selected>{{category.name}}</option>
|
<option value="{{category.id}}" selected>{{category.name}}</option>
|
||||||
{% else %}
|
{% else %}
|
||||||
<option value="{{category.id}}">{{category.name}}</option>
|
<option value="{{category.id}}">{{category.name}}</option>
|
||||||
{% endif%}
|
{% endif%}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="input-form-row">
|
<div class="input-form-row">
|
||||||
<input type="number" id="quantity" name="quantity" placeholder=0 min=0 value="{{product.quantityAvailable}}" required>
|
<input type="number" id="quantity" name="quantity" placeholder=0 min=0 value="{{product.quantityAvailable}}" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input type="submit" id="Create Product" value="Create Product">
|
<input type="submit" id="Create Product" value="Create Product">
|
||||||
</form>
|
</form>
|
||||||
<div class="product-add-to-cart"></div>
|
<div class="product-add-to-cart"></div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<img class="product-image" src="{{url_for('static', filename='assets/img/products/' + product.image)}}" alt="Brake Disks"/>
|
<img class="product-image" src="{{url_for('static', filename='assets/img/products/' + product.image)}}" alt="Brake Disks"/>
|
||||||
<div class="product-details">
|
<div class="product-details">
|
||||||
<div class="product-title">{{product.name}}</div>
|
<div class="product-title">{{product.name}}</div>
|
||||||
<div class="product-description">{{product.description}}</div>
|
<div class="product-description">{{product.description}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="product-acquisition-pane">
|
<div class="product-acquisition-pane">
|
||||||
<div class="product-price">£{{product.cost}}</div>
|
<div class="product-price">£{{product.cost}}</div>
|
||||||
<div class="product-delivery">Earliest Delivery Friday 24th December</div>
|
<div class="product-delivery">Earliest Delivery Friday 24th December</div>
|
||||||
<div class="product-postage">+£{{product.cost}} P&P</div>
|
<div class="product-postage">+£{{product.cost}} P&P</div>
|
||||||
<div class="product-stock">
|
<div class="product-stock">
|
||||||
{% if product.quantityAvailable > 0 %}
|
{% if product.quantityAvailable > 0 %}
|
||||||
<div class="product-instock">In Stock</div>
|
<div class="product-instock">In Stock</div>
|
||||||
<div class="product-quantity">{{product.quantityAvailable}} Available</div>
|
<div class="product-quantity">{{product.quantityAvailable}} Available</div>
|
||||||
<input type="submit" class="product-add-to-cart" value="Add to Cart" />
|
<input type="submit" class="product-add-to-cart" value="Add to Cart" />
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="product-nostock">Out of Stock</div>
|
<div class="product-nostock">Out of Stock</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,27 +1,27 @@
|
|||||||
<link rel="stylesheet" href="{{url_for('static', filename='css/loginform.css')}}" />
|
<link rel="stylesheet" href="{{url_for('static', filename='css/loginform.css')}}" />
|
||||||
|
|
||||||
<div id="input-form-wrap">
|
<div id="input-form-wrap">
|
||||||
<h2>Sign Up</h2>
|
<h2>Sign Up</h2>
|
||||||
<form class="input-form" method="POST">
|
<form class="input-form" method="POST">
|
||||||
<div class="input-form-row">
|
<div class="input-form-row">
|
||||||
<input type="text" id="firstname" name="firstname" placeholder="First Name" required>
|
<input type="text" id="firstname" name="firstname" placeholder="First Name" required>
|
||||||
<input type="text" id="lastname" name="lastname" placeholder="Last Name" required>
|
<input type="text" id="lastname" name="lastname" placeholder="Last Name" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="input-form-row">
|
<div class="input-form-row">
|
||||||
<input type="text" id="username" name="username" placeholder="Username" required>
|
<input type="text" id="username" name="username" placeholder="Username" required>
|
||||||
<input type="email" id="email" name="email" placeholder="Email Address" required>
|
<input type="email" id="email" name="email" placeholder="Email Address" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input type="password" id="password" name="password" minlength=8 placeholder="Password" required>
|
<input type="password" id="password" name="password" minlength=8 placeholder="Password" required>
|
||||||
<label class="checkbox">Signup as a Seller?
|
<label class="checkbox">Signup as a Seller?
|
||||||
<input type="checkbox" id="seller" name="seller"/>
|
<input type="checkbox" id="seller" name="seller"/>
|
||||||
<span class="checkmark"></span>
|
<span class="checkmark"></span>
|
||||||
</label>
|
</label>
|
||||||
<input type="submit" id="Sign Up" value="Sign Up">
|
<input type="submit" id="Sign Up" value="Sign Up">
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div id="create-account-wrap">
|
<div id="create-account-wrap">
|
||||||
<p>Already have an account? <a href="login">Login</a><p>
|
<p>Already have an account? <a href="login">Login</a><p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
# Setup test environment variables
|
# Setup test environment variables
|
||||||
os.environ["ENVIRON"] = "test"
|
os.environ["ENVIRON"] = "test"
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
# Ensure test environment is set before using
|
# Ensure test environment is set before using
|
||||||
# Runs the database creation scripts
|
# Runs the database creation scripts
|
||||||
import scripts.create_database
|
import scripts.create_database
|
||||||
|
@ -1,70 +1,70 @@
|
|||||||
import pytest
|
import pytest
|
||||||
import sqlite3
|
import sqlite3
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from controllers.database.product import ProductController
|
from controllers.database.product import ProductController
|
||||||
from models.products.product import Product
|
from models.products.product import Product
|
||||||
|
|
||||||
product = Product(
|
product = Product(
|
||||||
"product",
|
"product",
|
||||||
"image.png",
|
"image.png",
|
||||||
"description",
|
"description",
|
||||||
10.00,
|
10.00,
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
datetime.now(),
|
datetime.now(),
|
||||||
1
|
1
|
||||||
)
|
)
|
||||||
|
|
||||||
# Tests a new product can be created
|
# Tests a new product can be created
|
||||||
|
|
||||||
|
|
||||||
def test_create_product():
|
def test_create_product():
|
||||||
db = ProductController()
|
db = ProductController()
|
||||||
db.create(product)
|
db.create(product)
|
||||||
|
|
||||||
# Tests the database maintains integrity when we try
|
# Tests the database maintains integrity when we try
|
||||||
# and add a product with the same details
|
# and add a product with the same details
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip
|
@pytest.mark.skip
|
||||||
def test_duplicate_product():
|
def test_duplicate_product():
|
||||||
db = ProductController()
|
db = ProductController()
|
||||||
with pytest.raises(sqlite3.IntegrityError):
|
with pytest.raises(sqlite3.IntegrityError):
|
||||||
db.create(product)
|
db.create(product)
|
||||||
|
|
||||||
# Tests that products can be refined by category
|
# Tests that products can be refined by category
|
||||||
|
|
||||||
|
|
||||||
def test_search_category():
|
def test_search_category():
|
||||||
db = ProductController()
|
db = ProductController()
|
||||||
|
|
||||||
# Check each category for correct amount of test products
|
# 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("Car Parts")) == 9 + 1 # Added in previous test
|
||||||
assert len(db.read_all("Books")) == 9
|
assert len(db.read_all("Books")) == 9
|
||||||
assert db.read_all("Phones") is None
|
assert db.read_all("Phones") is None
|
||||||
|
|
||||||
|
|
||||||
# Tests that products can be refined by search term
|
# Tests that products can be refined by search term
|
||||||
def test_search_term():
|
def test_search_term():
|
||||||
db = ProductController()
|
db = ProductController()
|
||||||
|
|
||||||
# Check each search term for correct amount of test products
|
# Check each search term for correct amount of test products
|
||||||
assert len(db.read_all(search_term="test")) == 33
|
assert len(db.read_all(search_term="test")) == 33
|
||||||
assert len(db.read_all("Car Parts", "test")) == 9
|
assert len(db.read_all("Car Parts", "test")) == 9
|
||||||
assert len(db.read_all(search_term="product")) == 1
|
assert len(db.read_all(search_term="product")) == 1
|
||||||
assert db.read_all(search_term="not_test") is None
|
assert db.read_all(search_term="not_test") is None
|
||||||
|
|
||||||
# Test we the same product details get returned from the database
|
# Test we the same product details get returned from the database
|
||||||
|
|
||||||
|
|
||||||
def test_read_product():
|
def test_read_product():
|
||||||
db = ProductController()
|
db = ProductController()
|
||||||
|
|
||||||
# Test the same product is returned
|
# Test the same product is returned
|
||||||
new_product = db.read("product")
|
new_product = db.read("product")
|
||||||
assert isinstance(new_product, list)
|
assert isinstance(new_product, list)
|
||||||
assert isinstance(new_product[0], Product)
|
assert isinstance(new_product[0], Product)
|
||||||
|
|
||||||
# Update the ID on the item as database assigns new id
|
# Update the ID on the item as database assigns new id
|
||||||
product.id = new_product[0].id
|
product.id = new_product[0].id
|
||||||
assert new_product[0].__dict__ == product.__dict__
|
assert new_product[0].__dict__ == product.__dict__
|
||||||
|
@ -1,74 +1,74 @@
|
|||||||
import pytest
|
import pytest
|
||||||
import sqlite3
|
import sqlite3
|
||||||
from controllers.database.user import UserController
|
from controllers.database.user import UserController
|
||||||
from models.users.customer import Customer
|
from models.users.customer import Customer
|
||||||
from models.users.seller import Seller
|
from models.users.seller import Seller
|
||||||
|
|
||||||
customer = Customer(
|
customer = Customer(
|
||||||
"testcustomer",
|
"testcustomer",
|
||||||
"Password1",
|
"Password1",
|
||||||
"firstname",
|
"firstname",
|
||||||
"lastname",
|
"lastname",
|
||||||
"test@test",
|
"test@test",
|
||||||
"123456789"
|
"123456789"
|
||||||
)
|
)
|
||||||
|
|
||||||
seller = Seller(
|
seller = Seller(
|
||||||
"testseller",
|
"testseller",
|
||||||
"Password1",
|
"Password1",
|
||||||
"firstname",
|
"firstname",
|
||||||
"lastname",
|
"lastname",
|
||||||
"seller@seller",
|
"seller@seller",
|
||||||
"987654321"
|
"987654321"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Tests a new user can be created
|
# Tests a new user can be created
|
||||||
|
|
||||||
|
|
||||||
def test_create_user():
|
def test_create_user():
|
||||||
db = UserController()
|
db = UserController()
|
||||||
db.create(customer)
|
db.create(customer)
|
||||||
|
|
||||||
# Tests the database maintains integrity when we try
|
# Tests the database maintains integrity when we try
|
||||||
# and add a user with the same details
|
# and add a user with the same details
|
||||||
|
|
||||||
|
|
||||||
def test_duplicate_user():
|
def test_duplicate_user():
|
||||||
db = UserController()
|
db = UserController()
|
||||||
with pytest.raises(sqlite3.IntegrityError):
|
with pytest.raises(sqlite3.IntegrityError):
|
||||||
db.create(customer)
|
db.create(customer)
|
||||||
|
|
||||||
# Test we the same user details get returned from the database
|
# Test we the same user details get returned from the database
|
||||||
|
|
||||||
|
|
||||||
def test_read_user():
|
def test_read_user():
|
||||||
db = UserController()
|
db = UserController()
|
||||||
|
|
||||||
# Test the same user is returned
|
# Test the same user is returned
|
||||||
user = db.read(customer.username)
|
user = db.read(customer.username)
|
||||||
assert isinstance(user, Customer)
|
assert isinstance(user, Customer)
|
||||||
|
|
||||||
# Update the ID on the item as database assigns new id
|
# Update the ID on the item as database assigns new id
|
||||||
customer.id = user.id
|
customer.id = user.id
|
||||||
assert user.__dict__ == customer.__dict__
|
assert user.__dict__ == customer.__dict__
|
||||||
|
|
||||||
|
|
||||||
# Tests a new seller can be created
|
# Tests a new seller can be created
|
||||||
def test_create_seller():
|
def test_create_seller():
|
||||||
db = UserController()
|
db = UserController()
|
||||||
db.create(seller)
|
db.create(seller)
|
||||||
|
|
||||||
# Test that the same seller details get returned from the database
|
# Test that the same seller details get returned from the database
|
||||||
|
|
||||||
|
|
||||||
def test_read_seller():
|
def test_read_seller():
|
||||||
db = UserController()
|
db = UserController()
|
||||||
|
|
||||||
# Test the same user is returned
|
# Test the same user is returned
|
||||||
user = db.read(seller.username)
|
user = db.read(seller.username)
|
||||||
assert isinstance(user, Seller)
|
assert isinstance(user, Seller)
|
||||||
|
|
||||||
# Update the ID on the item as database assigns new id
|
# Update the ID on the item as database assigns new id
|
||||||
seller.id = user.id
|
seller.id = user.id
|
||||||
user.store = ""
|
user.store = ""
|
||||||
assert user.__dict__ == seller.__dict__
|
assert user.__dict__ == seller.__dict__
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import pycodestyle
|
import pycodestyle
|
||||||
|
|
||||||
# Tests files to ensure they conform to pep8 standards
|
# Tests files to ensure they conform to pep8 standards
|
||||||
|
|
||||||
|
|
||||||
def test_pep8_conformance():
|
def test_pep8_conformance():
|
||||||
"""Test that we conform to PEP8."""
|
"""Test that we conform to PEP8."""
|
||||||
pep8style = pycodestyle.StyleGuide()
|
pep8style = pycodestyle.StyleGuide()
|
||||||
dirs = ["./controllers", "./models", "./scripts", "./tests", "./utils"]
|
dirs = ["./controllers", "./models", "./scripts", "./tests", "./utils"]
|
||||||
result = pep8style.check_files(dirs)
|
result = pep8style.check_files(dirs)
|
||||||
assert result.total_errors == 0
|
assert result.total_errors == 0
|
||||||
|
@ -1,46 +1,46 @@
|
|||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
|
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
|
||||||
|
|
||||||
|
|
||||||
def allowed_file(filename) -> bool:
|
def allowed_file(filename) -> bool:
|
||||||
""" Ensures only filenames ending with the correct extension are allowed.
|
""" Ensures only filenames ending with the correct extension are allowed.
|
||||||
Note: This does not verify that the content inside of the file
|
Note: This does not verify that the content inside of the file
|
||||||
matches the type specified
|
matches the type specified
|
||||||
"""
|
"""
|
||||||
return '.' in filename and \
|
return '.' in filename and \
|
||||||
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
||||||
|
|
||||||
|
|
||||||
def save_image(file) -> str | None:
|
def save_image(file) -> str | None:
|
||||||
""" Saves a given file to disk with a random UUID4 generated
|
""" Saves a given file to disk with a random UUID4 generated
|
||||||
filename. Returns the filename as a string.
|
filename. Returns the filename as a string.
|
||||||
"""
|
"""
|
||||||
# Ensure that the correct file type is uploaded
|
# Ensure that the correct file type is uploaded
|
||||||
if file is None or not allowed_file(file.filename):
|
if file is None or not allowed_file(file.filename):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Create the product object and push to database
|
# Create the product object and push to database
|
||||||
filename = str(uuid.uuid4()) + pathlib.Path(file.filename).suffix
|
filename = str(uuid.uuid4()) + pathlib.Path(file.filename).suffix
|
||||||
|
|
||||||
path = os.environ.get('FILESTORE')
|
path = os.environ.get('FILESTORE')
|
||||||
file.save(os.path.join(path, filename))
|
file.save(os.path.join(path, filename))
|
||||||
return filename
|
return filename
|
||||||
|
|
||||||
|
|
||||||
def create_directory(dir: str):
|
def create_directory(dir: str):
|
||||||
""" Creates the given directory string is not alreay made """
|
""" Creates the given directory string is not alreay made """
|
||||||
try:
|
try:
|
||||||
os.makedirs(dir)
|
os.makedirs(dir)
|
||||||
except FileExistsError:
|
except FileExistsError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def remove_file(dir: str):
|
def remove_file(dir: str):
|
||||||
""" Removes a given file if it is present at the given dir """
|
""" Removes a given file if it is present at the given dir """
|
||||||
try:
|
try:
|
||||||
os.remove(dir)
|
os.remove(dir)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
|
@ -1,26 +1,26 @@
|
|||||||
from flask import session
|
from flask import session
|
||||||
from models.users.user import User
|
from models.users.user import User
|
||||||
from controllers.database.user import UserController
|
from controllers.database.user import UserController
|
||||||
|
|
||||||
|
|
||||||
def is_logged_in() -> User | None:
|
def is_logged_in() -> User | None:
|
||||||
""" Returns the user object if the user is logged in
|
""" Returns the user object if the user is logged in
|
||||||
Otherwise returns a None type
|
Otherwise returns a None type
|
||||||
"""
|
"""
|
||||||
user_id = session.get('user_id')
|
user_id = session.get('user_id')
|
||||||
|
|
||||||
if user_id is not None:
|
if user_id is not None:
|
||||||
db = UserController()
|
db = UserController()
|
||||||
return db.read_id(user_id)
|
return db.read_id(user_id)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def is_role(role: str) -> bool:
|
def is_role(role: str) -> bool:
|
||||||
""" Function that returns true if the user is logged in as"""
|
""" Function that returns true if the user is logged in as"""
|
||||||
user = is_logged_in()
|
user = is_logged_in()
|
||||||
|
|
||||||
if user is not None:
|
if user is not None:
|
||||||
return user.role == role
|
return user.role == role
|
||||||
|
|
||||||
# User isn't logged in
|
# User isn't logged in
|
||||||
return False
|
return False
|
||||||
|
Loading…
Reference in New Issue
Block a user