Merge pull request #144 from dod-ccpo/request-creator-name

Fix fields in requests listing
This commit is contained in:
richard-dds 2018-08-09 08:44:18 -04:00 committed by GitHub
commit 1338e36b1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 266 additions and 99 deletions

12
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "2b149e0d8c23814a2c701b53f5c75b36714a2ccd4e2a2769924ef6e2a3f09e97"
"sha256": "5fc8273838354406366b401529a6f512a73ac6a8ecea6699afa4ab7b4996bf13"
},
"pipfile-spec": 6,
"requires": {
@ -271,6 +271,7 @@
"sha256:1d936da41ee06216d89fdc7ead1ee9a5da2811a8787515a976b646e110c3f622",
"sha256:e4ef42e82b0b493c5849eed98b5ab49d6767caf982127e9a33167f1153b36cc5"
],
"markers": "python_version != '3.0.*' and python_version != '3.1.*' and python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.3.*'",
"version": "==2018.5"
},
"redis": {
@ -501,6 +502,7 @@
"sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8",
"sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497"
],
"markers": "python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.2.*'",
"version": "==4.3.4"
},
"itsdangerous": {
@ -618,6 +620,7 @@
"sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1",
"sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1"
],
"markers": "python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.2.*'",
"version": "==0.7.1"
},
"prompt-toolkit": {
@ -640,6 +643,7 @@
"sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7",
"sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e"
],
"markers": "python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.2.*'",
"version": "==1.5.4"
},
"pygments": {
@ -689,15 +693,11 @@
},
"pyyaml": {
"hashes": [
"sha256:1cbc199009e78f92d9edf554be4fe40fb7b0bef71ba688602a00e97a51909110",
"sha256:254bf6fda2b7c651837acb2c718e213df29d531eebf00edb54743d10bcb694eb",
"sha256:3108529b78577327d15eec243f0ff348a0640b0c3478d67ad7f5648f93bac3e2",
"sha256:3c17fb92c8ba2f525e4b5f7941d850e7a48c3a59b32d331e2502a3cdc6648e76",
"sha256:6f89b5c95e93945b597776163403d47af72d243f366bf4622ff08bdfd1c950b7",
"sha256:8d6d96001aa7f0a6a4a95e8143225b5d06e41b1131044913fecb8f85a125714b",
"sha256:be622cc81696e24d0836ba71f6272a2b5767669b0d79fdcf0295d51ac2e156c8",
"sha256:c8a88edd93ee29ede719080b2be6cb2333dfee1dccba213b422a9c8e97f2967b",
"sha256:f39411e380e2182ad33be039e8ee5770a5d9efe01a2bfb7ae58d9ba31c4a2a9d"
"sha256:c8a88edd93ee29ede719080b2be6cb2333dfee1dccba213b422a9c8e97f2967b"
],
"version": "==4.2b4"
},

View File

