From 96ecdedfdf2344a118c7c4a5d8b1ed27389bd587 Mon Sep 17 00:00:00 2001 From: Kevin Chung Date: Sun, 28 Jun 2020 23:29:48 -0400 Subject: [PATCH] Add account searching to the user facing side (#1517) * Add account searching to the user facing side of CTFd * Closes #1354 --- CTFd/forms/teams.py | 22 ++++++++++- CTFd/forms/users.py | 16 ++++++++ CTFd/teams.py | 25 +++++++++++-- CTFd/themes/admin/templates/teams/teams.html | 2 +- CTFd/themes/core/templates/teams/teams.html | 39 +++++++++++++++++--- CTFd/themes/core/templates/users/users.html | 39 +++++++++++++++++--- CTFd/users.py | 26 +++++++++++-- 7 files changed, 148 insertions(+), 21 deletions(-) diff --git a/CTFd/forms/teams.py b/CTFd/forms/teams.py index c5726190..fc047cad 100644 --- a/CTFd/forms/teams.py +++ b/CTFd/forms/teams.py @@ -38,7 +38,27 @@ class TeamCaptainForm(BaseForm): class TeamSearchForm(BaseForm): field = SelectField( "Search Field", - choices=[("name", "Name"), ("id", "ID"), ("affiliation", "Affiliation")], + choices=[ + ("name", "Name"), + ("id", "ID"), + ("affiliation", "Affiliation"), + ("website", "Website"), + ], + default="name", + validators=[InputRequired()], + ) + q = StringField("Parameter", validators=[InputRequired()]) + submit = SubmitField("Search") + + +class PublicTeamSearchForm(BaseForm): + field = SelectField( + "Search Field", + choices=[ + ("name", "Name"), + ("affiliation", "Affiliation"), + ("website", "Website"), + ], default="name", validators=[InputRequired()], ) diff --git a/CTFd/forms/users.py b/CTFd/forms/users.py index 63da61dd..7b47d172 100644 --- a/CTFd/forms/users.py +++ b/CTFd/forms/users.py @@ -15,6 +15,7 @@ class UserSearchForm(BaseForm): ("id", "ID"), ("email", "Email"), ("affiliation", "Affiliation"), + ("website", "Website"), ("ip", "IP Address"), ], default="name", @@ -24,6 +25,21 @@ class UserSearchForm(BaseForm): submit = SubmitField("Search") +class PublicUserSearchForm(BaseForm): + field = SelectField( + "Search Field", + choices=[ + ("name", "Name"), + ("affiliation", "Affiliation"), + ("website", "Website"), + ], + default="name", + validators=[InputRequired()], + ) + q = StringField("Parameter", validators=[InputRequired()]) + submit = SubmitField("Search") + + class UserEditForm(BaseForm): name = StringField("User Name", validators=[InputRequired()]) email = EmailField("Email", validators=[InputRequired()]) diff --git a/CTFd/teams.py b/CTFd/teams.py index ede3984b..962a6316 100644 --- a/CTFd/teams.py +++ b/CTFd/teams.py @@ -20,15 +20,34 @@ teams = Blueprint("teams", __name__) @check_account_visibility @require_team_mode def listing(): - page = abs(request.args.get("page", 1, type=int)) + q = request.args.get("q") + field = request.args.get("field", "name") + filters = [] + + if field not in ("name", "affiliation", "website"): + field = "name" + + if q: + filters.append(getattr(Teams, field).like("%{}%".format(q))) teams = ( Teams.query.filter_by(hidden=False, banned=False) + .filter(*filters) .order_by(Teams.id.asc()) - .paginate(page=page, per_page=50) + .paginate(per_page=50) ) - return render_template("teams/teams.html", teams=teams) + args = dict(request.args) + args.pop("page", 1) + + return render_template( + "teams/teams.html", + teams=teams, + prev_page=url_for(request.endpoint, page=teams.prev_num, **args), + next_page=url_for(request.endpoint, page=teams.next_num, **args), + q=q, + field=field, + ) @teams.route("/teams/join", methods=["GET", "POST"]) diff --git a/CTFd/themes/admin/templates/teams/teams.html b/CTFd/themes/admin/templates/teams/teams.html index 1cde4ca4..84e1dd3d 100644 --- a/CTFd/themes/admin/templates/teams/teams.html +++ b/CTFd/themes/admin/templates/teams/teams.html @@ -28,7 +28,7 @@ {% endif %} - {% with form = Forms.teams.TeamSearchForm() %} + {% with form = Forms.teams.TeamSearchForm(field=field, q=q) %}
{{ form.field(class="form-control custom-select w-100") }} diff --git a/CTFd/themes/core/templates/teams/teams.html b/CTFd/themes/core/templates/teams/teams.html index 2785aac4..4ea15b8e 100644 --- a/CTFd/themes/core/templates/teams/teams.html +++ b/CTFd/themes/core/templates/teams/teams.html @@ -10,6 +10,37 @@
+
+
+ {% if q and field %} +
+ Searching for teams with {{ field }} matching {{ q }} +
+
+ Page {{ teams.page }} of {{ teams.total }} results +
+ {% endif %} + + {% with form = Forms.teams.PublicTeamSearchForm(field=field, q=q) %} + +
+ {{ form.field(class="form-control custom-select w-100") }} +
+
+ {{ form.q(class="form-control w-100", placeholder="Search for matching teams") }} +
+
+ +
+ + {% endwith %} +
+
+ +
+
@@ -76,9 +107,7 @@
Page
{% if teams.page != 1 %} - - <<< - + <<< {% endif %} {% if teams.next_num %} - - >>> - + >>> {% endif %}
diff --git a/CTFd/themes/core/templates/users/users.html b/CTFd/themes/core/templates/users/users.html index 416e1c07..23383ff5 100644 --- a/CTFd/themes/core/templates/users/users.html +++ b/CTFd/themes/core/templates/users/users.html @@ -10,6 +10,37 @@
+
+
+ {% if q and field %} +
+ Searching for users with {{ field }} matching {{ q }} +
+
+ Page {{ users.page }} of {{ users.total }} results +
+ {% endif %} + + {% with form = Forms.users.PublicUserSearchForm(field=field, q=q) %} +
+
+ {{ form.field(class="form-control custom-select w-100") }} +
+
+ {{ form.q(class="form-control w-100", placeholder="Search for matching users") }} +
+
+ +
+ + {% endwith %} +
+
+ +
+
@@ -79,9 +110,7 @@
Page
{% if users.page != 1 %} - - <<< - + <<< {% endif %} {% if users.next_num %} - - >>> - + >>> {% endif %}
diff --git a/CTFd/users.py b/CTFd/users.py index d5d6f1fa..c5905ae8 100644 --- a/CTFd/users.py +++ b/CTFd/users.py @@ -1,4 +1,4 @@ -from flask import Blueprint, render_template, request +from flask import Blueprint, render_template, request, url_for from CTFd.models import Users from CTFd.utils import config @@ -16,15 +16,33 @@ users = Blueprint("users", __name__) @users.route("/users") @check_account_visibility def listing(): - page = abs(request.args.get("page", 1, type=int)) + q = request.args.get("q") + field = request.args.get("field", "name") + if field not in ("name", "affiliation", "website"): + field = "name" + + filters = [] + if q: + filters.append(getattr(Users, field).like("%{}%".format(q))) users = ( Users.query.filter_by(banned=False, hidden=False) + .filter(*filters) .order_by(Users.id.asc()) - .paginate(page=page, per_page=50) + .paginate(per_page=50) ) - return render_template("users/users.html", users=users) + args = dict(request.args) + args.pop("page", 1) + + return render_template( + "users/users.html", + users=users, + prev_page=url_for(request.endpoint, page=users.prev_num, **args), + next_page=url_for(request.endpoint, page=users.next_num, **args), + q=q, + field=field, + ) @users.route("/profile")