First commit! :fire:

Commit
9770cf8345d47cb086bd386c0d5c0fca165a6d11
Author
Marius Peter <marius.peter@tutanota.com>
Author date
Committer
Marius Peter <marius.peter@tutanota.com>
Committer date
Changed files
.gitignore
index 00000000..ed088610 000000..100644
@@ -0,0 +1,4 @@
1 Added: # .gitignore
2 Added:
3 Added: .venv/
4 Added: *~undo-tree~
app/.gitignore
index 00000000..1ac0ed94 000000..100644
@@ -0,0 +1,4 @@
1 Added: # .gitignore for app
2 Added:
3 Added: *__pycache__
4 Added: *.db
app/database.py
index 00000000..e69de29b 000000..100644
app/forms.py
index 00000000..daa394b8 000000..100644
@@ -0,0 +1,24 @@
1 Added: from flask_wtf import FlaskForm
2 Added: from wtforms import StringField, PasswordField, SubmitField, BooleanField
3 Added: from wtforms.validators import DataRequired, Length, EqualTo
4 Added:
5 Added:
6 Added: class RegistrationForm(FlaskForm):
7 Added: alias = StringField("Alias", validators=[DataRequired(), Length(min=2, max=20)])
8 Added: password = PasswordField("Password", validators=[DataRequired()])
9 Added: password_confirm = PasswordField(
10 Added: "Confirm Password", validators=[DataRequired(), EqualTo("password")]
11 Added: )
12 Added: submit = SubmitField("Create Alias")
13 Added:
14 Added:
15 Added: class LoginForm(FlaskForm):
16 Added: alias = StringField("Alias", validators=[DataRequired(), Length(min=2, max=20)])
17 Added: password = PasswordField("Password", validators=[DataRequired()])
18 Added: remember = BooleanField("Remember Alias")
19 Added: submit = SubmitField("Login Alias")
20 Added:
21 Added:
22 Added: class NewMessage(FlaskForm):
23 Added: recipient = StringField("Recipient", validators=[DataRequired()])
24 Added: message = StringField("message", validators=[DataRequired()])
app/model.py
index 00000000..fa4e00a5 000000..100644
@@ -0,0 +1,39 @@
1 Added: import SQAlchemy
2 Added:
3 Added: class Users(db.Model):
4 Added: id = db.Column(db.Integer, primary_key=True)
5 Added: name_first = db.Column(db.String(20), nullable=False)
6 Added: name_last = db.Column(db.String(20), nullable=False)
7 Added:
8 Added: def __repr__(self):
9 Added: return f"<User {self.name_first} {self.name_last}>"
10 Added:
11 Added:
12 Added: class Projects(db.Model):
13 Added: id = db.Column(db.Integer, primary_key=True)
14 Added: name = db.Column(db.String(20), nullable=False)
15 Added: name_full = db.Column(db.String(20), nullable=False)
16 Added: nickname = db.Column(db.String(20), nullable=False)
17 Added: city = db.Column(db.String(20), nullable=False)
18 Added:
19 Added: def __repr__(self):
20 Added: return f"<Project {self.name}>"
21 Added:
22 Added:
23 Added: class Modules(db.Model):
24 Added: id = db.Column(db.Integer, primary_key=True)
25 Added: name = db.Column(db.String(20), unique=True, nullable=False)
26 Added: description = db.Column(db.String(50), nullable=False)
27 Added:
28 Added: def __repr__(self):
29 Added: return f"<Module {self.name}>"
30 Added:
31 Added:
32 Added: class Doobie:
33 Added: def __init__(self, name, prices, quantity):
34 Added: self.name = name
35 Added: self.prices = prices
36 Added: self.quantity = quantity
37 Added:
38 Added: def __repr__(self):
39 Added: return self.name
app/placeholders.py
index 00000000..a2360b41 000000..100644
@@ -0,0 +1,7 @@
1 Added: modules = [
2 Added: "catalog",
3 Added: "creator",
4 Added: "logger",
5 Added: "calculator",
6 Added: "stock",
7 Added: ]
app/routes.py
index 00000000..d2bd3f0d 000000..100644
@@ -0,0 +1,215 @@
1 Added: """
2 Added: routes.py module
3 Added: ----------------
4 Added:
5 Added: This Python module contains the logic supporting:
6 Added: 1. Navigating between website pages
7 Added: 2. Interpreting user requests to the server
8 Added: 3. Dispatching requested content back to the user
9 Added:
10 Added: Python dependencies:
11 Added: - flask: provides web application features
12 Added: - forms: provides secure user form submission
13 Added: - sqlalchemy: provides communication with database on server.
14 Added:
15 Added: Personal imports:
16 Added: These are used to avoid cluttering this file with
17 Added: placeholder data for posts' content.
18 Added: """
19 Added:
20 Added:
21 Added: from flask import Flask, render_template, request, redirect, flash, url_for, jsonify
22 Added: from flask_sqlalchemy import SQLAlchemy
23 Added: from flask_bootstrap import Bootstrap
24 Added:
25 Added: from flask_wtf import FlaskForm
26 Added: from wtforms import (
27 Added: SubmitField,
28 Added: SelectField,
29 Added: RadioField,
30 Added: HiddenField,
31 Added: StringField,
32 Added: IntegerField,
33 Added: FloatField,
34 Added: )
35 Added: from wtforms.validators import InputRequired, Length, Regexp, NumberRange
36 Added: from datetime import datetime
37 Added:
38 Added: import placeholders as p
39 Added:
40 Added: app = Flask(__name__)
41 Added:
42 Added: # Flask-Bootstrap requires this line
43 Added: Bootstrap(app)
44 Added:
45 Added:
46 Added: # Flask-WTF encryption key
47 Added: app.config["SECRET_KEY"] = "Scooby_Lu,_where_are_you?"
48 Added:
49 Added: # Our database name
50 Added: db_name = "fapg.db"
51 Added: app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///" + db_name
52 Added: app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = True
53 Added: db = SQLAlchemy(app)
54 Added:
55 Added:
56 Added: class Users(db.Model):
57 Added: id = db.Column(db.Integer, primary_key=True)
58 Added: name_first = db.Column(db.String(20), nullable=False)
59 Added: name_last = db.Column(db.String(20), nullable=False)
60 Added: email = db.Column(db.String(20), nullable=False)
61 Added: phone_mobile = db.Column(db.Integer, nullable=False)
62 Added: phone_alternative = db.Column(db.Integer)
63 Added: updated = db.Column(db.String)
64 Added:
65 Added: def __init__(
66 Added: self, name_first, name_last, email, phone_mobile, phone_alternative, updated
67 Added: ):
68 Added: self.name_first = name_first
69 Added: self.name_last = name_last
70 Added: self.email = email
71 Added: self.phone_mobile = phone_mobile
72 Added: self.phone_alternative = phone_alternative
73 Added: self.updated = updated
74 Added:
75 Added: def __repr__(self):
76 Added: return f"<User {self.name_first} {self.name_last}>"
77 Added:
78 Added:
79 Added: class Products(db.Model):
80 Added: id = db.Column(db.Integer, primary_key=True)
81 Added: name = db.Column(db.String(20), nullable=False)
82 Added: supplier = db.Column(db.String(20), nullable=False)
83 Added: price = db.Column(db.Float(10), nullable=False)
84 Added: updated = db.Column(db.String)
85 Added:
86 Added: def __init__(self, name, supplier, price, updated):
87 Added: self.name = name
88 Added: self.supplier = supplier
89 Added: self.price = price
90 Added: self.updated = updated
91 Added:
92 Added: def __repr__(self):
93 Added: return f"<Product {self.name} by {self.supplier}>"
94 Added:
95 Added:
96 Added: class Projects(db.Model):
97 Added: id = db.Column(db.Integer, primary_key=True)
98 Added: name = db.Column(db.String(20), nullable=False)
99 Added: name_full = db.Column(db.String(20), nullable=False)
100 Added: nickname = db.Column(db.String(20), nullable=False)
101 Added: city = db.Column(db.String(20), nullable=False)
102 Added:
103 Added: def __repr__(self):
104 Added: return f"<Project {self.name}>"
105 Added:
106 Added:
107 Added: class Modules(db.Model):
108 Added: id = db.Column(db.Integer, primary_key=True)
109 Added: name = db.Column(db.String(20), unique=True, nullable=False)
110 Added: description = db.Column(db.String(50), nullable=False)
111 Added:
112 Added: def __init__(self, name, description, updated):
113 Added: self.name = name
114 Added: self.description = description
115 Added: self.updated = updated
116 Added:
117 Added: def __repr__(self):
118 Added: return f"<Module {self.name}>"
119 Added:
120 Added:
121 Added: class AddProduct(FlaskForm):
122 Added: # id used only by update/edit
123 Added: id = HiddenField()
124 Added: name = StringField("Product name", validators=[InputRequired()])
125 Added: supplier = SelectField(
126 Added: "Choose a supplier",
127 Added: choices=[
128 Added: ("", ""),
129 Added: ("Mister Brown", "Mister Brown"),
130 Added: ("Madame Cerise", "Madame Cerise"),
131 Added: ("Biton la Malice", "G. Biton la Malice"),
132 Added: ("Leroy Merlin", "Leroy Merlin"),
133 Added: ("other", "Other"),
134 Added: ],
135 Added: )
136 Added: price = FloatField("Retail price per unit")
137 Added: # updated - date - handled in the route function
138 Added: updated = HiddenField()
139 Added: submit = SubmitField("Add/Update Product")
140 Added:
141 Added:
142 Added: # add a new product to the database
143 Added: @app.route("/add_product", methods=["GET", "POST"])
144 Added: def add_product():
145 Added: form = AddProduct()
146 Added: if form.validate_on_submit():
147 Added: name = request.form["name"]
148 Added: supplier = request.form["supplier"]
149 Added: price = request.form["price"]
150 Added: # get today's date from function, above all the routes
151 Added: updated = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
152 Added: # the data to be inserted into FAPG model - the table, products
153 Added: record = Products(name, supplier, price, updated)
154 Added: # Flask-SQLAlchemy magic adds record to database
155 Added: db.session.add(record)
156 Added: db.session.commit()
157 Added: # create a message to send to the template
158 Added: message = f"The data for product {name} has been submitted."
159 Added: return render_template("add_product.html", message=message)
160 Added: else:
161 Added: # show validaton errors
162 Added: # see https://pythonprogramming.net/flash-flask-tutorial/
163 Added: for field, errors in form.errors.items():
164 Added: for error in errors:
165 Added: flash(
166 Added: "Error in {}: {}".format(getattr(form, field).label.text, error),
167 Added: "error",
168 Added: )
169 Added: return render_template("add_product.html", form=form)
170 Added:
171 Added:
172 Added: @app.route("/")
173 Added: @app.route("/fapg/home")
174 Added: def project():
175 Added: """This is our project welcome page."""
176 Added: michel = Users(
177 Added: name_first="Michel",
178 Added: name_last="Peter",
179 Added: email="le-boss@fapg.com",
180 Added: phone_mobile="00000000",
181 Added: phone_alternative="0000000000",
182 Added: updated="2022-04-21",
183 Added: )
184 Added: modules = Modules.query.all()
185 Added: print(module.name for module in modules)
186 Added: return render_template("home.html", user=michel, project=fapg, modules=modules)
187 Added:
188 Added:
189 Added: @app.route("/module/<module>")
190 Added: def render_module(module):
191 Added: modules = Modules.query.all()
192 Added: catalog = Products.query.all()
193 Added: user_modules = [module.name for module in modules]
194 Added: print(user_modules)
195 Added: # If a module was purchased by a user and added to their database,
196 Added: # they have access to the corresponding module route.
197 Added: if module in user_modules:
198 Added: return render_template(
199 Added: f"modules/{module}.html",
200 Added: modules=modules,
201 Added: catalog=catalog,
202 Added: )
203 Added: else:
204 Added: return render_template("errors/module-not-found.html", module=module)
205 Added:
206 Added:
207 Added: # If this file is executed as a script (i.e. double-clicked),
208 Added: # the Python interpreter will run the Flask process and begin serving
209 Added: # the web pages on the standard localhost address (127.0.0.1).
210 Added: # But if this file is called as a module by another Python script, it will not
211 Added: # serve content to the web pages, but the function definitions contained in
212 Added: # this file will be available to the calling script.
213 Added: # E.g. calling script will know what the yes() function is.
214 Added: if __name__ == "__main__":
215 Added: app.run(debug=True)
app/static/fonts/PublicSans-Black.otf
index 00000000..bbbaa26f 000000..100644

