diff options
35 files changed, 549 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed08861 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# .gitignore + +.venv/ +*~undo-tree~ diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..1ac0ed9 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,4 @@ +# .gitignore for app + +*__pycache__ +*.db
\ No newline at end of file diff --git a/app/database.py b/app/database.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/database.py diff --git a/app/forms.py b/app/forms.py new file mode 100644 index 0000000..daa394b --- /dev/null +++ b/app/forms.py @@ -0,0 +1,24 @@ +from flask_wtf import FlaskForm +from wtforms import StringField, PasswordField, SubmitField, BooleanField +from wtforms.validators import DataRequired, Length, EqualTo + + +class RegistrationForm(FlaskForm): + alias = StringField("Alias", validators=[DataRequired(), Length(min=2, max=20)]) + password = PasswordField("Password", validators=[DataRequired()]) + password_confirm = PasswordField( + "Confirm Password", validators=[DataRequired(), EqualTo("password")] + ) + submit = SubmitField("Create Alias") + + +class LoginForm(FlaskForm): + alias = StringField("Alias", validators=[DataRequired(), Length(min=2, max=20)]) + password = PasswordField("Password", validators=[DataRequired()]) + remember = BooleanField("Remember Alias") + submit = SubmitField("Login Alias") + + +class NewMessage(FlaskForm): + recipient = StringField("Recipient", validators=[DataRequired()]) + message = StringField("message", validators=[DataRequired()]) diff --git a/app/model.py b/app/model.py new file mode 100644 index 0000000..fa4e00a --- /dev/null +++ b/app/model.py @@ -0,0 +1,39 @@ +import SQAlchemy + +class Users(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) + + def __repr__(self): + return f"<User {self.name_first} {self.name_last}>" + + +class Projects(db.Model): + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(20), nullable=False) + name_full = db.Column(db.String(20), nullable=False) + nickname = db.Column(db.String(20), nullable=False) + city = db.Column(db.String(20), nullable=False) + + def __repr__(self): + return f"<Project {self.name}>" + + +class Modules(db.Model): + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(20), unique=True, nullable=False) + description = db.Column(db.String(50), nullable=False) + + def __repr__(self): + return f"<Module {self.name}>" + + +class Doobie: + def __init__(self, name, prices, quantity): + self.name = name + self.prices = prices + self.quantity = quantity + + def __repr__(self): + return self.name diff --git a/app/placeholders.py b/app/placeholders.py new file mode 100644 index 0000000..a2360b4 --- /dev/null +++ b/app/placeholders.py @@ -0,0 +1,7 @@ +modules = [ + "catalog", + "creator", + "logger", + "calculator", + "stock", +] diff --git a/app/routes.py b/app/routes.py new file mode 100644 index 0000000..d2bd3f0 --- /dev/null +++ b/app/routes.py @@ -0,0 +1,215 @@ +""" +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_sqlalchemy import SQLAlchemy +from flask_bootstrap import Bootstrap + +from flask_wtf import FlaskForm +from wtforms import ( + SubmitField, + SelectField, + RadioField, + HiddenField, + StringField, + IntegerField, + FloatField, +) +from wtforms.validators import InputRequired, Length, Regexp, NumberRange +from datetime import datetime + +import placeholders as p + +app = Flask(__name__) + +# Flask-Bootstrap requires this line +Bootstrap(app) + + +# Flask-WTF encryption key +app.config["SECRET_KEY"] = "Scooby_Lu,_where_are_you?" + +# Our database name +db_name = "fapg.db" +app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///" + db_name +app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = True +db = SQLAlchemy(app) + + +class Users(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 __init__( + self, name_first, name_last, email, phone_mobile, phone_alternative, updated + ): + self.name_first = name_first + self.name_last = name_last + self.email = email + self.phone_mobile = phone_mobile + self.phone_alternative = phone_alternative + self.updated = updated + + def __repr__(self): + return f"<User {self.name_first} {self.name_last}>" + + +class Products(db.Model): + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(20), nullable=False) + supplier = db.Column(db.String(20), nullable=False) + price = db.Column(db.Float(10), nullable=False) + updated = db.Column(db.String) + + def __init__(self, name, supplier, price, updated): + self.name = name + self.supplier = supplier + self.price = price + self.updated = updated + + def __repr__(self): + return f"<Product {self.name} by {self.supplier}>" + + +class Projects(db.Model): + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(20), nullable=False) + name_full = db.Column(db.String(20), nullable=False) + nickname = db.Column(db.String(20), nullable=False) + city = db.Column(db.String(20), nullable=False) + + def __repr__(self): + return f"<Project {self.name}>" + + +class Modules(db.Model): + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(20), unique=True, nullable=False) + description = db.Column(db.String(50), nullable=False) + + def __init__(self, name, description, updated): + self.name = name + self.description = description + self.updated = updated + + def __repr__(self): + return f"<Module {self.name}>" + + +class AddProduct(FlaskForm): + # id used only by update/edit + id = HiddenField() + name = StringField("Product name", validators=[InputRequired()]) + supplier = SelectField( + "Choose a supplier", + choices=[ + ("", ""), + ("Mister Brown", "Mister Brown"), + ("Madame Cerise", "Madame Cerise"), + ("Biton la Malice", "G. Biton la Malice"), + ("Leroy Merlin", "Leroy Merlin"), + ("other", "Other"), + ], + ) + price = FloatField("Retail price per unit") + # updated - date - handled in the route function + updated = HiddenField() + submit = SubmitField("Add/Update Product") + + +# add a new product to the database +@app.route("/add_product", methods=["GET", "POST"]) +def add_product(): + form = AddProduct() + if form.validate_on_submit(): + name = request.form["name"] + supplier = request.form["supplier"] + price = request.form["price"] + # get today's date from function, above all the routes + updated = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + # the data to be inserted into FAPG model - the table, products + record = Products(name, supplier, price, updated) + # Flask-SQLAlchemy magic adds record to database + db.session.add(record) + db.session.commit() + # create a message to send to the template + message = f"The data for product {name} has been submitted." + return render_template("add_product.html", message=message) + else: + # show validaton errors + # see https://pythonprogramming.net/flash-flask-tutorial/ + for field, errors in form.errors.items(): + for error in errors: + flash( + "Error in {}: {}".format(getattr(form, field).label.text, error), + "error", + ) + return render_template("add_product.html", form=form) + + +@app.route("/") +@app.route("/fapg/home") +def project(): + """This is our project welcome page.""" + michel = Users( + name_first="Michel", + name_last="Peter", + email="le-boss@fapg.com", + phone_mobile="00000000", + phone_alternative="0000000000", + updated="2022-04-21", + ) + modules = Modules.query.all() + print(module.name for module in modules) + return render_template("home.html", user=michel, project=fapg, modules=modules) + + +@app.route("/module/<module>") +def render_module(module): + modules = Modules.query.all() + catalog = Products.query.all() + user_modules = [module.name for module in modules] + print(user_modules) + # If a module was purchased by a user and added to their database, + # they have access to the corresponding module route. + if module in user_modules: + return render_template( + f"modules/{module}.html", + modules=modules, + catalog=catalog, + ) + else: + return render_template("errors/module-not-found.html", module=module) + + +# If this file is executed as a script (i.e. double-clicked), +# the Python interpreter will run the Flask process and begin serving +# the web pages on the standard localhost address (127.0.0.1). +# But if this file is called as a module by another Python script, it will not +# serve content to the web pages, but the function definitions contained in +# this file will be available to the calling script. +# E.g. calling script will know what the yes() function is. +if __name__ == "__main__": + app.run(debug=True) diff --git a/app/static/fonts/PublicSans-Black.otf b/app/static/fonts/PublicSans-Black.otf Binary files differnew file mode 100644 index 0000000..bbbaa26 --- /dev/null +++ b/app/static/fonts/PublicSans-Black.otf diff --git a/app/static/fonts/PublicSans-BlackItalic.otf b/app/static/fonts/PublicSans-BlackItalic.otf Binary files differnew file mode 100644 index 0000000..46e3f71 --- /dev/null +++ b/app/static/fonts/PublicSans-BlackItalic.otf diff --git a/app/static/fonts/PublicSans-Bold.otf b/app/static/fonts/PublicSans-Bold.otf Binary files differnew file mode 100644 index 0000000..7a2b62b --- /dev/null +++ b/app/static/fonts/PublicSans-Bold.otf diff --git a/app/static/fonts/PublicSans-BoldItalic.otf b/app/static/fonts/PublicSans-BoldItalic.otf Binary files differnew file mode 100644 index 0000000..718357f --- /dev/null +++ b/app/static/fonts/PublicSans-BoldItalic.otf diff --git a/app/static/fonts/PublicSans-ExtraBold.otf b/app/static/fonts/PublicSans-ExtraBold.otf Binary files differnew file mode 100644 index 0000000..09b52dd --- /dev/null +++ b/app/static/fonts/PublicSans-ExtraBold.otf diff --git a/app/static/fonts/PublicSans-ExtraBoldItalic.otf b/app/static/fonts/PublicSans-ExtraBoldItalic.otf Binary files differnew file mode 100644 index 0000000..5b33222 --- /dev/null +++ b/app/static/fonts/PublicSans-ExtraBoldItalic.otf diff --git a/app/static/fonts/PublicSans-ExtraLight.otf b/app/static/fonts/PublicSans-ExtraLight.otf Binary files differnew file mode 100644 index 0000000..49b407d --- /dev/null +++ b/app/static/fonts/PublicSans-ExtraLight.otf diff --git a/app/static/fonts/PublicSans-ExtraLightItalic.otf b/app/static/fonts/PublicSans-ExtraLightItalic.otf Binary files differnew file mode 100644 index 0000000..c76e855 --- /dev/null +++ b/app/static/fonts/PublicSans-ExtraLightItalic.otf diff --git a/app/static/fonts/PublicSans-Italic.otf b/app/static/fonts/PublicSans-Italic.otf Binary files differnew file mode 100644 index 0000000..38996ad --- /dev/null +++ b/app/static/fonts/PublicSans-Italic.otf diff --git a/app/static/fonts/PublicSans-Light.otf b/app/static/fonts/PublicSans-Light.otf Binary files differnew file mode 100644 index 0000000..126544e --- /dev/null +++ b/app/static/fonts/PublicSans-Light.otf diff --git a/app/static/fonts/PublicSans-LightItalic.otf b/app/static/fonts/PublicSans-LightItalic.otf Binary files differnew file mode 100644 index 0000000..1e6aa6f --- /dev/null +++ b/app/static/fonts/PublicSans-LightItalic.otf diff --git a/app/static/fonts/PublicSans-Medium.otf b/app/static/fonts/PublicSans-Medium.otf Binary files differnew file mode 100644 index 0000000..93507a5 --- /dev/null +++ b/app/static/fonts/PublicSans-Medium.otf diff --git a/app/static/fonts/PublicSans-MediumItalic.otf b/app/static/fonts/PublicSans-MediumItalic.otf Binary files differnew file mode 100644 index 0000000..f5ddb90 --- /dev/null +++ b/app/static/fonts/PublicSans-MediumItalic.otf diff --git a/app/static/fonts/PublicSans-Regular.otf b/app/static/fonts/PublicSans-Regular.otf Binary files differnew file mode 100644 index 0000000..d2b3f16 --- /dev/null +++ b/app/static/fonts/PublicSans-Regular.otf diff --git a/app/static/fonts/PublicSans-SemiBold.otf b/app/static/fonts/PublicSans-SemiBold.otf Binary files differnew file mode 100644 index 0000000..4ab6b89 --- /dev/null +++ b/app/static/fonts/PublicSans-SemiBold.otf diff --git a/app/static/fonts/PublicSans-SemiBoldItalic.otf b/app/static/fonts/PublicSans-SemiBoldItalic.otf Binary files differnew file mode 100644 index 0000000..a28f6c0 --- /dev/null +++ b/app/static/fonts/PublicSans-SemiBoldItalic.otf diff --git a/app/static/fonts/PublicSans-Thin.otf b/app/static/fonts/PublicSans-Thin.otf Binary files differnew file mode 100644 index 0000000..dee0ae2 --- /dev/null +++ b/app/static/fonts/PublicSans-Thin.otf diff --git a/app/static/fonts/PublicSans-ThinItalic.otf b/app/static/fonts/PublicSans-ThinItalic.otf Binary files differnew file mode 100644 index 0000000..c6b481a --- /dev/null +++ b/app/static/fonts/PublicSans-ThinItalic.otf diff --git a/app/static/styles/style.css b/app/static/styles/style.css new file mode 100644 index 0000000..ca60a7b --- /dev/null +++ b/app/static/styles/style.css @@ -0,0 +1,79 @@ +:root { + --primary-color: #003B5C; + --secondary-color: #C3D7EE; + --home: #c8c8c8; + --yes: #80FF80; + --no: #FF8080; + font-size: 18; + --fast-speed: 0.2s; + --med-speed: 0.4s; + --slow-speed: 1s; +} + +body { + font-family: 'Public Sans', sans-serif; + line-height: 1.2; + margin: 0; + padding: 0; +} + +/* h1 { */ +/* margin-left: 2rem; */ +/* margin-right: 1rem; */ +/* } */ + +/* h2 { */ +/* margin-left: 1rem; */ +/* margin-right: 1rem; */ +/* } */ + +/* p { */ +/* margin-left: 1rem; */ +/* margin-right: 1rem; */ +/* } */ + + +@font-face { + font-family: "Public Sans"; + src: url("/static/fonts/PublicSans-Regular.otf"); +} + +nav { + display: flex; + background: darkgrey; + color: white; + margin: 0.5em; + padding: 0.5em; +} + +h1 { + margin: 0; +} + +nav ul { + display: flex; + flex-wrap: wrap; + margin: 0; + justify-content: right; + list-style: none; +} + +nav ul li { + margin: 0 0 0.5em 0; + padding: 0.5em 0; +} + +.button { + height: 1em; + padding: 0.5em; + margin: 0 0.5em; + background: dimgray; + color: white; + border-radius: 8px; + text-decoration: none; +} + +.button:hover { + background: white; + color: black; +} diff --git a/app/templates/add_product.html b/app/templates/add_product.html new file mode 100644 index 0000000..ad7ea0a --- /dev/null +++ b/app/templates/add_product.html @@ -0,0 +1,44 @@ +{# -*- mode: jinja2; -*- #} + +{% extends "base.html" %} +{% import "bootstrap/wtf.html" as wtf %} + +{% block content %} + + {% block title %}Add a New Product{% endblock %} + + {% if message %} + + {# the form was submitted and message exists #} + <p class="lead"><strong>{{ message }}</strong></p> + {# links #} + <p><a href="{{ url_for('add_product') }}" class="button">Submit another product.</a></p> +<p><a href="/fapg/home">Return to the index.</a></p> + +{% else %} + + {# the form is displayed when template opens via GET not POST #} + <p class="lead alert alert-primary">Add a new sock to our inventory.</p> +<p class="ml-4"><a href="/fapg/home" class="button">Return to the index.</a></p> +{# show flash - based on WTForms validators +see https://pythonprogramming.net/flash-flask-tutorial/ +get_flashed_messages() exists here because of flash() +in the route function +#} +{% with errors = get_flashed_messages() %} + {% if errors %} + {% for err in errors %} + <div class="alert alert-danger alert-dismissible" role="alert"> + <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button> + {{ err }} + </div> + {% endfor %} + {% endif %} +{% endwith %} +{# end of flash #} + +{# the form, thanks to WTForms #} +{{ wtf.quick_form(form) }} + +{% endif %} +{% endblock %} diff --git a/app/templates/base.html b/app/templates/base.html new file mode 100644 index 0000000..0b53b5c --- /dev/null +++ b/app/templates/base.html @@ -0,0 +1,30 @@ +{# -*- mode: jinja2; -*- #} + +<!doctype html> + +<html lang="en"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to"> + <title>Farm Manager</title> + <meta name="author" content="Marius Peter"> + <meta name="description" content="A draft page for Farm Manager."> + <!-- <link rel="icon" href="/favicon.ico"> --> + <link rel="stylesheet" href="{{ url_for('static', filename='styles/style.css') }}"> + </head> + <body> + <nav> + <a href="/" class="button">Home</a> + <h1>{% block title %}{% endblock %}</h1> + <ul> + {% for module in modules %} + <li><a href="/module/{{ module.name }}" class="button">{{ module.name }}</a></li> + {% endfor %} + </ul> + </nav> + <div id="content"> + {% block content %}{% endblock %} + </div> + <!-- <script src="js/scripts.js"></script> --> + </body> +</html> diff --git a/app/templates/errors/module-not-found.html b/app/templates/errors/module-not-found.html new file mode 100644 index 0000000..6496503 --- /dev/null +++ b/app/templates/errors/module-not-found.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} + +{% block title %} +Module not found +{% endblock %} + +{% block content %} +<p> + No module found with name <strong>{{ module }}</strong>. +</p> +<p> + If you'd like to suggest a module, please send us an e-mail +</p> +{% endblock %} diff --git a/app/templates/home.html b/app/templates/home.html new file mode 100644 index 0000000..a9bac51 --- /dev/null +++ b/app/templates/home.html @@ -0,0 +1,22 @@ +<!-- -*- mode: jinja2; -*- --> + +{% extends "base.html" %} + +{% block title %} + Welcome, {{ user.name_first }}! +{% endblock %} + + +{% block content %} + + <p>You are logged in as <strong>{{ user }}</strong> on + project <strong>{{ project }}</strong>.</p> + + <h2>Available modules</h2> + <dl> + {% for module in modules %} + <dt style="font-weight: bold">{{ module.name }}</dt><dd>{{ module.description }}<dd> + {% endfor %} + </dl> + +{% endblock %} diff --git a/app/templates/modules/calculator.html b/app/templates/modules/calculator.html new file mode 100644 index 0000000..f951a17 --- /dev/null +++ b/app/templates/modules/calculator.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} +{% block title %} +Calculator +{% endblock %} + +{% block content %} +<i>insert module content here.</i> +{% endblock %} diff --git a/app/templates/modules/catalog.html b/app/templates/modules/catalog.html new file mode 100644 index 0000000..857c654 --- /dev/null +++ b/app/templates/modules/catalog.html @@ -0,0 +1,35 @@ +<!-- -*- mode: jinja2; -*- --> + +{% extends "base.html" %} +{% block title %} + Catalog +{% endblock %} + +{% block content %} + <i>insert module content here.</i> + + <p> + <a href="{{ url_for('add_product') }}" class="button">Add product to your catalog</a> + </p> + + <table> + <thead> + <tr> + <th>Name</th> + <th>Price</th> + <th>Supplier</th> + <th>Updated</th> + </tr> + </thead> + <tbody> + {% for product in catalog %} + <tr> + <td>{{ product.name }}</td> + <td>{{ product.price }}</td> + <td>{{ product.supplier }}</td> + <td>{{ product.updated }}</td> + </tr> + {% endfor %} + </tbody> + </table> +{% endblock %} diff --git a/app/templates/modules/creator.html b/app/templates/modules/creator.html new file mode 100644 index 0000000..588f428 --- /dev/null +++ b/app/templates/modules/creator.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} +{% block title %} +Creator +{% endblock %} + +{% block content %} +<i>insert module content here.</i> +{% endblock %} diff --git a/app/templates/modules/logger.html b/app/templates/modules/logger.html new file mode 100644 index 0000000..7ba93b3 --- /dev/null +++ b/app/templates/modules/logger.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} +{% block title %} +Logger +{% endblock %} + +{% block content %} +<i>insert module content here.</i> +{% endblock %} diff --git a/app/templates/modules/stock.html b/app/templates/modules/stock.html new file mode 100644 index 0000000..5d20d5c --- /dev/null +++ b/app/templates/modules/stock.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} +{% block title %} +Stock +{% endblock %} + +{% block content %} +<i>insert module content here.</i> +{% endblock %} |