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": { "_meta": {
"hash": { "hash": {
"sha256": "2b149e0d8c23814a2c701b53f5c75b36714a2ccd4e2a2769924ef6e2a3f09e97" "sha256": "5fc8273838354406366b401529a6f512a73ac6a8ecea6699afa4ab7b4996bf13"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -271,6 +271,7 @@
"sha256:1d936da41ee06216d89fdc7ead1ee9a5da2811a8787515a976b646e110c3f622", "sha256:1d936da41ee06216d89fdc7ead1ee9a5da2811a8787515a976b646e110c3f622",
"sha256:e4ef42e82b0b493c5849eed98b5ab49d6767caf982127e9a33167f1153b36cc5" "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" "version": "==2018.5"
}, },
"redis": { "redis": {
@ -501,6 +502,7 @@
"sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8", "sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8",
"sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497" "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" "version": "==4.3.4"
}, },
"itsdangerous": { "itsdangerous": {
@ -618,6 +620,7 @@
"sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1", "sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1",
"sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1" "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" "version": "==0.7.1"
}, },
"prompt-toolkit": { "prompt-toolkit": {
@ -640,6 +643,7 @@
"sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7", "sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7",
"sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e" "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" "version": "==1.5.4"
}, },
"pygments": { "pygments": {
@ -689,15 +693,11 @@
}, },
"pyyaml": { "pyyaml": {
"hashes": [ "hashes": [
"sha256:1cbc199009e78f92d9edf554be4fe40fb7b0bef71ba688602a00e97a51909110",
"sha256:254bf6fda2b7c651837acb2c718e213df29d531eebf00edb54743d10bcb694eb", "sha256:254bf6fda2b7c651837acb2c718e213df29d531eebf00edb54743d10bcb694eb",
"sha256:3108529b78577327d15eec243f0ff348a0640b0c3478d67ad7f5648f93bac3e2", "sha256:3108529b78577327d15eec243f0ff348a0640b0c3478d67ad7f5648f93bac3e2",
"sha256:3c17fb92c8ba2f525e4b5f7941d850e7a48c3a59b32d331e2502a3cdc6648e76", "sha256:3c17fb92c8ba2f525e4b5f7941d850e7a48c3a59b32d331e2502a3cdc6648e76",
"sha256:6f89b5c95e93945b597776163403d47af72d243f366bf4622ff08bdfd1c950b7",
"sha256:8d6d96001aa7f0a6a4a95e8143225b5d06e41b1131044913fecb8f85a125714b", "sha256:8d6d96001aa7f0a6a4a95e8143225b5d06e41b1131044913fecb8f85a125714b",
"sha256:be622cc81696e24d0836ba71f6272a2b5767669b0d79fdcf0295d51ac2e156c8", "sha256:c8a88edd93ee29ede719080b2be6cb2333dfee1dccba213b422a9c8e97f2967b"
"sha256:c8a88edd93ee29ede719080b2be6cb2333dfee1dccba213b422a9c8e97f2967b",
"sha256:f39411e380e2182ad33be039e8ee5770a5d9efe01a2bfb7ae58d9ba31c4a2a9d"
], ],
"version": "==4.2b4" "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(): def downgrade():
db = op.get_bind() pass
db.execute("DELETE FROM roles WHERE name = 'default'")

View File

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

View File

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

View File

@ -37,6 +37,7 @@ class Users(object):
db.session.add(user) db.session.add(user)
db.session.commit() db.session.commit()
except IntegrityError: except IntegrityError:
db.session.rollback()
raise AlreadyExistsError("user") raise AlreadyExistsError("user")
return 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.types import DateTime
from sqlalchemy.dialects.postgresql import JSONB, UUID from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from atst.models import Base from atst.models import Base
@ -11,13 +11,19 @@ class Request(Base):
__tablename__ = "requests" __tablename__ = "requests"
id = Id() id = Id()
creator = Column(UUID(as_uuid=True))
time_created = Column(DateTime(timezone=True), server_default=func.now()) time_created = Column(DateTime(timezone=True), server_default=func.now())
body = Column(JSONB) body = Column(JSONB)
status_events = relationship( status_events = relationship(
"RequestStatusEvent", backref="request", order_by="RequestStatusEvent.sequence" "RequestStatusEvent", backref="request", order_by="RequestStatusEvent.sequence"
) )
user_id = Column(ForeignKey("users.id"), nullable=False)
creator = relationship("User")
@property @property
def status(self): def status(self):
return self.status_events[-1].new_status 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): class RequestStatus(Enum):
STARTED = "started" STARTED = "Started"
PENDING_FINANCIAL_VERIFICATION = "pending_financial_verification" PENDING_FINANCIAL_VERIFICATION = "Pending Financial Verification"
PENDING_CCPO_APPROVAL = "pending_ccpo_approval" PENDING_CCPO_APPROVAL = "Pending CCPO Approval"
APPROVED = "approved" APPROVED = "Approved"
EXPIRED = "expired" EXPIRED = "Expired"
DELETED = "deleted" DELETED = "Deleted"
class RequestStatusEvent(Base): class RequestStatusEvent(Base):
@ -29,3 +29,7 @@ class RequestStatusEvent(Base):
sequence = Column( sequence = Column(
BigInteger, Sequence("request_status_events_sequence_seq"), nullable=False 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", "dod_id": "1234567890",
"first_name": "Sam", "first_name": "Sam",
"last_name": "Seeceepio", "last_name": "Seeceepio",
"atat_role": "ccpo", "atat_role_name": "ccpo",
"email": "sam@test.com"
}, },
"amanda": { "amanda": {
"dod_id": "2345678901", "dod_id": "2345678901",
"first_name": "Amanda", "first_name": "Amanda",
"last_name": "Adamson", "last_name": "Adamson",
"atat_role": "default", "atat_role_name": "default",
"email": "amanda@test.com"
}, },
"brandon": { "brandon": {
"dod_id": "3456789012", "dod_id": "3456789012",
"first_name": "Brandon", "first_name": "Brandon",
"last_name": "Buchannan", "last_name": "Buchannan",
"atat_role": "default", "atat_role_name": "default",
"email": "brandon@test.com"
}, },
"christina": { "christina": {
"dod_id": "4567890123", "dod_id": "4567890123",
"first_name": "Christina", "first_name": "Christina",
"last_name": "Collins", "last_name": "Collins",
"atat_role": "default", "atat_role_name": "default",
"email": "christina@test.com"
}, },
"dominick": { "dominick": {
"dod_id": "5678901234", "dod_id": "5678901234",
"first_name": "Dominick", "first_name": "Dominick",
"last_name": "Domingo", "last_name": "Domingo",
"atat_role": "default", "atat_role_name": "default",
"email": "dominick@test.com"
}, },
"erica": { "erica": {
"dod_id": "6789012345", "dod_id": "6789012345",
"first_name": "Erica", "first_name": "Erica",
"last_name": "Eichner", "last_name": "Eichner",
"atat_role": "default", "atat_role_name": "default",
"email": "erica@test.com"
}, },
} }
@bp.route("/login-dev") @bp.route("/login-dev")
def login_dev(): def login_dev():
role = request.args.get("username", "amanda") role = request.args.get("username", "amanda")
user_data = _DEV_USERS[role] 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 = Users.get_or_create_by_dod_id(
user = _set_user_permissions(user_data["dod_id"], user_data["atat_role"], basic_data) 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 session["user_id"] = user.id
return redirect(url_for("atst.home")) 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 from atst.domain.requests import Requests
def map_request(user, request): def map_request(request):
time_created = pendulum.instance(request.time_created) time_created = pendulum.instance(request.time_created)
is_new = time_created.add(days=1) > pendulum.now() is_new = time_created.add(days=1) > pendulum.now()
app_count = request.body.get("details_of_use", {}).get("num_software_systems", 0)
return { return {
"order_id": request.id, "order_id": request.id,
"is_new": is_new, "is_new": is_new,
"status": request.status, "status": request.status_displayname,
"app_count": 1, "app_count": app_count,
"date": time_created.format("M/DD/YYYY"), "date": time_created.format("M/DD/YYYY"),
"full_name": user.full_name "full_name": request.creator.full_name,
} }
@requests_bp.route("/requests", methods=["GET"]) @requests_bp.route("/requests", methods=["GET"])
def requests_index(): def requests_index():
requests = [] requests = []
if ( if "review_and_approve_jedi_workspace_request" in g.current_user.atat_permissions:
"review_and_approve_jedi_workspace_request"
in g.current_user.atat_permissions
):
requests = Requests.get_many() requests = Requests.get_many()
else: 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) return render_template("requests.html", requests=mapped_requests)

