diff options
Diffstat (limited to 'app')
34 files changed, 545 insertions, 0 deletions
| 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.otfBinary files differ new 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.otfBinary files differ new 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.otfBinary files differ new 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.otfBinary files differ new 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.otfBinary files differ new 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.otfBinary files differ new 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.otfBinary files differ new 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.otfBinary files differ new 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.otfBinary files differ new 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.otfBinary files differ new 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.otfBinary files differ new 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.otfBinary files differ new 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.otfBinary files differ new 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.otfBinary files differ new 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.otfBinary files differ new 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.otfBinary files differ new 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.otfBinary files differ new 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.otfBinary files differ new 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 %} | 
