diff options
Diffstat (limited to 'app')
-rw-r--r-- | app/main.py | 75 | ||||
-rw-r--r-- | app/model.py | 161 | ||||
-rw-r--r-- | app/models.py | 260 | ||||
-rw-r--r-- | app/modules/__init__.py | 8 | ||||
-rw-r--r-- | app/modules/auth/forms.py | 29 | ||||
-rw-r--r-- | app/modules/auth/routes.py | 73 | ||||
-rw-r--r-- | app/modules/common.py | 75 | ||||
-rw-r--r-- | app/modules/customers/forms.py | 36 | ||||
-rw-r--r-- | app/modules/customers/routes.py | 38 | ||||
-rw-r--r-- | app/modules/ferti/forms.py | 47 | ||||
-rw-r--r-- | app/modules/ferti/routes.py | 46 | ||||
-rw-r--r-- | app/modules/forms.py | 6 | ||||
-rw-r--r-- | app/modules/forms.py.bkp (renamed from app/forms.py) | 51 | ||||
-rw-r--r-- | app/modules/invoices/forms.py | 33 | ||||
-rw-r--r-- | app/modules/invoices/routes.py | 47 | ||||
-rw-r--r-- | app/modules/modules.py.bkp | 136 | ||||
-rw-r--r-- | app/modules/orders/forms.py | 35 | ||||
-rw-r--r-- | app/modules/orders/routes.py | 30 | ||||
-rw-r--r-- | app/modules/products/forms.py | 23 | ||||
-rw-r--r-- | app/modules/products/routes.py | 26 | ||||
-rw-r--r-- | app/routes.py | 135 |
21 files changed, 1061 insertions, 309 deletions
diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..ac5625f --- /dev/null +++ b/app/main.py @@ -0,0 +1,75 @@ +# -*- mode: python; -*- + +""" +routes.py module +---------------- + +This Python module contains the logic supporting: +1. Navigating between website pages +2. Interpreting user requests to the server +3. Dispatching requested content back to the user + +Python dependencies: +- flask: provides web application features +- forms: provides secure user form submission +- sqlalchemy: provides communication with database on server. + +Personal imports: +These are used to avoid cluttering this file with +placeholder data for posts' content. +""" + + +from flask import ( + Blueprint, + render_template, + send_file, + request, + redirect, + flash, + url_for, + jsonify, + abort, +) +from flask_login import login_required, current_user + + +# from datetime import datetime +# import inspect + +from . import db + +main = Blueprint("main", __name__) + +from .models import Module, User + +modules = [ + "auth", + "customers", + "ferti", + "invoices", + "orders", + "products", +] + + +@main.route("/") +@main.route("/index") +def home(): + """This is our project home page.""" + modules = Module.query.all() + return render_template( + "home.html", user=current_user, project="fapg", modules=modules + ) + + +@main.route("/download-database") +def download_database(): + return send_file("fapg.db") + + +@main.route("/modules") +def all_modules(): + # If unlogged, should present promotional material for available + # modules. + return render_template("modules-promo.html", modules=modules) diff --git a/app/model.py b/app/model.py deleted file mode 100644 index 1cf59d8..0000000 --- a/app/model.py +++ /dev/null @@ -1,161 +0,0 @@ -# import SQLAlchemy -from flask_sqlalchemy import SQLAlchemy -from datetime import datetime - -db = SQLAlchemy() - - -class Module(db.Model): - name = db.Column( - "Name", db.String(20), unique=True, nullable=False, primary_key=True - ) - description = db.Column("Description", db.String(50), nullable=False) - - def __repr__(self): - return f"<Module {self.name}>" - - -class Customer(db.Model): - primary_key = db.Column("CustomerId", db.Integer, nullable=False, primary_key=True) - name = db.Column("Name", db.String(20), nullable=False) - name_alternative = db.Column("NameAlternative", db.String(20)) - date_time_created = db.Column("DateTimeCreated", db.String(20), nullable=False) - date_time_updated = db.Column("DateTimeUpdated", db.String(20), nullable=False) - code_customer = db.Column("CodeCustomer", db.String(20), unique=True) - code_accounting = db.Column("CodeAccounting", db.String(20), unique=True) - address = db.Column("Address", db.String(20)) - postal_code = db.Column("PostalCode", db.Integer) - city = db.Column("City", db.String(20)) - country = db.Column("Country", db.String(20)) - phone = db.Column("Phone", db.String(20)) - website = db.Column("Website", db.String(20)) - email = db.Column("Email", db.String(20)) - professional_id_1 = db.Column("ProfessionalId1", db.String(20), unique=True) - professional_id_2 = db.Column("ProfessionalId2", db.String(20), unique=True) - tax_id = db.Column("TaxId", db.String(20), unique=True) - payment_terms = db.Column("PaymentTerms", db.String(20), unique=True) - - def __repr__(self): - return f"<Customer {self.name}>" - - -class Product(db.Model): - primary_key = db.Column("ProductId", db.Integer, primary_key=True) - name = db.Column("Name", db.String(20), nullable=False, unique=True) - code_accounting = db.Column("CodeAccounting", db.String(20)) - unit_weight = db.Column("UnitWeight", db.Float) - price_net = db.Column("PriceNet", db.Float) - price_gross = db.Column("PriceGross", db.Float) - tax_rate = db.Column("TaxRate", db.Float) - date_time_created = db.Column( - "DateTimeCreated", - db.String, - default=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), - ) - date_time_updated = db.Column( - "DateTimeUpdated", - db.String, - default=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), - ) - - def __init__( - self, - name, - code_accounting, - unit_weight, - price_net, - price_gross, - tax_rate, - ): - self.name = name - self.code_accounting = code_accounting - self.unit_weight = unit_weight - self.price_net = price_net - self.price_gross = price_gross - self.tax_rate = tax_rate - - def __repr__(self): - return f"<Product {self.name}>" - - -class Log(db.Model): - primary_key = db.Column("LogId", db.Integer, primary_key=True) - target = db.Column("Target", db.String, default="False") - nno3 = db.Column("NNO3", db.Float, default=0) - p = db.Column("P", db.Float, default=0) - k = db.Column("K", db.Float, default=0) - ca = db.Column("Ca", db.Float, default=0) - mg = db.Column("Mg", db.Float, default=0) - s = db.Column("S", db.Float, default=0) - na = db.Column("Na", db.Float, default=0) - cl = db.Column("Cl", db.Float, default=0) - fe = db.Column("Fe", db.Float, default=0) - zn = db.Column("Zn", db.Float, default=0) - b = db.Column("B", db.Float, default=0) - mn = db.Column("Mn", db.Float, default=0) - cu = db.Column("Cu", db.Float, default=0) - mo = db.Column("Mo", db.Float, default=0) - si = db.Column("Si", db.Float, default=0) - nnh4 = db.Column("NNH4", db.Float, default=0) - date_time_created = db.Column( - "DateTimeCreated", - db.String, - default=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), - ) - date_time_updated = db.Column( - "DateTimeUpdated", - db.String, - default=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), - ) - - def __init__( - self, target, nno3, p, k, ca, mg, s, na, cl, fe, zn, b, mn, cu, mo, si, nnh4 - ): - self.target = target - self.nno3 = nno3 - self.p = p - self.k = k - self.ca = ca - self.mg = mg - self.s = s - self.na = na - self.cl = cl - self.fe = fe - self.zn = zn - self.b = b - self.mn = mn - self.cu = cu - self.mo = mo - self.si = si - self.nnh4 = nnh4 - self.date_time_created = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - def __repr__(self): - return f"<Log from {self.date_time_created} updated {self.date_time_updated}>" - - -# Everything after here is garbage - - -class User(db.Model): - id = db.Column(db.Integer, primary_key=True) - name_first = db.Column(db.String(20), nullable=False) - name_last = db.Column(db.String(20), nullable=False) - email = db.Column(db.String(20), nullable=False) - phone_mobile = db.Column(db.Integer, nullable=False) - phone_alternative = db.Column(db.Integer) - updated = db.Column(db.String) - - def __repr__(self): - return f"<User {self.name_first} {self.name_last}>" - - -class Order(db.Model): # TODO - id = db.Column(db.Integer, primary_key=True) - date = db.Column(db.String(20), nullable=False) - customer = db.Column(db.String(20), nullable=False) - content = db.Column(db.String(10), nullable=False) - updated = db.Column(db.String) - - def __repr__(self): - return f"<Order {self.name} at {self.updated}>" diff --git a/app/models.py b/app/models.py new file mode 100644 index 0000000..507d135 --- /dev/null +++ b/app/models.py @@ -0,0 +1,260 @@ +# -*- mode: python; -*- + + +from flask_sqlalchemy import SQLAlchemy + +from datetime import datetime +from flask_login import UserMixin +from . import db + + +def date_time_now(): + return datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + +class User(UserMixin, db.Model): + __tablename__ = "User" + + def get_id(self): + return self.primary_key + + primary_key = db.Column("UserId", db.Integer, primary_key=True) + username = db.Column("Username", db.String(20), nullable=False) + hashed_password = db.Column("HashedPassword", db.String(100), nullable=False) + name_first = db.Column("NameFirst", db.String(20), nullable=False) + name_last = db.Column("NameLast", db.String(20), nullable=False) + email = db.Column("Email", db.String(20), nullable=False) + phone_mobile = db.Column("PhoneMobile", db.Integer) + phone_alternative = db.Column("PhoneAlternative", db.Integer) + date_time_created = db.Column( + "DateTimeCreated", db.String, server_default=date_time_now() + ) + date_time_updated = db.Column( + "DateTimeUpdated", db.String, server_onupdate=date_time_now() + ) + + def __repr__(self): + return f"<User {self.name_first} {self.name_last}>" + + +class Module(db.Model): + __tablename__ = "Module" + name = db.Column( + "Name", db.String(20), unique=True, nullable=False, primary_key=True + ) + description = db.Column("Description", db.String(50), nullable=False) + + def __repr__(self): + return f"<Module {self.name}>" + + +class Customer(db.Model): + __tablename__ = "Customer" + primary_key = db.Column("CustomerId", db.Integer, nullable=False, primary_key=True) + name = db.Column("Name", db.String(20), nullable=False) + name_alternative = db.Column("NameAlternative", db.String(20)) + code_customer = db.Column("CodeCustomer", db.String(20), unique=True) + code_accounting = db.Column("CodeAccounting", db.String(20), unique=True) + address = db.Column("Address", db.String(20)) + postal_code = db.Column("PostalCode", db.Integer) + city = db.Column("City", db.String(20)) + country = db.Column("Country", db.String(20)) + phone = db.Column("Phone", db.String(20)) + website = db.Column("Website", db.String(20)) + email = db.Column("Email", db.String(20)) + professional_id_1 = db.Column("ProfessionalId1", db.String(20), unique=True) + professional_id_2 = db.Column("ProfessionalId2", db.String(20), unique=True) + tax_id = db.Column("TaxId", db.String(20), unique=True) + payment_terms = db.Column("PaymentTerms", db.String(20), unique=True) + date_time_created = db.Column("DateTimeCreated", db.String(20), nullable=False) + date_time_updated = db.Column("DateTimeUpdated", db.String(20), nullable=False) + invoices = db.relationship("Invoice", back_populates="customer") + + def __init__( + self, + name, + name_alternative, + code_customer, + code_accounting, + address, + postal_code, + city, + country, + phone, + website, + email, + professional_id_1, + professional_id_2, + tax_id, + payment_terms, + ): + self.name = name + self.name_alternative = name_alternative + self.code_customer = code_customer + self.code_accounting = code_accounting + self.address = address + self.postal_code = postal_code + self.city = city + self.country = country + self.phone = phone + self.website = website + self.email = email + self.professional_id_1 = professional_id_1 + self.professional_id_2 = professional_id_2 + self.tax_id = tax_id + self.payment_terms = payment_terms + + def __repr__(self): + return f"<Customer {self.name}>" + + +class Invoice(db.Model): + __tablename__ = "Invoice" + primary_key = db.Column("InvoiceId", db.Integer, primary_key=True) + invoice_id_alt = db.Column("InvoiceIdAlt", db.String) + customer_reference = db.Column("CustomerReference", db.String) + date_billed = db.Column("DateBilled", db.String) + date_due = db.Column("DateDue", db.String) + amount_net = db.Column("AmountNet", db.String) + amount_gross = db.Column("AmountGross", db.String) + amount_tax = db.Column("AmountTax", db.String) + date_time_created = db.Column( + "DateTimeCreated", db.String, default=date_time_now() + ) + customer_id = db.Column( + "CustomerId", db.Integer, db.ForeignKey("Customer.CustomerId") + ) + customer = db.relationship("Customer", back_populates="invoices") + + def __init__( + self, + invoice_id_alt, + customer_id, + customer_reference, + date_billed, + date_due, + amount_net, + amount_gross, + amount_tax, + ): + self.invoice_id_alt = invoice_id_alt + self.customer_id = customer_id + self.customer_reference = customer_reference + self.date_billed = date_billed + self.date_due = date_due + self.amount_net = amount_net + self.amount_gross = amount_gross + self.amount_tax = amount_tax + + def __repr__(self): + return f"<Invoice for {self.customer.name} at {self.updated}>" + + +class Product(db.Model): + __tablename__ = "Product" + primary_key = db.Column("ProductId", db.Integer, primary_key=True) + name = db.Column("Name", db.String(20), nullable=False, unique=True) + code_accounting = db.Column("CodeAccounting", db.String(20)) + unit_weight = db.Column("UnitWeight", db.Float) + price_net = db.Column("PriceNet", db.Float) + price_gross = db.Column("PriceGross", db.Float) + tax_rate = db.Column("TaxRate", db.Float) + date_time_created = db.Column( + "DateTimeCreated", db.String, server_default=date_time_now() + ) + date_time_updated = db.Column( + "DateTimeUpdated", db.String, server_onupdate=date_time_now() + ) + + def __init__( + self, + name, + code_accounting, + unit_weight, + price_net, + price_gross, + tax_rate, + ): + self.name = name + self.code_accounting = code_accounting + self.unit_weight = unit_weight + self.price_net = price_net + self.price_gross = price_gross + self.tax_rate = tax_rate + + def __repr__(self): + return f"<Product {self.name}>" + + +class FertiLog(db.Model): + __tablename__ = "Fertilog" + primary_key = db.Column("FertiLogId", db.Integer, primary_key=True) + nno3 = db.Column("NNO3", db.Float, default=0) + p = db.Column("P", db.Float, default=0) + k = db.Column("K", db.Float, default=0) + ca = db.Column("Ca", db.Float, default=0) + mg = db.Column("Mg", db.Float, default=0) + s = db.Column("S", db.Float, default=0) + na = db.Column("Na", db.Float, default=0) + cl = db.Column("Cl", db.Float, default=0) + fe = db.Column("Fe", db.Float, default=0) + zn = db.Column("Zn", db.Float, default=0) + b = db.Column("B", db.Float, default=0) + mn = db.Column("Mn", db.Float, default=0) + cu = db.Column("Cu", db.Float, default=0) + mo = db.Column("Mo", db.Float, default=0) + si = db.Column("Si", db.Float, default=0) + nnh4 = db.Column("NNH4", db.Float, default=0) + date_time_created = db.Column( + "DateTimeCreated", db.String, server_default=date_time_now() + ) + date_time_updated = db.Column( + "DateTimeUpdated", db.String, server_onupdate=date_time_now() + ) + + def __init__(self, nno3, p, k, ca, mg, s, na, cl, fe, zn, b, mn, cu, mo, si, nnh4): + self.nno3 = nno3 + self.p = p + self.k = k + self.ca = ca + self.mg = mg + self.s = s + self.na = na + self.cl = cl + self.fe = fe + self.zn = zn + self.b = b + self.mn = mn + self.cu = cu + self.mo = mo + self.si = si + self.nnh4 = nnh4 + self.date_time_created = date_time_now + + def __repr__(self): + return f"<Log ID {self.primary_key}>" + + +class FertiTarget(FertiLog): + __tablename__ = "Fertitarget" + primary_key = db.Column("FertiTargetId", db.Integer, primary_key=True) + targeted_log = db.Column( + "FertiLogId", db.Integer, db.ForeignKey("Fertilog.FertiLogId") + ) + + def __repr__(self): + return f"<Target ID {self.primary_key}>" + + +# Everything after here is garbage + + +class Order(db.Model): # TODO + id = db.Column(db.Integer, primary_key=True) + date = db.Column(db.String(20), nullable=False) + customer = db.Column(db.String(20), nullable=False) + content = db.Column(db.String(10), nullable=False) + updated = db.Column(db.String) + + def __repr__(self): + return f"<Order {self.name} at {self.updated}>" diff --git a/app/modules/__init__.py b/app/modules/__init__.py new file mode 100644 index 0000000..56f1d5a --- /dev/null +++ b/app/modules/__init__.py @@ -0,0 +1,8 @@ +# -*- mode: python; -*- + +from .common import common +from .auth.routes import auth +from .products.routes import products +from .customers.routes import customers +from .ferti.routes import ferti +from .invoices.routes import invoices diff --git a/app/modules/auth/forms.py b/app/modules/auth/forms.py new file mode 100644 index 0000000..c051133 --- /dev/null +++ b/app/modules/auth/forms.py @@ -0,0 +1,29 @@ +from flask_wtf import FlaskForm +from wtforms import ( + SubmitField, + SelectField, + HiddenField, + StringField, + PasswordField, + IntegerField, + FloatField, + BooleanField, + DateTimeField, +) +from wtforms.validators import InputRequired, Length, NumberRange + + +class LoginForm(FlaskForm): + username = StringField("Username", validators=[InputRequired()]) + password = PasswordField("Password", validators=[InputRequired()]) + remember = BooleanField("Remember") + submit = SubmitField("Login") + + +class RegisterForm(LoginForm): + name_first = StringField("First name") + name_last = StringField("Last name") + email = StringField("E-mail address") + phone_mobile = StringField("Phone number (mobile)") + phone_alternative = StringField("Phone number (alternative)") + submit = SubmitField("Register") diff --git a/app/modules/auth/routes.py b/app/modules/auth/routes.py new file mode 100644 index 0000000..dd9e396 --- /dev/null +++ b/app/modules/auth/routes.py @@ -0,0 +1,73 @@ +# -*- mode: python; -*- + +from flask import Blueprint, render_template, redirect, url_for, request, flash +from flask_login import login_user, login_required, logout_user +from werkzeug.security import generate_password_hash, check_password_hash + +from ... import db +from ...models import User +from .forms import LoginForm, RegisterForm + + +auth = Blueprint("auth", __name__) + + +@auth.route("/login", methods=["GET", "POST"]) +def login(): + form = LoginForm() + if form.validate_on_submit(): + req = request.form + # print(req["remember"]) + remember = True if req.get("remember") else False + user = User.query.filter_by(username=req["username"]).first() + if user is None: + flash("User not registered.", "error") + return redirect(url_for("auth.register")) + if check_password_hash(user.hashed_password, req["password"]) is False: + flash("Wrong password.", "error") + return redirect(url_for("auth.login")) + login_user(user, remember=remember) + flash( + f"Logged in as user {user.username} successfully. " + + f"You will {'not' if remember is False else ''} be remembered next time!" + ) + return redirect(url_for("main.home")) + return render_template("modules/login.html", form=form) + + +@auth.route("/register", methods=["GET", "POST"]) +def register(): + form = RegisterForm() + if form.validate_on_submit(): + req = request.form + user_already_exists = User.query.filter_by( + name_first=req["name_first"], + name_last=req["name_last"], + ).first() + if user_already_exists: + flash( + f"User {req['name_first']} {req['name_last']} already exists.", "error" + ) + return redirect(url_for("auth.login")) + new_user = User( + username=req["username"], + hashed_password=generate_password_hash(req["password"], method="sha256"), + name_first=req["name_first"], + name_last=req["name_last"], + email=req["email"], + phone_mobile=req["phone_mobile"], + phone_alternative=req["phone_alternative"], + ) + db.session.add(new_user) + db.session.commit() + flash(f"Created user {req['name_first']} {req['name_last']} successfully.") + return redirect(url_for("main.home")) + return render_template("register.html", form=form) + + +@auth.route("/logout") +@login_required +def logout(): + logout_user() + flash(f"Logged out successfully.") + return redirect(url_for("main.home")) diff --git a/app/modules/common.py b/app/modules/common.py new file mode 100644 index 0000000..9e96f41 --- /dev/null +++ b/app/modules/common.py @@ -0,0 +1,75 @@ +# -*- mode: python; -*- + +import inspect +from flask import Blueprint, request, render_template, redirect, flash, jsonify +from flask_login import login_required, current_user + +from .. import db +from .. import models +from . import forms + +from wtforms import SelectField + + +common = Blueprint("common", __name__) + + +@common.route("/modules/<module>/add/<table>", methods=["GET", "POST"]) +@login_required +def add_item(module, table): + """Add new item to table accessible via module.""" + # print("db table keys are", db.metadata.tables.keys()) + if table not in db.metadata.tables.keys(): + return render_template("errors/item-not-found.html", table=table) + form = getattr(forms, f"Add{table}")() + if form.validate_on_submit(): + model = getattr(models, table) + table_fields = inspect.signature(model).parameters + form_values = {key: request.form[key] for key in table_fields} + print(f"Ready to insert {form_values}") + record = model(**form_values) + db.session.add(record) + db.session.commit() + item_pk = model.query.order_by(model.primary_key.desc()).first().primary_key + flash(f"Successfully added item #{item_pk} to {table} table.", "info") + return redirect(f"/modules/{module}") + return render_template("modules/add-item.html", table=table, form=form) + + +@common.route("/modules/<module>/edit/<table>/<int:pk>", methods=["GET", "POST"]) +@login_required +def edit_item(module, table, pk): + """Edit existing item in table accessible via module.""" + if table not in db.metadata.tables.keys(): + return render_template("errors/item-not-found.html", table=table) + model = getattr(models, table) + item = model.query.filter_by(primary_key=pk).first() + # Instantiate form with selected item's field values. + form = getattr(forms, f"Add{table}")(**item.__dict__) + if form.validate_on_submit(): + table_fields = inspect.signature(model).parameters + form_values = {key: request.form[key] for key in table_fields} + print(f"Ready to update {form_values}") + model.query.filter_by(primary_key=pk).update(form_values) + db.session.commit() + flash(f"Successfully edited item #{pk} in {table} table.", "info") + return redirect(f"/modules/{module}") + return render_template("modules/edit-item.html", table=table, pk=pk, form=form) + + +@common.route("/modules/<module>/delete/<table>/<int:pk>", methods=["POST"]) +@login_required +def delete_item(module, table, pk): + """Delete item with Primary Key = pk from table in module.""" + model = getattr(models, table) + record = model.query.filter_by(primary_key=pk).first() + db.session.delete(record) + db.session.commit() + flash(f"Successfully removed item #{pk} from {table} table.", "info") + return redirect(f"/modules/{module}") + + +@common.route("/modules/settings") +@login_required +def settings(): + return render_template("modules/settings.html", user=current_user) diff --git a/app/modules/customers/forms.py b/app/modules/customers/forms.py new file mode 100644 index 0000000..2c5bcaa --- /dev/null +++ b/app/modules/customers/forms.py @@ -0,0 +1,36 @@ +# -*- mode: python; -*- + + +from flask_wtf import FlaskForm +from wtforms import ( + SubmitField, + SelectField, + HiddenField, + StringField, + PasswordField, + IntegerField, + FloatField, + BooleanField, + DateTimeField, +) +from wtforms.validators import InputRequired, Length, NumberRange + + +class AddCustomer(FlaskForm): + name = StringField("Customer name", validators=[InputRequired()]) + name_alternative = StringField("Alternative name") + # date_time_created = DateTimeField("Creation date") + code_customer = StringField("Customer code") + code_accounting = StringField("Accounting code") + address = StringField("Address") + postal_code = StringField("Postal Code") + city = StringField("City") + country = StringField("Country") + phone = StringField("Phone") + website = StringField("Website") + email = StringField("E-mail") + professional_id_1 = StringField("Professional ID 1") + professional_id_2 = StringField("Professional ID 2") + tax_id = StringField("Tax ID") + payment_terms = StringField("Payment Terms", default="Tu vas payer sale chien!") + submit = SubmitField("Add/Update Product") diff --git a/app/modules/customers/routes.py b/app/modules/customers/routes.py new file mode 100644 index 0000000..c8ae072 --- /dev/null +++ b/app/modules/customers/routes.py @@ -0,0 +1,38 @@ +# -*- mode: python; -*- + + +from flask import ( + Blueprint, + render_template, + send_file, + request, + redirect, + flash, + url_for, + jsonify, + abort, +) +from flask_login import login_required, current_user +import inspect + + +from ... import db +from ...models import * +from .forms import * + +customers = Blueprint("customers", __name__) + + +@customers.route("/modules/customers") +@login_required +def view(): + modules = Module.query.all() + cust = Customer.query.order_by(Customer.primary_key.desc()) + module = "customers" + flash(f"Successfully accessed module {module}.", "info") + flash(f"Still fighting against styling in {module}.", "error") + return render_template( + f"modules/{module}.html", + modules=modules, + customers=cust, + ) diff --git a/app/modules/ferti/forms.py b/app/modules/ferti/forms.py new file mode 100644 index 0000000..d926931 --- /dev/null +++ b/app/modules/ferti/forms.py @@ -0,0 +1,47 @@ +from flask_wtf import FlaskForm +from wtforms import ( + SubmitField, + SelectField, + HiddenField, + StringField, + PasswordField, + IntegerField, + FloatField, + BooleanField, + DateTimeField, +) +from wtforms.validators import InputRequired, Length, NumberRange + + +class AddFertiLog(FlaskForm): + target = SelectField( + "Type", + choices=[("False", "Log"), ("True", "Target")], + validators=[InputRequired()], + ) + nno3 = FloatField("NNO3", default=0) + p = FloatField("P", default=0) + k = FloatField("K", default=0) + ca = FloatField("Ca", default=0) + mg = FloatField("Mg", default=0) + s = FloatField("S", default=0) + na = FloatField("Na", default=0) + cl = FloatField("Cl", default=0) + fe = FloatField("Fe", default=0) + zn = FloatField("Zn", default=0) + b = FloatField("B", default=0) + mn = FloatField("Mn", default=0) + cu = FloatField("Cu", default=0) + mo = FloatField("Mo", default=0) + si = FloatField("Si", default=0) + nnh4 = FloatField("NNH4", default=0) + submit = SubmitField("Add/Update Log") + + +class AddFertiTarget(AddFertiLog): + targeted_log = SelectField( + "Log to target", + choices=[("value1", "Last"), ("value2", "Named")], + validators=[InputRequired()], + ) + submit = SubmitField("Add/Update Log") diff --git a/app/modules/ferti/routes.py b/app/modules/ferti/routes.py new file mode 100644 index 0000000..b3bb03f --- /dev/null +++ b/app/modules/ferti/routes.py @@ -0,0 +1,46 @@ +# -*- mode: python; -*- + + +from flask import ( + Blueprint, + render_template, + send_file, + request, + redirect, + flash, + url_for, + jsonify, + abort, +) +from flask_login import login_required, current_user + + +# from datetime import datetime +import inspect + + + + +from ... import db +from ...models import * +from .forms import * + + +ferti = Blueprint("ferti", __name__) + + + +@ferti.route("/modules/ferti") +@login_required +def view(): + modules = Module.query.all() + logs = Fertilog.query.order_by(Fertilog.primary_key.desc()).all() + targets = Fertitarget.query.all() + module = "ferti" + flash(f"Latest target is {targets}.", "info") + flash(f"Still fighting against styling in {module}.", "error") + return render_template( + f"modules/{module}.html", + modules=modules, + logs=logs, + ) diff --git a/app/modules/forms.py b/app/modules/forms.py new file mode 100644 index 0000000..f98995d --- /dev/null +++ b/app/modules/forms.py @@ -0,0 +1,6 @@ +# -*- mode: python; -*- + +from .customers.forms import AddCustomer +from .ferti.forms import AddFertiLog, AddFertiTarget +from .invoices.forms import AddInvoice +from .products.forms import AddProduct diff --git a/app/forms.py b/app/modules/forms.py.bkp index 302db58..f0f6bfb 100644 --- a/app/forms.py +++ b/app/modules/forms.py.bkp @@ -1,23 +1,36 @@ -""" -Form classes must be named 'Add{Item}', Item referring to the -model concerned. -""" +# -*- mode: python; -*- from flask_wtf import FlaskForm from wtforms import ( SubmitField, SelectField, - RadioField, HiddenField, StringField, PasswordField, IntegerField, FloatField, + BooleanField, DateTimeField, ) from wtforms.validators import InputRequired, Length, NumberRange +class LoginForm(FlaskForm): + username = StringField("Username", validators=[InputRequired()]) + password = PasswordField("Password", validators=[InputRequired()]) + remember = BooleanField("Remember") + submit = SubmitField("Login") + + +class RegisterForm(LoginForm): + name_first = StringField("First name") + name_last = StringField("Last name") + email = StringField("E-mail address") + phone_mobile = StringField("Phone number (mobile)") + phone_alternative = StringField("Phone number (alternative)") + submit = SubmitField("Register") + + class AddCustomer(FlaskForm): name = StringField("Customer name", validators=[InputRequired()]) name_alternative = StringField("Alternative name") @@ -48,7 +61,7 @@ class AddProduct(FlaskForm): submit = SubmitField("Add/Update Product") -class AddLog(FlaskForm): +class AddFertilog(FlaskForm): target = SelectField( "Type", choices=[("False", "Log"), ("True", "Target")], @@ -73,6 +86,16 @@ class AddLog(FlaskForm): submit = SubmitField("Add/Update Log") +class AddFertitarget(AddFertilog): + targeted_log = SelectField( + "Log to target", + choices=[("value1", "Last"), ("value2", "Named")], + validators=[InputRequired()], + ) + submit = SubmitField("Add/Update Log") + + + class AddOrder(FlaskForm): # TODO name = StringField("Product name", validators=[InputRequired()]) code_accounting = StringField("Accounting code", default=0) @@ -84,10 +107,12 @@ class AddOrder(FlaskForm): # TODO class AddInvoice(FlaskForm): - name = StringField("Product name", validators=[InputRequired()]) - code_accounting = StringField("Accounting code", default=0) - unit_weight = FloatField("Unit weight", default=0) - price_net = FloatField("Price (net)", default=0) - price_gross = FloatField("Price (gross)", default=0) - tax_rate = FloatField("Tax rate", default=0) - submit = SubmitField("Add/Update Product") + invoice_id_alt = StringField("Alternative invoice ID") + customer_id = StringField("Customer ID") + customer_reference = StringField("Customer reference") + date_billed = StringField("Date billed") + date_due = StringField("Date due") + amount_net = FloatField("Amount without tax", default=0) + amount_gross = FloatField("Amount with tax", default=0) + amount_tax = FloatField("Amount of tax", default=0) + submit = SubmitField("Add new Invoice") diff --git a/app/modules/invoices/forms.py b/app/modules/invoices/forms.py new file mode 100644 index 0000000..8ef7bd0 --- /dev/null +++ b/app/modules/invoices/forms.py @@ -0,0 +1,33 @@ +from flask_wtf import FlaskForm +from wtforms import ( + SubmitField, + SelectField, + HiddenField, + StringField, + PasswordField, + IntegerField, + FloatField, + BooleanField, + DateTimeField, +) +from wtforms.validators import InputRequired, Length, NumberRange + +from ...models import Customer + + +class AddInvoice(FlaskForm): + customer_id = SelectField("Customer", choices=[]) + invoice_id_alt = StringField("Alternative invoice ID") + customer_reference = StringField("Customer reference") + date_billed = StringField("Date billed") + date_due = StringField("Date due") + amount_net = FloatField("Amount without tax", default=0) + amount_gross = FloatField("Amount with tax", default=0) + amount_tax = FloatField("Amount of tax", default=0) + submit = SubmitField("Add new Invoice") + + def __init__(self, **kwargs): + super().__init__() + self.customer_id.choices = [ + (cus.primary_key, cus.name) for cus in Customer.query.all() + ] diff --git a/app/modules/invoices/routes.py b/app/modules/invoices/routes.py new file mode 100644 index 0000000..b8a0a68 --- /dev/null +++ b/app/modules/invoices/routes.py @@ -0,0 +1,47 @@ +# -*- mode: python; -*- + + +from flask import ( + Blueprint, + render_template, + send_file, + request, + redirect, + flash, + url_for, + jsonify, + abort, +) +from flask_login import login_required, current_user + + +from ... import db +from ...models import * +from .forms import * + + +invoices = Blueprint("invoices", __name__) + + + +@invoices.route("/modules/invoices") +@login_required +def view(): + modules = Module.query.all() + page = request.args.get('page', 1, type=int) + invoices = Invoice.query.order_by(Invoice.primary_key.desc()).paginate(page=page, per_page=5) + module = "invoices" + flash(f"Successfully accessed module {module}.", "info") + flash(f"Still fighting against styling in {module}.", "error") + return render_template( + f"modules/{module}.html", + modules=modules, + invoices=invoices, + ) + + +@invoices.route("/modules/invoices/preview/<int:pk>") +@login_required +def preview(pk): + invoice = Invoice.query.filter_by(primary_key=pk).first() + return render_template("modules/invoice-preview.html", invoice=invoice) diff --git a/app/modules/modules.py.bkp b/app/modules/modules.py.bkp new file mode 100644 index 0000000..b962a0f --- /dev/null +++ b/app/modules/modules.py.bkp @@ -0,0 +1,136 @@ +# -*- mode: python; -*- + + +from flask import ( + Blueprint, + render_template, + send_file, + request, + redirect, + flash, + url_for, + jsonify, + abort, +) +from flask_login import login_required, current_user + + +# from datetime import datetime +import inspect + +from . import db + +modules = Blueprint("modules", __name__) + + +from .models import * +from .forms import * + + +@modules.route("/modules") +def modules_home(): + return redirect("/index") + + +@modules.route("/add-<item>", methods=["GET", "POST"]) +@login_required +def add_item(item): + """Add a new item to a corresponding database table. + + The item must match a database model class name (table). Then, we + match the model class attributes with request.form values. + + """ + print("db table keys are", db.metadata.tables.keys()) + if item.capitalize() not in db.metadata.tables.keys(): + return render_template("errors/item-not-found.html", item=item) + if request.method == "GET": + form = globals()[f"Add{item.capitalize()}"]() + return render_template("add-item.html", item=item, form=form) + if request.method == "POST": + table = globals()[item.capitalize()] + table_fields = inspect.signature(table).parameters + form_values = {key: request.form[key] for key in table_fields} + print(f"Ready to insert {form_values}") + record = table(**form_values) + db.session.add(record) + db.session.commit() + item_pk = table.query.order_by(table.primary_key.desc()).first().primary_key + flash( + f"Successfully added item #{item_pk} to {table.__table__.name.capitalize()} table.", + "info", + ) + return redirect("/modules") + + +@modules.route("/delete-<pk>-from-<table>", methods=["POST"]) +@login_required +def delete_item(pk, table): + """Delete item with Primary Key = pk from corresponding database + table. + """ + model = globals()[table] + record = model.query.filter_by(primary_key=pk).first() + db.session.delete(record) + db.session.commit() + flash(f"Successfully removed item #{pk} from {table} table.", "info") + return redirect("/modules") + + +@modules.route("/modules/customers") +@login_required +def customers(): + modules = Module.query.all() + customers = Customer.query.order_by(Customer.primary_key.desc()) + module = "customers" + flash(f"Successfully accessed module {module}.", "info") + flash(f"Still fighting against styling in {module}.", "error") + return render_template( + f"modules/{module}.html", + modules=modules, + customers=customers, + ) + + +@modules.route("/modules/products") +@login_required +def products(): + modules = Module.query.all() + products = Product.query.order_by(Product.primary_key.desc()) + module = "products" + flash(f"Successfully accessed module {module}.", "info") + flash(f"Still fighting against styling in {module}.", "error") + return render_template( + f"modules/{module}.html", + modules=modules, + products=products, + ) + + +@modules.route("/modules/ferti") +@login_required +def ferti(): + modules = Module.query.all() + logs = Fertilog.query.order_by(Fertilog.primary_key.desc()).all() + targets = Fertitarget.query.all() + module = "ferti" + flash(f"Latest target is {targets}.", "info") + flash(f"Still fighting against styling in {module}.", "error") + return render_template( + f"modules/{module}.html", + modules=modules, + logs=logs, + ) + + +# @main.route("/add-invoice", methods=["GET", "POST"]) +# def add_invoice(): +# form = AddInvoice() +# if request.method == "GET": +# return render_template("add-invoice.html") + + +# @main.route("/preview-invoice", methods=["GET", "POST"]) +# def preview_invoice(): +# if request.method == "GET": +# return render_template("preview-invoice.html") diff --git a/app/modules/orders/forms.py b/app/modules/orders/forms.py new file mode 100644 index 0000000..66ab47b --- /dev/null +++ b/app/modules/orders/forms.py @@ -0,0 +1,35 @@ +from flask_wtf import FlaskForm +from wtforms import ( + SubmitField, + SelectField, + HiddenField, + StringField, + PasswordField, + IntegerField, + FloatField, + BooleanField, + DateTimeField, +) +from wtforms.validators import InputRequired, Length, NumberRange + + +class AddOrder(FlaskForm): # TODO + name = StringField("Product name", validators=[InputRequired()]) + code_accounting = StringField("Accounting code", default=0) + unit_weight = FloatField("Unit weight", default=0) + price_net = FloatField("Price (net)", default=0) + price_gross = FloatField("Price (gross)", default=0) + tax_rate = FloatField("Tax rate", default=0) + submit = SubmitField("Add/Update Product") + + +class AddInvoice(FlaskForm): + invoice_id_alt = StringField("Alternative invoice ID") + customer_id = StringField("Customer ID") + customer_reference = StringField("Customer reference") + date_billed = StringField("Date billed") + date_due = StringField("Date due") + amount_net = FloatField("Amount without tax", default=0) + amount_gross = FloatField("Amount with tax", default=0) + amount_tax = FloatField("Amount of tax", default=0) + submit = SubmitField("Add new Invoice") diff --git a/app/modules/orders/routes.py b/app/modules/orders/routes.py new file mode 100644 index 0000000..4027a44 --- /dev/null +++ b/app/modules/orders/routes.py @@ -0,0 +1,30 @@ +# -*- mode: python; -*- + + +from flask import ( + Blueprint, + render_template, + send_file, + request, + redirect, + flash, + url_for, + jsonify, + abort, +) +from flask_login import login_required, current_user + + +# from datetime import datetime +import inspect + + + +from ... import db +from ...models import * +from .forms import * + + +orders = Blueprint("orders", __name__) + + diff --git a/app/modules/products/forms.py b/app/modules/products/forms.py new file mode 100644 index 0000000..f00441f --- /dev/null +++ b/app/modules/products/forms.py @@ -0,0 +1,23 @@ +from flask_wtf import FlaskForm +from wtforms import ( + SubmitField, + SelectField, + HiddenField, + StringField, + PasswordField, + IntegerField, + FloatField, + BooleanField, + DateTimeField, +) +from wtforms.validators import InputRequired, Length, NumberRange + + +class AddProduct(FlaskForm): + name = StringField("Product name", validators=[InputRequired()]) + code_accounting = StringField("Accounting code", default=0) + unit_weight = FloatField("Unit weight", default=0) + price_net = FloatField("Price (net)", default=0) + price_gross = FloatField("Price (gross)", default=0) + tax_rate = FloatField("Tax rate", default=0) + submit = SubmitField("Add/Update Product") diff --git a/app/modules/products/routes.py b/app/modules/products/routes.py new file mode 100644 index 0000000..735fef9 --- /dev/null +++ b/app/modules/products/routes.py @@ -0,0 +1,26 @@ +# -*- mode: python; -*- + + +from flask import Blueprint, render_template, request, redirect, flash +from flask_login import login_required + +from ... import db +from ...models import Module, Product +from .forms import * + +products = Blueprint("products", __name__) + + +@products.route("/modules/products") +@login_required +def list(): + modules = Module.query.all() + prods = Product.query.order_by(Product.primary_key.desc()) + module = "products" + flash(f"Successfully accessed module {module}.", "info") + flash(f"Still fighting against styling in {module}.", "error") + return render_template( + f"modules/{module}.html", + modules=modules, + products=prods, + ) diff --git a/app/routes.py b/app/routes.py deleted file mode 100644 index 11cd3c2..0000000 --- a/app/routes.py +++ /dev/null @@ -1,135 +0,0 @@ -""" -routes.py module ----------------- - -This Python module contains the logic supporting: -1. Navigating between website pages -2. Interpreting user requests to the server -3. Dispatching requested content back to the user - -Python dependencies: -- flask: provides web application features -- forms: provides secure user form submission -- sqlalchemy: provides communication with database on server. - -Personal imports: -These are used to avoid cluttering this file with -placeholder data for posts' content. -""" - - -from flask import Flask, render_template, request, redirect, flash, url_for, jsonify -from flask_bootstrap import Bootstrap - -from datetime import datetime -import inspect - -from model import * -from forms import * - -app = Flask(__name__) - - -app.config.from_pyfile("../config.py") -db.init_app(app) - - -@app.route("/") -@app.route("/fapg/home") -def project(): - """This is our project welcome page.""" - michel = User( - name_first="Michel", - name_last="Peter", - email="le-boss@fapg.com", - phone_mobile="00000000", - phone_alternative="0000000000", - updated="2022-04-21", - ) - modules = Module.query.all() - print(module.name for module in modules) - return render_template("home.html", user=michel, project="fapg", modules=modules) - - -@app.route("/modules") -def all_modules(): - return redirect("/") - - -@app.route("/modules/<module>") -def render_module(module): - modules = Module.query.all() - if module not in [mod.name for mod in modules]: - return render_template("errors/module-not-found.html", module=module) - customers = Customer.query.order_by(Customer.primary_key.desc()) - products = Product.query.order_by(Product.primary_key.desc()) - logs = Log.query.order_by(Log.primary_key.desc()) - latest_target = ( - Log.query.filter_by(target="True").order_by(Log.primary_key.desc()).first() - ) - flash(f"Successfully accessed module {module}.", "info") - flash(f"Still fighting against styling in {module}.", "error") - return render_template( - f"modules/{module}.html", - module=module, - modules=modules, - customers=customers, - products=products, - logs=logs, - target=latest_target, - ) - - -@app.route("/delete-<pk>-from-<table>", methods=["POST"]) -def delete_item(pk, table): - """Delete item with Primary Key = pk from corresponding database - table. - """ - model = globals()[table] - record = model.query.filter_by(primary_key=pk).first() - db.session.delete(record) - db.session.commit() - flash(f"Successfully removed item #{pk} from {table} table.", "info") - return redirect("/modules") - - -@app.route("/add-<item>", methods=["GET", "POST"]) -def add_item(item): - """Add a new item to a corresponding database table. - - The item must match a database model class name (table). Then, we - match the model class attributes with request.form values. - - """ - if item not in db.metadata.tables.keys(): - return render_template("errors/item-not-found.html", item=item) - if request.method == "GET": - form = globals()[f"Add{item.capitalize()}"]() - return render_template("add-item.html", item=item, form=form) - if request.method == "POST": - table = globals()[item.capitalize()] - table_fields = inspect.signature(table).parameters - form_values = [request.form[key] for key in table_fields] - debug = f"Ready to insert {form_values}" - record = table(*form_values) - db.session.add(record) - db.session.commit() - item_pk = table.query.order_by(table.primary_key.desc()).first().primary_key - flash( - f"Successfully added item #{item_pk} to {table.__table__.name.capitalize()} table.", - "info", - ) - return redirect(f"/modules") - - -@app.route("/add-invoice", methods=["GET", "POST"]) -def add_invoice(): - form = AddInvoice() - if request.method == "GET": - return render_template("add-invoice.html") - - -@app.route("/preview-invoice", methods=["GET", "POST"]) -def preview_invoice(): - if request.method == "GET": - return render_template("preview-invoice.html") |