View File

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

View File

@ -111,7 +111,7 @@ def requests_submit(request_id=None):
def _check_can_view_request(request_id): def _check_can_view_request(request_id):
if Permissions.REVIEW_AND_APPROVE_JEDI_WORKSPACE_REQUEST in g.current_user.atat_permissions: if Permissions.REVIEW_AND_APPROVE_JEDI_WORKSPACE_REQUEST in g.current_user.atat_permissions:
pass pass
elif Requests.exists(request_id, g.current_user.id): elif Requests.exists(request_id, g.current_user):
pass pass
else: else:
raise UnauthorizedError(g.current_user, "view request {}".format(request_id)) 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.app import make_app, make_config
from atst.database import db as _db from atst.database import db as _db
from .mocks import MOCK_USER
import tests.factories as factories import tests.factories as factories
@ -75,7 +74,6 @@ class DummyForm(dict):
class DummyField(object): class DummyField(object):
def __init__(self, data=None, errors=(), raw_data=None): def __init__(self, data=None, errors=(), raw_data=None):
self.data = data self.data = data
self.errors = list(errors) self.errors = list(errors)
@ -93,9 +91,11 @@ def dummy_field():
@pytest.fixture @pytest.fixture
def user_session(monkeypatch): def user_session(monkeypatch, session):
def set_user_session(user=None):
def set_user_session(user=MOCK_USER): monkeypatch.setattr(
monkeypatch.setattr("atst.domain.auth.get_current_user", lambda *args: user) "atst.domain.auth.get_current_user",
lambda *args: user or factories.UserFactory.build(),
)
return set_user_session return set_user_session

