Skip to content

Commit

Permalink
Merge branch 'develop' into 2253-refactor/api-v3-mbu
Browse files Browse the repository at this point in the history
  • Loading branch information
macintoshpie committed Jun 30, 2020
2 parents 1680221 + 06d63a6 commit f3867ee
Show file tree
Hide file tree
Showing 7 changed files with 594 additions and 16 deletions.
14 changes: 13 additions & 1 deletion seed/api/v3/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from seed.views.v3.data_quality_checks import DataQualityCheckViewSet
from seed.views.v3.data_quality_check_rules import DataQualityCheckRuleViewSet
from seed.views.v3.datasets import DatasetViewSet
from seed.views.v3.geocode import GeocodeViewSet
from seed.views.v3.import_files import ImportFileViewSet
from seed.views.v3.measures import MeasureViewSet
from seed.views.v3.labels import LabelViewSet
Expand All @@ -22,7 +23,9 @@
from seed.views.v3.organizations import OrganizationViewSet
from seed.views.v3.organization_users import OrganizationUserViewSet
from seed.views.v3.properties import PropertyViewSet
from seed.views.v3.property_scenarios import PropertyScenarioViewSet
from seed.views.v3.taxlots import TaxlotViewSet
from seed.views.v3.ubid import UbidViewSet
from seed.views.v3.uploads import UploadViewSet
from seed.views.v3.users import UserViewSet

Expand All @@ -32,6 +35,7 @@
api_v3_router.register(r'columns', ColumnViewSet, base_name='columns')
api_v3_router.register(r'cycles', CycleViewSet, base_name='cycles')
api_v3_router.register(r'datasets', DatasetViewSet, base_name='datasets')
api_v3_router.register(r'geocode', GeocodeViewSet, base_name='geocode')
api_v3_router.register(r'labels', LabelViewSet, base_name='labels')
api_v3_router.register(r'data_quality_checks', DataQualityCheckViewSet, base_name='data_quality_checks')
api_v3_router.register(r'import_files', ImportFileViewSet, base_name='import_files')
Expand All @@ -40,6 +44,7 @@
api_v3_router.register(r'organizations', OrganizationViewSet, base_name='organizations')
api_v3_router.register(r'properties', PropertyViewSet, base_name='properties')
api_v3_router.register(r'taxlots', TaxlotViewSet, base_name='taxlots')
api_v3_router.register(r'ubid', UbidViewSet, base_name='ubid')
api_v3_router.register(r'upload', UploadViewSet, base_name='upload')
api_v3_router.register(r'users', UserViewSet, base_name='user')

Expand All @@ -48,9 +53,15 @@

organizations_router = nested_routers.NestedSimpleRouter(api_v3_router, r'organizations', lookup='organization')
organizations_router.register(r'users', OrganizationUserViewSet, base_name='organization-users')
taxlots_router = nested_routers.NestedSimpleRouter(api_v3_router, r'taxlots', lookup='taxlots')

properties_router = nested_routers.NestedSimpleRouter(api_v3_router, r'properties', lookup='property')
properties_router.register(r'notes', NoteViewSet, base_name='property-notes')
properties_router.register(r'scenarios', PropertyScenarioViewSet, base_name='property-scenarios')

taxlots_router = nested_routers.NestedSimpleRouter(api_v3_router, r'taxlots', lookup='taxlot')
taxlots_router.register(r'notes', NoteViewSet, base_name='taxlot-notes')


urlpatterns = [
url(r'^', include(api_v3_router.urls)),
url(r'^', include(data_quality_checks_router.urls)),
Expand All @@ -65,5 +76,6 @@
{'inventory_type': 'taxlot'},
),
url(r'^', include(organizations_router.urls)),
url(r'^', include(properties_router.urls)),
url(r'^', include(taxlots_router.urls)),
]
20 changes: 20 additions & 0 deletions seed/utils/api_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,26 @@ def query_boolean_field(name, required, description):
type=openapi.TYPE_BOOLEAN
)

@staticmethod
def form_string_field(name, required, description):
return openapi.Parameter(
name,
openapi.IN_FORM,
description=description,
required=required,
type=openapi.TYPE_STRING
)

@staticmethod
def upload_file_field(name, required, description):
return openapi.Parameter(
name,
openapi.IN_FORM,
description=description,
required=required,
type=openapi.TYPE_FILE
)