Binary files differ

app/static/fonts/PublicSans-BlackItalic.otf
index 00000000..46e3f71e 000000..100644

Binary files differ

app/static/fonts/PublicSans-Bold.otf
index 00000000..7a2b62bd 000000..100644

Binary files differ

app/static/fonts/PublicSans-BoldItalic.otf
index 00000000..718357f3 000000..100644

Binary files differ

app/static/fonts/PublicSans-ExtraBold.otf
index 00000000..09b52ddf 000000..100644

Binary files differ

app/static/fonts/PublicSans-ExtraBoldItalic.otf
index 00000000..5b332223 000000..100644

Binary files differ

app/static/fonts/PublicSans-ExtraLight.otf
index 00000000..49b407d8 000000..100644

Binary files differ

app/static/fonts/PublicSans-ExtraLightItalic.otf
index 00000000..c76e8558 000000..100644

Binary files differ

app/static/fonts/PublicSans-Italic.otf
index 00000000..38996ad9 000000..100644

Binary files differ

app/static/fonts/PublicSans-Light.otf
index 00000000..126544eb 000000..100644

Binary files differ

app/static/fonts/PublicSans-LightItalic.otf
index 00000000..1e6aa6f1 000000..100644

Binary files differ

app/static/fonts/PublicSans-Medium.otf
index 00000000..93507a5e 000000..100644

