diff options
| author | Gertjan van den Burg <gertjanvandenburg@gmail.com> | 2019-03-18 16:57:31 +0000 |
|---|---|---|
| committer | Gertjan van den Burg <gertjanvandenburg@gmail.com> | 2019-03-18 16:57:31 +0000 |
| commit | a3184c5d142848b5147811ed246e39545e978805 (patch) | |
| tree | c3b1d0e87dcbc2c8aad3ed531c81f9bc4117a992 | |
| parent | use bootstrap (diff) | |
| download | AnnotateChange-a3184c5d142848b5147811ed246e39545e978805.tar.gz AnnotateChange-a3184c5d142848b5147811ed246e39545e978805.zip | |
refactor
| -rw-r--r-- | annotate_change_v2.py | 6 | ||||
| -rw-r--r-- | app/__init__.py | 115 | ||||
| -rw-r--r-- | app/auth/__init__.py | 7 | ||||
| -rw-r--r-- | app/auth/email.py | 22 | ||||
| -rw-r--r-- | app/auth/forms.py (renamed from app/forms.py) | 0 | ||||
| -rw-r--r-- | app/auth/routes.py (renamed from app/routes.py) | 53 | ||||
| -rw-r--r-- | app/email.py | 22 | ||||
| -rw-r--r-- | app/errors.py | 13 | ||||
| -rw-r--r-- | app/errors/__init__.py | 7 | ||||
| -rw-r--r-- | app/errors/handlers.py | 16 | ||||
| -rw-r--r-- | app/main/__init__.py | 9 | ||||
| -rw-r--r-- | app/main/routes.py | 12 | ||||
| -rw-r--r-- | app/models.py | 6 | ||||
| -rw-r--r-- | app/templates/auth/login.html | 16 | ||||
| -rw-r--r-- | app/templates/auth/register.html (renamed from app/templates/register.html) | 0 | ||||
| -rw-r--r-- | app/templates/auth/reset_password.html (renamed from app/templates/reset_password.html) | 0 | ||||
| -rw-r--r-- | app/templates/auth/reset_password_request.html (renamed from app/templates/reset_password_request.html) | 0 | ||||
| -rw-r--r-- | app/templates/base.html | 8 | ||||
| -rw-r--r-- | app/templates/email/reset_password.html | 2 | ||||
| -rw-r--r-- | app/templates/email/reset_password.txt | 2 | ||||
| -rw-r--r-- | app/templates/errors/404.html (renamed from app/templates/404.html) | 2 | ||||
| -rw-r--r-- | app/templates/errors/500.html (renamed from app/templates/500.html) | 2 | ||||
| -rw-r--r-- | app/templates/login.html | 29 |
23 files changed, 203 insertions, 146 deletions
diff --git a/annotate_change_v2.py b/annotate_change_v2.py index d099b92..65959cb 100644 --- a/annotate_change_v2.py +++ b/annotate_change_v2.py @@ -1 +1,5 @@ -from app import app +# -*- coding: utf-8 -*- + +from app import create_app, db + +app = create_app() diff --git a/app/__init__.py b/app/__init__.py index 3ee019e..9908dd8 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -9,55 +9,82 @@ from logging.handlers import SMTPHandler, RotatingFileHandler from flask import Flask from flask_bootstrap import Bootstrap -from flask_sqlalchemy import SQLAlchemy -from flask_migrate import Migrate from flask_login import LoginManager from flask_mail import Mail +from flask_migrate import Migrate +from flask_sqlalchemy import SQLAlchemy from .config import Config -app = Flask(__name__) -app.config.from_object(Config) -db = SQLAlchemy(app) -migrate = Migrate(app, db) -login = LoginManager(app) -login.login_view = "login" -mail = Mail(app) -bootstrap = Bootstrap(app) - -from app import routes, models, errors - -if not app.debug: - if app.config["MAIL_SERVER"]: - auth = None - if app.config["MAIL_USERNAME"] or app.config["MAIL_PASSWORD"]: - auth = (app.config["MAIL_USERNAME"], app.config["MAIL_PASSWORD"]) - secure = None - if app.config["MAIL_USE_TLS"]: - secure = () - mail_handler = SMTPHandler( - mailhost=(app.config["MAIL_SERVER"], app.config["MAIL_PORT"]), - fromaddr="no-reply@" + app.config["MAIL_SERVER"], - toaddrs=app.config["ADMINS"], - subject="AnnotateChange Failure", - credentials=auth, - secure=secure, +db = SQLAlchemy() +migrate = Migrate() +login = LoginManager() +login.login_view = "auth.login" +mail = Mail() +bootstrap = Bootstrap() + + +def create_app(config_class=Config): + app = Flask(__name__) + app.config.from_object(config_class) + + db.init_app(app) + migrate.init_app(app, db) + login.init_app(app) + mail.init_app(app) + bootstrap.init_app(app) + + from app.errors import bp as errors_bp + + app.register_blueprint(errors_bp) + + from app.auth import bp as auth_bp + + app.register_blueprint(auth_bp, url_prefix="/auth") + + from app.main import bp as main_bp + + app.register_blueprint(main_bp) + + if not app.debug: + if app.config["MAIL_SERVER"]: + auth = None + if app.config["MAIL_USERNAME"] or app.config["MAIL_PASSWORD"]: + auth = ( + app.config["MAIL_USERNAME"], + app.config["MAIL_PASSWORD"], + ) + secure = None + if app.config["MAIL_USE_TLS"]: + secure = () + mail_handler = SMTPHandler( + mailhost=(app.config["MAIL_SERVER"], app.config["MAIL_PORT"]), + fromaddr="no-reply@" + app.config["MAIL_SERVER"], + toaddrs=app.config["ADMINS"], + subject="AnnotateChange Failure", + credentials=auth, + secure=secure, + ) + mail_handler.setLevel(logging.ERROR) + app.logger.addHandler(mail_handler) + + if not os.path.exists("logs"): + os.mkdir("logs") + file_handler = RotatingFileHandler( + "logs/annotatechange.log", maxBytes=10240, backupCount=10 ) - mail_handler.setLevel(logging.ERROR) - app.logger.addHandler(mail_handler) - - if not os.path.exists("logs"): - os.mkdir("logs") - file_handler = RotatingFileHandler( - "logs/annotatechange.log", maxBytes=10240, backupCount=10 - ) - file_handler.setFormatter( - logging.Formatter( - "%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]" + file_handler.setFormatter( + logging.Formatter( + "%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]" + ) ) - ) - file_handler.setLevel(logging.INFO) - app.logger.addHandler(file_handler) + file_handler.setLevel(logging.INFO) + app.logger.addHandler(file_handler) + + app.logger.setLevel(logging.INFO) + app.logger.info("AnnotateChange startup") + + return app + - app.logger.setLevel(logging.INFO) - app.logger.info("AnnotateChange startup") +from app import models diff --git a/app/auth/__init__.py b/app/auth/__init__.py new file mode 100644 index 0000000..c938693 --- /dev/null +++ b/app/auth/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- + +from flask import Blueprint + +bp = Blueprint('auth', __name__) + +from app.auth import routes diff --git a/app/auth/email.py b/app/auth/email.py new file mode 100644 index 0000000..9f6f9d0 --- /dev/null +++ b/app/auth/email.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- + +from threading import Thread + +from flask import current_app, render_template + +from app import mail + + +def send_password_reset_email(user): + token = user.get_reset_password_token() + send_email( + "[AnnotateChange] Reset your password", + sender=current_app.config["ADMINS"][0], + recipients=[user.email], + text_body=render_template( + "email/reset_password.txt", user=user, token=token + ), + html_body=render_template( + "email/reset_password.html", user=user, token=token + ), + ) diff --git a/app/forms.py b/app/auth/forms.py index 8f7662a..8f7662a 100644 --- a/app/forms.py +++ b/app/auth/forms.py diff --git a/app/routes.py b/app/auth/routes.py index ba07a02..88ff7a0 100644 --- a/app/routes.py +++ b/app/auth/routes.py @@ -7,56 +7,49 @@ from flask_login import current_user, login_user, logout_user, login_required from werkzeug.urls import url_parse -from app import app from app import db +from app.auth import bp -from app.forms import ( +from app.auth.forms import ( LoginForm, RegistrationForm, ResetPasswordRequestForm, ResetPasswordForm, ) from app.models import User -from app.email import send_password_reset_email +from app.auth.email import send_password_reset_email -@app.route("/") -@app.route("/index") -@login_required -def index(): - return render_template("index.html", title="Home") - - -@app.route("/login", methods=("GET", "POST")) +@bp.route("/login", methods=("GET", "POST")) def login(): if current_user.is_authenticated: current_user.last_active = datetime.datetime.utcnow() db.session.commit() - return redirect(url_for("index")) + return redirect(url_for("main.index")) form = LoginForm() if form.validate_on_submit(): user = User.query.filter_by(username=form.username.data).first() if user is None or not user.check_password(form.password.data): flash("Invalid username or password", category="error") - return redirect(url_for("login")) + return redirect(url_for("auth.login")) login_user(user, remember=form.remember_me.data) next_page = request.args.get("next") if not next_page or url_parse(next_page).netloc != "": - next_page = url_for("index") + next_page = url_for("main.index") return redirect(next_page) - return render_template("login.html", title="Sign In", form=form) + return render_template("auth/login.html", title="Sign In", form=form) -@app.route("/logout") +@bp.route("/logout") def logout(): logout_user() - return redirect(url_for("index")) + return redirect(url_for("main.index")) -@app.route("/register", methods=("GET", "POST")) +@bp.route("/register", methods=("GET", "POST")) def register(): if current_user.is_authenticated: - return redirect(url_for("index")) + return redirect(url_for("main.index")) form = RegistrationForm() if form.validate_on_submit(): user = User(username=form.username.data, email=form.email.data) @@ -64,37 +57,37 @@ def register(): db.session.add(user) db.session.commit() flash("Thank you, you are now a registered user!") - return redirect(url_for("login")) - return render_template("register.html", title="Register", form=form) + return redirect(url_for("auth.login")) + return render_template("auth/register.html", title="Register", form=form) -@app.route("/reset_password_request", methods=("GET", "POST")) +@bp.route("/reset_password_request", methods=("GET", "POST")) def reset_password_request(): if current_user.is_authenticated: - return redirect(url_for("index")) + return redirect(url_for("main.index")) form = ResetPasswordRequestForm() if form.validate_on_submit(): user = User.query.filter_by(email=form.email.data).first() if user: send_password_reset_email(user) flash("Check your email for the instructions to reset your password.") - return redirect(url_for("login")) + return redirect(url_for("auth.login")) return render_template( - "reset_password_request.html", title="Reset Password", form=form + "auth/reset_password_request.html", title="Reset Password", form=form ) -@app.route("/reset_password/<token>", methods=("GET", "POST")) +@bp.route("/reset_password/<token>", methods=("GET", "POST")) def reset_password(token): if current_user.is_authenticated: - return redirect(url_for("index")) + return redirect(url_for("main.index")) user = User.verify_reset_password_token(token) if not user: - return redirect(url_for("index")) + return redirect(url_for("main.index")) form = ResetPasswordForm() if form.validate_on_submit(): user.set_password(form.password.data) db.session.commit() flash("Your password has been reset.") - return redirect(url_for("login")) - return render_template("reset_password.html", form=form) + return redirect(url_for("auth.login")) + return render_template("auth/reset_password.html", form=form) diff --git a/app/email.py b/app/email.py index 8c71a50..7b97364 100644 --- a/app/email.py +++ b/app/email.py @@ -2,10 +2,9 @@ from threading import Thread -from flask import render_template +from flask import current_app from flask_mail import Message -from app import app from app import mail @@ -18,19 +17,6 @@ def send_email(subject, sender, recipients, text_body, html_body): msg = Message(subject, sender=sender, recipients=recipients) msg.body = text_body msg.html = html_body - Thread(target=send_async_email, args=(app, msg)).start() - - -def send_password_reset_email(user): - token = user.get_reset_password_token() - send_email( - "[AnnotateChange] Reset your password", - sender=app.config["ADMINS"][0], - recipients=[user.email], - text_body=render_template( - "email/reset_password.txt", user=user, token=token - ), - html_body=render_template( - "email/reset_password.html", user=user, token=token - ), - ) + Thread( + target=send_async_email, args=(current_app._get_current_object(), msg) + ).start() diff --git a/app/errors.py b/app/errors.py deleted file mode 100644 index f459038..0000000 --- a/app/errors.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- - -from flask import render_template -from app import app, db - -@app.errorhandler(404) -def not_found_error(error): - return render_template('404.html'), 404 - -@app.errorhandler(500) -def internal_error(error): - db.session.rollback() - return render_template('500.html'), 500 diff --git a/app/errors/__init__.py b/app/errors/__init__.py new file mode 100644 index 0000000..8a85dca --- /dev/null +++ b/app/errors/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- + +from flask import Blueprint + +bp = Blueprint("errors", __name__) + +from app.errors import handlers diff --git a/app/errors/handlers.py b/app/errors/handlers.py new file mode 100644 index 0000000..6c2b1e7 --- /dev/null +++ b/app/errors/handlers.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from flask import render_template +from app import db +from app.errors import bp + + +@bp.app_errorhandler(404) +def not_found_error(error): + return render_template("errors/404.html"), 404 + + +@bp.app_errorhandler(500) +def internal_error(error): + db.session.rollback() + return render_template("errors/500.html"), 500 diff --git a/app/main/__init__.py b/app/main/__init__.py new file mode 100644 index 0000000..2cb605e --- /dev/null +++ b/app/main/__init__.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- + +from flask import Blueprint + +bp = Blueprint('main', __name__) + +from app.main import routes + + diff --git a/app/main/routes.py b/app/main/routes.py new file mode 100644 index 0000000..5d14fc6 --- /dev/null +++ b/app/main/routes.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- + +from flask import render_template +from flask_login import login_required + +from app.main import bp + +@bp.route("/") +@bp.route("/index") +@login_required +def index(): + return render_template("index.html", title="Home") diff --git a/app/models.py b/app/models.py index cb90fda..ed9138f 100644 --- a/app/models.py +++ b/app/models.py @@ -4,11 +4,11 @@ import datetime import jwt import time +from flask import current_app from flask_login import UserMixin from werkzeug.security import generate_password_hash, check_password_hash -from app import app from app import db from app import login @@ -34,7 +34,7 @@ class User(UserMixin, db.Model): def get_reset_password_token(self, expires_in=600): return jwt.encode( {"reset_password": self.id, "exp": time.time() + expires_in}, - app.config["SECRET_KEY"], + current_app.config["SECRET_KEY"], algorithm="HS256", ).decode("utf-8") @@ -42,7 +42,7 @@ class User(UserMixin, db.Model): def verify_reset_password_token(token): try: _id = jwt.decode( - token, app.config["SECRET_KEY"], algorithms=["HS256"] + token, current_app.config["SECRET_KEY"], algorithms=["HS256"] )["reset_password"] except: return None diff --git a/app/templates/auth/login.html b/app/templates/auth/login.html new file mode 100644 index 0000000..27268b2 --- /dev/null +++ b/app/templates/auth/login.html @@ -0,0 +1,16 @@ +{% extends "base.html" %} +{% import 'bootstrap/wtf.html' as wtf %} + +{% block app_content %} +<h1>Sign In</h1> +<div class="row"> + <div class="col-md-4"> + {{ wtf.quick_form(form) }} + </div> +</div> +<p>New User? <a href="{{ url_for('auth.register') }}">Click to Register!</a></p> +<p> +Forgot your password? +<a href="{{ url_for('auth.reset_password_request') }}">Click here to reset it</a> +</p> +{% endblock %} diff --git a/app/templates/register.html b/app/templates/auth/register.html index c430b38..c430b38 100644 --- a/app/templates/register.html +++ b/app/templates/auth/register.html diff --git a/app/templates/reset_password.html b/app/templates/auth/reset_password.html index cab00c9..cab00c9 100644 --- a/app/templates/reset_password.html +++ b/app/templates/auth/reset_password.html diff --git a/app/templates/reset_password_request.html b/app/templates/auth/reset_password_request.html index c516141..c516141 100644 --- a/app/templates/reset_password_request.html +++ b/app/templates/auth/reset_password_request.html diff --git a/app/templates/base.html b/app/templates/base.html index ee03e3d..8e51695 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -14,17 +14,17 @@ <span class="icon-bar"></span> <span class="icon-bar"></span> </button> - <a class="navbar-brand" href="{{ url_for('index') }}">AnnotateChange</a> + <a class="navbar-brand" href="{{ url_for('main.index') }}">AnnotateChange</a> </div> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> - <li><a href="{{ url_for('index') }}">Home</a></li> + <li><a href="{{ url_for('main.index') }}">Home</a></li> </ul> <ul class="nav navbar-nav navbar-right"> {% if current_user.is_anonymous %} - <li><a href="{{ url_for('login') }}">Login</a></li> + <li><a href="{{ url_for('auth.login') }}">Login</a></li> {% else %} - <li><a href="{{ url_for('logout') }}">Logout</a></li> + <li><a href="{{ url_for('auth.logout') }}">Logout</a></li> {% endif %} </ul> </div> diff --git a/app/templates/email/reset_password.html b/app/templates/email/reset_password.html index f7403a5..5ce604d 100644 --- a/app/templates/email/reset_password.html +++ b/app/templates/email/reset_password.html @@ -1,7 +1,7 @@ <p>Dear {{ user.username }},</p> <p> To reset your password - <a href="{{ url_for('reset_password', token=token, _external=True) }}"> + <a href="{{ url_for('auth.reset_password', token=token, _external=True) }}"> click here </a>. </p> diff --git a/app/templates/email/reset_password.txt b/app/templates/email/reset_password.txt index da1330f..0f7b0e7 100644 --- a/app/templates/email/reset_password.txt +++ b/app/templates/email/reset_password.txt @@ -2,7 +2,7 @@ Dear {{ user.username }}, To reset your password click on the following link: -{{ url_for('reset_password', token=token, _external=True) }} +{{ url_for('auth.reset_password', token=token, _external=True) }} If you have not requested a password reset then you can simply ignore this email. diff --git a/app/templates/404.html b/app/templates/errors/404.html index 51295c4..80f5bd6 100644 --- a/app/templates/404.html +++ b/app/templates/errors/404.html @@ -2,5 +2,5 @@ {% block app_content %} <h1>Page Not Found</h1> - <p><a href="{{ url_for('index') }}">Home</p> + <p><a href="{{ url_for('main.index') }}">Home</p> {% endblock %} diff --git a/app/templates/500.html b/app/templates/errors/500.html index adda72b..5297e6a 100644 --- a/app/templates/500.html +++ b/app/templates/errors/500.html @@ -3,5 +3,5 @@ {% block app_content %} <h1>An unexpected error has occurred</h1> <p>The administrator has been notified, apologies for the inconvenience.</p> - <p><a href="{{ url_for('index') }}">Home</p> + <p><a href="{{ url_for('main.index') }}">Home</p> {% endblock %} diff --git a/app/templates/login.html b/app/templates/login.html deleted file mode 100644 index 9b862f6..0000000 --- a/app/templates/login.html +++ /dev/null @@ -1,29 +0,0 @@ -{% extends "base.html" %} - -{% block app_content %} - <h1>Sign In</h1> - <form action="" method="post" novalidate> - {{ form.hidden_tag() }} - <p> - {{ form.username.label }}<br> - {{ form.username(size=32) }} - {% for error in form.username.errors %} - <span style="color: red;">[{{ error }}]</span> - {% endfor %} - </p> - <p> - {{ form.password.label }}<br> - {{ form.password(size=32) }} - {% for error in form.username.errors %} - <span style="color: red;">[{{ error }}]</span> - {% endfor %} - </p> - <p>{{ form.remember_me() }} {{ form.remember_me.label }}</p> - <p>{{ form.submit() }}</p> - </form> - <p>New User? <a href="{{ url_for('register') }}">Click to Register!</a></p> - <p> - Forgot your password? - <a href="{{ url_for('reset_password_request') }}">Click here to reset it</a> - </p> -{% endblock %} |
