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
This commit is contained in:
Lunaresk 2022-07-21 11:55:46 +02:00
parent b978e0da56
commit 4459000d4f
15 changed files with 57 additions and 37 deletions

View File

@ -1,15 +1,20 @@
FROM python:3.10-slim FROM python@sha256:38000b248a186dcae150fe2f64d23bd44a0730347d1e5e4d1faedd449a9a4913
RUN useradd scan2kasse RUN useradd scan2kasse
WORKDIR /home/scan2kasse WORKDIR /home/scan2kasse
RUN apt update && apt upgrade
RUN apt install -y libpq-dev gcc g++
COPY requirements.txt requirements.txt COPY requirements.txt requirements.txt
RUN python -m venv venv RUN python -m venv venv
RUN venv/bin/pip install -r requirements.txt RUN venv/bin/pip install -r requirements.txt
RUN venv/bin/pip install gunicorn
COPY app app COPY app app
COPY migrations migrations COPY migrations migrations
COPY configs configs
COPY run.py boot.sh ./ COPY run.py boot.sh ./
RUN chmod +x boot.sh RUN chmod +x boot.sh

View File

@ -1,3 +1,4 @@
from configs.config import Config
from flask import Flask from flask import Flask
from flask_bootstrap import Bootstrap from flask_bootstrap import Bootstrap
from flask_login import LoginManager from flask_login import LoginManager
@ -7,7 +8,6 @@ from logging import getLogger
from logging.config import fileConfig from logging.config import fileConfig
from os import makedirs from os import makedirs
from os.path import dirname, exists from os.path import dirname, exists
from yaml import safe_load
try: try:
dir_name = dirname(__file__) dir_name = dirname(__file__)
@ -18,10 +18,10 @@ try:
except NameError: except NameError:
DIR = "./" DIR = "./"
if not exists(DIR + "logs"): if not exists(DIR + "../logs"):
makedirs(DIR + "logs") makedirs(DIR + "../logs")
fileConfig(DIR + "configs/log.conf") fileConfig(DIR + "../configs/log.conf")
LOGGER = getLogger("root") LOGGER = getLogger("root")
bootstrap = Bootstrap() bootstrap = Bootstrap()
@ -31,9 +31,9 @@ login.login_view = 'web_login'
migrate = Migrate() migrate = Migrate()
def create_app(config_file="configs/config.yaml"): def create_app(config_class=Config):
app = Flask(__name__) app = Flask(__name__)
app.config.from_file(config_file, safe_load) app.config.from_object(config_class)
bootstrap.init_app(app) bootstrap.init_app(app)
db.init_app(app) db.init_app(app)
login.init_app(app) login.init_app(app)

View File

@ -13,7 +13,7 @@ def web_register():
return redirect(url_for('main.index')) return redirect(url_for('main.index'))
form = RegistrationForm() form = RegistrationForm()
if form.validate_on_submit(): if form.validate_on_submit():
user = User(username=form.username.data, email=form.email.data) user = User(email=form.email.data)
user.set_password(form.password.data) user.set_password(form.password.data)
db.session.add(user) db.session.add(user)
db.session.commit() db.session.commit()
@ -27,9 +27,9 @@ def web_login():
return redirect(url_for('main.index')) return redirect(url_for('main.index'))
form = LoginForm() form = LoginForm()
if form.validate_on_submit(): if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first() user = User.query.filter_by(email=form.email.data).first()
if user is None or not user.check_password(form.password.data): if user is None or not user.check_password(form.password.data):
flash('Invalid username or password') flash('Invalid email or password')
return redirect(url_for('auth.web_login')) return redirect(url_for('auth.web_login'))
login_user(user, remember=form.remember_me.data) login_user(user, remember=form.remember_me.data)
next_page = request.args.get('next') next_page = request.args.get('next')

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

