Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Audit Template Report Submission Endpoint #4411

Merged
merged 8 commits into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

silly python thing...

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

autopep caught this...

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(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my few updates to have swagger expose the correct info.

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