mirror of
https://github.com/aljazceru/CTFd.git
synced 2025-12-17 14:04:20 +01:00
Add fields to team forms
This commit is contained in:
@@ -4,24 +4,99 @@ from wtforms.validators import InputRequired
|
|||||||
|
|
||||||
from CTFd.forms import BaseForm
|
from CTFd.forms import BaseForm
|
||||||
from CTFd.forms.fields import SubmitField
|
from CTFd.forms.fields import SubmitField
|
||||||
|
from CTFd.models import TeamFieldEntries, TeamFields
|
||||||
from CTFd.utils.countries import SELECT_COUNTRIES_LIST
|
from CTFd.utils.countries import SELECT_COUNTRIES_LIST
|
||||||
|
|
||||||
|
|
||||||
|
def build_custom_team_fields(
|
||||||
|
form_cls,
|
||||||
|
include_entries=False,
|
||||||
|
fields_kwargs=None,
|
||||||
|
field_entries_kwargs=None,
|
||||||
|
blacklisted_items=("affiliation", "website"),
|
||||||
|
):
|
||||||
|
if fields_kwargs is None:
|
||||||
|
fields_kwargs = {}
|
||||||
|
if field_entries_kwargs is None:
|
||||||
|
field_entries_kwargs = {}
|
||||||
|
|
||||||
|
fields = []
|
||||||
|
new_fields = TeamFields.query.filter_by(**fields_kwargs).all()
|
||||||
|
user_fields = {}
|
||||||
|
|
||||||
|
# Only include preexisting values if asked
|
||||||
|
if include_entries is True:
|
||||||
|
for f in TeamFieldEntries.query.filter_by(**field_entries_kwargs).all():
|
||||||
|
user_fields[f.field_id] = f.value
|
||||||
|
|
||||||
|
for field in new_fields:
|
||||||
|
if field.name.lower() in blacklisted_items:
|
||||||
|
continue
|
||||||
|
|
||||||
|
form_field = getattr(form_cls, f"fields[{field.id}]")
|
||||||
|
|
||||||
|
# Add the field_type to the field so we know how to render it
|
||||||
|
form_field.field_type = field.field_type
|
||||||
|
|
||||||
|
# Only include preexisting values if asked
|
||||||
|
if include_entries is True:
|
||||||
|
initial = user_fields.get(field.id, "")
|
||||||
|
form_field.data = initial
|
||||||
|
if form_field.render_kw:
|
||||||
|
form_field.render_kw["data-initial"] = initial
|
||||||
|
else:
|
||||||
|
form_field.render_kw = {"data-initial": initial}
|
||||||
|
|
||||||
|
fields.append(form_field)
|
||||||
|
return fields
|
||||||
|
|
||||||
|
|
||||||
|
def attach_custom_team_fields(form_cls, **kwargs):
|
||||||
|
new_fields = TeamFields.query.filter_by(**kwargs).all()
|
||||||
|
for field in new_fields:
|
||||||
|
validators = []
|
||||||
|
if field.required:
|
||||||
|
validators.append(InputRequired())
|
||||||
|
|
||||||
|
if field.field_type == "text":
|
||||||
|
input_field = StringField(
|
||||||
|
field.name, description=field.description, validators=validators
|
||||||
|
)
|
||||||
|
elif field.field_type == "boolean":
|
||||||
|
input_field = BooleanField(
|
||||||
|
field.name, description=field.description, validators=validators
|
||||||
|
)
|
||||||
|
|
||||||
|
setattr(form_cls, f"fields[{field.id}]", input_field)
|
||||||
|
|
||||||
|
|
||||||
class TeamJoinForm(BaseForm):
|
class TeamJoinForm(BaseForm):
|
||||||
name = StringField("Team Name", validators=[InputRequired()])
|
name = StringField("Team Name", validators=[InputRequired()])
|
||||||
password = PasswordField("Team Password", validators=[InputRequired()])
|
password = PasswordField("Team Password", validators=[InputRequired()])
|
||||||
submit = SubmitField("Join")
|
submit = SubmitField("Join")
|
||||||
|
|
||||||
|
|
||||||
class TeamRegisterForm(BaseForm):
|
def TeamRegisterForm(*args, **kwargs):
|
||||||
|
class _TeamRegisterForm(BaseForm):
|
||||||
name = StringField("Team Name", validators=[InputRequired()])
|
name = StringField("Team Name", validators=[InputRequired()])
|
||||||
password = PasswordField("Team Password", validators=[InputRequired()])
|
password = PasswordField("Team Password", validators=[InputRequired()])
|
||||||
submit = SubmitField("Create")
|
submit = SubmitField("Create")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def extra(self):
|
||||||
|
return build_custom_team_fields(
|
||||||
|
self, include_entries=False, blacklisted_items=()
|
||||||
|
)
|
||||||
|
|
||||||
class TeamSettingsForm(BaseForm):
|
attach_custom_team_fields(_TeamRegisterForm)
|
||||||
|
return _TeamRegisterForm(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def TeamSettingsForm(*args, **kwargs):
|
||||||
|
class _TeamSettingsForm(BaseForm):
|
||||||
name = StringField(
|
name = StringField(
|
||||||
"Team Name", description="Your team's public name shown to other competitors"
|
"Team Name",
|
||||||
|
description="Your team's public name shown to other competitors",
|
||||||
)
|
)
|
||||||
password = PasswordField(
|
password = PasswordField(
|
||||||
"New Team Password", description="Set a new team join password"
|
"New Team Password", description="Set a new team join password"
|
||||||
@@ -35,7 +110,8 @@ class TeamSettingsForm(BaseForm):
|
|||||||
description="Your team's affiliation publicly shown to other competitors",
|
description="Your team's affiliation publicly shown to other competitors",
|
||||||
)
|
)
|
||||||
website = URLField(
|
website = URLField(
|
||||||
"Website", description="Your team's website publicly shown to other competitors"
|
"Website",
|
||||||
|
description="Your team's website publicly shown to other competitors",
|
||||||
)
|
)
|
||||||
country = SelectField(
|
country = SelectField(
|
||||||
"Country",
|
"Country",
|
||||||
@@ -44,6 +120,27 @@ class TeamSettingsForm(BaseForm):
|
|||||||
)
|
)
|
||||||
submit = SubmitField("Submit")
|
submit = SubmitField("Submit")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def extra(self):
|
||||||
|
return build_custom_team_fields(
|
||||||
|
self,
|
||||||
|
include_entries=True,
|
||||||
|
fields_kwargs={"editable": True},
|
||||||
|
field_entries_kwargs={"team_id": self.obj.id},
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Custom init to persist the obj parameter to the rest of the form
|
||||||
|
"""
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
obj = kwargs.get("obj")
|
||||||
|
if obj:
|
||||||
|
self.obj = obj
|
||||||
|
|
||||||
|
attach_custom_team_fields(_TeamSettingsForm)
|
||||||
|
return _TeamSettingsForm(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class TeamCaptainForm(BaseForm):
|
class TeamCaptainForm(BaseForm):
|
||||||
# Choices are populated dynamically at form creation time
|
# Choices are populated dynamically at form creation time
|
||||||
@@ -82,7 +179,7 @@ class PublicTeamSearchForm(BaseForm):
|
|||||||
submit = SubmitField("Search")
|
submit = SubmitField("Search")
|
||||||
|
|
||||||
|
|
||||||
class TeamCreateForm(BaseForm):
|
class TeamBaseForm(BaseForm):
|
||||||
name = StringField("Team Name", validators=[InputRequired()])
|
name = StringField("Team Name", validators=[InputRequired()])
|
||||||
email = EmailField("Email")
|
email = EmailField("Email")
|
||||||
password = PasswordField("Password")
|
password = PasswordField("Password")
|
||||||
@@ -94,5 +191,41 @@ class TeamCreateForm(BaseForm):
|
|||||||
submit = SubmitField("Submit")
|
submit = SubmitField("Submit")
|
||||||
|
|
||||||
|
|
||||||
class TeamEditForm(TeamCreateForm):
|
def TeamCreateForm(*args, **kwargs):
|
||||||
|
class _TeamCreateForm(TeamBaseForm):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def extra(self):
|
||||||
|
return build_custom_team_fields(self, include_entries=False)
|
||||||
|
|
||||||
|
attach_custom_team_fields(_TeamCreateForm)
|
||||||
|
|
||||||
|
return _TeamCreateForm(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def TeamEditForm(*args, **kwargs):
|
||||||
|
class _TeamEditForm(TeamBaseForm):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def extra(self):
|
||||||
|
return build_custom_team_fields(
|
||||||
|
self,
|
||||||
|
include_entries=True,
|
||||||
|
fields_kwargs=None,
|
||||||
|
field_entries_kwargs={"team_id": self.obj.id},
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Custom init to persist the obj parameter to the rest of the form
|
||||||
|
"""
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
obj = kwargs.get("obj")
|
||||||
|
if obj:
|
||||||
|
self.obj = obj
|
||||||
|
|
||||||
|
attach_custom_team_fields(_TeamEditForm)
|
||||||
|
|
||||||
|
return _TeamEditForm(*args, **kwargs)
|
||||||
|
|||||||
@@ -76,9 +76,7 @@ def attach_custom_user_fields(form_cls, **kwargs):
|
|||||||
field.name, description=field.description, validators=validators
|
field.name, description=field.description, validators=validators
|
||||||
)
|
)
|
||||||
|
|
||||||
setattr(
|
setattr(form_cls, f"fields[{field.id}]", input_field)
|
||||||
form_cls, f"fields[{field.id}]", input_field,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class UserSearchForm(BaseForm):
|
class UserSearchForm(BaseForm):
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from flask import Blueprint, redirect, render_template, request, url_for
|
from flask import Blueprint, redirect, render_template, request, url_for
|
||||||
|
|
||||||
from CTFd.cache import clear_team_session, clear_user_session
|
from CTFd.cache import clear_team_session, clear_user_session
|
||||||
from CTFd.models import Teams, db
|
from CTFd.models import TeamFieldEntries, TeamFields, Teams, db
|
||||||
from CTFd.utils import config, get_config
|
from CTFd.utils import config, get_config
|
||||||
from CTFd.utils.crypto import verify_password
|
from CTFd.utils.crypto import verify_password
|
||||||
from CTFd.utils.decorators import authed_only, ratelimit
|
from CTFd.utils.decorators import authed_only, ratelimit
|
||||||
@@ -133,14 +133,61 @@ def new():
|
|||||||
if not teamname:
|
if not teamname:
|
||||||
errors.append("That team name is invalid")
|
errors.append("That team name is invalid")
|
||||||
|
|
||||||
|
# Process additional user fields
|
||||||
|
fields = {}
|
||||||
|
for field in TeamFields.query.all():
|
||||||
|
fields[field.id] = field
|
||||||
|
|
||||||
|
entries = {}
|
||||||
|
for field_id, field in fields.items():
|
||||||
|
value = request.form.get(f"fields[{field_id}]", "").strip()
|
||||||
|
if field.required is True and (value is None or value == ""):
|
||||||
|
errors.append("Please provide all required fields")
|
||||||
|
break
|
||||||
|
|
||||||
|
# Handle special casing of existing profile fields
|
||||||
|
if field.name.lower() == "affiliation":
|
||||||
|
affiliation = value
|
||||||
|
break
|
||||||
|
elif field.name.lower() == "website":
|
||||||
|
website = value
|
||||||
|
break
|
||||||
|
|
||||||
|
if field.field_type == "boolean":
|
||||||
|
entries[field_id] = bool(value)
|
||||||
|
else:
|
||||||
|
entries[field_id] = value
|
||||||
|
|
||||||
|
# if website:
|
||||||
|
# valid_website = validators.validate_url(website)
|
||||||
|
# else:
|
||||||
|
# valid_website = True
|
||||||
|
|
||||||
|
# if affiliation:
|
||||||
|
# valid_affiliation = len(affiliation) < 128
|
||||||
|
# else:
|
||||||
|
# valid_affiliation = True
|
||||||
|
|
||||||
if errors:
|
if errors:
|
||||||
return render_template("teams/new_team.html", errors=errors)
|
return render_template("teams/new_team.html", errors=errors)
|
||||||
|
|
||||||
team = Teams(name=teamname, password=passphrase, captain_id=user.id)
|
team = Teams(name=teamname, password=passphrase, captain_id=user.id)
|
||||||
|
|
||||||
|
# if website:
|
||||||
|
# user.website = website
|
||||||
|
# if affiliation:
|
||||||
|
# user.affiliation = affiliation
|
||||||
|
# if country:
|
||||||
|
# user.country = country
|
||||||
|
|
||||||
db.session.add(team)
|
db.session.add(team)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
for field_id, value in entries.items():
|
||||||
|
entry = TeamFieldEntries(field_id=field_id, value=value, team_id=team.id)
|
||||||
|
db.session.add(entry)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
user.team_id = team.id
|
user.team_id = team.id
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +1,33 @@
|
|||||||
{% with form = Forms.teams.TeamEditForm() %}
|
{% with form = Forms.teams.TeamCreateForm() %}
|
||||||
|
{% from "admin/macros/forms.html" import render_extra_fields %}
|
||||||
<form id="team-info-create-form" method="POST">
|
<form id="team-info-create-form" method="POST">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<b>{{ form.name.label }}</b>
|
{{ form.name.label }}
|
||||||
{{ form.name(class="form-control") }}
|
{{ form.name(class="form-control") }}
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<b>{{ form.email.label }}</b>
|
{{ form.email.label }}
|
||||||
{{ form.email(class="form-control") }}
|
{{ form.email(class="form-control") }}
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<b>{{ form.password.label }}</b>
|
{{ form.password.label }}
|
||||||
{{ form.password(class="form-control") }}
|
{{ form.password(class="form-control") }}
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<b>{{ form.website.label }}</b>
|
{{ form.website.label }}
|
||||||
{{ form.website(class="form-control") }}
|
{{ form.website(class="form-control") }}
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<b>{{ form.affiliation.label }}</b>
|
{{ form.affiliation.label }}
|
||||||
{{ form.affiliation(class="form-control") }}
|
{{ form.affiliation(class="form-control") }}
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<b>{{ form.country.label }}</b>
|
{{ form.country.label }}
|
||||||
{{ form.country(class="form-control custom-select") }}
|
{{ form.country(class="form-control custom-select") }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{ render_extra_fields(form.extra) }}
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="form-check form-check-inline">
|
<div class="form-check form-check-inline">
|
||||||
{{ form.hidden(class="form-check-input") }}
|
{{ form.hidden(class="form-check-input") }}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{% with form = Forms.teams.TeamCreateForm(obj=team) %}
|
{% with form = Forms.teams.TeamEditForm(obj=team) %}
|
||||||
|
{% from "admin/macros/forms.html" import render_extra_fields %}
|
||||||
<form id="team-info-edit-form" method="POST">
|
<form id="team-info-edit-form" method="POST">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
{{ form.name.label }}
|
{{ form.name.label }}
|
||||||
@@ -24,6 +25,9 @@
|
|||||||
{{ form.country.label }}
|
{{ form.country.label }}
|
||||||
{{ form.country(class="form-control custom-select") }}
|
{{ form.country(class="form-control custom-select") }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{ render_extra_fields(form.extra) }}
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="form-check form-check-inline">
|
<div class="form-check form-check-inline">
|
||||||
{{ form.hidden(class="form-check-input") }}
|
{{ form.hidden(class="form-check-input") }}
|
||||||
|
|||||||
@@ -15,15 +15,19 @@
|
|||||||
{% include "components/errors.html" %}
|
{% include "components/errors.html" %}
|
||||||
|
|
||||||
{% with form = Forms.teams.TeamRegisterForm() %}
|
{% with form = Forms.teams.TeamRegisterForm() %}
|
||||||
|
{% from "macros/forms.html" import render_extra_fields %}
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
{{ form.name.label }}
|
<b>{{ form.name.label }}</b>
|
||||||
{{ form.name(class="form-control") }}
|
{{ form.name(class="form-control") }}
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
{{ form.password.label }}
|
<b>{{ form.password.label }}</b>
|
||||||
{{ form.password(class="form-control") }}
|
{{ form.password(class="form-control") }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{ render_extra_fields(form.extra) }}
|
||||||
|
|
||||||
<div class="row pt-3">
|
<div class="row pt-3">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<p>After creating your team, share the team name and password with your teammates so they can join your team.</p>
|
<p>After creating your team, share the team name and password with your teammates so they can join your team.</p>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body clearfix">
|
<div class="modal-body clearfix">
|
||||||
{% with form = Forms.teams.TeamSettingsForm(obj=team) %}
|
{% with form = Forms.teams.TeamSettingsForm(obj=team) %}
|
||||||
|
{% from "macros/forms.html" import render_extra_fields %}
|
||||||
<form id="team-info-form" method="POST">
|
<form id="team-info-form" method="POST">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<b>{{ form.name.label }}</b>
|
<b>{{ form.name.label }}</b>
|
||||||
@@ -58,6 +59,11 @@
|
|||||||
{{ form.country.description }}
|
{{ form.country.description }}
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
{{ render_extra_fields(form.extra) }}
|
||||||
|
|
||||||
<div id="results">
|
<div id="results">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ def test_boolean_checkbox_field():
|
|||||||
assert "checkbox" in resp
|
assert "checkbox" in resp
|
||||||
|
|
||||||
r = client.patch(
|
r = client.patch(
|
||||||
"/api/v1/users/me", json={"fields": [{"field_id": 1, "value": False}]},
|
"/api/v1/users/me", json={"fields": [{"field_id": 1, "value": False}]}
|
||||||
)
|
)
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
assert UserFieldEntries.query.count() == 1
|
assert UserFieldEntries.query.count() == 1
|
||||||
|
|||||||
Reference in New Issue
Block a user