@ -44,9 +44,9 @@ def get_report_from_user():
return jsonify(result_list) return jsonify(result_list)
else: else:
if "establishment" in request.args: if "establishment" in request.args:
return render_template("overview.html", results=result_list, establishment = Establishment.query.get(int(request.args['establishment']))) return render_template("main/overview.html", results=result_list, establishment = Establishment.query.get(int(request.args['establishment'])))
else: else:
return render_template("overview.html", results=result_list) return render_template("main/overview.html", results=result_list)
@bp.route('/token_authorization') @bp.route('/token_authorization')
def token_authorization(): def token_authorization():
@ -95,7 +95,7 @@ def new_item():
db.session.add(new_item) db.session.add(new_item)
db.session.commit() db.session.commit()
return redirect(url_for('index')) return redirect(url_for('index'))
return render_template('admin/new_item.html', form=form) return render_template('main/new_item.html', form=form)
@bp.route('/overview/register_boughts', methods=['GET']) @bp.route('/overview/register_boughts', methods=['GET'])
@login_required @login_required
@ -113,4 +113,4 @@ def check_unregistered_items():
if request.content_type == "application/json": if request.content_type == "application/json":
return jsonify(result_list) return jsonify(result_list)
else: else:
return render_template("overview.html", results=result_list) return render_template("main/overview.html", results=result_list)

View File

@ -24,7 +24,7 @@ class User(UserMixin, db.Model):
return check_password_hash(self.password_hash, password) return check_password_hash(self.password_hash, password)
def __repr__(self) -> str: def __repr__(self) -> str:
return f"<User {self.id} ({self.username})>" return f"<User {self.id} ({self.email})>"
class Establishment(db.Model): class Establishment(db.Model):
id = db.Column(db.BigInteger, primary_key=True) id = db.Column(db.BigInteger, primary_key=True)
@ -64,7 +64,7 @@ class Category(db.Model):
return f"<Category {self.id} ({self.name})>" return f"<Category {self.id} ({self.name})>"
class Item(db.Model): class Item(db.Model):
id = db.Column(db.BigInteger, primary_key=True) id = db.Column(db.BigInteger, primary_key=True, autoincrement=False)
name = db.Column(db.String(64), nullable=False) name = db.Column(db.String(64), nullable=False)
brand = db.Column(db.ForeignKey('brand.id'), nullable=False, server_onupdate=db.FetchedValue()) brand = db.Column(db.ForeignKey('brand.id'), nullable=False, server_onupdate=db.FetchedValue())
description = db.Column(db.Text, nullable=False) description = db.Column(db.Text, nullable=False)
@ -104,7 +104,7 @@ class AmountChange(db.Model):
return f"<Amount_Change {self.item} ({self.date})>" return f"<Amount_Change {self.item} ({self.date})>"
class Receipt(db.Model): class Receipt(db.Model):
id = db.Column(db.Numeric(precision=22, scale=0), primary_key=True) id = db.Column(db.Numeric(precision=24, scale=0), primary_key=True, autoincrement=False)
date = db.Column(db.Date, nullable=False) date = db.Column(db.Date, nullable=False)
from_user = db.Column(db.ForeignKey("login_token.token"), server_onupdate=db.FetchedValue()) from_user = db.Column(db.ForeignKey("login_token.token"), server_onupdate=db.FetchedValue())
registered = db.Column(db.Boolean, nullable=False, server_default=str(False)) registered = db.Column(db.Boolean, nullable=False, server_default=str(False))

View File

@ -3,7 +3,7 @@
{% block app_content %} {% block app_content %}
{% if establishment %} {% if establishment %}
{% if current_user.id == establishment.owner %} {% if current_user.id == establishment.owner %}
<button type="button" class="btn btn-outline-dark px-2" data-bs-toggle="button" autocomplete="off" onclick="window.location.href='{{ url_for('check_unregistered_items', establishment=establishment.id) }}'"> <button type="button" class="btn btn-outline-dark px-2" data-bs-toggle="button" autocomplete="off" onclick="window.location.href='{{ url_for('main.check_unregistered_items', establishment=establishment.id) }}'">
Abrechnung Abrechnung
</button> </button>
{% endif %} {% endif %}
@ -12,7 +12,7 @@
<div class="card"> <div class="card">
<button class="btn btn-primary" data-bs-toggle="collapse" data-bs-target="#b{{ user.id }}" aria-expanded="true"> <button class="btn btn-primary" data-bs-toggle="collapse" data-bs-target="#b{{ user.id }}" aria-expanded="true">
<div class="card-header"> <div class="card-header">
<h3>{{ user.username }}: {{ user.sum/100 }} €</h3> <h3>{{ user.email }}: {{ user.sum/100 }} €</h3>
</div> </div>
</button> </button>
<div class="collapse" id="b{{ user.id }}"> <div class="collapse" id="b{{ user.id }}">

