refactoring, reorganizing
Changed the folder structure for better maintenance and inserted Dockerfile for image building.
This commit is contained in:
parent
f26dee5489
commit
b978e0da56
22
Dockerfile
Normal file
22
Dockerfile
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
FROM python:3.10-slim
|
||||||
|
|
||||||
|
RUN useradd scan2kasse
|
||||||
|
|
||||||
|
WORKDIR /home/scan2kasse
|
||||||
|
|
||||||
|
COPY requirements.txt requirements.txt
|
||||||
|
RUN python -m venv venv
|
||||||
|
RUN venv/bin/pip install -r requirements.txt
|
||||||
|
|
||||||
|
COPY app app
|
||||||
|
COPY migrations migrations
|
||||||
|
COPY run.py boot.sh ./
|
||||||
|
RUN chmod +x boot.sh
|
||||||
|
|
||||||
|
ENV FLASK_APP run.py
|
||||||
|
|
||||||
|
RUN chown -R scan2kasse:scan2kasse ./
|
||||||
|
USER scan2kasse
|
||||||
|
|
||||||
|
EXPOSE 5000
|
||||||
|
ENTRYPOINT ["./boot.sh"]
|
||||||
@ -1,12 +1,13 @@
|
|||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
from flask_bootstrap import Bootstrap
|
||||||
from flask_login import LoginManager
|
from flask_login import LoginManager
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from flask_migrate import Migrate
|
from flask_migrate import Migrate
|
||||||
from yaml import safe_load
|
|
||||||
from logging import getLogger
|
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__)
|
||||||
@ -23,11 +24,28 @@ if not exists(DIR + "logs"):
|
|||||||
fileConfig(DIR + "configs/log.conf")
|
fileConfig(DIR + "configs/log.conf")
|
||||||
LOGGER = getLogger("root")
|
LOGGER = getLogger("root")
|
||||||
|
|
||||||
app = Flask(__name__)
|
bootstrap = Bootstrap()
|
||||||
app.config.from_file("configs/config.yaml", safe_load)
|
db = SQLAlchemy()
|
||||||
db = SQLAlchemy(app)
|
login = LoginManager()
|
||||||
migrate = Migrate(app, db, render_as_batch=True)
|
|
||||||
login = LoginManager(app)
|
|
||||||
login.login_view = 'web_login'
|
login.login_view = 'web_login'
|
||||||
|
migrate = Migrate()
|
||||||
|
|
||||||
from app import routes, models
|
|
||||||
|
def create_app(config_file="configs/config.yaml"):
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config.from_file(config_file, safe_load)
|
||||||
|
bootstrap.init_app(app)
|
||||||
|
db.init_app(app)
|
||||||
|
login.init_app(app)
|
||||||
|
migrate.init_app(app, db, render_as_batch=True)
|
||||||
|
|
||||||
|
from app.auth import bp as auth_bp
|
||||||
|
app.register_blueprint(auth_bp, url_prefix='/auth')
|
||||||
|
from app.errors import bp as errors_bp
|
||||||
|
app.register_blueprint(errors_bp)
|
||||||
|
from app.main import bp as main_bp
|
||||||
|
app.register_blueprint(main_bp)
|
||||||
|
|
||||||
|
return app
|
||||||
|
|
||||||
|
from app import models
|
||||||
5
app/auth/__init__.py
Normal file
5
app/auth/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from flask import Blueprint
|
||||||
|
|
||||||
|
bp = Blueprint('auth', __name__)
|
||||||
|
|
||||||
|
from app.auth import forms, routes
|
||||||
27
app/auth/forms.py
Normal file
27
app/auth/forms.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from app.models import User
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import StringField, PasswordField, BooleanField, SubmitField
|
||||||
|
from wtforms.validators import DataRequired, Email, EqualTo, ValidationError
|
||||||
|
|
||||||
|
class LoginForm(FlaskForm):
|
||||||
|
email = StringField('Email', validators=[DataRequired(), Email()])
|
||||||
|
password = PasswordField('Password', validators=[DataRequired()])
|
||||||
|
remember_me = BooleanField('Remember Me')
|
||||||
|
submit = SubmitField('Sign In', render_kw={"class": "btn btn-primary mt-3"})
|
||||||
|
|
||||||
|
class RegistrationForm(FlaskForm):
|
||||||
|
email = StringField('Email', validators=[DataRequired(), Email()])
|
||||||
|
password = PasswordField('Password', validators=[DataRequired()])
|
||||||
|
password2 = PasswordField(
|
||||||
|
'Repeat Password', validators=[DataRequired(), EqualTo('password')])
|
||||||
|
submit = SubmitField('Register', render_kw={"class": "btn btn-primary mt-3"})
|
||||||
|
|
||||||
|
def validate_username(self, username):
|
||||||
|
user = User.query.filter_by(username=username.data).first()
|
||||||
|
if user is not None:
|
||||||
|
raise ValidationError('Please use a different username.')
|
||||||
|
|
||||||
|
def validate_email(self, email):
|
||||||
|
user = User.query.filter_by(email=email.data).first()
|
||||||
|
if user is not None:
|
||||||
|
raise ValidationError('Please use a different email address.')
|
||||||
44
app/auth/routes.py
Normal file
44
app/auth/routes.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
from app import db
|
||||||
|
from app.auth import bp
|
||||||
|
from app.auth.forms import LoginForm, RegistrationForm
|
||||||
|
from app.models import User
|
||||||
|
from app.utils.routes_utils import render_custom_template as render_template
|
||||||
|
from flask import flash, redirect, request, url_for
|
||||||
|
from flask_login import current_user, login_user, logout_user
|
||||||
|
from werkzeug.urls import url_parse
|
||||||
|
|
||||||
|
@bp.route(f'/register', methods=['GET', 'POST'])
|
||||||
|
def web_register():
|
||||||
|
if current_user.is_authenticated:
|
||||||
|
return redirect(url_for('main.index'))
|
||||||
|
form = RegistrationForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
user = User(username=form.username.data, email=form.email.data)
|
||||||
|
user.set_password(form.password.data)
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.commit()
|
||||||
|
flash('Congratulations, you are now a registered user!')
|
||||||
|
return redirect(url_for('auth.login'))
|
||||||
|
return render_template('auth/register.html', title='Register', form=form)
|
||||||
|
|
||||||
|
@bp.route(f'/login', methods=['GET', 'POST'])
|
||||||
|
def web_login():
|
||||||
|
if current_user.is_authenticated:
|
||||||
|
return redirect(url_for('main.index'))
|
||||||
|
form = LoginForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
user = User.query.filter_by(username=form.username.data).first()
|
||||||
|
if user is None or not user.check_password(form.password.data):
|
||||||
|
flash('Invalid username or password')
|
||||||
|
return redirect(url_for('auth.web_login'))
|
||||||
|
login_user(user, remember=form.remember_me.data)
|
||||||
|
next_page = request.args.get('next')
|
||||||
|
if not next_page or url_parse(next_page).netloc != '':
|
||||||
|
next_page = url_for('main.index')
|
||||||
|
return redirect(next_page)
|
||||||
|
return render_template('auth/login.html', title='Sign In', form=form)
|
||||||
|
|
||||||
|
@bp.route(f'/logout')
|
||||||
|
def web_logout():
|
||||||
|
logout_user()
|
||||||
|
return redirect(url_for('main.index'))
|
||||||
5
app/errors/__init__.py
Normal file
5
app/errors/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from flask import Blueprint
|
||||||
|
|
||||||
|
bp = Blueprint('errors', __name__)
|
||||||
|
|
||||||
|
from app.errors import handlers
|
||||||
12
app/errors/handlers.py
Normal file
12
app/errors/handlers.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from app import db
|
||||||
|
from app.errors import bp
|
||||||
|
from flask import render_template
|
||||||
|
|
||||||
|
@bp.app_errorhandler(404)
|
||||||
|
def not_found_error(error):
|
||||||
|
return render_template('errors/404.html'), 404
|
||||||
|
|
||||||
|
@bp.app_errorhandler(500)
|
||||||
|
def internal_error(error):
|
||||||
|
db.session.rollback()
|
||||||
|
return render_template('errors/500.html'), 500
|
||||||
61
app/forms.py
61
app/forms.py
@ -1,61 +0,0 @@
|
|||||||
from app.models import Brand, Category, User
|
|
||||||
from flask_wtf import FlaskForm
|
|
||||||
from wtforms import StringField, PasswordField, BooleanField, SubmitField, SelectMultipleField, DateField, IntegerField, SelectField, FloatField
|
|
||||||
from wtforms.validators import DataRequired, Email, EqualTo, Optional, ValidationError
|
|
||||||
|
|
||||||
class LoginForm(FlaskForm):
|
|
||||||
username = StringField('Username', validators=[DataRequired()])
|
|
||||||
password = PasswordField('Password', validators=[DataRequired()])
|
|
||||||
remember_me = BooleanField('Remember Me')
|
|
||||||
submit = SubmitField('Sign In')
|
|
||||||
|
|
||||||
class RegistrationForm(FlaskForm):
|
|
||||||
username = StringField('Username', validators=[DataRequired()])
|
|
||||||
email = StringField('Email', validators=[DataRequired(), Email()])
|
|
||||||
password = PasswordField('Password', validators=[DataRequired()])
|
|
||||||
password2 = PasswordField(
|
|
||||||
'Repeat Password', validators=[DataRequired(), EqualTo('password')])
|
|
||||||
submit = SubmitField('Register')
|
|
||||||
|
|
||||||
def validate_username(self, username):
|
|
||||||
user = User.query.filter_by(username=username.data).first()
|
|
||||||
if user is not None:
|
|
||||||
raise ValidationError('Please use a different username.')
|
|
||||||
|
|
||||||
def validate_email(self, email):
|
|
||||||
user = User.query.filter_by(email=email.data).first()
|
|
||||||
if user is not None:
|
|
||||||
raise ValidationError('Please use a different email address.')
|
|
||||||
|
|
||||||
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", validators=[Optional()])
|
|
||||||
category = SelectMultipleField("Categories", choices=[], validators=[Optional()])
|
|
||||||
brand = SelectField("Brand", choices=[], validators=[DataRequired()])
|
|
||||||
submit = SubmitField("Submit")
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def new(cls):
|
|
||||||
form = cls()
|
|
||||||
form.category.choices = [(c.id, c.name) for c in Category.query.order_by("name").all()]
|
|
||||||
form.brand.choices = [(b.id, b.name) for b in Brand.query.order_by("name").all()]
|
|
||||||
return form
|
|
||||||
|
|
||||||
class NewCategoryForm(FlaskForm):
|
|
||||||
name = StringField("Name", validators=[DataRequired()])
|
|
||||||
submit = SubmitField("Submit")
|
|
||||||
|
|
||||||
class NewBrandForm(FlaskForm):
|
|
||||||
name = StringField("Name", validators=[DataRequired()])
|
|
||||||
submit = SubmitField("Submit")
|
|
||||||
|
|
||||||
class NeueWGForm(FlaskForm):
|
|
||||||
wg_name = StringField("Name", validators=[DataRequired()])
|
|
||||||
submit = SubmitField("Submit")
|
|
||||||
|
|
||||||
class WGBeitretenForm(FlaskForm):
|
|
||||||
submit = SubmitField("Submit")
|
|
||||||
5
app/main/__init__.py
Normal file
5
app/main/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from flask import Blueprint
|
||||||
|
|
||||||
|
bp = Blueprint('main', __name__)
|
||||||
|
|
||||||
|
from app.main import forms, routes
|
||||||
37
app/main/forms.py
Normal file
37
app/main/forms.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
from app.models import Brand, Category
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import DateField, FloatField, IntegerField, SelectField, SelectMultipleField, StringField, SubmitField
|
||||||
|
from wtforms.validators import DataRequired, Optional
|
||||||
|
|
||||||
|
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", validators=[Optional()])
|
||||||
|
category = SelectMultipleField("Categories", choices=[], validators=[Optional()])
|
||||||
|
brand = SelectField("Brand", choices=[], validators=[DataRequired()])
|
||||||
|
submit = SubmitField("Submit")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def new(cls):
|
||||||
|
form = cls()
|
||||||
|
form.category.choices = [(c.id, c.name) for c in Category.query.order_by("name").all()]
|
||||||
|
form.brand.choices = [(b.id, b.name) for b in Brand.query.order_by("name").all()]
|
||||||
|
return form
|
||||||
|
|
||||||
|
class NewCategoryForm(FlaskForm):
|
||||||
|
name = StringField("Name", validators=[DataRequired()])
|
||||||
|
submit = SubmitField("Submit", render_kw={"class": "btn btn-primary mt-3"})
|
||||||
|
|
||||||
|
class NewBrandForm(FlaskForm):
|
||||||
|
name = StringField("Name", validators=[DataRequired()])
|
||||||
|
submit = SubmitField("Submit", render_kw={"class": "btn btn-primary mt-3"})
|
||||||
|
|
||||||
|
class NewEstablishmentForm(FlaskForm):
|
||||||
|
establishment_name = StringField("Name", validators=[DataRequired()])
|
||||||
|
submit = SubmitField("Submit", render_kw={"class": "btn btn-primary mt-3"})
|
||||||
|
|
||||||
|
class JoinEstablishmentForm(FlaskForm):
|
||||||
|
submit = SubmitField("Submit", render_kw={"class": "btn btn-primary mt-3"})
|
||||||
@ -1,106 +1,24 @@
|
|||||||
from app import app, db, LOGGER
|
from app import db, LOGGER
|
||||||
from app.forms import NewItemForm, LoginForm, RegistrationForm
|
from app.main.forms import NewItemForm
|
||||||
from app.models import Establishment, LoginToken, User, Item, Brand, PriceChange, AmountChange
|
from app.main import bp
|
||||||
|
from app.models import AmountChange, Brand, Establishment, LoginToken, Item, PriceChange
|
||||||
from app.utils import view_utils, database_utils
|
from app.utils import view_utils, database_utils
|
||||||
from app.utils.routes_utils import render_custom_template as render_template
|
from app.utils.routes_utils import render_custom_template as render_template
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from flask import abort, flash, redirect, request, url_for
|
from flask import abort, redirect, request, url_for
|
||||||
from flask.json import jsonify
|
from flask.json import jsonify
|
||||||
from flask_login import current_user, login_required, login_user, logout_user
|
from flask_login import current_user, login_required
|
||||||
from werkzeug.urls import url_parse
|
|
||||||
|
|
||||||
APPNAME = "scan2kasse"
|
@bp.route('/')
|
||||||
|
@bp.route('/index')
|
||||||
@app.route(f'/{APPNAME}')
|
|
||||||
def index():
|
def index():
|
||||||
return render_template("base.html")
|
return render_template("base.html")
|
||||||
|
|
||||||
@app.route(f'/{APPNAME}/token_authorization')
|
# @bp.route('/')
|
||||||
def token_authorization():
|
# def test():
|
||||||
LOGGER.debug("Token Login")
|
# return "Hello World"
|
||||||
if not request.json or 'login' not in request.json:
|
|
||||||
abort(400)
|
|
||||||
if not LoginToken.query.filter_by(token=request.json['login']).first():
|
|
||||||
abort(403)
|
|
||||||
return jsonify({}), 200
|
|
||||||
|
|
||||||
@app.route(f'/{APPNAME}/token_insert', methods=['POST'])
|
@bp.route('/overview', methods=['GET'])
|
||||||
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('/register', methods=['GET', 'POST'])
|
|
||||||
def web_register():
|
|
||||||
if current_user.is_authenticated:
|
|
||||||
return redirect(url_for('index'))
|
|
||||||
form = RegistrationForm()
|
|
||||||
if form.validate_on_submit():
|
|
||||||
user = User(username=form.username.data, email=form.email.data)
|
|
||||||
user.set_password(form.password.data)
|
|
||||||
db.session.add(user)
|
|
||||||
db.session.commit()
|
|
||||||
flash('Congratulations, you are now a registered user!')
|
|
||||||
return redirect(url_for('login'))
|
|
||||||
return render_template('register.html', title='Register', form=form)
|
|
||||||
|
|
||||||
@app.route(f'/{APPNAME}/login', methods=['GET', 'POST'])
|
|
||||||
def web_login():
|
|
||||||
if current_user.is_authenticated:
|
|
||||||
return redirect(url_for('index'))
|
|
||||||
form = LoginForm()
|
|
||||||
if form.validate_on_submit():
|
|
||||||
user = User.query.filter_by(username=form.username.data).first()
|
|
||||||
if user is None or not user.check_password(form.password.data):
|
|
||||||
flash('Invalid username or password')
|
|
||||||
return redirect(url_for('web_login'))
|
|
||||||
login_user(user, remember=form.remember_me.data)
|
|
||||||
next_page = request.args.get('next')
|
|
||||||
if not next_page or url_parse(next_page).netloc != '':
|
|
||||||
next_page = url_for('index')
|
|
||||||
return redirect(next_page)
|
|
||||||
return render_template('login.html', title='Sign In', form=form)
|
|
||||||
|
|
||||||
@app.route(f'/{APPNAME}/logout')
|
|
||||||
def web_logout():
|
|
||||||
logout_user()
|
|
||||||
return redirect(url_for('index'))
|
|
||||||
|
|
||||||
@app.route(f'/{APPNAME}/newitem', methods=['GET', 'POST'])
|
|
||||||
@login_required
|
|
||||||
def new_item():
|
|
||||||
if current_user.is_anonymous:
|
|
||||||
abort(403)
|
|
||||||
form=NewItemForm.new()
|
|
||||||
if form.is_submitted():
|
|
||||||
LOGGER.debug("submitted")
|
|
||||||
if form.validate():
|
|
||||||
LOGGER.debug("valid")
|
|
||||||
else:
|
|
||||||
LOGGER.debug(form.errors)
|
|
||||||
if form.validate_on_submit():
|
|
||||||
LOGGER.debug("valid form")
|
|
||||||
brand = Brand.query.get(form.brand.data)
|
|
||||||
new_item = Item(id = form.id.data, name = form.name.data, brand = brand.id, description = form.description.data)
|
|
||||||
# if form.category.data:
|
|
||||||
# category = Category.query.get(id = form.category.data)
|
|
||||||
# new_item.Category = category
|
|
||||||
new_item.PriceChange = [PriceChange(Item = new_item, date = date(2021, 12, 1), price = form.price_change.data)]
|
|
||||||
if form.amount_change.data:
|
|
||||||
new_item.AmountChange = [AmountChange(Item = new_item, date = date(2021, 12, 1), amount = form.amount_change.data)]
|
|
||||||
db.session.add(new_item)
|
|
||||||
db.session.commit()
|
|
||||||
return redirect(url_for('index'))
|
|
||||||
return render_template('admin/new_item.html', form=form)
|
|
||||||
|
|
||||||
@app.route(f'/{APPNAME}/overview', methods=['GET'])
|
|
||||||
@login_required
|
@login_required
|
||||||
def get_report_from_user():
|
def get_report_from_user():
|
||||||
if current_user.is_anonymous:
|
if current_user.is_anonymous:
|
||||||
@ -130,7 +48,56 @@ def get_report_from_user():
|
|||||||
else:
|
else:
|
||||||
return render_template("overview.html", results=result_list)
|
return render_template("overview.html", results=result_list)
|
||||||
|
|
||||||
@app.route(f'/{APPNAME}/overview/register_boughts', methods=['GET'])
|
@bp.route('/token_authorization')
|
||||||
|
def token_authorization():
|
||||||
|
LOGGER.debug("Token Login")
|
||||||
|
if not request.json or 'login' not in request.json:
|
||||||
|
abort(400)
|
||||||
|
if not LoginToken.query.filter_by(token=request.json['login']).first():
|
||||||
|
abort(403)
|
||||||
|
return jsonify({}), 200
|
||||||
|
|
||||||
|
@bp.route('/token_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
|
||||||
|
|
||||||
|
@bp.route('/new_item', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
def new_item():
|
||||||
|
if current_user.is_anonymous:
|
||||||
|
abort(403)
|
||||||
|
form=NewItemForm.new()
|
||||||
|
if form.is_submitted():
|
||||||
|
LOGGER.debug("submitted")
|
||||||
|
if form.validate():
|
||||||
|
LOGGER.debug("valid")
|
||||||
|
else:
|
||||||
|
LOGGER.debug(form.errors)
|
||||||
|
if form.validate_on_submit():
|
||||||
|
LOGGER.debug("valid form")
|
||||||
|
brand = Brand.query.get(form.brand.data)
|
||||||
|
new_item = Item(id = form.id.data, name = form.name.data, brand = brand.id, description = form.description.data)
|
||||||
|
# if form.category.data:
|
||||||
|
# category = Category.query.get(id = form.category.data)
|
||||||
|
# new_item.Category = category
|
||||||
|
new_item.PriceChange = [PriceChange(Item = new_item, date = date(2021, 12, 1), price = form.price_change.data)]
|
||||||
|
if form.amount_change.data:
|
||||||
|
new_item.AmountChange = [AmountChange(Item = new_item, date = date(2021, 12, 1), amount = form.amount_change.data)]
|
||||||
|
db.session.add(new_item)
|
||||||
|
db.session.commit()
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
return render_template('admin/new_item.html', form=form)
|
||||||
|
|
||||||
|
@bp.route('/overview/register_boughts', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
def check_unregistered_items():
|
def check_unregistered_items():
|
||||||
if current_user.is_anonymous or not request.args or 'establishment' not in request.args:
|
if current_user.is_anonymous or not request.args or 'establishment' not in request.args:
|
||||||
@ -4,14 +4,13 @@ from flask_login import UserMixin
|
|||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
|
|
||||||
item_category = db.Table("item_category",
|
item_category = db.Table("item_category",
|
||||||
db.Column("item", db.ForeignKey("item.id"), primary_key=True),
|
db.Column("item", db.ForeignKey("item.id"), primary_key=True, server_onupdate=db.FetchedValue()),
|
||||||
db.Column("category", db.ForeignKey("category.id"), primary_key=True)
|
db.Column("category", db.ForeignKey("category.id"), primary_key=True, server_onupdate=db.FetchedValue())
|
||||||
)
|
)
|
||||||
|
|
||||||
class User(UserMixin, db.Model):
|
class User(UserMixin, db.Model):
|
||||||
id = db.Column(db.BigInteger, primary_key=True)
|
id = db.Column(db.BigInteger, primary_key=True)
|
||||||
email = db.Column(db.String(64), nullable=False, unique=True)
|
email = db.Column(db.String(255), nullable=False, unique=True)
|
||||||
username = db.Column(db.String(64), nullable=False, unique=True)
|
|
||||||
password_hash = db.Column(db.String(128), nullable=False)
|
password_hash = db.Column(db.String(128), nullable=False)
|
||||||
|
|
||||||
LoginToken = db.relationship("LoginToken", backref='User', lazy='dynamic')
|
LoginToken = db.relationship("LoginToken", backref='User', lazy='dynamic')
|
||||||
@ -40,9 +39,10 @@ class Establishment(db.Model):
|
|||||||
return f"<Establishment {self.id} ({self.name})>"
|
return f"<Establishment {self.id} ({self.name})>"
|
||||||
|
|
||||||
class LoginToken(db.Model):
|
class LoginToken(db.Model):
|
||||||
user = db.Column(db.ForeignKey('user.id'), primary_key=True)
|
user = db.Column(db.ForeignKey('user.id'), primary_key=True, server_onupdate=db.FetchedValue())
|
||||||
establishment = db.Column(db.ForeignKey('establishment.id'), primary_key=True)
|
establishment = db.Column(db.ForeignKey('establishment.id'), primary_key=True, server_onupdate=db.FetchedValue())
|
||||||
token = db.Column(db.String(15), nullable=True, unique=True)
|
token = db.Column(db.String(15), nullable=True, unique=True)
|
||||||
|
paid = db.Column(db.BigInteger, nullable=False, server_default=str(0))
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"LoginToken {self.token}"
|
return f"LoginToken {self.token}"
|
||||||
@ -66,7 +66,7 @@ class Category(db.Model):
|
|||||||
class Item(db.Model):
|
class Item(db.Model):
|
||||||
id = db.Column(db.BigInteger, primary_key=True)
|
id = db.Column(db.BigInteger, primary_key=True)
|
||||||
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)
|
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)
|
||||||
|
|
||||||
Category = db.relationship("Category", secondary=item_category, lazy="dynamic", back_populates="Item")
|
Category = db.relationship("Category", secondary=item_category, lazy="dynamic", back_populates="Item")
|
||||||
@ -78,18 +78,17 @@ class Item(db.Model):
|
|||||||
return f"<Item {self.id} ({self.name})>"
|
return f"<Item {self.id} ({self.name})>"
|
||||||
|
|
||||||
class Bought(db.Model):
|
class Bought(db.Model):
|
||||||
token = db.Column(db.ForeignKey('login_token.token'), primary_key=True)
|
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)
|
item = db.Column(db.ForeignKey('item.id'), primary_key=True, server_onupdate=db.FetchedValue())
|
||||||
date = db.Column(db.Date, primary_key=True)
|
date = db.Column(db.Date, primary_key=True)
|
||||||
amount = db.Column(db.SmallInteger, nullable=False)
|
amount = db.Column(db.SmallInteger, nullable=False)
|
||||||
registered = db.Column(db.Boolean, nullable=False, server_default=str(False))
|
registered = db.Column(db.Boolean, nullable=False, server_default=str(False))
|
||||||
paid = db.Column(db.SmallInteger, nullable=False, server_default=str(0))
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<Bought Object>"
|
return f"<Bought Object>"
|
||||||
|
|
||||||
class PriceChange(db.Model):
|
class PriceChange(db.Model):
|
||||||
item = db.Column(db.ForeignKey('item.id'), primary_key=True)
|
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)))
|
date = db.Column(db.Date, primary_key=True, server_default=str(date(2021, 12, 1)))
|
||||||
price = db.Column(db.SmallInteger, nullable=False)
|
price = db.Column(db.SmallInteger, nullable=False)
|
||||||
|
|
||||||
@ -97,7 +96,7 @@ class PriceChange(db.Model):
|
|||||||
return f"<Price_Change {self.item} ({self.date})>"
|
return f"<Price_Change {self.item} ({self.date})>"
|
||||||
|
|
||||||
class AmountChange(db.Model):
|
class AmountChange(db.Model):
|
||||||
item = db.Column(db.ForeignKey('item.id'), primary_key=True)
|
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)))
|
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))
|
amount = db.Column(db.SmallInteger, nullable=False, server_default=str(1))
|
||||||
|
|
||||||
@ -107,15 +106,15 @@ class AmountChange(db.Model):
|
|||||||
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=22, scale=0), primary_key=True)
|
||||||
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())
|
||||||
registered = db.Column(db.Boolean, nullable=False, server_default=str(False))
|
registered = db.Column(db.Boolean, nullable=False, server_default=str(False))
|
||||||
paid = db.Column(db.SmallInteger, nullable=False, server_default=str(0))
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<Receipt {self.id}>"
|
return f"<Receipt {self.id}>"
|
||||||
|
|
||||||
class ItemReceipt(db.Model):
|
class ItemReceipt(db.Model):
|
||||||
receipt = db.Column(db.ForeignKey("receipt.id"), primary_key=True)
|
receipt = db.Column(db.ForeignKey("receipt.id"), primary_key=True, server_onupdate=db.FetchedValue())
|
||||||
item = db.Column(db.ForeignKey("item.id"), primary_key=True)
|
item = db.Column(db.ForeignKey("item.id"), primary_key=True, server_onupdate=db.FetchedValue())
|
||||||
amount = db.Column(db.SmallInteger, nullable=False)
|
amount = db.Column(db.SmallInteger, nullable=False)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
|
|||||||
19
app/templates/auth/login.html
Normal file
19
app/templates/auth/login.html
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
<h1>Sign In</h1>
|
||||||
|
<form action="" method="post" novalidate>
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
<p>
|
||||||
|
{{ wtf.form_field(form.email) }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{ wtf.form_field(form.password) }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{ wtf.form_field(form.remember_me) }}
|
||||||
|
</p>
|
||||||
|
{{ wtf.form_field(form.submit, class=form.submit.render_kw["class"]) }}
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
19
app/templates/auth/register.html
Normal file
19
app/templates/auth/register.html
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
<h1>Register</h1>
|
||||||
|
<form action="" method="post">
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
<p>
|
||||||
|
{{ wtf.form_field(form.email) }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{ wtf.form_field(form.password) }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{ wtf.form_field(form.password2) }}
|
||||||
|
</p>
|
||||||
|
{{ wtf.form_field(form.submit, class=form.submit.render_kw["class"]) }}
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
@ -1,104 +1,116 @@
|
|||||||
<!doctype html>
|
{% extends "bootstrap/base.html" %}
|
||||||
<html>
|
|
||||||
<head>
|
{% block title %}
|
||||||
<meta charset="utf-8">
|
{% if title %}
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
{{ title }}
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
{% else %}
|
||||||
<link rel="stylesheet" href={{ url_for('static', filename="sidebars.css")}}>
|
Scan2Kasse
|
||||||
{% if title %}
|
{% endif %}
|
||||||
<title>{{ title }}</title>
|
{% endblock %}
|
||||||
{% else %}
|
|
||||||
<title>Scan2Kasse</title>
|
{% block metas %}
|
||||||
{% endif %}
|
<meta charset="utf-8">
|
||||||
<!-- <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> -->
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
</head>
|
{% endblock %}
|
||||||
<body>
|
|
||||||
<main>
|
{% block styles %}
|
||||||
<div class="flex-shrink-0 p-3 bg-black bg-opacity-10 scrollbar-primary h-100 position-fixed" style="width: 280px;">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||||
<a href="/" class="d-flex align-items-center pb-3 mb-3 link-dark text-decoration-none border-bottom border-dark">
|
<link rel="stylesheet" href={{ url_for('static', filename="sidebars.css")}}>
|
||||||
<svg class="bi me-2" width="30" height="24"><use xlink:href="#bootstrap"></use></svg>
|
{% endblock %}
|
||||||
<span class="fs-5 fw-semibold">Scan2Kasse</span>
|
|
||||||
</a>
|
{% block navbar %}
|
||||||
<ul class="list-unstyled ps-0">
|
<main>
|
||||||
<li class="mb-1">
|
<div class="flex-shrink-0 p-3 bg-black bg-opacity-10 scrollbar-primary h-100 position-fixed" style="width: 280px;">
|
||||||
<button class="btn btn-toggle align-items-center rounded collapsed" data-bs-toggle="collapse" data-bs-target="#home-collapse" aria-expanded="true">
|
<a href="/" class="d-flex align-items-center pb-3 mb-3 link-dark text-decoration-none border-bottom border-dark">
|
||||||
Home (WIP)
|
<svg class="bi me-2" width="30" height="24"><use xlink:href="#bootstrap"></use></svg>
|
||||||
</button>
|
<span class="fs-5 fw-semibold">Scan2Kasse</span>
|
||||||
<div class="collapse show" id="home-collapse">
|
</a>
|
||||||
<ul class="btn-toggle-nav list-unstyled fw-normal pb-1 small">
|
<ul class="list-unstyled ps-0">
|
||||||
<li><a href="#" class="link-dark rounded">Übersicht</a></li>
|
<li class="mb-1">
|
||||||
<li><a href="#" class="link-dark rounded">Updates</a></li>
|
<button class="btn btn-toggle align-items-center rounded collapsed" data-bs-toggle="collapse" data-bs-target="#home-collapse" aria-expanded="true">
|
||||||
<li><a href="#" class="link-dark rounded">Reports</a></li>
|
Home (WIP)
|
||||||
</ul>
|
</button>
|
||||||
</div>
|
<div class="collapse show" id="home-collapse">
|
||||||
</li>
|
<ul class="btn-toggle-nav list-unstyled fw-normal pb-1 small">
|
||||||
<li class="mb-1">
|
<li><a href="#" class="link-dark rounded">Übersicht</a></li>
|
||||||
<button class="btn btn-toggle align-items-center rounded collapsed" data-bs-toggle="collapse" data-bs-target="#dashboard-collapse" aria-expanded="false">
|
<li><a href="#" class="link-dark rounded">Updates</a></li>
|
||||||
Übersicht
|
<li><a href="#" class="link-dark rounded">Reports</a></li>
|
||||||
</button>
|
</ul>
|
||||||
<div class="collapse" id="dashboard-collapse">
|
</div>
|
||||||
<ul class="btn-toggle-nav list-unstyled fw-normal pb-1 small">
|
</li>
|
||||||
{% if establishments %}
|
<li class="mb-1">
|
||||||
<li><a href="#" class="link-dark rounded">Allgemein</a></li>
|
<button class="btn btn-toggle align-items-center rounded collapsed" data-bs-toggle="collapse" data-bs-target="#dashboard-collapse" aria-expanded="false">
|
||||||
{% for establishment in establishments %}
|
Übersicht
|
||||||
<li><a href="{{ url_for('get_report_from_user', establishment=establishment.id) }}" class="link-dark rounded">{{ establishment.name }}</a></li>
|
</button>
|
||||||
{% endfor %}
|
<div class="collapse" id="dashboard-collapse">
|
||||||
{% endif %}
|
<ul class="btn-toggle-nav list-unstyled fw-normal pb-1 small">
|
||||||
</ul>
|
{% if establishments %}
|
||||||
</div>
|
<li><a href="#" class="link-dark rounded">Allgemein</a></li>
|
||||||
</li>
|
{% for establishment in establishments %}
|
||||||
<!-- <li class="mb-1">
|
<li><a href="{{ url_for('main.get_report_from_user', establishment=establishment.id) }}" class="link-dark rounded">{{ establishment.name }}</a></li>
|
||||||
<button class="btn btn-toggle align-items-center rounded collapsed" data-bs-toggle="collapse" data-bs-target="#orders-collapse" aria-expanded="false">
|
{% endfor %}
|
||||||
Orders
|
{% endif %}
|
||||||
</button>
|
</ul>
|
||||||
<div class="collapse" id="orders-collapse">
|
</div>
|
||||||
<ul class="btn-toggle-nav list-unstyled fw-normal pb-1 small">
|
</li>
|
||||||
<li><a href="#" class="link-dark rounded">New</a></li>
|
<!-- <li class="mb-1">
|
||||||
<li><a href="#" class="link-dark rounded">Processed</a></li>
|
<button class="btn btn-toggle align-items-center rounded collapsed" data-bs-toggle="collapse" data-bs-target="#orders-collapse" aria-expanded="false">
|
||||||
<li><a href="#" class="link-dark rounded">Shipped</a></li>
|
Orders
|
||||||
<li><a href="#" class="link-dark rounded">Returned</a></li>
|
</button>
|
||||||
</ul>
|
<div class="collapse" id="orders-collapse">
|
||||||
</div>
|
<ul class="btn-toggle-nav list-unstyled fw-normal pb-1 small">
|
||||||
</li> -->
|
<li><a href="#" class="link-dark rounded">New</a></li>
|
||||||
<li class="border-top border-dark my-3"></li>
|
<li><a href="#" class="link-dark rounded">Processed</a></li>
|
||||||
<li class="mb-1">
|
<li><a href="#" class="link-dark rounded">Shipped</a></li>
|
||||||
<button class="btn btn-toggle align-items-center rounded collapsed" data-bs-toggle="collapse" data-bs-target="#account-collapse" aria-expanded="false">
|
<li><a href="#" class="link-dark rounded">Returned</a></li>
|
||||||
Account
|
</ul>
|
||||||
</button>
|
</div>
|
||||||
<div class="collapse" id="account-collapse" style="">
|
</li> -->
|
||||||
<ul class="btn-toggle-nav list-unstyled fw-normal pb-1 small">
|
<li class="border-top border-dark my-3"></li>
|
||||||
<!-- <li><a href="#" class="link-dark rounded">New...</a></li>
|
<li class="mb-1">
|
||||||
<li><a href="#" class="link-dark rounded">Settings</a></li> -->
|
<button class="btn btn-toggle align-items-center rounded collapsed" data-bs-toggle="collapse" data-bs-target="#account-collapse" aria-expanded="false">
|
||||||
{% if current_user.is_authenticated %}
|
Account
|
||||||
<!-- <li><a href="#" class="link-dark rounded">Profile</a></li> -->
|
</button>
|
||||||
<li><a href={{ url_for('web_logout') }} class="link-dark rounded">Sign out</a></li>
|
<div class="collapse" id="account-collapse" style="">
|
||||||
{% else %}
|
<ul class="btn-toggle-nav list-unstyled fw-normal pb-1 small">
|
||||||
<li><a href={{ url_for('web_register') }} class="link-dark rounded">Register</a></li>
|
<!-- <li><a href="#" class="link-dark rounded">New...</a></li>
|
||||||
<li><a href={{ url_for('web_login') }} class="link-dark rounded">Sign in</a></li>
|
<li><a href="#" class="link-dark rounded">Settings</a></li> -->
|
||||||
{% endif %}
|
{% if current_user.is_authenticated %}
|
||||||
</ul>
|
<!-- <li><a href="#" class="link-dark rounded">Profile</a></li> -->
|
||||||
</div>
|
<li><a href={{ url_for('auth.web_logout') }} class="link-dark rounded">Sign out</a></li>
|
||||||
</li>
|
{% else %}
|
||||||
</ul>
|
<li><a href={{ url_for('auth.web_register') }} class="link-dark rounded">Register</a></li>
|
||||||
</div>
|
<li><a href={{ url_for('auth.web_login') }} class="link-dark rounded">Sign in</a></li>
|
||||||
{% with messages = get_flashed_messages() %}
|
{% endif %}
|
||||||
{% if messages %}
|
</ul>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% with messages = get_flashed_messages() %}
|
||||||
|
{% if messages %}
|
||||||
<ul>
|
<ul>
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<li>{{ message }}</li>
|
<li>{{ message }}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
<div style="width: 280px;"></div>
|
<div style="width: 280px;"></div>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row my-3"></div>
|
<div class="row my-3"></div>
|
||||||
<div class="row-md-3">
|
<div class="row-md-3">
|
||||||
{% block content %}{% endblock %}
|
{% block app_content %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
<div class="row my-3"></div>
|
<div class="row my-3"></div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
<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>
|
<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>
|
||||||
</html>
|
<!-- <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> -->
|
||||||
|
{% endblock %}
|
||||||
6
app/templates/errors/404.html
Normal file
6
app/templates/errors/404.html
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
<h1>File Not Found</h1>
|
||||||
|
<p><a href="{{ url_for('main.index') }}">Back</a></p>
|
||||||
|
{% endblock %}
|
||||||
7
app/templates/errors/500.html
Normal file
7
app/templates/errors/500.html
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
<h1>An unexpected error has occurred</h1>
|
||||||
|
<p>The administrator has been notified. Sorry for the inconvenience!</p>
|
||||||
|
<p><a href="{{ url_for('main.index') }}">Back</a></p>
|
||||||
|
{% endblock %}
|
||||||
@ -1,24 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>Sign In</h1>
|
|
||||||
<form action="" method="post" novalidate>
|
|
||||||
{{ form.hidden_tag() }}
|
|
||||||
<p>
|
|
||||||
{{ form.username.label }}<br>
|
|
||||||
{{ form.username(size=32) }}
|
|
||||||
{% for error in form.username.errors %}
|
|
||||||
<span style="color: red;">[{{ error }}]</span>
|
|
||||||
{% endfor %}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{{ form.password.label }}<br>
|
|
||||||
{{ form.password(size=32) }}
|
|
||||||
{% for error in form.password.errors %}
|
|
||||||
<span style="color: red;">[{{ error }}]</span>
|
|
||||||
{% endfor %}
|
|
||||||
</p>
|
|
||||||
<p>{{ form.remember_me() }} {{ form.remember_me.label }}</p>
|
|
||||||
<p>{{ form.submit() }}</p>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block app_content %}
|
||||||
<form action="" method="post">
|
<form action="" method="post">
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
<div class="row">{{form.id.label}}</div>
|
<div class="row">{{form.id.label}}</div>
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block 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('check_unregistered_items', establishment=establishment.id) }}'">
|
||||||
@ -1,37 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>Register</h1>
|
|
||||||
<form action="" method="post">
|
|
||||||
{{ form.hidden_tag() }}
|
|
||||||
<p>
|
|
||||||
{{ form.username.label }}<br>
|
|
||||||
{{ form.username(size=32) }}<br>
|
|
||||||
{% for error in form.username.errors %}
|
|
||||||
<span style="color: red;">[{{ error }}]</span>
|
|
||||||
{% endfor %}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{{ form.email.label }}<br>
|
|
||||||
{{ form.email(size=64) }}<br>
|
|
||||||
{% for error in form.email.errors %}
|
|
||||||
<span style="color: red;">[{{ error }}]</span>
|
|
||||||
{% endfor %}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{{ form.password.label }}<br>
|
|
||||||
{{ form.password(size=32) }}<br>
|
|
||||||
{% for error in form.password.errors %}
|
|
||||||
<span style="color: red;">[{{ error }}]</span>
|
|
||||||
{% endfor %}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{{ form.password2.label }}<br>
|
|
||||||
{{ form.password2(size=32) }}<br>
|
|
||||||
{% for error in form.password2.errors %}
|
|
||||||
<span style="color: red;">[{{ error }}]</span>
|
|
||||||
{% endfor %}
|
|
||||||
</p>
|
|
||||||
<p>{{ form.submit() }}</p>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
1
app/utils/constants.py
Normal file
1
app/utils/constants.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
APPNAME = "scan2kasse"
|
||||||
5
boot.sh
Normal file
5
boot.sh
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
source venv/bin/activate
|
||||||
|
flask db upgrade
|
||||||
|
flask translate compile
|
||||||
|
python run.py
|
||||||
@ -31,12 +31,10 @@ def upgrade():
|
|||||||
)
|
)
|
||||||
op.create_table('user',
|
op.create_table('user',
|
||||||
sa.Column('id', sa.BigInteger(), nullable=False),
|
sa.Column('id', sa.BigInteger(), nullable=False),
|
||||||
sa.Column('email', sa.String(length=64), nullable=False),
|
sa.Column('email', sa.String(length=255), nullable=False),
|
||||||
sa.Column('username', sa.String(length=64), nullable=False),
|
|
||||||
sa.Column('password_hash', sa.String(length=128), nullable=False),
|
sa.Column('password_hash', sa.String(length=128), nullable=False),
|
||||||
sa.PrimaryKeyConstraint('id'),
|
sa.PrimaryKeyConstraint('id'),
|
||||||
sa.UniqueConstraint('email'),
|
sa.UniqueConstraint('email')
|
||||||
sa.UniqueConstraint('username')
|
|
||||||
)
|
)
|
||||||
op.create_table('establishment',
|
op.create_table('establishment',
|
||||||
sa.Column('id', sa.BigInteger(), nullable=False),
|
sa.Column('id', sa.BigInteger(), nullable=False),
|
||||||
@ -45,11 +43,22 @@ def upgrade():
|
|||||||
sa.ForeignKeyConstraint(['owner'], ['user.id'], ),
|
sa.ForeignKeyConstraint(['owner'], ['user.id'], ),
|
||||||
sa.PrimaryKeyConstraint('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',
|
op.create_table('receipt',
|
||||||
sa.Column('id', sa.Numeric(precision=22, scale=0), nullable=False),
|
sa.Column('id', sa.Numeric(precision=22, scale=0), 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('registered', sa.Boolean(), server_default='False', nullable=False),
|
sa.Column('registered', sa.Boolean(), server_default='False', nullable=False),
|
||||||
sa.Column('paid', sa.SmallInteger(), server_default='0', nullable=False),
|
sa.ForeignKeyConstraint(['from_user'], ['login_token.token'], ),
|
||||||
sa.PrimaryKeyConstraint('id')
|
sa.PrimaryKeyConstraint('id')
|
||||||
)
|
)
|
||||||
op.create_table('item',
|
op.create_table('item',
|
||||||
@ -60,15 +69,6 @@ def upgrade():
|
|||||||
sa.ForeignKeyConstraint(['brand'], ['brand.id'], ),
|
sa.ForeignKeyConstraint(['brand'], ['brand.id'], ),
|
||||||
sa.PrimaryKeyConstraint('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.ForeignKeyConstraint(['establishment'], ['establishment.id'], ),
|
|
||||||
sa.ForeignKeyConstraint(['user'], ['user.id'], ),
|
|
||||||
sa.PrimaryKeyConstraint('user', 'establishment'),
|
|
||||||
sa.UniqueConstraint('token')
|
|
||||||
)
|
|
||||||
op.create_table('amount_change',
|
op.create_table('amount_change',
|
||||||
sa.Column('item', sa.BigInteger(), nullable=False),
|
sa.Column('item', sa.BigInteger(), nullable=False),
|
||||||
sa.Column('date', sa.Date(), server_default='2021-12-01', nullable=False),
|
sa.Column('date', sa.Date(), server_default='2021-12-01', nullable=False),
|
||||||
@ -82,7 +82,6 @@ def upgrade():
|
|||||||
sa.Column('date', sa.Date(), nullable=False),
|
sa.Column('date', sa.Date(), nullable=False),
|
||||||
sa.Column('amount', sa.SmallInteger(), nullable=False),
|
sa.Column('amount', sa.SmallInteger(), nullable=False),
|
||||||
sa.Column('registered', sa.Boolean(), server_default='False', nullable=False),
|
sa.Column('registered', sa.Boolean(), server_default='False', nullable=False),
|
||||||
sa.Column('paid', sa.SmallInteger(), server_default='0', nullable=False),
|
|
||||||
sa.ForeignKeyConstraint(['item'], ['item.id'], ),
|
sa.ForeignKeyConstraint(['item'], ['item.id'], ),
|
||||||
sa.ForeignKeyConstraint(['token'], ['login_token.token'], ),
|
sa.ForeignKeyConstraint(['token'], ['login_token.token'], ),
|
||||||
sa.PrimaryKeyConstraint('token', 'item', 'date')
|
sa.PrimaryKeyConstraint('token', 'item', 'date')
|
||||||
@ -119,11 +118,11 @@ def downgrade():
|
|||||||
op.drop_table('item_category')
|
op.drop_table('item_category')
|
||||||
op.drop_table('bought')
|
op.drop_table('bought')
|
||||||
op.drop_table('amount_change')
|
op.drop_table('amount_change')
|
||||||
op.drop_table('login_token')
|
|
||||||
op.drop_table('item')
|
op.drop_table('item')
|
||||||
op.drop_table('user')
|
|
||||||
op.drop_table('receipt')
|
op.drop_table('receipt')
|
||||||
|
op.drop_table('login_token')
|
||||||
op.drop_table('establishment')
|
op.drop_table('establishment')
|
||||||
|
op.drop_table('user')
|
||||||
op.drop_table('category')
|
op.drop_table('category')
|
||||||
op.drop_table('brand')
|
op.drop_table('brand')
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
4
run.py
4
run.py
@ -1,7 +1,9 @@
|
|||||||
from app import app, db
|
from app import create_app, db
|
||||||
from app.models import *
|
from app.models import *
|
||||||
from gevent.pywsgi import WSGIServer
|
from gevent.pywsgi import WSGIServer
|
||||||
|
|
||||||
|
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,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user