1
0
Fork 0
mirror of https://codeberg.org/Mo8it/AdvLabDB.git synced 2024-09-19 18:31:16 +00:00

Moved users and register to UserModelView

This commit is contained in:
Mo 2021-04-24 12:05:58 +02:00
parent f9dd498d92
commit f4de3df51d
9 changed files with 114 additions and 207 deletions

View file

@ -9,8 +9,10 @@ from flask_debugtoolbar import DebugToolbarExtension
app = Flask(__name__)
app.debug = True #DEBUG
app.config["SERVER_NAME"] = "127.0.0.1:5000" #DEBUG
app.config["SECRET_KEY"] = "dev"
app.config['SECURITY_PASSWORD_SALT'] = "devSalt" # os.environ.get("SECURITY_PASSWORD_SALT", "")
app.config["SECURITY_PASSWORD_SALT"] = "devSalt" # os.environ.get("SECURITY_PASSWORD_SALT", "")
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///../advLab.db"
db = SQLAlchemy(app)
@ -21,7 +23,7 @@ app.config["DEBUG_TB_INTERCEPT_REDIRECTS"] = False #DEBUG
DebugToolbarExtension(app) #DEBUG
from advlabdb import customClasses
admin = Admin(app, name="Admin@AdvLabDB", template_mode='bootstrap3', index_view=customClasses.AdminIndexView())
admin = Admin(app, name="Admin@AdvLabDB", template_mode='bootstrap3', index_view=customClasses.SecureAdminIndexView())
from advlabdb import models

View file

@ -1,13 +1,15 @@
from flask_admin import AdminIndexView as OldAdminIndexView
from flask_admin.contrib.sqla import ModelView as OldModelView
from flask_admin import AdminIndexView
from flask_admin.contrib.sqla import ModelView
from flask_security import current_user
class AdminIndexView(OldAdminIndexView):
class SecureAdminIndexView(AdminIndexView):
def is_accessible(self):
return current_user.has_role("admin")
class ModelView(OldModelView):
class SecureModelView(ModelView):
can_export = True
def is_accessible(self):
return current_user.has_role("admin")

View file

@ -1,17 +1,53 @@
from advlabdb import admin
from advlabdb.customClasses import ModelView
from advlabdb.models import *
from flask import url_for, render_template, flash
from advlabdb.customClasses import SecureModelView
from flask_admin.menu import MenuLink
from flask_security import hash_password
from wtforms import BooleanField
admin.add_view(ModelView(Student, db.session))
admin.add_view(ModelView(PartStudent, db.session))
admin.add_view(ModelView(Group, db.session))
admin.add_view(ModelView(GroupExperiment, db.session))
admin.add_view(ModelView(Experiment, db.session))
admin.add_view(ModelView(PartExperiment, db.session))
admin.add_view(ModelView(Assistant, db.session))
admin.add_view(ModelView(Appointment, db.session))
admin.add_view(ModelView(Part, db.session))
admin.add_view(ModelView(Semester, db.session))
admin.add_view(ModelView(ExperimentMark, db.session))
admin.add_view(ModelView(User, db.session))
admin.add_view(ModelView(Role, db.session))
from advlabdb import admin, app, user_datastore
from advlabdb.models import *
from advlabdb.utils import randomPassword
class UserModelView(SecureModelView):
column_list = ["email", "active", "roles", "assistant"]
column_searchable_list = ["email"]
column_filters = ["active"]
form_columns = ["email", "roles"]
def create_model(self, form):
password = randomPassword()
passwordHash = hash_password(password)
email = form.email.data.lower()
print(form.roles.data[0].name)
roles = [role.name for role in form.roles.data]
if "admin" in roles:
flash("You have registered a new admin!", "danger")
newUser = user_datastore.create_user(email=email, password=passwordHash, roles=roles)
db.session.commit()
flash(f"{email} registered with roles: {', '.join([role.name for role in form.roles.data])}.", category="success")
flash(f"Random password: {password}", category="warning")
return newUser
class RoleModelView(SecureModelView):
column_exclude_list = ["update_datetime"]
admin.add_view(SecureModelView(Student, db.session))
admin.add_view(SecureModelView(PartStudent, db.session))
admin.add_view(SecureModelView(Group, db.session))
admin.add_view(SecureModelView(GroupExperiment, db.session))
admin.add_view(SecureModelView(Experiment, db.session))
admin.add_view(SecureModelView(PartExperiment, db.session))
admin.add_view(SecureModelView(Assistant, db.session))
admin.add_view(SecureModelView(Appointment, db.session))
admin.add_view(SecureModelView(Part, db.session))
admin.add_view(SecureModelView(Semester, db.session))
admin.add_view(SecureModelView(ExperimentMark, db.session))
admin.add_view(UserModelView(User, db.session))
admin.add_view(RoleModelView(Role, db.session))
with app.app_context():
admin.add_link(MenuLink(name="Home", url=url_for("index"), category="Links"))