@ -0,0 +1,43 @@
"""rename request creator
Revision ID: 05d6272bdb43
Revises: 77b065750596
Create Date: 2018-08-07 20:21:22.559283
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '05d6272bdb43'
down_revision = '77b065750596'
branch_labels = None
depends_on = None
def upgrade():
db = op.get_bind()
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('requests', sa.Column('user_id', postgresql.UUID(as_uuid=True), nullable=True))
op.create_foreign_key('requests_user_id_fk', 'requests', 'users', ['user_id'], ['id'])
# ### end Alembic commands ###
db.execute("UPDATE requests SET user_id = creator")
op.alter_column('requests', 'user_id', nullable=False)
op.drop_column('requests', 'creator')
def downgrade():
db = op.get_bind()
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('requests', sa.Column('creator', postgresql.UUID(), autoincrement=False, nullable=True))
op.drop_constraint('requests_user_id_fk', 'requests', type_='foreignkey')
# ### end Alembic commands ###
db.execute("UPDATE requests SET creator = user_id")
op.drop_column('requests', 'user_id')

View File

@ -34,6 +34,4 @@ def upgrade():
def downgrade():
db = op.get_bind()
db.execute("DELETE FROM roles WHERE name = 'default'")
pass

View File

@ -169,15 +169,4 @@ def upgrade():
def downgrade():
db = op.get_bind()
db.execute("""
DELETE FROM roles
WHERE name IN (
'ccpo',
'owner',
'admin',
'developer',
'billing_auditor',
'security_auditor'
);
""")
pass

View File

@ -31,8 +31,8 @@ class Requests(object):
AUTO_APPROVE_THRESHOLD = 1000000
@classmethod
def create(cls, creator_id, body):
request = Request(creator=creator_id, body=body)
def create(cls, creator, body):
request = Request(creator=creator, body=body)
request = Requests.set_status(request, RequestStatus.STARTED)
db.session.add(request)
@ -41,11 +41,11 @@ class Requests(object):
return request
@classmethod
def exists(cls, request_id, creator_id):
def exists(cls, request_id, creator):
try:
return db.session.query(
exists().where(
and_(Request.id == request_id, Request.creator == creator_id)
and_(Request.id == request_id, Request.creator == creator)
)
).scalar()
except exc.DataError:
@ -61,10 +61,10 @@ class Requests(object):
return request
@classmethod
def get_many(cls, creator_id=None):
def get_many(cls, creator=None):
filters = []
if creator_id:
filters.append(Request.creator == creator_id)
if creator:
filters.append(Request.creator == creator)
requests = (
db.session.query(Request)

View File

@ -37,6 +37,7 @@ class Users(object):
db.session.add(user)
db.session.commit()
except IntegrityError:
db.session.rollback()
raise AlreadyExistsError("user")
return user

View File

@ -1,6 +1,6 @@
from sqlalchemy import Column, func
from sqlalchemy import Column, func, ForeignKey
from sqlalchemy.types import DateTime
from sqlalchemy.dialects.postgresql import JSONB, UUID
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import relationship
from atst.models import Base
@ -11,13 +11,19 @@ class Request(Base):
__tablename__ = "requests"
id = Id()
creator = Column(UUID(as_uuid=True))
time_created = Column(DateTime(timezone=True), server_default=func.now())
body = Column(JSONB)
status_events = relationship(
"RequestStatusEvent", backref="request", order_by="RequestStatusEvent.sequence"
)
user_id = Column(ForeignKey("users.id"), nullable=False)
creator = relationship("User")
@property
def status(self):
return self.status_events[-1].new_status
@property
def status_displayname(self):
return self.status_events[-1].displayname

View File

@ -9,12 +9,12 @@ from atst.models.types import Id
class RequestStatus(Enum):
STARTED = "started"
PENDING_FINANCIAL_VERIFICATION = "pending_financial_verification"
PENDING_CCPO_APPROVAL = "pending_ccpo_approval"
APPROVED = "approved"
EXPIRED = "expired"
DELETED = "deleted"
STARTED = "Started"
PENDING_FINANCIAL_VERIFICATION = "Pending Financial Verification"
PENDING_CCPO_APPROVAL = "Pending CCPO Approval"
APPROVED = "Approved"
EXPIRED = "Expired"
DELETED = "Deleted"
class RequestStatusEvent(Base):
@ -29,3 +29,7 @@ class RequestStatusEvent(Base):
sequence = Column(
BigInteger, Sequence("request_status_events_sequence_seq"), nullable=False
)
@property
def displayname(self):
return self.new_status.value

View File

@ -9,50 +9,56 @@ _DEV_USERS = {
"dod_id": "1234567890",
"first_name": "Sam",
"last_name": "Seeceepio",
"atat_role": "ccpo",
"atat_role_name": "ccpo",
"email": "sam@test.com"
},
"amanda": {
"dod_id": "2345678901",
"first_name": "Amanda",
"last_name": "Adamson",
"atat_role": "default",
"atat_role_name": "default",
"email": "amanda@test.com"
},
"brandon": {
"dod_id": "3456789012",
"first_name": "Brandon",
"last_name": "Buchannan",
"atat_role": "default",
"atat_role_name": "default",
"email": "brandon@test.com"
},
"christina": {
"dod_id": "4567890123",
"first_name": "Christina",
"last_name": "Collins",
"atat_role": "default",
"atat_role_name": "default",
"email": "christina@test.com"
},
"dominick": {
"dod_id": "5678901234",
"first_name": "Dominick",
"last_name": "Domingo",
"atat_role": "default",
"atat_role_name": "default",
"email": "dominick@test.com"
},
"erica": {
"dod_id": "6789012345",
"first_name": "Erica",
"last_name": "Eichner",
"atat_role": "default",
"atat_role_name": "default",
"email": "erica@test.com"
},
}
@bp.route("/login-dev")
def login_dev():
role = request.args.get("username", "amanda")
user_data = _DEV_USERS[role]
basic_data = {k:v for k,v in user_data.items() if k not in ["dod_id", "atat_role"]}
user = _set_user_permissions(user_data["dod_id"], user_data["atat_role"], basic_data)
user = Users.get_or_create_by_dod_id(
user_data["dod_id"],
atat_role_name=user_data["atat_role_name"],
first_name=user_data["first_name"],
last_name=user_data["last_name"],
email=user_data["email"]
)
session["user_id"] = user.id
return redirect(url_for("atst.home"))
def _set_user_permissions(dod_id, role, user_data):
return Users.get_or_create_by_dod_id(dod_id, atat_role_name=role, **user_data)

View File

@ -5,31 +5,29 @@ from . import requests_bp
from atst.domain.requests import Requests
def map_request(user, request):
def map_request(request):
time_created = pendulum.instance(request.time_created)
is_new = time_created.add(days=1) > pendulum.now()
app_count = request.body.get("details_of_use", {}).get("num_software_systems", 0)
return {
"order_id": request.id,
"is_new": is_new,
"status": request.status,
"app_count": 1,
"status": request.status_displayname,
"app_count": app_count,
"date": time_created.format("M/DD/YYYY"),
"full_name": user.full_name
"full_name": request.creator.full_name,
}
@requests_bp.route("/requests", methods=["GET"])
def requests_index():
requests = []
if (
"review_and_approve_jedi_workspace_request"
in g.current_user.atat_permissions
):
if "review_and_approve_jedi_workspace_request" in g.current_user.atat_permissions:
requests = Requests.get_many()
else:
requests = Requests.get_many(creator_id=g.current_user.id)
requests = Requests.get_many(creator=g.current_user)
mapped_requests = [map_request(g.current_user, r) for r in requests]
mapped_requests = [map_request(r) for r in requests]
return render_template("requests.html", requests=mapped_requests)

View File

@ -124,5 +124,5 @@ class JEDIRequestFlow(object):
if self.request_id:
Requests.update(self.request_id, request_data)
else:
request = Requests.create(self.current_user.id, request_data)
request = Requests.create(self.current_user, request_data)
self.request_id = request.id

View File

@ -111,7 +111,7 @@ def requests_submit(request_id=None):
def _check_can_view_request(request_id):
if Permissions.REVIEW_AND_APPROVE_JEDI_WORKSPACE_REQUEST in g.current_user.atat_permissions:
pass
elif Requests.exists(request_id, g.current_user.id):
elif Requests.exists(request_id, g.current_user):
pass
else:
raise UnauthorizedError(g.current_user, "view request {}".format(request_id))

37
script/seed.py Normal file
View File

@ -0,0 +1,37 @@
# Add root project dir to the python path
import os
import sys
parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
sys.path.append(parent_dir)
from atst.app import make_config, make_app
from atst.domain.users import Users
from atst.domain.requests import Requests
from atst.domain.exceptions import AlreadyExistsError
from tests.factories import RequestFactory
from atst.routes.dev import _DEV_USERS as DEV_USERS
def seed_db():
users = []
for dev_user in DEV_USERS.values():
try:
user = Users.create(**dev_user)
users.append(user)
except AlreadyExistsError:
pass
for user in users:
for dollar_value in [1, 200, 3000, 40000, 500000, 1000000]:
request = Requests.create(
user, RequestFactory.build_request_body(user, dollar_value)
)
Requests.submit(request)
if __name__ == "__main__":
config = make_config()
app = make_app(config)
with app.app_context():
seed_db()

View File

@ -5,7 +5,6 @@ import alembic.command
from atst.app import make_app, make_config
from atst.database import db as _db
from .mocks import MOCK_USER
import tests.factories as factories
@ -75,7 +74,6 @@ class DummyForm(dict):
class DummyField(object):
def __init__(self, data=None, errors=(), raw_data=None):
self.data = data
self.errors = list(errors)
@ -93,9 +91,11 @@ def dummy_field():
@pytest.fixture
def user_session(monkeypatch):
def set_user_session(user=MOCK_USER):
monkeypatch.setattr("atst.domain.auth.get_current_user", lambda *args: user)
def user_session(monkeypatch, session):
def set_user_session(user=None):
monkeypatch.setattr(
"atst.domain.auth.get_current_user",
lambda *args: user or factories.UserFactory.build(),
)
return set_user_session

View File

@ -25,7 +25,7 @@ def test_nonexistent_request_raises():
def test_new_request_has_started_status():
request = Requests.create(uuid4(), {})
request = Requests.create(UserFactory.build(), {})
assert request.status == RequestStatus.STARTED
@ -53,6 +53,6 @@ def test_dont_auto_approve_if_no_dollar_value_specified(new_request):
def test_exists(session):
user_allowed = UserFactory.create()
user_denied = UserFactory.create()
request = RequestFactory.create(creator=user_allowed.id)
assert Requests.exists(request.id, user_allowed.id)
assert not Requests.exists(request.id, user_denied.id)
request = RequestFactory.create(creator=user_allowed)
assert Requests.exists(request.id, user_allowed)
assert not Requests.exists(request.id, user_denied)

View File

@ -13,31 +13,6 @@ from atst.models.request_status_event import RequestStatusEvent
from atst.domain.roles import Roles
class RequestStatusFactory(factory.alchemy.SQLAlchemyModelFactory):
class Meta:
model = RequestStatusEvent
class RequestFactory(factory.alchemy.SQLAlchemyModelFactory):
class Meta:
model = Request
id = factory.Sequence(lambda x: uuid4())
status_events = factory.RelatedFactory(
RequestStatusFactory, "request", new_status=RequestStatus.STARTED
)
body = {}
class PENumberFactory(factory.alchemy.SQLAlchemyModelFactory):
class Meta:
model = PENumber
class TaskOrderFactory(factory.alchemy.SQLAlchemyModelFactory):
class Meta:
model = TaskOrder
class RoleFactory(factory.alchemy.SQLAlchemyModelFactory):
class Meta:
@ -64,3 +39,64 @@ class RequestStatusEventFactory(factory.alchemy.SQLAlchemyModelFactory):
model = RequestStatusEvent
id = factory.Sequence(lambda x: uuid4())
class RequestFactory(factory.alchemy.SQLAlchemyModelFactory):
class Meta:
model = Request
id = factory.Sequence(lambda x: uuid4())
status_events = factory.RelatedFactory(
RequestStatusEventFactory, "request", new_status=RequestStatus.STARTED
)
creator = factory.SubFactory(UserFactory)
body = factory.LazyAttribute(lambda r: RequestFactory.build_request_body(r.creator))
@classmethod
def build_request_body(cls, user, dollar_value=1000000):
return {
"primary_poc": {
"dodid_poc": user.dod_id,
"email_poc": user.email,
"fname_poc": user.first_name,
"lname_poc": user.last_name
},
"details_of_use": {
"jedi_usage": "adf",
"start_date": "2018-08-08",
"cloud_native": "yes",
"dollar_value": dollar_value,
"dod_component": "us_navy",
"data_transfers": "less_than_100gb",
"jedi_migration": "yes",
"num_software_systems": 1,
"number_user_sessions": 2,
"average_daily_traffic": 1,
"engineering_assessment": "yes",
"technical_support_team": "yes",
"estimated_monthly_spend": 100,
"expected_completion_date": "less_than_1_month",
"rationalization_software_systems": "yes",
"organization_providing_assistance": "in_house_staff"
},
"information_about_you": {
"citizenship": "United States",
"designation": "military",
"phone_number": "1234567890",
"email_request": user.email,
"fname_request": user.first_name,
"lname_request": user.last_name,
"service_branch": "ads",
"date_latest_training": "2018-08-06"
}
}
class PENumberFactory(factory.alchemy.SQLAlchemyModelFactory):
class Meta:
model = PENumber
class TaskOrderFactory(factory.alchemy.SQLAlchemyModelFactory):
class Meta:
model = TaskOrder

View File

@ -1,4 +1,4 @@
from tests.factories import RequestFactory
from tests.factories import RequestFactory, UserFactory
from atst.domain.requests import Requests, RequestStatus
@ -19,3 +19,52 @@ def test_pending_ccpo_approval_requires_ccpo():
request = Requests.set_status(request, RequestStatus.PENDING_CCPO_APPROVAL)
assert Requests.action_required_by(request) == "ccpo"
def test_request_has_creator():
user = UserFactory.create()
request = RequestFactory.create(creator=user)
assert request.creator == user
def test_request_status_started_displayname():
request = RequestFactory.create()
request = Requests.set_status(request, RequestStatus.STARTED)
assert request.status_displayname == "Started"
def test_request_status_pending_financial_displayname():
request = RequestFactory.create()
request = Requests.set_status(request, RequestStatus.PENDING_FINANCIAL_VERIFICATION)
assert request.status_displayname == "Pending Financial Verification"
def test_request_status_pending_ccpo_displayname():
request = RequestFactory.create()
request = Requests.set_status(request, RequestStatus.PENDING_CCPO_APPROVAL)
assert request.status_displayname == "Pending CCPO Approval"
def test_request_status_pending_approved_displayname():
request = RequestFactory.create()
request = Requests.set_status(request, RequestStatus.APPROVED)
assert request.status_displayname == "Approved"
def test_request_status_pending_expired_displayname():
request = RequestFactory.create()
request = Requests.set_status(request, RequestStatus.EXPIRED)
assert request.status_displayname == "Expired"
def test_request_status_pending_deleted_displayname():
request = RequestFactory.create()
request = Requests.set_status(request, RequestStatus.DELETED)
assert request.status_displayname == "Deleted"

View File

@ -33,7 +33,7 @@ def test_submit_valid_request_form(monkeypatch, client, user_session):
def test_owner_can_view_request(client, user_session):
user = UserFactory.create()
user_session(user)
request = RequestFactory.create(creator=user.id)
request = RequestFactory.create(creator=user)
response = client.get("/requests/new/1/{}".format(request.id), follow_redirects=True)