Binary files differ

app/static/fonts/PublicSans-MediumItalic.otf
index 00000000..f5ddb908 000000..100644

Binary files differ

app/static/fonts/PublicSans-Regular.otf
index 00000000..d2b3f169 000000..100644

Binary files differ

app/static/fonts/PublicSans-SemiBold.otf
index 00000000..4ab6b890 000000..100644

Binary files differ

app/static/fonts/PublicSans-SemiBoldItalic.otf
index 00000000..a28f6c0c 000000..100644

Binary files differ

app/static/fonts/PublicSans-Thin.otf
index 00000000..dee0ae29 000000..100644

Binary files differ

app/static/fonts/PublicSans-ThinItalic.otf
index 00000000..c6b481a0 000000..100644

Binary files differ

app/static/styles/style.css
index 00000000..ca60a7ba 000000..100644
@@ -0,0 +1,79 @@
1 Added: :root {
2 Added: --primary-color: #003B5C;
3 Added: --secondary-color: #C3D7EE;
4 Added: --home: #c8c8c8;
5 Added: --yes: #80FF80;
6 Added: --no: #FF8080;
7 Added: font-size: 18;
8 Added: --fast-speed: 0.2s;
9 Added: --med-speed: 0.4s;
10 Added: --slow-speed: 1s;
11 Added: }
12 Added:
13 Added: body {
14 Added: font-family: 'Public Sans', sans-serif;
15 Added: line-height: 1.2;
16 Added: margin: 0;
17 Added: padding: 0;
18 Added: }
19 Added:
20 Added: /* h1 { */
21 Added: /* margin-left: 2rem; */
22 Added: /* margin-right: 1rem; */
23 Added: /* } */
24 Added:
25 Added: /* h2 { */
26 Added: /* margin-left: 1rem; */
27 Added: /* margin-right: 1rem; */
28 Added: /* } */
29 Added:
30 Added: /* p { */
31 Added: /* margin-left: 1rem; */
32 Added: /* margin-right: 1rem; */
33 Added: /* } */
34 Added:
35 Added:
36 Added: @font-face {
37 Added: font-family: "Public Sans";
38 Added: src: url("/static/fonts/PublicSans-Regular.otf");
39 Added: }
40 Added:
41 Added: nav {
42 Added: display: flex;
43 Added: background: darkgrey;
44 Added: color: white;
45 Added: margin: 0.5em;
46 Added: padding: 0.5em;
47 Added: }
48 Added:
49 Added: h1 {
50 Added: margin: 0;
51 Added: }
52 Added:
53 Added: nav ul {
54 Added: display: flex;
55 Added: flex-wrap: wrap;
56 Added: margin: 0;
57 Added: justify-content: right;
58 Added: list-style: none;
59 Added: }
60 Added:
61 Added: nav ul li {
62 Added: margin: 0 0 0.5em 0;
63 Added: padding: 0.5em 0;
64 Added: }
65 Added:
66 Added: .button {
67 Added: height: 1em;
68 Added: padding: 0.5em;
69 Added: margin: 0 0.5em;
70 Added: background: dimgray;
71 Added: color: white;
72 Added: border-radius: 8px;
73 Added: text-decoration: none;
74 Added: }
75 Added:
76 Added: .button:hover {
77 Added: background: white;
78 Added: color: black;
79 Added: }
app/templates/add_product.html
index 00000000..ad7ea0a8 000000..100644
@@ -0,0 +1,44 @@
1 Added: {# -*- mode: jinja2; -*- #}
2 Added:
3 Added: {% extends "base.html" %}
4 Added: {% import "bootstrap/wtf.html" as wtf %}
5 Added:
6 Added: {% block content %}
7 Added:
8 Added: {% block title %}Add a New Product{% endblock %}
9 Added:
10 Added: {% if message %}
11 Added:
12 Added: {# the form was submitted and message exists #}
13 Added: <p class="lead"><strong>{{ message }}</strong></p>
14 Added: {# links #}
15 Added: <p><a href="{{ url_for('add_product') }}" class="button">Submit another product.</a></p>
16 Added: <p><a href="/fapg/home">Return to the index.</a></p>
17 Added:
18 Added: {% else %}
19 Added:
20 Added: {# the form is displayed when template opens via GET not POST #}
21 Added: <p class="lead alert alert-primary">Add a new sock to our inventory.</p>
22 Added: <p class="ml-4"><a href="/fapg/home" class="button">Return to the index.</a></p>
23 Added: {# show flash - based on WTForms validators
24 Added: see https://pythonprogramming.net/flash-flask-tutorial/
25 Added: get_flashed_messages() exists here because of flash()
26 Added: in the route function
27 Added: #}
28 Added: {% with errors = get_flashed_messages() %}
29 Added: {% if errors %}
30 Added: {% for err in errors %}
31 Added: <div class="alert alert-danger alert-dismissible" role="alert">
32 Added: <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
33 Added: {{ err }}
34 Added: </div>
35 Added: {% endfor %}
36 Added: {% endif %}
37 Added: {% endwith %}
38 Added: {# end of flash #}
39 Added:
40 Added: {# the form, thanks to WTForms #}
41 Added: {{ wtf.quick_form(form) }}
42 Added:
43 Added: {% endif %}
44 Added: {% endblock %}
app/templates/base.html
index 00000000..0b53b5c6 000000..100644
@@ -0,0 +1,30 @@
1 Added: {# -*- mode: jinja2; -*- #}
2 Added:
3 Added: <!doctype html>
4 Added:
5 Added: <html lang="en">
6 Added: <head>
7 Added: <meta charset="utf-8">
8 Added: <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to">
9 Added: <title>Farm Manager</title>
10 Added: <meta name="author" content="Marius Peter">
11 Added: <meta name="description" content="A draft page for Farm Manager.">
12 Added: <!-- <link rel="icon" href="/favicon.ico"> -->
13 Added: <link rel="stylesheet" href="{{ url_for('static', filename='styles/style.css') }}">
14 Added: </head>
15 Added: <body>
16 Added: <nav>
17 Added: <a href="/" class="button">Home</a>
18 Added: <h1>{% block title %}{% endblock %}</h1>
19 Added: <ul>
20 Added: {% for module in modules %}
21 Added: <li><a href="/module/{{ module.name }}" class="button">{{ module.name }}</a></li>
22 Added: {% endfor %}
23 Added: </ul>
24 Added: </nav>
25 Added: <div id="content">
26 Added: {% block content %}{% endblock %}
27 Added: </div>
28 Added: <!-- <script src="js/scripts.js"></script> -->
29 Added: </body>
30 Added: </html>
app/templates/errors/module-not-found.html
index 00000000..6496503e 000000..100644
@@ -0,0 +1,14 @@
1 Added: {% extends "base.html" %}
2 Added:
3 Added: {% block title %}
4 Added: Module not found
5 Added: {% endblock %}
6 Added:
7 Added: {% block content %}
8 Added: <p>
9 Added: No module found with name <strong>{{ module }}</strong>.
10 Added: </p>
11 Added: <p>
12 Added: If you'd like to suggest a module, please send us an e-mail
13 Added: </p>
14 Added: {% endblock %}
app/templates/home.html
index 00000000..a9bac514 000000..100644
@@ -0,0 +1,22 @@
1 Added: <!-- -*- mode: jinja2; -*- -->
2 Added:
3 Added: {% extends "base.html" %}
4 Added:
5 Added: {% block title %}
6 Added: Welcome, {{ user.name_first }}!
7 Added: {% endblock %}
8 Added:
9 Added:
10 Added: {% block content %}
11 Added:
12 Added: <p>You are logged in as <strong>{{ user }}</strong> on
13 Added: project <strong>{{ project }}</strong>.</p>
14 Added:
15 Added: <h2>Available modules</h2>
16 Added: <dl>
17 Added: {% for module in modules %}
18 Added: <dt style="font-weight: bold">{{ module.name }}</dt><dd>{{ module.description }}<dd>
19 Added: {% endfor %}
20 Added: </dl>
21 Added:
22 Added: {% endblock %}
app/templates/modules/calculator.html
index 00000000..f951a171 000000..100644
@@ -0,0 +1,8 @@
1 Added: {% extends "base.html" %}
2 Added: {% block title %}
3 Added: Calculator
4 Added: {% endblock %}
5 Added:
6 Added: {% block content %}
7 Added: <i>insert module content here.</i>
8 Added: {% endblock %}
app/templates/modules/catalog.html
index 00000000..857c654d 000000..100644
@@ -0,0 +1,35 @@
1 Added: <!-- -*- mode: jinja2; -*- -->
2 Added:
3 Added: {% extends "base.html" %}
4 Added: {% block title %}
5 Added: Catalog
6 Added: {% endblock %}
7 Added:
8 Added: {% block content %}
9 Added: <i>insert module content here.</i>
10 Added:
11 Added: <p>
12 Added: <a href="{{ url_for('add_product') }}" class="button">Add product to your catalog</a>
13 Added: </p>
14 Added:
15 Added: <table>
16 Added: <thead>
17 Added: <tr>
18 Added: <th>Name</th>
19 Added: <th>Price</th>
20 Added: <th>Supplier</th>
21 Added: <th>Updated</th>
22 Added: </tr>
23 Added: </thead>
24 Added: <tbody>
25 Added: {% for product in catalog %}
26 Added: <tr>
27 Added: <td>{{ product.name }}</td>
28 Added: <td>{{ product.price }}</td>
29 Added: <td>{{ product.supplier }}</td>
30 Added: <td>{{ product.updated }}</td>
31 Added: </tr>
32 Added: {% endfor %}
33 Added: </tbody>
34 Added: </table>
35 Added: {% endblock %}
app/templates/modules/creator.html
index 00000000..588f4286 000000..100644
@@ -0,0 +1,8 @@
1 Added: {% extends "base.html" %}
2 Added: {% block title %}
3 Added: Creator
4 Added: {% endblock %}
5 Added:
6 Added: {% block content %}
7 Added: <i>insert module content here.</i>
8 Added: {% endblock %}
app/templates/modules/logger.html
index 00000000..7ba93b38 000000..100644
@@ -0,0 +1,8 @@
1 Added: {% extends "base.html" %}
2 Added: {% block title %}
3 Added: Logger
4 Added: {% endblock %}
5 Added:
6 Added: {% block content %}
7 Added: <i>insert module content here.</i>
8 Added: {% endblock %}
app/templates/modules/stock.html
index 00000000..5d20d5c6 000000..100644
@@ -0,0 +1,8 @@
1 Added: {% extends "base.html" %}
2 Added: {% block title %}
3 Added: Stock
4 Added: {% endblock %}
5 Added:
6 Added: {% block content %}
7 Added: <i>insert module content here.</i>
8 Added: {% endblock %}