View File

@ -29,7 +29,7 @@ def insert_bought_items(token: str, items: dict, date: str = None):
return {'user':token, 'date': date, 'items': items} if items else {} return {'user':token, 'date': date, 'items': items} if items else {}
def get_report(**kwargs): def get_report(**kwargs):
query_select = db.session.query(bwp.c.token, User.username, bwp.c.date, bwp.c.item, Item.name, bwp.c.amount, bwp.c.price) query_select = db.session.query(bwp.c.token, User.email, bwp.c.date, bwp.c.item, Item.name, bwp.c.amount, bwp.c.price)
query_select = query_select.select_from(bwp).join(LoginToken, LoginToken.token==bwp.c.token).join(User, LoginToken.user==User.id).join(Item, Item.id==bwp.c.item) query_select = query_select.select_from(bwp).join(LoginToken, LoginToken.token==bwp.c.token).join(User, LoginToken.user==User.id).join(Item, Item.id==bwp.c.item)
match kwargs: match kwargs:
case {"token": token}: case {"token": token}:
@ -62,7 +62,7 @@ def get_unregistered_and_register(intEstablishment: int):
if current_user.id != establishment.owner: if current_user.id != establishment.owner:
LOGGER.debug("!!!Wrong User!!!") LOGGER.debug("!!!Wrong User!!!")
return False return False
query_select = db.session.query(bwp.c.token, User.username, bwp.c.date, bwp.c.item, Item.name, bwp.c.amount, bwp.c.price) query_select = db.session.query(bwp.c.token, User.email, bwp.c.date, bwp.c.item, Item.name, bwp.c.amount, bwp.c.price)
query_select = query_select.select_from(bwp).join(LoginToken, LoginToken.token==bwp.c.token).join(User, LoginToken.user==User.id) query_select = query_select.select_from(bwp).join(LoginToken, LoginToken.token==bwp.c.token).join(User, LoginToken.user==User.id)
query_select = query_select.join(Item, Item.id==bwp.c.item).join(Bought, and_(Bought.token==bwp.c.token, Bought.item==bwp.c.item, Bought.date==bwp.c.date)) query_select = query_select.join(Item, Item.id==bwp.c.item).join(Bought, and_(Bought.token==bwp.c.token, Bought.item==bwp.c.item, Bought.date==bwp.c.date))
query_select = query_select.filter(bwp.c.token.in_(db.session.query(LoginToken.token).filter_by(establishment = intEstablishment))) query_select = query_select.filter(bwp.c.token.in_(db.session.query(LoginToken.token).filter_by(establishment = intEstablishment)))

View File

@ -9,7 +9,7 @@ def group_results(results: tuple) -> list:
try: try:
result_user_index = [result[0] == result_item['id'] for result_item in result_list].index(True) result_user_index = [result[0] == result_item['id'] for result_item in result_list].index(True)
except ValueError as e: except ValueError as e:
result_list.append({"id": result[0], "username": result[1], "sum": 0, "item_infos": []}) result_list.append({"id": result[0], "email": result[1], "sum": 0, "item_infos": []})
result_user_index = -1 result_user_index = -1
result_user = result_list[result_user_index] result_user = result_list[result_user_index]
try: try:

12
boot.sh
View File

