Merge pull request #415 from dod-ccpo/require-personal-info
Require personal info
This commit is contained in:
commit
57b24d8fc3
@ -7,6 +7,7 @@ UNPROTECTED_ROUTES = [
|
|||||||
"atst.root",
|
"atst.root",
|
||||||
"dev.login_dev",
|
"dev.login_dev",
|
||||||
"atst.login_redirect",
|
"atst.login_redirect",
|
||||||
|
"atst.logout",
|
||||||
"atst.unauthorized",
|
"atst.unauthorized",
|
||||||
"atst.helpdocs",
|
"atst.helpdocs",
|
||||||
"static",
|
"static",
|
||||||
@ -21,10 +22,26 @@ def apply_authentication(app):
|
|||||||
user = get_current_user()
|
user = get_current_user()
|
||||||
if user:
|
if user:
|
||||||
g.current_user = user
|
g.current_user = user
|
||||||
|
if should_redirect_to_user_profile(request, user):
|
||||||
|
return redirect(url_for("users.user", next=request.path))
|
||||||
elif not _unprotected_route(request):
|
elif not _unprotected_route(request):
|
||||||
return redirect(url_for("atst.root", next=request.path))
|
return redirect(url_for("atst.root", next=request.path))
|
||||||
|
|
||||||
|
|
||||||
|
def should_redirect_to_user_profile(request, user):
|
||||||
|
has_complete_profile = user.profile_complete
|
||||||
|
is_unprotected_route = _unprotected_route(request)
|
||||||
|
is_requesting_user_endpoint = request.endpoint in [
|
||||||
|
"users.user",
|
||||||
|
"users.update_user",
|
||||||
|
]
|
||||||
|
|
||||||
|
if has_complete_profile or is_unprotected_route or is_requesting_user_endpoint:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def get_current_user():
|
def get_current_user():
|
||||||
user_id = session.get("user_id")
|
user_id = session.get("user_id")
|
||||||
if user_id:
|
if user_id:
|
||||||
|
@ -2,14 +2,16 @@ import pendulum
|
|||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from wtforms.fields.html5 import DateField, EmailField, TelField
|
from wtforms.fields.html5 import DateField, EmailField, TelField
|
||||||
from wtforms.fields import RadioField, StringField
|
from wtforms.fields import RadioField, StringField
|
||||||
from wtforms.validators import Email, Required, Optional
|
from wtforms.validators import Email, DataRequired, Optional
|
||||||
|
|
||||||
from .fields import SelectField
|
from .fields import SelectField
|
||||||
from .forms import ValidatedForm
|
from .forms import ValidatedForm
|
||||||
from .data import SERVICE_BRANCHES
|
from .data import SERVICE_BRANCHES
|
||||||
|
from atst.models.user import User
|
||||||
|
|
||||||
from .validators import Name, DateRange, PhoneNumber
|
from .validators import Name, DateRange, PhoneNumber
|
||||||
|
|
||||||
|
|
||||||
USER_FIELDS = {
|
USER_FIELDS = {
|
||||||
"first_name": StringField("First Name", validators=[Name()]),
|
"first_name": StringField("First Name", validators=[Name()]),
|
||||||
"last_name": StringField("Last Name", validators=[Name()]),
|
"last_name": StringField("Last Name", validators=[Name()]),
|
||||||
@ -66,21 +68,25 @@ def inherit_field(unbound_field, required=True):
|
|||||||
kwargs["validators"] = []
|
kwargs["validators"] = []
|
||||||
|
|
||||||
if required:
|
if required:
|
||||||
kwargs["validators"].append(Required())
|
kwargs["validators"].append(DataRequired())
|
||||||
else:
|
else:
|
||||||
kwargs["validators"].append(Optional())
|
kwargs["validators"].append(Optional())
|
||||||
|
|
||||||
return unbound_field.field_class(*unbound_field.args, **kwargs)
|
return unbound_field.field_class(*unbound_field.args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def inherit_user_field(field_name):
|
||||||
|
required = field_name in User.REQUIRED_FIELDS
|
||||||
|
return inherit_field(USER_FIELDS[field_name], required=required)
|
||||||
|
|
||||||
|
|
||||||
class EditUserForm(ValidatedForm):
|
class EditUserForm(ValidatedForm):
|
||||||
first_name = inherit_field(USER_FIELDS["first_name"])
|
|
||||||
last_name = inherit_field(USER_FIELDS["last_name"])
|
first_name = inherit_user_field("first_name")
|
||||||
email = inherit_field(USER_FIELDS["email"])
|
last_name = inherit_user_field("last_name")
|
||||||
phone_number = inherit_field(USER_FIELDS["phone_number"], required=False)
|
email = inherit_user_field("email")
|
||||||
service_branch = inherit_field(USER_FIELDS["service_branch"], required=False)
|
phone_number = inherit_user_field("phone_number")
|
||||||
citizenship = inherit_field(USER_FIELDS["citizenship"], required=False)
|
service_branch = inherit_user_field("service_branch")
|
||||||
designation = inherit_field(USER_FIELDS["designation"], required=False)
|
citizenship = inherit_user_field("citizenship")
|
||||||
date_latest_training = inherit_field(
|
designation = inherit_user_field("designation")
|
||||||
USER_FIELDS["date_latest_training"], required=False
|
date_latest_training = inherit_user_field("date_latest_training")
|
||||||
)
|
|
||||||
|
@ -164,19 +164,12 @@ class DetailsOfUseForm(ValidatedForm):
|
|||||||
|
|
||||||
class InformationAboutYouForm(ValidatedForm):
|
class InformationAboutYouForm(ValidatedForm):
|
||||||
fname_request = inherit_field(USER_FIELDS["first_name"])
|
fname_request = inherit_field(USER_FIELDS["first_name"])
|
||||||
|
|
||||||
lname_request = inherit_field(USER_FIELDS["last_name"])
|
lname_request = inherit_field(USER_FIELDS["last_name"])
|
||||||
|
email_request = inherit_field(USER_FIELDS["email"])
|
||||||
email_request = EmailField("E-mail Address", validators=[InputRequired(), Email()])
|
|
||||||
|
|
||||||
phone_number = inherit_field(USER_FIELDS["phone_number"])
|
phone_number = inherit_field(USER_FIELDS["phone_number"])
|
||||||
|
|
||||||
service_branch = inherit_field(USER_FIELDS["service_branch"])
|
service_branch = inherit_field(USER_FIELDS["service_branch"])
|
||||||
|
|
||||||
citizenship = inherit_field(USER_FIELDS["citizenship"])
|
citizenship = inherit_field(USER_FIELDS["citizenship"])
|
||||||
|
|
||||||
designation = inherit_field(USER_FIELDS["designation"])
|
designation = inherit_field(USER_FIELDS["designation"])
|
||||||
|
|
||||||
date_latest_training = inherit_field(USER_FIELDS["date_latest_training"])
|
date_latest_training = inherit_field(USER_FIELDS["date_latest_training"])
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,6 +28,27 @@ class User(Base, mixins.TimestampsMixin, mixins.AuditableMixin):
|
|||||||
|
|
||||||
provisional = Column(Boolean)
|
provisional = Column(Boolean)
|
||||||
|
|
||||||
|
REQUIRED_FIELDS = [
|
||||||
|
"email",
|
||||||
|
"dod_id",
|
||||||
|
"first_name",
|
||||||
|
"last_name",
|
||||||
|
"phone_number",
|
||||||
|
"service_branch",
|
||||||
|
"citizenship",
|
||||||
|
"designation",
|
||||||
|
"date_latest_training",
|
||||||
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def profile_complete(self):
|
||||||
|
return all(
|
||||||
|
[
|
||||||
|
getattr(self, field_name) is not None
|
||||||
|
for field_name in self.REQUIRED_FIELDS
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def atat_permissions(self):
|
def atat_permissions(self):
|
||||||
return self.atat_role.permissions
|
return self.atat_role.permissions
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from flask import Blueprint, render_template, g, request as http_request
|
from flask import Blueprint, render_template, g, request as http_request, redirect
|
||||||
from atst.forms.edit_user import EditUserForm
|
from atst.forms.edit_user import EditUserForm
|
||||||
from atst.domain.users import Users
|
from atst.domain.users import Users
|
||||||
|
|
||||||
@ -10,16 +10,21 @@ bp = Blueprint("users", __name__)
|
|||||||
def user():
|
def user():
|
||||||
user = g.current_user
|
user = g.current_user
|
||||||
form = EditUserForm(data=user.to_dictionary())
|
form = EditUserForm(data=user.to_dictionary())
|
||||||
return render_template("user/edit.html", form=form, user=user)
|
return render_template(
|
||||||
|
"user/edit.html", next=http_request.args.get("next"), form=form, user=user
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/user", methods=["POST"])
|
@bp.route("/user", methods=["POST"])
|
||||||
def update_user():
|
def update_user():
|
||||||
user = g.current_user
|
user = g.current_user
|
||||||
form = EditUserForm(http_request.form)
|
form = EditUserForm(http_request.form)
|
||||||
rerender_args = {"form": form, "user": user}
|
next_url = http_request.args.get("next")
|
||||||
|
rerender_args = {"form": form, "user": user, "next": next_url}
|
||||||
if form.validate():
|
if form.validate():
|
||||||
Users.update(user, form.data)
|
Users.update(user, form.data)
|
||||||
rerender_args["updated"] = True
|
rerender_args["updated"] = True
|
||||||
|
if next_url:
|
||||||
|
return redirect(next_url)
|
||||||
|
|
||||||
return render_template("user/edit.html", **rerender_args)
|
return render_template("user/edit.html", **rerender_args)
|
||||||
|
@ -4,6 +4,13 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class='col'>
|
<div class='col'>
|
||||||
|
|
||||||
|
{% if next is not none %}
|
||||||
|
{{ Alert('You must complete your profile',
|
||||||
|
message='<p>Before continuing, you must complete your profile</p>',
|
||||||
|
level='info'
|
||||||
|
) }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if form.errors %}
|
{% if form.errors %}
|
||||||
{{ Alert('There were some errors',
|
{{ Alert('There were some errors',
|
||||||
message="<p>Please see below.</p>",
|
message="<p>Please see below.</p>",
|
||||||
@ -25,7 +32,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% set form_action = url_for('users.update_user') %}
|
{% set form_action = url_for('users.update_user', next=next) %}
|
||||||
{% include "fragments/edit_user_form.html" %}
|
{% include "fragments/edit_user_form.html" %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
27
tests/forms/test_edit_user.py
Normal file
27
tests/forms/test_edit_user.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import pytest
|
||||||
|
from werkzeug.datastructures import ImmutableMultiDict
|
||||||
|
|
||||||
|
from atst.forms.edit_user import EditUserForm
|
||||||
|
|
||||||
|
from tests.factories import UserFactory
|
||||||
|
|
||||||
|
|
||||||
|
def test_edit_user_form_requires_all_fields():
|
||||||
|
user = UserFactory.create()
|
||||||
|
user_data = user.to_dictionary()
|
||||||
|
del user_data["date_latest_training"]
|
||||||
|
form_data = ImmutableMultiDict(user_data)
|
||||||
|
form = EditUserForm(form_data)
|
||||||
|
assert not form.validate()
|
||||||
|
assert form.errors == {"date_latest_training": ["This field is required."]}
|
||||||
|
|
||||||
|
|
||||||
|
def test_edit_user_form_valid_with_all_fields():
|
||||||
|
user = UserFactory.create()
|
||||||
|
user_data = user.to_dictionary()
|
||||||
|
user_data["date_latest_training"] = user_data["date_latest_training"].strftime(
|
||||||
|
"%m/%d/%Y"
|
||||||
|
)
|
||||||
|
form_data = ImmutableMultiDict(user_data)
|
||||||
|
form = EditUserForm(form_data)
|
||||||
|
assert form.validate()
|
17
tests/models/test_user.py
Normal file
17
tests/models/test_user.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from atst.models.user import User
|
||||||
|
|
||||||
|
from tests.factories import UserFactory
|
||||||
|
|
||||||
|
|
||||||
|
def test_profile_complete_with_all_info():
|
||||||
|
user = UserFactory.create()
|
||||||
|
assert user.profile_complete
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("missing_field", User.REQUIRED_FIELDS)
|
||||||
|
def test_profile_complete_with_missing_info(missing_field):
|
||||||
|
user = UserFactory.create()
|
||||||
|
setattr(user, missing_field, None)
|
||||||
|
assert not user.profile_complete
|
45
tests/routes/test_auth.py
Normal file
45
tests/routes/test_auth.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
from flask import url_for
|
||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
|
from tests.factories import UserFactory
|
||||||
|
|
||||||
|
|
||||||
|
def test_request_page_with_complete_profile(client, user_session):
|
||||||
|
user = UserFactory.create()
|
||||||
|
user_session(user)
|
||||||
|
response = client.get("/requests", follow_redirects=False)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_redirect_when_profile_missing_fields(client, user_session):
|
||||||
|
user = UserFactory.create(date_latest_training=None)
|
||||||
|
user_session(user)
|
||||||
|
requested_url = "/requests"
|
||||||
|
response = client.get(requested_url, follow_redirects=False)
|
||||||
|
assert response.status_code == 302
|
||||||
|
assert "/user?next={}".format(quote(requested_url, safe="")) in response.location
|
||||||
|
|
||||||
|
|
||||||
|
def test_unprotected_route_with_incomplete_profile(client, user_session):
|
||||||
|
user = UserFactory.create(date_latest_training=None)
|
||||||
|
user_session(user)
|
||||||
|
response = client.get("/about", follow_redirects=False)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_completing_user_profile(client, user_session):
|
||||||
|
user = UserFactory.create(phone_number=None)
|
||||||
|
user_session(user)
|
||||||
|
response = client.get("/requests", follow_redirects=True)
|
||||||
|
assert b"You must complete your profile" in response.data
|
||||||
|
|
||||||
|
updated_data = {**user.to_dictionary(), "phone_number": "5558675309"}
|
||||||
|
updated_data["date_latest_training"] = updated_data[
|
||||||
|
"date_latest_training"
|
||||||
|
].strftime("%m/%d/%Y")
|
||||||
|
response = client.post(url_for("users.update_user"), data=updated_data)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
response = client.get("/requests", follow_redirects=False)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert b"You must complete your profile" not in response.data
|
@ -23,3 +23,21 @@ def test_user_can_update_profile(user_session, client):
|
|||||||
updated_user = Users.get_by_dod_id(user.dod_id)
|
updated_user = Users.get_by_dod_id(user.dod_id)
|
||||||
assert updated_user.first_name == "chad"
|
assert updated_user.first_name == "chad"
|
||||||
assert updated_user.last_name == "vader"
|
assert updated_user.last_name == "vader"
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_is_redirected_when_updating_profile(user_session, client):
|
||||||
|
user = UserFactory.create()
|
||||||
|
user_session(user)
|
||||||
|
next_url = "/requests"
|
||||||
|
|
||||||
|
user_data = user.to_dictionary()
|
||||||
|
user_data["date_latest_training"] = user_data["date_latest_training"].strftime(
|
||||||
|
"%m/%d/%Y"
|
||||||
|
)
|
||||||
|
response = client.post(
|
||||||
|
url_for("users.update_user", next=next_url),
|
||||||
|
data=user_data,
|
||||||
|
follow_redirects=False,
|
||||||
|
)
|
||||||
|
assert response.status_code == 302
|
||||||
|
assert response.location.endswith(next_url)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user