From 1d91d783ed9b82c54bffb7d0cd11ea36829a084c Mon Sep 17 00:00:00 2001 From: Hayden Swanson <hayden_swanson22@yahoo.com> Date: Thu, 14 Sep 2023 16:28:08 -0400 Subject: [PATCH 1/8] renamed secure views for the inherited admin classes for better readability --- application/routes.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/application/routes.py b/application/routes.py index 94e9bf0..47928ae 100644 --- a/application/routes.py +++ b/application/routes.py @@ -37,16 +37,16 @@ def admin_required(cls): #new classes with same name or very different names? sign to maybe use flask-security @admin_required -class ModelView(ModelView): pass +class SecureModelView(ModelView): pass @admin_required -class AdminIndexView(AdminIndexView): pass +class SecureAdminIndexView(AdminIndexView): pass @admin_required -class BaseView(BaseView): pass +class SecureBaseView(BaseView): pass #add custom views here that only inheret from MyModelView or MyBaseView, otherwise it is a security vulnerablity -class AdminRegisterUser(BaseView): #using basview because I already made a register user form +class AdminRegisterUser(SecureBaseView): #using basview because I already made a register user form @expose('/',methods=['GET','POST']) def register(self): form = RegistrationForm() @@ -62,16 +62,16 @@ class AdminRegisterUser(BaseView): #using basview because I already made a regis return redirect(url_for('register.register')) return self.render('admin/register.html', title='Register User', form=form) -class MainPage(BaseView): +class MainPage(SecureBaseView): @expose('/', methods=['GET','POST']) def return_main(self): return redirect(url_for('home')) #initialize extension admin -admin = Admin(app, name='Admin', index_view=AdminIndexView()) +admin = Admin(app, name='Admin', index_view=SecureAdminIndexView()) admin.add_view(AdminRegisterUser(name='Register User', endpoint='register')) -admin.add_view(ModelView(User, db.session)) +admin.add_view(SecureModelView(User, db.session)) admin.add_view(MainPage(name='Return to Main Page')) #-----------user interface-----------------# -- GitLab From 3bd36bb200e75947f0c4a0f56e81eb7799531238 Mon Sep 17 00:00:00 2001 From: Hayden Swanson <hayden_swanson22@yahoo.com> Date: Thu, 14 Sep 2023 20:35:44 -0400 Subject: [PATCH 2/8] upgrading styling for sign in page, the current implementation is very hacky should add seperate css styles but itll do for now... --- application/__init__.py | 5 ++ application/forms.py | 4 +- application/static/cms_logo.png | Bin 0 -> 8695 bytes application/templates/base.html | 86 ++++++++++++++++++------------- application/templates/login.html | 80 +++++++++++++++++++--------- requirements.txt | 3 +- 6 files changed, 114 insertions(+), 64 deletions(-) create mode 100644 application/static/cms_logo.png diff --git a/application/__init__.py b/application/__init__.py index 3be7f7d..817bdcd 100644 --- a/application/__init__.py +++ b/application/__init__.py @@ -15,6 +15,9 @@ from flask_login import LoginManager #for dates and time from flask_moment import Moment +#for powerful styling tools +from flask_bootstrap import Bootstrap5 + #initiate flask app app = Flask(__name__) @@ -32,6 +35,8 @@ login.login_view = 'login' #sbring them to the endpoint for loggin in, ie the on moment = Moment(app) +bootstrap = Bootstrap5(app) + #import routes and models from application import routes, models #define here so no circular import errors diff --git a/application/forms.py b/application/forms.py index 41fff02..2076eee 100644 --- a/application/forms.py +++ b/application/forms.py @@ -6,8 +6,8 @@ from application.models import User class LoginForm(FlaskForm): - username = StringField('Username', validators=[DataRequired()]) - password = PasswordField('Password', validators=[DataRequired()]) + username = StringField('Username', validators=[DataRequired()], render_kw={"placeholder": "Username", 'style': 'font-size: 20px'}) + password = PasswordField('Password', validators=[DataRequired()], render_kw={"placeholder": "Password", 'style': 'font-size: 20px'}) remember_me = BooleanField('Remember Me') submit = SubmitField('Sign In') diff --git a/application/static/cms_logo.png b/application/static/cms_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..96ac7984793fdb197e6c15c8547e955c0d9f9144 GIT binary patch literal 8695 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 V#JrsndOQFC002ovPDHLkV1lSN>Ye}q literal 0 HcmV?d00001 diff --git a/application/templates/base.html b/application/templates/base.html index eef0c65..4501416 100644 --- a/application/templates/base.html +++ b/application/templates/base.html @@ -1,46 +1,60 @@ -<!DOCTYPE html> +<!doctype html> <html lang="en"> - <head> - <meta charset="UTF-8"> - {% if title %} - <title>{{ title }} - ETL Module Construction</title> - {% else %} - <title>ETL Module Construction</title> - {% endif %} - {{ moment.include_moment() }} - </head> - <body> - <div>ETL Module Construction: - <!-- url for the route defined in routes --> - <a href="{{ url_for('home') }}">Home</a> - {% if current_user.is_anonymous %} - <!-- above will only be True if user is not logged in --> - <a href="{{ url_for('login') }}">Login</a> - {% else %} - <a href="{{ url_for('logout') }}">Logout</a> - {% endif %} + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + {% if title %} + <title>{{ title }} - ETL Module Construction</title> + {% else %} + <title>ETL Module Construction</title> + {% endif %} + <!-- for date and times --> + {{ moment.include_moment() }} - {% if current_user.is_admin %} - <a href="{{ url_for('admin.index') }}">Admin</a> - {% endif %} + {{ bootstrap.load_css() }} + </head> + <body> + <!-- Navigation Bar --> + <nav class="navbar navbar-expand-lg bg-body-tertiary"> + <div class="container-fluid"> + <a class="navbar-brand" href="{{ url_for('home') }}">ETL Module Construction</a> + <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> + <span class="navbar-toggler-icon"></span> + </button> + <div class="collapse navbar-collapse" id="navbarSupportedContent"> + <ul class="navbar-nav me-auto mb-2 mb-lg-0"> + {% if not current_user.is_anonymous %} + <li class="nav-item"> + <a class="nav-link" href="{{ url_for('logout')}}">Logout</a> + </li> + {% endif %} + {% if current_user.is_admin %} + <li class="nav-item"> + <a class="nav-link" href="{{ url_for('admin.index') }}">Admin</a> + </li> + {% endif %} + </ul> </div> - <hr> - <!-- derived templates insert themselves here --> - <!-- base.html takes care of general page structure! --> - {% with messages = get_flashed_messages() %} - <!-- looks for flashed messages --> - {% if messages %} + </div> + </nav> + + <!-- turn this into an alert --> + {% with messages = get_flashed_messages() %} + <!-- looks for flashed messages --> + {% if messages %} <ul> {% for message in messages %} - <li>{{ message }}</li> + <li>{{ message }}</li> {% endfor %} </ul> - {% endif %} - {% endwith %} - - {% block content %}{% endblock %} - + {% endif %} + {% endwith %} + <!-- derived templates insert themselves here --> + <!-- base.html takes care of general page structure! --> + {% block content %}{% endblock %} + + {{ bootstrap.load_js() }} - </body> + </body> </html> \ No newline at end of file diff --git a/application/templates/login.html b/application/templates/login.html index 2e57a06..0a24316 100644 --- a/application/templates/login.html +++ b/application/templates/login.html @@ -1,30 +1,60 @@ {% extends "base.html" %} {% block content %} +<!-- +<body class="text-center" data-new-gr-c-s-check-loaded="14.1125.0" data-gr-ext-installed=""> + + <main class="form-signin"> + <form data-bitwarden-watching="1"> + <img class="mb-4" src="/static/cms_logo.png" alt="" width="72" height="57"> + <h1 class="h3 mb-3 fw-normal">Please sign in</h1> + + <div class="form-floating"> + <input type="email" class="form-control" id="floatingInput" placeholder="name@example.com"> + <label for="floatingInput">Username</label> + </div> + <div class="form-floating"> + <input type="password" class="form-control" id="floatingPassword" placeholder="Password"> + <label for="floatingPassword">Password</label> + </div> + + <div class="checkbox mb-3"> + <label> + <input type="checkbox" value="remember-me"> Remember me + </label> + </div> + <button class="w-100 btn btn-lg btn-primary" type="submit">Sign in</button> + <p class="mt-5 mb-3 text-muted">© 2017–2021</p> + </form> + </main> +</body> --> - <h1> Sign In </h1> - <form action="" method="post" novalidate> - <!-- protects against CSRF attacks, just need this and SECRET_KEY defined in Flask configuration --> - {{ form.hidden_tag() }} - <p> - <!-- form comes from LoginForm class --> - <!-- they know how to render the html --> - {{ 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.password.errors %} - <span style="color: red;">[{{ error }}]</span> - {% endfor %} - </p> - <p>{{ form.remember_me() }} {{ form.remember_me.label }}</p> - <p>{{ form.submit() }}</p> - - </form> - +<div class="text-center" data-new-gr-c-s-check-loaded="14.1125.0" data-gr-ext-installed=""> + <main class="form-signin"> + <div style="box-sizing: border-box; border: 1px solid rgb(236, 234, 234); width: 300px; margin: auto; margin-top: 40px; background-color: rgb(232, 238, 255); box-shadow: 0 0 2px rgb(232, 238, 255); border-radius: 8px;"> + <h1 class="h3 mt-3 fw-normal" style="font-size: 40px; width: 250px; margin: auto"> Sign In </h1> + <form action="" method="post" data-bitwarden-watching="1" novalidate style="width: 250px; margin: auto; padding-bottom: 10px;" > + <img class="mb-4" src="{{ url_for('static', filename='cms_logo.png') }}"> + <!-- protects against CSRF attacks, just need this and SECRET_KEY defined in Flask configuration --> + {{ form.hidden_tag() }} + <p> + <!-- form comes from LoginForm class --> + <!-- they know how to render the html --> + {{ form.username(size=14) }} + {% for error in form.username.errors %} + <div style="color: red;">[{{ error }}]</div> + {% endfor %} + </p> + <p> + {{ form.password(size=14) }} + {% for error in form.password.errors %} + <div style="color: red;">[{{ error }}]</div> + {% endfor %} + </p> + <span style="margin-right: 10px;">{{ form.submit(class="w-10 btn btn-m btn-primary") }}</span> + <span>{{ form.remember_me(class="checkbox mb-3") }} {{ form.remember_me.label }}</span> + </form> + </div> + </main> +</div> {% endblock %} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 5d3c21f..7a02bad 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,5 @@ flask-login==0.6.2 flask-admin==1.6.1 email-validator==2.0.0.post2 flask-moment==1.0.5 -psycopg2==2.9.7 \ No newline at end of file +psycopg2==2.9.7 +bootstrap-flask==2.3.0 \ No newline at end of file -- GitLab From 2c0c18f89bd2ba4386f2b90e83603a0832b570a5 Mon Sep 17 00:00:00 2001 From: Hayden Swanson <hayden_swanson22@yahoo.com> Date: Fri, 15 Sep 2023 12:19:01 -0400 Subject: [PATCH 3/8] even more styling, I now know that styling can turn into a massive time sink --- application/forms.py | 12 +++--- application/routes.py | 11 ++++- application/templates/admin/register.html | 49 ++++++++++++----------- application/templates/base.html | 7 +++- application/templates/home.html | 20 ++++----- application/templates/login.html | 34 ++-------------- 6 files changed, 59 insertions(+), 74 deletions(-) diff --git a/application/forms.py b/application/forms.py index 2076eee..94effc9 100644 --- a/application/forms.py +++ b/application/forms.py @@ -14,12 +14,12 @@ class LoginForm(FlaskForm): class RegistrationForm(FlaskForm): - username = StringField('Username', validators=[DataRequired()]) - email = StringField('Email', validators=[DataRequired(), Email()]) - affiliation = StringField('Affiliation', validators=[DataRequired()]) - password = PasswordField('Password', validators=[DataRequired()]) - password2 = PasswordField('Repeat Password', validators=[DataRequired(), EqualTo('password')]) - is_admin = BooleanField('Give Admin Privledges') + username = StringField('Username', validators=[DataRequired()], render_kw={'class': "form-control"}) + email = StringField('Email', validators=[DataRequired(), Email()], render_kw={'class': "form-control"}) + affiliation = StringField('Affiliation', validators=[DataRequired()], render_kw={'class': "form-control"}) + password = PasswordField('Password', validators=[DataRequired()], render_kw={'class': "form-control"}) + password2 = PasswordField('Repeat Password', validators=[DataRequired(), EqualTo('password')], render_kw={'class': "form-control"}) + is_admin = BooleanField('Admin Privledges') submit = SubmitField('Register') def validate_username(self, username): diff --git a/application/routes.py b/application/routes.py index 47928ae..daebd34 100644 --- a/application/routes.py +++ b/application/routes.py @@ -37,7 +37,14 @@ def admin_required(cls): #new classes with same name or very different names? sign to maybe use flask-security @admin_required -class SecureModelView(ModelView): pass +class SecureModelView(ModelView): + #column_exclude_list = ('password_hash') + def _password_hash_formatter(view, context, model, name): + # Format your string here e.g show first 20 characters + # can return any valid HTML e.g. a link to another view to show the detail or a popup window + return model.password_hash[:10] + '...' + + column_formatters = {'password_hash': _password_hash_formatter} @admin_required class SecureAdminIndexView(AdminIndexView): pass @@ -68,7 +75,7 @@ class MainPage(SecureBaseView): return redirect(url_for('home')) #initialize extension admin -admin = Admin(app, name='Admin', index_view=SecureAdminIndexView()) +admin = Admin(app, name='Admin', index_view=SecureAdminIndexView(), template_mode='bootstrap4') admin.add_view(AdminRegisterUser(name='Register User', endpoint='register')) admin.add_view(SecureModelView(User, db.session)) diff --git a/application/templates/admin/register.html b/application/templates/admin/register.html index c03923d..cbaded7 100644 --- a/application/templates/admin/register.html +++ b/application/templates/admin/register.html @@ -1,45 +1,46 @@ {% extends 'admin/master.html' %} {% block body %} - <h1>Register Users</h1> + <h1>Register User</h1> <form action="", method="post"> {{ form.hidden_tag() }} - <p> - {{ form.username.label }}<br> - {{ form.username(size=32) }}<br> + <div class="form-group"> + {{ form.username.label }} + {{ 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) }}<br> + </div> + <div class="form-group"> + {{ form.password.label }} + {{ form.password(size=32) }} {% for error in form.password.errors %} <span style="color: red;">[{{ error }}]</span> {% endfor %} - </p> - <p> - {{ form.password2.label }}<br> - {{ form.password2(size=32) }}<br> + </div> + <div class="form-group"> + {{ form.password2.label }} + {{ form.password2(size=32) }} {% for error in form.password2.errors %} <span style="color: red;">[{{ error }}]</span> {% endfor %} - </p> - <p> - {{ form.email.label }}<br> - {{ form.email(size=64) }}<br> + </div> + <div class="form-group"> + {{ form.email.label }} + {{ form.email(size=32) }} {% for error in form.email.errors %} <span style="color: red;">[{{ error }}]</span> {% endfor %} - </p> - <p> - {{ form.affiliation.label }}<br> - {{ form.affiliation(size=32) }}<br> + </div> + <div class="form-group"> + {{ form.affiliation.label }} + {{ form.affiliation(size=32) }} {% for error in form.affiliation.errors %} <span style="color: red;">[{{ error }}]</span> {% endfor %} - </p> - <p>{{ form.is_admin() }} {{ form.is_admin.label }}</p> - <p>{{ form.submit() }}</p> + </div> + <div class="form-group form-check">{{ form.is_admin() }} {{ form.is_admin.label }}</div> + <div style="margin-right: 10px;">{{ form.submit(class="w-10 btn btn-m btn-primary") }}</div> + </form> -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/application/templates/base.html b/application/templates/base.html index 4501416..23e6740 100644 --- a/application/templates/base.html +++ b/application/templates/base.html @@ -16,12 +16,15 @@ </head> <body> <!-- Navigation Bar --> - <nav class="navbar navbar-expand-lg bg-body-tertiary"> + + <nav class="navbar navbar-expand-sm -bs-primary-bg-subtle" style="margin-bottom: 10px; background-color: #92e569;"> <div class="container-fluid"> <a class="navbar-brand" href="{{ url_for('home') }}">ETL Module Construction</a> + {% if not current_user.is_anonymous %} <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> + {% endif %} <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav me-auto mb-2 mb-lg-0"> {% if not current_user.is_anonymous %} @@ -45,7 +48,7 @@ {% if messages %} <ul> {% for message in messages %} - <li>{{ message }}</li> + <div class="alert alert-warning" role="alert" style="margin-right: 20px;">{{ message }}</div> {% endfor %} </ul> {% endif %} diff --git a/application/templates/home.html b/application/templates/home.html index 48bf3b7..c8bd785 100644 --- a/application/templates/home.html +++ b/application/templates/home.html @@ -1,14 +1,16 @@ {% extends "base.html" %} {% block content %} +<div style="margin-left: 20px;"> <h1> Welcome, {{ current_user.username }}!</h1> - <h2> Here are a list of current users:</h2> - {% for user in users %} - <div> - <li>{{ user.username }} - {% if user.created_at %} - <p>Created at: {{ moment(user.created_at).format('LLL') }}</p> - {% endif %} - </li> + <h4> Here are a list of current users:</h2> + <div style="margin-left: 20px;"> + {% for user in users %} + <li>{{ user.username }} + {% if user.created_at %} + <p>Created at: {{ moment(user.created_at).format('LLL') }}</p> + {% endif %} + </li> + {% endfor %} </div> - {% endfor %} +</div> {% endblock %} \ No newline at end of file diff --git a/application/templates/login.html b/application/templates/login.html index 0a24316..2e8bae2 100644 --- a/application/templates/login.html +++ b/application/templates/login.html @@ -1,38 +1,10 @@ {% extends "base.html" %} {% block content %} -<!-- -<body class="text-center" data-new-gr-c-s-check-loaded="14.1125.0" data-gr-ext-installed=""> - - <main class="form-signin"> - <form data-bitwarden-watching="1"> - <img class="mb-4" src="/static/cms_logo.png" alt="" width="72" height="57"> - <h1 class="h3 mb-3 fw-normal">Please sign in</h1> - - <div class="form-floating"> - <input type="email" class="form-control" id="floatingInput" placeholder="name@example.com"> - <label for="floatingInput">Username</label> - </div> - <div class="form-floating"> - <input type="password" class="form-control" id="floatingPassword" placeholder="Password"> - <label for="floatingPassword">Password</label> - </div> - - <div class="checkbox mb-3"> - <label> - <input type="checkbox" value="remember-me"> Remember me - </label> - </div> - <button class="w-100 btn btn-lg btn-primary" type="submit">Sign in</button> - <p class="mt-5 mb-3 text-muted">© 2017–2021</p> - </form> - </main> -</body> --> - <div class="text-center" data-new-gr-c-s-check-loaded="14.1125.0" data-gr-ext-installed=""> <main class="form-signin"> - <div style="box-sizing: border-box; border: 1px solid rgb(236, 234, 234); width: 300px; margin: auto; margin-top: 40px; background-color: rgb(232, 238, 255); box-shadow: 0 0 2px rgb(232, 238, 255); border-radius: 8px;"> - <h1 class="h3 mt-3 fw-normal" style="font-size: 40px; width: 250px; margin: auto"> Sign In </h1> + <div style="box-sizing: border-box; border: 1px solid rgb(236, 234, 234); width: 300px; margin: auto; margin-top: 40px; background-color: #85d2fbbc; box-shadow: 0 0 2px rgb(232, 238, 255); border-radius: 8px;"> + <h1 class="h3 mt-3 fw-normal" style="font-size: 40px; width: 250px; margin: auto; color: black;"> Sign In </h1> <form action="" method="post" data-bitwarden-watching="1" novalidate style="width: 250px; margin: auto; padding-bottom: 10px;" > <img class="mb-4" src="{{ url_for('static', filename='cms_logo.png') }}"> <!-- protects against CSRF attacks, just need this and SECRET_KEY defined in Flask configuration --> @@ -52,7 +24,7 @@ {% endfor %} </p> <span style="margin-right: 10px;">{{ form.submit(class="w-10 btn btn-m btn-primary") }}</span> - <span>{{ form.remember_me(class="checkbox mb-3") }} {{ form.remember_me.label }}</span> + <span>{{ form.remember_me }} {{ form.remember_me.label }}</span> </form> </div> </main> -- GitLab From 7e3781ec84d1fe055fb1dcaae11c191515a90613 Mon Sep 17 00:00:00 2001 From: Hayden Swanson <hayden_swanson22@yahoo.com> Date: Fri, 15 Sep 2023 12:43:55 -0400 Subject: [PATCH 4/8] small style additions --- application/templates/base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/templates/base.html b/application/templates/base.html index 23e6740..727e738 100644 --- a/application/templates/base.html +++ b/application/templates/base.html @@ -17,7 +17,7 @@ <body> <!-- Navigation Bar --> - <nav class="navbar navbar-expand-sm -bs-primary-bg-subtle" style="margin-bottom: 10px; background-color: #92e569;"> + <nav class="navbar navbar-expand-sm -bs-primary-bg-subtle" style="margin-bottom: 20px; background-color: #92e569;"> <div class="container-fluid"> <a class="navbar-brand" href="{{ url_for('home') }}">ETL Module Construction</a> {% if not current_user.is_anonymous %} -- GitLab From a26a1663ad6d984709a956414a5eeb542d995881 Mon Sep 17 00:00:00 2001 From: Hayden Swanson <hayden_swanson22@yahoo.com> Date: Fri, 15 Sep 2023 16:18:17 -0400 Subject: [PATCH 5/8] added base tables for our database schema, no relationships at the moment --- .gitignore | 1 + application/models.py | 124 ++++++++++++++++++++++++++++++++++++++---- application/routes.py | 5 +- 3 files changed, 117 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index f824511..b335a5e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ __pycache__/ play.py env.py +build_db.py .idea/ \ No newline at end of file diff --git a/application/models.py b/application/models.py index 5589e03..226b9a2 100644 --- a/application/models.py +++ b/application/models.py @@ -40,11 +40,11 @@ class User(UserMixin, db.Model): email: Mapped[str] = mapped_column(db.String(50), nullable=False) affiliation: Mapped[str] = mapped_column(db.String(50), nullable=True) is_admin: Mapped[bool] = mapped_column(default=False, nullable=False) + is_active: Mapped[bool] = mapped_column(default=True, nullable=False) created_at: Mapped[datetime] = mapped_column(default=now_utc, nullable=False) def set_password(self, password: str) -> None: #is salted - #not working... self.password_hash = generate_password_hash(password) #if need allow password = "..." to set password, checkout flask-sqlalchemy repo flask-sqlalchemy/examples/flaskr/flaskr/auth @@ -56,20 +56,122 @@ class User(UserMixin, db.Model): return f'<User {self.username}>' -# class module(db.Model): -# pass +class assembly(db.Model): + __tablename__ = "assembly" -# class assembly(db.Model): -# pass + id: Mapped[int] = mapped_column(db.Integer, autoincrement=True, primary_key=True) + user_id: Mapped[int] = mapped_column(db.Integer, nullable=False) + #both module and component_id can be null, depends on the step, should we restrict the possibility of bothing being null? + module_id: Mapped[int] = mapped_column(db.Integer, nullable=True) + component_id: Mapped[int] = mapped_column(db.Integer, nullable=True) + assembly_type_id: Mapped[int] = mapped_column(db.Integer, nullable=False) -# class assembly_step(db.Model): -# pass + location: Mapped[str] = mapped_column(db.String(50), unique=False, nullable=True) + completed_at: Mapped[datetime] = mapped_column(default=now_utc, nullable=False) -# class component(db.Model): -# pass + data: Mapped[str] = mapped_column(db.String, unique=False, nullable=True) + + + def __repr__(self) -> str: + return f'<Assembly {self.id}>' + + +class test(db.Model): + __tablename__ = "test" + + id: Mapped[int] = mapped_column(db.Integer, autoincrement=True, primary_key=True) + user_id: Mapped[int] = mapped_column(db.Integer, nullable=False) + #both module and component_id can be null, depends on the step, should we restrict the possibility of bothing being null? + module_id: Mapped[int] = mapped_column(db.Integer, nullable=True) + component_id: Mapped[int] = mapped_column(db.Integer, nullable=True) + test_type_id: Mapped[int] = mapped_column(db.Integer, nullable=False) + + location: Mapped[str] = mapped_column(db.String(50), unique=False, nullable=True) + completed_at: Mapped[datetime] = mapped_column(default=now_utc, nullable=False) + + data: Mapped[str] = mapped_column(db.String, unique=False, nullable=True) + + + def __repr__(self) -> str: + return f'<Test {self.id}>' + + +#-------------SENSOR COMPONENTS------------------# +#module pcb table to track the module pcb +class module(db.Model): + __tablename__ = "module" + + id: Mapped[int] = mapped_column(db.Integer, autoincrement=True, primary_key=True) + module_type_id: Mapped[int] = mapped_column(db.Integer, nullable=False) + + module_serial_number: Mapped[str] = mapped_column(db.String(50), unique=True, nullable=False) + + current_location: Mapped[str] = mapped_column(db.String(50), unique=False, nullable=True) + + def __repr__(self) -> str: + return f'<Module {self.module_serial_number}>' + + +#ETROCs, LGADs, Baseplates etc... +class component(db.Model): + __tablename__ = "component" + + id: Mapped[int] = mapped_column(db.Integer, autoincrement=True, primary_key=True) + module_id: Mapped[int] = mapped_column(db.Integer, nullable=True) #for production or throughput demo nullable should be False, every component should have an associated module pcb + + #this gets if its an ETROC, LGAD, etc, any version or type you want + component_type_id: Mapped[int] = mapped_column(db.Integer, nullable=False) + + component_serial_number: Mapped[str] = mapped_column(db.String(50), unique=True, nullable=False) + + location: Mapped[str] = mapped_column(db.String(50), unique=False, nullable=True) + + def __repr__(self) -> str: + return f'<Component {self.component_serial_number}>' +#-------------------------------------------------# + + +#----------Lookup Tables-----------# +class module_type(db.Model): + __tablename__ = "module_type" + + id: Mapped[int] = mapped_column(db.Integer, autoincrement=True, primary_key=True) + module_name: Mapped[str] = mapped_column(db.String(50), unique=True, nullable=False) + + def __repr__(self) -> str: + return f'<Module Name {self.module_name}>' + + +class component_type(db.Model): + __tablename__ = "component_type" + + id: Mapped[int] = mapped_column(db.Integer, autoincrement=True, primary_key=True) + component_name: Mapped[str] = mapped_column(db.String(50), unique=True, nullable=False) + + def __repr__(self) -> str: + return f'<Component Name {self.component_name}>' + + +class test_type(db.Model): + __tablename__ = "test_type" + + id: Mapped[int] = mapped_column(db.Integer, autoincrement=True, primary_key=True) + test_name: Mapped[str] = mapped_column(db.String(50), unique=True, nullable=False) + + def __repr__(self) -> str: + return f'<Test Name {self.test_name}>' + + +class assembly_type(db.Model): + __tablename__ = "assembly_type" + + id: Mapped[int] = mapped_column(db.Integer, autoincrement=True, primary_key=True) + assembly_name: Mapped[str] = mapped_column(db.String(50), unique=True, nullable=False) + + def __repr__(self) -> str: + return f'<Assembly Name {self.assembly_name}>' +#---------------------------------# -# class component_type(db.Model): -# pass diff --git a/application/routes.py b/application/routes.py index daebd34..957867c 100644 --- a/application/routes.py +++ b/application/routes.py @@ -106,8 +106,9 @@ def login(): user = User.query.filter_by(username=form.username.data).first() #check if username exists or their password is correct - if user is None or not user.check_password(form.password.data): #hashed password defined inside User class - flash('Invalid username or password') + if user is None or not user.check_password(form.password.data) or not user.is_active: #hashed password defined inside User class + if user is None or not user.check_password(form.password.data): flash('Invalid username or password') + elif not user.is_active: flash('Your account has been deactivated, contact an administrator') return redirect(url_for('login')) #if it is correct then for flask login_user, log them in -- GitLab From 9cd2a2178c87f4db82ac786df3c860e6c4f7c73e Mon Sep 17 00:00:00 2001 From: Hayden Swanson <hayden_swanson22@yahoo.com> Date: Mon, 18 Sep 2023 18:45:35 -0400 Subject: [PATCH 6/8] added relationships to database, getting ready to fill with data and make forms! --- application/models.py | 70 +++++++++++++++++++++++++++++-------------- application/routes.py | 20 +++++++++---- 2 files changed, 62 insertions(+), 28 deletions(-) diff --git a/application/models.py b/application/models.py index 226b9a2..b351868 100644 --- a/application/models.py +++ b/application/models.py @@ -43,6 +43,13 @@ class User(UserMixin, db.Model): is_active: Mapped[bool] = mapped_column(default=True, nullable=False) created_at: Mapped[datetime] = mapped_column(default=now_utc, nullable=False) + #Relationships + #'assembly' = the table for hte many part of the relationship (one user can have many assemblies) + #backref='user' = name of a field that will be added to the objects of the many class, ie user, assembly.user + #lazy='dynamic' = gives an object back instead of a direct SQL query + assemblies = db.relationship('Assembly', backref='user', lazy='dynamic') + tests = db.relationship('Test', backref='user', lazy='dynamic') + def set_password(self, password: str) -> None: #is salted self.password_hash = generate_password_hash(password) @@ -56,123 +63,142 @@ class User(UserMixin, db.Model): return f'<User {self.username}>' -class assembly(db.Model): +class Assembly(db.Model): __tablename__ = "assembly" id: Mapped[int] = mapped_column(db.Integer, autoincrement=True, primary_key=True) - user_id: Mapped[int] = mapped_column(db.Integer, nullable=False) + user_id: Mapped[int] = mapped_column(db.Integer, db.ForeignKey('user.id'), nullable=False) #both module and component_id can be null, depends on the step, should we restrict the possibility of bothing being null? - module_id: Mapped[int] = mapped_column(db.Integer, nullable=True) - component_id: Mapped[int] = mapped_column(db.Integer, nullable=True) - assembly_type_id: Mapped[int] = mapped_column(db.Integer, nullable=False) + module_id: Mapped[int] = mapped_column(db.Integer, db.ForeignKey('module.id'), nullable=True) + component_id: Mapped[int] = mapped_column(db.Integer, db.ForeignKey('component.id'), nullable=True) + assembly_type_id: Mapped[int] = mapped_column(db.Integer, db.ForeignKey('assembly_type.id'), nullable=False) location: Mapped[str] = mapped_column(db.String(50), unique=False, nullable=True) completed_at: Mapped[datetime] = mapped_column(default=now_utc, nullable=False) data: Mapped[str] = mapped_column(db.String, unique=False, nullable=True) - def __repr__(self) -> str: return f'<Assembly {self.id}>' -class test(db.Model): +class Test(db.Model): __tablename__ = "test" id: Mapped[int] = mapped_column(db.Integer, autoincrement=True, primary_key=True) - user_id: Mapped[int] = mapped_column(db.Integer, nullable=False) + user_id: Mapped[int] = mapped_column(db.Integer, db.ForeignKey('user.id'), nullable=False) #both module and component_id can be null, depends on the step, should we restrict the possibility of bothing being null? - module_id: Mapped[int] = mapped_column(db.Integer, nullable=True) - component_id: Mapped[int] = mapped_column(db.Integer, nullable=True) - test_type_id: Mapped[int] = mapped_column(db.Integer, nullable=False) + module_id: Mapped[int] = mapped_column(db.Integer, db.ForeignKey('module.id'), nullable=True) + component_id: Mapped[int] = mapped_column(db.Integer, db.ForeignKey('component.id'), nullable=True) + test_type_id: Mapped[int] = mapped_column(db.Integer, db.ForeignKey('test_type.id'), nullable=False) location: Mapped[str] = mapped_column(db.String(50), unique=False, nullable=True) completed_at: Mapped[datetime] = mapped_column(default=now_utc, nullable=False) data: Mapped[str] = mapped_column(db.String, unique=False, nullable=True) - def __repr__(self) -> str: return f'<Test {self.id}>' #-------------SENSOR COMPONENTS------------------# #module pcb table to track the module pcb -class module(db.Model): +class Module(db.Model): __tablename__ = "module" id: Mapped[int] = mapped_column(db.Integer, autoincrement=True, primary_key=True) - module_type_id: Mapped[int] = mapped_column(db.Integer, nullable=False) + module_type_id: Mapped[int] = mapped_column(db.Integer, db.ForeignKey('module_type.id'), nullable=False) module_serial_number: Mapped[str] = mapped_column(db.String(50), unique=True, nullable=False) current_location: Mapped[str] = mapped_column(db.String(50), unique=False, nullable=True) + #Relationships + assemblies = db.relationship('Assembly', backref='module', lazy='dynamic') + tests = db.relationship('Test', backref='module', lazy='dynamic') + components = db.relationship('Component', backref='module', lazy='dynamic') + def __repr__(self) -> str: return f'<Module {self.module_serial_number}>' #ETROCs, LGADs, Baseplates etc... -class component(db.Model): +class Component(db.Model): __tablename__ = "component" id: Mapped[int] = mapped_column(db.Integer, autoincrement=True, primary_key=True) - module_id: Mapped[int] = mapped_column(db.Integer, nullable=True) #for production or throughput demo nullable should be False, every component should have an associated module pcb + module_id: Mapped[int] = mapped_column(db.Integer, db.ForeignKey('module.id'), nullable=True) #for production or throughput demo nullable should be False, every component should have an associated module pcb #this gets if its an ETROC, LGAD, etc, any version or type you want - component_type_id: Mapped[int] = mapped_column(db.Integer, nullable=False) + component_type_id: Mapped[int] = mapped_column(db.Integer, db.ForeignKey('component_type.id'), nullable=False) component_serial_number: Mapped[str] = mapped_column(db.String(50), unique=True, nullable=False) location: Mapped[str] = mapped_column(db.String(50), unique=False, nullable=True) + #Relationships + assemblies = db.relationship('Assembly', backref='component', lazy='dynamic') + tests = db.relationship('Test', backref='component', lazy='dynamic') + def __repr__(self) -> str: return f'<Component {self.component_serial_number}>' #-------------------------------------------------# #----------Lookup Tables-----------# -class module_type(db.Model): +class ModuleType(db.Model): __tablename__ = "module_type" id: Mapped[int] = mapped_column(db.Integer, autoincrement=True, primary_key=True) module_name: Mapped[str] = mapped_column(db.String(50), unique=True, nullable=False) + modules = db.relationship('Module', backref='module_type', lazy='dynamic') + def __repr__(self) -> str: return f'<Module Name {self.module_name}>' -class component_type(db.Model): +class ComponentType(db.Model): __tablename__ = "component_type" id: Mapped[int] = mapped_column(db.Integer, autoincrement=True, primary_key=True) component_name: Mapped[str] = mapped_column(db.String(50), unique=True, nullable=False) + #Relationships + components = db.relationship('Component', backref='component_type', lazy='dynamic') + def __repr__(self) -> str: return f'<Component Name {self.component_name}>' -class test_type(db.Model): +class TestType(db.Model): __tablename__ = "test_type" id: Mapped[int] = mapped_column(db.Integer, autoincrement=True, primary_key=True) test_name: Mapped[str] = mapped_column(db.String(50), unique=True, nullable=False) + #Relationships + tests = db.relationship('Test', backref='test_type', lazy='dynamic') + def __repr__(self) -> str: return f'<Test Name {self.test_name}>' -class assembly_type(db.Model): +class AssemblyType(db.Model): __tablename__ = "assembly_type" id: Mapped[int] = mapped_column(db.Integer, autoincrement=True, primary_key=True) assembly_name: Mapped[str] = mapped_column(db.String(50), unique=True, nullable=False) + #Relationships + assemblies = db.relationship('Assembly', backref='assembly_type', lazy='dynamic') + def __repr__(self) -> str: return f'<Assembly Name {self.assembly_name}>' #---------------------------------# - +#consider a table for locations +#consider a table for ... diff --git a/application/routes.py b/application/routes.py index 957867c..2918761 100644 --- a/application/routes.py +++ b/application/routes.py @@ -10,7 +10,7 @@ from application.forms import LoginForm, RegistrationForm #login functionalities form flask_login from flask_login import current_user, login_user, logout_user, login_required -from application.models import User +from application.models import * #-------------admin interface-----------------# #admin class @@ -37,14 +37,13 @@ def admin_required(cls): #new classes with same name or very different names? sign to maybe use flask-security @admin_required -class SecureModelView(ModelView): - #column_exclude_list = ('password_hash') - def _password_hash_formatter(view, context, model, name): +class SecureModelView(ModelView): + def _password_hash_formatter(model): # Format your string here e.g show first 20 characters # can return any valid HTML e.g. a link to another view to show the detail or a popup window return model.password_hash[:10] + '...' - column_formatters = {'password_hash': _password_hash_formatter} + column_formatters = {'password_hash': _password_hash_formatter} @admin_required class SecureAdminIndexView(AdminIndexView): pass @@ -78,7 +77,16 @@ class MainPage(SecureBaseView): admin = Admin(app, name='Admin', index_view=SecureAdminIndexView(), template_mode='bootstrap4') admin.add_view(AdminRegisterUser(name='Register User', endpoint='register')) -admin.add_view(SecureModelView(User, db.session)) + +#consider adding something to the views to see all column names, requires custom view for all +admin.add_view(SecureModelView(Assembly, db.session)) +admin.add_view(SecureModelView(Component, db.session)) +admin.add_view(SecureModelView(Module, db.session)) +admin.add_view(SecureModelView(ComponentType, db.session)) +admin.add_view(SecureModelView(ModuleType, db.session)) +admin.add_view(SecureModelView(TestType, db.session)) +admin.add_view(SecureModelView(AssemblyType, db.session)) + admin.add_view(MainPage(name='Return to Main Page')) #-----------user interface-----------------# -- GitLab From 6f04c9b52fde3495cef800d48e2db80ed185c0b1 Mon Sep 17 00:00:00 2001 From: Hayden Swanson <hayden_swanson22@yahoo.com> Date: Tue, 19 Sep 2023 12:54:10 -0400 Subject: [PATCH 7/8] Basic form for recording tests --- application/forms.py | 56 +++++++++++++++++++++++++++++++-- application/routes.py | 53 ++++++++++++++++++++++++++++++- application/templates/base.html | 3 ++ application/templates/test.html | 46 +++++++++++++++++++++++++++ 4 files changed, 155 insertions(+), 3 deletions(-) create mode 100644 application/templates/test.html diff --git a/application/forms.py b/application/forms.py index 94effc9..c3b67ae 100644 --- a/application/forms.py +++ b/application/forms.py @@ -1,8 +1,8 @@ from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, BooleanField, SubmitField -from wtforms.validators import ValidationError, DataRequired, Email, EqualTo -from application.models import User +from wtforms.validators import ValidationError, DataRequired, Email, EqualTo, Optional +from application.models import * class LoginForm(FlaskForm): @@ -34,3 +34,55 @@ class RegistrationForm(FlaskForm): if user is not None: raise ValidationError('Please use a different email address') + +class RecordTest(FlaskForm): + + module = StringField('Module', validators=[Optional()], render_kw={"placeholder": "Module Serial Number", 'class': "form-control"}) + component = StringField('Component', validators=[Optional()], render_kw={"placeholder": "ETROC, LGAD, etc... Serial Number",'class': "form-control"}) + test_type = StringField('test_type', validators=[DataRequired()], render_kw={"placeholder":"What test did you perform?", 'class': "form-control"}) + location = StringField('location', validators=[Optional()], render_kw={"placeholder": "Where did this test take place?",'class': "form-control"}) + test_data = StringField('data', validators=[DataRequired()], render_kw={"placeholder": "Data in the format of a string",'class': "form-control"}) + submit = SubmitField('Record Test') + + def validate(self,extra_validators=None): + #not really sure how this bit works + if not super(RecordTest, self).validate(extra_validators): + return False + #this I get + if not self.module.data and not self.component.data: + msg = 'At least one serial number must be set' + self.module.errors.append(msg) + self.component.errors.append(msg) + return False + return True + + + def validate_module(self, module): + module_query = Module.query.filter_by(module_serial_number=module.data).first() + if module_query is None: + raise ValidationError('Module Serial Number does not exist, please register it') + + def validate_component(self, component): + component_query = Component.query.filter_by(component_serial_number=component.data).first() + if component_query is None: + raise ValidationError('Component Serial Number does not exist, please register it') + + def validate_test_type(self, test_type): + test_type_query = TestType.query.filter_by(test_name=test_type.data).first() + if test_type_query is None: + raise ValidationError('Test type does not exist, please register it') + + # def validate_mod_comp_not_empty(self, module, component): + # if module.data == '' and component.data == '': + # raise ValidationError("Module and Component Serial numbers both can't be empty") + + #TO DO: + #query is done twice, here and in routes, is this bad? + #probably bad to have module and component string fields right? One or the other? + #conisder stripping white space + #also add a Flask-WTF FileField that takes precident over data + #ideally a searchable table and you select the + #test, module, or component because it could get long! Maybe locations too + #think about smart way to do this registration of test, module and component with the lookup tables + #idea being so you dont have to also manually fill something seperate out for the lookup tables + diff --git a/application/routes.py b/application/routes.py index 2918761..505af4a 100644 --- a/application/routes.py +++ b/application/routes.py @@ -6,7 +6,7 @@ from flask import render_template, flash, redirect, url_for, request from werkzeug.urls import url_parse #class forms, uses flask-wtf -from application.forms import LoginForm, RegistrationForm +from application.forms import LoginForm, RegistrationForm, RecordTest #login functionalities form flask_login from flask_login import current_user, login_user, logout_user, login_required @@ -144,4 +144,55 @@ def logout(): return redirect(url_for('home')) +@app.route('/test', methods=['GET','POST']) +@login_required +def record_test(): + form = RecordTest() + print('hi first') + if form.validate_on_submit(): + print(f'Module data: [{form.module.data}], {type(form.module.data)}') + print(f'Component data: [{form.component.data}], {type(form.component.data)}') + def check_empty(form_input): + #if empty string set to None so it is NULL in database + if form_input == '': + return None + else: + return form_input + + module_input = check_empty(form.module.data) + component_input = check_empty(form.component.data) + + print(f'Module data: [{module_input}], {type(module_input)}') + print(f'Component data: [{component_input}], {type(component_input)}') + + test_type = TestType.query.filter_by(test_name=form.test_type.data).first() + + if module_input is not None: + mod_obj = Module.query.filter_by(module_serial_number=module_input).first() + test = Test(test_type=test_type, + user=current_user, + location=check_empty(form.location.data), + data=form.test_data.data, + module=mod_obj) + if component_input is not None: + comp_obj = Component.query.filter_by(component_serial_number=component_input).first() + test = Test(test_type=test_type, + user=current_user, + location=check_empty(form.location.data), + data=form.test_data.data, + component=comp_obj) + if module_input is not None and component_input is not None: + test = Test(test_type=test_type, + user=current_user, + location=check_empty(form.location.data), + data=form.test_data.data, + component=comp_obj, + module=mod_obj) + + db.session.add(test) + db.session.commit() + redirect(url_for('record_test')) + + return render_template('test.html',title='Record Test', form=form) + diff --git a/application/templates/base.html b/application/templates/base.html index 727e738..8fbbf99 100644 --- a/application/templates/base.html +++ b/application/templates/base.html @@ -27,6 +27,9 @@ {% endif %} <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav me-auto mb-2 mb-lg-0"> + <li class="nav-item"> + <a class="nav-link" href="{{ url_for('record_test')}}">Record Test</a> + </li> {% if not current_user.is_anonymous %} <li class="nav-item"> <a class="nav-link" href="{{ url_for('logout')}}">Logout</a> diff --git a/application/templates/test.html b/application/templates/test.html new file mode 100644 index 0000000..824d371 --- /dev/null +++ b/application/templates/test.html @@ -0,0 +1,46 @@ +{% extends "base.html" %} + +{% block content %} +<h1>Record Test</h1> +<form action="", method="post"> + {{ form.hidden_tag() }} + <div class="form-group"> + {{ form.module.label }} + {{ form.module(size=32) }} + {% for error in form.module.errors %} + <span style="color: red;">[{{ error }}]</span> + {% endfor %} + </div> + <div class="form-group"> + {{ form.component.label }} + {{ form.component(size=32) }} + {% for error in form.component.errors %} + <span style="color: red;">[{{ error }}]</span> + {% endfor %} + </div> + <div class="form-group"> + {{ form.test_type.label }} + {{ form.test_type(size=32) }} + {% for error in form.test_type.errors %} + <span style="color: red;">[{{ error }}]</span> + {% endfor %} + </div> + <div class="form-group"> + {{ form.location.label }} + {{ form.location(size=32) }} + {% for error in form.location.errors %} + <span style="color: red;">[{{ error }}]</span> + {% endfor %} + </div> + <div class="form-group"> + {{ form.test_data.label }} + {{ form.test_data(size=32) }} + {% for error in form.test_data.errors %} + <span style="color: red;">[{{ error }}]</span> + {% endfor %} + </div> + <div style="margin-right: 10px;">{{ form.submit(class="w-10 btn btn-m btn-primary") }}</div> + +</form> + +{% endblock %} \ No newline at end of file -- GitLab From 9dab91309bf8e684da43f6c7764a1b50012c7a8d Mon Sep 17 00:00:00 2001 From: Hayden Swanson <hayden_swanson22@yahoo.com> Date: Tue, 19 Sep 2023 17:38:06 -0400 Subject: [PATCH 8/8] further improvements on the register test form --- application/forms.py | 15 +-------------- application/routes.py | 28 ++++++---------------------- 2 files changed, 7 insertions(+), 36 deletions(-) diff --git a/application/forms.py b/application/forms.py index c3b67ae..00228b0 100644 --- a/application/forms.py +++ b/application/forms.py @@ -56,7 +56,6 @@ class RecordTest(FlaskForm): return False return True - def validate_module(self, module): module_query = Module.query.filter_by(module_serial_number=module.data).first() if module_query is None: @@ -72,17 +71,5 @@ class RecordTest(FlaskForm): if test_type_query is None: raise ValidationError('Test type does not exist, please register it') - # def validate_mod_comp_not_empty(self, module, component): - # if module.data == '' and component.data == '': - # raise ValidationError("Module and Component Serial numbers both can't be empty") - - #TO DO: - #query is done twice, here and in routes, is this bad? - #probably bad to have module and component string fields right? One or the other? - #conisder stripping white space - #also add a Flask-WTF FileField that takes precident over data - #ideally a searchable table and you select the - #test, module, or component because it could get long! Maybe locations too - #think about smart way to do this registration of test, module and component with the lookup tables - #idea being so you dont have to also manually fill something seperate out for the lookup tables + diff --git a/application/routes.py b/application/routes.py index 505af4a..804c20a 100644 --- a/application/routes.py +++ b/application/routes.py @@ -150,8 +150,6 @@ def record_test(): form = RecordTest() print('hi first') if form.validate_on_submit(): - print(f'Module data: [{form.module.data}], {type(form.module.data)}') - print(f'Component data: [{form.component.data}], {type(form.component.data)}') def check_empty(form_input): #if empty string set to None so it is NULL in database if form_input == '': @@ -162,36 +160,22 @@ def record_test(): module_input = check_empty(form.module.data) component_input = check_empty(form.component.data) - print(f'Module data: [{module_input}], {type(module_input)}') - print(f'Component data: [{component_input}], {type(component_input)}') - test_type = TestType.query.filter_by(test_name=form.test_type.data).first() + #TO DO: prob a better way to do this if module_input is not None: mod_obj = Module.query.filter_by(module_serial_number=module_input).first() - test = Test(test_type=test_type, - user=current_user, - location=check_empty(form.location.data), - data=form.test_data.data, - module=mod_obj) + test = Test(test_type=test_type, user=current_user, location=check_empty(form.location.data), data=form.test_data.data, module=mod_obj) if component_input is not None: comp_obj = Component.query.filter_by(component_serial_number=component_input).first() - test = Test(test_type=test_type, - user=current_user, - location=check_empty(form.location.data), - data=form.test_data.data, - component=comp_obj) + test = Test(test_type=test_type, user=current_user, location=check_empty(form.location.data), data=form.test_data.data, component=comp_obj) + if module_input is not None and component_input is not None: - test = Test(test_type=test_type, - user=current_user, - location=check_empty(form.location.data), - data=form.test_data.data, - component=comp_obj, - module=mod_obj) + test = Test(test_type=test_type, user=current_user, location=check_empty(form.location.data), data=form.test_data.data, component=comp_obj, module=mod_obj) db.session.add(test) db.session.commit() - redirect(url_for('record_test')) + return render_template('test.html',title='Record Test', form=form) -- GitLab