Merge branch 'master' into 'main'
.Add feature 'Collapsing The Navigation Bar' See merge request ikt206-g-26v-devops/Group23/flask!1
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.pyc
|
||||||
60
README.md
Normal file
60
README.md
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# flask-example
|
||||||
|
|
||||||
|
A minimal web app developed with [Flask](http://flask.pocoo.org/) framework.
|
||||||
|
|
||||||
|
The main purpose is to introduce how to implement the essential elements in web application with Flask, including
|
||||||
|
|
||||||
|
- URL Building
|
||||||
|
|
||||||
|
- Authentication with Sessions
|
||||||
|
|
||||||
|
- Template & Template Inheritance
|
||||||
|
|
||||||
|
- Error Handling
|
||||||
|
|
||||||
|
- Integrating with *Bootstrap*
|
||||||
|
|
||||||
|
- Interaction with Database (SQLite)
|
||||||
|
|
||||||
|
- Invoking static resources
|
||||||
|
|
||||||
|
For more basic knowledge of Flask, you can refer to [a tutorial on Tutorialspoint](https://www.tutorialspoint.com/flask/).
|
||||||
|
|
||||||
|
|
||||||
|
## How to Run
|
||||||
|
|
||||||
|
- Step 1: Make sure you have Python
|
||||||
|
|
||||||
|
- Step 2: Install the requirements: `pip install -r requirements.txt`
|
||||||
|
|
||||||
|
- Step 3: Go to this app's directory and run `python app.py`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Details about This Toy App
|
||||||
|
|
||||||
|
There are three tabs in this toy app
|
||||||
|
|
||||||
|
- **Public**: this is a page which can be accessed by anyone, no matter if the user has logged in or not.
|
||||||
|
|
||||||
|
- **Private**: Only logged-in user can access this page. Otherwise the user will get a 401 error page.
|
||||||
|
|
||||||
|
- **Admin Page**: This part is only open to the user who logged in as "Admin". In this tab, the administrator can manage accounts (list, delete, or add).
|
||||||
|
|
||||||
|
|
||||||
|
A few accounts were set for testing, like ***admin*** (password: admin), ***test*** (password: 123456), etc. You can also delete or add accounts after you log in as ***admin***.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- http://flask.pocoo.org/
|
||||||
|
|
||||||
|
- https://www.tutorialspoint.com/flask/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Credict
|
||||||
|
Image private.jpg: https://commons.wikimedia.org/wiki/File:(315-365)_Locked_(6149414678).jpg
|
||||||
|
|
||||||
|
Image public.jpg: https://commons.wikimedia.org/wiki/File:Drown%3F!_(131380682).jpg
|
||||||
204
app.py
Normal file
204
app.py
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
import os
|
||||||
|
import datetime
|
||||||
|
import hashlib
|
||||||
|
from flask import Flask, session, url_for, redirect, render_template, request, abort, flash
|
||||||
|
from database import list_users, verify, delete_user_from_db, add_user
|
||||||
|
from database import read_note_from_db, write_note_into_db, delete_note_from_db, match_user_id_with_note_id
|
||||||
|
from database import image_upload_record, list_images_for_user, match_user_id_with_image_uid, delete_image_from_db
|
||||||
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config.from_object('config')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@app.errorhandler(401)
|
||||||
|
def FUN_401(error):
|
||||||
|
return render_template("page_401.html"), 401
|
||||||
|
|
||||||
|
@app.errorhandler(403)
|
||||||
|
def FUN_403(error):
|
||||||
|
return render_template("page_403.html"), 403
|
||||||
|
|
||||||
|
@app.errorhandler(404)
|
||||||
|
def FUN_404(error):
|
||||||
|
return render_template("page_404.html"), 404
|
||||||
|
|
||||||
|
@app.errorhandler(405)
|
||||||
|
def FUN_405(error):
|
||||||
|
return render_template("page_405.html"), 405
|
||||||
|
|
||||||
|
@app.errorhandler(413)
|
||||||
|
def FUN_413(error):
|
||||||
|
return render_template("page_413.html"), 413
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def FUN_root():
|
||||||
|
return render_template("index.html")
|
||||||
|
|
||||||
|
@app.route("/public/")
|
||||||
|
def FUN_public():
|
||||||
|
return render_template("public_page.html")
|
||||||
|
|
||||||
|
@app.route("/private/")
|
||||||
|
def FUN_private():
|
||||||
|
if "current_user" in session.keys():
|
||||||
|
notes_list = read_note_from_db(session['current_user'])
|
||||||
|
notes_table = zip([x[0] for x in notes_list],\
|
||||||
|
[x[1] for x in notes_list],\
|
||||||
|
[x[2] for x in notes_list],\
|
||||||
|
["/delete_note/" + x[0] for x in notes_list])
|
||||||
|
|
||||||
|
images_list = list_images_for_user(session['current_user'])
|
||||||
|
images_table = zip([x[0] for x in images_list],\
|
||||||
|
[x[1] for x in images_list],\
|
||||||
|
[x[2] for x in images_list],\
|
||||||
|
["/delete_image/" + x[0] for x in images_list])
|
||||||
|
|
||||||
|
return render_template("private_page.html", notes = notes_table, images = images_table)
|
||||||
|
else:
|
||||||
|
return abort(401)
|
||||||
|
|
||||||
|
@app.route("/admin/")
|
||||||
|
def FUN_admin():
|
||||||
|
if session.get("current_user", None) == "ADMIN":
|
||||||
|
user_list = list_users()
|
||||||
|
user_table = zip(range(1, len(user_list)+1),\
|
||||||
|
user_list,\
|
||||||
|
[x + y for x,y in zip(["/delete_user/"] * len(user_list), user_list)])
|
||||||
|
return render_template("admin.html", users = user_table)
|
||||||
|
else:
|
||||||
|
return abort(401)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/write_note", methods = ["POST"])
|
||||||
|
def FUN_write_note():
|
||||||
|
text_to_write = request.form.get("text_note_to_take")
|
||||||
|
write_note_into_db(session['current_user'], text_to_write)
|
||||||
|
|
||||||
|
return(redirect(url_for("FUN_private")))
|
||||||
|
|
||||||
|
@app.route("/delete_note/<note_id>", methods = ["GET"])
|
||||||
|
def FUN_delete_note(note_id):
|
||||||
|
if session.get("current_user", None) == match_user_id_with_note_id(note_id): # Ensure the current user is NOT operating on other users' note.
|
||||||
|
delete_note_from_db(note_id)
|
||||||
|
else:
|
||||||
|
return abort(401)
|
||||||
|
return(redirect(url_for("FUN_private")))
|
||||||
|
|
||||||
|
|
||||||
|
# Reference: http://flask.pocoo.org/docs/0.12/patterns/fileuploads/
|
||||||
|
ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif'])
|
||||||
|
def allowed_file(filename):
|
||||||
|
return '.' in filename and \
|
||||||
|
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
||||||
|
|
||||||
|
@app.route("/upload_image", methods = ['POST'])
|
||||||
|
def FUN_upload_image():
|
||||||
|
if request.method == 'POST':
|
||||||
|
# check if the post request has the file part
|
||||||
|
if 'file' not in request.files:
|
||||||
|
flash('No file part', category='danger')
|
||||||
|
return(redirect(url_for("FUN_private")))
|
||||||
|
file = request.files['file']
|
||||||
|
# if user does not select file, browser also submit a empty part without filename
|
||||||
|
if file.filename == '':
|
||||||
|
flash('No selected file', category='danger')
|
||||||
|
return(redirect(url_for("FUN_private")))
|
||||||
|
if file and allowed_file(file.filename):
|
||||||
|
filename = secure_filename(file.filename)
|
||||||
|
upload_time = str(datetime.datetime.now())
|
||||||
|
image_uid = hashlib.sha1((upload_time + filename).encode()).hexdigest()
|
||||||
|
# Save the image into UPLOAD_FOLDER
|
||||||
|
file.save(os.path.join(app.config['UPLOAD_FOLDER'], image_uid + "-" + filename))
|
||||||
|
# Record this uploading in database
|
||||||
|
image_upload_record(image_uid, session['current_user'], filename, upload_time)
|
||||||
|
return(redirect(url_for("FUN_private")))
|
||||||
|
|
||||||
|
return(redirect(url_for("FUN_private")))
|
||||||
|
|
||||||
|
@app.route("/delete_image/<image_uid>", methods = ["GET"])
|
||||||
|
def FUN_delete_image(image_uid):
|
||||||
|
if session.get("current_user", None) == match_user_id_with_image_uid(image_uid): # Ensure the current user is NOT operating on other users' note.
|
||||||
|
# delete the corresponding record in database
|
||||||
|
delete_image_from_db(image_uid)
|
||||||
|
# delete the corresponding image file from image pool
|
||||||
|
image_to_delete_from_pool = [y for y in [x for x in os.listdir(app.config['UPLOAD_FOLDER'])] if y.split("-", 1)[0] == image_uid][0]
|
||||||
|
os.remove(os.path.join(app.config['UPLOAD_FOLDER'], image_to_delete_from_pool))
|
||||||
|
else:
|
||||||
|
return abort(401)
|
||||||
|
return(redirect(url_for("FUN_private")))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/login", methods = ["POST"])
|
||||||
|
def FUN_login():
|
||||||
|
id_submitted = request.form.get("id").upper()
|
||||||
|
if (id_submitted in list_users()) and verify(id_submitted, request.form.get("pw")):
|
||||||
|
session['current_user'] = id_submitted
|
||||||
|
|
||||||
|
return(redirect(url_for("FUN_root")))
|
||||||
|
|
||||||
|
@app.route("/logout/")
|
||||||
|
def FUN_logout():
|
||||||
|
session.pop("current_user", None)
|
||||||
|
return(redirect(url_for("FUN_root")))
|
||||||
|
|
||||||
|
@app.route("/delete_user/<id>/", methods = ['GET'])
|
||||||
|
def FUN_delete_user(id):
|
||||||
|
if session.get("current_user", None) == "ADMIN":
|
||||||
|
if id == "ADMIN": # ADMIN account can't be deleted.
|
||||||
|
return abort(403)
|
||||||
|
|
||||||
|
# [1] Delete this user's images in image pool
|
||||||
|
images_to_remove = [x[0] for x in list_images_for_user(id)]
|
||||||
|
for f in images_to_remove:
|
||||||
|
image_to_delete_from_pool = [y for y in [x for x in os.listdir(app.config['UPLOAD_FOLDER'])] if y.split("-", 1)[0] == f][0]
|
||||||
|
os.remove(os.path.join(app.config['UPLOAD_FOLDER'], image_to_delete_from_pool))
|
||||||
|
# [2] Delele the records in database files
|
||||||
|
delete_user_from_db(id)
|
||||||
|
return(redirect(url_for("FUN_admin")))
|
||||||
|
else:
|
||||||
|
return abort(401)
|
||||||
|
|
||||||
|
@app.route("/add_user", methods = ["POST"])
|
||||||
|
def FUN_add_user():
|
||||||
|
if session.get("current_user", None) == "ADMIN": # only Admin should be able to add user.
|
||||||
|
# before we add the user, we need to ensure this is doesn't exsit in database. We also need to ensure the id is valid.
|
||||||
|
if request.form.get('id').upper() in list_users():
|
||||||
|
user_list = list_users()
|
||||||
|
user_table = zip(range(1, len(user_list)+1),\
|
||||||
|
user_list,\
|
||||||
|
[x + y for x,y in zip(["/delete_user/"] * len(user_list), user_list)])
|
||||||
|
return(render_template("admin.html", id_to_add_is_duplicated = True, users = user_table))
|
||||||
|
if " " in request.form.get('id') or "'" in request.form.get('id'):
|
||||||
|
user_list = list_users()
|
||||||
|
user_table = zip(range(1, len(user_list)+1),\
|
||||||
|
user_list,\
|
||||||
|
[x + y for x,y in zip(["/delete_user/"] * len(user_list), user_list)])
|
||||||
|
return(render_template("admin.html", id_to_add_is_invalid = True, users = user_table))
|
||||||
|
else:
|
||||||
|
add_user(request.form.get('id'), request.form.get('pw'))
|
||||||
|
return(redirect(url_for("FUN_admin")))
|
||||||
|
else:
|
||||||
|
return abort(401)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(debug=True, host="0.0.0.0")
|
||||||
3
config.py
Normal file
3
config.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
SECRET_KEY = "fdsafasd"
|
||||||
|
UPLOAD_FOLDER = "image_pool"
|
||||||
|
MAX_CONTENT_LENGTH = 16 * 1024 * 1024
|
||||||
161
database.py
Normal file
161
database.py
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
import sqlite3
|
||||||
|
import hashlib
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
user_db_file_location = "database_file/users.db"
|
||||||
|
note_db_file_location = "database_file/notes.db"
|
||||||
|
image_db_file_location = "database_file/images.db"
|
||||||
|
|
||||||
|
def list_users():
|
||||||
|
_conn = sqlite3.connect(user_db_file_location)
|
||||||
|
_c = _conn.cursor()
|
||||||
|
|
||||||
|
_c.execute("SELECT id FROM users;")
|
||||||
|
result = [x[0] for x in _c.fetchall()]
|
||||||
|
|
||||||
|
_conn.close()
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def verify(id, pw):
|
||||||
|
_conn = sqlite3.connect(user_db_file_location)
|
||||||
|
_c = _conn.cursor()
|
||||||
|
|
||||||
|
_c.execute("SELECT pw FROM users WHERE id = '" + id + "';")
|
||||||
|
result = _c.fetchone()[0] == hashlib.sha256(pw.encode()).hexdigest()
|
||||||
|
|
||||||
|
_conn.close()
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def delete_user_from_db(id):
|
||||||
|
_conn = sqlite3.connect(user_db_file_location)
|
||||||
|
_c = _conn.cursor()
|
||||||
|
_c.execute("DELETE FROM users WHERE id = ?;", (id))
|
||||||
|
_conn.commit()
|
||||||
|
_conn.close()
|
||||||
|
|
||||||
|
# when we delete a user FROM database USERS, we also need to delete all his or her notes data FROM database NOTES
|
||||||
|
_conn = sqlite3.connect(note_db_file_location)
|
||||||
|
_c = _conn.cursor()
|
||||||
|
_c.execute("DELETE FROM notes WHERE user = ?;", (id))
|
||||||
|
_conn.commit()
|
||||||
|
_conn.close()
|
||||||
|
|
||||||
|
# when we delete a user FROM database USERS, we also need to
|
||||||
|
# [1] delete all his or her images FROM image pool (done in app.py)
|
||||||
|
# [2] delete all his or her images records FROM database IMAGES
|
||||||
|
_conn = sqlite3.connect(image_db_file_location)
|
||||||
|
_c = _conn.cursor()
|
||||||
|
_c.execute("DELETE FROM images WHERE owner = ?;", (id))
|
||||||
|
_conn.commit()
|
||||||
|
_conn.close()
|
||||||
|
|
||||||
|
def add_user(id, pw):
|
||||||
|
_conn = sqlite3.connect(user_db_file_location)
|
||||||
|
_c = _conn.cursor()
|
||||||
|
|
||||||
|
_c.execute("INSERT INTO users values(?, ?)", (id.upper(), hashlib.sha256(pw.encode()).hexdigest()))
|
||||||
|
|
||||||
|
_conn.commit()
|
||||||
|
_conn.close()
|
||||||
|
|
||||||
|
def read_note_from_db(id):
|
||||||
|
_conn = sqlite3.connect(note_db_file_location)
|
||||||
|
_c = _conn.cursor()
|
||||||
|
|
||||||
|
command = "SELECT note_id, timestamp, note FROM notes WHERE user = '" + id.upper() + "';"
|
||||||
|
_c.execute(command)
|
||||||
|
result = _c.fetchall()
|
||||||
|
|
||||||
|
_conn.commit()
|
||||||
|
_conn.close()
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def match_user_id_with_note_id(note_id):
|
||||||
|
# Given the note id, confirm if the current user is the owner of the note which is being operated.
|
||||||
|
_conn = sqlite3.connect(note_db_file_location)
|
||||||
|
_c = _conn.cursor()
|
||||||
|
|
||||||
|
command = "SELECT user FROM notes WHERE note_id = '" + note_id + "';"
|
||||||
|
_c.execute(command)
|
||||||
|
result = _c.fetchone()[0]
|
||||||
|
|
||||||
|
_conn.commit()
|
||||||
|
_conn.close()
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def write_note_into_db(id, note_to_write):
|
||||||
|
_conn = sqlite3.connect(note_db_file_location)
|
||||||
|
_c = _conn.cursor()
|
||||||
|
|
||||||
|
current_timestamp = str(datetime.datetime.now())
|
||||||
|
_c.execute("INSERT INTO notes values(?, ?, ?, ?)", (id.upper(), current_timestamp, note_to_write, hashlib.sha1((id.upper() + current_timestamp).encode()).hexdigest()))
|
||||||
|
|
||||||
|
_conn.commit()
|
||||||
|
_conn.close()
|
||||||
|
|
||||||
|
def delete_note_from_db(note_id):
|
||||||
|
_conn = sqlite3.connect(note_db_file_location)
|
||||||
|
_c = _conn.cursor()
|
||||||
|
|
||||||
|
_c.execute("DELETE FROM notes WHERE note_id = ?;", (note_id))
|
||||||
|
|
||||||
|
_conn.commit()
|
||||||
|
_conn.close()
|
||||||
|
|
||||||
|
def image_upload_record(uid, owner, image_name, timestamp):
|
||||||
|
_conn = sqlite3.connect(image_db_file_location)
|
||||||
|
_c = _conn.cursor()
|
||||||
|
|
||||||
|
_c.execute("INSERT INTO images VALUES (?, ?, ?, ?)", (uid, owner, image_name, timestamp))
|
||||||
|
|
||||||
|
_conn.commit()
|
||||||
|
_conn.close()
|
||||||
|
|
||||||
|
def list_images_for_user(owner):
|
||||||
|
_conn = sqlite3.connect(image_db_file_location)
|
||||||
|
_c = _conn.cursor()
|
||||||
|
|
||||||
|
command = "SELECT uid, timestamp, name FROM images WHERE owner = '{0}'".format(owner)
|
||||||
|
_c.execute(command)
|
||||||
|
result = _c.fetchall()
|
||||||
|
|
||||||
|
_conn.commit()
|
||||||
|
_conn.close()
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def match_user_id_with_image_uid(image_uid):
|
||||||
|
# Given the note id, confirm if the current user is the owner of the note which is being operated.
|
||||||
|
_conn = sqlite3.connect(image_db_file_location)
|
||||||
|
_c = _conn.cursor()
|
||||||
|
|
||||||
|
command = "SELECT owner FROM images WHERE uid = '" + image_uid + "';"
|
||||||
|
_c.execute(command)
|
||||||
|
result = _c.fetchone()[0]
|
||||||
|
|
||||||
|
_conn.commit()
|
||||||
|
_conn.close()
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def delete_image_from_db(image_uid):
|
||||||
|
_conn = sqlite3.connect(image_db_file_location)
|
||||||
|
_c = _conn.cursor()
|
||||||
|
|
||||||
|
_c.execute("DELETE FROM images WHERE uid = ?;", (image_uid))
|
||||||
|
|
||||||
|
_conn.commit()
|
||||||
|
_conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print(list_users())
|
||||||
BIN
database_file/images.db
Normal file
BIN
database_file/images.db
Normal file
Binary file not shown.
BIN
database_file/notes.db
Normal file
BIN
database_file/notes.db
Normal file
Binary file not shown.
BIN
database_file/users.db
Normal file
BIN
database_file/users.db
Normal file
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
BIN
image_pool/cbebc37b8e9ae56d722fc3966bb78da4ce48f9a6-flask.png
Normal file
BIN
image_pool/cbebc37b8e9ae56d722fc3966bb78da4ce48f9a6-flask.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 208 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Flask==1.1.2
|
||||||
|
Werkzeug==1.0.1
|
||||||
|
markupsafe==2.0.1
|
||||||
6
static/css/bootstrap.min.css
vendored
Executable file
6
static/css/bootstrap.min.css
vendored
Executable file
File diff suppressed because one or more lines are too long
7
static/css/bootstrap.min.united.css
vendored
Normal file
7
static/css/bootstrap.min.united.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
static/img/flask-powered.png
Executable file
BIN
static/img/flask-powered.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
BIN
static/img/public.jpg
Normal file
BIN
static/img/public.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 83 KiB |
7
static/js/bootstrap.min.js
vendored
Normal file
7
static/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
static/js/jquery.min.js
vendored
Normal file
4
static/js/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
68
templates/admin.html
Normal file
68
templates/admin.html
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block page_title %}Admin Dashboard{% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
{{ super() }}
|
||||||
|
|
||||||
|
{# only invoked when failed adding new ID due to duplication #}
|
||||||
|
{% if id_to_add_is_duplicated %}
|
||||||
|
<div class="alert alert-dismissible alert-danger">
|
||||||
|
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||||
|
<strong>Warning!</strong> The account name already exists.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# only invoked when failed adding new ID due to invalid character #}
|
||||||
|
{% if id_to_add_is_invalid %}
|
||||||
|
<div class="alert alert-dismissible alert-danger">
|
||||||
|
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||||
|
<strong>Warning!</strong> The account name is invalid.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class = "container">
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<h3>Add Account</h3>
|
||||||
|
|
||||||
|
<form class="form-inline" action="/add_user" method='post'>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="id">ID</label>
|
||||||
|
<input type="text" class="form-control" name="id">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="pw">Password</label>
|
||||||
|
<input type="password" class="form-control" name="pw">
|
||||||
|
</div>
|
||||||
|
<br><br>
|
||||||
|
<button type="submit" class="btn">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<h3>Manage Existing Accounts</h3>
|
||||||
|
|
||||||
|
<table class="table small">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
{% for number, id, act in users %}
|
||||||
|
<tr>
|
||||||
|
<th> {{ number }} </th>
|
||||||
|
<td> {{ id }} </td>
|
||||||
|
<td><a href={{act}}>Delete</a></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
20
templates/index.html
Normal file
20
templates/index.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block page_title %}Welcome{% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
{{ super() }}
|
||||||
|
|
||||||
|
<p>This is a minimal web app developed with <a href="http://flask.pocoo.org/">Flask</a> framework.</p>
|
||||||
|
<p>The main purpose is to introduce how to implement the essential elements in web applications with Flask, including</p>
|
||||||
|
<ul>
|
||||||
|
<li>URL Building</li>
|
||||||
|
<li>Authentication with Sessions</li>
|
||||||
|
<li>Template & Template Inheritance</li>
|
||||||
|
<li>Error Handling</li>
|
||||||
|
<li>Integrating with Bootstrap</li>
|
||||||
|
<li>Interaction with Database (SQLite)</li>
|
||||||
|
<li>Invoking static resources</li>
|
||||||
|
<li>Upload files</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>For more basic knowledge of Flask, you can refer to <a href="https://www.tutorialspoint.com/flask/">a tutorial on Tutorialspoint</a>.</p>
|
||||||
|
{% endblock %}
|
||||||
87
templates/layout.html
Normal file
87
templates/layout.html
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
|
||||||
|
<html>
|
||||||
|
<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.united.css') }}">
|
||||||
|
<script src="{{ url_for('static', filename='js/jquery.min.js') }}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/bootstrap.min.js') }}"></script>
|
||||||
|
|
||||||
|
<title>Flask Example</title>
|
||||||
|
<nav class="navbar navbar-inverse">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="navbar-header">
|
||||||
|
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#myNavbar">
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
</button>
|
||||||
|
<a class="navbar-brand" href="/">Flask Example</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="collapse navbar-collapse" id="myNavbar">
|
||||||
|
<ul class="nav navbar-nav">
|
||||||
|
<li><a href="{{ url_for('FUN_root') }}">Home</a></li>
|
||||||
|
<li><a href="{{ url_for('FUN_public') }}">Public</a></li>
|
||||||
|
{% if session.get("current_user", None) != None %}
|
||||||
|
<li><a href="{{ url_for('FUN_private') }}">Private</a></li>
|
||||||
|
{% endif%}
|
||||||
|
{% if session.get("current_user", None) == "ADMIN" %}
|
||||||
|
<li><a href="{{ url_for('FUN_admin') }}">Admin Dashboard</a></li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul class="nav navbar-nav navbar-right">
|
||||||
|
{% if session.get("current_user", None) == None %}
|
||||||
|
<form action="/login" method="post" class="navbar-form navbar-right">
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" name="id" placeholder="User Name" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="password" name="pw" placeholder="Password" class="form-control">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-success">Log In</button>
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<li>
|
||||||
|
<a><b>{{ session.get("current_user") }}</b></a></li>
|
||||||
|
<li><a href="{{ url_for('FUN_logout') }}"><b><u>Logout</u></b></a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% if messages %}
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<div class="alert alert-{{ category }}">
|
||||||
|
{{ message }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<h1>{% block page_title %}{% endblock %}</h1>
|
||||||
|
|
||||||
|
<p>{% block body %}{% endblock %}</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='container'>
|
||||||
|
<hr>
|
||||||
|
Developed by <a href='https://github.com/XD-DENG'>XD-DENG</a>
|
||||||
|
<a href="http://flask.pocoo.org/"><img
|
||||||
|
src="{{ url_for('static', filename='img/flask-powered.png') }}"
|
||||||
|
border="0"
|
||||||
|
align="right"
|
||||||
|
alt="Flask powered"
|
||||||
|
title="Flask powered"></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</html>
|
||||||
6
templates/page_401.html
Normal file
6
templates/page_401.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block page_title %}Unauthorized(401){% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
{{ super() }}
|
||||||
|
You're not allowed to access.
|
||||||
|
{% endblock %}
|
||||||
6
templates/page_403.html
Normal file
6
templates/page_403.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block page_title %}Forbidden(403){% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
{{ super() }}
|
||||||
|
This operation is forbidden.
|
||||||
|
{% endblock %}
|
||||||
6
templates/page_404.html
Normal file
6
templates/page_404.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block page_title %}Not Found (404){% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
{{ super() }}
|
||||||
|
The resource can not be found.
|
||||||
|
{% endblock %}
|
||||||
6
templates/page_405.html
Normal file
6
templates/page_405.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block page_title %}Method not allowd (405){% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
{{ super() }}
|
||||||
|
The method of your request is not allowed.
|
||||||
|
{% endblock %}
|
||||||
6
templates/page_413.html
Normal file
6
templates/page_413.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block page_title %}Request if too big(413){% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
{{ super() }}
|
||||||
|
Please check the file size you're uploading.
|
||||||
|
{% endblock %}
|
||||||
76
templates/private_page.html
Normal file
76
templates/private_page.html
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block page_title %}Private Page{% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
{{ super() }}
|
||||||
|
|
||||||
|
<h4>You can take notes here. Only yourself can access them. They will be removed when your account is removed.</h4>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h3>Add Note</h3>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="textArea" class="col-lg-3 control-label">Note to Take</label>
|
||||||
|
<div class="col-lg-9">
|
||||||
|
<form action="/write_note" method="post">
|
||||||
|
<input class="form-control" name="text_note_to_take"></input>
|
||||||
|
<button type="submit" class="btn btn-success">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
{% if notes %}
|
||||||
|
<h3>Your Notes</h3>
|
||||||
|
<table class="table small">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Note ID</th>
|
||||||
|
<th>Timestamp</th>
|
||||||
|
<th>Note</th>
|
||||||
|
<th>Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
{% for note_id, timestamp, note, act in notes %}
|
||||||
|
<tr>
|
||||||
|
<td> {{ note_id }} </td>
|
||||||
|
<td> {{ timestamp }} </td>
|
||||||
|
<td> {{ note }} </td>
|
||||||
|
<td><a href={{act}}>Delete</a></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<h3>Upload Image</h3>
|
||||||
|
<form method='post' action='/upload_image' enctype=multipart/form-data>
|
||||||
|
<p><input type=file name=file>
|
||||||
|
<input type=submit value=Upload>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% if images %}
|
||||||
|
<h3>Your Images</h3>
|
||||||
|
<table class="table small">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Image ID</th>
|
||||||
|
<th>Timestamp</th>
|
||||||
|
<th>Image Name</th>
|
||||||
|
<th>Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
{% for image_id, timestamp, image_name, act in images %}
|
||||||
|
<tr>
|
||||||
|
<td> {{ image_id }} </td>
|
||||||
|
<td> {{ timestamp }} </td>
|
||||||
|
<td> {{ image_name }} </td>
|
||||||
|
<td><a href={{act}}>Delete</a></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
7
templates/public_page.html
Normal file
7
templates/public_page.html
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block page_title %}Public Page{% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
{{ super() }}
|
||||||
|
<img src="{{ url_for('static', filename='img/public.jpg') }}" class="img-circle" alt="Cinque Terre" width="304" height="236">
|
||||||
|
You can access this no matter whether you have logged in.
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user