Skip to content

Commit

Permalink
Merge pull request #3100 from SEED-platform/3081-refactor/util-proper…
Browse files Browse the repository at this point in the history
…ty-taxlot-fiter

3081 refactor/util property taxlot fiter
  • Loading branch information
perryr16 committed Jan 18, 2022
2 parents 80f4afa + bf488fb commit 4f9159a
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 313 deletions.
175 changes: 175 additions & 0 deletions seed/utils/filter_state.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
"""
:copyright (c) 2014 - 2022, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals from the U.S. Department of Energy) and contributors. All rights reserved. # NOQA
:author
"""

from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
from django.db.utils import DataError
from django.http import JsonResponse, HttpResponse
from rest_framework import status
from rest_framework.request import Request
from seed.lib.superperms.orgs.models import Organization
from seed.models import (VIEW_LIST, VIEW_LIST_PROPERTY, VIEW_LIST_TAXLOT,
Column, ColumnListProfile, ColumnListProfileColumn,
Cycle, PropertyView)
from seed.models import TaxLotProperty, TaxLotView
from seed.search import build_view_filters_and_sorts, FilterException
from seed.serializers.pint import (apply_display_unit_preferences)
from typing import Optional, Literal


def get_filtered_results(request: Request, inventory_type: Literal['property', 'taxlot'], profile_id: int) -> HttpResponse:
page = request.query_params.get('page', 1)
per_page = request.query_params.get('per_page', 1)
org_id = request.query_params.get('organization_id')
cycle_id = request.query_params.get('cycle')
# check if there is a query paramater for the profile_id. If so, then use that one
profile_id = request.query_params.get('profile_id', profile_id)

if not org_id:
return JsonResponse(
{'status': 'error', 'message': 'Need to pass organization_id as query parameter'},
status=status.HTTP_400_BAD_REQUEST)
org = Organization.objects.get(id=org_id)

if cycle_id:
cycle = Cycle.objects.get(organization_id=org_id, pk=cycle_id)
else:
cycle = Cycle.objects.filter(organization_id=org_id).order_by('name')
if cycle:
cycle = cycle.first()
if not cycle:
return JsonResponse({
'status': 'error',
'message': 'Could not locate cycle',
'pagination': {
'total': 0
},
'cycle_id': None,
'results': []
})

if inventory_type == 'property':
views_list = (
PropertyView.objects.select_related('property', 'state', 'cycle')
.filter(property__organization_id=org_id, cycle=cycle)
)
elif inventory_type == 'taxlot':
views_list = (
TaxLotView.objects.select_related('taxlot', 'state', 'cycle')
.filter(taxlot__organization_id=org_id, cycle=cycle)
)

include_related = (
str(request.query_params.get('include_related', 'true')).lower() == 'true'
)

# Retrieve all the columns that are in the db for this organization
columns_from_database = Column.retrieve_all(
org_id=org_id,
inventory_type=inventory_type,
only_used=False,
include_related=include_related
)
try:
filters, annotations, order_by = build_view_filters_and_sorts(request.query_params, columns_from_database)
except FilterException as e:
return JsonResponse(
{
'status': 'error',
'message': f'Error filtering: {str(e)}'
},
status=status.HTTP_400_BAD_REQUEST
)

views_list = views_list.annotate(**annotations).filter(filters).order_by(*order_by)

# Return property views limited to the 'property_view_ids' list. Otherwise, if selected is empty, return all
if f'{inventory_type}_view_ids' in request.data and request.data[f'{inventory_type}_view_ids']:
views_list = views_list.filter(id__in=request.data[f'{inventory_type}_view_ids'])

paginator = Paginator(views_list, per_page)

try:
views = paginator.page(page)
page = int(page)
except PageNotAnInteger:
views = paginator.page(1)
page = 1
except EmptyPage:
views = paginator.page(paginator.num_pages)
page = paginator.num_pages
except DataError as e:
return JsonResponse(
{
'status': 'error',
'recommended_action': 'update_column_settings',
'message': f'Error filtering - your data might not match the column settings data type: {str(e)}'
},
status=status.HTTP_400_BAD_REQUEST
)

# This uses an old method of returning the show_columns. There is a new method that
# is prefered in v2.1 API with the ProfileIdMixin.
if inventory_type == 'property':
profile_inventory_type = VIEW_LIST_PROPERTY
elif inventory_type == 'taxlot':
profile_inventory_type = VIEW_LIST_TAXLOT