View File

@ -25,7 +25,7 @@ def test_nonexistent_request_raises():
def test_new_request_has_started_status(): def test_new_request_has_started_status():
request = Requests.create(uuid4(), {}) request = Requests.create(UserFactory.build(), {})
assert request.status == RequestStatus.STARTED 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): def test_exists(session):
user_allowed = UserFactory.create() user_allowed = UserFactory.create()
user_denied = UserFactory.create() user_denied = UserFactory.create()
request = RequestFactory.create(creator=user_allowed.id) request = RequestFactory.create(creator=user_allowed)
assert Requests.exists(request.id, user_allowed.id) assert Requests.exists(request.id, user_allowed)
assert not Requests.exists(request.id, user_denied.id) 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 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 RoleFactory(factory.alchemy.SQLAlchemyModelFactory):
class Meta: class Meta:
@ -64,3 +39,64 @@ class RequestStatusEventFactory(factory.alchemy.SQLAlchemyModelFactory):
model = RequestStatusEvent model = RequestStatusEvent
id = factory.Sequence(lambda x: uuid4()) 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 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) request = Requests.set_status(request, RequestStatus.PENDING_CCPO_APPROVAL)
assert Requests.action_required_by(request) == "ccpo" 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): def test_owner_can_view_request(client, user_session):
user = UserFactory.create() user = UserFactory.create()
user_session(user) 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) response = client.get("/requests/new/1/{}".format(request.id), follow_redirects=True)