mirror of
https://codeberg.org/Mo8it/AdvLabDB.git
synced 2024-12-20 23:41:20 +00:00
Moved users and register to UserModelView
This commit is contained in:
parent
f9dd498d92
commit
f4de3df51d
9 changed files with 114 additions and 207 deletions
|
@ -9,8 +9,10 @@ from flask_debugtoolbar import DebugToolbarExtension
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.debug = True #DEBUG
|
app.debug = True #DEBUG
|
||||||
|
|
||||||
|
app.config["SERVER_NAME"] = "127.0.0.1:5000" #DEBUG
|
||||||
|
|
||||||
app.config["SECRET_KEY"] = "dev"
|
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"
|
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///../advLab.db"
|
||||||
db = SQLAlchemy(app)
|
db = SQLAlchemy(app)
|
||||||
|
@ -21,7 +23,7 @@ app.config["DEBUG_TB_INTERCEPT_REDIRECTS"] = False #DEBUG
|
||||||
DebugToolbarExtension(app) #DEBUG
|
DebugToolbarExtension(app) #DEBUG
|
||||||
|
|
||||||
from advlabdb import customClasses
|
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
|
from advlabdb import models
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
from flask_admin import AdminIndexView as OldAdminIndexView
|
from flask_admin import AdminIndexView
|
||||||
from flask_admin.contrib.sqla import ModelView as OldModelView
|
from flask_admin.contrib.sqla import ModelView
|
||||||
from flask_security import current_user
|
from flask_security import current_user
|
||||||
|
|
||||||
|
|
||||||
class AdminIndexView(OldAdminIndexView):
|
class SecureAdminIndexView(AdminIndexView):
|
||||||
def is_accessible(self):
|
def is_accessible(self):
|
||||||
return current_user.has_role("admin")
|
return current_user.has_role("admin")
|
||||||
|
|
||||||
|
|
||||||
class ModelView(OldModelView):
|
class SecureModelView(ModelView):
|
||||||
|
can_export = True
|
||||||
|
|
||||||
def is_accessible(self):
|
def is_accessible(self):
|
||||||
return current_user.has_role("admin")
|
return current_user.has_role("admin")
|
||||||
|
|
|
@ -1,17 +1,53 @@
|
||||||
from advlabdb import admin
|
from flask import url_for, render_template, flash
|
||||||
from advlabdb.customClasses import ModelView
|
from advlabdb.customClasses import SecureModelView
|
||||||
from advlabdb.models import *
|
from flask_admin.menu import MenuLink
|
||||||
|
from flask_security import hash_password
|
||||||
|
from wtforms import BooleanField
|
||||||
|
|
||||||
admin.add_view(ModelView(Student, db.session))
|
from advlabdb import admin, app, user_datastore
|
||||||
admin.add_view(ModelView(PartStudent, db.session))
|
from advlabdb.models import *
|
||||||
admin.add_view(ModelView(Group, db.session))
|
from advlabdb.utils import randomPassword
|
||||||
admin.add_view(ModelView(GroupExperiment, db.session))
|
|
||||||
admin.add_view(ModelView(Experiment, db.session))
|
class UserModelView(SecureModelView):
|
||||||
admin.add_view(ModelView(PartExperiment, db.session))
|
column_list = ["email", "active", "roles", "assistant"]
|
||||||
admin.add_view(ModelView(Assistant, db.session))
|
column_searchable_list = ["email"]
|
||||||
admin.add_view(ModelView(Appointment, db.session))
|
column_filters = ["active"]
|
||||||
admin.add_view(ModelView(Part, db.session))
|
form_columns = ["email", "roles"]
|
||||||
admin.add_view(ModelView(Semester, db.session))
|
|
||||||
admin.add_view(ModelView(ExperimentMark, db.session))
|
def create_model(self, form):
|
||||||
admin.add_view(ModelView(User, db.session))
|
password = randomPassword()
|
||||||
admin.add_view(ModelView(Role, db.session))
|
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"))
|
||||||
|
|
|
@ -24,6 +24,9 @@ class Student(db.Model):
|
||||||
note = db.Column(db.Text, nullable=True)
|
note = db.Column(db.Text, nullable=True)
|
||||||
part_students = db.relationship("PartStudent", backref="student", lazy=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):
|
class PartStudent(db.Model):
|
||||||
# A student doing a specific part
|
# A student doing a specific part
|
||||||
|
@ -67,6 +70,9 @@ class Experiment(db.Model):
|
||||||
final_weighting = db.Column(db.Float, nullable=False)
|
final_weighting = db.Column(db.Float, nullable=False)
|
||||||
part_experiments = db.relationship("PartExperiment", backref="experiment", lazy=True)
|
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
|
# Helper table for the many to many relationship between Assistant and PartExperiment
|
||||||
experiment_assistant = db.Table("experiment_assistant",
|
experiment_assistant = db.Table("experiment_assistant",
|
||||||
|
@ -99,6 +105,9 @@ class Assistant(db.Model):
|
||||||
appointments = db.relationship("Appointment", backref="assistant", lazy=True)
|
appointments = db.relationship("Appointment", backref="assistant", lazy=True)
|
||||||
experiment_marks = db.relationship("ExperimentMark", 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):
|
class Appointment(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
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
|
label = db.Column(db.String(100), nullable=False) # WS2122 for example
|
||||||
parts = db.relationship("Part", backref="semester", lazy=True)
|
parts = db.relationship("Part", backref="semester", lazy=True)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<{self.label}>"
|
||||||
|
|
||||||
|
|
||||||
class ExperimentMark(db.Model):
|
class ExperimentMark(db.Model):
|
||||||
# A mark for a student after a specific experiment
|
# A mark for a student after a specific experiment
|
||||||
|
@ -137,6 +149,10 @@ class ExperimentMark(db.Model):
|
||||||
class User(db.Model, FsUserMixin):
|
class User(db.Model, FsUserMixin):
|
||||||
assistant = db.relationship("Assistant", backref="user", lazy=True, uselist=False)
|
assistant = db.relationship("Assistant", backref="user", lazy=True, uselist=False)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<{self.email}>"
|
||||||
|
|
||||||
|
|
||||||
class Role(db.Model, FsRoleMixin):
|
class Role(db.Model, FsRoleMixin):
|
||||||
pass
|
def __repr__(self):
|
||||||
|
return f"<{self.name}>"
|
||||||
|
|
|
@ -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"])
|
@app.route("/deactivate_users", methods=["GET"])
|
||||||
@roles_required("admin")
|
@roles_required("admin")
|
||||||
def deactivate_users():
|
def deactivate_users():
|
||||||
|
@ -241,42 +217,3 @@ def semesters():
|
||||||
def set_semester():
|
def set_semester():
|
||||||
session["activeSemesterId"] = int(request.args.get("semester_id"))
|
session["activeSemesterId"] = int(request.args.get("semester_id"))
|
||||||
return redirect(request.referrer)
|
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,
|
|
||||||
)
|
|
||||||
|
|
|
@ -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 %}
|
|
|
@ -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 %}
|
|
|
@ -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
70
poetry.lock
generated
|
@ -258,7 +258,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlalchemy"
|
name = "sqlalchemy"
|
||||||
version = "1.4.9"
|
version = "1.4.11"
|
||||||
description = "Database Abstraction Library"
|
description = "Database Abstraction Library"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -498,40 +498,40 @@ pyflakes = [
|
||||||
{file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"},
|
{file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"},
|
||||||
]
|
]
|
||||||
sqlalchemy = [
|
sqlalchemy = [
|
||||||
{file = "SQLAlchemy-1.4.9-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:e26791ac43806dec1f18d328596db87f1b37f9d8271997dd1233054b4c377f51"},
|
{file = "SQLAlchemy-1.4.11-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:b8b7d66ee8b8ac272adce0af1342a60854f0d89686e6d3318127a6a82a2f765c"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c4485040d86d4b3d9aa509fd3c492de3687d9bf52fb85d66b33912ad068a088c"},
|
{file = "SQLAlchemy-1.4.11-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:03a503ecff0cc2be3ad4dafd220eaff13721edb11c191670b7662932fb0a5c3a"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp27-cp27m-win32.whl", hash = "sha256:a8763fe4de02f746666161b130cc3e5d1494a6f5475f5622f05251739fc22e55"},
|
{file = "SQLAlchemy-1.4.11-cp27-cp27m-win32.whl", hash = "sha256:9cf94161cb55507cee147bf8abcfd3c076b353ad18743296764dd81108ea74f8"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp27-cp27m-win_amd64.whl", hash = "sha256:e7d262415e4adf148441bd9f10ae4e5498d6649962fabc62a64ec7b4891d56c5"},
|
{file = "SQLAlchemy-1.4.11-cp27-cp27m-win_amd64.whl", hash = "sha256:d08173144aebdf30c21a331b532db16535cfa83deed12e8703fa6c67c0894ffc"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:c6f228b79fd757d9ca539c9958190b3a44308f743dc7d83575aa0891033f6c86"},
|
{file = "SQLAlchemy-1.4.11-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:31e941d6db8b026bc63e46ef71e877913f128bd44260b90c645432626b7f9a47"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:cfbf2cf8e8ef0a1d23bfd0fa387057e6e522d55e43821f1d115941d913ee7762"},
|
{file = "SQLAlchemy-1.4.11-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:1e14fa32969badef9c309f55352e5c46f321bd29f7c600556caacdaa3eddfcf6"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:815a8cdf9c0fa504d0bfbe83fb3e596b7663fc828b73259a20299c01330467aa"},
|
{file = "SQLAlchemy-1.4.11-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6389b10e23329dc8b5600c1a84e3da2628d0f437d8a5cd05aefd1470ec571dd1"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:cfa4a336de7d32ae30b54f7b8ec888fb5c6313a1b7419a9d7b3f49cdd83012a3"},
|
{file = "SQLAlchemy-1.4.11-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:4f631edf45a943738fa77612e85fc5c5d3fb637c4f5a530f7eedd1a7cd7a70a7"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:065ac7331b87494a86bf3dc4430c1ee7779d6dc532213c528394ddd00804e518"},
|
{file = "SQLAlchemy-1.4.11-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:4a7d4da2acf6d5d068fb41c48950827c49c3c68bfb46a1da45ea8fbf7ed4b471"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:690fbca2a208314504a2ab46d3e7dae320247fcb1967863b9782a70bf49fc600"},
|
{file = "SQLAlchemy-1.4.11-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:f772e4428d413c0affe2a34836278fbe9df9a9c0940705860c2d3a4b50af1a66"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp36-cp36m-win32.whl", hash = "sha256:4edff2b4101a1c442fb1b17d594a5fdf99145f27c5eaffae12c26aef2bb2bf65"},
|
{file = "SQLAlchemy-1.4.11-cp36-cp36m-win32.whl", hash = "sha256:0140f6dac2659fa6783e7029085ab0447d8eb23cf4d831fb907588d27ba158f7"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp36-cp36m-win_amd64.whl", hash = "sha256:6c6090d73820dcf04549f0b6e80f67b46c8191f0e40bf09c6d6f8ece2464e8b6"},
|
{file = "SQLAlchemy-1.4.11-cp36-cp36m-win_amd64.whl", hash = "sha256:7d89add44938ea4f52c7641d5805c9e154fed4381e874ef3221483eeb191a96d"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:fc82688695eacf77befc3d839df2bc7ff314cd1d547f120835acdcbac1a480b8"},
|
{file = "SQLAlchemy-1.4.11-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:452c4e002be727cb6f929dbd32bbc666a0921b86555b8af09709060ed3954bd3"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4e554872766d2783abf0a11704536596e8794229fb0fa63d311a74caae58c6c5"},
|
{file = "SQLAlchemy-1.4.11-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:069de3a701d33709236efe0d06f38846b738b19c63d45cc47f54590982ba7802"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bce6eaf7b9a3a445911e225570b8fd26b7e98654ac9f308a8a52addb64a2a488"},
|
{file = "SQLAlchemy-1.4.11-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bb1072fdf48ba870c0fe81bee8babe4ba2f096fb56bb4f3e0c2386a7626e405c"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:25aaf0bec9eadde9789e3c0178c718ae6923b57485fdeae85999bc3089d9b871"},
|
{file = "SQLAlchemy-1.4.11-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:8f96d4b6a49d3f0f109365bb6303ae5d266d3f90280ca68cf8b2c46032491038"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:f239778cf03cd46da4962636501f6dea55af9b4684cd7ceee104ad4f0290e878"},
|
{file = "SQLAlchemy-1.4.11-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:98214f04802a3fc740038744d8981a8f2fdca710f791ca125fc4792737d9f3a7"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp37-cp37m-win32.whl", hash = "sha256:b0266e133d819d33b555798822606e876187a96798e2d8c9b7f85e419d73ef94"},
|
{file = "SQLAlchemy-1.4.11-cp37-cp37m-win32.whl", hash = "sha256:9fdf0713166f33e5e6ea98cf59deb305cb323131277f6880de6c509f468076f8"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp37-cp37m-win_amd64.whl", hash = "sha256:230b210fc6d1af5d555d1d04ff9bd4259d6ab82b020369724ab4a1c805a32dd3"},
|
{file = "SQLAlchemy-1.4.11-cp37-cp37m-win_amd64.whl", hash = "sha256:6ebd58e73b7bd902688c0bb8dbabb0c36b756f02cc7b27ad5efa2f380c611f95"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:a28c7b96bc5beef585172ca9d79068ae7fa2527feaa26bd63371851d7894c66f"},
|
{file = "SQLAlchemy-1.4.11-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:a41ab83ecfadf38a47bdfaf4e488f71579df47a711e1ab1dce30d34c7c25bd00"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:457a1652bc1c5f832165ff341380b3742bfb98b9ceca24576350992713ad700f"},
|
{file = "SQLAlchemy-1.4.11-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:034b42a6a59bf4ddc57e5a38a9dbac83ccd94c0b565ba91dba4ff58149706028"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:e9e95568eafae18ac40d00694b82dc3febe653f81eee83204ef248563f39696d"},
|
{file = "SQLAlchemy-1.4.11-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:1735e06a3d5b0793d5ee2d952df8a5c63edaff6383c2210c9b5c93dc2ea4c315"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:0d8aab144cf8d31c1ac834802c7df4430248f74bd8b3ed3149f9c9eec0eafe50"},
|
{file = "SQLAlchemy-1.4.11-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:96de1d4a2e05d4a017087cb29cd6a8ebfeecfd0e9f872880b1a589f011c1c02e"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:cde2cf3ee76e8c538f2f43f5cf9252ad53404fc350801191128bab68f335a8b2"},
|
{file = "SQLAlchemy-1.4.11-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:7180830ea1082b96b94884bc352b274e29b45151b6ee911bf1fd79cba2de659b"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp38-cp38-win32.whl", hash = "sha256:bb97aeaa699c43da62e35856ab56e5154d062c09a3593a2c12c67d6a21059920"},
|
{file = "SQLAlchemy-1.4.11-cp38-cp38-win32.whl", hash = "sha256:961b089e64c2ad29ad367487dd3ba1aa3eeba56bc82037ce91732baaa0f6ca90"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp38-cp38-win_amd64.whl", hash = "sha256:fbdcf9019e92253fc6aa0bcd5937302664c3a4d53884c425c0caa994e56c4421"},
|
{file = "SQLAlchemy-1.4.11-cp38-cp38-win_amd64.whl", hash = "sha256:19633df6be629200ff3c026f2837e1dd17908fb1bcea860290a5a45e6fa5148e"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:2e1b8d31c97a2b91aea8ed8299ad360a32d60728a89f2aac9c98eef07a633a0e"},
|
{file = "SQLAlchemy-1.4.11-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:789be639501445d85fd4ca41d04f0f5c6cbb6deb0c6826aaa6f22774fe84ef94"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7bdb0f972bc35054c05088e91cec8fa810c3aa565b690bae75c005ee430e12e8"},
|
{file = "SQLAlchemy-1.4.11-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:ac14fee167653ec6dee32d6aa4d501d90ae1bfbbc3eb5816940bccf227f0d617"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ec7c33e22beac16b4c5348c41cd94cfee056152e55a0efc62843deebfc53fcb4"},
|
{file = "SQLAlchemy-1.4.11-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:cd823071b97c1a6ac3af9e43b5d861126a1304033dcd18dfe354a02ec45642fe"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:29816a338982c30dd7ee76c4e79f17d5991abb1b6561e9f1d72703d030a79c86"},
|
{file = "SQLAlchemy-1.4.11-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:842b0d4698381aac047f8ae57409c90b7e63ebabf5bc02814ddc8eaefd13499e"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:099e63ffad329989080c533896267c40f9cb38ed5704168f7dae3afdda121e10"},
|
{file = "SQLAlchemy-1.4.11-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:45a720029756800628359192630fffdc9660ab6f27f0409bd24d9e09d75d6c18"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp39-cp39-win32.whl", hash = "sha256:343c679899afdc4952ac659dc46f2075a2bd4fba87ca0df264be838eecd02096"},
|
{file = "SQLAlchemy-1.4.11-cp39-cp39-win32.whl", hash = "sha256:e7d76312e904aa4ea221a92c0bc2e299ad46e4580e2d72ca1f7e6d31dce5bfab"},
|
||||||
{file = "SQLAlchemy-1.4.9-cp39-cp39-win_amd64.whl", hash = "sha256:386f215248c3fb2fab9bb77f631bc3c6cd38354ca2363d241784f8297d16b80a"},
|
{file = "SQLAlchemy-1.4.11-cp39-cp39-win_amd64.whl", hash = "sha256:4a2e7f037d3ca818d6d0490e3323fd451545f580df30d62b698da2f247015a34"},
|
||||||
{file = "SQLAlchemy-1.4.9.tar.gz", hash = "sha256:f31757972677fbe9132932a69a4f23db59187a072cc26427f56a3082b46b6dac"},
|
{file = "SQLAlchemy-1.4.11.tar.gz", hash = "sha256:4ad4044eb86fbcbdff2106e44f479fbdac703d77860b3e19988c8a8786e73061"},
|
||||||
]
|
]
|
||||||
werkzeug = [
|
werkzeug = [
|
||||||
{file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"},
|
{file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"},
|
||||||
|
|
Loading…
Reference in a new issue