Compare commits

...

41 Commits
v0.7.0 ... main

Author SHA1 Message Date
f24880c09a fix: raise user password char length (#18)
All checks were successful
Create Release / release (push) Successful in 28s
Create Release / docker (push) Successful in 8m55s
Co-authored-by: Lunaresk <lingsonb@gmail.com>
Reviewed-on: #18
2025-06-03 21:39:00 +02:00
3622cbca05 fix: workflow login
All checks were successful
Create Release / release (push) Successful in 21s
Create Release / docker (push) Successful in 8m43s
2025-04-09 01:29:54 +02:00
637a5f7ad5 fix: stylesheet
Some checks failed
Create Release / release (push) Successful in 2m1s
Create Release / docker (push) Failing after 8m22s
2025-04-09 00:50:15 +02:00
15ba3c060a Workflow Action for Docker Repo (#2)
Reviewed-on: #2
2024-11-03 17:57:37 +00:00
bde9a50767 fix: update dockerfile 2024-11-03 15:47:29 +01:00
60e221146a Actions Workflow Fix 2024-11-03 10:51:25 +01:00
d34f2adb2f fix: correct OCR language 2024-11-02 23:46:02 +01:00
a6f229e7fb minor: set folder for receipts 2024-08-26 19:43:47 +02:00
5a453a140e major: integrate ocr to pdf reader 2024-08-25 22:19:53 +02:00
0f6f76a66b fix: money can now be entered in decimal 2024-08-18 18:39:56 +02:00
14b7e1f84c bug: model fix 2024-06-16 17:25:43 +02:00
560016cc18 minor: add table as save for unknown items 2024-06-16 16:21:57 +02:00
b57dbba5d6 minor: display payments on overview
Payments are now displayed at the bottom of the lists.
Also, receipts can now be uploaded without necessarily
providing an image of the original.
2024-02-18 17:37:06 +01:00
7ae57ae3a4 major: add calculation for payments 2024-02-09 23:12:23 +01:00
04a13635dc minor: add button for upload receipt 2023-11-27 22:13:50 +01:00
5c46a24338 bugfix 2023-11-27 00:49:21 +01:00
f41fdd076b major: add dynamic receipt item forms
New:
- Added the possibility to insert custom items on receipt upload in case
the receipt couldn't be read.
Changes:
- Changed the display of the sum at the overview. You now see what you
owe your establishment instead of just seeing how much you bought.
- In addition the sum in the overview now accounts for membership time.
You don't have to pay for something bought after you moved out for
example.
2023-11-26 23:16:05 +01:00
4d1e7eb944 major: edit database and finish check_items
New:
- Removed the connection between tables 'receipt_item' and 'item'.
Instead the position on the receipt will be stored as a primary key
with the receipt.id.
- Uploading a receipt and checking items for invoice is now working.
- Uploading a receipt now redirects directly to the form where the
items on the receipt can be marked for invoice.
- Added pagination for candidates-page.

Refactoring:
- Added _rencer_field_errors.html for centralized error display.
- Added edeka_parser.py for better management of different parsers.

Known Bugs:
- Uploading a receipt without any fields results in an error.

Todo:
- Generate a general form if no fields are recognized on the uploaded
receipt.
- Create form where Admin can confirm receipt invoices.
- Display Notification for admin when users upload receipt.
- Display Notification for admin on new candidate.
2023-11-12 16:05:09 +01:00
6c0fa5a58b major: add receipt upload
You can now upload receipts and check which ones should be accounted.
Currently without function, needs to be implemented.
2023-08-06 22:25:08 +02:00
5e4a59e15f Merge remote-tracking branch 'origin/feat_receipt_upload' 2023-07-16 23:53:30 +02:00
d311b6f8cf minor: Flatastic module started 2023-07-01 13:53:42 +02:00
8e17a2fb25 minor: function for testing purposes
Reinserted the testfunction
2023-06-20 00:01:30 +02:00
dceda1446f major: refactoring
Refactored most imports to relative paths and made some experimental
changes to angular.
2023-06-19 23:41:40 +02:00
b21284f640 minor: add payment
Admins can now add payments for their users.
2023-05-28 09:07:54 +02:00
597725f46c minor: revert dockerfile and boot.sh
Minor improvements, bugfixes and first experiments with angular.
2023-04-16 16:27:46 +02:00
8069a794e2 Merge branch 'feat_receipt_upload' 2023-03-19 12:56:08 +01:00
2e33f9b5a8 major: improvements and functions
Features:
- Added page for an overview of establishments with a possibility to
request a membership.
- Added page for admins of an establishment to accept or deny those
candidates. This is currently only usable via URL, the navigation to
this site is not yet implemented in the HTML files.
- Added page to add new establishments by providing a name.

Improvements:
- Better folder structure.
- The establishment-specific overview can now be viewed with another
URL, as well as some other pages.

Bugfixes:
- Seriously I don't know anymore what I fixed and what not. But it works
just better now :)

Future:
- Angular has been added to separate the Flask-Backend with the
frontend. Angular is currently not connected to the backend, but this
will change in the future.
2023-03-19 12:31:10 +01:00
764738b20d major: upload and storage of recipes
PDF-Recipes are uploadable and some basic functions are already
implemented.
Also minor bugfixes.
2022-11-14 21:03:00 +01:00
b997e25a08
Merge pull request #1 from Lunaresk/feature/S2K-11-passwort-recovery-erstellen 2022-11-14 15:47:36 +01:00
c716c7c3ba feat: password recovery
Implemented the "Forgot Password" field with functionalities. If you're
self-hosting, you need to use some variables, which will be explained at
a later point in the readme and on the docker page.
2022-11-13 18:20:12 +01:00
6cf5a91c8c major: refactoring and creating api structure
Moved the token-based functions in the new API folder and started
commenting them. Changed the database_utils function to deal with the
new format commented in api/routes.py.
Minor adjustments in boot.sh function.
2022-08-24 13:30:48 +02:00
22e45857ff minor changes and bugfixes 2022-07-25 16:01:53 +02:00
6d038dc962 bugfix 2022-07-21 17:14:18 +02:00
4459000d4f docker image and changes in models
Made some convenience changes in models and created a Dockerfile for
deployment.
Also changed the configs to be compatible for custom variables in Docker
2022-07-21 11:55:46 +02:00
b978e0da56 refactoring, reorganizing
Changed the folder structure for better maintenance and inserted
Dockerfile for image building.
2022-07-13 09:00:26 +02:00
f26dee5489 minor: insert primitive possibility for invoice
It's ugly. Future updates will make it more beautiful.
2022-03-23 13:45:45 +01:00
050fed33b2 fix: create tables properly 2022-03-21 00:51:43 +01:00
c245e01fcf minor: insert column owner in table establishment 2022-03-20 17:10:58 +01:00
fa798a5da1 several fixes, mainly page design 2022-03-10 12:32:50 +01:00
7f59f20b78 fix: create server-side defaults 2022-02-20 16:49:10 +01:00
b705f82ecb major: refactor, renaming, huge changes
Everything kinda changed. Huge refactoring.
Website now has sidebar instead of top navigation.
Database got huge changes.
More forms!
2022-02-20 02:04:16 +01:00
257 changed files with 21203 additions and 554 deletions

10
.dockerignore Normal file
View File

@ -0,0 +1,10 @@
# Ignore pycaches
logs
__pycache__
Pipfile*
.env*
.flaskenv*
!.env.project
!.env.vault

12
.env.vault Normal file
View File

@ -0,0 +1,12 @@
#################################################################################
# #
# This file uniquely identifies your project in dotenv-vault. #
# You SHOULD commit this file to source control. #
# #
# Generated with 'npx dotenv-vault new' #
# #
# Learn more at https://dotenv.org/env-vault #
# #
#################################################################################
DOTENV_VAULT=vlt_54536d38674329461d10b74ec7e56be43df6021bb5267bedec1f774758371784

View File

@ -4,19 +4,47 @@ on:
tags: tags:
- '*' - '*'
jobs: jobs:
build: release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@master - uses: https://gitea.com/actions/checkout@master
- name: Archive Server - name: Zip Artifacts
uses: thedoctor0/zip-release@master uses: https://github.com/thedoctor0/zip-release@master
with: with:
type: 'zip' type: 'zip'
filename: 'server.zip' filename: 'server.zip'
exclusions: '*.git*' exclusions: '*.git*'
- name: Release Archive - name: Release Archive
uses: ncipollo/release-action@v1 uses: https://gitea.com/actions/gitea-release-action@v1
with: with:
allowUpdates: true server_url: https://gitea.wpgcommunity.net
artifacts: "server.zip" files: 'server.zip'
token: ${{ secrets.GITHUB_TOKEN }} docker:
runs-on: ubuntu-latest
steps:
- uses: https://gitea.com/actions/checkout@master
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ vars.DOCKER_REPO }}/costhive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
config-inline: |
[registry."${{ vars.DOCKER_REPO }}"]
http = true
insecure = true
- name: Login to Gitea Container Registry
uses: docker/login-action@v3
with:
registry: ${{ vars.DOCKER_REPO }}
username: ${{ vars.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASS }}
- name: Build and push
uses: https://github.com/docker/build-push-action@v6
with:
push: true
context: .
tags: ${{ steps.meta.outputs.tags }}

235
.gitignore vendored
View File

