| # @name: __init__.py | |||||
| # @version: 0.1 | |||||
| # @creation_date: 2021-10-20 | |||||
| # @license: The MIT License <https://opensource.org/licenses/MIT> | |||||
| # @author: Simon Bowie <ad7588@coventry.ac.uk> | |||||
| # @purpose: Initialises the app, SQLAlchemy, and configuration variables | |||||
| # @acknowledgements: | |||||
| # https://www.digitalocean.com/community/tutorials/how-to-add-authentication-to-your-app-with-flask-login | |||||
| # Config stuff adapted from https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-iii-web-forms | |||||
| from flask import Flask | from flask import Flask | ||||
| from flask_sqlalchemy import SQLAlchemy | from flask_sqlalchemy import SQLAlchemy | ||||
| from flask_login import LoginManager | from flask_login import LoginManager | ||||
| def create_app(): | def create_app(): | ||||
| app = Flask(__name__) | app = Flask(__name__) | ||||
| # get config variables from OS environment variables: set in env file passed through Docker Compose | |||||
| app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY') | app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY') | ||||
| app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL') | app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL') | ||||
| # @name: auth.py | |||||
| # @version: 0.1 | |||||
| # @creation_date: 2021-10-20 | |||||
| # @license: The MIT License <https://opensource.org/licenses/MIT> | |||||
| # @author: Simon Bowie <ad7588@coventry.ac.uk> | |||||
| # @purpose: auth route for authorisation functions like logging in, signing up, and logging out | |||||
| # @acknowledgements: | |||||
| # https://www.digitalocean.com/community/tutorials/how-to-add-authentication-to-your-app-with-flask-login | |||||
| from flask import Blueprint, render_template, redirect, url_for, request, flash | from flask import Blueprint, render_template, redirect, url_for, request, flash | ||||
| from werkzeug.security import generate_password_hash, check_password_hash | from werkzeug.security import generate_password_hash, check_password_hash | ||||
| from flask_login import login_user, logout_user, login_required | from flask_login import login_user, logout_user, login_required | ||||
| auth = Blueprint('auth', __name__) | auth = Blueprint('auth', __name__) | ||||
| # routes for login page | |||||
| @auth.route('/login') | @auth.route('/login') | ||||
| def login(): | def login(): | ||||
| return render_template('login.html') | return render_template('login.html') | ||||
| password = request.form.get('password') | password = request.form.get('password') | ||||
| remember = True if request.form.get('remember') else False | remember = True if request.form.get('remember') else False | ||||
| # SQLAlchemy query to get user data based on their email | |||||
| user = User.query.filter_by(email=email).first() | user = User.query.filter_by(email=email).first() | ||||
| # check if the user actually exists | # check if the user actually exists | ||||
| login_user(user, remember=remember) | login_user(user, remember=remember) | ||||
| return redirect(url_for('main.profile')) | return redirect(url_for('main.profile')) | ||||
| # routes for signup page | |||||
| @auth.route('/signup') | @auth.route('/signup') | ||||
| def signup(): | def signup(): | ||||
| return render_template('signup.html') | return render_template('signup.html') | ||||
| return redirect(url_for('auth.login')) | return redirect(url_for('auth.login')) | ||||
| # route for logout function | |||||
| @auth.route('/logout') | @auth.route('/logout') | ||||
| @login_required | @login_required | ||||
| def logout(): | def logout(): |
| # @name: main.py | |||||
| # @version: 0.1 | |||||
| # @creation_date: 2021-10-20 | |||||
| # @license: The MIT License <https://opensource.org/licenses/MIT> | |||||
| # @author: Simon Bowie <ad7588@coventry.ac.uk> | |||||
| # @purpose: Main route for index and other pages | |||||
| # @acknowledgements: | |||||
| # https://www.digitalocean.com/community/tutorials/how-to-add-authentication-to-your-app-with-flask-login | |||||
| from flask import Blueprint, render_template | from flask import Blueprint, render_template | ||||
| from flask_login import login_required, current_user | from flask_login import login_required, current_user | ||||
| from . import db | from . import db | ||||
| main = Blueprint('main', __name__) | main = Blueprint('main', __name__) | ||||
| # route for index page | |||||
| @main.route('/') | @main.route('/') | ||||
| def index(): | def index(): | ||||
| return render_template('index.html') | return render_template('index.html') | ||||
| # route for profile page | |||||
| @main.route('/profile') | @main.route('/profile') | ||||
| @login_required | @login_required | ||||
| def profile(): | def profile(): |
| # @name: models.py | |||||
| # @version: 0.1 | |||||
| # @creation_date: 2021-10-20 | |||||
| # @license: The MIT License <https://opensource.org/licenses/MIT> | |||||
| # @author: Simon Bowie <ad7588@coventry.ac.uk> | |||||
| # @purpose: Database models for tables in the database | |||||
| # @acknowledgements: | |||||
| # https://www.digitalocean.com/community/tutorials/how-to-add-authentication-to-your-app-with-flask-login | |||||
| from flask_login import UserMixin | from flask_login import UserMixin | ||||
| from . import db | from . import db | ||||
| from datetime import datetime | from datetime import datetime | ||||
| # table for users | |||||
| class User(UserMixin, db.Model): | class User(UserMixin, db.Model): | ||||
| id = db.Column(db.Integer, primary_key=True) # primary keys are required by SQLAlchemy | id = db.Column(db.Integer, primary_key=True) # primary keys are required by SQLAlchemy | ||||
| email = db.Column(db.String(100), unique=True) | email = db.Column(db.String(100), unique=True) | ||||
| password = db.Column(db.String(100)) | password = db.Column(db.String(100)) | ||||
| name = db.Column(db.String(1000)) | name = db.Column(db.String(1000)) | ||||
| # table for tools | |||||
| class Tool(db.Model): | class Tool(db.Model): | ||||
| id = db.Column(db.Integer, primary_key=True) # primary keys are required by SQLAlchemy | id = db.Column(db.Integer, primary_key=True) # primary keys are required by SQLAlchemy | ||||
| created = db.Column(db.DateTime, default=datetime.utcnow) | created = db.Column(db.DateTime, default=datetime.utcnow) |
| <!-- | |||||
| # @name: base.html | |||||
| # @version: 0.1 | |||||
| # @creation_date: 2021-10-20 | |||||
| # @license: The MIT License <https://opensource.org/licenses/MIT> | |||||
| # @author: Simon Bowie <ad7588@coventry.ac.uk> | |||||
| # @purpose: Basic layout for all pages | |||||
| # @acknowledgements: | |||||
| # https://www.digitalocean.com/community/tutorials/how-to-make-a-web-application-using-flask-in-python-3 | |||||
| # Bootstrap 5.1.3: https://getbootstrap.com/ | |||||
| --> | |||||
| <!DOCTYPE html> | <!DOCTYPE html> | ||||
| <html> | <html> | ||||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
| <title>COPIM online toolkit</title> | <title>COPIM online toolkit</title> | ||||
| <!-- Bootstrap CSS --> | <!-- Bootstrap CSS --> | ||||
| <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> | |||||
| <!--<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">--> | |||||
| <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> | |||||
| </head> | </head> | ||||
| <body> | <body> |
| {% block content %} | {% block content %} | ||||
| <h2>{% block title %} {{ tool['name'] }} {% endblock %}</h2> | <h2>{% block title %} {{ tool['name'] }} {% endblock %}</h2> | ||||
| <span class="badge badge-primary">{{ tool['created'] }}</span> | |||||
| <span class="badge bg-primary">{{ tool['created'] }}</span> | |||||
| <p>{{ tool['description'] }}</p> | <p>{{ tool['description'] }}</p> | ||||
| {% endblock %} | {% endblock %} |
| <a href="{{ url_for('tool.show_tool', tool_id=tool['id']) }}"> | <a href="{{ url_for('tool.show_tool', tool_id=tool['id']) }}"> | ||||
| <h2>{{ tool['name'] }}</h2> | <h2>{{ tool['name'] }}</h2> | ||||
| </a> | </a> | ||||
| <span class="badge badge-primary">{{ tool['created'] }}</span> | |||||
| <span class="badge bg-primary">{{ tool['created'] }}</span> | |||||
| {% if current_user.is_authenticated %} | {% if current_user.is_authenticated %} | ||||
| <a href="{{ url_for('tool.edit_tool', tool_id=tool['id']) }}"> | <a href="{{ url_for('tool.edit_tool', tool_id=tool['id']) }}"> | ||||
| <span class="badge badge-warning">Edit</span> | |||||
| <span class="badge bg-warning">Edit</span> | |||||
| </a> | </a> | ||||
| {% endif %} | {% endif %} | ||||
| <hr> | <hr> |
| # @name: tool.py | |||||
| # @version: 0.1 | |||||
| # @creation_date: 2021-10-20 | |||||
| # @license: The MIT License <https://opensource.org/licenses/MIT> | |||||
| # @author: Simon Bowie <ad7588@coventry.ac.uk> | |||||
| # @purpose: tool route for tool-related functions and pages | |||||
| # @acknowledgements: | |||||
| # https://www.digitalocean.com/community/tutorials/how-to-make-a-web-application-using-flask-in-python-3 | |||||
| from flask import Blueprint, render_template, request, flash, redirect, url_for | from flask import Blueprint, render_template, request, flash, redirect, url_for | ||||
| from flask_login import login_required, current_user | from flask_login import login_required, current_user | ||||
| from .models import Tool | from .models import Tool | ||||
| tool = Blueprint('tool', __name__) | tool = Blueprint('tool', __name__) | ||||
| # function to retrieve data about a single tool from the database | |||||
| def get_tool(tool_id): | def get_tool(tool_id): | ||||
| tool = Tool.query.filter_by(id=tool_id).first() | tool = Tool.query.filter_by(id=tool_id).first() | ||||
| if tool is None: | if tool is None: | ||||
| abort(404) | abort(404) | ||||
| return tool | return tool | ||||
| # route for displaying all tools in database | |||||
| @tool.route('/tools') | @tool.route('/tools') | ||||
| def get_tools(): | def get_tools(): | ||||
| tools = Tool.query | tools = Tool.query | ||||
| return render_template('tools.html', tools=tools) | return render_template('tools.html', tools=tools) | ||||
| # route for displaying a single tool based on the ID in the database | |||||
| @tool.route('/tools/<int:tool_id>') | @tool.route('/tools/<int:tool_id>') | ||||
| def show_tool(tool_id): | def show_tool(tool_id): | ||||
| tool = get_tool(tool_id) | tool = get_tool(tool_id) | ||||
| return render_template('tool.html', tool=tool) | return render_template('tool.html', tool=tool) | ||||
| # route for editing a single tool based on the ID in the database | |||||
| @tool.route('/tools/<int:tool_id>/edit', methods=('GET', 'POST')) | @tool.route('/tools/<int:tool_id>/edit', methods=('GET', 'POST')) | ||||
| @login_required | @login_required | ||||
| def edit_tool(tool_id): | def edit_tool(tool_id): | ||||
| return render_template('edit.html', tool=tool) | return render_template('edit.html', tool=tool) | ||||
| # route for function to delete a single tool from the edit page | |||||
| @tool.route('/tools/<int:tool_id>/delete', methods=('POST',)) | |||||
| @login_required | |||||
| def delete_tool(tool_id): | |||||
| tool = get_tool(tool_id) | |||||
| deletion = Tool.query.get(tool_id) | |||||
| db.session.delete(deletion) | |||||
| db.session.commit() | |||||
| flash('Successfully deleted!') | |||||
| return redirect(url_for('tool.get_tools')) | |||||
| # route for creating a new tool | |||||
| @tool.route('/tools/create', methods=('GET', 'POST')) | @tool.route('/tools/create', methods=('GET', 'POST')) | ||||
| @login_required | @login_required | ||||
| def create_tool(): | def create_tool(): | ||||
| db.session.commit() | db.session.commit() | ||||
| return render_template('create.html') | return render_template('create.html') | ||||
| @tool.route('/tools/<int:tool_id>/delete', methods=('POST',)) | |||||
| @login_required | |||||
| def delete_tool(tool_id): | |||||
| tool = get_tool(tool_id) | |||||
| deletion = Tool.query.get(tool_id) | |||||
| db.session.delete(deletion) | |||||
| db.session.commit() | |||||
| flash('Successfully deleted!') | |||||
| return redirect(url_for('tool.get_tools')) |