mirror of
https://github.com/aljazceru/CTFd.git
synced 2025-12-18 22:44:24 +01:00
Switch to using a Flask SQLAlchemy pagination object for submission searching
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
from flask import render_template, request
|
from flask import render_template, request, url_for
|
||||||
|
|
||||||
from CTFd.admin import admin
|
from CTFd.admin import admin
|
||||||
from CTFd.models import Challenges, Submissions
|
from CTFd.models import Challenges, Submissions
|
||||||
@@ -10,16 +10,21 @@ from CTFd.utils.modes import get_model
|
|||||||
@admin.route("/admin/submissions/<submission_type>")
|
@admin.route("/admin/submissions/<submission_type>")
|
||||||
@admins_only
|
@admins_only
|
||||||
def submissions_listing(submission_type):
|
def submissions_listing(submission_type):
|
||||||
filters = {}
|
filters_by = {}
|
||||||
if submission_type:
|
if submission_type:
|
||||||
filters["type"] = submission_type
|
filters_by["type"] = submission_type
|
||||||
|
filters = []
|
||||||
|
|
||||||
curr_page = abs(int(request.args.get("page", 1, type=int)))
|
q = request.args.get("q")
|
||||||
results_per_page = 50
|
field = request.args.get("field")
|
||||||
page_start = results_per_page * (curr_page - 1)
|
page = request.args.get("page", 1, type=int)
|
||||||
page_end = results_per_page * (curr_page - 1) + results_per_page
|
|
||||||
sub_count = Submissions.query.filter_by(**filters).count()
|
if q:
|
||||||
page_count = int(sub_count / results_per_page) + (sub_count % results_per_page > 0)
|
submissions = []
|
||||||
|
if Submissions.__mapper__.has_property(
|
||||||
|
field
|
||||||
|
): # The field exists as an exposed column
|
||||||
|
filters.append(getattr(Submissions, field).like("%{}%".format(q)))
|
||||||
|
|
||||||
Model = get_model()
|
Model = get_model()
|
||||||
|
|
||||||
@@ -34,18 +39,23 @@ def submissions_listing(submission_type):
|
|||||||
Challenges.name.label("challenge_name"),
|
Challenges.name.label("challenge_name"),
|
||||||
Model.name.label("team_name"),
|
Model.name.label("team_name"),
|
||||||
)
|
)
|
||||||
.filter_by(**filters)
|
.filter_by(**filters_by)
|
||||||
|
.filter(*filters)
|
||||||
.join(Challenges)
|
.join(Challenges)
|
||||||
.join(Model)
|
.join(Model)
|
||||||
.order_by(Submissions.date.desc())
|
.order_by(Submissions.date.desc())
|
||||||
.slice(page_start, page_end)
|
.paginate(page=page, per_page=50)
|
||||||
.all()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
args = dict(request.args)
|
||||||
|
args.pop('page', 1)
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"admin/submissions.html",
|
"admin/submissions.html",
|
||||||
submissions=submissions,
|
submissions=submissions,
|
||||||
page_count=page_count,
|
prev_page=url_for(request.endpoint, type=submission_type, **args, page=submissions.prev_num),
|
||||||
curr_page=curr_page,
|
next_page=url_for(request.endpoint, type=submission_type, **args, page=submissions.next_num),
|
||||||
type=submission_type,
|
type=submission_type,
|
||||||
|
q=q,
|
||||||
|
field=field,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -80,6 +80,24 @@ export default () => {
|
|||||||
window.location.href = url.toString();
|
window.location.href = url.toString();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(".page-prev").click(function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
let url = new URL(window.location);
|
||||||
|
let page = url.searchParams.get("page");
|
||||||
|
page = page ? page : 1;
|
||||||
|
url.searchParams.set("page", --page);
|
||||||
|
window.location.href = url.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".page-next").click(function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
let url = new URL(window.location);
|
||||||
|
let page = url.searchParams.get("page");
|
||||||
|
page = page ? page : 1;
|
||||||
|
url.searchParams.set("page", ++page);
|
||||||
|
window.location.href = url.toString();
|
||||||
|
});
|
||||||
|
|
||||||
makeSortableTables();
|
makeSortableTables();
|
||||||
$('[data-toggle="tooltip"]').tooltip();
|
$('[data-toggle="tooltip"]').tooltip();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ eval("\n\nvar _CTFd = _interopRequireDefault(__webpack_require__(/*! core/CTFd *
|
|||||||
/***/ (function(module, exports, __webpack_require__) {
|
/***/ (function(module, exports, __webpack_require__) {
|
||||||
|
|
||||||
;
|
;
|
||||||
eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\n__webpack_require__(/*! bootstrap/dist/js/bootstrap.bundle */ \"./node_modules/bootstrap/dist/js/bootstrap.bundle.js\");\n\nvar _utils = __webpack_require__(/*! core/utils */ \"./CTFd/themes/core/assets/js/utils.js\");\n\nvar _jquery = _interopRequireDefault(__webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nvar _default = function _default() {\n // TODO: This is kind of a hack to mimic a React-like state construct.\n // It should be removed once we have a real front-end framework in place.\n (0, _jquery.default)(\":input\").each(function () {\n (0, _jquery.default)(this).data(\"initial\", (0, _jquery.default)(this).val());\n });\n (0, _jquery.default)(\".form-control\").bind({\n focus: function focus() {\n (0, _jquery.default)(this).addClass(\"input-filled-valid\");\n },\n blur: function blur() {\n if ((0, _jquery.default)(this).val() === \"\") {\n (0, _jquery.default)(this).removeClass(\"input-filled-valid\");\n }\n }\n });\n (0, _jquery.default)(\".modal\").on(\"show.bs.modal\", function (e) {\n (0, _jquery.default)(\".form-control\").each(function () {\n if ((0, _jquery.default)(this).val()) {\n (0, _jquery.default)(this).addClass(\"input-filled-valid\");\n }\n });\n });\n (0, _jquery.default)(function () {\n (0, _jquery.default)(\".form-control\").each(function () {\n if ((0, _jquery.default)(this).val()) {\n (0, _jquery.default)(this).addClass(\"input-filled-valid\");\n }\n });\n (0, _jquery.default)(\"tr[data-href]\").click(function () {\n var sel = getSelection().toString();\n\n if (!sel) {\n var href = (0, _jquery.default)(this).attr(\"data-href\");\n\n if (href) {\n window.location = href;\n }\n }\n\n return false;\n });\n (0, _jquery.default)(\"[data-checkbox]\").click(function (e) {\n if ((0, _jquery.default)(e.target).is(\"input[type=checkbox]\")) {\n e.stopImmediatePropagation();\n return;\n }\n\n var checkbox = (0, _jquery.default)(this).find(\"input[type=checkbox]\"); // Doing it this way with an event allows data-checkbox-all to work\n\n checkbox.click();\n e.stopImmediatePropagation();\n });\n (0, _jquery.default)(\"[data-checkbox-all]\").on(\"click change\", function (e) {\n var checked = (0, _jquery.default)(this).prop(\"checked\");\n var idx = (0, _jquery.default)(this).index() + 1;\n (0, _jquery.default)(this).closest(\"table\").find(\"tr td:nth-child(\".concat(idx, \") input[type=checkbox]\")).prop(\"checked\", checked);\n e.stopImmediatePropagation();\n });\n (0, _jquery.default)(\"tr[data-href] a, tr[data-href] button\").click(function (e) {\n // TODO: This is a hack to allow modal close buttons to work\n if (!(0, _jquery.default)(this).attr(\"data-dismiss\")) {\n e.stopPropagation();\n }\n });\n (0, _jquery.default)(\".page-select\").change(function () {\n var url = new URL(window.location);\n url.searchParams.set(\"page\", this.value);\n window.location.href = url.toString();\n });\n (0, _utils.makeSortableTables)();\n (0, _jquery.default)('[data-toggle=\"tooltip\"]').tooltip();\n });\n};\n\nexports.default = _default;\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/styles.js?");
|
eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\n__webpack_require__(/*! bootstrap/dist/js/bootstrap.bundle */ \"./node_modules/bootstrap/dist/js/bootstrap.bundle.js\");\n\nvar _utils = __webpack_require__(/*! core/utils */ \"./CTFd/themes/core/assets/js/utils.js\");\n\nvar _jquery = _interopRequireDefault(__webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nvar _default = function _default() {\n // TODO: This is kind of a hack to mimic a React-like state construct.\n // It should be removed once we have a real front-end framework in place.\n (0, _jquery.default)(\":input\").each(function () {\n (0, _jquery.default)(this).data(\"initial\", (0, _jquery.default)(this).val());\n });\n (0, _jquery.default)(\".form-control\").bind({\n focus: function focus() {\n (0, _jquery.default)(this).addClass(\"input-filled-valid\");\n },\n blur: function blur() {\n if ((0, _jquery.default)(this).val() === \"\") {\n (0, _jquery.default)(this).removeClass(\"input-filled-valid\");\n }\n }\n });\n (0, _jquery.default)(\".modal\").on(\"show.bs.modal\", function (e) {\n (0, _jquery.default)(\".form-control\").each(function () {\n if ((0, _jquery.default)(this).val()) {\n (0, _jquery.default)(this).addClass(\"input-filled-valid\");\n }\n });\n });\n (0, _jquery.default)(function () {\n (0, _jquery.default)(\".form-control\").each(function () {\n if ((0, _jquery.default)(this).val()) {\n (0, _jquery.default)(this).addClass(\"input-filled-valid\");\n }\n });\n (0, _jquery.default)(\"tr[data-href]\").click(function () {\n var sel = getSelection().toString();\n\n if (!sel) {\n var href = (0, _jquery.default)(this).attr(\"data-href\");\n\n if (href) {\n window.location = href;\n }\n }\n\n return false;\n });\n (0, _jquery.default)(\"[data-checkbox]\").click(function (e) {\n if ((0, _jquery.default)(e.target).is(\"input[type=checkbox]\")) {\n e.stopImmediatePropagation();\n return;\n }\n\n var checkbox = (0, _jquery.default)(this).find(\"input[type=checkbox]\"); // Doing it this way with an event allows data-checkbox-all to work\n\n checkbox.click();\n e.stopImmediatePropagation();\n });\n (0, _jquery.default)(\"[data-checkbox-all]\").on(\"click change\", function (e) {\n var checked = (0, _jquery.default)(this).prop(\"checked\");\n var idx = (0, _jquery.default)(this).index() + 1;\n (0, _jquery.default)(this).closest(\"table\").find(\"tr td:nth-child(\".concat(idx, \") input[type=checkbox]\")).prop(\"checked\", checked);\n e.stopImmediatePropagation();\n });\n (0, _jquery.default)(\"tr[data-href] a, tr[data-href] button\").click(function (e) {\n // TODO: This is a hack to allow modal close buttons to work\n if (!(0, _jquery.default)(this).attr(\"data-dismiss\")) {\n e.stopPropagation();\n }\n });\n (0, _jquery.default)(\".page-select\").change(function () {\n var url = new URL(window.location);\n url.searchParams.set(\"page\", this.value);\n window.location.href = url.toString();\n });\n (0, _jquery.default)(\".page-prev\").click(function (e) {\n e.preventDefault();\n var url = new URL(window.location);\n var page = url.searchParams.get(\"page\");\n page = page ? page : 1;\n url.searchParams.set(\"page\", --page);\n window.location.href = url.toString();\n });\n (0, _jquery.default)(\".page-next\").click(function (e) {\n e.preventDefault();\n var url = new URL(window.location);\n var page = url.searchParams.get(\"page\");\n page = page ? page : 1;\n url.searchParams.set(\"page\", ++page);\n window.location.href = url.toString();\n });\n (0, _utils.makeSortableTables)();\n (0, _jquery.default)('[data-toggle=\"tooltip\"]').tooltip();\n });\n};\n\nexports.default = _default;\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/styles.js?");
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,8 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
{% if q and field %}
|
{% if q and field %}
|
||||||
<h4 class="text-center">Searching for submissions with {{field}} matching {{q}}</h4>
|
<h5 class="text-muted text-center">Searching for submissions with <strong>{{ field }}</strong> matching <strong>{{ q }}</strong></h5>
|
||||||
|
<h6 class="text-muted text-center pb-3">Page {{ submissions.page }} of {{ submissions.total }} results</h6>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<form method="GET" class="form-inline">
|
<form method="GET" class="form-inline">
|
||||||
@@ -71,7 +72,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for sub in submissions %}
|
{% for sub in submissions.items %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="border-right" data-checkbox>
|
<td class="border-right" data-checkbox>
|
||||||
<div class="form-check text-center">
|
<div class="form-check text-center">
|
||||||
@@ -102,19 +103,19 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{% if page_count > 1 %}
|
{% if submissions.pages > 1 %}
|
||||||
<div class="text-center">Page
|
<div class="text-center">Page
|
||||||
<br>
|
<br>
|
||||||
{% if curr_page != 1 %}
|
{% if submissions.page != 1 %}
|
||||||
<a href="?page={{ curr_page - 1 }}"><<<</a>
|
<a href="{{ prev_page }}"><<<</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<select class="page-select">
|
<select class="page-select">
|
||||||
{% for page in range(1, page_count + 1) %}
|
{% for page in range(1, submissions.pages + 1) %}
|
||||||
<option {% if curr_page == page %}selected{% endif %}>{{ page }}</option>
|
<option {% if submissions.page == page %}selected{% endif %}>{{ page }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
{% if curr_page != page_count %}
|
{% if submissions.next_num %}
|
||||||
<a href="?page={{ curr_page + 1 }}">>>></a>
|
<a href="{{ next_page }}">>>></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
Reference in New Issue
Block a user