Skip to content

Commit

Permalink
Audit Template Report Submission Endpoint (#4411)
Browse files Browse the repository at this point in the history
* endpoint for  AT submission pdf report download

* fix upload at submission endpoint

* add test

* update swagger to support downloading AT pdf or xml.

* fix typing

* spelling

---------

Co-authored-by: Nicholas Long <[email protected]>
Co-authored-by: Nicholas Long <[email protected]>
  • Loading branch information
3 people authored Nov 23, 2023
1 parent a7ef1b1 commit bfe6765
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 4 deletions.
2 changes: 2 additions & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@
"lokalize",
"lookup",
"lte",
"lxml",
"MapItem",
"mappable",
"mapquest",
Expand Down Expand Up @@ -235,6 +236,7 @@
"noqa",
"npm",
"nrows",
"nsmap",
"num",
"Octant",
"officedocument",
Expand Down
38 changes: 37 additions & 1 deletion seed/audit_template/audit_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import json
import logging
from datetime import datetime
from typing import Any, Tuple

import requests
from celery import shared_task
Expand Down Expand Up @@ -53,6 +54,41 @@ def get_building_xml(self, audit_template_building_id, token):

return response, ""

def get_submission(self, audit_template_submission_id: int, report_format: str = 'pdf') -> Tuple[Any, str]:
"""Download an Audit Template submission report.
Args:
audit_template_submission_id (int): value of the "Submission ID" as seen on Audit Template
report_format (str, optional): Report format, either `xml` or `pdf`. Defaults to 'pdf'.
Returns:
requests.response: Result from Audit Template website
"""
# supporting 'PDF' and 'XML' formats only for now
token, message = self.get_api_token()
if not token:
return None, message

# validate format
if report_format.lower() not in ['xml', 'pdf']:
report_format = 'pdf'

# set headers
headers = {'accept': 'application/pdf'}
if report_format.lower() == 'xml':
headers = {'accept': 'application/xml'}

url = f'{self.API_URL}/rp/submissions/{audit_template_submission_id}.{report_format}?token={token}'
try:
response = requests.request("GET", url, headers=headers)

if response.status_code != 200:
return None, f'Expected 200 response from Audit Template get_submission but got {response.status_code!r}: {response.content!r}'
except Exception as e:
return None, f'Unexpected error from Audit Template: {e}'

return response, ""

def get_buildings(self, cycle_id):
token, message = self.get_api_token()
if not token:
Expand Down Expand Up @@ -161,7 +197,7 @@ def build_xml(self, state, report_type, display_field):
view = state.propertyview_set.first()

gfa = state.gross_floor_area
if type(gfa) == int:
if isinstance(gfa, int):
gross_floor_area = str(gfa)
elif gfa.units != ureg.feet**2:
gross_floor_area = str(gfa.to(ureg.feet ** 2).magnitude)
Expand Down
16 changes: 16 additions & 0 deletions seed/tests/test_audit_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def setUp(self):

self.get_building_url = reverse('api:v3:audit_template-get-building-xml', args=['1'])
self.get_buildings_url = reverse('api:v3:audit_template-get-buildings')
self.get_submission_url = reverse('api:v3:audit_template-get-submission', args=['1'])

self.good_authenticate_response = mock.Mock()
self.good_authenticate_response.status_code = 200
Expand All @@ -57,6 +58,11 @@ def setUp(self):
self.good_get_building_response.status_code = 200
self.good_get_building_response.text = "building response"

self.good_get_submission_response = mock.Mock()
self.good_get_submission_response.status_code = 200
self.good_get_submission_response.text = "submission response"
self.good_get_submission_response.content = "submission response"

self.bad_get_building_response = mock.Mock()
self.bad_get_building_response.status_code = 400
self.bad_get_building_response.content = "bad building response"
Expand All @@ -71,6 +77,16 @@ def test_get_building_xml_from_audit_template(self, mock_request):
self.assertEqual(200, response.status_code, response.content)
self.assertEqual(response.content, b"building response")

@mock.patch('requests.request')
def test_get_submission_from_audit_template(self, mock_request):
# -- Act
mock_request.side_effect = [self.good_authenticate_response, self.good_get_submission_response]
response = self.client.get(self.get_submission_url, data={"organization_id": self.org.id})

# -- Assert
self.assertEqual(200, response.status_code, response.content)
self.assertEqual(response.content, b"submission response")

@mock.patch('requests.request')
def test_get_building_xml_from_audit_template_org_has_no_at_token(self, mock_request):
# -- Setup
Expand Down
2 changes: 1 addition & 1 deletion seed/views/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ def health_check(request):
celery_status = False

try:
redis_status = not cache.has_key('redis-ping')
redis_status = 'redis-ping' not in cache
except Exception:
redis_status = False

Expand Down
48 changes: 46 additions & 2 deletions seed/views/v3/audit_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,57 @@


class AuditTemplateViewSet(viewsets.ViewSet, OrgMixin):
@swagger_auto_schema(manual_parameters=[
AutoSchemaHelper.query_org_id_field(),
AutoSchemaHelper.base_field(
name='id',
location_attr='IN_PATH',
type='TYPE_INTEGER',
required=True,
description='Audit Template Submission ID.'),
AutoSchemaHelper.query_string_field('report_format', False, 'Report format Valid values are: xml, pdf. Defaults to pdf.')
])
@has_perm_class('can_view_data')
@action(detail=True, methods=['GET'])
def get_submission(self, request, pk):
"""
Fetches a Report Submission (XML or PDF) from Audit Template (only)
"""
# get report format or default to pdf
default_report_format = 'pdf'
report_format = request.query_params.get('report_format', default_report_format)

valid_file_formats = ['xml', 'pdf']
if report_format.lower() not in valid_file_formats:
message = f"The report_format specified is invalid. Must be one of: {valid_file_formats}."
return JsonResponse({
'success': False,
'message': message
}, status=400)

# retrieve report
at = AuditTemplate(self.get_organization(self.request))
response, message = at.get_submission(pk, report_format)

if response is None:
return JsonResponse({
'success': False,
'message': message
}, status=400)
if report_format.lower() == 'xml':
return HttpResponse(response.text)
else:
response2 = HttpResponse(response.content)
response2.headers["Content-Type"] = 'application/pdf'
response2.headers["Content-Disposition"] = f'attachment; filename="at_submission_{pk}.pdf"'
return response2

@swagger_auto_schema(manual_parameters=[AutoSchemaHelper.query_org_id_field()])
@has_perm_class('can_view_data')
@action(detail=True, methods=['GET'])
def get_building_xml(self, request, pk):
"""
Fetches a Building XML for an Audit Template property and updates the corresponding PropertyView
Fetches a Building XML for an Audit Template property (only)
"""
at = AuditTemplate(self.get_organization(self.request))
response, message = at.get_building(pk)
Expand Down Expand Up @@ -145,7 +189,7 @@ def get_buildings(self, request):
at = AuditTemplate(org)
result = at.get_buildings(cycle_id)

if type(result) is tuple:
if isinstance(result, tuple):
return JsonResponse({
'success': False,
'message': result[1]
Expand Down

0 comments on commit bfe6765

Please sign in to comment.