major: upload and storage of recipes
PDF-Recipes are uploadable and some basic functions are already implemented. Also minor bugfixes.
This commit is contained in:
parent
b997e25a08
commit
764738b20d
@ -45,11 +45,13 @@ def create_app(config_class=Config):
|
|||||||
from app.auth import bp as auth_bp
|
from app.auth import bp as auth_bp
|
||||||
app.register_blueprint(auth_bp, url_prefix='/auth')
|
app.register_blueprint(auth_bp, url_prefix='/auth')
|
||||||
from app.errors import bp as errors_bp
|
from app.errors import bp as errors_bp
|
||||||
app.register_blueprint(errors_bp)
|
app.register_blueprint(errors_bp, url_prefix='/error')
|
||||||
from app.main import bp as main_bp
|
from app.main import bp as main_bp
|
||||||
app.register_blueprint(main_bp)
|
app.register_blueprint(main_bp)
|
||||||
from app.api import bp as api_bp
|
from app.api import bp as api_bp
|
||||||
app.register_blueprint(api_bp, url_prefix="/api")
|
app.register_blueprint(api_bp, url_prefix="/api")
|
||||||
|
from app.receipts import bp as receipts_bp
|
||||||
|
app.register_blueprint(receipts_bp, url_prefix='/receipts')
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
from app.models import User
|
from app.models import User
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import StringField, PasswordField, BooleanField, SubmitField
|
from wtforms import BooleanField, PasswordField, StringField, SubmitField
|
||||||
from wtforms.validators import DataRequired, Email, EqualTo, ValidationError
|
from wtforms.validators import DataRequired, Email, EqualTo, ValidationError
|
||||||
|
|
||||||
class LoginForm(FlaskForm):
|
class LoginForm(FlaskForm):
|
||||||
|
|||||||
@ -8,7 +8,7 @@ from flask import flash, redirect, request, url_for
|
|||||||
from flask_login import current_user, login_user, logout_user
|
from flask_login import current_user, login_user, logout_user
|
||||||
from werkzeug.urls import url_parse
|
from werkzeug.urls import url_parse
|
||||||
|
|
||||||
@bp.route(f'/register', methods=['GET', 'POST'])
|
@bp.route('/register', methods=['GET', 'POST'])
|
||||||
def web_register():
|
def web_register():
|
||||||
if current_user.is_authenticated:
|
if current_user.is_authenticated:
|
||||||
return redirect(url_for('main.index'))
|
return redirect(url_for('main.index'))
|
||||||
@ -22,7 +22,7 @@ def web_register():
|
|||||||
return redirect(url_for('auth.web_login'))
|
return redirect(url_for('auth.web_login'))
|
||||||
return render_template('auth/register.html', title='Register', form=form)
|
return render_template('auth/register.html', title='Register', form=form)
|
||||||
|
|
||||||
@bp.route(f'/login', methods=['GET', 'POST'])
|
@bp.route('/login', methods=['GET', 'POST'])
|
||||||
def web_login():
|
def web_login():
|
||||||
if current_user.is_authenticated:
|
if current_user.is_authenticated:
|
||||||
return redirect(url_for('main.index'))
|
return redirect(url_for('main.index'))
|
||||||
@ -39,7 +39,7 @@ def web_login():
|
|||||||
return redirect(next_page)
|
return redirect(next_page)
|
||||||
return render_template('auth/login.html', title='Sign In', form=form)
|
return render_template('auth/login.html', title='Sign In', form=form)
|
||||||
|
|
||||||
@bp.route(f'/logout')
|
@bp.route('/logout')
|
||||||
def web_logout():
|
def web_logout():
|
||||||
logout_user()
|
logout_user()
|
||||||
return redirect(url_for('main.index'))
|
return redirect(url_for('main.index'))
|
||||||
|
|||||||
@ -2,6 +2,10 @@ from app import db
|
|||||||
from app.errors import bp
|
from app.errors import bp
|
||||||
from flask import render_template
|
from flask import render_template
|
||||||
|
|
||||||
|
@bp.app_errorhandler(403)
|
||||||
|
def not_allowed_error(error):
|
||||||
|
return render_template('errors/403.html'), 403
|
||||||
|
|
||||||
@bp.app_errorhandler(404)
|
@bp.app_errorhandler(404)
|
||||||
def not_found_error(error):
|
def not_found_error(error):
|
||||||
return render_template('errors/404.html'), 404
|
return render_template('errors/404.html'), 404
|
||||||
|
|||||||
@ -12,7 +12,7 @@ class NewItemForm(FlaskForm):
|
|||||||
amount_change = IntegerField("Amount", validators=[Optional()])
|
amount_change = IntegerField("Amount", validators=[Optional()])
|
||||||
category = SelectMultipleField("Categories", choices=[], validators=[Optional()])
|
category = SelectMultipleField("Categories", choices=[], validators=[Optional()])
|
||||||
brand = SelectField("Brand", choices=[], validators=[DataRequired()])
|
brand = SelectField("Brand", choices=[], validators=[DataRequired()])
|
||||||
submit = SubmitField("Submit")
|
submit = SubmitField("Submit", render_kw={"class": "btn btn-primary mt-3"})
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def new(cls):
|
def new(cls):
|
||||||
|
|||||||
@ -52,9 +52,12 @@ def get_report_from_user():
|
|||||||
def token_authorization():
|
def token_authorization():
|
||||||
LOGGER.debug("Token Login")
|
LOGGER.debug("Token Login")
|
||||||
if not request.json or 'login' not in request.json:
|
if not request.json or 'login' not in request.json:
|
||||||
|
LOGGER.debug("JSON not delivered or 'login' not in JSON")
|
||||||
abort(400)
|
abort(400)
|
||||||
if not LoginToken.query.filter_by(token=request.json['login']).first():
|
if not LoginToken.query.filter_by(token=request.json['login']).first():
|
||||||
|
LOGGER.debug(f"Token <{request.json['login']}> not recognized")
|
||||||
abort(403)
|
abort(403)
|
||||||
|
LOGGER.debug("Token accepted")
|
||||||
return jsonify({}), 200
|
return jsonify({}), 200
|
||||||
|
|
||||||
@bp.route('/token_insert', methods=['POST'])
|
@bp.route('/token_insert', methods=['POST'])
|
||||||
@ -94,7 +97,7 @@ def new_item():
|
|||||||
new_item.AmountChange = [AmountChange(Item = new_item, date = date(2021, 12, 1), amount = 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.add(new_item)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return redirect(url_for('index'))
|
return redirect(url_for('main.index'))
|
||||||
return render_template('main/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'])
|
||||||
|
|||||||
@ -58,9 +58,11 @@ class Establishment(db.Model):
|
|||||||
class LoginToken(db.Model):
|
class LoginToken(db.Model):
|
||||||
user = db.Column(db.ForeignKey('user.id'), primary_key=True, server_onupdate=db.FetchedValue())
|
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())
|
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=False, unique=True)
|
||||||
paid = db.Column(db.BigInteger, nullable=False, server_default=str(0))
|
paid = db.Column(db.BigInteger, nullable=False, server_default=str(0))
|
||||||
|
|
||||||
|
Receipt = db.relationship("Receipt", backref='LoginToken', lazy='dynamic')
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"LoginToken {self.token}"
|
return f"LoginToken {self.token}"
|
||||||
|
|
||||||
@ -125,6 +127,8 @@ class Receipt(db.Model):
|
|||||||
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))
|
||||||
|
|
||||||
|
ItemReceipt = db.relationship("ItemReceipt", backref='Receipt', lazy='dynamic')
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<Receipt {self.id}>"
|
return f"<Receipt {self.id}>"
|
||||||
|
|||||||
5
app/receipts/__init__.py
Normal file
5
app/receipts/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from flask import Blueprint
|
||||||
|
|
||||||
|
bp = Blueprint('receipts', __name__)
|
||||||
|
|
||||||
|
from app.receipts import forms, routes
|
||||||
16
app/receipts/forms.py
Normal file
16
app/receipts/forms.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from flask_wtf.file import FileAllowed, FileField, FileRequired
|
||||||
|
from wtforms import BooleanField, SelectMultipleField, SubmitField, widgets
|
||||||
|
from wtforms.validators import Optional
|
||||||
|
|
||||||
|
class MultiCheckboxField(SelectMultipleField):
|
||||||
|
widget = widgets.ListWidget(prefix_label=False)
|
||||||
|
option_widget = widgets.CheckboxInput()
|
||||||
|
|
||||||
|
class UploadReceiptForm(FlaskForm):
|
||||||
|
pdfReceipt = FileField("PDF", validators=[FileRequired(), FileAllowed(["pdf"], "Invalid Format, must be .pdf")])
|
||||||
|
submit = SubmitField("Submit", render_kw={"class": "btn btn-primary mt-3"})
|
||||||
|
|
||||||
|
class CheckItemsForm(FlaskForm):
|
||||||
|
items = MultiCheckboxField("Items")
|
||||||
|
submit = SubmitField("Submit", render_kw={"class": "btn btn-primary mt-3"})
|
||||||
61
app/receipts/routes.py
Normal file
61
app/receipts/routes.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
from app import db, LOGGER
|
||||||
|
from app.receipts import bp
|
||||||
|
from app.receipts.forms import CheckItemsForm, UploadReceiptForm
|
||||||
|
from app.models import Receipt, LoginToken
|
||||||
|
from app.utils.routes_utils import render_custom_template as render_template
|
||||||
|
from flask import abort, request, url_for
|
||||||
|
from flask_login import current_user, login_required
|
||||||
|
from app.utils.pdf_receipt_parser import PDFReceipt
|
||||||
|
|
||||||
|
PDFDir = "./"
|
||||||
|
|
||||||
|
@bp.route('/upload_receipt', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
def upload_receipt():
|
||||||
|
"""Upload of a receipt."""
|
||||||
|
if current_user.is_anonymous:
|
||||||
|
abort(403)
|
||||||
|
if "establishment" in request.args:
|
||||||
|
if LoginToken.query.filter_by(establishment=request.args['establishment'], user=current_user.id).first():
|
||||||
|
form = UploadReceiptForm()
|
||||||
|
LOGGER.debug(form.pdfReceipt.data)
|
||||||
|
if form.is_submitted():
|
||||||
|
LOGGER.debug("submitted")
|
||||||
|
if form.validate():
|
||||||
|
LOGGER.debug("valid")
|
||||||
|
else:
|
||||||
|
LOGGER.debug(form.errors)
|
||||||
|
if form.validate_on_submit():
|
||||||
|
receipt = PDFReceipt(form.pdfReceipt.data)
|
||||||
|
dbReceipt = Receipt(id = receipt.id, date = receipt.date,
|
||||||
|
from_user = LoginToken.query.filter_by(establishment=request.args['establishment'], user=current_user.id).first().token)
|
||||||
|
form.pdfReceipt.data.save(PDFDir + f"{str(receipt.date)}_{receipt.id}.pdf")
|
||||||
|
db.session.add(dbReceipt)
|
||||||
|
db.session.commit()
|
||||||
|
return receipt.text.replace("\n", "<br>")
|
||||||
|
return render_template("receipts/upload.html", form = form)
|
||||||
|
abort(403)
|
||||||
|
|
||||||
|
@bp.route('/confirm_receipt', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
def confirm_receipt_items():
|
||||||
|
"""Check items from a receipt if they should be accounted for payment."""
|
||||||
|
if "receipt" in request.args:
|
||||||
|
receipt_details = Receipt.query.get(request.args['receipt'])
|
||||||
|
if current_user.is_anonymous and current_user.id == receipt_details.LoginToken.Establishment.owner:
|
||||||
|
receipt = PDFReceipt._getPDFReceiptFromFile(PDFDir + f"{receipt.date}_{receipt.id}.pdf")
|
||||||
|
form = CheckItemsForm()
|
||||||
|
# TODO: Precheck if items are already in database. If yes, check if item is present only once or multiple
|
||||||
|
# times and provide dropdown menu if necessary. If not, provide input field.
|
||||||
|
temp_choices = []
|
||||||
|
for item in receipt.items:
|
||||||
|
match item:
|
||||||
|
case {"itemname": itemname, "price": price}:
|
||||||
|
temp_choices.append((itemname.replace(" ", "_"), f"{itemname, price}"))
|
||||||
|
case {"itemname": itemname, "price": price, "amount": amount}:
|
||||||
|
temp_choices.append((itemname.replace(" ", "_"), f"{itemname}, {price} * {amount}"))
|
||||||
|
form.choices = temp_choices
|
||||||
|
if form.validate_on_submit():
|
||||||
|
pass # TODO
|
||||||
|
return render_template("receipts/confirm_items.html")
|
||||||
|
abort(403)
|
||||||
@ -5,16 +5,10 @@
|
|||||||
<h1>Sign In</h1>
|
<h1>Sign In</h1>
|
||||||
<form action="" method="post" novalidate>
|
<form action="" method="post" novalidate>
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
<p>
|
{{ wtf.form_field(form.email, class=form.email.render_kw["class"] or "form-control mb-3") }}
|
||||||
{{ wtf.form_field(form.email) }}
|
{{ wtf.form_field(form.password, class=form.password.render_kw["class"] or "form-control mb-3") }}
|
||||||
</p>
|
{{ wtf.form_field(form.remember_me, class=form.remember_me.render_kw["class"] or "mb-3") }}
|
||||||
<p>
|
{{ wtf.form_field(form.submit, class=form.submit.render_kw["class"] or "btn btn-primary mt-3") }}
|
||||||
{{ 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>
|
</form>
|
||||||
<p>
|
<p>
|
||||||
Forgot Your Password?
|
Forgot Your Password?
|
||||||
|
|||||||
@ -5,15 +5,9 @@
|
|||||||
<h1>Register</h1>
|
<h1>Register</h1>
|
||||||
<form action="" method="post">
|
<form action="" method="post">
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
<p>
|
{{ wtf.form_field(form.email, class=form.email.render_kw["class"] or "form-control mb-3") }}
|
||||||
{{ wtf.form_field(form.email) }}
|
{{ wtf.form_field(form.password, class=form.password.render_kw["class"] or "form-control mb-3") }}
|
||||||
</p>
|
{{ wtf.form_field(form.password2, class=form.password2.render_kw["class"] or "form-control mb-3") }}
|
||||||
<p>
|
{{ wtf.form_field(form.submit, class=form.submit.render_kw["class"] or "btn btn-primary mt-3") }}
|
||||||
{{ 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>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -45,9 +45,18 @@
|
|||||||
<div class="collapse" id="dashboard-collapse">
|
<div class="collapse" id="dashboard-collapse">
|
||||||
<ul class="btn-toggle-nav list-unstyled fw-normal pb-1 small">
|
<ul class="btn-toggle-nav list-unstyled fw-normal pb-1 small">
|
||||||
{% if establishments %}
|
{% if establishments %}
|
||||||
<li><a href="#" class="link-dark rounded">Allgemein</a></li>
|
<!-- <li><a href="#" class="link-dark rounded">Allgemein</a></li> -->
|
||||||
{% for establishment in establishments %}
|
{% for establishment in establishments %}
|
||||||
<li><a href="{{ url_for('main.get_report_from_user', establishment=establishment.id) }}" class="link-dark rounded">{{ establishment.name }}</a></li>
|
<li>
|
||||||
|
<button class="btn btn-toggle align-items-center rounded collapsed" data-bs-toggle="collapse" data-bs-target="#est_{{ establishment.id }}" aria-expanded="false">
|
||||||
|
{{ establishment.name }}
|
||||||
|
</button>
|
||||||
|
<div class="collapse" id="est_{{ establishment.id }}">
|
||||||
|
<ul class="btn-toggle-nav list-unstyled fw-normal pb-1 small">
|
||||||
|
<li><a href="{{ url_for('main.get_report_from_user', establishment=establishment.id) }}" class="link-dark rounded">Übersicht</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
7
app/templates/errors/403.html
Normal file
7
app/templates/errors/403.html
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
<h1>You are not allowed to do this!</h1>
|
||||||
|
<h4>how dare you</h4>
|
||||||
|
<p><a href="{{ url_for('main.index') }}">Back</a></p>
|
||||||
|
{% endblock %}
|
||||||
@ -1,24 +1,17 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
{% block app_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>
|
{{ wtf.form_field(form.id, class=form.id.render_kw["class"] or "form-control mb-3") }}
|
||||||
<div class="row">{{form.id()}}</div>
|
{{ wtf.form_field(form.name, class=form.name.render_kw["class"] or "form-control mb-3") }}
|
||||||
<div class="row">{{form.name.label}}</div>
|
{{ wtf.form_field(form.description, class=form.description.render_kw["class"] or "form-control mb-3") }}
|
||||||
<div class="row">{{form.name()}}</div>
|
{{ wtf.form_field(form.date, class=form.date.render_kw["class"] or "form-control mb-3") }}
|
||||||
<div class="row">{{form.description.label}}</div>
|
{{ wtf.form_field(form.price_change, class=form.price_change.render_kw["class"] or "form-control mb-3") }}
|
||||||
<div class="row">{{form.description()}}</div>
|
{{ wtf.form_field(form.amount_change, class=form.amount_change.render_kw["class"] or "form-control mb-3") }}
|
||||||
<div class="row">{{form.date.label}}</div>
|
{{ wtf.form_field(form.category, class=form.category.render_kw["class"] or "form-control mb-3") }}
|
||||||
<div class="row">{{form.date()}}</div>
|
{{ wtf.form_field(form.brand, class=form.brand.render_kw["class"] or "form-control mb-3") }}
|
||||||
<div class="row">{{form.price_change.label}}</div>
|
{{ wtf.form_field(form.submit, class=form.submit.render_kw["class"] or "btn btn-primary mt-3") }}
|
||||||
<div class="row">{{form.price_change()}}</div>
|
|
||||||
<div class="row">{{form.amount_change.label}}</div>
|
|
||||||
<div class="row">{{form.amount_change()}}</div>
|
|
||||||
<div class="row">{{form.category.label}}</div>
|
|
||||||
<div class="row">{{form.category()}}</div>
|
|
||||||
<div class="row">{{form.brand.label}}</div>
|
|
||||||
<div class="row">{{form.brand()}}</div>
|
|
||||||
<div class="row">{{form.submit()}}</div>
|
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
9
app/templates/receipts/confirm_items.html
Normal file
9
app/templates/receipts/confirm_items.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
<form action="" method="post" novalidate enctype="multipart/form-data">
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
{{ wtf.form_field(form.submit, class=form.submit.render_kw["class"] or "btn btn-primary mt-3") }}
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
10
app/templates/receipts/upload.html
Normal file
10
app/templates/receipts/upload.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
<form action="" method="post" novalidate enctype="multipart/form-data">
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
{{ wtf.form_field(form.pdfReceipt, class=form.pdfReceipt.render_kw["class"] or "form-control") }}
|
||||||
|
{{ wtf.form_field(form.submit, class=form.submit.render_kw["class"] or "btn btn-primary mt-3") }}
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
54
app/utils/pdf_receipt_parser.py
Normal file
54
app/utils/pdf_receipt_parser.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
import fitz
|
||||||
|
from re import search
|
||||||
|
|
||||||
|
class PDFReceipt:
|
||||||
|
"""Class to use a PDF-Receipt as an object.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
strPDFFile -- The path to the PDF-File as a string.
|
||||||
|
parser -- A keyword in lowercase to tell how the receipt is formated.
|
||||||
|
Currently supported: 'edeka'
|
||||||
|
"""
|
||||||
|
def __init__(self, bPDFFile, parser: str = "edeka") -> None:
|
||||||
|
self.text = PDFReceipt._getTextFromPDF(bPDFFile)
|
||||||
|
self.id, self.date, self.items = PDFReceipt._getInfosFromText(self.text, parser)
|
||||||
|
|
||||||
|
def _getTextFromPDF(file):
|
||||||
|
with fitz.open("pdf", file) as doc:
|
||||||
|
text = ""
|
||||||
|
for page in doc:
|
||||||
|
text += page.get_text()
|
||||||
|
return text.strip()
|
||||||
|
|
||||||
|
def _getItemsTextFromText(text, start="", end=""):
|
||||||
|
return text[text.index(start)+len(start):text.index(end)].strip()
|
||||||
|
|
||||||
|
def _convertItemsTextToDict(text):
|
||||||
|
temp = text.split("\n")
|
||||||
|
resultsArr = []
|
||||||
|
i = 0
|
||||||
|
while i < len(temp):
|
||||||
|
if search("(\d+) x", temp[i]):
|
||||||
|
resultsArr.append({"itemname": temp[i+2], "price": temp[i+1], "amount": temp[i][:-2]})
|
||||||
|
i += 4
|
||||||
|
else:
|
||||||
|
resultsArr.append({"itemname": temp[i], "price": temp[i+1][:-2]})
|
||||||
|
i += 2
|
||||||
|
return resultsArr
|
||||||
|
|
||||||
|
def _getInfosFromText(text: str, parser: str = "edeka"):
|
||||||
|
if parser.lower() == "edeka":
|
||||||
|
items = PDFReceipt._convertItemsTextToDict(PDFReceipt._getItemsTextFromText(text, "EUR", "----------"))
|
||||||
|
strDate = text.split("\n")[-1].split(" ")[0]
|
||||||
|
date = datetime.strptime(strDate, "%d.%m.%y").date()
|
||||||
|
strReceiptNumber = text.split("\n")[-1].split(" ")[-1]
|
||||||
|
try:
|
||||||
|
intReceiptNumber = int(strReceiptNumber)
|
||||||
|
except:
|
||||||
|
raise ValueError("Receipt Number not an integer.")
|
||||||
|
return (intReceiptNumber, date, items)
|
||||||
|
|
||||||
|
def getPDFReceiptFromFile(strPDFFile: str, parser: str = "edeka"):
|
||||||
|
with open(strPDFFile) as doc:
|
||||||
|
return PDFReceipt(doc, parser)
|
||||||
@ -6,7 +6,7 @@ load_dotenv(os.path.join(basedir, '.env'))
|
|||||||
|
|
||||||
|
|
||||||
class Config(object):
|
class Config(object):
|
||||||
SECRET_KEY = os.environ.get('SECRET_KEY') or "MY_5€cr37_K€Y"
|
SECRET_KEY = os.environ.get('SECRET_KEY') or "s0m37h!n6-obfu5c471ng"
|
||||||
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL', '').replace(
|
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL', '').replace(
|
||||||
'postgres://', 'postgresql://') or \
|
'postgres://', 'postgresql://') or \
|
||||||
(f"postgresql://{os.environ.get('DATABASE_USER', 'scan2kasse')}:{os.environ.get('DATABASE_PASS', 'asdf1337')}"
|
(f"postgresql://{os.environ.get('DATABASE_USER', 'scan2kasse')}:{os.environ.get('DATABASE_PASS', 'asdf1337')}"
|
||||||
|
|||||||
@ -2,19 +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]
|
||||||
level = DEBUG
|
handlers = void
|
||||||
|
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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user