@ -1,3 +1,45 @@
# Created by https://www.toptal.com/developers/gitignore/api/flask,python,git,visualstudiocode,angular,venv
# Edit at https://www.toptal.com/developers/gitignore?templates=flask,python,git,visualstudiocode,angular,venv
### Angular ###
## Angular ##
# compiled output
dist/
tmp/
app/**/*.js
app/**/*.js.map
# dependencies
node_modules/
bower_components/
# IDEs and editors
.idea/
# misc
.sass-cache/
connect.lock/
coverage/
libpeerconnection.log/
npm-debug.log
testem.log
typings/
.angular/
# e2e
e2e/*.js
e2e/*.map
# System Files
.DS_Store/
### Flask ###
instance/*
!instance/.gitignore
.webassets-cache
.env
### Flask.Python Stack ###
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/
*.py[cod] *.py[cod]
@ -10,7 +52,6 @@ __pycache__/
.Python .Python
build/ build/
develop-eggs/ develop-eggs/
dist/
downloads/ downloads/
eggs/ eggs/
.eggs/ .eggs/
@ -20,7 +61,6 @@ parts/
sdist/ sdist/
var/ var/
wheels/ wheels/
pip-wheel-metadata/
share/python-wheels/ share/python-wheels/
*.egg-info/ *.egg-info/
.installed.cfg .installed.cfg
@ -50,6 +90,7 @@ coverage.xml
*.py,cover *.py,cover
.hypothesis/ .hypothesis/
.pytest_cache/ .pytest_cache/
cover/
# Translations # Translations
*.mo *.mo
@ -63,7 +104,6 @@ db.sqlite3-journal
# Flask stuff: # Flask stuff:
instance/ instance/
.webassets-cache
# Scrapy stuff: # Scrapy stuff:
.scrapy .scrapy
@ -72,6 +112,7 @@ instance/
docs/_build/ docs/_build/
# PyBuilder # PyBuilder
.pybuilder/
target/ target/
# Jupyter Notebook # Jupyter Notebook
@ -82,7 +123,9 @@ profile_default/
ipython_config.py ipython_config.py
# pyenv # pyenv
.python-version # 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 # pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
@ -91,7 +134,22 @@ ipython_config.py
# install all needed dependencies. # install all needed dependencies.
#Pipfile.lock #Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow # 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__/ __pypackages__/
# Celery stuff # Celery stuff
@ -102,9 +160,7 @@ celerybeat.pid
*.sage.py *.sage.py
# Environments # Environments
.env
.venv .venv
.flaskenv
env/ env/
venv/ venv/
ENV/ ENV/
@ -129,9 +185,174 @@ dmypy.json
# Pyre type checker # Pyre type checker
.pyre/ .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/
### Git ###
# Created by git for backups. To disable backups in Git:
# $ git config --global mergetool.keepBackup false
*.orig
# Created by git when using merge tools for conflicts
*.BACKUP.*
*.BASE.*
*.LOCAL.*
*.REMOTE.*
*_BACKUP_*.txt
*_BASE_*.txt
*_LOCAL_*.txt
*_REMOTE_*.txt
### 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/
### venv ###
# Virtualenv
# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
[Bb]in
[Ii]nclude
[Ll]ib
[Ll]ib64
[Ll]ocal
[Ss]cripts
pyvenv.cfg
pip-selfcheck.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/flask,python,git,visualstudiocode,angular,venv
# Misc # Misc
config.yaml config.yaml
scans.json scans.json
test_*.*
test.* test.*
tests*
*.db *.db
.vscode .vscode
*.tar
*.code-workspace
.env*
.flaskenv*
!.env.project
!.env.vault
*.ps1
*.pdf
*.backup
Dockerfile.*
docker-compose.*
*debug.py

27
Dockerfile Normal file
View File

@ -0,0 +1,27 @@
FROM python@sha256:c66cf219ac0083a9af2ff90e16530f16cd503c59eb7909feb3b8f3524dc1a87e
# python:3.12.2-slim-bullseye (amd64)
RUN useradd costhive
WORKDIR /home/costhive
RUN apt update && apt -y upgrade; \
apt install -y libpq-dev gcc g++ swig make cmake m4; \
rm -rf /var/lib/apt/lists
COPY boot.sh backend/requirements.txt ./
RUN python -m venv venv; \
venv/bin/pip install --upgrade pip; \
venv/bin/pip install wheel gunicorn; \
venv/bin/pip install -r requirements.txt
COPY backend backend
ENV FLASK_APP=run.py
RUN chmod +x boot.sh; \
chown -R costhive:costhive .
USER costhive
EXPOSE 5000
ENTRYPOINT ["./boot.sh"]

View File

@ -1 +1 @@
# WG_Scan2Kasse # CostHive

View File

@ -1,32 +0,0 @@
from flask import Flask
from flask_login import LoginManager
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from yaml import safe_load
from logging import getLogger
from logging.config import fileConfig
from os import makedirs
from os.path import dirname, exists
try:
dir_name = dirname(__file__)
if dir_name:
DIR = dir_name + "/"
else:
DIR = "./"
except NameError:
DIR = "./"
if not exists(DIR + "logs"):
makedirs(DIR + "logs")
fileConfig(DIR + "configs/log.conf")
LOGGER = getLogger("root")
app = Flask(__name__)
app.config.from_file("configs/config.yaml", safe_load)
db = SQLAlchemy(app)
migrate = Migrate(app, db, render_as_batch=True)
login = LoginManager(app)
from app import views, models

View File

@ -1,3 +0,0 @@
SECRET_KEY: "MY_5€cr37_K€Y"
SQLALCHEMY_DATABASE_URI: "dialect+driver://username:password@host:port/database"
SQLALCHEMY_TRACK_MODIFICATIONS: False

View File

@ -1,29 +0,0 @@
from app.models import Brand, Category
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField, SelectMultipleField, DateField, IntegerField, SelectField, FloatField
from wtforms.validators import DataRequired
class LoginForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired()])
remember_me = BooleanField('Remember Me')
submit = SubmitField('Sign In')
class NewItemForm(FlaskForm):
id = IntegerField("Product EAN", validators=[DataRequired()])
name = StringField("Name", validators=[DataRequired()])
description = StringField("Description", validators=[DataRequired()])
date = DateField("Insert Date", validators=[DataRequired()])
price_change = FloatField("Price", validators=[DataRequired()])
amount_change = IntegerField("Amount")
category = SelectMultipleField("Categories", choices=[(c.id, c.name) for c in Category.query.order_by("name").all()], validators=[DataRequired()])
brand = SelectField("Brand", choices=[(b.id, b.name) for b in Brand.query.order_by("name").all()], validators=[DataRequired()])
submit = SubmitField("Submit")
class NewCategoryForm(FlaskForm):
name = StringField("Name", validators=[DataRequired()])
submit = SubmitField("Submit")
class NewBrandForm(FlaskForm):
name = StringField("Name", validators=[DataRequired()])
submit = SubmitField("Submit")

View File

@ -1,133 +0,0 @@
from app import db, login
from flask_login import UserMixin
from sqlalchemy.sql import text
from sqlalchemy_utils.view import create_view
from werkzeug.security import generate_password_hash, check_password_hash
item_category = db.Table("item_category",
db.Column("item", db.ForeignKey("item.id"), primary_key=True),
db.Column("category", db.ForeignKey("category.id"), primary_key=True)
)
class User(UserMixin, db.Model):
id = db.Column(db.String(10), primary_key=True)
name = db.Column(db.String(64), nullable=False)
password_hash = db.Column(db.String(128), nullable=False)
Bought = db.relationship("Bought", backref='User', lazy='dynamic')
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
def __repr__(self) -> str:
return f"<User {self.id} ({self.name})>"
class LoginToken(db.Model):
user = db.Column(db.ForeignKey('user.id'), primary_key=True)
token = db.Column(db.String(32), nullable=False)
class Brand(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(32), nullable=False)
def __repr__(self) -> str:
return f"<Brand {self.id} ({self.name})>"
class Category(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(32), nullable=False)
Item = db.relationship("Item", secondary=item_category, lazy="dynamic", back_populates="Category")
def __repr__(self) -> str:
return f"<Category {self.id} ({self.name})>"
class Item(db.Model):
id = db.Column(db.BigInteger, primary_key=True)
name = db.Column(db.String(64), nullable=False)
brand = db.Column(db.ForeignKey('brand.id'), nullable=False)
description = db.Column(db.Text, nullable=False)
Category = db.relationship("Category", secondary=item_category, lazy="dynamic", back_populates="Item")
Bought = db.relationship("Bought", backref='Item', lazy='dynamic')
PriceChange = db.relationship("PriceChange", backref='Item', lazy='dynamic')
AmountChange = db.relationship("AmountChange", backref='Item', lazy='dynamic')
def __repr__(self) -> str:
return f"<Item {self.id} ({self.name})>"
class Bought(db.Model):
user = db.Column(db.ForeignKey('user.id'), primary_key=True)
item = db.Column(db.ForeignKey('item.id'), primary_key=True)
date = db.Column(db.Date, primary_key=True)
amount = db.Column(db.SmallInteger, nullable=False)
# registered = db.Column(db.Boolean, nullable=False, default=False)
# paid = db.Column(db.SmallInteger, nullable=False, default=0)
def __repr__(self) -> str:
return f"<Bought Object>"
class PriceChange(db.Model):
item = db.Column(db.ForeignKey('item.id'), primary_key=True)
date = db.Column(db.Date, primary_key=True)
price = db.Column(db.SmallInteger, nullable=False)
def __repr__(self) -> str:
return f"<Price_Change {self.item} ({self.date})>"
class AmountChange(db.Model):
item = db.Column(db.ForeignKey('item.id'), primary_key=True)
date = db.Column(db.Date, primary_key=True)
amount = db.Column(db.SmallInteger, nullable=False, default=1)
def __repr__(self) -> str:
return f"<Amount_Change {self.item} ({self.date})>"
class Receipt(db.Model):
id = db.Column(db.Numeric(precision=22, scale=0), primary_key=True)
date = db.Column(db.Date, nullable=False)
registered = db.Column(db.Boolean, nullable=False, default=False)
paid = db.Column(db.SmallInteger, nullable=False, default=0)
def __repr__(self) -> str:
return f"<Receipt {self.id}>"
class ItemReceipt(db.Model):
receipt = db.Column(db.ForeignKey("receipt.id"), primary_key=True)
item = db.Column(db.ForeignKey("item.id"), primary_key=True)
amount = db.Column(db.SmallInteger, nullable=False)
def __repr__(self) -> str:
return f"<ItemReceipt {self.receipt}: {self.item}>"
def query_price_per_amount_view():
p = db.aliased(PriceChange, name="p")
a = db.aliased(AmountChange, name="a")
date = db.func.greatest(p.date, a.date).label("date")
price = (db.func.ceil(p.price.cast(db.Float)/db.func.coalesce(a.amount, 1))/100).label("price")
select = db.select(p.item.label("item"), date, price)
select = select.distinct(p.item, date)
select = select.join(a, p.item==a.item, isouter=True)
select = select.order_by(p.item, db.desc(db.func.greatest(p.date, a.date)))
return select
price_per_amount = create_view("price_per_amount", query_price_per_amount_view(), db.metadata)
def query_bought_with_prices_view():
b = db.aliased(Bought, name="b")
ppa = price_per_amount.alias("ppa")
select = db.select(b.user.label("user"), b.date.label("date"), b.item.label("item"), b.amount.label("amount"), ppa.c.price.label("price"))
select = select.distinct(b.user, b.date, b.item)
select = select.join(ppa, b.item==ppa.c.item)
select = select.where(ppa.c.date<=b.date)
select = select.order_by(db.desc(b.user), db.desc(b.date), db.desc(b.item), db.desc(ppa.c.date))
return select
bought_with_prices = create_view("bought_with_prices", query_bought_with_prices_view(), db.metadata)
@login.user_loader
def load_user(id):
return User.query.get(int(id))

View File

@ -1,43 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link rel="stylesheet" href="style.css">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#">Home</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Overview
</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item" href="#">Monthly</a></li>
<li><a class="dropdown-item" href="#">Yearly</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#">Full Overview</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<div class="container">
<div class="row-md-3">
{% block content %}{% endblock %}
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
</body>
</html>

View File

@ -1,27 +0,0 @@
{% extends "base.html" %}
{% block content %}
{% for user, dates in results.items() %}
<div class="row">
<h3>{{ user }}: {{ dates.sum }} €</h3>
{% for date, items in dates.items() %}
{% if date != "sum" %}
<div class="row">
<div class="col-sm-1"></div>
<div class="col">
<h4>{{ date }}</h4>
{% for item, values in items.items() %}
<div class="row">
<div class="col-sm-1"></div>
<div class="col">
{{ values.0 }}x {{ item }} je {{ values.1 }}
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
{% endfor %}
</div>
{% endfor %}
{% endblock %}

View File

@ -1,39 +0,0 @@
from calendar import month
from app import db, LOGGER
from app.models import Bought, bought_with_prices
from copy import deepcopy
from datetime import date as dtdate, timedelta
from psycopg2 import errors
from sqlalchemy import text
from sqlalchemy.dialects.postgresql import insert
def insert_bought_items(user: str, items: dict, date: str = None):
if not date:
date = dtdate.today()
for item, amount in deepcopy(items).items():
query_insert = insert(Bought).values(user=user, item=int(item), date=date, amount=int(amount))
query_insert = query_insert.on_conflict_do_update("bought_pkey", set_=dict(amount=text(f'bought.amount + {amount}')))
try:
db.session.execute(query_insert)
db.session.commit()
except errors.ForeignKeyViolation as e:
db.session.rollback()
except Exception as e:
db.session.rollback()
LOGGER.exception()
else:
del(items[item])
return {'user':user, 'date': date, 'items': items} if items else {}
def get_report(**kwargs):
query_select = bought_with_prices.select()
if "user" in kwargs:
query_select = query_select.where(bought_with_prices.c.user == kwargs['user'])
match kwargs:
case {"month": month}:
year = kwargs["year"] if "year" in kwargs else dtdate.today().year
query_select = query_select.where(bought_with_prices.c.date.between(dtdate(int(year), int(month), 1), dtdate(int(year), int(month)+1, 1)-timedelta(days=1)))
case {"year": year}:
query_select = query_select.where(bought_with_prices.c.date.between(dtdate(int(year), 1, 1), dtdate(int(year), 12, 31)))
results = db.session.execute(query_select)
return tuple(results)

View File

@ -1,18 +0,0 @@
from app import LOGGER
def group_results(results: tuple) -> dict:
result_dict = {}
LOGGER.debug("Grouping...")
for result in results:
if result[0] not in result_dict:
result_dict[result[0]] = {"sum": 0}
if str(result[1]) not in result_dict[result[0]]:
result_dict[result[0]][str(result[1])] = {}
result_dict[result[0]][str(result[1])][result[2]] = (
result[3], result[4])
price = int(result[3]) * int(float(result[4].split(" ")[0].replace(",", "."))*100)
result_dict[result[0]]["sum"] += price
for key in result_dict.keys():
result_dict[key]["sum"] /= 100
LOGGER.debug("Grouped.")
return result_dict

View File

@ -1,66 +0,0 @@
from app import app, LOGGER
#from app.forms import NewItemForm
from app.models import User
from flask import abort, request, render_template
from flask.json import jsonify
from app.utils import view_utils, database_utils
APPNAME = "scan2kasse"
@app.route('/')
def index():
return "<h1>Hello, World!</h>", 200
@app.route('/test')
def test():
if request.args:
LOGGER.debug(request.args['testing'])
#form = NewItemForm()
#return render_template("test.html", form=form)
@app.route(f'/{APPNAME}/login')
def login():
if not request.json or 'login' not in request.json:
abort(400)
if not User.query.get(request.json['login']):
abort(403)
return jsonify({}), 200
@app.route(f'/{APPNAME}/insert', methods=['POST'])
def insert():
match request.json:
case {'user': user, 'items': items, 'date': date}:
failed = database_utils.insert_bought_items(user, items, date)
case {'user': user, 'items': items}:
failed = database_utils.insert_bought_items(user, items)
case _:
abort(400)
if failed:
return jsonify(failed), 400
return jsonify({'inserted': True}), 201
@app.route(f'/{APPNAME}/overview', methods=['GET'])
def get_report_from_user():
user, month, year = [None]*3
if request.args:
args = request.args
if 'month' in args:
month = int(args['month'])
if 'year' in args:
year = int(args['year'])
if month and (month > 12 or month < 1):
abort(400)
LOGGER.info("Getting results.")
results = database_utils.get_report(user=user, year=year, month=month)
LOGGER.debug(f"Results received: {results}")
if results:
result_dict = view_utils.group_results(results)
else:
result_dict = {}
if request.content_type == "application/json":
return jsonify(result_dict)
else:
return render_template("overview.html", results=result_dict)

31
backend/Pipfile Normal file
View File

@ -0,0 +1,31 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
email-validator = "*"
flask = "*"
flask-bootstrap = "*"
flask-cors = "*"
flask-login = "*"
flask-mail = "*"
flask-marshmallow = "*"
flask-migrate = "*"
flask-sqlalchemy = "*"
flask-wtf = "*"
marshmallow-sqlalchemy = "*"
psycopg2-binary = "*"
pyjwt = "*"
python-dotenv = "*"
pymupdf = "*"
requests = "*"
sqlalchemy-utils = "*"
wtforms-sqlalchemy = "*"
pytest = "*"
[dev-packages]
[requires]
python_version = "3.11"
python_full_version = "3.11.4"

721
backend/Pipfile.lock generated Normal file
View File

@ -0,0 +1,721 @@
{
"_meta": {
"hash": {
"sha256": "6f12189a28bcfca261128bb19798f69649619d796466a01c69fe3a8df6457fbf"
},
"pipfile-spec": 6,
"requires": {
"python_full_version": "3.11.4",
"python_version": "3.11"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"alembic": {
"hashes": [
"sha256:47d52e3dfb03666ed945becb723d6482e52190917fdb47071440cfdba05d92cb",
"sha256:bca5877e9678b454706347bc10b97cb7d67f300320fa5c3a94423e8266e2823f"
],
"markers": "python_version >= '3.7'",
"version": "==1.12.1"
},
"blinker": {
"hashes": [
"sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9",
"sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182"
],
"markers": "python_version >= '3.8'",
"version": "==1.7.0"
},
"certifi": {
"hashes": [
"sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082",
"sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"
],
"markers": "python_version >= '3.6'",
"version": "==2023.7.22"
},
"charset-normalizer": {
"hashes": [
"sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027",
"sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087",
"sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786",
"sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8",
"sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09",
"sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185",
"sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574",
"sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e",
"sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519",
"sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898",
"sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269",
"sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3",
"sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f",
"sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6",
"sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8",
"sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a",
"sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73",
"sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc",
"sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714",
"sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2",
"sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc",
"sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce",
"sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d",
"sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e",
"sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6",
"sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269",
"sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96",
"sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d",
"sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a",
"sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4",
"sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77",
"sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d",
"sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0",
"sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed",
"sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068",
"sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac",
"sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25",
"sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8",
"sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab",
"sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26",
"sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2",
"sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db",
"sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f",
"sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5",
"sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99",
"sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c",
"sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d",
"sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811",
"sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa",
"sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a",
"sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03",
"sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b",
"sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04",
"sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c",
"sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001",
"sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458",
"sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389",
"sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99",
"sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985",
"sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537",
"sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238",
"sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f",
"sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d",
"sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796",
"sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a",
"sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143",
"sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8",
"sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c",
"sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5",
"sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5",
"sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711",
"sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4",
"sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6",
"sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c",
"sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7",
"sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4",
"sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b",
"sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae",
"sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12",
"sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c",
"sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae",
"sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8",
"sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887",
"sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b",
"sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4",
"sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f",
"sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5",
"sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33",
"sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519",
"sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"
],
"markers": "python_full_version >= '3.7.0'",
"version": "==3.3.2"
},
"click": {
"hashes": [
"sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28",
"sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"
],
"markers": "python_version >= '3.7'",
"version": "==8.1.7"
},
"colorama": {
"hashes": [
"sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44",
"sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"
],
"markers": "sys_platform == 'win32'",
"version": "==0.4.6"
},
"dnspython": {
"hashes": [
"sha256:57c6fbaaeaaf39c891292012060beb141791735dbb4004798328fc2c467402d8",
"sha256:8dcfae8c7460a2f84b4072e26f1c9f4101ca20c071649cb7c34e8b6a93d58984"
],
"markers": "python_version >= '3.8' and python_version < '4.0'",
"version": "==2.4.2"
},
"dominate": {
"hashes": [
"sha256:1a916479c45b95fedba0d077b081d77c2d2e0f0f484ac827105087af23661d73",
"sha256:4c90c3befaf88e612b71f4b39af7bcbef8977acfa855cec957225a8fbf504007"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.8.0"
},
"email-validator": {
"hashes": [
"sha256:a4b0bd1cf55f073b924258d19321b1f3aa74b4b5a71a42c305575dba920e1a44",
"sha256:c973053efbeddfef924dc0bd93f6e77a1ea7ee0fce935aea7103c7a3d6d2d637"
],
"index": "pypi",
"version": "==2.1.0.post1"
},
"flask": {
"hashes": [
"sha256:21128f47e4e3b9d597a3e8521a329bf56909b690fcc3fa3e477725aa81367638",
"sha256:cfadcdb638b609361d29ec22360d6070a77d7463dcb3ab08d2c2f2f168845f58"
],
"index": "pypi",
"version": "==3.0.0"
},
"flask-bootstrap": {
"hashes": [
"sha256:cb08ed940183f6343a64e465e83b3a3f13c53e1baabb8d72b5da4545ef123ac8"
],
"index": "pypi",
"version": "==3.3.7.1"
},
"flask-cors": {
"hashes": [
"sha256:bc3492bfd6368d27cfe79c7821df5a8a319e1a6d5eab277a3794be19bdc51783",
"sha256:f268522fcb2f73e2ecdde1ef45e2fd5c71cc48fe03cffb4b441c6d1b40684eb0"
],
"index": "pypi",
"version": "==4.0.0"
},
"flask-login": {
"hashes": [
"sha256:5e23d14a607ef12806c699590b89d0f0e0d67baeec599d75947bf9c147330333",
"sha256:849b25b82a436bf830a054e74214074af59097171562ab10bfa999e6b78aae5d"
],
"index": "pypi",
"version": "==0.6.3"
},
"flask-mail": {
"hashes": [
"sha256:22e5eb9a940bf407bcf30410ecc3708f3c56cc44b29c34e1726fe85006935f41"
],
"index": "pypi",
"version": "==0.9.1"
},
"flask-marshmallow": {
"hashes": [
"sha256:2083ae55bebb5142fff98c6bbd483a2f5dbc531a8bc1be2180ed5f75e7f3fccc",
"sha256:ce08a153f74da6ebfffd8065d1687508b0179df37fff7fc0c8f28ccfb64f1b56"
],
"index": "pypi",
"version": "==0.15.0"
},
"flask-migrate": {
"hashes": [
"sha256:613a2df703998e78716cace68cd83972960834424457f5b67f56e74fff950aef",
"sha256:d3f437a8b5f3849d1bb1b60e1b818efc564c66e3fefe90b62e5db08db295e1b1"
],
"index": "pypi",
"version": "==4.0.5"
},
"flask-sqlalchemy": {
"hashes": [
"sha256:4ba4be7f419dc72f4efd8802d69974803c37259dd42f3913b0dcf75c9447e0a0",
"sha256:e4b68bb881802dda1a7d878b2fc84c06d1ee57fb40b874d3dc97dabfa36b8312"
],
"index": "pypi",
"version": "==3.1.1"
},
"flask-wtf": {
"hashes": [
"sha256:8bb269eb9bb46b87e7c8233d7e7debdf1f8b74bf90cc1789988c29b37a97b695",
"sha256:fa6793f2fb7e812e0fe9743b282118e581fb1b6c45d414b8af05e659bd653287"
],
"index": "pypi",
"version": "==1.2.1"
},
"greenlet": {
"hashes": [
"sha256:0a02d259510b3630f330c86557331a3b0e0c79dac3d166e449a39363beaae174",
"sha256:0b6f9f8ca7093fd4433472fd99b5650f8a26dcd8ba410e14094c1e44cd3ceddd",
"sha256:100f78a29707ca1525ea47388cec8a049405147719f47ebf3895e7509c6446aa",
"sha256:1757936efea16e3f03db20efd0cd50a1c86b06734f9f7338a90c4ba85ec2ad5a",
"sha256:19075157a10055759066854a973b3d1325d964d498a805bb68a1f9af4aaef8ec",
"sha256:19bbdf1cce0346ef7341705d71e2ecf6f41a35c311137f29b8a2dc2341374565",
"sha256:20107edf7c2c3644c67c12205dc60b1bb11d26b2610b276f97d666110d1b511d",
"sha256:22f79120a24aeeae2b4471c711dcf4f8c736a2bb2fabad2a67ac9a55ea72523c",
"sha256:2847e5d7beedb8d614186962c3d774d40d3374d580d2cbdab7f184580a39d234",
"sha256:28e89e232c7593d33cac35425b58950789962011cc274aa43ef8865f2e11f46d",
"sha256:329c5a2e5a0ee942f2992c5e3ff40be03e75f745f48847f118a3cfece7a28546",
"sha256:337322096d92808f76ad26061a8f5fccb22b0809bea39212cd6c406f6a7060d2",
"sha256:3fcc780ae8edbb1d050d920ab44790201f027d59fdbd21362340a85c79066a74",
"sha256:41bdeeb552d814bcd7fb52172b304898a35818107cc8778b5101423c9017b3de",
"sha256:4eddd98afc726f8aee1948858aed9e6feeb1758889dfd869072d4465973f6bfd",
"sha256:52e93b28db27ae7d208748f45d2db8a7b6a380e0d703f099c949d0f0d80b70e9",
"sha256:55d62807f1c5a1682075c62436702aaba941daa316e9161e4b6ccebbbf38bda3",
"sha256:5805e71e5b570d490938d55552f5a9e10f477c19400c38bf1d5190d760691846",
"sha256:599daf06ea59bfedbec564b1692b0166a0045f32b6f0933b0dd4df59a854caf2",
"sha256:60d5772e8195f4e9ebf74046a9121bbb90090f6550f81d8956a05387ba139353",
"sha256:696d8e7d82398e810f2b3622b24e87906763b6ebfd90e361e88eb85b0e554dc8",
"sha256:6e6061bf1e9565c29002e3c601cf68569c450be7fc3f7336671af7ddb4657166",
"sha256:80ac992f25d10aaebe1ee15df45ca0d7571d0f70b645c08ec68733fb7a020206",
"sha256:816bd9488a94cba78d93e1abb58000e8266fa9cc2aa9ccdd6eb0696acb24005b",
"sha256:85d2b77e7c9382f004b41d9c72c85537fac834fb141b0296942d52bf03fe4a3d",
"sha256:87c8ceb0cf8a5a51b8008b643844b7f4a8264a2c13fcbcd8a8316161725383fe",
"sha256:89ee2e967bd7ff85d84a2de09df10e021c9b38c7d91dead95b406ed6350c6997",
"sha256:8bef097455dea90ffe855286926ae02d8faa335ed8e4067326257cb571fc1445",
"sha256:8d11ebbd679e927593978aa44c10fc2092bc454b7d13fdc958d3e9d508aba7d0",
"sha256:91e6c7db42638dc45cf2e13c73be16bf83179f7859b07cfc139518941320be96",
"sha256:97e7ac860d64e2dcba5c5944cfc8fa9ea185cd84061c623536154d5a89237884",
"sha256:990066bff27c4fcf3b69382b86f4c99b3652bab2a7e685d968cd4d0cfc6f67c6",
"sha256:9fbc5b8f3dfe24784cee8ce0be3da2d8a79e46a276593db6868382d9c50d97b1",
"sha256:ac4a39d1abae48184d420aa8e5e63efd1b75c8444dd95daa3e03f6c6310e9619",
"sha256:b2c02d2ad98116e914d4f3155ffc905fd0c025d901ead3f6ed07385e19122c94",
"sha256:b2d3337dcfaa99698aa2377c81c9ca72fcd89c07e7eb62ece3f23a3fe89b2ce4",
"sha256:b489c36d1327868d207002391f662a1d163bdc8daf10ab2e5f6e41b9b96de3b1",
"sha256:b641161c302efbb860ae6b081f406839a8b7d5573f20a455539823802c655f63",
"sha256:b8ba29306c5de7717b5761b9ea74f9c72b9e2b834e24aa984da99cbfc70157fd",
"sha256:b9934adbd0f6e476f0ecff3c94626529f344f57b38c9a541f87098710b18af0a",
"sha256:ce85c43ae54845272f6f9cd8320d034d7a946e9773c693b27d620edec825e376",
"sha256:cf868e08690cb89360eebc73ba4be7fb461cfbc6168dd88e2fbbe6f31812cd57",
"sha256:d2905ce1df400360463c772b55d8e2518d0e488a87cdea13dd2c71dcb2a1fa16",
"sha256:d57e20ba591727da0c230ab2c3f200ac9d6d333860d85348816e1dca4cc4792e",
"sha256:d6a8c9d4f8692917a3dc7eb25a6fb337bff86909febe2f793ec1928cd97bedfc",
"sha256:d923ff276f1c1f9680d32832f8d6c040fe9306cbfb5d161b0911e9634be9ef0a",
"sha256:daa7197b43c707462f06d2c693ffdbb5991cbb8b80b5b984007de431493a319c",
"sha256:dbd4c177afb8a8d9ba348d925b0b67246147af806f0b104af4d24f144d461cd5",
"sha256:dc4d815b794fd8868c4d67602692c21bf5293a75e4b607bb92a11e821e2b859a",
"sha256:e9d21aaa84557d64209af04ff48e0ad5e28c5cca67ce43444e939579d085da72",
"sha256:ea6b8aa9e08eea388c5f7a276fabb1d4b6b9d6e4ceb12cc477c3d352001768a9",
"sha256:eabe7090db68c981fca689299c2d116400b553f4b713266b130cfc9e2aa9c5a9",
"sha256:f2f6d303f3dee132b322a14cd8765287b8f86cdc10d2cb6a6fae234ea488888e",
"sha256:f33f3258aae89da191c6ebaa3bc517c6c4cbc9b9f689e5d8452f7aedbb913fa8",
"sha256:f7bfb769f7efa0eefcd039dd19d843a4fbfbac52f1878b1da2ed5793ec9b1a65",
"sha256:f89e21afe925fcfa655965ca8ea10f24773a1791400989ff32f467badfe4a064",
"sha256:fa24255ae3c0ab67e613556375a4341af04a084bd58764731972bcbc8baeba36"
],
"markers": "platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32')))))",
"version": "==3.0.1"
},
"idna": {
"hashes": [
"sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4",
"sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"
],
"markers": "python_version >= '3.5'",
"version": "==3.4"
},
"iniconfig": {
"hashes": [
"sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3",
"sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"
],
"markers": "python_version >= '3.7'",
"version": "==2.0.0"
},
"itsdangerous": {
"hashes": [
"sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44",
"sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"
],
"markers": "python_version >= '3.7'",
"version": "==2.1.2"
},
"jinja2": {
"hashes": [
"sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852",
"sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"
],
"markers": "python_version >= '3.7'",
"version": "==3.1.2"
},
"mako": {
"hashes": [
"sha256:57d4e997349f1a92035aa25c17ace371a4213f2ca42f99bee9a602500cfd54d9",
"sha256:e3a9d388fd00e87043edbe8792f45880ac0114e9c4adc69f6e9bfb2c55e3b11b"
],
"markers": "python_version >= '3.8'",
"version": "==1.3.0"
},
"markupsafe": {
"hashes": [
"sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e",
"sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e",
"sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431",
"sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686",
"sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c",
"sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559",
"sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc",
"sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb",
"sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939",
"sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c",
"sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0",
"sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4",
"sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9",
"sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575",
"sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba",
"sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d",
"sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd",
"sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3",
"sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00",
"sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155",
"sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac",
"sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52",
"sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f",
"sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8",
"sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b",
"sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007",
"sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24",
"sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea",
"sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198",
"sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0",
"sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee",
"sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be",
"sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2",
"sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1",
"sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707",
"sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6",
"sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c",
"sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58",
"sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823",
"sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779",
"sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636",
"sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c",
"sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad",
"sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee",
"sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc",
"sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2",
"sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48",
"sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7",
"sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e",
"sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b",
"sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa",
"sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5",
"sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e",
"sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb",
"sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9",
"sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57",
"sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc",
"sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc",
"sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2",
"sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"
],
"markers": "python_version >= '3.7'",
"version": "==2.1.3"
},
"marshmallow": {
"hashes": [
"sha256:5d2371bbe42000f2b3fb5eaa065224df7d8f8597bc19a1bbfa5bfe7fba8da889",
"sha256:684939db93e80ad3561392f47be0230743131560a41c5110684c16e21ade0a5c"
],
"markers": "python_version >= '3.8'",
"version": "==3.20.1"
},
"marshmallow-sqlalchemy": {
"hashes": [
"sha256:3523a774390ef0c1c0f7c708a7519809c5396cf608720f14f55c36f74ff5bbec",
"sha256:3cee0bf61ed10687c0a41448e1916649b28222334a02f7b937c39d1c69c18bee"
],
"index": "pypi",
"version": "==0.29.0"
},
"packaging": {
"hashes": [
"sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5",
"sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"
],
"markers": "python_version >= '3.7'",
"version": "==23.2"
},
"pluggy": {
"hashes": [
"sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12",
"sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"
],
"markers": "python_version >= '3.8'",
"version": "==1.3.0"
},
"psycopg2-binary": {
"hashes": [
"sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9",
"sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77",
"sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e",
"sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84",
"sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3",
"sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2",
"sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67",
"sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876",
"sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152",
"sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f",
"sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a",
"sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6",
"sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503",
"sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f",
"sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493",
"sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996",
"sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f",
"sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e",
"sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59",
"sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94",
"sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7",
"sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682",
"sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420",
"sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae",
"sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291",
"sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe",
"sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980",
"sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93",
"sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692",
"sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119",
"sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716",
"sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472",
"sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b",
"sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2",
"sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc",
"sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c",
"sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5",
"sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab",
"sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984",
"sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9",
"sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf",
"sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0",
"sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f",
"sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212",
"sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb",
"sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be",
"sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90",
"sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041",
"sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7",
"sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860",
"sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d",
"sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245",
"sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27",
"sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417",
"sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359",
"sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202",
"sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0",
"sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7",
"sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba",
"sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1",
"sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd",
"sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07",
"sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98",
"sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55",
"sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d",
"sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972",
"sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f",
"sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e",
"sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26",
"sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957",
"sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53",
"sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52"
],
"index": "pypi",
"version": "==2.9.9"
},
"pyjwt": {
"hashes": [
"sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de",
"sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"
],
"index": "pypi",
"version": "==2.8.0"
},
"pymupdf": {
"hashes": [
"sha256:0f852d125defc26716878b1796f4d68870e9065041d00cf46bde317fd8d30e68",
"sha256:1103eea4ab727e32b9cb93347b35f71562033018c333a7f3a17d115e980fea4a",
"sha256:19d1711d5908c4527ad2deef5af2d066649f3f9a12950faf30be5f7251d18abc",
"sha256:1cbcf05c06f314fdf3042ceee674e9a0ac7fae598347d5442e2138c6046d4e82",
"sha256:224c341fe254adda97c8f06a4c5838cdbcf609fa89e70b1fb179752533378f2f",
"sha256:271bdf6059bb8347f9c9c6b721329bd353a933681b1fc62f43241b410e7ab7ae",
"sha256:2885a26220a32fb45ea443443b72194bb7107d6862d8d546b59e4ad0c8a1f2c9",
"sha256:2c141f33e2733e48de8524dfd2de56d889feef0c7773b20a8cd216c03ab24793",
"sha256:2e27857a15c8a810d0b66455b8c8a79013640b6267a9b4ea808a5fe1f47711f2",
"sha256:361cab1be45481bd3dc4e00ec82628ebc189b4f4b6fd9bd78a00cfeed54e0034",
"sha256:3ce2d3678dbf822cff213b1902f2e59756313e543efd516a2b4f15bb0353bd6c",
"sha256:3f0f9b76bc4f039e7587003cbd40684d93a98441549dd033cab38ca07d61988d",
"sha256:4ac9673a6d6ee7e80cb242dacb43f9ca097b502d9c5e44687dbdffc2bce7961a",
"sha256:4d06751d5cd213e96f84f2faaa71a51cf4d641851e07579247ca1190121f173b",
"sha256:526b26a5207e923aab65877ad305644402851823a352cb92d362053426899354",
"sha256:57725e15872f7ab67a9fb3e06e5384d1047b2121e85755c93a6d4266d3ca8983",
"sha256:57e22bea69690450197b34dcde16bd9fe0265ac4425b4033535ccc5c044246fb",
"sha256:5bdf7020b90987412381acc42427dd1b7a03d771ee9ec273de003e570164ec1a",
"sha256:5cd05700c8f18c9dafef63ac2ed3b1099ca06017ca0c32deea13093cea1b8671",
"sha256:618b8e884190ac1cca9df1c637f87669d2d532d421d4ee7e4763c848dc4f3a1e",
"sha256:6e319c1f49476e07b9a12017c2d031687617713f8a46b7adcec03c636ed04607",
"sha256:761501a4965264e81acdd8f2224f993020bf24474e9b34fcdb5805a6826eda1c",
"sha256:8fd9c4ee1dd4744a515b9190d8ba9133348b0d94c362293ed77726aa1c13b0a6",
"sha256:951d280c1daafac2fd6a664b031f7f98b27eb2def55d39c92a19087bd8041c5d",
"sha256:991a37e1cba43775ce094da87cf0bf72172a5532a09644003276bc8bfdfe9f1a",
"sha256:c4eb71b88a22c1008f764b3121b36a9d25340f9920b870508356050a365d9ca1",
"sha256:c8ea81964c1433ea163ad4b53c56053a87a9ef6e1bd7a879d4d368a3988b60d1",
"sha256:e047571d799b30459ad7ee0bc6e68900a7f6b928876f956c976f279808814e72",
"sha256:e2d64799c6d9a3735be9e162a5d11061c0b7fbcb1e5fc7446e0993d0f815a93a",
"sha256:e33f8ec5ba7265fe78b30332840b8f454184addfa79f9c27f160f19789aa5ffd",
"sha256:fd8388e82b6045807d19addf310d8119d32908e89f76cc8bbf8cf1ec36fce947"
],
"index": "pypi",
"version": "==1.23.6"
},
"pymupdfb": {
"hashes": [
"sha256:009e2cff166059e13bf71f93919e688f46b8fc11d122433574cfb0cc9134690e",
"sha256:7132b30e6ad6ff2013344e3a481b2287fe0be3710d80694807dd6e0d8635f085",
"sha256:7bef75988e6979b10ca804cf9487f817aae43b0fff1c6e315b3b9ee0cf1cc32f",
"sha256:9925816cbe3e05e920f9be925e5752c2eef42b793885b62075bb0f6a69178598",
"sha256:9d24ddadc204e895bee5000ddc7507c801643548e59f5a56aad6d32981d17eeb",
"sha256:e5af77580aad3d1103aeec57009d156bfca429cecda14a17c573fcbe97bafb30"
],
"markers": "python_version >= '3.8'",
"version": "==1.23.6"
},
"pytest": {
"hashes": [
"sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac",
"sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"
],
"index": "pypi",
"version": "==7.4.3"
},
"python-dotenv": {
"hashes": [
"sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba",
"sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"
],
"index": "pypi",
"version": "==1.0.0"
},
"requests": {
"hashes": [
"sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f",
"sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"
],
"index": "pypi",
"version": "==2.31.0"
},
"sqlalchemy": {
"hashes": [
"sha256:0666031df46b9badba9bed00092a1ffa3aa063a5e68fa244acd9f08070e936d3",
"sha256:0a8c6aa506893e25a04233bc721c6b6cf844bafd7250535abb56cb6cc1368884",
"sha256:0e680527245895aba86afbd5bef6c316831c02aa988d1aad83c47ffe92655e74",
"sha256:14aebfe28b99f24f8a4c1346c48bc3d63705b1f919a24c27471136d2f219f02d",
"sha256:1e018aba8363adb0599e745af245306cb8c46b9ad0a6fc0a86745b6ff7d940fc",
"sha256:227135ef1e48165f37590b8bfc44ed7ff4c074bf04dc8d6f8e7f1c14a94aa6ca",
"sha256:31952bbc527d633b9479f5f81e8b9dfada00b91d6baba021a869095f1a97006d",
"sha256:3e983fa42164577d073778d06d2cc5d020322425a509a08119bdcee70ad856bf",
"sha256:42d0b0290a8fb0165ea2c2781ae66e95cca6e27a2fbe1016ff8db3112ac1e846",
"sha256:42ede90148b73fe4ab4a089f3126b2cfae8cfefc955c8174d697bb46210c8306",
"sha256:4895a63e2c271ffc7a81ea424b94060f7b3b03b4ea0cd58ab5bb676ed02f4221",
"sha256:4af79c06825e2836de21439cb2a6ce22b2ca129bad74f359bddd173f39582bf5",
"sha256:5f94aeb99f43729960638e7468d4688f6efccb837a858b34574e01143cf11f89",
"sha256:616fe7bcff0a05098f64b4478b78ec2dfa03225c23734d83d6c169eb41a93e55",
"sha256:62d9e964870ea5ade4bc870ac4004c456efe75fb50404c03c5fd61f8bc669a72",
"sha256:638c2c0b6b4661a4fd264f6fb804eccd392745c5887f9317feb64bb7cb03b3ea",
"sha256:63bfc3acc970776036f6d1d0e65faa7473be9f3135d37a463c5eba5efcdb24c8",
"sha256:6463aa765cf02b9247e38b35853923edbf2f6fd1963df88706bc1d02410a5577",
"sha256:64ac935a90bc479fee77f9463f298943b0e60005fe5de2aa654d9cdef46c54df",
"sha256:683ef58ca8eea4747737a1c35c11372ffeb84578d3aab8f3e10b1d13d66f2bc4",
"sha256:75eefe09e98043cff2fb8af9796e20747ae870c903dc61d41b0c2e55128f958d",
"sha256:787af80107fb691934a01889ca8f82a44adedbf5ef3d6ad7d0f0b9ac557e0c34",
"sha256:7c424983ab447dab126c39d3ce3be5bee95700783204a72549c3dceffe0fc8f4",
"sha256:7e0dc9031baa46ad0dd5a269cb7a92a73284d1309228be1d5935dac8fb3cae24",
"sha256:87a3d6b53c39cd173990de2f5f4b83431d534a74f0e2f88bd16eabb5667e65c6",
"sha256:89a01238fcb9a8af118eaad3ffcc5dedaacbd429dc6fdc43fe430d3a941ff965",
"sha256:9585b646ffb048c0250acc7dad92536591ffe35dba624bb8fd9b471e25212a35",
"sha256:964971b52daab357d2c0875825e36584d58f536e920f2968df8d581054eada4b",
"sha256:967c0b71156f793e6662dd839da54f884631755275ed71f1539c95bbada9aaab",
"sha256:9ca922f305d67605668e93991aaf2c12239c78207bca3b891cd51a4515c72e22",
"sha256:a86cb7063e2c9fb8e774f77fbf8475516d270a3e989da55fa05d08089d77f8c4",
"sha256:aeb397de65a0a62f14c257f36a726945a7f7bb60253462e8602d9b97b5cbe204",
"sha256:b41f5d65b54cdf4934ecede2f41b9c60c9f785620416e8e6c48349ab18643855",
"sha256:bd45a5b6c68357578263d74daab6ff9439517f87da63442d244f9f23df56138d",
"sha256:c14eba45983d2f48f7546bb32b47937ee2cafae353646295f0e99f35b14286ab",
"sha256:c1bda93cbbe4aa2aa0aa8655c5aeda505cd219ff3e8da91d1d329e143e4aff69",
"sha256:c4722f3bc3c1c2fcc3702dbe0016ba31148dd6efcd2a2fd33c1b4897c6a19693",
"sha256:c80c38bd2ea35b97cbf7c21aeb129dcbebbf344ee01a7141016ab7b851464f8e",
"sha256:cabafc7837b6cec61c0e1e5c6d14ef250b675fa9c3060ed8a7e38653bd732ff8",
"sha256:cc1d21576f958c42d9aec68eba5c1a7d715e5fc07825a629015fe8e3b0657fb0",
"sha256:d0f7fb0c7527c41fa6fcae2be537ac137f636a41b4c5a4c58914541e2f436b45",
"sha256:d4041ad05b35f1f4da481f6b811b4af2f29e83af253bf37c3c4582b2c68934ab",
"sha256:d5578e6863eeb998980c212a39106ea139bdc0b3f73291b96e27c929c90cd8e1",
"sha256:e3b5036aa326dc2df50cba3c958e29b291a80f604b1afa4c8ce73e78e1c9f01d",
"sha256:e599a51acf3cc4d31d1a0cf248d8f8d863b6386d2b6782c5074427ebb7803bda",
"sha256:f3420d00d2cb42432c1d0e44540ae83185ccbbc67a6054dcc8ab5387add6620b",
"sha256:f48ed89dd11c3c586f45e9eec1e437b355b3b6f6884ea4a4c3111a3358fd0c18",
"sha256:f508ba8f89e0a5ecdfd3761f82dda2a3d7b678a626967608f4273e0dba8f07ac",
"sha256:fd54601ef9cc455a0c61e5245f690c8a3ad67ddb03d3b91c361d076def0b4c60"
],
"markers": "python_version >= '3.7'",
"version": "==2.0.23"
},
"sqlalchemy-utils": {
"hashes": [
"sha256:6c96b0768ea3f15c0dc56b363d386138c562752b84f647fb8d31a2223aaab801",
"sha256:a2181bff01eeb84479e38571d2c0718eb52042f9afd8c194d0d02877e84b7d74"
],
"index": "pypi",
"version": "==0.41.1"
},
"typing-extensions": {
"hashes": [
"sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0",
"sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"
],
"markers": "python_version >= '3.8'",
"version": "==4.8.0"
},
"urllib3": {
"hashes": [
"sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84",
"sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"
],
"markers": "python_version >= '3.7'",
"version": "==2.0.7"
},
"visitor": {
"hashes": [
"sha256:2c737903b2b6864ebc6167eef7cf3b997126f1aa94bdf590f90f1436d23e480a"
],
"version": "==0.1.3"
},
"werkzeug": {
"hashes": [
"sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc",
"sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"
],
"markers": "python_version >= '3.8'",
"version": "==3.0.1"
},
"wtforms": {
"hashes": [
"sha256:5e51df8af9a60f6beead75efa10975e97768825a82146a65c7cbf5b915990620",
"sha256:ae7c54b29806c70f7bce8eb9f24afceb10ca5c32af3d9f04f74d2f66ccc5c7e0"
],
"markers": "python_version >= '3.8'",
"version": "==3.1.1"
},
"wtforms-sqlalchemy": {
"hashes": [
"sha256:7ca42824ad7c453a036f502b42d9c17d4ad8398ff9248a62ed538d1ce0bc41c3",
"sha256:90195d7592bf256d82498c42c79d416832e4a4e6fbca4f1e745a018f66d26c47"
],
"index": "pypi",
"version": "==0.3"
}
},
"develop": {}
}

0
backend/README.md Normal file
View File

23
backend/configs/config.py Normal file
View File

@ -0,0 +1,23 @@
import os
from dotenv import load_dotenv
basedir = os.path.abspath(os.path.dirname(__file__))
load_dotenv(os.path.join(basedir, '.env'))
os.environ["TESSDATA_PREFIX"] = os.path.join(basedir, 'tessdata')
class Config(object):
SECRET_KEY = os.environ.get('SECRET_KEY') or "s0m37h!n6-obfu5c471ng"
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL', '').replace(
'postgres://', 'postgresql://') or \
(f"postgresql://{os.environ.get('DATABASE_USER', 'costhive')}:{os.environ.get('DATABASE_PASS', 'asdf1337')}"
f"@{os.environ.get('DATABASE_HOST', 'localhost')}:{os.environ.get('DATABASE_PORT', '5432')}"
f"/{os.environ.get('DATABASE_DB', '') or os.environ.get('DATABASE_USER', 'costhive')}")
SQLALCHEMY_TRACK_MODIFICATIONS = False
MAIL_SERVER = os.environ.get('MAIL_SERVER')
MAIL_PORT = int(os.environ.get('MAIL_PORT'))
MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS') is not None
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
ADMINS = ['postmaster@wpgcommunity.net']
POSTS_PER_PAGE = 15
RECEIPT_FOLDER = f"{basedir}/../PDFReceipts"

View File

@ -2,20 +2,25 @@
keys=root, main keys=root, main
[handlers] [handlers]
keys=console, file keys=console, file, void
[formatters] [formatters]
keys=stdout keys=stdout
[logger_root] [logger_root]
handlers = console, file handlers = void
level = DEBUG level = CRITICAL
[logger_main] [logger_main]
handlers = console, file handlers = console, file
level = DEBUG level = DEBUG
qualname = main qualname = main
[handler_void]
class = logging.StreamHandler
level = CRITICAL
formatter = stdout
[handler_console] [handler_console]
class = logging.StreamHandler class = logging.StreamHandler
level = DEBUG level = DEBUG
@ -25,7 +30,7 @@ formatter = stdout
class = logging.FileHandler class = logging.FileHandler
level = INFO level = INFO
formatter = stdout formatter = stdout
kwargs = {"filename": "app/logs/infos.log"} kwargs = {"filename": "logs/infos.log"}
[formatter_stdout] [formatter_stdout]
format = %(asctime)s [%(threadName)s] [%(levelname)s]: %(message)s format = %(asctime)s [%(threadName)s] [%(levelname)s]: %(message)s

Binary file not shown.

View File

@ -0,0 +1 @@
tessedit_create_alto 1

View File

@ -0,0 +1,7 @@
tessedit_ambigs_training 1
load_freq_dawg 0
load_punc_dawg 0
load_system_dawg 0
load_number_dawg 0
ambigs_debug_level 3
load_fixed_length_dawgs 0

View File

@ -0,0 +1 @@
tessedit_zero_rejection T

View File

@ -0,0 +1,5 @@
load_bigram_dawg True
tessedit_enable_bigram_correction True
tessedit_bigram_debug 3
save_raw_choices True
save_alt_choices True

View File

@ -0,0 +1,12 @@
disable_character_fragments T
file_type .bl
textord_fast_pitch_test T
tessedit_zero_rejection T
tessedit_minimal_rejection F
tessedit_write_rep_codes F
edges_children_fix F
edges_childarea 0.65
edges_boxarea 0.9
tessedit_resegment_from_boxes T
tessedit_train_from_boxes T
textord_no_rejects T

View File

@ -0,0 +1,13 @@
file_type .bl
#tessedit_use_nn F
textord_fast_pitch_test T
tessedit_zero_rejection T
tessedit_minimal_rejection F
tessedit_write_rep_codes F
edges_children_fix F
edges_childarea 0.65
edges_boxarea 0.9
tessedit_resegment_from_boxes T
tessedit_train_from_boxes T
#textord_repeat_extraction F
textord_no_rejects T

View File

@ -0,0 +1 @@
tessedit_char_whitelist 0123456789-.

View File

@ -0,0 +1 @@
tessedit_write_images T

View File

@ -0,0 +1,2 @@
tessedit_create_hocr 1
hocr_font_info 0

View File

@ -0,0 +1,2 @@
interactive_display_mode T
tessedit_display_outwords T

View File

@ -0,0 +1,4 @@
textord_skewsmooth_offset 8
textord_skewsmooth_offset2 8
textord_merge_desc 0.5
textord_no_rejects 1

View File

@ -0,0 +1,2 @@
tessedit_resegment_from_line_boxes 1
tessedit_make_boxes_from_boxes 1

View File

@ -0,0 +1 @@
debug_file tesseract.log

View File

@ -0,0 +1,11 @@
file_type .bl
textord_fast_pitch_test T
tessedit_zero_rejection T
tessedit_minimal_rejection F
tessedit_write_rep_codes F
edges_children_fix F
edges_childarea 0.65
edges_boxarea 0.9
tessedit_train_line_recognizer T
textord_no_rejects T
tessedit_init_config_only T

View File

@ -0,0 +1 @@
tessedit_create_lstmbox 1

View File

@ -0,0 +1,4 @@
stopper_debug_level 1
classify_debug_level 1
segsearch_debug_level 1
language_model_debug_level 3

View File

@ -0,0 +1 @@
tessedit_create_boxfile 1

View File

@ -0,0 +1 @@
tessedit_create_pdf 1

View File

@ -0,0 +1 @@
debug_file /dev/null

View File

@ -0,0 +1,2 @@
tessedit_resegment_from_boxes 1
tessedit_make_boxes_from_boxes 1

View File

@ -0,0 +1,12 @@
textord_show_blobs 0
textord_debug_tabfind 3
textord_tabfind_show_partitions 1
textord_tabfind_show_initial_partitions 1
textord_tabfind_show_columns 1
textord_tabfind_show_blocks 1
textord_tabfind_show_initialtabs 1
textord_tabfind_show_finaltabs 1
textord_tabfind_show_strokewidths 1
textord_tabfind_show_vlines 0
textord_tabfind_show_images 1
tessedit_dump_pageseg_images 0

View File

@ -0,0 +1 @@
tessedit_create_tsv 1

View File

@ -0,0 +1,3 @@
# This config file should be used with other config files which create renderers.
# usage example: tesseract eurotext.tif eurotext txt hocr pdf
tessedit_create_txt 1

View File

@ -0,0 +1,2 @@
tessedit_write_unlv 1
unlv_tilde_crunching T

View File

@ -0,0 +1 @@
tessedit_create_wordstrbox 1

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,2 @@
1-\d\d\d-GOOG-411
www.\n\\\*.com

View File

@ -0,0 +1,5 @@
the
quick
brown
fox
jumped

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
# No content needed as all defaults are correct.

View File

@ -0,0 +1,2 @@
chop_enable 0
wordrec_enable_assoc 0

View File

@ -0,0 +1,7 @@
#################################################
# Adaptive Matcher Using PreAdapted Templates
#################################################
classify_enable_adaptive_debugger 1
matcher_debug_flags 6
matcher_debug_level 1

View File

@ -0,0 +1,12 @@
#################################################
# Adaptive Matcher Using PreAdapted Templates
#################################################
classify_enable_adaptive_debugger 1
matcher_debug_flags 6
matcher_debug_level 1
wordrec_display_splits 0
wordrec_display_all_blobs 1
wordrec_display_segmentations 2
classify_debug_level 1

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,9 @@
#################################################
# Adaptive Matcher Using PreAdapted Templates
#################################################
wordrec_display_splits 0
wordrec_display_all_blobs 1
wordrec_display_segmentations 2
classify_debug_level 1
stopper_debug_level 1

View File

@ -0,0 +1,34 @@
"""Add table for logintoken de-/activation dates
Revision ID: 015f4256bb4c
Revises: 9a8c73f0ab11
Create Date: 2023-11-19 20:52:03.377745
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '015f4256bb4c'
down_revision = '9a8c73f0ab11'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('login_token_dates',
sa.Column('token', sa.String(length=15), nullable=False),
sa.Column('activation_date', sa.Date(), server_default=sa.text('now()'), nullable=False),
sa.Column('deactivation_date', sa.Date(), nullable=True),
sa.ForeignKeyConstraint(['token'], ['login_token.token'], ),
sa.PrimaryKeyConstraint('token', 'activation_date')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('login_token_dates')
# ### end Alembic commands ###

View File

@ -0,0 +1,128 @@
"""full structure
Revision ID: 05fce74b56cb
Revises:
Create Date: 2022-02-20 16:31:34.589805
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '05fce74b56cb'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('brand',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=32), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_table('category',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=32), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
op.create_table('user',
sa.Column('id', sa.BigInteger(), nullable=False),
sa.Column('email', sa.String(length=255), nullable=False),
sa.Column('password_hash', sa.String(length=128), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('email')
)
op.create_table('establishment',
sa.Column('id', sa.BigInteger(), nullable=False),
sa.Column('name', sa.String(length=64), nullable=False),
sa.Column('owner', sa.BigInteger(), nullable=False),
sa.ForeignKeyConstraint(['owner'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('login_token',
sa.Column('user', sa.BigInteger(), nullable=False),
sa.Column('establishment', sa.BigInteger(), nullable=False),
sa.Column('token', sa.String(length=15), nullable=True),
sa.Column('paid', sa.BigInteger(), server_default='0', nullable=False),
sa.ForeignKeyConstraint(['establishment'], ['establishment.id'], ),
sa.ForeignKeyConstraint(['user'], ['user.id'], ),
sa.PrimaryKeyConstraint('user', 'establishment'),
sa.UniqueConstraint('token')
)
op.create_table('receipt',
sa.Column('id', sa.Numeric(precision=24, scale=0), autoincrement=False, nullable=False),
sa.Column('date', sa.Date(), nullable=False),
sa.Column('from_user', sa.String(length=15), nullable=True),
sa.Column('registered', sa.Boolean(), server_default='False', nullable=False),
sa.ForeignKeyConstraint(['from_user'], ['login_token.token'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('item',
sa.Column('id', sa.BigInteger(), autoincrement=False, nullable=False),
sa.Column('name', sa.String(length=64), nullable=False),
sa.Column('brand', sa.Integer(), nullable=False),
sa.Column('description', sa.Text(), nullable=False),
sa.ForeignKeyConstraint(['brand'], ['brand.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('amount_change',
sa.Column('item', sa.BigInteger(), nullable=False),
sa.Column('date', sa.Date(), server_default='2021-12-01', nullable=False),
sa.Column('amount', sa.SmallInteger(), server_default='1', nullable=False),
sa.ForeignKeyConstraint(['item'], ['item.id'], ),
sa.PrimaryKeyConstraint('item', 'date')
)
op.create_table('bought',
sa.Column('token', sa.String(length=15), nullable=False),
sa.Column('item', sa.BigInteger(), nullable=False),
sa.Column('date', sa.Date(), nullable=False),
sa.Column('amount', sa.SmallInteger(), nullable=False),
sa.Column('registered', sa.Boolean(), server_default='False', nullable=False),
sa.ForeignKeyConstraint(['item'], ['item.id'], ),
sa.ForeignKeyConstraint(['token'], ['login_token.token'], ),
sa.PrimaryKeyConstraint('token', 'item', 'date')
)
op.create_table('item_category',
sa.Column('item', sa.BigInteger(), nullable=False),
sa.Column('category', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['category'], ['category.id'], ),
sa.ForeignKeyConstraint(['item'], ['item.id'], ),
sa.PrimaryKeyConstraint('item', 'category')
)
op.create_table('item_receipt',
sa.Column('receipt', sa.Numeric(precision=24, scale=0), nullable=False),
sa.Column('item', sa.BigInteger(), nullable=False),
sa.Column('amount', sa.SmallInteger(), nullable=False),
sa.ForeignKeyConstraint(['item'], ['item.id'], ),
sa.ForeignKeyConstraint(['receipt'], ['receipt.id'], ),
sa.PrimaryKeyConstraint('receipt', 'item')
)
op.create_table('price_change',
sa.Column('item', sa.BigInteger(), nullable=False),
sa.Column('date', sa.Date(), server_default='2021-12-01', nullable=False),
sa.Column('price', sa.SmallInteger(), nullable=False),
sa.ForeignKeyConstraint(['item'], ['item.id'], ),
sa.PrimaryKeyConstraint('item', 'date')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('price_change')
op.drop_table('item_receipt')
op.drop_table('item_category')
op.drop_table('bought')
op.drop_table('amount_change')
op.drop_table('item')
op.drop_table('receipt')
op.drop_table('login_token')
op.drop_table('establishment')
op.drop_table('user')
op.drop_table('category')
op.drop_table('brand')
# ### end Alembic commands ###

View File

@ -0,0 +1,54 @@
"""receipt id serial
Revision ID: 0fa2ef37e440
Revises: f6f97ed9c053
Create Date: 2023-07-25 21:26:25.353435
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '0fa2ef37e440'
down_revision = 'f6f97ed9c053'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.execute("CREATE SEQUENCE receipt_id_seq;")
with op.batch_alter_table('receipt', schema=None) as batch_op:
batch_op.alter_column('id',
existing_type=sa.BIGINT(),
type_=sa.Integer(),
existing_nullable=False,
server_default=sa.sql.func.next_value(sa.Sequence('receipt_id_seq')))
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
batch_op.alter_column('receipt',
existing_type=sa.BIGINT(),
type_=sa.Integer(),
existing_nullable=False)
op.execute("ALTER SEQUENCE receipt_id_seq OWNED BY receipt.id;")
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
batch_op.alter_column('receipt',
existing_type=sa.Integer(),
type_=sa.BIGINT(),
existing_nullable=False)
with op.batch_alter_table('receipt', schema=None) as batch_op:
batch_op.alter_column('id',
existing_type=sa.Integer(),
type_=sa.BIGINT(),
existing_nullable=False)
# ### end Alembic commands ###

View File

@ -0,0 +1,35 @@
"""new table for bought entries with unknown items 2
Revision ID: 2a64d3b9235a
Revises: 015f4256bb4c
Create Date: 2024-06-02 13:19:59.901053
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '2a64d3b9235a'
down_revision = '015f4256bb4c'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('bought_with_unknown_item',
sa.Column('token', sa.String(length=15), nullable=False),
sa.Column('item', sa.BigInteger(), nullable=False),
sa.Column('date', sa.Date(), nullable=False),
sa.Column('amount', sa.SmallInteger(), nullable=False),
sa.ForeignKeyConstraint(['token'], ['login_token.token'], ),
sa.PrimaryKeyConstraint('token', 'item', 'date')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('bought_with_unknown_item')
# ### end Alembic commands ###

View File

@ -0,0 +1,40 @@
"""create views
Revision ID: 2be4d1ae5493
Revises: 05fce74b56cb
Create Date: 2022-02-20 16:33:01.900378
"""
from alembic import op
import sqlalchemy as sa
from src import db
from src.utils.view_utils import selectable_price_per_amount_view, selectable_bought_with_prices_view
from sqlalchemy_utils import create_view
# revision identifiers, used by Alembic.
revision = '2be4d1ae5493'
down_revision = '05fce74b56cb'
branch_labels = None
depends_on = None
def upgrade():
op.rename_table("item_receipt", "receipt_item")
metadata = sa.MetaData()
create_view('price_per_amount',
selectable_price_per_amount_view(),
metadata
)
create_view('bought_with_prices',
selectable_bought_with_prices_view(),
metadata
)
metadata.create_all(db.engine)
def downgrade():
with db.engine.connect() as con:
con.execute("DROP VIEW bought_with_prices;")
con.execute("DROP VIEW price_per_amount;")
op.rename_table("receipt_item", "item_receipt")

View File

@ -0,0 +1,32 @@
"""Remove accepted from receiptitem
Revision ID: 36f532800705
Revises: cdbe3f5e3b30
Create Date: 2023-11-11 10:45:44.652161
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '36f532800705'
down_revision = 'cdbe3f5e3b30'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
batch_op.drop_column('accepted')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
batch_op.add_column(sa.Column('accepted', sa.BOOLEAN(), autoincrement=False, nullable=False))
# ### end Alembic commands ###

View File

@ -0,0 +1,38 @@
"""Delete unnecessary columns
Revision ID: 4fec22c35530
Revises: 610854a6e2a0
Create Date: 2023-05-28 08:10:27.689070
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '4fec22c35530'
down_revision = '610854a6e2a0'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('bought', schema=None) as batch_op:
batch_op.drop_column('registered')
with op.batch_alter_table('login_token', schema=None) as batch_op:
batch_op.drop_column('paid')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('login_token', schema=None) as batch_op:
batch_op.add_column(sa.Column('paid', sa.BIGINT(), server_default=sa.text("'0'::bigint"), autoincrement=False, nullable=False))
with op.batch_alter_table('bought', schema=None) as batch_op:
batch_op.add_column(sa.Column('registered', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=False))
# ### end Alembic commands ###

View File

@ -0,0 +1,53 @@
"""establishment candidate table
Revision ID: 610854a6e2a0
Revises: 2be4d1ae5493
Create Date: 2023-03-18 13:36:12.953900
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '610854a6e2a0'
down_revision = '2be4d1ae5493'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('establishment_candidate',
sa.Column('user', sa.BigInteger(), nullable=False),
sa.Column('establishment', sa.BigInteger(), nullable=False),
sa.ForeignKeyConstraint(['establishment'], ['establishment.id'], ),
sa.ForeignKeyConstraint(['user'], ['user.id'], ),
sa.PrimaryKeyConstraint('user', 'establishment')
)
op.create_table('payment',
sa.Column('id', sa.BigInteger(), nullable=False, autoincrement=True),
sa.Column('token', sa.String(length=15), nullable=False),
sa.Column('date', sa.Date(), server_default=sa.text('now()'), nullable=False),
sa.Column('amount', sa.BigInteger(), server_default='0', nullable=False),
sa.ForeignKeyConstraint(['token'], ['login_token.token'], ),
sa.PrimaryKeyConstraint('id', 'token')
)
with op.batch_alter_table('login_token', schema=None) as batch_op:
batch_op.alter_column('token',
existing_type=sa.VARCHAR(length=15),
nullable=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('login_token', schema=None) as batch_op:
batch_op.alter_column('token',
existing_type=sa.VARCHAR(length=15),
nullable=True)
op.drop_table('payment')
op.drop_table('establishment_candidate')
# ### end Alembic commands ###

View File

@ -0,0 +1,38 @@
"""raise password char length
Revision ID: 782a2409df41
Revises: 926395732c3e
Create Date: 2025-06-03 21:01:23.169897
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '782a2409df41'
down_revision = '926395732c3e'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('user', schema=None) as batch_op:
batch_op.alter_column('password_hash',
existing_type=sa.VARCHAR(length=128),
type_=sa.String(length=255),
existing_nullable=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('user', schema=None) as batch_op:
batch_op.alter_column('password_hash',
existing_type=sa.String(length=255),
type_=sa.VARCHAR(length=128),
existing_nullable=False)
# ### end Alembic commands ###

View File

@ -0,0 +1,38 @@
"""raise bonid digits
Revision ID: 926395732c3e
Revises: 2a64d3b9235a
Create Date: 2024-08-24 10:33:39.109944
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '926395732c3e'
down_revision = '2a64d3b9235a'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('receipt', schema=None) as batch_op:
batch_op.alter_column('bonid',
existing_type=sa.NUMERIC(precision=24, scale=0),
type_=sa.Numeric(precision=28, scale=0),
existing_nullable=True)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('receipt', schema=None) as batch_op:
batch_op.alter_column('bonid',
existing_type=sa.Numeric(precision=28, scale=0),
type_=sa.NUMERIC(precision=24, scale=0),
existing_nullable=True)
# ### end Alembic commands ###

View File

@ -0,0 +1,35 @@
"""add receiptitem names
Revision ID: 9a8c73f0ab11
Revises: 36f532800705
Create Date: 2023-11-18 23:38:56.865780
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '9a8c73f0ab11'
down_revision = '36f532800705'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
batch_op.add_column(sa.Column('name', sa.String(), nullable=True))
op.execute("UPDATE receipt_item SET name='Unknown' WHERE name IS NULL;")
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
batch_op.alter_column('name', nullable=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
batch_op.drop_column('name')
# ### end Alembic commands ###

View File

@ -0,0 +1,64 @@
"""Remove item.receiptitem relationship
Revision ID: cdbe3f5e3b30
Revises: 0fa2ef37e440
Create Date: 2023-09-19 19:02:23.003034
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'cdbe3f5e3b30'
down_revision = '0fa2ef37e440'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('receipt', schema=None) as batch_op:
batch_op.alter_column('id',
existing_type=sa.INTEGER(),
type_=sa.BigInteger(),
existing_nullable=False,
autoincrement=True)
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
batch_op.add_column(sa.Column('price', sa.SmallInteger(), nullable=False))
batch_op.alter_column('receipt',
existing_type=sa.INTEGER(),
type_=sa.BigInteger(),
existing_nullable=False)
batch_op.alter_column('item',
existing_type=sa.BIGINT(),
type_=sa.SmallInteger(),
existing_nullable=False)
batch_op.drop_constraint('item_receipt_item_fkey', type_='foreignkey')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
batch_op.create_foreign_key('item_receipt_item_fkey', 'item', ['item'], ['id'])
batch_op.alter_column('item',
existing_type=sa.SmallInteger(),
type_=sa.BIGINT(),
existing_nullable=False)
batch_op.alter_column('receipt',
existing_type=sa.BigInteger(),
type_=sa.INTEGER(),
existing_nullable=False)
batch_op.drop_column('price')
with op.batch_alter_table('receipt', schema=None) as batch_op:
batch_op.alter_column('id',
existing_type=sa.BigInteger(),
type_=sa.INTEGER(),
existing_nullable=False,
autoincrement=True)
# ### end Alembic commands ###

View File

@ -0,0 +1,64 @@
"""receipt-id reorganized
Revision ID: f6f97ed9c053
Revises: 4fec22c35530
Create Date: 2023-07-02 14:16:06.260479
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'f6f97ed9c053'
down_revision = '4fec22c35530'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
batch_op.add_column(sa.Column('accepted', sa.Boolean(), nullable=False))
batch_op.drop_constraint("item_receipt_receipt_fkey")
batch_op.alter_column('receipt',
existing_type=sa.NUMERIC(precision=24, scale=0),
type_=sa.BigInteger(),
existing_nullable=False)
with op.batch_alter_table('receipt', schema=None) as batch_op:
batch_op.add_column(sa.Column('bonid', sa.Numeric(precision=24, scale=0), nullable=True))
with op.batch_alter_table('receipt', schema=None) as batch_op:
batch_op.execute("UPDATE receipt SET bonid=id;")
batch_op.alter_column('id',
existing_type=sa.NUMERIC(precision=24, scale=0),
type_=sa.BigInteger(),
existing_nullable=False,
autoincrement=True)
batch_op.create_unique_constraint(None, ['bonid'])
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
batch_op.create_foreign_key(None, 'receipt', ['receipt'], ['id'])
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
batch_op.alter_column('receipt',
existing_type=sa.BigInteger(),
type_=sa.NUMERIC(precision=24, scale=0),
existing_nullable=False)
batch_op.drop_column('accepted')
with op.batch_alter_table('receipt', schema=None) as batch_op:
batch_op.drop_constraint(None, type_='unique')
batch_op.alter_column('id',
existing_type=sa.BigInteger(),
type_=sa.NUMERIC(precision=24, scale=0),
existing_nullable=False,
autoincrement=True)
batch_op.drop_column('bonid')
# ### end Alembic commands ###

View File

@ -0,0 +1,18 @@
from .payment import Payment
from .receipt_item import ReceiptItem
from .receipt import Receipt
from .login_token import LoginToken
from .bought import Bought
from .bought_with_unknown_item import BoughtWithUnknownItem
from .establishment import Establishment
from .establishment_candidate import EstablishmentCandidate
from .user import User
from .amount_change import AmountChange
from .price_change import PriceChange
from .category import Category
from .item import Item
from .brand import Brand
from .item_category import item_category
from .login_token_dates import LoginTokenDates
from .schemas import *

View File

@ -0,0 +1,13 @@
from datetime import date
from src import db
class AmountChange(db.Model):
item = db.Column(db.ForeignKey('item.id'), primary_key=True,
server_onupdate=db.FetchedValue())
date = db.Column(db.Date, primary_key=True,
server_default=str(date(2021, 12, 1)))
amount = db.Column(db.SmallInteger, nullable=False, server_default=str(1))
def __repr__(self) -> str:
return f"<Amount_Change {self.item} ({self.date})>"

13
backend/models/bought.py Normal file
View File

@ -0,0 +1,13 @@
from src import db
class Bought(db.Model):
token = db.Column(db.ForeignKey('login_token.token'),
primary_key=True, server_onupdate=db.FetchedValue())
item = db.Column(db.ForeignKey('item.id'), primary_key=True,
server_onupdate=db.FetchedValue())
date = db.Column(db.Date, primary_key=True)
amount = db.Column(db.SmallInteger, nullable=False)
def __repr__(self) -> str:
return f"<Bought Object>"

View File

@ -0,0 +1,14 @@
from src import db
from .bought import Bought
class BoughtWithUnknownItem(db.Model):
token = db.Column(db.ForeignKey('login_token.token'),
primary_key=True, server_onupdate=db.FetchedValue())
item = db.Column(db.BigInteger, primary_key=True,
server_onupdate=db.FetchedValue())
date = db.Column(db.Date, primary_key=True)
amount = db.Column(db.SmallInteger, nullable=False)
def __repr__(self) -> str:
return f"<Bought Object>"

14
backend/models/brand.py Normal file
View File

@ -0,0 +1,14 @@
from src import db
class Brand(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(32), nullable=False)
Item = db.relationship("Item", backref='Brand', lazy='dynamic')
def __repr__(self) -> str:
return f"<Brand {self.id} ({self.name})>"
def __str__(self) -> str:
return f"{self.name}"

View File

@ -0,0 +1,13 @@
from src import db
from .item_category import item_category
class Category(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(32), nullable=False, unique=True)
Item = db.relationship("Item", secondary=item_category,
lazy="dynamic", back_populates="Category")
def __repr__(self) -> str:
return f"<Category {self.id} ({self.name})>"

View File

@ -0,0 +1,17 @@
from src import db
class Establishment(db.Model):
id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
name = db.Column(db.String(64), nullable=False)
owner = db.Column(db.ForeignKey('user.id'), nullable=False)
LoginToken = db.relationship(
"LoginToken", backref='Establishment', lazy='dynamic')
Bought = db.relationship("Bought", secondary="login_token",
lazy='dynamic', overlaps="Establishment,LoginToken,Bought")
EstablishmentCandidate = db.relationship(
"EstablishmentCandidate", backref='Establishment', lazy='dynamic')
def __repr__(self) -> str:
return f"<Establishment {self.id} ({self.name})>"

View File

@ -0,0 +1,10 @@
from src import db
class EstablishmentCandidate(db.Model):
user = db.Column(db.ForeignKey('user.id'), primary_key=True)
establishment = db.Column(db.ForeignKey(
'establishment.id'), primary_key=True)
def __repr__(self) -> str:
return f"<EstablishmentCandidate {self.user} ({self.establishment})>"

24
backend/models/item.py Normal file
View File

@ -0,0 +1,24 @@
from src import db, ma
from .item_category import item_category
class Item(db.Model):
id = db.Column(db.BigInteger, primary_key=True, autoincrement=False)
name = db.Column(db.String(64), nullable=False)
brand = db.Column(db.ForeignKey('brand.id'), nullable=False,
server_onupdate=db.FetchedValue())
description = db.Column(db.Text, nullable=False)
AmountChange = db.relationship(
"AmountChange", backref='Item', lazy='dynamic')
Bought = db.relationship("Bought", backref='Item', lazy='dynamic')
Category = db.relationship(
"Category", secondary=item_category, lazy="dynamic", back_populates="Item")
PriceChange = db.relationship(
"PriceChange", backref='Item', lazy='dynamic')
def __repr__(self) -> str:
return f"<Item {self.id} ({self.name})>"
def __str__(self) -> str:
return f"({self.id}) {self.description}"

View File

@ -0,0 +1,6 @@
from src import db
item_category = db.Table("item_category",
db.Column("item", db.ForeignKey("item.id"), primary_key=True, server_onupdate=db.FetchedValue()),
db.Column("category", db.ForeignKey("category.id"), primary_key=True, server_onupdate=db.FetchedValue())
)

View File

@ -0,0 +1,16 @@
from src import db
class LoginToken(db.Model):
user = db.Column(db.ForeignKey('user.id'), primary_key=True,
server_onupdate=db.FetchedValue())
establishment = db.Column(db.ForeignKey(
'establishment.id'), primary_key=True, server_onupdate=db.FetchedValue())
token = db.Column(db.String(15), nullable=False, unique=True)
LoginTokenDates = db.relationship("LoginTokenDates", backref='LoginToken', lazy='dynamic')
Payment = db.relationship("Payment", backref='LoginToken', lazy='dynamic')
Receipt = db.relationship("Receipt", backref='LoginToken', lazy='dynamic')
def __repr__(self) -> str:
return f"<LoginToken {self.token}>"

View File

@ -0,0 +1,13 @@
from src import db
class LoginTokenDates(db.Model):
token = db.Column(db.ForeignKey('login_token.token'),
nullable = False, primary_key=True,
server_onupdate=db.FetchedValue())
activation_date = db.Column(db.Date,
nullable=False, primary_key=True,
server_default=db.func.now())
deactivation_date = db.Column(db.Date, nullable=True)
def __repr__(self) -> str:
return f"<LoginTokenDates {self.token, self.activation_date, self.deactivation_date}>"

12
backend/models/payment.py Normal file
View File

@ -0,0 +1,12 @@
from src import db
class Payment(db.Model):
id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
token = db.Column(db.ForeignKey('login_token.token'),
server_onupdate=db.FetchedValue(), nullable=False)
date = db.Column(db.Date, nullable=False, server_default=db.func.now())
amount = db.Column(db.BigInteger, nullable=False, server_default=str(0))
def __repr__(self) -> str:
return f"<Payment {self.id}>"

View File

@ -0,0 +1,13 @@
from datetime import date
from src import db
class PriceChange(db.Model):
item = db.Column(db.ForeignKey('item.id'), primary_key=True,
server_onupdate=db.FetchedValue())
date = db.Column(db.Date, primary_key=True,
server_default=str(date(2021, 12, 1)))
price = db.Column(db.SmallInteger, nullable=False)
def __repr__(self) -> str:
return f"<Price_Change {self.item} ({self.date})>"

17
backend/models/receipt.py Normal file
View File

@ -0,0 +1,17 @@
from src import db
class Receipt(db.Model):
id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
date = db.Column(db.Date, nullable=False)
bonid = db.Column(db.Numeric(precision=28, scale=0), unique=True)
from_user = db.Column(db.ForeignKey("login_token.token"),
server_onupdate=db.FetchedValue())
registered = db.Column(db.Boolean, nullable=False,
server_default=str(False))
ReceiptItem = db.relationship(
"ReceiptItem", backref='Receipt', lazy='dynamic')
def __repr__(self) -> str:
return f"<Receipt {self.id}>"

View File

@ -0,0 +1,13 @@
from src import db
class ReceiptItem(db.Model):
receipt = db.Column(db.ForeignKey("receipt.id"),
primary_key=True, server_onupdate=db.FetchedValue())
item = db.Column(db.SmallInteger, primary_key=True, nullable=False)
name = db.Column(db.String, nullable=False)
amount = db.Column(db.SmallInteger, nullable=False, default=str(1))
price = db.Column(db.SmallInteger, nullable=False)
def __repr__(self) -> str:
return f"<ReceiptItem {self.receipt}: {self.item}>"

View File

@ -0,0 +1,13 @@
from .brand import BrandSchema
from .item import ItemSchema
from .category import CategorySchema
from .price_change import PriceChangeSchema
from .amount_change import AmountChangeSchema
from .user import UserSchema
from .establishment import EstablishmentSchema
from .establishment_candidate import EstablishmentCandidateSchema
from .login_token import LoginTokenSchema
from .bought import BoughtSchema
from .receipt import ReceiptSchema
from .receipt_item import ReceiptItemSchema
from .payment import PaymentSchema

View File

@ -0,0 +1,13 @@
from src import ma
from ..amount_change import AmountChange
from .item import ItemSchema
class AmountChangeSchema(ma.SQLAlchemySchema):
class Meta:
model = AmountChange
include_fk = True
Item = ma.Nested(ItemSchema)
date = ma.auto_field()
amount = ma.auto_field()

View File

@ -0,0 +1,15 @@
from src import ma
from ..bought import Bought
from .login_token import LoginTokenSchema
from .item import ItemSchema
class BoughtSchema(ma.SQLAlchemySchema):
class Meta:
model = Bought
include_fk = True
LoginToken = ma.Nested(LoginTokenSchema)
Item = ma.Nested(ItemSchema)
date = ma.auto_field()
amount = ma.auto_field()

View File

@ -0,0 +1,10 @@
from src import ma
from ..brand import Brand
class BrandSchema(ma.SQLAlchemySchema):
class Meta:
model = Brand
id = ma.auto_field()
name = ma.auto_field()

View File

@ -0,0 +1,11 @@
# from marshmallow import Schema, fields
from src import ma
from ..category import Category
class CategorySchema(ma.SQLAlchemySchema):
class Meta:
model = Category
id = ma.auto_field()
name = ma.auto_field()

Some files were not shown because too many files have changed in this diff Show More