View file

@ -24,6 +24,9 @@ class Student(db.Model):
note = db.Column(db.Text, nullable=True)
part_students = db.relationship("PartStudent", backref="student", lazy=True)
def __repr__(self):
return f"<{self.first_name} {self.last_name}>"
class PartStudent(db.Model):
# A student doing a specific part
@ -67,6 +70,9 @@ class Experiment(db.Model):
final_weighting = db.Column(db.Float, nullable=False)
part_experiments = db.relationship("PartExperiment", backref="experiment", lazy=True)
def __repr__(self):
return f"<{self.number}>"
# Helper table for the many to many relationship between Assistant and PartExperiment
experiment_assistant = db.Table("experiment_assistant",
@ -99,6 +105,9 @@ class Assistant(db.Model):
appointments = db.relationship("Appointment", backref="assistant", lazy=True)
experiment_marks = db.relationship("ExperimentMark", backref="assistant", lazy=True)
def __repr__(self):
return f"<{self.first_name} {self.last_name}>"
class Appointment(db.Model):
id = db.Column(db.Integer, primary_key=True)
@ -122,6 +131,9 @@ class Semester(db.Model):
label = db.Column(db.String(100), nullable=False) # WS2122 for example
parts = db.relationship("Part", backref="semester", lazy=True)
def __repr__(self):
return f"<{self.label}>"
class ExperimentMark(db.Model):
# A mark for a student after a specific experiment
@ -137,6 +149,10 @@ class ExperimentMark(db.Model):
class User(db.Model, FsUserMixin):
assistant = db.relationship("Assistant", backref="user", lazy=True, uselist=False)
def __repr__(self):
return f"<{self.email}>"
class Role(db.Model, FsRoleMixin):
pass
def __repr__(self):
return f"<{self.name}>"

View file

@ -177,30 +177,6 @@ def groups():
)
@app.route("/users", methods=["GET", "POST"])
@roles_required("admin")
def users():
if request.method == 'POST':
if "registerUser" in request.form:
return redirect(url_for("register"))
else:
headerAndDataList = [["Email", "row.email"],
["Roles", "[role.name for role in row.roles]"],
["Assistant", "row.assistant"]]
activeUsersTable = makeTable(headerAndDataList=headerAndDataList,
rows=User.query.filter(User.active == True).all(),
tableId="activeUsersTable")
inactiveUsersTable = makeTable(headerAndDataList=headerAndDataList,
rows=User.query.filter(User.active == False).all(),
tableId="inactiveUsersTable")
return render_template("users.html",
activeUsersTable=activeUsersTable,
inactiveUsersTable=inactiveUsersTable,
)
@app.route("/deactivate_users", methods=["GET"])
@roles_required("admin")
def deactivate_users():
@ -241,42 +217,3 @@ def semesters():
def set_semester():
session["activeSemesterId"] = int(request.args.get("semester_id"))
return redirect(request.referrer)
@app.route("/register", methods=["GET", "POST"])
@roles_required("admin")
def register():
form = RegistrationForm()
if form.validate_on_submit():
password = randomPassword()
passwordHash = hash_password(password)
email = form.email.data.lower()
assistant = form.assistant.data
admin = form.admin.data
registered = True
if admin:
if assistant:
roles = ["admin", "assistant"]
else:
roles = ["admin"]
flash("You have registered a new admin!", "danger")
elif assistant:
roles = ["assistant"]
else:
flash("The user has to be assistant and/or admin!", "warning")
registered = False
if registered:
newUser = user_datastore.create_user(email=email, password=passwordHash, roles=roles)
db.session.commit()
return render_template("registered.html",
email=email,
password=password,
roles=[role.name for role in newUser.roles],
)
return render_template("register.html",
form=form,
)

