mirror of
https://codeberg.org/Mo8it/AdvLabDB.git
synced 2024-12-20 23:41:20 +00:00
Apply changes from the database
This commit is contained in:
parent
bb1b6a86b2
commit
8178c72fe2
6 changed files with 113 additions and 125 deletions
|
@ -27,9 +27,9 @@ from advlabdb.models import (
|
||||||
Semester,
|
Semester,
|
||||||
Student,
|
Student,
|
||||||
User,
|
User,
|
||||||
|
Program,
|
||||||
)
|
)
|
||||||
from advlabdb.utils import (
|
from advlabdb.utils import (
|
||||||
partFromLabelInUserActiveSemester,
|
|
||||||
randomPassword,
|
randomPassword,
|
||||||
setUserActiveSemester,
|
setUserActiveSemester,
|
||||||
userActiveSemester,
|
userActiveSemester,
|
||||||
|
@ -100,12 +100,12 @@ class SemesterView(SecureModelView):
|
||||||
can_delete = False
|
can_delete = False
|
||||||
can_view_details = True
|
can_view_details = True
|
||||||
|
|
||||||
column_list = ["label", "parts"]
|
column_list = ["label", "number", "parts"]
|
||||||
column_details_list = column_list + ["active_users"]
|
column_details_list = column_list + ["active_users", "groups"]
|
||||||
form_columns = ["semester_label", "year", "transfer_parts", "transfer_assistants"]
|
form_columns = ["label", "year", "transfer_parts", "transfer_assistants"]
|
||||||
|
|
||||||
form_extra_fields = {
|
form_extra_fields = {
|
||||||
"semester_label": RadioField("Semester", choices=["WS", "SS"], validators=[DataRequired()]),
|
"label": RadioField("Semester", choices=["WS", "SS"], validators=[DataRequired()]),
|
||||||
"year": TextField("Year", validators=[DataRequired()]),
|
"year": TextField("Year", validators=[DataRequired()]),
|
||||||
"transfer_parts": BooleanField(
|
"transfer_parts": BooleanField(
|
||||||
"Transfer parts",
|
"Transfer parts",
|
||||||
|
@ -121,7 +121,7 @@ class SemesterView(SecureModelView):
|
||||||
|
|
||||||
def create_model(self, form):
|
def create_model(self, form):
|
||||||
try:
|
try:
|
||||||
model = Semester(label=form.semester_label.data + form.year.data)
|
model = Semester(label=form.label.data, year=form.year.data)
|
||||||
|
|
||||||
self.session.add(model)
|
self.session.add(model)
|
||||||
self.on_model_change(form, model, True)
|
self.on_model_change(form, model, True)
|
||||||
|
@ -137,7 +137,7 @@ class SemesterView(SecureModelView):
|
||||||
def after_model_change(self, form, model, is_created):
|
def after_model_change(self, form, model, is_created):
|
||||||
admin.add_link(
|
admin.add_link(
|
||||||
MenuLink(
|
MenuLink(
|
||||||
name=model.label,
|
name=model.repr(),
|
||||||
url=url_for("set_semester") + "?semester_id=" + str(model.id),
|
url=url_for("set_semester") + "?semester_id=" + str(model.id),
|
||||||
category="Active semester",
|
category="Active semester",
|
||||||
)
|
)
|
||||||
|
@ -174,8 +174,8 @@ class SemesterView(SecureModelView):
|
||||||
|
|
||||||
class PartView(SecureModelView):
|
class PartView(SecureModelView):
|
||||||
can_view_details = True
|
can_view_details = True
|
||||||
column_details_list = ["label", "semester", "part_students", "groups"]
|
column_details_list = ["program", "number", "semester", "part_students"]
|
||||||
form_columns = ["label", "semester"]
|
form_columns = ["program", "number", "semester"]
|
||||||
|
|
||||||
def queryFilter(self):
|
def queryFilter(self):
|
||||||
return Part.semester == userActiveSemester()
|
return Part.semester == userActiveSemester()
|
||||||
|
@ -212,7 +212,7 @@ def partQueryFactory():
|
||||||
|
|
||||||
|
|
||||||
def groupQueryFactory():
|
def groupQueryFactory():
|
||||||
return Group.query.filter(Group.part_id.in_([part.id for part in userActiveSemester().parts]))
|
return Group.query.filter(Group.semester == userActiveSemester())
|
||||||
|
|
||||||
|
|
||||||
markChoices = [(-1, "-")] + list(zip(range(16)[::-1], range(16)[::-1]))
|
markChoices = [(-1, "-")] + list(zip(range(16)[::-1], range(16)[::-1]))
|
||||||
|
@ -261,8 +261,7 @@ class PartStudentView(SecureModelView):
|
||||||
return form(get_form_data(), obj=obj)
|
return form(get_form_data(), obj=obj)
|
||||||
|
|
||||||
def on_model_change(self, form, model, is_created):
|
def on_model_change(self, form, model, is_created):
|
||||||
if model.group and model.part != model.group.part:
|
PartStudent.check(model.group, model.group)
|
||||||
raise ModelViewException("Student's part and group's part do not match!")
|
|
||||||
|
|
||||||
def update_model(self, form, model):
|
def update_model(self, form, model):
|
||||||
if form.final_part_mark.data == -1:
|
if form.final_part_mark.data == -1:
|
||||||
|
@ -277,29 +276,21 @@ def partStudentsQueryFactory():
|
||||||
|
|
||||||
class GroupView(SecureModelView):
|
class GroupView(SecureModelView):
|
||||||
class CreateForm(Form):
|
class CreateForm(Form):
|
||||||
part = QuerySelectField(
|
part_students = QuerySelectMultipleField(
|
||||||
"Part",
|
"Part Students", query_factory=partStudentsQueryFactory, validators=[DataRequired()]
|
||||||
query_factory=partQueryFactory,
|
|
||||||
validators=[DataRequired()],
|
|
||||||
allow_blank=True,
|
|
||||||
blank_text="-",
|
|
||||||
)
|
)
|
||||||
part_students = QuerySelectMultipleField("Part Students", query_factory=partStudentsQueryFactory)
|
|
||||||
|
|
||||||
class EditForm(CreateForm):
|
form = CreateForm
|
||||||
part = None
|
|
||||||
|
|
||||||
form = EditForm
|
column_list = ["number", "semester", "program", "part_students", "group_experiments"]
|
||||||
|
column_filters = ["number", "semester", "program"]
|
||||||
column_list = ["number", "part", "part_students", "group_experiments"]
|
|
||||||
column_filters = ["number", "part"]
|
|
||||||
|
|
||||||
def queryFilter(self):
|
def queryFilter(self):
|
||||||
return Group.part_id.in_([part.id for part in userActiveSemester().parts])
|
return Group.semester == userActiveSemester()
|
||||||
|
|
||||||
def create_model(self, form):
|
def create_model(self, form):
|
||||||
try:
|
try:
|
||||||
model = Group.customInit(form.part.data, form.part_students.data)
|
model = Group.customInit(form.part_students.data)
|
||||||
|
|
||||||
self.session.add(model)
|
self.session.add(model)
|
||||||
self.on_model_change(form, model, True)
|
self.on_model_change(form, model, True)
|
||||||
|
@ -313,19 +304,15 @@ class GroupView(SecureModelView):
|
||||||
return model
|
return model
|
||||||
|
|
||||||
def update_model(self, form, model):
|
def update_model(self, form, model):
|
||||||
Group.checkPartStudents(form.part_students.data)
|
Group.check(form.part_students.data, model.program)
|
||||||
|
|
||||||
return super().update_model(form, model)
|
return super().update_model(form, model)
|
||||||
|
|
||||||
def create_form(self, obj=None):
|
|
||||||
form = self.CreateForm
|
|
||||||
return form(get_form_data(), obj=obj)
|
|
||||||
|
|
||||||
|
|
||||||
class ExperimentView(SecureModelView):
|
class ExperimentView(SecureModelView):
|
||||||
can_view_details = True
|
can_view_details = True
|
||||||
column_filters = ["active"]
|
column_filters = ["active"]
|
||||||
column_list = ["label", "title", "active"]
|
column_list = ["number", "program", "title", "active"]
|
||||||
column_details_list = column_list + [
|
column_details_list = column_list + [
|
||||||
"desciption",
|
"desciption",
|
||||||
"wiki_link",
|
"wiki_link",
|
||||||
|
@ -420,10 +407,7 @@ class GroupExperimentView(SecureModelView):
|
||||||
|
|
||||||
def queryFilter(self):
|
def queryFilter(self):
|
||||||
return GroupExperiment.group_id.in_(
|
return GroupExperiment.group_id.in_(
|
||||||
[
|
[group.id for group in Group.query.filter(Group.semester == userActiveSemester())]
|
||||||
group.id
|
|
||||||
for group in Group.query.filter(Group.part_id.in_([part.id for part in userActiveSemester().parts]))
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def create_model(self, form):
|
def create_model(self, form):
|
||||||
|
@ -617,6 +601,11 @@ class ExperimentMarkView(SecureModelView):
|
||||||
self.session.rollback()
|
self.session.rollback()
|
||||||
|
|
||||||
|
|
||||||
|
class ProgramView(SecureModelView):
|
||||||
|
column_list = ["label"]
|
||||||
|
column_details_list = column_list + ["parts", "experiments", "groups"]
|
||||||
|
|
||||||
|
|
||||||
admin.add_view(StudentView(Student, db.session))
|
admin.add_view(StudentView(Student, db.session))
|
||||||
admin.add_view(PartStudentView(PartStudent, db.session))
|
admin.add_view(PartStudentView(PartStudent, db.session))
|
||||||
admin.add_view(GroupView(Group, db.session))
|
admin.add_view(GroupView(Group, db.session))
|
||||||
|
@ -626,6 +615,7 @@ admin.add_view(ExperimentMarkView(ExperimentMark, db.session))
|
||||||
admin.add_view(ExperimentView(Experiment, db.session))
|
admin.add_view(ExperimentView(Experiment, db.session))
|
||||||
admin.add_view(SemesterExperimentView(SemesterExperiment, db.session))
|
admin.add_view(SemesterExperimentView(SemesterExperiment, db.session))
|
||||||
admin.add_view(AssistantView(Assistant, db.session))
|
admin.add_view(AssistantView(Assistant, db.session))
|
||||||
|
admin.add_view(ProgramView(Program, db.session))
|
||||||
admin.add_view(PartView(Part, db.session))
|
admin.add_view(PartView(Part, db.session))
|
||||||
admin.add_view(SemesterView(Semester, db.session))
|
admin.add_view(SemesterView(Semester, db.session))
|
||||||
admin.add_view(UserView(User, db.session))
|
admin.add_view(UserView(User, db.session))
|
||||||
|
@ -636,7 +626,7 @@ with app.app_context():
|
||||||
for semester in semesters:
|
for semester in semesters:
|
||||||
admin.add_link(
|
admin.add_link(
|
||||||
MenuLink(
|
MenuLink(
|
||||||
name=semester.label,
|
name=semester.repr(),
|
||||||
url=url_for("set_semester") + "?semester_id=" + str(semester.id),
|
url=url_for("set_semester") + "?semester_id=" + str(semester.id),
|
||||||
category="Active semester",
|
category="Active semester",
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{% extends "admin/model/create.html" %}
|
{% extends "admin/model/create.html" %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
User's active semester: {{userActiveSemester().label}}
|
User's active semester: {{userActiveSemester().repr()}}
|
||||||
{{super()}}
|
{{super()}}
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -1,6 +1,6 @@
|
||||||
{% extends "admin/model/edit.html" %}
|
{% extends "admin/model/edit.html" %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
User's active semester: {{userActiveSemester().label}}
|
User's active semester: {{userActiveSemester().repr()}}
|
||||||
{{super()}}
|
{{super()}}
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -1,7 +1,7 @@
|
||||||
{% extends "admin/index.html" %}
|
{% extends "admin/index.html" %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
User's active semester: {{userActiveSemester(flashWarning=True).label}}
|
User's active semester: {{userActiveSemester(flashWarning=True).repr()}}
|
||||||
<h3>Welcome back, commander!</h3>
|
<h3>Welcome back, commander!</h3>
|
||||||
{{super()}}
|
{{super()}}
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -1,6 +1,6 @@
|
||||||
{% extends "admin/model/list.html" %}
|
{% extends "admin/model/list.html" %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
User's active semester: {{userActiveSemester(flashWarning=True).label}}
|
User's active semester: {{userActiveSemester(flashWarning=True).repr()}}
|
||||||
{{super()}}
|
{{super()}}
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -1,81 +1,79 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
|
||||||
<!-- Required meta tags -->
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
|
|
||||||
<!-- Bootstrap CSS -->
|
<head>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" integrity="sha256-nq7J0kse50upWdNiXRDsuGd/AkfaHz0hX8HgCUsCASY=" crossorigin="anonymous">
|
<!-- Required meta tags -->
|
||||||
<!-- Fontawesome CSS -->
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.15.3/css/all.min.css" integrity="sha256-2H3fkXt6FEmrReK448mDVGKb3WW2ZZw35gI7vqHOE4Y=" crossorigin="anonymous">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<!-- Bootstrap Table CSS -->
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.18.2/dist/bootstrap-table.min.css" integrity="sha256-mypXFtMz9LOewBHR7iL9Ls3/eerooDeTAtLpo6gmTwU=" crossorigin="anonymous">
|
|
||||||
|
|
||||||
{% if title %}
|
<!-- Bootstrap CSS -->
|
||||||
<title>{{title}}</title>
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css"
|
||||||
{% else %}
|
integrity="sha256-nq7J0kse50upWdNiXRDsuGd/AkfaHz0hX8HgCUsCASY=" crossorigin="anonymous">
|
||||||
<title>AdvLabDB</title>
|
<!-- Fontawesome CSS -->
|
||||||
{% endif %}
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.15.3/css/all.min.css"
|
||||||
</head>
|
integrity="sha256-2H3fkXt6FEmrReK448mDVGKb3WW2ZZw35gI7vqHOE4Y=" crossorigin="anonymous">
|
||||||
<body>
|
<!-- Bootstrap Table CSS -->
|
||||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.18.2/dist/bootstrap-table.min.css"
|
||||||
<div class="container-fluid">
|
integrity="sha256-mypXFtMz9LOewBHR7iL9Ls3/eerooDeTAtLpo6gmTwU=" crossorigin="anonymous">
|
||||||
<a class="navbar-brand" href="{{url_for('index')}}">AdvLabDB</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
|
<title>AdvLabDB</title>
|
||||||
<span class="navbar-toggler-icon"></span>
|
</head>
|
||||||
</button>
|
|
||||||
<div class="collapse navbar-collapse" id="navbar">
|
<body>
|
||||||
<ul class="navbar-nav">
|
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||||
{% for item in navbarItems(title) %}
|
|
||||||
<li class="nav-item">
|
|
||||||
{{item|safe}}
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
{% if current_user.is_authenticated %}
|
|
||||||
<li class="nav-item dropdown">
|
|
||||||
<a class="nav-link dropdown-toggle" id="navbarDropdownMenuLink" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
|
||||||
Semester {{userActiveSemester().label}}
|
|
||||||
</a>
|
|
||||||
<ul class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
|
|
||||||
{% for item in semesterDropDownItems() %}
|
|
||||||
{{item|safe}}
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<br>
|
<a class="navbar-brand" href="{{url_for('index')}}">AdvLabDB</a>
|
||||||
{% with messages = get_flashed_messages(with_categories=True) %}
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar"
|
||||||
{% if messages %}
|
aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
{% for category, message in messages %}
|
<span class="navbar-toggler-icon"></span>
|
||||||
<div class="alert alert-{{category}}">
|
</button>
|
||||||
{{message}}
|
<div class="collapse navbar-collapse" id="navbar">
|
||||||
</div>
|
<ul class="navbar-nav">
|
||||||
{% endfor %}
|
<li class="nav-item">
|
||||||
{% endif %}
|
<a class="nav-link" href="{{url_for('security.login')}}">Login</a>
|
||||||
{% endwith %}
|
</li>
|
||||||
|
</ul>
|
||||||
{% block content %}{% endblock content %}
|
</div>
|
||||||
<br>
|
|
||||||
</div>
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<br>
|
||||||
|
{% with messages = get_flashed_messages(with_categories=True) %}
|
||||||
|
{% if messages %}
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<div class="alert alert-{{category}}">
|
||||||
|
{{message}}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
{% block content %}{% endblock content %}
|
||||||
|
<br>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- jQuery JS -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"
|
||||||
|
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||||
|
<!-- Bootstrap Bundle with Popper -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/js/bootstrap.bundle.min.js"
|
||||||
|
integrity="sha256-tfbRzZ36wuPoeUKXyuewrLOzcfgdO2ovc4ozuYRWMs4=" crossorigin="anonymous"></script>
|
||||||
|
<!-- Bootstrap Table JS -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.18.2/dist/bootstrap-table.min.js"
|
||||||
|
integrity="sha256-8mXaKD8IdqCXwwzNpp/WHddQZi77Bin0cma5y1G+B9E=" crossorigin="anonymous"></script>
|
||||||
|
<!-- Bootstrap Table Export extension JS -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/tableexport.jquery.plugin@1.10.21/libs/FileSaver/FileSaver.min.js"
|
||||||
|
integrity="sha256-S1CD3kZ9K/shgqLK8lnh6K5A/GQANjwwG+gLA1OL+Jg=" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/tableexport.jquery.plugin@1.10.21/libs/jsPDF/jspdf.min.js"
|
||||||
|
integrity="sha256-B74p+AfarPBbH2e1zQiOJFDizKgXS2sPNkUsST4+cKA=" crossorigin="anonymous"></script>
|
||||||
|
<script
|
||||||
|
src="https://cdn.jsdelivr.net/npm/tableexport.jquery.plugin@1.10.21/libs/jsPDF-AutoTable/jspdf.plugin.autotable.js"
|
||||||
|
integrity="sha256-V3ovkrEpDyy74G52YBOG4TSBoC2sHkbbitBEp9/kZcY=" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/tableexport.jquery.plugin@1.10.21/tableExport.min.js"
|
||||||
|
integrity="sha256-/VfFIAF4GLBN7iah7RFGi+zISoczKczfQTP4Dh4N0uw=" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.18.2/dist/extensions/export/bootstrap-table-export.min.js"
|
||||||
|
integrity="sha256-C4hVBAtUi42+NXYGkjgx4WD7lGG205ZVtcHo6UMIge8=" crossorigin="anonymous"></script>
|
||||||
|
{% block scripts %}{% endblock scripts %}
|
||||||
|
</body>
|
||||||
|
|
||||||
<!-- jQuery JS -->
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
|
||||||
<!-- Bootstrap Bundle with Popper -->
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/js/bootstrap.bundle.min.js" integrity="sha256-tfbRzZ36wuPoeUKXyuewrLOzcfgdO2ovc4ozuYRWMs4=" crossorigin="anonymous"></script>
|
|
||||||
<!-- Bootstrap Table JS -->
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.18.2/dist/bootstrap-table.min.js" integrity="sha256-8mXaKD8IdqCXwwzNpp/WHddQZi77Bin0cma5y1G+B9E=" crossorigin="anonymous"></script>
|
|
||||||
<!-- Bootstrap Table Export extension JS -->
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/tableexport.jquery.plugin@1.10.21/libs/FileSaver/FileSaver.min.js" integrity="sha256-S1CD3kZ9K/shgqLK8lnh6K5A/GQANjwwG+gLA1OL+Jg=" crossorigin="anonymous"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/tableexport.jquery.plugin@1.10.21/libs/jsPDF/jspdf.min.js" integrity="sha256-B74p+AfarPBbH2e1zQiOJFDizKgXS2sPNkUsST4+cKA=" crossorigin="anonymous"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/tableexport.jquery.plugin@1.10.21/libs/jsPDF-AutoTable/jspdf.plugin.autotable.js" integrity="sha256-V3ovkrEpDyy74G52YBOG4TSBoC2sHkbbitBEp9/kZcY=" crossorigin="anonymous"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/tableexport.jquery.plugin@1.10.21/tableExport.min.js" integrity="sha256-/VfFIAF4GLBN7iah7RFGi+zISoczKczfQTP4Dh4N0uw=" crossorigin="anonymous"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.18.2/dist/extensions/export/bootstrap-table-export.min.js" integrity="sha256-C4hVBAtUi42+NXYGkjgx4WD7lGG205ZVtcHo6UMIge8=" crossorigin="anonymous"></script>
|
|
||||||
{% block scripts %}{% endblock scripts %}
|
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
Loading…
Reference in a new issue