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

Add permissions to BuildingFile #4443

Merged
merged 2 commits into from
Dec 15, 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
10 changes: 4 additions & 6 deletions seed/models/building_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
from seed.data_importer.utils import kbtu_thermal_conversion_factors
from seed.hpxml.hpxml import HPXML as HPXMLParser
from seed.lib.merging.merging import merge_state
from seed.lib.superperms.orgs.models import Organization
from seed.models import (
AUDIT_IMPORT,
MERGE_STATE_MERGED,
Expand Down Expand Up @@ -99,9 +98,7 @@ def _create_property_state(self, organization_id, data):
"""
# sub-select the data that are needed to create the PropertyState object
db_columns = Column.retrieve_db_field_table_and_names_from_db_tables()
org = Organization.objects.get(pk=organization_id)
# TODO: allow user to choose ali
create_data = {"organization_id": organization_id, "raw_access_level_instance": org.root}
create_data = {"organization_id": organization_id}
extra_data = {}
for k, v in data.items():
# Skip the keys that are for measures and reports and process later
Expand Down Expand Up @@ -140,7 +137,7 @@ def _kbtu_thermal_conversion_factors(self):

return self._cache_kbtu_thermal_conversion_factors

def process(self, organization_id, cycle, property_view=None, promote_property_state=True):
def process(self, organization_id, cycle, property_view=None, promote_property_state=True, access_level_instance=None):
"""
Process the building file that was uploaded and create the correct models for the object

Expand Down Expand Up @@ -388,7 +385,8 @@ def process(self, organization_id, cycle, property_view=None, promote_property_s

# set the property_state to the new one
property_state = merged_state
elif not property_view and promote_property_state:
elif not property_view and promote_property_state and access_level_instance:
property_state.raw_access_level_instance = access_level_instance
property_view = property_state.promote(cycle)
else:
return True, property_state, None, messages
Expand Down
91 changes: 84 additions & 7 deletions seed/tests/test_building_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@

from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase
from django.urls import reverse

from config.settings.common import BASE_DIR
from seed.models import User
from seed.models import Property, User
from seed.models.building_file import BuildingFile
from seed.models.events import ATEvent
from seed.models.meters import Meter, MeterReading
from seed.models.scenarios import Scenario
from seed.tests.util import AccessLevelBaseTestCase
from seed.utils.organizations import create_organization


Expand Down Expand Up @@ -49,7 +51,7 @@ def test_buildingsync_constructor(self):
file_type=BuildingFile.BUILDINGSYNC,
)

status, property_state, property_view, messages = bf.process(self.org.id, self.org.cycles.first())
status, property_state, property_view, messages = bf.process(self.org.id, self.org.cycles.first(), access_level_instance=self.org.root)
self.assertTrue(status)
self.assertEqual(property_state.address_line_1, '123 Main St')
self.assertEqual(property_state.property_type, 'Office')
Expand All @@ -66,7 +68,7 @@ def test_buildingsync_constructor_diff_ns(self):
file_type=BuildingFile.BUILDINGSYNC,
)

status, property_state, property_view, messages = bf.process(self.org.id, self.org.cycles.first())
status, property_state, property_view, messages = bf.process(self.org.id, self.org.cycles.first(), access_level_instance=self.org.root)
self.assertTrue(status)
self.assertEqual(property_state.address_line_1, '1215 - 18th St')
self.assertEqual(messages, {'errors': [], 'warnings': []})
Expand All @@ -83,7 +85,7 @@ def test_buildingsync_constructor_single_scenario(self):
file_type=BuildingFile.BUILDINGSYNC,
)

status, property_state, property_view, messages = bf.process(self.org.id, self.org.cycles.first())
status, property_state, property_view, messages = bf.process(self.org.id, self.org.cycles.first(), access_level_instance=self.org.root)
self.assertTrue(status)
self.assertEqual(property_state.address_line_1, '123 Main St')
self.assertEqual(messages, {'errors': [], 'warnings': []})
Expand All @@ -107,7 +109,7 @@ def test_buildingsync_bricr_import(self):
file_type=BuildingFile.BUILDINGSYNC,
)

status, property_state, property_view, messages = bf.process(self.org.id, self.org.cycles.first())
status, property_state, property_view, messages = bf.process(self.org.id, self.org.cycles.first(), access_level_instance=self.org.root)
self.assertTrue(status, f'Expected process() to succeed; messages: {messages}')
self.assertEqual(property_state.address_line_1, '123 MAIN BLVD')
self.assertEqual(messages, {'errors': [], 'warnings': []})
Expand All @@ -132,7 +134,7 @@ def test_buildingsync_bricr_update_retains_scenarios(self):
file_type=BuildingFile.BUILDINGSYNC,
)

status, property_state, property_view, messages = bf.process(self.org.id, self.org.cycles.first())
status, property_state, property_view, messages = bf.process(self.org.id, self.org.cycles.first(), access_level_instance=self.org.root)
self.assertTrue(status, f'Expected process() to succeed; messages: {messages}')
self.assertEqual(property_state.address_line_1, '123 MAIN BLVD')
self.assertEqual(messages, {'errors': [], 'warnings': []})
Expand Down Expand Up @@ -180,8 +182,83 @@ def test_hpxml_constructor(self):
file_type=BuildingFile.HPXML
)

status, property_state, property_view, messages = bf.process(self.org.id, self.org.cycles.first())
status, property_state, property_view, messages = bf.process(self.org.id, self.org.cycles.first(), access_level_instance=self.org.root)
self.assertTrue(status)
self.assertEqual(property_state.owner, 'Jane Customer')
self.assertEqual(property_state.energy_score, 8)
self.assertEqual(messages, {'errors': [], 'warnings': []})