@staticmethod
def path_id_field(description):
return openapi.Parameter(
Expand Down
152 changes: 152 additions & 0 deletions seed/views/v3/geocode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# !/usr/bin/env python
# encoding: utf-8

from django.db.models import Subquery
from django.http import JsonResponse

from drf_yasg.utils import swagger_auto_schema

from rest_framework import viewsets
from rest_framework.decorators import action

from seed.decorators import ajax_request_class

from seed.lib.superperms.orgs.decorators import has_perm_class

from seed.models.properties import PropertyState, PropertyView
from seed.models.tax_lots import TaxLotState, TaxLotView
from seed.utils.api import api_endpoint_class, OrgMixin
from seed.utils.api_schema import AutoSchemaHelper
from seed.utils.geocode import geocode_buildings


class GeocodeViewSet(viewsets.ViewSet, OrgMixin):

@swagger_auto_schema(
manual_parameters=[AutoSchemaHelper.query_org_id_field()],
request_body=AutoSchemaHelper.schema_factory(
{
'property_view_ids': ['integer'],
'taxlot_view_ids': ['integer'],
},
description='IDs by inventory type for records to be geocoded.'
)
)
@api_endpoint_class
@ajax_request_class
@has_perm_class('can_modify_data')
@action(detail=False, methods=['POST'])
def geocode_by_ids(self, request):
"""
Submit a request to geocode property and tax lot records.
"""
body = dict(request.data)
org_id = self.get_organization(request)
property_view_ids = body.get('property_view_ids')
taxlot_view_ids = body.get('taxlot_view_ids')

if property_view_ids:
property_views = PropertyView.objects.filter(
id__in=property_view_ids,
cycle__organization_id=org_id
)
properties = PropertyState.objects.filter(
id__in=Subquery(property_views.values('state_id'))
)
geocode_buildings(properties)

if taxlot_view_ids:
taxlot_views = TaxLotView.objects.filter(
id__in=taxlot_view_ids,
cycle__organization_id=org_id
)
taxlots = TaxLotState.objects.filter(
id__in=Subquery(taxlot_views.values('state_id'))
)
geocode_buildings(taxlots)

return JsonResponse({'status': 'success'})

@swagger_auto_schema(
manual_parameters=[AutoSchemaHelper.query_org_id_field()],
request_body=AutoSchemaHelper.schema_factory(
{
'property_view_ids': ['integer'],
'taxlot_view_ids': ['integer'],
},
description='IDs by inventory type for records to be used in building a geocoding summary.'
)
)
@api_endpoint_class
@ajax_request_class
@has_perm_class('can_view_data')
@action(detail=False, methods=['POST'])
def confidence_summary(self, request):
"""
Generate a summary of geocoding confidence values for property and
tax lot records.
"""
body = dict(request.data)
org_id = self.get_organization(request)
property_view_ids = body.get('property_view_ids')
taxlot_view_ids = body.get('taxlot_view_ids')

result = {}

if property_view_ids:
property_views = PropertyView.objects.filter(
id__in=property_view_ids,
cycle__organization_id=org_id
)
result["properties"] = {
'not_geocoded': PropertyState.objects.filter(
id__in=Subquery(property_views.values('state_id')),
geocoding_confidence__isnull=True
).count(),
'high_confidence': PropertyState.objects.filter(
id__in=Subquery(property_views.values('state_id')),
geocoding_confidence__startswith='High'
).count(),
'low_confidence': PropertyState.objects.filter(
id__in=Subquery(property_views.values('state_id')),
geocoding_confidence__startswith='Low'
).count(),
'manual': PropertyState.objects.filter(
id__in=Subquery(property_views.values('state_id')),
geocoding_confidence='Manually geocoded (N/A)'
).count(),
'missing_address_components': PropertyState.objects.filter(
id__in=Subquery(property_views.values('state_id')),
geocoding_confidence='Missing address components (N/A)'
).count(),
}

if taxlot_view_ids:
taxlot_views = TaxLotView.objects.filter(
id__in=taxlot_view_ids,
cycle__organization_id=org_id
)
result["tax_lots"] = {
'not_geocoded': TaxLotState.objects.filter(
id__in=Subquery(taxlot_views.values('state_id')),
geocoding_confidence__isnull=True
).count(),
'high_confidence': TaxLotState.objects.filter(
id__in=Subquery(taxlot_views.values('state_id')),
geocoding_confidence__startswith='High'
).count(),
'low_confidence': TaxLotState.objects.filter(
id__in=Subquery(taxlot_views.values('state_id')),
geocoding_confidence__startswith='Low'
).count(),
'manual': TaxLotState.objects.filter(
id__in=Subquery(taxlot_views.values('state_id')),
geocoding_confidence='Manually geocoded (N/A)'
).count(),
'missing_address_components': TaxLotState.objects.filter(
id__in=Subquery(taxlot_views.values('state_id')),
geocoding_confidence='Missing address components (N/A)'
).count(),
}

return result
18 changes: 9 additions & 9 deletions seed/views/v3/notes.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,22 +44,22 @@ class NoteViewSet(SEEDOrgNoPatchOrOrgCreateModelViewSet):
def get_queryset(self):
# check if the request is properties or taxlots
org_id = self.get_organization(self.request)
if self.kwargs.get('properties_pk', None):
return Note.objects.filter(organization_id=org_id, property_view_id=self.kwargs.get('properties_pk'))
elif self.kwargs.get('taxlots_pk', None):
return Note.objects.filter(organization_id=org_id, taxlot_view_id=self.kwargs.get('taxlots_pk'))
if self.kwargs.get('property_pk', None):
return Note.objects.filter(organization_id=org_id, property_view_id=self.kwargs.get('property_pk'))
elif self.kwargs.get('taxlot_pk', None):
return Note.objects.filter(organization_id=org_id, taxlot_view_id=self.kwargs.get('taxlot_pk'))
else:
return Note.objects.filter(organization_id=org_id)

def perform_create(self, serializer):
org_id = self.get_organization(self.request)
if self.kwargs.get('properties_pk', None):
if self.kwargs.get('property_pk', None):
serializer.save(
organization_id=org_id, user=self.request.user, property_view_id=self.kwargs.get('properties_pk', None)
organization_id=org_id, user=self.request.user, property_view_id=self.kwargs.get('property_pk', None)
)
elif self.kwargs.get('taxlots_pk', None):
elif self.kwargs.get('taxlot_pk', None):
serializer.save(
organization_id=org_id, user=self.request.user, taxlot_view_id=self.kwargs.get('taxlots_pk', None)
organization_id=org_id, user=self.request.user, taxlot_view_id=self.kwargs.get('taxlot_pk', None)
)
else:
_log.warn("Unable to create model without a property_pk or taxlots_pk")
_log.warn("Unable to create model without a property_pk or taxlot_pk")
Loading

0 comments on commit f3867ee

Please sign in to comment.