show_columns: Optional[list[int]] = None
if profile_id is None:
show_columns = None
elif profile_id == -1:
show_columns = list(Column.objects.filter(
organization_id=org_id
).values_list('id', flat=True))
else:
try:
profile = ColumnListProfile.objects.get(
organization_id=org_id,
id=profile_id,
profile_location=VIEW_LIST,
inventory_type=profile_inventory_type
)
show_columns = list(ColumnListProfileColumn.objects.filter(
column_list_profile_id=profile.id
).values_list('column_id', flat=True))
except ColumnListProfile.DoesNotExist:
show_columns = None

try:
related_results = TaxLotProperty.serialize(
views,
show_columns,
columns_from_database,
include_related
)
except DataError as e:
return JsonResponse(
{
'status': 'error',
'recommended_action': 'update_column_settings',
'message': f'Error filtering - your data might not match the column settings data type: {str(e)}'
},
status=status.HTTP_400_BAD_REQUEST
)

# collapse units here so we're only doing the last page; we're already a
# realized list by now and not a lazy queryset
unit_collapsed_results = [apply_display_unit_preferences(org, x) for x in related_results]

response = {
'pagination': {
'page': page,
'start': paginator.page(page).start_index(),
'end': paginator.page(page).end_index(),
'num_pages': paginator.num_pages,
'has_next': paginator.page(page).has_next(),
'has_previous': paginator.page(page).has_previous(),
'total': paginator.count
},
'cycle_id': cycle.id,
'results': unit_collapsed_results
}

