Working with DB, admin form, WTForms, .env
This commit is contained in:
parent
66133ac46c
commit
3f665df6a7
12 changed files with 263 additions and 25 deletions
5
.env
Normal file
5
.env
Normal file
|
@ -0,0 +1,5 @@
|
|||
MYSQL_HOST=localhost
|
||||
MUSQL_PORT=3306
|
||||
MYSQL_USER=darkcat09
|
||||
MYSQL_PASSWORD=
|
||||
MYSQL_DATABASE=${REPO_NAME_SNAKE}
|
6
db/schema.sql
Normal file
6
db/schema.sql
Normal file
|
@ -0,0 +1,6 @@
|
|||
CREATE TABLE IF NOT EXISTS ${REPO_NAME_SNAKE} (
|
||||
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
||||
email VARCHAR(32) NOT NULL,
|
||||
name VARCHAR(32) NOT NULL,
|
||||
age INTEGER NOT NULL
|
||||
);
|
43
flaskapp/admin.py
Normal file
43
flaskapp/admin.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
"""Admin routes"""
|
||||
|
||||
from flask import request, redirect
|
||||
from flask import render_template
|
||||
|
||||
from . import routes
|
||||
from . import forms
|
||||
from . import db
|
||||
|
||||
|
||||
class RouteAdmin(routes.Routes):
|
||||
"""Admin endpoints: GET and POST /add"""
|
||||
|
||||
def add_routes(self) -> None:
|
||||
"""Add admin routes"""
|
||||
|
||||
@self.app.route('/add', methods=['GET', 'POST'])
|
||||
def add_person():
|
||||
|
||||
form = forms.AddForm()
|
||||
|
||||
if request.method == 'GET':
|
||||
return render_template(
|
||||
'admin.html',
|
||||
form=form,
|
||||
)
|
||||
|
||||
if form.pswd.data != '1234':
|
||||
return 'Incorrect password', 403
|
||||
|
||||
cur = db.get_cursor()
|
||||
cur.execute(
|
||||
'insert into ${REPO_NAME_SNAKE} '
|
||||
'(email,name,age) values (%s,%s,%s)',
|
||||
(
|
||||
form.email.data,
|
||||
form.name.data,
|
||||
form.age.data,
|
||||
),
|
||||
)
|
||||
cur.close()
|
||||
|
||||
return redirect('/db')
|
|
@ -5,16 +5,21 @@ import secrets
|
|||
from typing import Optional
|
||||
from typing import Type, List
|
||||
|
||||
from flask import Flask
|
||||
from flaskext.mysql import MySQL # type: ignore
|
||||
from pathlib import Path
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from flask import Flask
|
||||
|
||||
from . import exts
|
||||
from .routes import Routes
|
||||
from . import pages
|
||||
from . import admin
|
||||
from . import errors
|
||||
from . import db
|
||||
|
||||
routes: List[Type[Routes]] = [
|
||||
pages.RoutePages,
|
||||
admin.RouteAdmin,
|
||||
errors.RouteErrors,
|
||||
]
|
||||
|
||||
|
@ -29,6 +34,11 @@ def create_app() -> Flask:
|
|||
template_folder='../templates',
|
||||
instance_relative_config=True,
|
||||
)
|
||||
# Load sample configuation
|
||||
if app.debug:
|
||||
load_dotenv(
|
||||
Path(__file__).parent.parent / '.env'
|
||||
)
|
||||
# Get the token from environment
|
||||
# or generate it using secrets
|
||||
app.config['SECRET_KEY'] = os.getenv(
|
||||
|
@ -36,16 +46,23 @@ def create_app() -> Flask:
|
|||
secrets.token_hex(32),
|
||||
)
|
||||
|
||||
# Setup MySQL database
|
||||
# Configurate MySQL database
|
||||
db_name = os.getenv('MYSQL_DATABASE', '${REPO_NAME_SNAKE}')
|
||||
app.config.update({
|
||||
'MYSQL_DATABASE_HOST': os.getenv('DB_HOST', 'localhost'),
|
||||
'MYSQL_DATABASE_PORT': parseint(os.getenv('DB_PORT'), 3306),
|
||||
'MYSQL_DATABASE_USER': os.getenv('DB_USER', 'root'),
|
||||
'MYSQL_DATABASE_PASSWORD': os.getenv('DB_PASSWORD', ''),
|
||||
'MYSQL_DATABASE_DB': os.getenv('DB_DATABASE', '${REPO_NAME_SNAKE}'),
|
||||
'MYSQL_DATABASE_HOST': os.getenv('MYSQL_HOST', 'localhost'),
|
||||
'MYSQL_DATABASE_PORT': parseint(os.getenv('MYSQL_PORT'), 3306),
|
||||
'MYSQL_DATABASE_USER': os.getenv('MYSQL_USER', 'root'),
|
||||
'MYSQL_DATABASE_PASSWORD': os.getenv('MYSQL_PASSWORD', ''),
|
||||
# 'MYSQL_DATABASE_DB': db_name,
|
||||
})
|
||||
sql = MySQL(app)
|
||||
db.set_db(sql)
|
||||
|
||||
# Load extensions
|
||||
for ext in exts.exts:
|
||||
ext.init_app(app)
|
||||
|
||||
db.create_db(db_name)
|
||||
with app.app_context():
|
||||
db.init_db()
|
||||
|
||||
# Create instance/ directory
|
||||
try:
|
||||
|
|
|
@ -1,25 +1,76 @@
|
|||
"""Simple wrapper for getting/setting
|
||||
db key in global Flask object called `g`"""
|
||||
"""Works with MySQL database objects"""
|
||||
|
||||
from flask import g
|
||||
from flaskext.mysql import MySQL # type: ignore
|
||||
from typing import List, Optional
|
||||
|
||||
from flask import current_app
|
||||
|
||||
from pymysql import Connection
|
||||
from pymysql.cursors import Cursor
|
||||
|
||||
from . import exts
|
||||
|
||||
db_obj: List[Optional[Connection]] = [None]
|
||||
|
||||
|
||||
def set_db(db: MySQL) -> None:
|
||||
"""Add the database to `g` object
|
||||
def init_db() -> None:
|
||||
"""Initializes MySQL database
|
||||
from schema.sql file"""
|
||||
|
||||
cur = get_cursor()
|
||||
|
||||
with current_app.open_resource('../db/schema.sql') as f:
|
||||
|
||||
schema: bytes = f.read() # type: ignore
|
||||
schema_str = schema.decode('utf-8')
|
||||
|
||||
for query in schema_str.split(';'):
|
||||
|
||||
query = query.strip()
|
||||
if query == '' or query.startswith('--'):
|
||||
continue
|
||||
|
||||
cur.execute(query)
|
||||
|
||||
cur.close()
|
||||
|
||||
|
||||
def create_db(name: str) -> None:
|
||||
"""Create the database if not exists
|
||||
|
||||
Args:
|
||||
db (MySQL): MySQL database
|
||||
name (str): Database name
|
||||
"""
|
||||
|
||||
g.db = db
|
||||
sql = get_db()
|
||||
cur = sql.cursor(Cursor)
|
||||
cur.execute(
|
||||
f'create database if not exists {name}'
|
||||
)
|
||||
cur.close()
|
||||
|
||||
sql.select_db(name)
|
||||
|
||||
|
||||
def get_db() -> MySQL:
|
||||
"""Get the database from `g` object
|
||||
def get_db() -> Connection:
|
||||
"""Get MySQL connection object
|
||||
|
||||
Returns:
|
||||
MySQL database
|
||||
"""
|
||||
|
||||
return g.db
|
||||
if db_obj[0] is None:
|
||||
db_obj[0] = exts.sql.connect()
|
||||
return db_obj[0] # type: ignore
|
||||
|
||||
|
||||
def get_cursor() -> Cursor:
|
||||
"""Get MySQL database cursor object
|
||||
for executing commands.
|
||||
Equivalent to:
|
||||
```
|
||||
conn = db.get_db()
|
||||
cur = conn.cursor()
|
||||
```
|
||||
"""
|
||||
|
||||
return get_db().cursor()
|
||||
|
|
7
flaskapp/exts.py
Normal file
7
flaskapp/exts.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
"""Flask extensions list"""
|
||||
|
||||
from flaskext.mysql import MySQL # type: ignore
|
||||
|
||||
|
||||
sql = MySQL(autocommit=True)
|
||||
exts = [sql]
|
25
flaskapp/forms.py
Normal file
25
flaskapp/forms.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
"""Flask app forms"""
|
||||
|
||||
from flask_wtf import FlaskForm # type: ignore
|
||||
from wtforms import StringField # type: ignore
|
||||
from wtforms import IntegerField
|
||||
from wtforms import PasswordField
|
||||
from wtforms.validators import DataRequired
|
||||
|
||||
|
||||
class AddForm(FlaskForm):
|
||||
"""Sample form for adding users to DB"""
|
||||
|
||||
pswd = PasswordField('Admin password (1234)')
|
||||
email = StringField(
|
||||
'Person\'s email',
|
||||
validators=[DataRequired()],
|
||||
)
|
||||
name = StringField(
|
||||
'Name',
|
||||
validators=[DataRequired()],
|
||||
)
|
||||
age = IntegerField(
|
||||
'Age',
|
||||
validators=[DataRequired()],
|
||||
)
|
|
@ -3,6 +3,7 @@
|
|||
from flask import render_template
|
||||
|
||||
from . import routes
|
||||
from . import db
|
||||
|
||||
|
||||
class RoutePages(routes.Routes):
|
||||
|
@ -13,3 +14,16 @@ class RoutePages(routes.Routes):
|
|||
@self.app.route('/')
|
||||
def index():
|
||||
return render_template('index.html')
|
||||
|
||||
@self.app.route('/db')
|
||||
def table():
|
||||
|
||||
cur = db.get_cursor()
|
||||
cur.execute('SELECT * FROM ${REPO_NAME_SNAKE}')
|
||||
rows = cur.fetchall()
|
||||
cur.close()
|
||||
|
||||
return render_template(
|
||||
'table.html',
|
||||
rows=rows,
|
||||
)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
flask==2.2.2
|
||||
flask-mysql==1.5.2
|
||||
flask-wtf==1.1.1
|
||||
gunicorn==20.1.0
|
||||
python-dotenv==0.21.1
|
||||
|
|
|
@ -9,14 +9,17 @@ body {
|
|||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
background: #fff;
|
||||
color: #000;
|
||||
--bg: #fff;
|
||||
--fg: #000;
|
||||
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background: #202023;
|
||||
color: #eee;
|
||||
--bg: #202023;
|
||||
--fg: #eee;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,3 +32,29 @@ a {
|
|||
a:hover {
|
||||
filter: brightness(120%);
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
form > div {
|
||||
margin-top: 5px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: end;
|
||||
}
|
||||
form > div > label {
|
||||
width: 50%;
|
||||
margin-right: 10px;
|
||||
}
|
||||
form > div > input {
|
||||
width: 50%;
|
||||
border: 1px solid var(--fg);
|
||||
outline: none;
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
}
|
||||
form > div > input:hover,
|
||||
form > div > input:focus {
|
||||
filter: brightness(130%);
|
||||
}
|
||||
|
|
21
templates/admin.html
Normal file
21
templates/admin.html
Normal file
|
@ -0,0 +1,21 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Admin{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Add a person to DB</h1>
|
||||
<form action="/add" method="post">
|
||||
{{ form.hidden_tag() }}
|
||||
{% for field in form %}
|
||||
{% if field.name != 'csrf_token' %}
|
||||
<div>
|
||||
{{ field.label }}
|
||||
{{ field }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<div>
|
||||
<input type="submit" value="Add">
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
18
templates/table.html
Normal file
18
templates/table.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Database{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Sample database</h1>
|
||||
<table>
|
||||
<tbody>
|
||||
{% for row in rows %}
|
||||
<tr>
|
||||
{% for cell in row %}
|
||||
<td>{{ cell }}</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
Loading…
Add table
Reference in a new issue