fedspendingtransparency/usaspending-api

View on GitHub
usaspending_api/awards/v2/views/subawards.py

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
from copy import deepcopy

from django.db.models import F
from rest_framework.response import Response
from rest_framework.views import APIView

from usaspending_api.search.models import SubawardSearch
from usaspending_api.common.cache_decorator import cache_response
from usaspending_api.common.helpers.generic_helper import get_simple_pagination_metadata
from usaspending_api.common.validator.pagination import PAGINATION
from usaspending_api.common.validator.tinyshield import TinyShield


class SubawardsViewSet(APIView):
    """
    This route sends a request to the backend to retrieve subawards either
    related, optionally, to a specific parent award, or for all parent
    awards if desired.
    """

    endpoint_doc = "usaspending_api/api_contracts/contracts/v2/subawards.md"

    subaward_lookup = {
        # "Display Name": "database_column"
        "id": "broker_subaward_id",
        "subaward_number": "subaward_number",
        "description": "subaward_description",
        "action_date": "sub_action_date",
        "amount": "subaward_amount",
        "recipient_name": "sub_awardee_or_recipient_legal",
    }

    def _parse_and_validate_request(self, request_dict):
        models = deepcopy(PAGINATION)
        models.append(
            {
                "key": "award_id",
                "name": "award_id",
                "type": "any",
                "models": [{"type": "integer"}, {"type": "text", "text_type": "search"}],
                "optional": True,
                "default": None,
                "allow_nulls": True,
            }
        )
        for model in models:
            # Change sort to an enum of the desired values
            if model["name"] == "sort":
                model["type"] = "enum"
                model["enum_values"] = list(self.subaward_lookup.keys())
                model["default"] = "subaward_number"

        validated_request_data = TinyShield(models).block(request_dict)
        return validated_request_data

    def _business_logic(self, request_data):
        lower_limit = (request_data["page"] - 1) * request_data["limit"]
        upper_limit = request_data["page"] * request_data["limit"]

        queryset = SubawardSearch.objects.all()

        award_id = request_data["award_id"]
        if award_id is not None:
            award_id_column = "award_id" if type(award_id) is int else "unique_award_key"
            queryset = queryset.filter(**{award_id_column: award_id})

        queryset = queryset.values(*list(self.subaward_lookup.values()))

        # always secondary-sort by PK in case a repeating value (e.g. subaward_number) crosses pages, so suborder isn't abitrary
        if request_data["order"] == "desc":
            queryset = queryset.order_by(
                F(self.subaward_lookup[request_data["sort"]]).desc(nulls_last=True), F("broker_subaward_id").desc()
            )
        else:
            queryset = queryset.order_by(
                F(self.subaward_lookup[request_data["sort"]]).asc(nulls_first=True), F("broker_subaward_id").asc()
            )

        rows = list(queryset[lower_limit : upper_limit + 1])
        return [{k: row[v] for k, v in self.subaward_lookup.items()} for row in rows]

    @cache_response()
    def post(self, request):
        request_data = self._parse_and_validate_request(request.data)
        results = self._business_logic(request_data)
        page_metadata = get_simple_pagination_metadata(len(results), request_data["limit"], request_data["page"])
        response = {"page_metadata": page_metadata, "results": results[: request_data["limit"]]}
        return Response(response)