View file

@ -1,38 +0,0 @@
{% set title = "Register" %}
{% extends "layout.html" %}
{% block content %}
<div class="content-section">
<form method="POST">
{{form.hidden_tag()}}
<fieldset>
<div>
{{form.email.label(class="form-control-label")}}
{% if form.email.errors %}
{{form.email(class="form-control form-control-lg is-invalid")}}
<div class="invalid-feedback">
{% for error in form.email.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{form.email(class="form-control form-control-lg")}}
{% endif %}
</div>
<br>
Roles
<div class="form-check">
{{form.assistant(class="form-check-input")}}
{{form.assistant.label(class="form-check-label")}}
<br>
{{form.admin(class="form-check-input")}}
{{form.admin.label(class="form-check-label")}}
</div>
</fieldset>
<br>
<div>
{{form.submit(class="btn btn-outline-info")}}
</div>
</form>
</div>
{% endblock content %}

View file

@ -1,12 +0,0 @@
{% set title = "Registered" %}
{% extends "layout.html" %}
{% block content %}
<h3>
New user registered with roles {{roles}}.
</h3>
<p>
Email: {{email}}
<br>
Random password: {{password}}
</p>
{% endblock content %}

View file

@ -1,36 +0,0 @@
{% set title = "Users" %}
{% extends "layout.html" %}
{% block content %}
<h3>Active users:</h3>
{{activeUsersTable|safe}}
<div>
<button id="deactivateUsersButton" class="btn btn-warning">Deactivate users</button>
</div>
<br>
<form method="post">
<button type="submit" name="registerUser" class="btn btn-primary">Register a new user</button>
</form>
<hr>
<h3>Inactive users:</h3>
{{inactiveUsersTable|safe}}
{% endblock content %}
{% block scripts %}
<script>
let $table = $('#activeUsersTable')
let $button = $('#deactivateUsersButton')
$(function() {
$button.click(function () {
let users = $table.bootstrapTable('getSelections');
let emails = [];
for (var i=0; i<users.length; i++) {
emails.push(users[i]["email"])
}
window.location.href = '{{url_for("deactivate_users")}}?json=' + JSON.stringify(emails);
})
})
</script>
{% endblock scripts %}

70
poetry.lock generated
View file

