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