class TestBuildingFilesPermission(AccessLevelBaseTestCase):
def setUp(self):
super().setUp()
self.cycle = self.cycle_factory.get_cycle()

self.root_property = self.property_factory.get_property(access_level_instance=self.root_level_instance)
self.root_property_state = self.property_state_factory.get_property_state()
self.root_property_view = self.property_view_factory.get_property_view(prprty=self.root_property, state=self.root_property_state)

self.child_property = self.property_factory.get_property(access_level_instance=self.child_level_instance)
self.child_property_state = self.property_state_factory.get_property_state()
self.child_property_view = self.property_view_factory.get_property_view(prprty=self.child_property, state=self.child_property_state)

self.filename = path.join(BASE_DIR, 'seed', 'building_sync', 'tests', 'data', 'buildingsync_v2_0_bricr_workflow.xml')
with open(self.filename, 'rb') as f:
simple_uploaded_file = SimpleUploadedFile(f.name, f.read())

self.root_bf = BuildingFile.objects.create(
file=simple_uploaded_file,
filename=self.filename,
file_type=BuildingFile.BUILDINGSYNC,
property_state=self.root_property_state
)

self.child_bf = BuildingFile.objects.create(
file=simple_uploaded_file,
filename=self.filename,
file_type=BuildingFile.BUILDINGSYNC,
property_state=self.child_property_state
)

def test_list(self):
url = reverse('api:v3:building_files-list') + f'?organization_id={self.org.id}'

self.login_as_root_member()
result = self.client.get(url)
assert set([d["id"] for d in result.json()["data"]]) == {self.root_bf.id, self.child_bf.id}

self.login_as_child_member()
result = self.client.get(url)
assert set([d["id"] for d in result.json()["data"]]) == {self.child_bf.id}

def test_get(self):
url = reverse('api:v3:building_files-detail', args=[self.root_bf.id]) + f'?organization_id={self.org.id}'

self.login_as_root_member()
result = self.client.get(url)
assert result.status_code == 200

self.login_as_child_member()
result = self.client.get(url)
assert result.status_code == 404

def test_create(self):
url = reverse('api:v3:building_files-list') + f'?organization_id={self.org.id}&cycle_id={self.cycle.id}'

self.login_as_root_member()
with open(self.filename, 'rb') as f:
response = self.client.post(url, {
'file': f,
'file_type': 'BuildingSync',
})
property = Property.objects.get(pk=response.json()["data"]["property_view"]["property"])
assert property.access_level_instance == self.root_level_instance

self.login_as_child_member()
with open(self.filename, 'rb') as f:
response = self.client.post(url, {
'file': f,
'file_type': 'BuildingSync',
})
property = Property.objects.get(pk=response.json()["data"]["property_view"]["property"])
assert property.access_level_instance == self.child_level_instance
33 changes: 25 additions & 8 deletions seed/views/v3/building_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,39 @@
from rest_framework.parsers import MultiPartParser

from seed.lib.superperms.orgs.decorators import has_perm_class
from seed.lib.superperms.orgs.models import AccessLevelInstance
from seed.models import BuildingFile, Cycle
from seed.serializers.building_file import BuildingFileSerializer
from seed.serializers.properties import PropertyViewAsStateSerializer
from seed.utils.api_schema import (
AutoSchemaHelper,
swagger_auto_schema_org_query_param
)
from seed.utils.api_schema import AutoSchemaHelper
from seed.utils.viewsets import SEEDOrgReadOnlyModelViewSet


@method_decorator(swagger_auto_schema_org_query_param, name='list')
@method_decorator(swagger_auto_schema_org_query_param, name='retrieve')
@method_decorator(
name='list',
decorator=[has_perm_class('requires_viewer')]
)
@method_decorator(
name='retrieve',
decorator=[has_perm_class('requires_viewer')]
)
class BuildingFileViewSet(SEEDOrgReadOnlyModelViewSet):
model = BuildingFile
orgfilter = 'property_state__organization'
parser_classes = (MultiPartParser,)
pagination_class = None

def get_queryset(self):
if hasattr(self.request, 'access_level_instance_id'):
access_level_instance = AccessLevelInstance.objects.get(pk=self.request.access_level_instance_id)
return BuildingFile.objects.filter(
property_state__propertyview__property__access_level_instance__lft__gte=access_level_instance.lft,
property_state__propertyview__property__access_level_instance__rgt__lte=access_level_instance.rgt,
)

else:
return BuildingFile.objects.filter(pk=-1)

def get_serializer_class(self):
if self.action == 'create':
# pass "empty" serializer for Swagger page
Expand Down Expand Up @@ -65,6 +80,8 @@ def create(self, request):
"""
Create a new property from a building file
"""
access_level_instance = AccessLevelInstance.objects.get(pk=self.request.access_level_instance_id)

if len(request.FILES) == 0:
return JsonResponse({
'success': False,
Expand Down Expand Up @@ -120,7 +137,7 @@ def create(self, request):
file_type=file_type,
)

p_status_tmp, property_state_tmp, property_view, messages_tmp = building_file.process(organization_id, cycle)
p_status_tmp, property_state_tmp, property_view, messages_tmp = building_file.process(organization_id, cycle, access_level_instance=access_level_instance)

# append errors to overall messages
for i in messages_tmp['errors']:
Expand All @@ -143,7 +160,7 @@ def create(self, request):
file_type=file_type,
)

p_status, property_state, property_view, messages = building_file.process(organization_id, cycle)
p_status, property_state, property_view, messages = building_file.process(organization_id, cycle, access_level_instance=access_level_instance)

if p_status and property_state:
if len(messages['warnings']) > 0:
Expand Down
Loading