From b435b159e39a2eab976a2b070a29dae4a91aa3a3 Mon Sep 17 00:00:00 2001 From: crunk Date: Sat, 30 Mar 2024 18:09:04 +0100 Subject: [PATCH] moved form code to application folder --- library/application/forms/borrowform.py | 26 ++++ .../application/forms/forgotpasswordform.py | 14 +++ library/application/forms/loginform.py | 17 +++ library/application/forms/registerform.py | 37 ++++++ .../application/forms/resetpasswordform.py | 22 ++++ library/application/forms/uploadform.py | 76 ++++++++++++ library/application/user/forgotpassword.py | 6 +- library/application/user/loginuser.py | 4 +- library/application/user/registeruser.py | 2 +- library/application/user/resetpassword.py | 2 +- library/migrations/README | 1 + library/migrations/alembic.ini | 50 ++++++++ library/migrations/env.py | 113 ++++++++++++++++++ library/migrations/script.py.mako | 24 ++++ library/migrations/versions/3105f85a9d8e_.py | 28 +++++ library/page.py | 11 +- library/static/css/style.css | 3 +- 17 files changed, 422 insertions(+), 14 deletions(-) create mode 100644 library/application/forms/borrowform.py create mode 100644 library/application/forms/forgotpasswordform.py create mode 100644 library/application/forms/loginform.py create mode 100644 library/application/forms/registerform.py create mode 100644 library/application/forms/resetpasswordform.py create mode 100644 library/application/forms/uploadform.py create mode 100644 library/migrations/README create mode 100644 library/migrations/alembic.ini create mode 100644 library/migrations/env.py create mode 100644 library/migrations/script.py.mako create mode 100644 library/migrations/versions/3105f85a9d8e_.py diff --git a/library/application/forms/borrowform.py b/library/application/forms/borrowform.py new file mode 100644 index 0000000..22e5431 --- /dev/null +++ b/library/application/forms/borrowform.py @@ -0,0 +1,26 @@ +"""Form object declaration.""" +from flask_wtf import FlaskForm +from wtforms import StringField, SubmitField, validators +from wtforms.validators import Length + + +class BorrowForm(FlaskForm): + """Borrow a publication form.""" + + borrowed = StringField( + "Fill in your name if you're going to borrow this publication.", + [ + validators.InputRequired(), + Length( + min=3, message="Just so we know who is borrowing this book." + ), + ], + ) + secret = StringField( + "Librarians secret:", + [ + validators.InputRequired(), + Length(min=2, message="Fill in the secret to unlock to library."), + ], + ) + submit = SubmitField("Borrow") diff --git a/library/application/forms/forgotpasswordform.py b/library/application/forms/forgotpasswordform.py new file mode 100644 index 0000000..50db65e --- /dev/null +++ b/library/application/forms/forgotpasswordform.py @@ -0,0 +1,14 @@ +"""Forgotten password form to help user.""" +from flask_wtf import FlaskForm +from wtforms import StringField, SubmitField, validators +from wtforms.validators import Email, Length + + +class ForgotPasswordForm(FlaskForm): + """Forgotten password csv-library form""" + + email = StringField( + "Email address:", + validators=[validators.InputRequired(), Email(), Length(6, 64)], + ) + submit = SubmitField("Send email") diff --git a/library/application/forms/loginform.py b/library/application/forms/loginform.py new file mode 100644 index 0000000..80f5580 --- /dev/null +++ b/library/application/forms/loginform.py @@ -0,0 +1,17 @@ +"""Login form to validate user.""" +from flask_wtf import FlaskForm +from wtforms import PasswordField, StringField, SubmitField, validators +from wtforms.validators import Email, Length + + +class LoginForm(FlaskForm): + """Login csv-library form""" + + email = StringField( + "Email address:", + validators=[validators.InputRequired(), Email(), Length(6, 64)], + ) + password = PasswordField( + "Password:", validators=[validators.InputRequired()] + ) + submit = SubmitField("Sign In") diff --git a/library/application/forms/registerform.py b/library/application/forms/registerform.py new file mode 100644 index 0000000..c90d27b --- /dev/null +++ b/library/application/forms/registerform.py @@ -0,0 +1,37 @@ +"""Register form to make a new user.""" +from flask_wtf import FlaskForm +from wtforms import PasswordField, StringField, SubmitField, validators +from wtforms.validators import Email, EqualTo, Length, ValidationError + + +class RegisterForm(FlaskForm): + """Register for csv-library form""" + + username = StringField( + "Username:", + validators=[validators.InputRequired(), Length(3, 150)], + ) + + email = StringField( + "Email address:", + validators=[ + validators.InputRequired(), + Email(), + Length(6, 64), + ], + ) + + password = PasswordField( + "New password:", + validators=[validators.InputRequired(), Length(12, 72)], + ) + + confirmpassword = PasswordField( + "Confirm your password:", + validators=[ + validators.InputRequired(), + Length(12, 72), + EqualTo("password", message="Passwords must match !"), + ], + ) + submit = SubmitField("Register to the library") diff --git a/library/application/forms/resetpasswordform.py b/library/application/forms/resetpasswordform.py new file mode 100644 index 0000000..cc8a302 --- /dev/null +++ b/library/application/forms/resetpasswordform.py @@ -0,0 +1,22 @@ +"""Reset Password Form form to reset a users PasswordField.""" +from flask_wtf import FlaskForm +from wtforms import PasswordField, SubmitField, validators +from wtforms.validators import EqualTo, Length + + +class ResetPasswordForm(FlaskForm): + """ResetPassword for csv-library form""" + + password = PasswordField( + "New password:", + validators=[validators.InputRequired(), Length(12, 72)], + ) + confirmpassword = PasswordField( + "Confirm your password:", + validators=[ + validators.InputRequired(), + Length(12, 72), + EqualTo("password", message="Passwords must match !"), + ], + ) + submit = SubmitField("Reset your password") diff --git a/library/application/forms/uploadform.py b/library/application/forms/uploadform.py new file mode 100644 index 0000000..b2b28d2 --- /dev/null +++ b/library/application/forms/uploadform.py @@ -0,0 +1,76 @@ +"""Form object declaration.""" +from flask_wtf import FlaskForm +from flask_wtf.file import FileAllowed, FileField +from wtforms import ( + IntegerField, + RadioField, + StringField, + SubmitField, + validators, +) +from wtforms.validators import Length, NumberRange + + +class PublicationForm(FlaskForm): + """Publication upload form.""" + + uploadpublication = StringField( + "Title of the publication:", + [ + validators.InputRequired(), + Length( + min=3, message="A publication in the library needs a title." + ), + ], + ) + author = StringField( + "The author or editor:", + [ + validators.InputRequired(), + Length( + min=3, + message=( + "If the author or editor is not known just type unkown." + ), + ), + ], + ) + year = IntegerField( + "Year:", + [validators.InputRequired(), NumberRange(min=0, max=2050)], + default=2023, + ) + fields = StringField("Fields:") + type = StringField( + "Type of publication:", + [ + validators.InputRequired(), + Length( + min=3, + message=( + "Here you can use terms such as zine, paperback," + " hardcover." + ), + ), + ], + ) + publishers = StringField("Publishers:") + license = StringField("License:") + highlights = StringField("Highlights from the publication:") + comments = StringField("Comments on the publication:") + image = FileField( + "Image of the book:", + validators=[FileAllowed(["jpg", "png", "gif"], "Images only!")], + ) + pdf = FileField( + "Pdf of the book:", + validators=[FileAllowed(["pdf"], "Only pdf uploads supported")], + ) + secret = StringField( + "Librarians secret:", + [ + validators.InputRequired(), + Length(min=2, message="Fill in the secret to unlock to library."), + ], + ) + submit = SubmitField("Submit") diff --git a/library/application/user/forgotpassword.py b/library/application/user/forgotpassword.py index c15187e..81f72f9 100644 --- a/library/application/user/forgotpassword.py +++ b/library/application/user/forgotpassword.py @@ -4,7 +4,7 @@ from uuid import uuid1 from app import db from flask import render_template from flask_mail import Message -from forms.forgotpasswordform import ForgotPasswordForm +from application.forms.forgotpasswordform import ForgotPasswordForm from sqlalchemy.exc import ( DatabaseError, DataError, @@ -14,7 +14,7 @@ from sqlalchemy.exc import ( from application.models.usermodel import User -def ForgotPassword(mail): +def ForgotPassword(): forgotpasswordform = ForgotPasswordForm() if forgotpasswordform.validate_on_submit(): user = User.query.filter_by( @@ -29,7 +29,7 @@ def ForgotPassword(mail): contain any new mail, please check your spam folder.)""" ) return render_template( - "forgotpassword.html", forgotpasswordform=forgotpasswordform + "user/forgotpassword.html", forgotpasswordform=forgotpasswordform ) diff --git a/library/application/user/loginuser.py b/library/application/user/loginuser.py index e07009a..d2d518f 100644 --- a/library/application/user/loginuser.py +++ b/library/application/user/loginuser.py @@ -1,7 +1,7 @@ from flask import abort, flash, redirect, render_template, request, url_for from flask_bcrypt import check_password_hash from flask_login import login_user -from forms.loginform import LoginForm +from application.forms.loginform import LoginForm from application.models.usermodel import User @@ -23,7 +23,7 @@ def LoginUser(): else: flash("Invalid email or password!", "danger") loginform.password.errors.append("Invalid email or password!") - return render_template("login.html", loginform=loginform) + return render_template("user/login.html", loginform=loginform) except Exception as e: flash(e, "danger") return render_template("user/login.html", loginform=loginform) diff --git a/library/application/user/registeruser.py b/library/application/user/registeruser.py index 4381053..34c7488 100644 --- a/library/application/user/registeruser.py +++ b/library/application/user/registeruser.py @@ -2,7 +2,7 @@ from app import db from flask import flash, redirect, render_template, url_for from flask_bcrypt import generate_password_hash from flask_login import login_user -from forms.registerform import RegisterForm +from application.forms.registerform import RegisterForm from sqlalchemy.exc import ( DatabaseError, DataError, diff --git a/library/application/user/resetpassword.py b/library/application/user/resetpassword.py index 67a7cf1..15f5cb8 100644 --- a/library/application/user/resetpassword.py +++ b/library/application/user/resetpassword.py @@ -4,7 +4,7 @@ from app import db from flask import flash, redirect, render_template, url_for from flask_bcrypt import generate_password_hash from flask_login import login_user -from forms.resetpasswordform import ResetPasswordForm +from application.forms.resetpasswordform import ResetPasswordForm from sqlalchemy.exc import ( DatabaseError, DataError, diff --git a/library/migrations/README b/library/migrations/README new file mode 100644 index 0000000..0e04844 --- /dev/null +++ b/library/migrations/README @@ -0,0 +1 @@ +Single-database configuration for Flask. diff --git a/library/migrations/alembic.ini b/library/migrations/alembic.ini new file mode 100644 index 0000000..ec9d45c --- /dev/null +++ b/library/migrations/alembic.ini @@ -0,0 +1,50 @@ +# A generic, single database configuration. + +[alembic] +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic,flask_migrate + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[logger_flask_migrate] +level = INFO +handlers = +qualname = flask_migrate + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/library/migrations/env.py b/library/migrations/env.py new file mode 100644 index 0000000..4c97092 --- /dev/null +++ b/library/migrations/env.py @@ -0,0 +1,113 @@ +import logging +from logging.config import fileConfig + +from flask import current_app + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) +logger = logging.getLogger('alembic.env') + + +def get_engine(): + try: + # this works with Flask-SQLAlchemy<3 and Alchemical + return current_app.extensions['migrate'].db.get_engine() + except (TypeError, AttributeError): + # this works with Flask-SQLAlchemy>=3 + return current_app.extensions['migrate'].db.engine + + +def get_engine_url(): + try: + return get_engine().url.render_as_string(hide_password=False).replace( + '%', '%%') + except AttributeError: + return str(get_engine().url).replace('%', '%%') + + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +config.set_main_option('sqlalchemy.url', get_engine_url()) +target_db = current_app.extensions['migrate'].db + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def get_metadata(): + if hasattr(target_db, 'metadatas'): + return target_db.metadatas[None] + return target_db.metadata + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, target_metadata=get_metadata(), literal_binds=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + + conf_args = current_app.extensions['migrate'].configure_args + if conf_args.get("process_revision_directives") is None: + conf_args["process_revision_directives"] = process_revision_directives + + connectable = get_engine() + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=get_metadata(), + **conf_args + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/library/migrations/script.py.mako b/library/migrations/script.py.mako new file mode 100644 index 0000000..2c01563 --- /dev/null +++ b/library/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/library/migrations/versions/3105f85a9d8e_.py b/library/migrations/versions/3105f85a9d8e_.py new file mode 100644 index 0000000..8ff6f8f --- /dev/null +++ b/library/migrations/versions/3105f85a9d8e_.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: 3105f85a9d8e +Revises: +Create Date: 2024-03-30 17:50:00.878071 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '3105f85a9d8e' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### diff --git a/library/page.py b/library/page.py index 5a654c2..ba3cfcb 100644 --- a/library/page.py +++ b/library/page.py @@ -13,16 +13,17 @@ from application.user.loginuser import LoginUser from application.user.registeruser import RegisterUser from application.user.forgotpassword import ForgotPassword from application.user.resetpassword import ResetPassword +from application.models.usermodel import User -from flask import Blueprint, redirect, render_template, request, session +from flask import Blueprint, redirect, render_template, request, session, url_for from flask_wtf.csrf import CSRFProtect, CSRFError from flask_login import ( logout_user, login_required, current_user, ) -from forms.borrowform import BorrowForm -from forms.uploadform import PublicationForm +from application.forms.borrowform import BorrowForm +from application.forms.uploadform import PublicationForm from icalendar import Calendar from PIL import Image from requests import get @@ -121,7 +122,7 @@ def saveimage(image, id): """helper function that can save images""" image.save(os.path.join(APP.config["UPLOAD_FOLDER"], image.filename)) orig_image = Image.open( - os.path.join(APP.config["UPLOAD_FOLDER"], image.filename) + os.path.join(APP.confmailig["UPLOAD_FOLDER"], image.filename) ) new_width = 640 new_height = int(new_width * orig_image.height / orig_image.width) @@ -150,7 +151,7 @@ def register(): @APP.route("/forgotpassword", methods=["GET", "POST"]) def forgotpassword(): - return ForgotPassword(mail) + return ForgotPassword() @APP.route("/resetpassword/", methods=["GET", "POST"]) diff --git a/library/static/css/style.css b/library/static/css/style.css index b76f52e..7e65c18 100644 --- a/library/static/css/style.css +++ b/library/static/css/style.css @@ -107,8 +107,7 @@ input { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; - text-align: right; - padding: 0.8em 2em; + padding: 0.8em; cursor: pointer; }