@ -1,5 +1,13 @@
#!/bin/bash #!/bin/bash
source venv/bin/activate source venv/bin/activate
while true; do
flask db upgrade 05fce74b56cb
if [[ "$?" == "0" ]]; then
break
fi
echo Deploy command failed, retrying in 5 secs...
sleep 5
done
flask db upgrade 2be4d1ae5493
flask db upgrade flask db upgrade
flask translate compile exec gunicorn -b :5000 --access-logfile - --error-logfile - run:app
python run.py

15
configs/config.py Normal file
View File

@ -0,0 +1,15 @@
import os
from dotenv import load_dotenv
basedir = os.path.abspath(os.path.dirname(__file__))
load_dotenv(os.path.join(basedir, '.env'))
class Config(object):
SECRET_KEY = os.environ.get('SECRET_KEY') or "MY_5€cr37_K€Y"
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL', '').replace(
'postgres://', 'postgresql://') or \
(f"postgresql://{os.environ.get('DATABASE_USER', 'scan2kasse')}:{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', 'scan2kasse')}")
SQLALCHEMY_TRACK_MODIFICATIONS = False

View File

@ -25,7 +25,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

View File

@ -54,7 +54,7 @@ def upgrade():
sa.UniqueConstraint('token') sa.UniqueConstraint('token')
) )
op.create_table('receipt', op.create_table('receipt',
sa.Column('id', sa.Numeric(precision=22, scale=0), nullable=False), sa.Column('id', sa.Numeric(precision=24, scale=0), autoincrement=False, nullable=False),
sa.Column('date', sa.Date(), nullable=False), sa.Column('date', sa.Date(), nullable=False),
sa.Column('from_user', sa.String(length=15), nullable=True), sa.Column('from_user', sa.String(length=15), nullable=True),
sa.Column('registered', sa.Boolean(), server_default='False', nullable=False), sa.Column('registered', sa.Boolean(), server_default='False', nullable=False),
@ -62,7 +62,7 @@ def upgrade():
sa.PrimaryKeyConstraint('id') sa.PrimaryKeyConstraint('id')
) )
op.create_table('item', op.create_table('item',
sa.Column('id', sa.BigInteger(), nullable=False), sa.Column('id', sa.BigInteger(), autoincrement=False, nullable=False),
sa.Column('name', sa.String(length=64), nullable=False), sa.Column('name', sa.String(length=64), nullable=False),
sa.Column('brand', sa.Integer(), nullable=False), sa.Column('brand', sa.Integer(), nullable=False),
sa.Column('description', sa.Text(), nullable=False), sa.Column('description', sa.Text(), nullable=False),
@ -94,7 +94,7 @@ def upgrade():
sa.PrimaryKeyConstraint('item', 'category') sa.PrimaryKeyConstraint('item', 'category')
) )
op.create_table('item_receipt', op.create_table('item_receipt',
sa.Column('receipt', sa.Numeric(precision=22, scale=0), nullable=False), sa.Column('receipt', sa.Numeric(precision=24, scale=0), nullable=False),
sa.Column('item', sa.BigInteger(), nullable=False), sa.Column('item', sa.BigInteger(), nullable=False),
sa.Column('amount', sa.SmallInteger(), nullable=False), sa.Column('amount', sa.SmallInteger(), nullable=False),
sa.ForeignKeyConstraint(['item'], ['item.id'], ), sa.ForeignKeyConstraint(['item'], ['item.id'], ),

Binary file not shown.

7
run.py
View File

@ -1,14 +1,9 @@
from app import create_app, db from app import create_app, db
from app.models import * from app.models import *
from gevent.pywsgi import WSGIServer
app = create_app() app = create_app()
@app.shell_context_processor @app.shell_context_processor
def make_shell_context(): def make_shell_context():
return {'db': db, 'User': User, 'Bought': Bought, 'Item': Item, return {'db': db, 'User': User, 'Bought': Bought, 'Item': Item,
"LoginToken": LoginToken, "Establishment": Establishment, "Receipt": Receipt} "LoginToken": LoginToken, "Establishment": Establishment, "Receipt": Receipt}
if __name__ == '__main__':
http_server = WSGIServer(('', 5000), app)
http_server.serve_forever()