Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/pairing-hotfix' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
nllong committed May 31, 2019
2 parents 20fcba3 + 5ca006a commit 05c7a4b
Show file tree
Hide file tree
Showing 10 changed files with 263 additions and 53 deletions.
2 changes: 1 addition & 1 deletion seed/management/commands/add_member_to_org.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
:copyright (c) 2014 - 2018, 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
:copyright (c) 2014 - 2019, 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.management.base import BaseCommand
Expand Down
34 changes: 29 additions & 5 deletions seed/models/data_quality.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import pytz
from builtins import str
from django.apps import apps
from django.db import models
from django.db import models, IntegrityError
from django.utils.timezone import get_current_timezone, make_aware, make_naive
from past.builtins import basestring
from quantityfield import ureg
Expand All @@ -22,7 +22,9 @@
from seed.models import (
Column,
StatusLabel,
Property,
PropertyView,
TaxLot,
TaxLotView
)
from seed.models import obj_to_dict
Expand Down Expand Up @@ -922,12 +924,34 @@ def update_status_label(self, label_class, rule, linked_id):
"""

if rule.status_label_id is not None and linked_id is not None:
label_org_id = rule.status_label.super_organization_id

if rule.table_name == 'PropertyState':
label_class.objects.get_or_create(property_id=linked_id,
statuslabel_id=rule.status_label_id)
property_parent_org_id = Property.objects.get(pk=linked_id).organization.get_parent().id
if property_parent_org_id == label_org_id:
label_class.objects.get_or_create(property_id=linked_id,
statuslabel_id=rule.status_label_id)
else:
raise IntegrityError(
'Label with super_organization_id={} cannot be applied to a property with parent '
'organization_id={}.'.format(
label_org_id,
property_parent_org_id
)
)
else:
label_class.objects.get_or_create(taxlot_id=linked_id,
statuslabel_id=rule.status_label_id)
taxlot_parent_org_id = TaxLot.objects.get(pk=linked_id).organization.get_parent().id
if taxlot_parent_org_id == label_org_id:
label_class.objects.get_or_create(taxlot_id=linked_id,
statuslabel_id=rule.status_label_id)
else:
raise IntegrityError(
'Label with super_organization_id={} cannot be applied to a taxlot with parent '
'organization_id={}.'.format(
label_org_id,
taxlot_parent_org_id
)
)
return True

def remove_status_label(self, label_class, rule, linked_id):
Expand Down
11 changes: 9 additions & 2 deletions seed/models/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from django.contrib.gis.db import models as geomodels
from django.db import IntegrityError
from django.db import models
from django.db.models.signals import pre_delete, pre_save, post_save
from django.db.models.signals import pre_delete, pre_save, post_save, m2m_changed
from django.dispatch import receiver
from django.forms.models import model_to_dict
from quantityfield.fields import QuantityField
Expand All @@ -39,7 +39,11 @@
TaxLotProperty
)
from seed.utils.address import normalize_address_str
from seed.utils.generic import split_model_fields, obj_to_dict
from seed.utils.generic import (
compare_orgs_between_label_and_target,
split_model_fields,
obj_to_dict,
)
from seed.utils.time import convert_datestr
from seed.utils.time import convert_to_js_timestamp

Expand Down Expand Up @@ -801,3 +805,6 @@ def sync_latitude_longitude_and_long_lat(sender, instance, **kwargs):
elif (latitude_change or longitude_change) and not lat_and_long_both_populated:
instance.long_lat = None
instance.geocoding_confidence = None


m2m_changed.connect(compare_orgs_between_label_and_target, sender=Property.labels.through)
11 changes: 9 additions & 2 deletions seed/models/tax_lots.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from django.contrib.gis.db import models as geomodels
from django.contrib.postgres.fields import JSONField
from django.db import models
from django.db.models.signals import post_save
from django.db.models.signals import post_save, m2m_changed
from django.dispatch import receiver

from seed.data_importer.models import ImportFile
Expand All @@ -30,7 +30,11 @@
MERGE_STATE_UNKNOWN,
)
from seed.utils.address import normalize_address_str
from seed.utils.generic import split_model_fields, obj_to_dict
from seed.utils.generic import (
compare_orgs_between_label_and_target,
split_model_fields,
obj_to_dict,
)
from seed.utils.time import convert_to_js_timestamp
from .auditlog import AUDIT_IMPORT
from .auditlog import DATA_UPDATE_TYPE
Expand Down Expand Up @@ -480,3 +484,6 @@ class TaxLotAuditLog(models.Model):

class Meta:
index_together = [['state', 'name'], ['parent_state1', 'parent_state2']]


m2m_changed.connect(compare_orgs_between_label_and_target, sender=TaxLot.labels.through)
4 changes: 2 additions & 2 deletions seed/static/seed/js/seed.js
Original file line number Diff line number Diff line change
Expand Up @@ -560,10 +560,10 @@ SEED_app.config(['stateHelperProvider', '$urlRouterProvider', '$locationProvider
});
}],
propertyInventory: ['inventory_service', function (inventory_service) {
return inventory_service.get_properties(1, undefined, undefined, undefined);
return inventory_service.get_properties(1, undefined, undefined, -1);
}],
taxlotInventory: ['inventory_service', function (inventory_service) {
return inventory_service.get_taxlots(1, undefined, undefined, undefined);
return inventory_service.get_taxlots(1, undefined, undefined, -1);
}],
cycles: ['cycle_service', function (cycle_service) {
return cycle_service.get_cycles();
Expand Down
114 changes: 114 additions & 0 deletions seed/tests/test_labels.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# !/usr/bin/env python
# encoding: utf-8
"""
:copyright (c) 2014 - 2019, 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 'Piper Merriam <pipermerriam@gmail.com>', Paul Munday<paul@paulmunday.net>
Unit tests for seed/views/labels.py
"""
from django.db import IntegrityError
from django.db import transaction

from seed.landing.models import SEEDUser as User
from seed.models import (
Property,
StatusLabel as Label,
TaxLot,
)
from seed.models.data_quality import DataQualityCheck, Rule
from seed.tests.util import DeleteModelsTestCase
from seed.utils.organizations import create_organization
from seed.views.labels import (
UpdateInventoryLabelsAPIView,
)


class TestLabelIntegrityChecks(DeleteModelsTestCase):
def setUp(self):
self.api_view = UpdateInventoryLabelsAPIView()

# Models can't be imported directly hence self
self.PropertyLabels = self.api_view.models['property']
self.TaxlotLabels = self.api_view.models['taxlot']

self.user_details = {
'username': 'test_user@demo.com',
'password': 'test_pass',
'email': 'test_user@demo.com'
}
self.user = User.objects.create_superuser(**self.user_details)
self.org, _, _ = create_organization(self.user)

org_2, _, _ = create_organization(self.user)
self.org_2_status_label = Label.objects.create(
name='org_2_label', super_organization=org_2
)

def test_error_occurs_when_trying_to_apply_a_label_to_property_from_a_different_org(self):
org_1_property = Property.objects.create(organization=self.org)

# Via Label API View
with transaction.atomic():
with self.assertRaises(IntegrityError):
self.api_view.add_labels(
self.api_view.models['property'].objects.none(),
'property',
[org_1_property.id],
[self.org_2_status_label.id]
)

# Via Property Model
with transaction.atomic():
with self.assertRaises(IntegrityError):
org_1_property.labels.add(self.org_2_status_label)

# Via PropertyState Rule with Label
org_1_dq = DataQualityCheck.objects.get(organization=self.org)
org_1_ps_rule = org_1_dq.rules.filter(table_name='PropertyState').first()
# Purposely give an Org 1 Rule an Org 2 Label
org_1_ps_rule.status_label = self.org_2_status_label
org_1_ps_rule.save()

with transaction.atomic():
with self.assertRaises(IntegrityError):
org_1_dq.update_status_label(
self.PropertyLabels,
Rule.objects.get(pk=org_1_ps_rule.id),
org_1_property.id,
)

self.assertFalse(Property.objects.get(pk=org_1_property.id).labels.all().exists())

def test_error_occurs_when_trying_to_apply_a_label_to_taxlot_from_a_different_org(self):
# Repeat for TaxLot
org_1_taxlot = TaxLot.objects.create(organization=self.org)

with transaction.atomic():
with self.assertRaises(IntegrityError):
self.api_view.add_labels(
self.api_view.models['taxlot'].objects.none(),
'taxlot',
[org_1_taxlot.id],
[self.org_2_status_label.id]
)

with transaction.atomic():
with self.assertRaises(IntegrityError):
org_1_taxlot.labels.add(self.org_2_status_label)

# Via TaxLot Rule with Label
org_1_dq = DataQualityCheck.objects.get(organization=self.org)
org_1_tls_rule = org_1_dq.rules.filter(table_name='TaxLotState').first()
# Purposely give an Org 1 Rule an Org 2 Label
org_1_tls_rule.status_label = self.org_2_status_label
org_1_tls_rule.save()

with transaction.atomic():
with self.assertRaises(IntegrityError):
org_1_dq.update_status_label(
self.TaxlotLabels,
Rule.objects.get(pk=org_1_tls_rule.id),
org_1_taxlot.id,
)

self.assertFalse(TaxLot.objects.get(pk=org_1_taxlot.id).labels.all().exists())
70 changes: 49 additions & 21 deletions seed/tests/test_labels_api_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from seed.landing.models import SEEDUser as User
from seed.models import (
Property,
StatusLabel as Label,
)
from seed.test_helpers.fake import (
Expand Down Expand Up @@ -115,12 +116,7 @@ def setUp(self):
# Models can't be imported directly hence self
self.PropertyLabels = self.api_view.models['property']
self.TaxlotLabels = self.api_view.models['taxlot']
self.mock_property_queryset = mock_queryset_factory(
self.PropertyLabels,
flatten=True,
property_id=range(1, 11),
statuslabel_id=[1] * 3 + [2] * 3 + [3] * 2 + [4] * 2
)

self.user_details = {
'username': 'test_user@demo.com',
'password': 'test_pass',
Expand All @@ -133,6 +129,24 @@ def setUp(self):
)
self.client.login(**self.user_details)

self.label_1 = Label.objects.all()[0]
self.label_2 = Label.objects.all()[1]
self.label_3 = Label.objects.all()[2]
self.label_4 = Label.objects.all()[3]

# Create some real Properties and StatusLabels since validations happen
for i in range(1, 11):
Property.objects.create(organization=self.org)

self.property_ids = Property.objects.all().order_by('id').values_list('id', flat=True)

self.mock_property_label_qs = mock_queryset_factory(
self.PropertyLabels,
flatten=True,
property_id=self.property_ids,
statuslabel_id=[self.label_1.id] * 3 + [self.label_2.id] * 3 + [self.label_3.id] * 2 + [self.label_4.id] * 2
)

def test_get_label_desc(self):
add_label_ids = [self.status_label.id]
remove_label_ids = []
Expand All @@ -148,37 +162,47 @@ def test_get_label_desc(self):

def test_get_inventory_id(self):
result = self.api_view.get_inventory_id(
self.mock_property_queryset[0], 'property'
self.mock_property_label_qs[0], 'property'
)
self.assertEqual(result, 1)
self.assertEqual(result, self.property_ids[0])

def test_exclude(self):
result = self.api_view.exclude(
self.mock_property_queryset, 'property', [3, 4]
self.mock_property_label_qs, 'property', [self.label_3.id, self.label_4.id]
)
expected = {3: [7, 8], 4: [9, 10]}

pid_7 = self.property_ids[6]
pid_8 = self.property_ids[7]
pid_9 = self.property_ids[8]
pid_10 = self.property_ids[9]

expected = {self.label_3.id: [pid_7, pid_8], self.label_4.id: [pid_9, pid_10]}
self.assertEqual(result, expected)

def test_label_factory(self):
result = self.api_view.label_factory('property', 100, 100)
result = self.api_view.label_factory('property', self.label_1.id, self.property_ids[0])
self.assertEqual(
result.__class__.__name__, self.PropertyLabels.__name__
)
self.assertEqual(result.property_id, 100)
self.assertEqual(result.statuslabel_id, 100)
self.assertEqual(result.property_id, self.property_ids[0])
self.assertEqual(result.statuslabel_id, self.label_1.id)

def test_add_remove_labels(self):
pid_1 = self.property_ids[0]
pid_2 = self.property_ids[1]
pid_3 = self.property_ids[2]

result = self.api_view.add_labels(
self.mock_property_queryset, 'property',
[1, 2, 3], [5, 6]
self.mock_property_label_qs, 'property',
[pid_1, pid_2, pid_3], [self.label_2.id, self.label_3.id]
)
self.assertEqual(result, [1, 2, 3] * 2)
self.assertEqual(result, [pid_1, pid_2, pid_3] * 2)
qs = self.PropertyLabels.objects.all()
self.assertEqual(len(qs), 6)
self.assertEqual(qs[0].property_id, 1)
self.assertEqual(qs[0].statuslabel_id, 5)
self.assertEqual(qs[0].property_id, pid_1)
self.assertEqual(qs[0].statuslabel_id, self.label_2.id)

result = self.api_view.remove_labels(qs, 'property', [5, 6])
result = self.api_view.remove_labels(qs, 'property', [self.label_2.id, self.label_3.id])
qs = self.PropertyLabels.objects.all()
self.assertEqual(len(qs), 0)

Expand All @@ -193,10 +217,14 @@ def test_put(self):
r, self.org.id
)

pid_1 = self.property_ids[0]
pid_2 = self.property_ids[1]
pid_3 = self.property_ids[2]

post_params = {
'add_label_ids': [self.status_label.id],
'remove_label_ids': [],
'inventory_ids': [1, 2, 3],
'inventory_ids': [pid_1, pid_2, pid_3],
}
response = client.put(
url, post_params, format='json'
Expand All @@ -215,7 +243,7 @@ def test_put(self):
post_params = {
'add_label_ids': [],
'remove_label_ids': [self.status_label.id],
'inventory_ids': [1, 2, 3],
'inventory_ids': [pid_1, pid_2, pid_3],
}
response = client.put(
url, post_params, format='json'
Expand Down

0 comments on commit 05c7a4b

Please sign in to comment.