@ -258,7 +258,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "sqlalchemy"
version = "1.4.9"
version = "1.4.11"
description = "Database Abstraction Library"
category = "main"
optional = false
@ -498,40 +498,40 @@ pyflakes = [
{file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"},
]
sqlalchemy = [
{file = "SQLAlchemy-1.4.9-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:e26791ac43806dec1f18d328596db87f1b37f9d8271997dd1233054b4c377f51"},
{file = "SQLAlchemy-1.4.9-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c4485040d86d4b3d9aa509fd3c492de3687d9bf52fb85d66b33912ad068a088c"},
{file = "SQLAlchemy-1.4.9-cp27-cp27m-win32.whl", hash = "sha256:a8763fe4de02f746666161b130cc3e5d1494a6f5475f5622f05251739fc22e55"},
{file = "SQLAlchemy-1.4.9-cp27-cp27m-win_amd64.whl", hash = "sha256:e7d262415e4adf148441bd9f10ae4e5498d6649962fabc62a64ec7b4891d56c5"},
{file = "SQLAlchemy-1.4.9-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:c6f228b79fd757d9ca539c9958190b3a44308f743dc7d83575aa0891033f6c86"},
{file = "SQLAlchemy-1.4.9-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:cfbf2cf8e8ef0a1d23bfd0fa387057e6e522d55e43821f1d115941d913ee7762"},
{file = "SQLAlchemy-1.4.9-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:815a8cdf9c0fa504d0bfbe83fb3e596b7663fc828b73259a20299c01330467aa"},
{file = "SQLAlchemy-1.4.9-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:cfa4a336de7d32ae30b54f7b8ec888fb5c6313a1b7419a9d7b3f49cdd83012a3"},
{file = "SQLAlchemy-1.4.9-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:065ac7331b87494a86bf3dc4430c1ee7779d6dc532213c528394ddd00804e518"},
{file = "SQLAlchemy-1.4.9-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:690fbca2a208314504a2ab46d3e7dae320247fcb1967863b9782a70bf49fc600"},
{file = "SQLAlchemy-1.4.9-cp36-cp36m-win32.whl", hash = "sha256:4edff2b4101a1c442fb1b17d594a5fdf99145f27c5eaffae12c26aef2bb2bf65"},
{file = "SQLAlchemy-1.4.9-cp36-cp36m-win_amd64.whl", hash = "sha256:6c6090d73820dcf04549f0b6e80f67b46c8191f0e40bf09c6d6f8ece2464e8b6"},
{file = "SQLAlchemy-1.4.9-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:fc82688695eacf77befc3d839df2bc7ff314cd1d547f120835acdcbac1a480b8"},
{file = "SQLAlchemy-1.4.9-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4e554872766d2783abf0a11704536596e8794229fb0fa63d311a74caae58c6c5"},
{file = "SQLAlchemy-1.4.9-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bce6eaf7b9a3a445911e225570b8fd26b7e98654ac9f308a8a52addb64a2a488"},
{file = "SQLAlchemy-1.4.9-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:25aaf0bec9eadde9789e3c0178c718ae6923b57485fdeae85999bc3089d9b871"},
{file = "SQLAlchemy-1.4.9-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:f239778cf03cd46da4962636501f6dea55af9b4684cd7ceee104ad4f0290e878"},
{file = "SQLAlchemy-1.4.9-cp37-cp37m-win32.whl", hash = "sha256:b0266e133d819d33b555798822606e876187a96798e2d8c9b7f85e419d73ef94"},
{file = "SQLAlchemy-1.4.9-cp37-cp37m-win_amd64.whl", hash = "sha256:230b210fc6d1af5d555d1d04ff9bd4259d6ab82b020369724ab4a1c805a32dd3"},
{file = "SQLAlchemy-1.4.9-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:a28c7b96bc5beef585172ca9d79068ae7fa2527feaa26bd63371851d7894c66f"},
{file = "SQLAlchemy-1.4.9-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:457a1652bc1c5f832165ff341380b3742bfb98b9ceca24576350992713ad700f"},
{file = "SQLAlchemy-1.4.9-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:e9e95568eafae18ac40d00694b82dc3febe653f81eee83204ef248563f39696d"},
{file = "SQLAlchemy-1.4.9-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:0d8aab144cf8d31c1ac834802c7df4430248f74bd8b3ed3149f9c9eec0eafe50"},
{file = "SQLAlchemy-1.4.9-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:cde2cf3ee76e8c538f2f43f5cf9252ad53404fc350801191128bab68f335a8b2"},
{file = "SQLAlchemy-1.4.9-cp38-cp38-win32.whl", hash = "sha256:bb97aeaa699c43da62e35856ab56e5154d062c09a3593a2c12c67d6a21059920"},
{file = "SQLAlchemy-1.4.9-cp38-cp38-win_amd64.whl", hash = "sha256:fbdcf9019e92253fc6aa0bcd5937302664c3a4d53884c425c0caa994e56c4421"},
{file = "SQLAlchemy-1.4.9-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:2e1b8d31c97a2b91aea8ed8299ad360a32d60728a89f2aac9c98eef07a633a0e"},
{file = "SQLAlchemy-1.4.9-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7bdb0f972bc35054c05088e91cec8fa810c3aa565b690bae75c005ee430e12e8"},
{file = "SQLAlchemy-1.4.9-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ec7c33e22beac16b4c5348c41cd94cfee056152e55a0efc62843deebfc53fcb4"},
{file = "SQLAlchemy-1.4.9-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:29816a338982c30dd7ee76c4e79f17d5991abb1b6561e9f1d72703d030a79c86"},
{file = "SQLAlchemy-1.4.9-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:099e63ffad329989080c533896267c40f9cb38ed5704168f7dae3afdda121e10"},
{file = "SQLAlchemy-1.4.9-cp39-cp39-win32.whl", hash = "sha256:343c679899afdc4952ac659dc46f2075a2bd4fba87ca0df264be838eecd02096"},
{file = "SQLAlchemy-1.4.9-cp39-cp39-win_amd64.whl", hash = "sha256:386f215248c3fb2fab9bb77f631bc3c6cd38354ca2363d241784f8297d16b80a"},
{file = "SQLAlchemy-1.4.9.tar.gz", hash = "sha256:f31757972677fbe9132932a69a4f23db59187a072cc26427f56a3082b46b6dac"},
{file = "SQLAlchemy-1.4.11-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:b8b7d66ee8b8ac272adce0af1342a60854f0d89686e6d3318127a6a82a2f765c"},
{file = "SQLAlchemy-1.4.11-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:03a503ecff0cc2be3ad4dafd220eaff13721edb11c191670b7662932fb0a5c3a"},
{file = "SQLAlchemy-1.4.11-cp27-cp27m-win32.whl", hash = "sha256:9cf94161cb55507cee147bf8abcfd3c076b353ad18743296764dd81108ea74f8"},
{file = "SQLAlchemy-1.4.11-cp27-cp27m-win_amd64.whl", hash = "sha256:d08173144aebdf30c21a331b532db16535cfa83deed12e8703fa6c67c0894ffc"},
{file = "SQLAlchemy-1.4.11-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:31e941d6db8b026bc63e46ef71e877913f128bd44260b90c645432626b7f9a47"},
{file = "SQLAlchemy-1.4.11-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:1e14fa32969badef9c309f55352e5c46f321bd29f7c600556caacdaa3eddfcf6"},
{file = "SQLAlchemy-1.4.11-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6389b10e23329dc8b5600c1a84e3da2628d0f437d8a5cd05aefd1470ec571dd1"},
{file = "SQLAlchemy-1.4.11-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:4f631edf45a943738fa77612e85fc5c5d3fb637c4f5a530f7eedd1a7cd7a70a7"},
{file = "SQLAlchemy-1.4.11-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:4a7d4da2acf6d5d068fb41c48950827c49c3c68bfb46a1da45ea8fbf7ed4b471"},
{file = "SQLAlchemy-1.4.11-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:f772e4428d413c0affe2a34836278fbe9df9a9c0940705860c2d3a4b50af1a66"},
{file = "SQLAlchemy-1.4.11-cp36-cp36m-win32.whl", hash = "sha256:0140f6dac2659fa6783e7029085ab0447d8eb23cf4d831fb907588d27ba158f7"},
{file = "SQLAlchemy-1.4.11-cp36-cp36m-win_amd64.whl", hash = "sha256:7d89add44938ea4f52c7641d5805c9e154fed4381e874ef3221483eeb191a96d"},
{file = "SQLAlchemy-1.4.11-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:452c4e002be727cb6f929dbd32bbc666a0921b86555b8af09709060ed3954bd3"},
{file = "SQLAlchemy-1.4.11-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:069de3a701d33709236efe0d06f38846b738b19c63d45cc47f54590982ba7802"},
{file = "SQLAlchemy-1.4.11-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bb1072fdf48ba870c0fe81bee8babe4ba2f096fb56bb4f3e0c2386a7626e405c"},
{file = "SQLAlchemy-1.4.11-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:8f96d4b6a49d3f0f109365bb6303ae5d266d3f90280ca68cf8b2c46032491038"},
{file = "SQLAlchemy-1.4.11-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:98214f04802a3fc740038744d8981a8f2fdca710f791ca125fc4792737d9f3a7"},
{file = "SQLAlchemy-1.4.11-cp37-cp37m-win32.whl", hash = "sha256:9fdf0713166f33e5e6ea98cf59deb305cb323131277f6880de6c509f468076f8"},
{file = "SQLAlchemy-1.4.11-cp37-cp37m-win_amd64.whl", hash = "sha256:6ebd58e73b7bd902688c0bb8dbabb0c36b756f02cc7b27ad5efa2f380c611f95"},
{file = "SQLAlchemy-1.4.11-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:a41ab83ecfadf38a47bdfaf4e488f71579df47a711e1ab1dce30d34c7c25bd00"},
{file = "SQLAlchemy-1.4.11-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:034b42a6a59bf4ddc57e5a38a9dbac83ccd94c0b565ba91dba4ff58149706028"},
{file = "SQLAlchemy-1.4.11-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:1735e06a3d5b0793d5ee2d952df8a5c63edaff6383c2210c9b5c93dc2ea4c315"},
{file = "SQLAlchemy-1.4.11-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:96de1d4a2e05d4a017087cb29cd6a8ebfeecfd0e9f872880b1a589f011c1c02e"},
{file = "SQLAlchemy-1.4.11-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:7180830ea1082b96b94884bc352b274e29b45151b6ee911bf1fd79cba2de659b"},
{file = "SQLAlchemy-1.4.11-cp38-cp38-win32.whl", hash = "sha256:961b089e64c2ad29ad367487dd3ba1aa3eeba56bc82037ce91732baaa0f6ca90"},
{file = "SQLAlchemy-1.4.11-cp38-cp38-win_amd64.whl", hash = "sha256:19633df6be629200ff3c026f2837e1dd17908fb1bcea860290a5a45e6fa5148e"},
{file = "SQLAlchemy-1.4.11-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:789be639501445d85fd4ca41d04f0f5c6cbb6deb0c6826aaa6f22774fe84ef94"},
{file = "SQLAlchemy-1.4.11-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:ac14fee167653ec6dee32d6aa4d501d90ae1bfbbc3eb5816940bccf227f0d617"},
{file = "SQLAlchemy-1.4.11-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:cd823071b97c1a6ac3af9e43b5d861126a1304033dcd18dfe354a02ec45642fe"},
{file = "SQLAlchemy-1.4.11-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:842b0d4698381aac047f8ae57409c90b7e63ebabf5bc02814ddc8eaefd13499e"},
{file = "SQLAlchemy-1.4.11-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:45a720029756800628359192630fffdc9660ab6f27f0409bd24d9e09d75d6c18"},
{file = "SQLAlchemy-1.4.11-cp39-cp39-win32.whl", hash = "sha256:e7d76312e904aa4ea221a92c0bc2e299ad46e4580e2d72ca1f7e6d31dce5bfab"},
{file = "SQLAlchemy-1.4.11-cp39-cp39-win_amd64.whl", hash = "sha256:4a2e7f037d3ca818d6d0490e3323fd451545f580df30d62b698da2f247015a34"},
{file = "SQLAlchemy-1.4.11.tar.gz", hash = "sha256:4ad4044eb86fbcbdff2106e44f479fbdac703d77860b3e19988c8a8786e73061"},
]
werkzeug = [
{file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"},