diff --git a/.circleci/config.yml b/.circleci/config.yml index 1bb49bb3..c28b28a7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -103,7 +103,7 @@ commands: --password $AZURE_SP_PASSWORD \ --username $AZURE_SP echo "Successfully logged in to Azure CLI." - az acr login --name $AZURE_REGISTRY + az acr login --name $AZURE_REGISTRY | grep "Succeeded" - run: name: Install kubectl command: | diff --git a/atst/app.py b/atst/app.py index 1499671c..d510ff82 100644 --- a/atst/app.py +++ b/atst/app.py @@ -1,7 +1,7 @@ import os import re from configparser import ConfigParser -from datetime import datetime +import pendulum from flask import Flask, request, g, session, url_for as flask_url_for from flask_session import Session import redis @@ -187,11 +187,11 @@ def map_config(config): "CELERY_RESULT_EXPIRES": 0, "CELERY_RESULT_EXTENDED": True, "OFFICE_365_DOMAIN": "onmicrosoft.com", - "CONTRACT_START_DATE": datetime.strptime( - config.get("default", "CONTRACT_START_DATE"), "%Y-%m-%d" + "CONTRACT_START_DATE": pendulum.from_format( + config.get("default", "CONTRACT_START_DATE"), "YYYY-MM-DD" ).date(), - "CONTRACT_END_DATE": datetime.strptime( - config.get("default", "CONTRACT_END_DATE"), "%Y-%m-%d" + "CONTRACT_END_DATE": pendulum.from_format( + config.get("default", "CONTRACT_END_DATE"), "YYYY-MM-DD" ).date(), "SESSION_COOKIE_SECURE": config.getboolean("default", "SESSION_COOKIE_SECURE"), } diff --git a/atst/domain/authnid/crl/__init__.py b/atst/domain/authnid/crl/__init__.py index 037a8bb4..9a8f6638 100644 --- a/atst/domain/authnid/crl/__init__.py +++ b/atst/domain/authnid/crl/__init__.py @@ -4,7 +4,6 @@ import hashlib import logging from OpenSSL import crypto, SSL -from datetime import datetime from flask import current_app as app from .util import load_crl_locations_cache, serialize_crl_locations_cache, CRL_LIST diff --git a/atst/domain/csp/files.py b/atst/domain/csp/files.py index 0f3e05a0..f2f9383c 100644 --- a/atst/domain/csp/files.py +++ b/atst/domain/csp/files.py @@ -1,5 +1,5 @@ -from datetime import datetime, timedelta from uuid import uuid4 +import pendulum class FileService: @@ -39,7 +39,7 @@ class AzureFileService(FileService): self.account_name = config["AZURE_ACCOUNT_NAME"] self.storage_key = config["AZURE_STORAGE_KEY"] self.container_name = config["AZURE_TO_BUCKET_NAME"] - self.timeout = timedelta(seconds=config["PERMANENT_SESSION_LIFETIME"]) + self.timeout = config["PERMANENT_SESSION_LIFETIME"] from azure.storage.common import CloudStorageAccount from azure.storage.blob import BlobSasPermissions @@ -68,7 +68,7 @@ class AzureFileService(FileService): self.container_name, object_name, permission=self.BlobSasPermissions(create=True), - expiry=datetime.utcnow() + self.timeout, + expiry=pendulum.now(tz="utc").add(self.timeout), protocol="https", ) return ({"token": sas_token}, object_name) @@ -81,7 +81,7 @@ class AzureFileService(FileService): container_name=self.container_name, blob_name=object_name, permission=self.BlobPermissions(read=True), - expiry=datetime.utcnow() + self.timeout, + expiry=pendulum.now(tz="utc").add(self.timeout), content_disposition=f"attachment; filename={filename}", protocol="https", ) diff --git a/atst/domain/invitations.py b/atst/domain/invitations.py index 069be936..f6bfeacb 100644 --- a/atst/domain/invitations.py +++ b/atst/domain/invitations.py @@ -1,5 +1,5 @@ -import datetime from sqlalchemy.orm.exc import NoResultFound +import pendulum from atst.database import db from atst.models import ApplicationInvitation, InvitationStatus, PortfolioInvitation @@ -99,9 +99,7 @@ class BaseInvitations(object): @classmethod def current_expiration_time(cls): - return datetime.datetime.now() + datetime.timedelta( - minutes=cls.EXPIRATION_LIMIT_MINUTES - ) + return pendulum.now(tz="utc").add(minutes=cls.EXPIRATION_LIMIT_MINUTES) @classmethod def _update_status(cls, invite, new_status): diff --git a/atst/domain/task_orders.py b/atst/domain/task_orders.py index 499bccb0..44d6b47e 100644 --- a/atst/domain/task_orders.py +++ b/atst/domain/task_orders.py @@ -1,5 +1,5 @@ -from datetime import datetime from sqlalchemy import or_ +import pendulum from atst.database import db from atst.models.clin import CLIN @@ -41,8 +41,7 @@ class TaskOrders(BaseDomainClass): @classmethod def sign(cls, task_order, signer_dod_id): task_order.signer_dod_id = signer_dod_id - task_order.signed_at = datetime.now() - + task_order.signed_at = pendulum.now(tz="utc") db.session.add(task_order) db.session.commit() diff --git a/atst/domain/users.py b/atst/domain/users.py index e5fdbad7..6ad61cde 100644 --- a/atst/domain/users.py +++ b/atst/domain/users.py @@ -1,6 +1,6 @@ from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.exc import IntegrityError -from datetime import datetime +import pendulum from atst.database import db from atst.models import User @@ -111,7 +111,7 @@ class Users(object): @classmethod def update_last_login(cls, user): - user.last_login = datetime.now() + user.last_login = pendulum.now(tz="utc") db.session.add(user) db.session.commit() diff --git a/atst/filters.py b/atst/filters.py index 84191017..0eb93d2b 100644 --- a/atst/filters.py +++ b/atst/filters.py @@ -1,5 +1,4 @@ import re -import datetime from atst.utils.localization import translate from flask import render_template from jinja2 import contextfilter @@ -54,10 +53,6 @@ def formattedDate(value, formatter="%m/%d/%Y"): return "-" -def dateFromString(value, formatter="%m/%Y"): - return datetime.datetime.strptime(value, formatter) - - def pageWindow(pagination, size=2): page = pagination.page num_pages = pagination.pages @@ -81,7 +76,6 @@ def register_filters(app): app.jinja_env.filters["dollars"] = dollars app.jinja_env.filters["usPhone"] = usPhone app.jinja_env.filters["formattedDate"] = formattedDate - app.jinja_env.filters["dateFromString"] = dateFromString app.jinja_env.filters["pageWindow"] = pageWindow app.jinja_env.filters["renderAuditEvent"] = renderAuditEvent app.jinja_env.filters["withExtraParams"] = with_extra_params diff --git a/atst/models/clin.py b/atst/models/clin.py index 13a63cee..accab107 100644 --- a/atst/models/clin.py +++ b/atst/models/clin.py @@ -9,7 +9,7 @@ from sqlalchemy import ( String, ) from sqlalchemy.orm import relationship -from datetime import date +import pendulum from atst.models.base import Base import atst.models.mixins as mixins @@ -75,5 +75,5 @@ class CLIN(Base, mixins.TimestampsMixin): @property def is_active(self): return ( - self.start_date <= date.today() <= self.end_date + self.start_date <= pendulum.today() <= self.end_date ) and self.task_order.signed_at diff --git a/atst/models/mixins/invites.py b/atst/models/mixins/invites.py index 18916dc4..c87917ba 100644 --- a/atst/models/mixins/invites.py +++ b/atst/models/mixins/invites.py @@ -1,4 +1,4 @@ -import datetime +import pendulum from enum import Enum import secrets @@ -90,7 +90,7 @@ class InvitesMixin(object): @property def is_expired(self): return ( - datetime.datetime.now(self.expiration_time.tzinfo) > self.expiration_time + pendulum.now(tz=self.expiration_time.tzinfo) > self.expiration_time and not self.status == Status.ACCEPTED ) diff --git a/atst/models/task_order.py b/atst/models/task_order.py index 0b3c27ba..d2a63964 100644 --- a/atst/models/task_order.py +++ b/atst/models/task_order.py @@ -1,5 +1,4 @@ from enum import Enum -from decimal import Decimal from sqlalchemy import Column, DateTime, ForeignKey, String from sqlalchemy.ext.hybrid import hybrid_property @@ -141,14 +140,6 @@ class TaskOrder(Base, mixins.TimestampsMixin): def total_contract_amount(self): return sum((clin.total_amount for clin in self.clins if clin.total_amount)) - @property - def invoiced_funds(self): - # TODO: implement this using reporting data from the CSP - if self.is_active: - return self.total_obligated_funds * Decimal(0.75) - else: - return 0 - @property def display_status(self): if self.status == Status.UNSIGNED: diff --git a/atst/routes/portfolios/index.py b/atst/routes/portfolios/index.py index 44cac768..6472fca1 100644 --- a/atst/routes/portfolios/index.py +++ b/atst/routes/portfolios/index.py @@ -1,5 +1,4 @@ -from datetime import datetime - +import pendulum from flask import redirect, render_template, url_for, request as http_request, g from .blueprint import portfolios_bp @@ -56,5 +55,5 @@ def reports(portfolio_id): ), current_obligated_funds=current_obligated_funds, expired_task_orders=Reports.expired_task_orders(portfolio), - retrieved=datetime.now(), # mocked datetime of reporting data retrival + retrieved=pendulum.now(), # mocked datetime of reporting data retrival ) diff --git a/atst/routes/users.py b/atst/routes/users.py index ec5557aa..b055bcb6 100644 --- a/atst/routes/users.py +++ b/atst/routes/users.py @@ -1,4 +1,4 @@ -import datetime as dt +import pendulum from flask import Blueprint, render_template, g, request as http_request, redirect from atst.forms.edit_user import EditUserForm from atst.domain.users import Users @@ -23,8 +23,8 @@ def user(): next=next_, form=form, user=user, - mindate=(dt.datetime.now() - dt.timedelta(days=365)), - maxdate=dt.datetime.now(), + mindate=pendulum.now(tz="utc").subtract(days=365), + maxdate=pendulum.now(tz="utc"), ) @@ -44,6 +44,6 @@ def update_user(): form=form, user=user, next=next_url, - mindate=(dt.datetime.now() - dt.timedelta(days=365)), - maxdate=dt.datetime.now(), + mindate=pendulum.now(tz="utc").subtract(days=365), + maxdate=pendulum.now(tz="utc"), ) diff --git a/script/seed_sample.py b/script/seed_sample.py index d1cf1c9e..bc8d13ba 100644 --- a/script/seed_sample.py +++ b/script/seed_sample.py @@ -1,7 +1,7 @@ # Add root application dir to the python path import os import sys -from datetime import timedelta, date +import pendulum import random from faker import Faker from werkzeug.datastructures import FileStorage @@ -170,10 +170,9 @@ def add_members_to_portfolio(portfolio): def add_task_orders_to_portfolio(portfolio): - today = date.today() - future = today + timedelta(days=100) - yesterday = today - timedelta(days=1) - five_days = timedelta(days=5) + today = pendulum.today() + future = today.add(days=100) + yesterday = today.subtract(days=1) def build_pdf(): return {"filename": "sample_task_order.pdf", "object_name": str(uuid4())} @@ -192,13 +191,13 @@ def add_task_orders_to_portfolio(portfolio): clins = [ CLINFactory.build( - task_order=unsigned_to, start_date=(today - five_days), end_date=today + task_order=unsigned_to, start_date=today.subtract(days=5), end_date=today ), CLINFactory.build( - task_order=upcoming_to, start_date=(today + five_days), end_date=future + task_order=upcoming_to, start_date=today.add(days=5), end_date=future ), CLINFactory.build( - task_order=expired_to, start_date=(today - five_days), end_date=yesterday + task_order=expired_to, start_date=today.subtract(days=5), end_date=yesterday ), CLINFactory.build( task_order=active_to, diff --git a/styles/components/_portfolio_layout.scss b/styles/components/_portfolio_layout.scss index 06fecf63..e9af6d5e 100644 --- a/styles/components/_portfolio_layout.scss +++ b/styles/components/_portfolio_layout.scss @@ -20,14 +20,11 @@ .col--grow { overflow: inherit; - display: table; - min-height: 10rem; + align-self: center; } &__name { @include h1; - display: table-cell; - vertical-align: middle; h1 { margin: 0; diff --git a/styles/elements/_inputs.scss b/styles/elements/_inputs.scss index 9e74ff50..c424b723 100644 --- a/styles/elements/_inputs.scss +++ b/styles/elements/_inputs.scss @@ -60,6 +60,12 @@ margin: ($gap * 2) 0; max-width: 75rem; + &-label-helper { + font-size: $small-font-size; + margin-left: $gap; + margin-right: $gap; + } + label { padding: 0 0 ($gap / 2) 0; margin: 0; diff --git a/templates/components/multi_checkbox_input.html b/templates/components/multi_checkbox_input.html index bfda1fa2..820efe3b 100644 --- a/templates/components/multi_checkbox_input.html +++ b/templates/components/multi_checkbox_input.html @@ -26,7 +26,7 @@
- {{ expended_funds | dollars }} -
-{{ task_order.total_contract_amount | dollars }}
{{ task_order.total_obligated_funds | dollars }}
{{ task_order.invoiced_funds | dollars }}
-