return JsonResponse(response)
165 changes: 7 additions & 158 deletions seed/views/v3/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@
"""
import os
from collections import namedtuple
from typing import Optional

from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
from django.db.models import Q, Subquery
from django.db.utils import DataError
from django.http import HttpResponse, JsonResponse
from django_filters import CharFilter, DateFilter
from django_filters import rest_framework as filters
Expand All @@ -17,26 +14,22 @@
from rest_framework.decorators import action
from rest_framework.parsers import JSONParser, MultiPartParser
from rest_framework.renderers import JSONRenderer
from rest_framework.request import Request
from seed.building_sync.building_sync import BuildingSync
from seed.data_importer.utils import usage_point_id
from seed.decorators import ajax_request_class
from seed.hpxml.hpxml import HPXML
from seed.lib.superperms.orgs.decorators import has_perm_class
from seed.lib.superperms.orgs.models import Organization
from seed.models import (AUDIT_USER_EDIT, DATA_STATE_MATCHING,
MERGE_STATE_DELETE, MERGE_STATE_MERGED,
MERGE_STATE_NEW, VIEW_LIST, VIEW_LIST_PROPERTY,
BuildingFile, Column, ColumnListProfile,
ColumnListProfileColumn, ColumnMappingProfile, Cycle,
MERGE_STATE_NEW,
BuildingFile, Column,
ColumnMappingProfile, Cycle,
Meter, Note, Property, PropertyAuditLog,
PropertyMeasure, PropertyState, PropertyView,
Simulation)
from seed.models import StatusLabel as Label
from seed.models import TaxLotProperty, TaxLotView
from seed.search import build_view_filters_and_sorts, FilterException
from seed.serializers.pint import (PintJSONEncoder,
apply_display_unit_preferences)
from seed.serializers.pint import (PintJSONEncoder)
from seed.serializers.properties import (PropertySerializer,
PropertyStateSerializer,
PropertyViewAsStateSerializer,
Expand All @@ -54,6 +47,7 @@
pair_unpair_property_taxlot,
properties_across_cycles,
update_result_with_master)
from seed.utils.filter_state import get_filtered_results

# Global toggle that controls whether or not to display the raw extra
# data fields in the columns returned for the view.
Expand Down Expand Up @@ -135,151 +129,6 @@ def search(self, request):
safe=False,
)

def _get_filtered_results(self, request: Request, profile_id: int):
page = request.query_params.get('page', 1)
per_page = request.query_params.get('per_page', 1)
org_id = self.get_organization(request)
cycle_id = request.query_params.get('cycle')
# check if there is a query paramater for the profile_id. If so, then use that one
profile_id = request.query_params.get('profile_id', profile_id)

if not org_id:
return JsonResponse(
{'status': 'error', 'message': 'Need to pass organization_id as query parameter'},
status=status.HTTP_400_BAD_REQUEST)
org = Organization.objects.get(id=org_id)

if cycle_id:
cycle = Cycle.objects.get(organization_id=org_id, pk=cycle_id)
else:
cycle = Cycle.objects.filter(organization_id=org_id).order_by('name')
if cycle:
cycle = cycle.first()
else:
return JsonResponse({
'status': 'error',
'message': 'Could not locate cycle',
'pagination': {
'total': 0
},
'cycle_id': None,
'results': []
})

property_views_list = (
PropertyView.objects.select_related('property', 'state', 'cycle')
.filter(property__organization_id=org_id, cycle=cycle)
)

include_related = (
str(request.query_params.get('include_related', 'true')).lower() == 'true'
)

# Retrieve all the columns that are in the db for this organization
columns_from_database = Column.retrieve_all(
org_id=org_id,
inventory_type='property',
only_used=False,
include_related=include_related
)
try:
filters, annotations, order_by = build_view_filters_and_sorts(request.query_params, columns_from_database)
except FilterException as e:
return JsonResponse(
{
'status': 'error',
'message': f'Error filtering: {str(e)}'
},
status=status.HTTP_400_BAD_REQUEST
)

property_views_list = property_views_list.annotate(**annotations).filter(filters).order_by(*order_by)

# Return property views limited to the 'property_view_ids' list. Otherwise, if selected is empty, return all
if 'property_view_ids' in request.data and request.data['property_view_ids']:
property_views_list = property_views_list.filter(id__in=request.data['property_view_ids'])

paginator = Paginator(property_views_list, per_page)

try:
property_views = paginator.page(page)
page = int(page)
except PageNotAnInteger:
property_views = paginator.page(1)
page = 1
except EmptyPage:
property_views = paginator.page(paginator.num_pages)
page = paginator.num_pages
except DataError as e:
return JsonResponse(
{
'status': 'error',
'recommended_action': 'update_column_settings',
'message': f'Error filtering - your data might not match the column settings data type: {str(e)}'
},
status=status.HTTP_400_BAD_REQUEST
)

# This uses an old method of returning the show_columns. There is a new method that
# is prefered in v2.1 API with the ProfileIdMixin.
show_columns: Optional[list[int]] = None
if profile_id is None:
show_columns = None
elif profile_id == -1:
show_columns = list(Column.objects.filter(
organization_id=org_id
).values_list('id', flat=True))
else:
try:
profile = ColumnListProfile.objects.get(
organization_id=org_id,
id=profile_id,
profile_location=VIEW_LIST,
inventory_type=VIEW_LIST_PROPERTY
)
show_columns = list(ColumnListProfileColumn.objects.filter(
column_list_profile_id=profile.id
).values_list('column_id', flat=True))
except ColumnListProfile.DoesNotExist:
show_columns = None

try:
related_results = TaxLotProperty.serialize(
property_views,
show_columns,
columns_from_database,
include_related
)
except DataError as e:
return JsonResponse(
{
'status': 'error',
'recommended_action': 'update_column_settings',
'message': f'Error filtering - your data might not match the column settings data type: {str(e)}'
},
status=status.HTTP_400_BAD_REQUEST
)

# collapse units here so we're only doing the last page; we're already a
# realized list by now and not a lazy queryset
unit_collapsed_results = [apply_display_unit_preferences(org, x) for x in related_results]

response = {
'pagination': {
'page': page,
'start': paginator.page(page).start_index(),
'end': paginator.page(page).end_index(),
'num_pages': paginator.num_pages,
'has_next': paginator.page(page).has_next(),
'has_previous': paginator.page(page).has_previous(),
'total': paginator.count
},
'cycle_id': cycle.id,
'results': unit_collapsed_results
}

return JsonResponse(response)

def _move_relationships(self, old_state, new_state):
"""
In general, we move the old relationships to the new state since the old state should not be
Expand Down Expand Up @@ -441,7 +290,7 @@ def list(self, request):
"""
List all the properties with all columns
"""
return self._get_filtered_results(request, profile_id=-1)
return get_filtered_results(request, 'property', profile_id=-1)

@swagger_auto_schema(
request_body=AutoSchemaHelper.schema_factory(
Expand Down Expand Up @@ -536,7 +385,7 @@ def filter(self, request):
except TypeError:
pass

return self._get_filtered_results(request, profile_id=profile_id)
return get_filtered_results(request, 'property', profile_id=profile_id)

@swagger_auto_schema(
manual_parameters=[AutoSchemaHelper.query_org_id_field(required=True)],
Expand Down

0 comments on commit 4f9159a

Please sign in to comment.