def test_record_is_created_when_nullables_are_still_pending(self): new_plot = Plot(geom=self.p1, instance=self.instance) new_plot.save_with_user(self.pending_user) new_tree = Tree(plot=new_plot, instance=self.instance, diameter=10, height=10, readonly=False) new_tree.save_with_user(self.pending_user) approve_or_reject_audits_and_apply( new_plot.audits(), self.commander_user, True) insert_audit = Audit.objects.filter(model='Tree')\ .get(field='id') field_audits = Audit.objects.filter(model='Tree')\ .filter(field__in=['readonly', 'diameter', 'plot']) for audit in field_audits: approve_or_reject_audit_and_apply( audit, self.commander_user, approved=True) approve_or_reject_audit_and_apply(insert_audit, self.commander_user, True) real_tree = Tree.objects.get(pk=new_tree.pk) self.assertEqual(real_tree.plot_id, new_plot.pk) self.assertEqual(real_tree.diameter, 10) self.assertEqual(real_tree.height, None) self.assertNotEqual(real_tree.readonly, True)
class UserCanDeleteTestCase(OTMTestCase): def setUp(self): instance = make_instance() self.creator_user = make_officer_user(instance) self.admin_user = make_admin_user(instance) self.other_user = make_officer_user(instance, username='******') self.plot = Plot(geom=instance.center, instance=instance) self.plot.save_with_user(self.creator_user) self.tree = Tree(plot=self.plot, instance=instance) self.tree.save_with_user(self.creator_user) self.rainBarrel = RainBarrel(geom=instance.center, instance=instance, capacity=5) self.rainBarrel.save_with_user(self.creator_user) def assert_can_delete(self, user, deletable, should_be_able_to_delete): can = deletable.user_can_delete(user) self.assertEqual(can, should_be_able_to_delete) def test_user_can_delete(self): self.assert_can_delete(self.creator_user, self.plot, True) self.assert_can_delete(self.admin_user, self.plot, True) self.assert_can_delete(self.other_user, self.plot, False) self.assert_can_delete(self.creator_user, self.rainBarrel, True) self.assert_can_delete(self.admin_user, self.rainBarrel, True) self.assert_can_delete(self.other_user, self.rainBarrel, False) self.assert_can_delete(self.creator_user, self.tree, True) self.assert_can_delete(self.admin_user, self.tree, True) self.assert_can_delete(self.other_user, self.tree, False)
def _commit_tree_data(self, data, plot, tree, tree_edited): for tree_attr, field_name in TreeImportRow.TREE_MAP.iteritems(): value = data.get(field_name, None) if value: tree_edited = True if tree is None: tree = Tree(instance=plot.instance) setattr(tree, tree_attr, value) ie = self.import_event tree_udf_defs = udf_defs(ie.instance, 'Tree') for udf_def in tree_udf_defs: udf_column_name = ie.get_udf_column_name(udf_def) value = data.get(udf_column_name, None) # Legitimate values could be falsey if value is not None: tree_edited = True if tree is None: tree = Tree(instance=plot.instance) tree.udfs[udf_def.name] = \ self._import_value_to_udf_value(udf_def, value) if tree_edited: tree.plot = plot tree.save_with_system_user_bypass_auth() tree.plot.update_updated_fields(ie.owner)
def handle(self, *args, **options): """ Create some seed data """ instance, user = self.setup_env(*args, **options) species_qs = instance.scope_model(Species) n = options['n'] self.stdout.write("Will create %s plots" % n) get_prob = lambda option: float(min(100, max(0, option))) / 100.0 tree_prob = get_prob(options['ptree']) species_prob = get_prob(options['pspecies']) diameter_prob = get_prob(options['pdiameter']) max_radius = options['radius'] center_x = instance.center.x center_y = instance.center.y import_event = ImportEvent(imported_by=user) import_event.save() ct = 0 cp = 0 for i in xrange(0, n): mktree = random.random() < tree_prob radius = random.gauss(0.0, max_radius) theta = random.random() * 2.0 * math.pi x = math.cos(theta) * radius + center_x y = math.sin(theta) * radius + center_y plot = Plot(instance=instance, geom=Point(x, y), import_event=import_event) plot.save_with_user(user) cp += 1 if mktree: add_species = random.random() < species_prob if add_species: species = random.choice(species_qs) else: species = None add_diameter = random.random() < diameter_prob if add_diameter: diameter = 2 + random.random() * 18 else: diameter = None tree = Tree(plot=plot, import_event=import_event, species=species, diameter=diameter, instance=instance) tree.save_with_user(user) ct += 1 self.stdout.write("Created %s trees and %s plots" % (ct, cp))
def test_treephoto_overrides_tree_and_plot(self): tree = Tree(diameter=10, plot=self.plot, instance=self.instance) tree.save_with_user(self.user) tree.add_photo(self.image, self.other) self.clear_and_set_and_reload() self.assertEqual(self.plot.updated_by_id, self.other.pk)
def test_delete_tree(self): plot = Plot(instance=self.instance, geom=Point(0, 0)) plot.save_with_user(self.user) tree = Tree(instance=self.instance, plot=plot) tree.save_with_user(self.user) self.assertEqual(Plot.objects.count(), 1) self.assertEqual(Tree.objects.count(), 1) self._login_workflow() self._go_to_plot_detail(plot.pk) self.select_buttons() self.assertCantClickDeleteOrCancel() self.assertEqual(Plot.objects.count(), 1) self.assertEqual(Tree.objects.count(), 1) self.delete_begin.click() self.delete_cancel.click() self.assertCantClickDeleteOrCancel() self.assertEqual(Plot.objects.count(), 1) self.assertEqual(Tree.objects.count(), 1) self.select_buttons() self.delete_begin.click() self.delete_confirm.click() sleep(DATABASE_COMMIT_DELAY) self.assertEqual(Plot.objects.count(), 1) self.assertEqual(Tree.objects.count(), 0)
def _plot_audits(user, instance, plot): fake_tree = Tree(instance=instance) tree_visible_fields = fake_tree.visible_fields(user) # Get a history of trees that were on this plot tree_history = plot.get_tree_history() tree_filter = Q(model='Tree', field__in=tree_visible_fields, model_id__in=tree_history) tree_delete_filter = Q(model='Tree', action=Audit.Type.Delete, model_id__in=tree_history) tree_collection_udfs_audit_names =\ fake_tree.visible_collection_udfs_audit_names(user) tree_collection_udfs_filter = Q( model__in=tree_collection_udfs_audit_names, model_id__in=Tree.static_collection_udfs_audit_ids( (instance,), tree_history, tree_collection_udfs_audit_names)) filters = [tree_filter, tree_delete_filter] cudf_filters = [tree_collection_udfs_filter] audits = _map_feature_audits(user, instance, plot, filters, cudf_filters) return audits
def test_treephoto_hash_to_model(self): plot = Plot(geom=Point(0, 0), instance=self.instance) plot.save_with_user(self.commander) tree = Tree(plot=plot, instance=self.instance) tree.save_with_user(self.commander) ipath = self.resource_path('tree1.gif') tp_dict = json.loads(self.photo_blob % ipath) self.assertEqual(TreePhoto.objects.count(), 0) save_treephoto_blank = partial(save_treephoto, MIGRATION_RULES, '') hashes_to_saved_objects( MIGRATION_RULES, "treephoto", [tp_dict], {'tree': {1: tree.pk}, 'user': {1: self.commander.pk}}, save_treephoto_blank, self.instance) self.assertEqual(TreePhoto.objects.count(), 1) photo = TreePhoto.objects.all()[0] self.assertIsNotNone(photo.image) self.assertIsNotNone(photo.thumbnail)
def setUp(self): super(ExportTreeTaskTest, self).setUp() set_write_permissions(self.instance, self.user, 'Plot', ['udf:Test choice']) set_write_permissions(self.instance, self.user, 'Tree', ['udf:Test int']) UserDefinedFieldDefinition.objects.create( instance=self.instance, model_type='Plot', datatype=json.dumps({'type': 'choice', 'choices': ['a', 'b', 'c']}), iscollection=False, name='Test choice') UserDefinedFieldDefinition.objects.create( instance=self.instance, model_type='Tree', datatype=json.dumps({'type': 'int'}), iscollection=False, name='Test int') p = Plot(geom=self.instance.center, instance=self.instance, address_street="123 Main Street") p.udfs['Test choice'] = 'a' p.save_with_user(self.user) t = Tree(plot=p, instance=self.instance, diameter=2) t.udfs['Test int'] = 4 t.save_with_user(self.user)
def add_tree_photo_helper(request, instance, feature_id, tree_id=None): plot = get_map_feature_or_404(feature_id, instance, 'Plot') tree_ids = [t.pk for t in plot.tree_set.all()] if tree_id and int(tree_id) in tree_ids: tree = Tree.objects.get(pk=tree_id) elif tree_id is None: # See if a tree already exists on this plot tree = plot.current_tree() if tree is None: # A tree doesn't exist, create a new tree create a # new tree, and attach it to this plot tree = Tree(plot=plot, instance=instance) # TODO: it is possible that a user has the ability to # 'create tree photos' but not trees. In this case we # raise an authorization exception here. # It is, however, possible to have both a pending # tree and a pending tree photo # This will be added later, when auth/admin work # correctly with this system tree.save_with_user(request.user) else: # Tree id is invalid or not in this plot raise Http404('Tree id %s not found on plot %s' % (tree_id, feature_id)) #TODO: Auth Error data = get_image_from_request(request) treephoto = tree.add_photo(data, request.user) return treephoto, tree
class WritableTest(PermissionsTestCase): def test_plot_is_writable_if_can_create_tree(self): self.commander_user = make_commander_user(self.instance) self.commander_role = \ self.commander_user.get_instance_user(self.instance).role self.tree_only_user = make_user(self.instance) self.tree_only_role = self.instance.default_role content_type = ContentType.objects.get_for_model(Tree) add_tree_perm = Permission.objects.get(content_type=content_type, codename='add_tree') self.tree_only_role.instance_permissions.add(add_tree_perm) self.tree_only_role.save() self.p = Point(-8515941.0, 4953519.0) self.plot = Plot(instance=self.instance, width=12, geom=self.p) self.plot.save_with_user(self.commander_user) plot2 = Plot(instance=self.instance, width=12, geom=self.p) self.assertRaises(AuthorizeException, plot2.save_with_user, self.tree_only_user) self.tree = Tree(instance=self.instance, plot=self.plot) self.tree.save_with_user(self.tree_only_user) self.assertTrue(self.tree.user_can_create(self.tree_only_user)) # The plot should be writable if the user can create a tree self.assertTrue(perms.map_feature_is_writable( self.tree_only_role, self.plot))
def setUp(self): super(ExportTreeTaskTest, self).setUp() set_write_permissions(self.instance, self.user, "Plot", ["udf:Test choice"]) set_write_permissions(self.instance, self.user, "Tree", ["udf:Test int"]) UserDefinedFieldDefinition.objects.create( instance=self.instance, model_type="Plot", datatype=json.dumps({"type": "choice", "choices": ["a", "b", "c"]}), iscollection=False, name="Test choice", ) UserDefinedFieldDefinition.objects.create( instance=self.instance, model_type="Tree", datatype=json.dumps({"type": "int"}), iscollection=False, name="Test int", ) p = Plot(geom=self.instance.center, instance=self.instance, address_street="123 Main Street") p.udfs["Test choice"] = "a" p.save_with_user(self.user) t = Tree(plot=p, instance=self.instance, diameter=2) t.udfs["Test int"] = 4 t.save_with_user(self.user)
def make_tree(self, user=None): user = user or make_commander_user(self.instance) plot = Plot(geom=self.instance.center, instance=self.instance) plot.save_with_user(user) tree = Tree(instance=self.instance, plot=plot) tree.save_with_user(user) return tree
def setUp(self): super(ExportTreeTaskTest, self).setUp() p = Plot(geom=Point(0, 0), instance=self.instance, address_street="123 Main Street") p.save_with_user(self.user) t = Tree(plot=p, instance=self.instance, diameter=2) t.save_with_user(self.user)
def test_reputations_increase_for_direct_writes(self): self.assertEqual(self.privileged_user.get_reputation(self.instance), 0) t = Tree(plot=self.plot, instance=self.instance, readonly=True) t.save_with_user(self.privileged_user) user = User.objects.get(pk=self.privileged_user.id) reputation = user.get_reputation(self.instance) self.assertGreater(reputation, 0)
def test_delete_tree_sets_updated(self): tree = Tree(diameter=10, plot=self.plot, instance=self.instance) tree.save_with_user(self.user) self.plot = Plot.objects.get(pk=self.plot.pk) self.inital_updated = self.plot.updated_at tree.delete_with_user(self.user) self.assertGreater(self.plot.updated_at, self.initial_updated)
def test_update_species(self): with self._assert_updates_eco_rev(True): tree = Tree(instance=self.instance, plot=self.plot) tree.save_with_user(self.user) species = Species(common_name='foo', instance=self.instance) species.save_with_user(self.user) request_dict = {'tree.species': species.pk} update_map_feature(request_dict, self.user, self.plot)
class EcoTest(TestCase): def setUp(self): self.factory = RequestFactory() self.instance, system_user = tm.make_instance_and_system_user() self.user = User(username="******") self.user.save_with_user(system_user) self.user.roles.add(tm.make_commander_role(self.instance)) self.species = Species(symbol='CEDR', genus='cedrus', species='atlantica', max_dbh=2000, max_height=100) self.species.save() p1 = Point(-8515941.0, 4953519.0) self.plot = Plot(geom=p1, instance=self.instance, created_by=self.user) self.plot.save_with_user(self.user) self.tree = Tree(plot=self.plot, instance=self.instance, readonly=False, species=self.species, diameter=1630, created_by=self.user) self.tree.save_with_user(self.user) def test_group_eco(self): pass # TODO: Once filtering has been enabled def test_eco_benefit_sanity(self): req = self.factory.get('/%s/eco/benefit/tree/%s/' % (self.instance.pk, self.tree.pk)) response = tree_benefits(req, instance_id=self.instance.pk, tree_id=self.tree.pk, region='NoEastXXX') self.assertEqual(response.status_code, 200) rslt = json.loads(response.content) bens = rslt['benefits'] def assertBValue(benefit, unit, value): self.assertEqual(bens[benefit]['unit'], unit) self.assertEqual(int(float(bens[benefit]['value'])), value) assertBValue('energy', 'kwh', 1896) assertBValue('airquality', 'lbs/year', 6) assertBValue('stormwater', 'gal', 3185) assertBValue('co2', 'lbs/year', 563)
def test_save_new_object_authorized(self): '''Save two new objects with authorized user, nothing should happen''' plot = Plot(geom=self.p1, instance=self.instance) plot.save_with_user(self.officer) tree = Tree(plot=plot, instance=self.instance) tree.save_with_user(self.officer)
class UserCanDeleteTestCase(OTMTestCase): def setUp(self): instance = make_instance() # Fancy name, but no write, create, or delete permissions instance.default_role.name = Role.ADMINISTRATOR self.creator_user = make_officer_user(instance) self.admin_user = make_admin_user(instance) self.other_user = make_observer_user(instance, username='******') self.tweaker_user = make_tweaker_user(instance) self.conjurer_user = make_conjurer_user(instance) self.plot = Plot(geom=instance.center, instance=instance) self.plot.save_with_user(self.creator_user) self.tree = Tree(plot=self.plot, instance=instance) self.tree.save_with_user(self.creator_user) self.rainBarrel = RainBarrel(geom=instance.center, instance=instance, capacity=5) self.rainBarrel.save_with_user(self.creator_user) def assert_can_delete(self, user, deletable, should_be_able_to_delete): can = deletable.user_can_delete(user) self.assertEqual(can, should_be_able_to_delete) def test_user_can_delete(self): self.assert_can_delete(self.conjurer_user, self.plot, True) self.assert_can_delete(self.conjurer_user, self.rainBarrel, True) self.assert_can_delete(self.conjurer_user, self.tree, True) self.assert_can_delete(self.creator_user, self.plot, True) self.assert_can_delete(self.creator_user, self.rainBarrel, True) self.assert_can_delete(self.creator_user, self.tree, True) self.assert_can_delete(self.admin_user, self.plot, True) self.assert_can_delete(self.admin_user, self.rainBarrel, True) self.assert_can_delete(self.admin_user, self.tree, True) def test_user_cannot_delete(self): self.assert_can_delete(self.tweaker_user, self.plot, False) self.assert_can_delete(self.tweaker_user, self.rainBarrel, False) self.assert_can_delete(self.tweaker_user, self.tree, False) self.assert_can_delete(self.other_user, self.plot, False) self.assert_can_delete(self.other_user, self.rainBarrel, False) self.assert_can_delete(self.other_user, self.tree, False) def test_admin_cannot_delete_by_flag(self): instance = self.tree.get_instance() role = self.admin_user.get_role(instance) role.instance_permissions.clear() self.assertTrue(self.admin_user.get_instance_user(instance).admin) self.assertEqual(role.instance_permissions.count(), 0) self.assert_can_delete(self.admin_user, self.tree, False)
def _test_hash_setup(self): self.initial_plot_hash = self.plot_obj.hash self.initial_map_feature_hash = self.map_feature_obj.hash # adding a tree should change the plot hash tree = Tree(diameter=10, plot=self.plot_obj, instance=self.instance) tree.save_with_user(self.user) self.final_plot_hash = self.plot_obj.hash self.final_map_feature_hash = self.map_feature_obj.hash
def test_delete_tree_sets_updated(self): tree = Tree(diameter=10, plot=self.plot, instance=self.instance) tree.save_with_user(self.user) self.plot.refresh_from_db() self.inital_updated = self.plot.updated_at tree.delete_with_user(self.fellow) self.plot.refresh_from_db() self.assertGreater(self.plot.updated_at, self.initial_updated) self.assertEqual(self.plot.updated_by, self.fellow)
def test_add_photo_sets_updated(self): tree = Tree(diameter=10, plot=self.plot, instance=self.instance) tree.save_with_user(self.user) photo = TreePhoto(instance=self.instance, map_feature=self.plot, tree=tree) photo.set_image(self.load_resource('tree1.gif')) photo.save_with_user(self.fellow) self.plot.refresh_from_db() self.assertGreater(self.plot.updated_at, self.initial_updated) self.assertEqual(self.plot.updated_by, self.fellow)
def _plot_audits(user, instance, plot): readable_plot_fields = plot.visible_fields(user) plot_filter = Q(model='Plot', model_id=plot.pk, field__in=readable_plot_fields) plot_collection_udfs_filter = Q( model__in=plot.visible_collection_udfs_audit_names(user), model_id__in=plot.collection_udfs_audit_ids()) fake_tree = Tree(instance=instance) tree_visible_fields = fake_tree.visible_fields(user) # Get a history of trees that were on this plot tree_history = plot.get_tree_history() tree_filter = Q(model='Tree', field__in=tree_visible_fields, model_id__in=tree_history) tree_delete_filter = Q(model='Tree', action=Audit.Type.Delete, model_id__in=tree_history) tree_collection_udfs_audit_names =\ fake_tree.visible_collection_udfs_audit_names(user) tree_collection_udfs_filter = Q( model__in=tree_collection_udfs_audit_names, model_id__in=Tree.static_collection_udfs_audit_ids( (instance,), tree_history, tree_collection_udfs_audit_names)) # Seems to be much faster to do three smaller # queries here instead of ORing them together # (about a 50% inprovement!) # TODO: Verify this is still the case now that we are also getting # collection udf audits iaudit = Audit.objects.filter(instance=instance) audits = [] for afilter in [tree_filter, tree_delete_filter, plot_filter]: audits += list(iaudit.filter(afilter).order_by('-updated')[:5]) # UDF collection audits have some fields which aren't very useful to show udf_collection_exclude_filter = Q( field__in=['model_id', 'field_definition']) for afilter in [plot_collection_udfs_filter, tree_collection_udfs_filter]: audits += list(iaudit.filter(afilter) .exclude(udf_collection_exclude_filter) .order_by('-updated')[:5]) audits = sorted(audits, key=lambda audit: audit.updated, reverse=True)[:5] return audits
def mkTree(u, plot=None, species=None): if not plot: plot = mkPlot(i) if not species: species = Species.objects.all()[0] t = Tree(plot=plot, species=species, last_updated_by=u, import_event=ImportEvent.objects.all()[0]) t.save() return t
def test_tree_overrides_plot(self): tree = Tree(diameter=10, plot=self.plot, instance=self.instance) tree.save_with_user(self.user) tree_audit = self.max_audit_for_model_type('Tree') plot_audit = self.max_audit_for_model_type('Plot') # Backdate the plot audit so it is definitely older than the tree audit plot_audit.created = tree_audit.created - timedelta(days=1) plot_audit.save() self.clear_and_set_and_reload() self.assertEqual(self.plot.updated_at, tree_audit.created)
def test_plot_with_tree_always_shows_tree_details(self): plot = Plot(instance=self.instance, geom=self.instance.center) plot.save_with_user(self.user) tree = Tree(plot=plot, diameter=10, instance=self.instance) tree.save_with_user(self.user) self.login_workflow() self.go_to_feature_detail(plot.pk) self._select_elements() self.edit_plot.click() self.cancel_edit.click() self.assertElementVisibility(self.tree_details_section, visible=True)
def test_user_can_create_tree_photo(self): self._add_builtin_permission(self.role_yes, TreePhoto, 'add_treephoto') commander = make_commander_user(self.instance) plot = Plot(instance=self.instance, geom=self.p) plot.save_with_user(commander) tree = Tree(plot=plot, instance=self.instance) tree.save_with_user(commander) user_yes = make_user(instance=self.instance, make_role=lambda inst: self.role_yes) photo = TreePhoto(instance=self.instance, map_feature=plot, tree=tree) photo.set_image(self.load_resource('tree1.gif')) self.assertTrue(photo.user_can_create(user_yes))
def mkTree(instance, user, plot=None, species=None): if not plot: plot = mkPlot(instance, user) if species is not None: s = Species.objects.all()[0] else: s = species t = Tree(plot=plot, instance=instance, species=s) t.save_with_user(user) return t
def test_user_cannot_delete_tree_photo(self): commander = make_commander_user(self.instance) plot = Plot(instance=self.instance, geom=self.p) plot.save_with_user(commander) tree = Tree(plot=plot, instance=self.instance) tree.save_with_user(commander) image = self.load_resource('tree1.gif') photo = tree.add_photo(image, commander) user_no = make_user(instance=self.instance, make_role=lambda inst: self.role_no) self.assertFalse(photo.user_can_delete(user_no))
class EcoTest(UrlTestCase): def setUp(self): # Example url for # CEAT, 1630 dbh, NoEastXXX # eco.json?otmcode=CEAT&diameter=1630®ion=NoEastXXX def mockbenefits(*args, **kwargs): benefits = { "Benefits": { "aq_nox_avoided": 0.6792, "aq_nox_dep": 0.371, "aq_ozone_dep": 0.775, "aq_pm10_avoided": 0.0436, "aq_pm10_dep": 0.491, "aq_sox_avoided": 0.372, "aq_sox_dep": 0.21, "aq_voc_avoided": 0.0254, "bvoc": -0.077, "co2_avoided": 255.5, "co2_sequestered": 0, "co2_storage": 6575, "electricity": 187, "hydro_interception": 12.06, "natural_gas": 5834.1 } } return (benefits, None) region = ITreeRegion.objects.get(code='NoEastXXX') p = region.geometry.point_on_surface self.instance = make_instance(is_public=True, point=p) self.user = make_commander_user(self.instance) self.species = Species(otm_code='CEAT', genus='cedrus', species='atlantica', max_diameter=2000, max_height=100, instance=self.instance) self.species.save_with_user(self.user) self.plot = Plot(geom=p, instance=self.instance) self.plot.save_with_user(self.user) self.tree = Tree(plot=self.plot, instance=self.instance, readonly=False, species=self.species, diameter=1630) self.tree.save_with_user(self.user) self.origBenefitFn = ecobackend.json_benefits_call ecobackend.json_benefits_call = mockbenefits def tearDown(self): ecobackend.json_benefits_call = self.origBenefitFn def assert_benefit_value(self, bens, benefit, unit, value): self.assertEqual(bens[benefit]['unit'], unit) self.assertEqual(int(float(bens[benefit]['value'])), value) def test_eco_benefit_sanity(self): rslt, basis, error = TreeBenefitsCalculator()\ .benefits_for_object(self.instance, self.tree.plot) bens = rslt['plot'] self.assert_benefit_value(bens, BenefitCategory.ENERGY, 'kwh', 1896) self.assert_benefit_value(bens, BenefitCategory.AIRQUALITY, 'lbs', 6) self.assert_benefit_value(bens, BenefitCategory.STORMWATER, 'gal', 3185) self.assert_benefit_value(bens, BenefitCategory.CO2, 'lbs', 563) self.assert_benefit_value(bens, BenefitCategory.CO2STORAGE, 'lbs', 6575) def testSearchBenefits(self): request = make_request( {'q': json.dumps({'tree.readonly': { 'IS': False }})}) # all trees request.instance_supports_ecobenefits = self.instance\ .has_itree_region() result = search_tree_benefits(request, self.instance) benefits = result['benefits'] self.assertTrue(len(benefits) > 0) def test_group_basis_empty(self): basis = {} example = { 'group1': { 'n_objects_used': 5, 'n_objects_discarded': 8 }, 'group2': { 'n_objects_used': 10, 'n_objects_discarded': 12 } } _combine_benefit_basis(basis, example) self.assertEqual(basis, example) def test_group_basis_combine_new_group(self): # New groups are added basis = {'group1': {'n_objects_used': 5, 'n_objects_discarded': 8}} new_group = { 'group2': { 'n_objects_used': 13, 'n_objects_discarded': 4 } } target = { 'group1': { 'n_objects_used': 5, 'n_objects_discarded': 8 }, 'group2': { 'n_objects_used': 13, 'n_objects_discarded': 4 } } _combine_benefit_basis(basis, new_group) self.assertEqual(basis, target) def test_group_basis_combine_existing_groups(self): basis = {'group1': {'n_objects_used': 5, 'n_objects_discarded': 8}} update_group = { 'group1': { 'n_objects_used': 13, 'n_objects_discarded': 4 } } target = {'group1': {'n_objects_used': 18, 'n_objects_discarded': 12}} _combine_benefit_basis(basis, update_group) self.assertEqual(basis, target) def test_combine_benefit_groups_empty(self): # with and without currency base_group = { 'group1': { 'benefit1': { 'value': 3, 'currency': 9, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' }, 'benefit2': { 'value': 3, 'currency': 9, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' } } } groups = {} _combine_grouped_benefits(groups, base_group) self.assertEqual(groups, base_group) def test_combine_benefit_groups_no_overlap(self): base_group = { 'group1': { 'benefit1': { 'value': 3, 'currency': 9, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' }, 'benefit2': { 'value': 4, 'currency': 10, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' } } } new_group = { 'group2': { 'benefit1': { 'value': 5, 'currency': 11, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' }, 'benefit2': { 'value': 6, 'currency': 19, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' } } } groups = {} _combine_grouped_benefits(groups, base_group) _combine_grouped_benefits(groups, new_group) target = { 'group1': base_group['group1'], 'group2': new_group['group2'] } self.assertEqual(groups, target) def test_combine_benefit_groups_sums_benefits(self): base_group = { 'group1': { 'benefit1': { 'value': 3, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' }, 'benefit2': { 'value': 4, 'currency': 10, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' }, 'benefit3': { 'value': 32, 'currency': 919, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' } } } new_group = { 'group1': { 'benefit1': { 'value': 5, 'currency': 11, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' }, 'benefit2': { 'value': 7, 'unit': 'gal', 'currency': 19, 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' }, 'benefit4': { 'value': 7, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' } } } groups = {} _combine_grouped_benefits(groups, base_group) _combine_grouped_benefits(groups, new_group) target = { 'group1': { 'benefit1': { 'value': 8, 'currency': 11, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' }, 'benefit2': { 'value': 11, 'currency': 29, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' }, 'benefit3': { 'value': 32, 'currency': 919, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' }, 'benefit4': { 'value': 7, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' } } } self.assertEqual(groups, target) def test_annotates_basis(self): basis = { 'group1': { 'n_objects_used': 5, 'n_objects_discarded': 15 }, 'group2': { 'n_objects_used': 2, 'n_objects_discarded': 18 } } target = { 'group1': { 'n_objects_used': 5, 'n_objects_discarded': 15, 'n_total': 20, 'n_pct_calculated': 0.25 }, 'group2': { 'n_objects_used': 2, 'n_objects_discarded': 18, 'n_total': 20, 'n_pct_calculated': 0.1 } } _annotate_basis_with_extra_stats(basis) self.assertEqual(basis, target)
class FilterParserTests(OTMTestCase): def _setup_tree_and_collection_udf(self): instance = make_instance() self.plotstew = make_collection_udf(instance, model='Plot', datatype=COLLECTION_UDF_DATATYPE) self.treestew = make_collection_udf(instance, model='Tree', datatype=COLLECTION_UDF_DATATYPE) commander = make_commander_user(instance) set_write_permissions(instance, commander, 'Plot', ['udf:Stewardship']) set_write_permissions(instance, commander, 'Tree', ['udf:Stewardship']) d1 = {'action': 'prune', 'date': "2014-05-3 00:00:00"} d2 = {'action': 'water', 'date': "2014-04-29 00:00:00"} self.plot = Plot(instance=instance, geom=instance.center) self.plot.udfs[self.plotstew.name] = [d1] self.plot.save_with_user(commander) self.tree = Tree(instance=instance, plot=self.plot) self.tree.udfs[self.treestew.name] = [d2] self.tree.save_with_user(commander) def destructure_query_set(self, node): """ Django query objects are not comparable by themselves, but they are built from a tree (django.util.tree) and stored in nodes This function generates a canonical representation using sets and tuples of a query tree This can be used to verify that query structures are made correctly """ if isinstance(node, Node): n = (node.connector, frozenset( {self.destructure_query_set(c) for c in node.children})) if node.negated: n = ('NOT', n) return n elif isinstance(node, tuple): # Lists are unhashable, so convert ValuesListQuerySets into tuples # for easy comparison return tuple( tuple(c) if isinstance(c, ValuesListQuerySet) else c for c in node) else: return node def test_key_parser_plots(self): # Plots searches on plot go directly to a field match = search._parse_predicate_key('plot.width', mapping=search.DEFAULT_MAPPING) self.assertEqual(match, ('plot', 'width')) def test_key_parser_plots_with_tree_map(self): # Plots searches on tree go require a prefix match = search._parse_predicate_key('plot.width', mapping=search.TREE_MAPPING) self.assertEqual(match, ('plot', 'plot__width')) def test_udf_fields_look_good(self): match = search._parse_predicate_key('plot.udf:The 1st Planter', mapping=search.DEFAULT_MAPPING) self.assertEqual(match, ('plot', 'udf:The 1st Planter')) def test_key_parser_trees(self): # Tree searches on plot require a prefix and the field match = search._parse_predicate_key('tree.dbh', mapping=search.DEFAULT_MAPPING) self.assertEqual(match, ('tree', 'tree__dbh')) def test_key_parser_trees_with_tree_map(self): # Tree searches on tree go directly to the field match = search._parse_predicate_key('tree.dbh', mapping=search.TREE_MAPPING) self.assertEqual(match, ('tree', 'dbh')) def test_key_parser_tree_collection_udf(self): # UDF searches go on the specified model's id match = search._parse_predicate_key('udf:tree:52.action', mapping=search.TREE_MAPPING) self.assertEqual(match, ('udf:tree:52', 'id')) def test_key_parser_plot_collection_udf(self): # UDF searches go on the specified model's id match = search._parse_predicate_key('udf:plot:52.action', mapping=search.TREE_MAPPING) self.assertEqual(match, ('udf:plot:52', 'plot__id')) def test_key_parser_invalid_model(self): # Invalid models should raise an exception self.assertRaises(search.ParseException, search._parse_predicate_key, "user.id", mapping=search.DEFAULT_MAPPING) def test_key_parser_too_many_dots(self): # Dotted fields are also not allowed self.assertRaises(search.ParseException, search._parse_predicate_key, "plot.width.other", mapping=search.DEFAULT_MAPPING) def test_combinator_and(self): qa = Q(a=1) qb = Q(b=1) qc = Q(c=1) # Simple AND ands = search._apply_combinator('AND', [qa, qb, qc]) self.assertEqual(self.destructure_query_set(ands), self.destructure_query_set(qa & qb & qc)) def test_combinator_or(self): qa = Q(a=1) qb = Q(b=1) qc = Q(c=1) # Simple OR ands = search._apply_combinator('OR', [qa, qb, qc]) self.assertEqual(self.destructure_query_set(ands), self.destructure_query_set(qa | qb | qc)) def test_combinator_invalid_combinator(self): qa = Q(a=1) qb = Q(b=1) qc = Q(c=1) # Error if not AND,OR self.assertRaises(search.ParseException, search._apply_combinator, 'ANDarg', [qa, qb]) self.assertRaises(search.ParseException, search._apply_combinator, qc, [qa, qb]) def test_combinator_invalid_empty(self): # Error if empty self.assertRaises(search.ParseException, search._apply_combinator, 'AND', []) def test_boundary_constraint(self): b = Boundary.objects.create(geom=MultiPolygon(make_simple_polygon(0)), name='whatever', category='whatever', sort_order=1) inparams = search._parse_dict_value({'IN_BOUNDARY': b.pk}) self.assertEqual(inparams, {'__within': b.geom}) def test_constraints_in(self): inparams = search._parse_dict_value({'IN': [1, 2, 3]}) self.assertEqual(inparams, {'__in': [1, 2, 3]}) def test_constraints_isnull(self): inparams = search._parse_dict_value({'ISNULL': True}) self.assertEqual(inparams, {'__isnull': True}) def test_constraints_is(self): # "IS" is a special case in that we don't need to appl # a suffix at all isparams = search._parse_dict_value({'IS': 'what'}) self.assertEqual(isparams, {'': 'what'}) def test_constraints_invalid_groups(self): # It is an error to combine mutually exclusive groups self.assertRaises(search.ParseException, search._parse_dict_value, { 'IS': 'what', 'IN': [1, 2, 3] }) self.assertRaises(search.ParseException, search._parse_dict_value, { 'IS': 'what', 'MIN': 3 }) def test_constraints_invalid_keys(self): self.assertRaises(search.ParseException, search._parse_dict_value, {'EXCLUSIVE': 9}) self.assertRaises(search.ParseException, search._parse_dict_value, {'IS NOT VALID KEY': 'what'}) def test_contraint_min(self): const = search._parse_dict_value({'MIN': 5}) self.assertEqual(const, {'__gte': 5}) def test_contraint_max(self): const = search._parse_dict_value({'MAX': 5}) self.assertEqual(const, {'__lte': 5}) def test_contraint_max_with_exclusive(self): const = search._parse_dict_value( {'MAX': { 'VALUE': 5, 'EXCLUSIVE': True }}) self.assertEqual(const, {'__lt': 5}) const = search._parse_dict_value( {'MAX': { 'VALUE': 5, 'EXCLUSIVE': False }}) self.assertEqual(const, {'__lte': 5}) def test_constraints_min_and_max(self): const = search._parse_dict_value({ 'MIN': 5, 'MAX': { 'VALUE': 9, 'EXCLUSIVE': False } }) self.assertEqual(const, {'__lte': 9, '__gte': 5}) def test_within_radius(self): const = search._parse_dict_value( {'WITHIN_RADIUS': { "RADIUS": 5, "POINT": { "x": 100, "y": 50 } }}) self.assertEqual(const, {'__dwithin': (Point(100, 50), Distance(m=5))}) def test_parse_species_predicate(self): pred = search._parse_predicate( { 'species.id': 113, 'species.flowering': True }, mapping=search.DEFAULT_MAPPING) target = ('AND', {('tree__species__id', 113), ('tree__species__flowering', True)}) self.assertEqual(self.destructure_query_set(pred), target) def test_like_predicate(self): pred = search._parse_predicate( {'tree.steward': { 'LIKE': 'thisisatest' }}, mapping=search.DEFAULT_MAPPING) target = ('AND', {('tree__steward__icontains', 'thisisatest')}) self.assertEqual(self.destructure_query_set(pred), target) def test_parse_predicate(self): pred = search._parse_predicate( { 'plot.width': { 'MIN': 5, 'MAX': { 'VALUE': 9, 'EXCLUSIVE': False } }, 'tree.height': 9 }, mapping=search.DEFAULT_MAPPING) p1 = ('AND', {('width__lte', 9), ('width__gte', 5), ('tree__height', 9)}) self.assertEqual(self.destructure_query_set(pred), p1) pred = search._parse_predicate( { 'tree.leaf_type': { 'IS': 9 }, 'tree.last_updated_by': 4 }, mapping=search.DEFAULT_MAPPING) p2 = ('AND', {('tree__leaf_type', 9), ('tree__last_updated_by', 4)}) self.assertEqual(self.destructure_query_set(pred), p2) def test_parse_predicate_with_tree_map(self): pred = search._parse_predicate( { 'plot.width': { 'MIN': 5, 'MAX': { 'VALUE': 9, 'EXCLUSIVE': False } }, 'tree.height': 9 }, mapping=search.TREE_MAPPING) p1 = ('AND', {('plot__width__lte', 9), ('plot__width__gte', 5), ('height', 9)}) self.assertEqual(self.destructure_query_set(pred), p1) def test_parse_filter_no_wrapper(self): pred = search._parse_filter( { 'plot.width': { 'MIN': 5, 'MAX': { 'VALUE': 9, 'EXCLUSIVE': False } }, 'tree.height': 9 }, mapping=search.DEFAULT_MAPPING) p = ('AND', {('width__lte', 9), ('width__gte', 5), ('tree__height', 9)}) self.assertEqual(self.destructure_query_set(pred), p) def test_parse_filter_and(self): pred = search._parse_filter([ 'AND', { 'plot.width': { 'MIN': 5, 'MAX': { 'VALUE': 9, 'EXCLUSIVE': False } }, 'tree.height': 9 }, { 'tree.leaf_type': { 'IS': 9 }, 'tree.last_updated_by': 4 } ], mapping=search.DEFAULT_MAPPING) p = ('AND', {('width__lte', 9), ('width__gte', 5), ('tree__height', 9), ('tree__leaf_type', 9), ('tree__last_updated_by', 4)}) self.assertEqual(self.destructure_query_set(pred), p) def test_parse_filter_or(self): pred = search._parse_filter([ 'OR', { 'plot.width': { 'MIN': 5, 'MAX': { 'VALUE': 9, 'EXCLUSIVE': False } }, 'tree.height': 9 }, { 'tree.leaf_type': { 'IS': 9 }, 'tree.last_updated_by': 4 } ], mapping=search.DEFAULT_MAPPING) p1 = ('AND', frozenset({('width__lte', 9), ('width__gte', 5), ('tree__height', 9)})) p2 = ('AND', frozenset({('tree__leaf_type', 9), ('tree__last_updated_by', 4)})) self.assertEqual(self.destructure_query_set(pred), ('OR', {p1, p2})) def test_parse_collection_udf_simple_predicate(self): self._setup_tree_and_collection_udf() pred = search._parse_predicate( {'udf:plot:%s.action' % self.plotstew.pk: 'prune'}, mapping=search.DEFAULT_MAPPING) target = ('AND', {('id__in', (self.plot.pk, ))}) self.assertEqual(self.destructure_query_set(pred), target) def test_parse_collection_udf_fail_nondate_comparison(self): self._setup_tree_and_collection_udf() with self.assertRaises(search.ParseException): search._parse_predicate( {'udf:tree:%s.date' % self.treestew.pk: { 'MAX': 3 }}, mapping=search.DEFAULT_MAPPING) def test_parse_collection_udf_nested_pass_date_comparison(self): self._setup_tree_and_collection_udf() pred = search._parse_predicate( { 'udf:tree:%s.date' % self.treestew.pk: { 'MAX': '2014-05-01 00:00:00' } }, mapping=search.DEFAULT_MAPPING) target = ('AND', {('tree__id__in', (self.tree.pk, ))}) self.assertEqual(self.destructure_query_set(pred), target) def test_parse_normal_value(self): self.assertEqual(search._parse_value(1), 1) def test_parse_list(self): self.assertEqual(search._parse_value([1, 2]), [1, 2]) def test_parse_date(self): date = datetime(2013, 4, 1, 12, 0, 0) self.assertEqual(search._parse_value("2013-04-01 12:00:00"), date)
def update_map_feature(request_dict, user, feature): """ Update a map feature. Expects JSON in the request body to be: {'model.field', ...} Where model is either 'tree', 'plot', or another map feature type and field is any field on the model. UDF fields should be prefixed with 'udf:'. This method can be used to create a new map feature by passing in an empty MapFeature object (i.e. Plot(instance=instance)) """ feature_object_names = [to_object_name(ft) for ft in feature.instance.map_feature_types] if isinstance(feature, Convertible): # We're going to always work in display units here feature.convert_to_display_units() def split_model_or_raise(identifier): parts = identifier.split('.', 1) if (len(parts) != 2 or parts[0] not in feature_object_names + ['tree']): raise Exception( 'Malformed request - invalid field %s' % identifier) else: return parts def set_attr_on_model(model, attr, val): field_classname = \ model._meta.get_field_by_name(attr)[0].__class__.__name__ if field_classname.endswith('PointField'): srid = val.get('srid', 3857) val = Point(val['x'], val['y'], srid=srid) val.transform(3857) elif field_classname.endswith('MultiPolygonField'): srid = val.get('srid', 4326) val = MultiPolygon(Polygon(val['polygon'], srid=srid), srid=srid) val.transform(3857) if attr == 'mapfeature_ptr': if model.mapfeature_ptr_id != value: raise Exception( 'You may not change the mapfeature_ptr_id') elif attr == 'id': if val != model.pk: raise Exception("Can't update id attribute") elif attr.startswith('udf:'): udf_name = attr[4:] if udf_name in [field.name for field in model.get_user_defined_fields()]: model.udfs[udf_name] = val else: raise KeyError('Invalid UDF %s' % attr) elif attr in model.fields(): model.apply_change(attr, val) else: raise Exception('Malformed request - invalid field %s' % attr) def save_and_return_errors(thing, user): try: if isinstance(thing, Convertible): thing.convert_to_database_units() thing.save_with_user(user) return {} except ValidationError as e: return package_validation_errors(thing._model_name, e) tree = None for (identifier, value) in request_dict.iteritems(): object_name, field = split_model_or_raise(identifier) if object_name in feature_object_names: model = feature elif object_name == 'tree' and feature.feature_type == 'Plot': # Get the tree or spawn a new one if needed tree = (tree or feature.current_tree() or Tree(instance=feature.instance)) # We always edit in display units tree.convert_to_display_units() model = tree if field == 'species' and value: value = get_object_or_404(Species, instance=feature.instance, pk=value) elif field == 'plot' and value == unicode(feature.pk): value = feature else: raise Exception( 'Malformed request - invalid model %s' % object_name) set_attr_on_model(model, field, value) errors = {} if feature.fields_were_updated(): errors.update(save_and_return_errors(feature, user)) if tree and tree.fields_were_updated(): tree.plot = feature errors.update(save_and_return_errors(tree, user)) if errors: raise ValidationError(errors) # Refresh feature.instance in case geo_rev_hash was updated feature.instance = Instance.objects.get(id=feature.instance.id) return feature, tree
def get_audits(logged_in_user, instance, query_vars, user, models, model_id, page=0, page_size=20, exclude_pending=True, should_count=False): start_pos = page * page_size end_pos = start_pos + page_size if instance: if instance.is_accessible_by(logged_in_user): instances = Instance.objects.filter(pk=instance.pk) else: instances = Instance.objects.none() # If we didn't specify an instance we only want to # show audits where the user has permission else: instances = Instance.objects\ .filter(pk__in=_instance_ids_edited_by(user))\ .filter(user_accessible_instance_filter( logged_in_user))\ .distinct() if not instances.exists(): # Force no results return { 'audits': Audit.objects.none(), 'total_count': 0, 'next_page': None, 'prev_page': None } map_feature_models = set(MapFeature.subclass_dict().keys()) model_filter = Q() # We only want to show the TreePhoto's image, not other fields # and we want to do it automatically if 'Tree' was specified as # a model. The same goes for MapFeature(s) <-> MapFeaturePhoto # There is no need to check permissions, because photos are always visible if 'Tree' in models: model_filter = model_filter | Q(model='TreePhoto', field='image') if map_feature_models.intersection(models): model_filter = model_filter | Q(model='MapFeaturePhoto', field='image') if logged_in_user == user: # The logged-in user can see all their own edits model_filter = model_filter | \ Q(model__in=models) | Q(model__startswith='udf:') else: # Filter other users' edits by their visibility to the logged-in user for inst in instances: for model in models: ModelClass = get_auditable_class(model) if issubclass(ModelClass, Authorizable): fake_model = ModelClass(instance=inst) visible_fields = fake_model.visible_fields(logged_in_user) model_filter = model_filter |\ Q(model=model, field__in=visible_fields, instance=inst) else: model_filter = model_filter | Q(model=model, instance=inst) # Add UDF collections related to model if model == 'Tree': fake_model = Tree(instance=inst) elif model == 'Plot': fake_model = Plot(instance=inst) else: continue model_collection_udfs_audit_names =\ fake_model.visible_collection_udfs_audit_names( logged_in_user) model_filter = model_filter |\ Q(model__in=model_collection_udfs_audit_names) udf_bookkeeping_fields = Q(model__startswith='udf:', field__in=('id', 'model_id', 'field_definition')) audits = (Audit.objects.filter(model_filter).filter( instance__in=instances).select_related('instance').exclude( udf_bookkeeping_fields).exclude( user=User.system_user()).order_by('-created')) if user: audits = audits.filter(user=user) if model_id: audits = audits.filter(model_id=model_id) if exclude_pending: audits = audits.exclude(requires_auth=True, ref__isnull=True) total_count = audits.count() if should_count else 0 audits = audits[start_pos:end_pos] query_vars = {k: v for (k, v) in query_vars.iteritems() if k != 'page'} next_page = None prev_page = None # We are using len(audits) instead of audits.count() because we # have already realized the queryset at this point if len(audits) == page_size: query_vars['page'] = page + 1 next_page = "?" + urllib.urlencode(query_vars) if page > 0: query_vars['page'] = page - 1 prev_page = "?" + urllib.urlencode(query_vars) return { 'audits': audits, 'total_count': total_count, 'next_page': next_page, 'prev_page': prev_page }
def test_delete_tree(self): tree = Tree(instance=self.instance, plot=self.plot) tree.save_with_user(self.user) self.go_to_feature_detail(self.plot.pk) self._execute_delete_workflow((1, 1), (1, 0))
def test_plot_history_shows_all_trees(self): p = Plot(instance=self.instance, geom=self.p) p.save_with_user(self.user) self.assertEqual(len(p.get_tree_history()), 0) t = Tree(plot=p, instance=self.instance) t.save_with_user(self.user) tpk = t.pk self.assertEqual(list(p.get_tree_history()), [tpk]) t.delete_with_user(self.user) self.assertEqual(list(p.get_tree_history()), [tpk]) t2 = Tree(plot=p, instance=self.instance) t2.save_with_user(self.user) self.assertEqual(list(p.get_tree_history()), [t2.pk, tpk]) t3 = Tree(plot=p, instance=self.instance) t3.save_with_user(self.user) self.assertEqual(list(p.get_tree_history()), [t3.pk, t2.pk, tpk])
def test_plot_tree_same_instance(self): plot = Plot(geom=self.p1, instance=self.instance2) plot.save_with_user(self.user) tree = Tree(plot=plot, instance=self.instance1, readonly=False) self.assertRaises(ValidationError, tree.save_with_user, self.user)
def test_update_diameter(self): with self._assert_updates_eco_rev(True): tree = Tree(instance=self.instance, plot=self.plot, diameter=3) tree.save_with_user(self.user) request_dict = {'tree.diameter': '5'} update_map_feature(request_dict, self.user, self.plot)
def get_audits(logged_in_user, instance, query_vars, user, models, model_id, page=0, page_size=20, exclude_pending=True, should_count=False): start_pos = page * page_size end_pos = start_pos + page_size model_filter = Q(model__in=models) # We only want to show the TreePhoto's image, not other fields # and we want to do it automatically if 'Tree' was specified as # a model # FIXME: This should also show MapFeaturePhoto if any map feature # models are in the filter if 'Tree' in models: model_filter = model_filter | Q(model='TreePhoto', field='image') if instance: if instance.is_accessible_by(logged_in_user): instances = Instance.objects.filter(pk=instance.pk) else: instances = [] # If we didn't specify an instance we only want to # show audits where the user has permission else: instances = Instance.objects.filter( _user_accessible_instance_filter(logged_in_user)) if len(instances) == 0: # Force no results return { 'audits': [], 'total_count': 0, 'next_page': None, 'prev_page': None } map_feature_models = set(MapFeature.subclass_dict().keys()) model_filter = Q() # We only want to show the TreePhoto's image, not other fields # and we want to do it automatically if 'Tree' was specified as # a model. The same goes for MapFeature(s) <-> MapFeaturePhoto # There is no need to check permissions, because photos are always visible if 'Tree' in models: model_filter = model_filter | Q(model='TreePhoto', field='image') if map_feature_models.intersection(models): model_filter = model_filter | Q(model='MapFeaturePhoto', field='image') # We need a filter per-instance in to only show fields visible to the user for inst in instances: for model in models: ModelClass = get_auditable_class(model) if issubclass(ModelClass, Authorizable): fake_model = ModelClass(instance=inst) visible_fields = fake_model.visible_fields(logged_in_user) model_filter = model_filter |\ Q(model=model, field__in=visible_fields, instance=inst) else: model_filter = model_filter | Q(model=model, instance=inst) # Only add UDF collections if their parent models are being shown if model == 'Tree': fake_model = Tree(instance=inst) elif model == 'Plot': fake_model = Plot(instance=inst) else: continue model_collection_udfs_audit_names =\ fake_model.visible_collection_udfs_audit_names(logged_in_user) # Don't show the fields that every collection UDF has, because they # are not very interesting model_filter = model_filter |\ (Q(model__in=model_collection_udfs_audit_names) & ~Q(field__in=('id', 'model_id', 'field_definition'))) audits = Audit.objects.filter(model_filter)\ .filter(instance__in=instances)\ .order_by('-created', 'id') if user: audits = audits.filter(user=user) if model_id: audits = audits.filter(model_id=model_id) if exclude_pending: audits = audits.exclude(requires_auth=True, ref__isnull=True) total_count = audits.count() if should_count else 0 audits = audits[start_pos:end_pos] query_vars = {k: v for (k, v) in query_vars.iteritems() if k != 'page'} next_page = None prev_page = None if len(audits) == page_size: query_vars['page'] = page + 1 next_page = "?" + urllib.urlencode(query_vars) if page > 0: query_vars['page'] = page - 1 prev_page = "?" + urllib.urlencode(query_vars) return { 'audits': audits, 'total_count': total_count, 'next_page': next_page, 'prev_page': prev_page }
def test_display_value_converts_model_name(self): self.assertEqual('Tree', display_name(Tree()))
def update_map_feature(request_dict, user, feature): """ Update a map feature. Expects JSON in the request body to be: {'model.field', ...} Where model is either 'tree', 'plot', or another map feature type and field is any field on the model. UDF fields should be prefixed with 'udf:'. This method can be used to create a new map feature by passing in an empty MapFeature object (i.e. Plot(instance=instance)) """ feature_object_names = [ to_object_name(ft) for ft in feature.instance.map_feature_types ] if isinstance(feature, Convertible): # We're going to always work in display units here feature.convert_to_display_units() def set_attr_on_model(model, attr, val): field_classname = \ model._meta.get_field_by_name(attr)[0].__class__.__name__ if field_classname.endswith('PointField'): srid = val.get('srid', 3857) val = Point(val['x'], val['y'], srid=srid) val.transform(3857) elif field_classname.endswith('MultiPolygonField'): srid = val.get('srid', 4326) val = MultiPolygon(Polygon(val['polygon'], srid=srid), srid=srid) val.transform(3857) if attr == 'mapfeature_ptr': if model.mapfeature_ptr_id != value: raise Exception('You may not change the mapfeature_ptr_id') elif attr == 'id': if val != model.pk: raise Exception("Can't update id attribute") elif attr.startswith('udf:'): udf_name = attr[4:] if udf_name in [ field.name for field in model.get_user_defined_fields() ]: model.udfs[udf_name] = val else: raise KeyError('Invalid UDF %s' % attr) elif attr in model.fields(): model.apply_change(attr, val) else: raise Exception('Malformed request - invalid field %s' % attr) def save_and_return_errors(thing, user): try: if isinstance(thing, Convertible): thing.convert_to_database_units() thing.save_with_user(user) return {} except ValidationError as e: return package_field_errors(thing._model_name, e) old_location = feature.geom tree = None for (identifier, value) in request_dict.iteritems(): split_template = 'Malformed request - invalid field %s' object_name, field = dotted_split(identifier, 2, failure_format_string=split_template) if (object_name not in feature_object_names + ['tree']): raise Exception(split_template % identifier) tree_udfc_names = [ fdef.canonical_name for fdef in udf_defs(feature.instance, 'Tree') if fdef.iscollection ] if ((field in tree_udfc_names and feature.current_tree() is None and value == [])): continue elif object_name in feature_object_names: model = feature elif object_name == 'tree' and feature.feature_type == 'Plot': # Get the tree or spawn a new one if needed tree = (tree or feature.current_tree() or Tree(instance=feature.instance)) # We always edit in display units tree.convert_to_display_units() model = tree if field == 'species' and value: value = get_object_or_404(Species, instance=feature.instance, pk=value) elif field == 'plot' and value == unicode(feature.pk): value = feature else: raise Exception('Malformed request - invalid model %s' % object_name) set_attr_on_model(model, field, value) errors = {} if feature.fields_were_updated(): errors.update(save_and_return_errors(feature, user)) if tree and tree.fields_were_updated(): tree.plot = feature errors.update(save_and_return_errors(tree, user)) if errors: # It simplifies the templates and client-side logic if the geometry # field errors are returned under the generic name if feature.geom_field_name in errors: errors['mapFeature.geom'] = errors[feature.geom_field_name] raise ValidationError(errors) if old_location is None or not feature.geom.equals_exact(old_location): feature.instance.update_geo_rev() return feature, tree
def test_tree_overrides_plot(self): tree = Tree(diameter=10, plot=self.plot, instance=self.instance) tree.save_with_user(self.other) self.clear_and_set_and_reload() self.assertEqual(self.plot.updated_by_id, self.other.pk)
def update_map_feature(request_dict, user, feature): """ Update a map feature. Expects JSON in the request body to be: {'model.field', ...} Where model is either 'tree', 'plot', or another map feature type and field is any field on the model. UDF fields should be prefixed with 'udf:'. This method can be used to create a new map feature by passing in an empty MapFeature object (i.e. Plot(instance=instance)) """ feature_object_names = [to_object_name(ft) for ft in feature.instance.map_feature_types] if isinstance(feature, Convertible): # We're going to always work in display units here feature.convert_to_display_units() def value_is_redundant(model, field_name, value): # The iOS app sends a key in `data` for every udf definition, # even if it hasn't changed. # If it is trying to delete a custom field that is not defined # for the model, flag it as redundant, # to avoid a `KeyError` when the update tries to delete them. if field_name.startswith('udf:') and \ value in [[], '[]', '', None]: udf_name = field_name.replace('udf:', '') if udf_name not in model.udfs: return True return False def set_attr_on_model(model, attr, val): field_classname = \ model._meta.get_field(attr).__class__.__name__ if field_classname.endswith('PointField'): srid = val.get('srid', 3857) val = Point(val['x'], val['y'], srid=srid) val.transform(3857) elif field_classname.endswith('MultiPolygonField'): srid = val.get('srid', 4326) val = MultiPolygon(Polygon(val['polygon'], srid=srid), srid=srid) val.transform(3857) if attr == 'mapfeature_ptr': if model.mapfeature_ptr_id != value: raise Exception( 'You may not change the mapfeature_ptr_id') elif attr == 'id': if val != model.pk: raise Exception("Can't update id attribute") elif attr.startswith('udf:'): udf_name = attr[4:] if udf_name in [field.name for field in model.get_user_defined_fields()]: model.udfs[udf_name] = val else: raise KeyError('Invalid UDF %s' % attr) elif attr in model.fields(): model.apply_change(attr, val) else: raise Exception('Malformed request - invalid field %s' % attr) def save_and_return_errors(thing, user): try: if isinstance(thing, Convertible): thing.convert_to_database_units() thing.save_with_user(user) return {} except ValidationError as e: return package_field_errors(thing._model_name, e) def skip_setting_value_on_tree(value, tree): # If the tree is not None, we always set a value. If the tree # is None (meaning that we would be creating a new Tree # object) then we only want to set a value if the value is # non-empty. return (tree is None) and (value in ([], '[]', '', None)) tree = None errors = {} rev_updates = ['universal_rev'] old_geom = feature.geom for (identifier, value) in request_dict.iteritems(): split_template = 'Malformed request - invalid field %s' object_name, field = dotted_split(identifier, 2, failure_format_string=split_template) if (object_name not in feature_object_names + ['tree']): raise ValueError(split_template % identifier) if (object_name == 'tree' and skip_setting_value_on_tree( value, feature.safe_get_current_tree())): continue elif object_name in feature_object_names: model = feature elif object_name == 'tree' and feature.feature_type == 'Plot': # Get the tree or spawn a new one if needed tree = (tree or feature.safe_get_current_tree() or Tree(instance=feature.instance)) # We always edit in display units tree.convert_to_display_units() model = tree if field == 'species' and value: value = get_object_or_404(Species, instance=feature.instance, pk=value) elif field == 'plot' and value == unicode(feature.pk): value = feature else: raise ValueError( 'Malformed request - invalid model %s' % object_name) if not value_is_redundant(model, field, value): set_attr_on_model(model, field, value) field_class = model._meta.get_field(field) if isinstance(field_class, GeometryField): rev_updates.append('geo_rev') rev_updates.append('eco_rev') elif identifier in ['tree.species', 'tree.diameter']: rev_updates.append('eco_rev') if feature.fields_were_updated(): errors.update(save_and_return_errors(feature, user)) if tree and tree.fields_were_updated(): tree.plot = feature errors.update(save_and_return_errors(tree, user)) if errors: # It simplifies the templates and client-side logic if the geometry # field errors are returned under the generic name if feature.geom_field_name in errors: errors['mapFeature.geom'] = errors[feature.geom_field_name] raise ValidationError(errors) if old_geom is not None and feature.geom != old_geom: update_hide_at_zoom_after_move(feature, user, old_geom) feature.instance.update_revs(*rev_updates) return feature, tree
def test_basic_audit(self): plot = Plot(geom=self.instance.center, instance=self.instance) plot.save_with_user(self.user1) self.assertAuditsEqual([ self.make_audit(plot.pk, 'id', None, str(plot.pk), model='Plot'), self.make_audit(plot.pk, 'readonly', None, 'False', model='Plot'), self.make_audit(plot.pk, 'geom', None, str(plot.geom), model='Plot')], plot.audits()) t = Tree(plot=plot, instance=self.instance, readonly=True) t.save_with_user(self.user1) expected_audits = [ self.make_audit(t.pk, 'id', None, str(t.pk)), self.make_audit(t.pk, 'readonly', None, True), self.make_audit(t.pk, 'plot', None, plot.pk)] self.assertAuditsEqual(expected_audits, t.audits()) t.readonly = False t.save_with_user(self.user2) expected_audits.insert( 0, self.make_audit(t.pk, 'readonly', 'True', 'False', action=Audit.Type.Update, user=self.user2)) self.assertAuditsEqual(expected_audits, t.audits()) old_pk = t.pk t.delete_with_user(self.user1) expected_audits.insert( 0, self.make_audit(old_pk, None, None, None, action=Audit.Type.Delete, user=self.user1)) self.assertAuditsEqual( expected_audits, Audit.audits_for_model('Tree', self.instance, old_pk))
class ModelUnicodeTests(TestCase): def setUp(self): self.instance = make_instance(name='Test Instance') self.species = Species(instance=self.instance, common_name='Test Common Name', genus='Test Genus', cultivar='Test Cultivar', species='Test Species') self.species.save_base() self.user = make_user(username='******', password='******') self.plot = Plot(geom=Point(0, 0), instance=self.instance, address_street="123 Main Street") self.plot.save_base() self.tree = Tree(plot=self.plot, instance=self.instance) self.tree.save_base() self.boundary = make_simple_boundary("Test Boundary") self.role = Role(instance=self.instance, name='Test Role', rep_thresh=2) self.role.save() self.field_permission = FieldPermission( model_name="Tree", field_name="readonly", permission_level=FieldPermission.READ_ONLY, role=self.role, instance=self.instance) self.field_permission.save_base() self.audit = Audit(action=Audit.Type.Update, model="Tree", field="readonly", model_id=1, user=self.user, previous_value=True, current_value=False) self.audit.save_base() self.reputation_metric = ReputationMetric(instance=self.instance, model_name="Tree", action="Test Action") self.reputation_metric.save_base() def test_instance_model(self): self.assertEqual(unicode(self.instance), "Test Instance") def test_species_model(self): self.assertEqual( unicode(self.species), "Test Common Name [Test Genus Test Species 'Test Cultivar']") def test_user_model(self): self.assertEqual(unicode(self.user), 'commander') def test_plot_model(self): self.assertEqual(unicode(self.plot), 'Plot (0.0, 0.0) 123 Main Street') def test_tree_model(self): self.assertEqual(unicode(self.tree), '') def test_boundary_model(self): self.assertEqual(unicode(self.boundary), 'Test Boundary') def test_role_model(self): self.assertEqual(unicode(self.role), 'Test Role (%s)' % self.role.pk) def test_field_permission_model(self): self.assertEqual(unicode(self.field_permission), 'Tree.readonly - Test Role (%s)' % self.role.pk) def test_audit_model(self): self.assertEqual( unicode(self.audit), 'pk=%s - action=Update - Tree.readonly:(1) - True => False' % self.audit.pk) def test_reputation_metric_model(self): self.assertEqual(unicode(self.reputation_metric), 'Test Instance - Tree - Test Action')
def test_lots_of_trees_and_plots(self): """ Make 3 plots: 2 pending and 1 approved Make 4 trees: 1 on each pending plot, 2 on approved plot Approve one pending plot. Approve all trees. The one on the (Still) pending plot should fail. all else should pass. """ plot1 = Plot(geom=self.instance.center, instance=self.instance) plot2 = Plot(geom=self.instance.center, instance=self.instance) plot3 = Plot(geom=self.instance.center, instance=self.instance) plot1.save_with_user(self.commander_user) plot2.save_with_user(self.pending_user) plot3.save_with_user(self.pending_user) tree1 = Tree(plot=plot1, instance=self.instance) tree1.save_with_user(self.pending_user) tree2 = Tree(plot=plot1, instance=self.instance) tree2.save_with_user(self.pending_user) tree3 = Tree(plot=plot2, instance=self.instance) tree3.save_with_user(self.pending_user) tree4 = Tree(plot=plot3, instance=self.instance) tree4.save_with_user(self.pending_user) approve_or_reject_audits_and_apply( plot2.audits(), self.commander_user, True) approve_or_reject_audits_and_apply( tree1.audits(), self.commander_user, True) approve_or_reject_audits_and_apply( tree2.audits(), self.commander_user, True) approve_or_reject_audits_and_apply( tree3.audits(), self.commander_user, True) self.assertRaises(ObjectDoesNotExist, Plot.objects.get, pk=plot3.pk) self.assertRaises(ObjectDoesNotExist, approve_or_reject_audits_and_apply, tree4.audits(), self.commander_user, True)
def test_add_tree_sets_updated(self): tree = Tree(diameter=10, plot=self.plot, instance=self.instance) tree.save_with_user(self.user) self.assertGreater(self.plot.updated_at, self.initial_updated)
class UserRoleFieldPermissionTest(MultiUserTestCase): def setUp(self): super(UserRoleFieldPermissionTest, self).setUp() self.plot = Plot(geom=self.p1, instance=self.instance) self.plot.save_with_user(self.commander_user) self.tree = Tree(plot=self.plot, instance=self.instance) self.tree.save_with_user(self.direct_user) def test_no_permission_cant_edit_object(self): self.plot.length = 10 self.assertRaises(AuthorizeException, self.plot.save_with_user, self.outlaw_user) self.assertNotEqual(Plot.objects.get(pk=self.plot.pk).length, 10) self.tree.diameter = 10 self.assertRaises(AuthorizeException, self.tree.save_with_user, self.outlaw_user) self.assertNotEqual(Tree.objects.get(pk=self.tree.pk).diameter, 10) def test_readonly_cant_edit_object(self): self.plot.length = 10 self.assertRaises(AuthorizeException, self.plot.save_with_user, self.observer_user) self.assertNotEqual(Plot.objects.get(pk=self.plot.pk).length, 10) self.tree.diameter = 10 self.assertRaises(AuthorizeException, self.tree.save_with_user, self.observer_user) self.assertNotEqual(Tree.objects.get(pk=self.tree.pk).diameter, 10) def test_writeperm_allows_write(self): self.plot.length = 10 self.plot.save_with_user(self.direct_user) self.assertEqual(Plot.objects.get(pk=self.plot.pk).length, 10) self.tree.diameter = 10 self.tree.save_with_user(self.direct_user) self.assertEqual(Tree.objects.get(pk=self.tree.pk).diameter, 10) def test_masking_authorized(self): "When masking with a superuser, nothing should happen" self.plot.width = 5 self.plot.save_with_user(self.commander_user) plot = Plot.objects.get(pk=self.plot.pk) plot.mask_unauthorized_fields(self.commander_user) self.assertEqual(self.plot.width, plot.width) def test_masking_unauthorized(self): "Masking changes an unauthorized field to None" self.plot.width = 5 self.plot.save_base() plot = Plot.objects.get(pk=self.plot.pk) plot.mask_unauthorized_fields(self.observer_user) self.assertEqual(plot.width, None) # geom is always readable self.assertEqual(plot.geom, self.plot.geom) plot = Plot.objects.get(pk=self.plot.pk) plot.mask_unauthorized_fields(self.outlaw_user) self.assertEqual(plot.width, None) # geom is always readable self.assertEqual(plot.geom, self.plot.geom) def test_write_fails_if_any_fields_cant_be_written(self): """ If a user tries to modify several fields simultaneously, only some of which s/he has access to, the write will fail for all fields.""" self.plot.length = 10 self.plot.width = 110 self.assertRaises(AuthorizeException, self.plot.save_with_user, self.direct_user) self.assertNotEqual(Plot.objects.get(pk=self.plot.pk).length, 10) self.assertNotEqual(Plot.objects.get(pk=self.plot.pk).width, 110) self.tree.diameter = 10 self.tree.canopy_height = 110 self.assertRaises(AuthorizeException, self.tree.save_with_user, self.direct_user) self.assertNotEqual(Tree.objects.get(pk=self.tree.pk).diameter, 10) self.assertNotEqual(Tree.objects.get(pk=self.tree.pk).canopy_height, 110)
def handle_row(self, row): self.log_verbose(row) # check the physical location ok, x, y = self.check_coords(row) if not ok: return plot = Plot() try: if self.base_srid != 4326: geom = Point(x, y, srid=self.base_srid) geom.transform(self.tf) self.log_verbose(geom) plot.geometry = geom else: plot.geometry = Point(x, y, srid=4326) except: self.log_error("ERROR: Geometry failed to transform", row) return # check the species (if any) ok, species = self.check_species(row) if not ok: return # check for tree info, should we create a tree or just a plot if species or self.check_tree_info(row): tree = Tree(plot=plot) else: tree = None if tree and species: tree.species = species[0] # check the proximity (try to match up with existing trees) # this may return a different plot/tree than created just above, # so don't set anything else on either until after this point ok, plot, tree = self.check_proximity(plot, tree, species, row) if not ok: return if row.get('ADDRESS') and not plot.address_street: plot.address_street = str(row['ADDRESS']).title() plot.geocoded_address = str(row['ADDRESS']).title() if not plot.geocoded_address: plot.geocoded_address = "" # FIXME: get this from the config? plot.address_state = 'CA' plot.import_event = self.import_event plot.last_updated_by = self.updater plot.data_owner = self.data_owner plot.readonly = self.readonly if row.get('PLOTTYPE'): for k, v in choices['plot_types']: if v == row['PLOTTYPE']: plot.type = k break if row.get('PLOTLENGTH'): plot.length = row['PLOTLENGTH'] if row.get('PLOTWIDTH'): plot.width = row['PLOTWIDTH'] if row.get('ID'): plot.owner_orig_id = row['ID'] if row.get('ORIGID'): plot.owner_additional_properties = "ORIGID=" + str(row['ORIGID']) if row.get('OWNER_ADDITIONAL_PROPERTIES'): plot.owner_additional_properties = str( plot.owner_additional_properties) + " " + str( row['OWNER_ADDITIONAL_PROPERTIES']) if row.get('OWNER_ADDITIONAL_ID'): plot.owner_additional_id = str(row['OWNER_ADDITIONAL_ID']) if row.get('POWERLINE'): for k, v in choices['powerlines']: if v == row['POWERLINE']: plot.powerline = k break sidewalk_damage = row.get('SIDEWALK') if sidewalk_damage is None or sidewalk_damage.strip() == "": pass elif sidewalk_damage is True or sidewalk_damage.lower( ) == "true" or sidewalk_damage.lower() == 'yes': plot.sidewalk_damage = 2 else: plot.sidewalk_damage = 1 plot.quick_save() pnt = plot.geometry n = Neighborhood.objects.filter(geometry__contains=pnt) z = ZipCode.objects.filter(geometry__contains=pnt) plot.neighborhoods = "" plot.neighborhood.clear() plot.zipcode = None if n: for nhood in n: if nhood: plot.neighborhoods = plot.neighborhoods + " " + nhood.id.__str__( ) plot.neighborhood.add(nhood) if z: plot.zipcode = z[0] plot.quick_save() if tree: tree.plot = plot tree.readonly = self.readonly tree.import_event = self.import_event tree.last_updated_by = self.updater if row.get('OWNER'): tree.tree_owner = str(row["OWNER"]) if row.get('STEWARD'): tree.steward_name = str(row["STEWARD"]) if row.get('SPONSOR'): tree.sponsor = str(row["SPONSOR"]) if row.get('DATEPLANTED'): date_string = str(row['DATEPLANTED']) try: date = datetime.strptime(date_string, "%m/%d/%Y") except: pass try: date = datetime.strptime(date_string, "%Y/%m/%d") except: pass if not date: raise ValueError( "Date strings must be in mm/dd/yyyy or yyyy/mm/dd format" ) tree.date_planted = date.strftime("%Y-%m-%d") if row.get('DIAMETER'): tree.dbh = float(row['DIAMETER']) if row.get('HEIGHT'): tree.height = float(row['HEIGHT']) if row.get('CANOPYHEIGHT'): tree.canopy_height = float(row['CANOPYHEIGHT']) if row.get('CONDITION'): for k, v in choices['conditions']: if v == row['CONDITION']: tree.condition = k break if row.get('CANOPYCONDITION'): for k, v in choices['canopy_conditions']: if v == row['CANOPYCONDITION']: tree.canopy_condition = k break # FOR OTM INDIA # GIRTH_CM", "GIRTH_M", "HEIGHT_FT", "HEIGHT_M", "NEST", "BURROWS", "FLOWERS", "FRUITS", "NAILS", "POSTER", "WIRES", "TREE_GUARD", "NUISANCE", "NUISANCE_DESC", "HEALTH_OF_TREE", "FOUND_ON_GROUND", "GROUND_DESCRIPTION", "RISK_ON_TREE", "RISK_DESC", "RARE", "ENDANGERED", "VULNERABLE", "PEST_AFFECTED", "REFER_TO_DEPT", "SPECIAL_OTHER", "SPECIAL_OTHER_DESCRIPTION", "LATITUDE", "LONGITUDE", "CREATION_DATE", "DEVICE_ID", "TIME", "DATE"]) if row.get('GIRTH_M'): tree.girth_m = float(row['GIRTH_M']) if row.get('HEIGHT_M'): tree.height_m = float(row['HEIGHT_M']) if row.get('NEST'): tree.nest = str(row['NEST']) if row.get('BURROWS'): tree.burrows = str(row['BURROWS']) if row.get('FLOWERS'): tree.flowers = str(row['FLOWERS']) if row.get('FRUITS'): tree.fruits = str(row['FRUITS']) if row.get('NAILS'): tree.nails = str(row['NAILS']) if row.get('POSTER'): tree.poster = str(row['POSTER']) if row.get('WIRES'): tree.wires = str(row['WIRES']) if row.get('TREE_GUARD'): tree.tree_guard = str(row['TREE_GUARD']) if row.get('NUISANCE'): tree.other_nuisance = bool(row['NUISANCE']) if row.get('NUISANCE_DESC'): tree.other_nuisance_desc = str(row['NUISANCE_DESC']) if row.get('HEALTH_OF_TREE'): tree.health_of_tree = str(row['HEALTH_OF_TREE']) if row.get('FOUND_ON_GROUND'): tree.found_on_ground = str(row['FOUND_ON_GROUND']) if row.get('GROUND_DESCRIPTION'): tree.ground_description = str(row['GROUND_DESCRIPTION']) if row.get('RISK_ON_TREE'): tree.risk_on_tree = str(row['RISK_ON_TREE']) if row.get('RISK_DESC'): tree.risk_desc = str(row['RISK_DESC']) if row.get('PEST_AFFECTED'): tree.pests = str(row['PEST_AFFECTED']) if row.get('REFER_TO_DEPT'): tree.refer_to_dept = str(row['REFER_TO_DEPT']) if row.get('SPECIAL_OTHER'): tree.special_other = str(row['SPECIAL_OTHER']) if row.get('SPECIAL_OTHER_DESCRIPTION'): tree.special_other_description = str( row['SPECIAL_OTHER_DESCRIPTION']) if row.get('LATITUDE'): tree.latitude = str(row['LATITUDE']) if row.get('LONGITUDE'): tree.longitude = str(row['LONGITUDE']) if row.get('PRABHAG_ID'): tree.prabhag_id = str(row['PRABHAG_ID']) if row.get('CLUSTER_ID'): tree.cluster_id = str(row['CLUSTER_ID']) #if row.get('ID'): # tree.id = str(row['ID']) #import pdb; pdb.set_trace() f = open("trees_in_otm_obj.log", "w") f.write("b4 save") f.write(str(tree.__dict__)) tree.quick_save() f.write("after save \n") f.write(str(tree.__dict__)) f.close() if row.get('PROJECT_1'): for k, v in Choices().get_field_choices('local'): if v == row['PROJECT_1']: local = TreeFlags(key=k, tree=tree, reported_by=self.updater) local.save() break if row.get('PROJECT_2'): for k, v in Choices().get_field_choices('local'): if v == row['PROJECT_2']: local = TreeFlags(key=k, tree=tree, reported_by=self.updater) local.save() break if row.get('PROJECT_3'): for k, v in Choices().get_field_choices('local'): if v == row['PROJECT_3']: local = TreeFlags(key=k, tree=tree, reported_by=self.updater) local.save() break # rerun validation tests and store results tree.validate_all()
class UserRoleModelPermissionTest(MultiUserTestCase): def setUp(self): super(UserRoleModelPermissionTest, self).setUp() self.plot = Plot(geom=self.p1, instance=self.instance) self.plot.save_with_user(self.direct_user) self.tree = Tree(plot=self.plot, instance=self.instance) self.tree.save_with_user(self.direct_user) def _change_user_role(self, user, role): iuser = user.get_instance_user(self.instance) iuser.role = role iuser.save_with_user(self.commander_user) def test_save_new_object_authorized_officer(self): ''' Save two new objects with authorized user, nothing should happen''' plot = Plot(geom=self.p1, instance=self.instance) plot.save_with_user(self.direct_user) tree = Tree(plot=plot, instance=self.instance) tree.save_with_user(self.direct_user) def test_save_new_object_authorized_conjurer(self): ''' Save two new objects with authorized user, nothing should happen''' plot = Plot(geom=self.p1, instance=self.instance) plot.save_with_user(self.conjurer_user) tree = Tree(plot=plot, instance=self.instance) tree.save_with_user(self.conjurer_user) def test_save_new_object_unauthorized_outlaw(self): plot = Plot(geom=self.p1, instance=self.instance) self.assertRaises(AuthorizeException, plot.save_with_user, self.outlaw_user) plot.save_base() tree = Tree(plot=plot, instance=self.instance) self.assertRaises(AuthorizeException, tree.save_with_user, self.outlaw_user) def test_save_new_object_unauthorized_tweaker(self): plot = Plot(geom=self.p1, instance=self.instance) self.assertRaises(AuthorizeException, plot.save_with_user, self.tweaker_user) plot.save_base() tree = Tree(plot=plot, instance=self.instance) self.assertRaises(AuthorizeException, tree.save_with_user, self.tweaker_user) def test_assign_commander_role_can_delete(self): with self.assertRaises(AuthorizeException): self.tree.delete_with_user(self.outlaw_user) self._change_user_role( self.outlaw_user, make_commander_role(self.tree.get_instance())) self.tree.delete_with_user(self.outlaw_user) self.assertEqual(Tree.objects.count(), 0) def test_delete_object(self): with self.assertRaises(AuthorizeException): self.tree.delete_with_user(self.outlaw_user) self.tree.delete_with_user(self.commander_user) with self.assertRaises(AuthorizeException): self.plot.delete_with_user(self.outlaw_user, cascade=True) self.plot.delete_with_user(self.commander_user, cascade=True) def test_delete_object_you_created(self): outlaw_role = self.outlaw_user.get_role(self.instance) self._change_user_role(self.direct_user, outlaw_role) self.tree.delete_with_user(self.direct_user) self.plot.delete_with_user(self.direct_user, cascade=True)
class FilterParserCollectionTests(OTMTestCase): def _setup_tree_and_collection_udf(self): instance = self.instance = make_instance() commander = self.commander = make_commander_user(instance) self.plotstew, self.treestew = \ _setup_collection_udfs(instance, commander) d1 = {'action': 'prune', 'date': "2014-05-3 00:00:00"} d2 = {'action': 'water', 'date': "2014-04-29 00:00:00"} self.plot = Plot(instance=instance, geom=instance.center) self.plot.udfs[self.plotstew.name] = [d1] self.plot.save_with_user(commander) self.tree = Tree(instance=instance, plot=self.plot) self.tree.udfs[self.treestew.name] = [d2] self.tree.save_with_user(commander) def test_key_parser_tree_collection_udf(self): # UDF searches go on the specified model's id match = search._parse_predicate_key('udf:tree:52.action', mapping=search.DEFAULT_MAPPING) self.assertEqual(match, ('udf:tree:52', 'tree__', 'action')) def test_key_parser_plot_collection_udf(self): # UDF searches go on the specified model's id match = search._parse_predicate_key('udf:plot:52.action', mapping=search.DEFAULT_MAPPING) self.assertEqual(match, ('udf:plot:52', '', 'action')) def test_parse_collection_udf_simple_predicate(self): self._setup_tree_and_collection_udf() pred = search._parse_query_dict( {'udf:plot:%s.action' % self.plotstew.pk: 'prune'}, mapping=search.DEFAULT_MAPPING) target = ('AND', {('id__in', (self.plot.pk, ))}) self.assertEqual(destructure_query_set(pred), target) def test_parse_collection_udf_fail_nondatenumeric_comparison(self): self._setup_tree_and_collection_udf() with self.assertRaises(search.ParseException): search._parse_query_dict( {'udf:tree:%s.date' % self.treestew.pk: { 'MAX': "foo" }}, mapping=search.DEFAULT_MAPPING) def test_parse_collection_udf_nested_pass_numeric_comparison(self): self._setup_tree_and_collection_udf() agility = make_collection_udf(self.instance, model='Tree', name='Agility', datatype=[{ 'type': 'float', 'name': 'current' }]) set_write_permissions(self.instance, self.commander, 'Tree', ['udf:Agility']) new_tree = Tree(instance=self.instance, plot=self.plot) new_tree.udfs[agility.name] = [{'current': '1.5'}] new_tree.save_with_user(self.commander) pred = search._parse_query_dict( {'udf:tree:%s.current' % agility.pk: { 'MIN': 1 }}, mapping=search.DEFAULT_MAPPING) target = ('AND', {('tree__id__in', (new_tree.pk, ))}) self.assertEqual(destructure_query_set(pred), target) def test_parse_collection_udf_nested_pass_date_comparison(self): self._setup_tree_and_collection_udf() pred = search._parse_query_dict( { 'udf:tree:%s.date' % self.treestew.pk: { 'MAX': '2014-05-01 00:00:00' } }, mapping=search.DEFAULT_MAPPING) target = ('AND', {('tree__id__in', (self.tree.pk, ))}) self.assertEqual(destructure_query_set(pred), target) def test_parse_collection_udf_date_and_action_should_fail(self): point = Point(0, 0) instance = make_instance(point=point) commander = make_commander_user(instance) plotstew, treestew = _setup_collection_udfs(instance, commander) _setup_models_for_collections(instance, commander, point, plotstew, treestew) pred = search._parse_query_dict( { 'udf:plot:%s.action' % plotstew.pk: { 'IS': 'water' }, 'udf:plot:%s.date' % plotstew.pk: # Range encompasses p1's prune but not p1's water action { 'MIN': '2013-09-01 00:00:00', 'MAX': '2013-10-31 00:00:00' } }, mapping=search.DEFAULT_MAPPING) connector, predset = destructure_query_set(pred) self.assertEqual(connector, 'AND') target = ('id__in', tuple()) self.assertIn(target, predset)
class EcoTest(EcoTestCase): def setUp(self): super(EcoTest, self).setUp() p = self.instance.center self.plot = Plot(geom=p, instance=self.instance) self.plot.save_with_user(self.user) self.tree = Tree(plot=self.plot, instance=self.instance, readonly=False, species=self.species, diameter=1630) self.tree.save_with_user(self.user) def test_eco_benefit_sanity(self): rslt, basis, error = TreeBenefitsCalculator()\ .benefits_for_object(self.instance, self.tree.plot) bens = rslt['plot'] self.assert_benefit_value(bens, BenefitCategory.ENERGY, 'kwh', 1896) self.assert_benefit_value(bens, BenefitCategory.AIRQUALITY, 'lbs', 6) self.assert_benefit_value(bens, BenefitCategory.STORMWATER, 'gal', 3185) self.assert_benefit_value(bens, BenefitCategory.CO2, 'lbs', 563) self.assert_benefit_value(bens, BenefitCategory.CO2STORAGE, 'lbs', 6575) def testSearchBenefits(self): request = make_request( {'q': json.dumps({'tree.readonly': { 'IS': False }})}) # all trees request.instance_supports_ecobenefits = self.instance\ .has_itree_region() result = search_tree_benefits(request, self.instance) benefits = result['benefits'] self.assertTrue(len(benefits) > 0) def test_group_basis_empty(self): basis = {} example = { 'group1': { 'n_objects_used': 5, 'n_objects_discarded': 8 }, 'group2': { 'n_objects_used': 10, 'n_objects_discarded': 12 } } _combine_benefit_basis(basis, example) self.assertEqual(basis, example) def test_group_basis_combine_new_group(self): # New groups are added basis = {'group1': {'n_objects_used': 5, 'n_objects_discarded': 8}} new_group = { 'group2': { 'n_objects_used': 13, 'n_objects_discarded': 4 } } target = { 'group1': { 'n_objects_used': 5, 'n_objects_discarded': 8 }, 'group2': { 'n_objects_used': 13, 'n_objects_discarded': 4 } } _combine_benefit_basis(basis, new_group) self.assertEqual(basis, target) def test_group_basis_combine_existing_groups(self): basis = {'group1': {'n_objects_used': 5, 'n_objects_discarded': 8}} update_group = { 'group1': { 'n_objects_used': 13, 'n_objects_discarded': 4 } } target = {'group1': {'n_objects_used': 18, 'n_objects_discarded': 12}} _combine_benefit_basis(basis, update_group) self.assertEqual(basis, target) def test_combine_benefit_groups_empty(self): # with and without currency base_group = { 'group1': { 'benefit1': { 'value': 3, 'currency': 9, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' }, 'benefit2': { 'value': 3, 'currency': 9, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' } } } groups = {} _combine_grouped_benefits(groups, base_group) self.assertEqual(groups, base_group) def test_combine_benefit_groups_no_overlap(self): base_group = { 'group1': { 'benefit1': { 'value': 3, 'currency': 9, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' }, 'benefit2': { 'value': 4, 'currency': 10, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' } } } new_group = { 'group2': { 'benefit1': { 'value': 5, 'currency': 11, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' }, 'benefit2': { 'value': 6, 'currency': 19, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' } } } groups = {} _combine_grouped_benefits(groups, base_group) _combine_grouped_benefits(groups, new_group) target = { 'group1': base_group['group1'], 'group2': new_group['group2'] } self.assertEqual(groups, target) def test_combine_benefit_groups_sums_benefits(self): base_group = { 'group1': { 'benefit1': { 'value': 3, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' }, 'benefit2': { 'value': 4, 'currency': 10, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' }, 'benefit3': { 'value': 32, 'currency': 919, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' } } } new_group = { 'group1': { 'benefit1': { 'value': 5, 'currency': 11, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' }, 'benefit2': { 'value': 7, 'unit': 'gal', 'currency': 19, 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' }, 'benefit4': { 'value': 7, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' } } } groups = {} _combine_grouped_benefits(groups, base_group) _combine_grouped_benefits(groups, new_group) target = { 'group1': { 'benefit1': { 'value': 8, 'currency': 11, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' }, 'benefit2': { 'value': 11, 'currency': 29, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' }, 'benefit3': { 'value': 32, 'currency': 919, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' }, 'benefit4': { 'value': 7, 'unit': 'gal', 'label': BenefitCategory.STORMWATER, 'unit-name': 'eco' } } } self.assertEqual(groups, target) def test_annotates_basis(self): basis = { 'group1': { 'n_objects_used': 5, 'n_objects_discarded': 15 }, 'group2': { 'n_objects_used': 2, 'n_objects_discarded': 18 } } target = { 'group1': { 'n_objects_used': 5, 'n_objects_discarded': 15, 'n_total': 20, 'n_pct_calculated': 0.25 }, 'group2': { 'n_objects_used': 2, 'n_objects_discarded': 18, 'n_total': 20, 'n_pct_calculated': 0.1 } } _annotate_basis_with_extra_stats(basis) self.assertEqual(basis, target)
class UserRoleFieldPermissionTest(OTMTestCase): def setUp(self): self.p1 = Point(-8515941.0, 4953519.0) self.instance = make_instance(point=self.p1) self.commander = make_commander_user(self.instance) self.officer = make_officer_user(self.instance) self.observer = make_observer_user(self.instance) self.outlaw = make_user_with_default_role(self.instance, 'outlaw') self.plot = Plot(geom=self.p1, instance=self.instance) self.plot.save_with_user(self.officer) self.tree = Tree(plot=self.plot, instance=self.instance) self.tree.save_with_user(self.officer) def test_no_permission_cant_edit_object(self): self.plot.length = 10 self.assertRaises(AuthorizeException, self.plot.save_with_user, self.outlaw) self.assertNotEqual(Plot.objects.get(pk=self.plot.pk).length, 10) self.tree.diameter = 10 self.assertRaises(AuthorizeException, self.tree.save_with_user, self.outlaw) self.assertNotEqual(Tree.objects.get(pk=self.tree.pk).diameter, 10) def test_readonly_cant_edit_object(self): self.plot.length = 10 self.assertRaises(AuthorizeException, self.plot.save_with_user, self.observer) self.assertNotEqual(Plot.objects.get(pk=self.plot.pk).length, 10) self.tree.diameter = 10 self.assertRaises(AuthorizeException, self.tree.save_with_user, self.observer) self.assertNotEqual(Tree.objects.get(pk=self.tree.pk).diameter, 10) def test_writeperm_allows_write(self): self.plot.length = 10 self.plot.save_with_user(self.officer) self.assertEqual(Plot.objects.get(pk=self.plot.pk).length, 10) self.tree.diameter = 10 self.tree.save_with_user(self.officer) self.assertEqual(Tree.objects.get(pk=self.tree.pk).diameter, 10) def test_save_new_object_authorized(self): '''Save two new objects with authorized user, nothing should happen''' plot = Plot(geom=self.p1, instance=self.instance) plot.save_with_user(self.officer) tree = Tree(plot=plot, instance=self.instance) tree.save_with_user(self.officer) def test_save_new_object_unauthorized(self): plot = Plot(geom=self.p1, instance=self.instance) self.assertRaises(AuthorizeException, plot.save_with_user, self.outlaw) plot.save_base() tree = Tree(plot=plot, instance=self.instance) self.assertRaises(AuthorizeException, tree.save_with_user, self.outlaw) def test_make_administrator_can_delete(self): with self.assertRaises(AuthorizeException): self.tree.delete_with_user(self.outlaw) iuser = self.outlaw.get_instance_user(self.instance) role = Role.objects.create(instance=self.instance, name=Role.ADMINISTRATOR, rep_thresh=0) iuser.role = role iuser.save_with_user(self.commander) self.tree.delete_with_user(self.outlaw) self.assertEqual(Tree.objects.count(), 0) def test_delete_object(self): with self.assertRaises(AuthorizeException): self.tree.delete_with_user(self.outlaw) with self.assertRaises(AuthorizeException): self.plot.delete_with_user(self.outlaw, cascade=True) with self.assertRaises(AuthorizeException): self.tree.delete_with_user(self.officer) with self.assertRaises(AuthorizeException): self.plot.delete_with_user(self.officer, cascade=True) self.tree.delete_with_user(self.commander) self.plot.delete_with_user(self.commander, cascade=True) def test_masking_authorized(self): "When masking with a superuser, nothing should happen" self.plot.width = 5 self.plot.save_with_user(self.commander) plot = Plot.objects.get(pk=self.plot.pk) plot.mask_unauthorized_fields(self.commander) self.assertEqual(self.plot.width, plot.width) def test_masking_unauthorized(self): "Masking changes an unauthorized field to None" self.plot.width = 5 self.plot.save_base() plot = Plot.objects.get(pk=self.plot.pk) plot.mask_unauthorized_fields(self.observer) self.assertEqual(None, plot.width) plot = Plot.objects.get(pk=self.plot.pk) plot.mask_unauthorized_fields(self.outlaw) self.assertEqual(None, plot.width) def test_masking_whole_queryset(self): "Masking also works on entire querysets" self.plot.width = 5 self.plot.save_base() plots = Plot.objects.filter(pk=self.plot.pk) plot = Plot.mask_queryset(plots, self.observer)[0] self.assertEqual(None, plot.width) def test_write_fails_if_any_fields_cant_be_written(self): """ If a user tries to modify several fields simultaneously, only some of which s/he has access to, the write will fail for all fields.""" self.plot.length = 10 self.plot.width = 110 self.assertRaises(AuthorizeException, self.plot.save_with_user, self.officer) self.assertNotEqual(Plot.objects.get(pk=self.plot.pk).length, 10) self.assertNotEqual(Plot.objects.get(pk=self.plot.pk).width, 110) self.tree.diameter = 10 self.tree.canopy_height = 110 self.assertRaises(AuthorizeException, self.tree.save_with_user, self.officer) self.assertNotEqual(Tree.objects.get(pk=self.tree.pk).diameter, 10) self.assertNotEqual( Tree.objects.get(pk=self.tree.pk).canopy_height, 110)
def context_dict_for_plot(request, plot, tree_id=None, **kwargs): context = context_dict_for_map_feature(request, plot, **kwargs) instance = request.instance user = request.user if tree_id: tree = get_object_or_404(Tree, instance=instance, plot=plot, pk=tree_id) else: tree = plot.current_tree() if tree: tree.convert_to_display_units() if tree is not None: photos = tree.photos() # can't send a regular photo qs because the API will # serialize this to JSON, which is not supported for qs context['photos'] = [ context_dict_for_photo(request, photo) for photo in photos ] else: photos = [] def get_external_link_url(user, feature, tree=None): if not user or not feature or not feature.is_plot: return None instance = feature.instance external_link_config = \ get_attr_from_json_field(instance, 'config.externalLink') or None if not external_link_config or \ not external_link_config.get('url', None) or \ not external_link_config.get('text', None): return None role = Role.objects.get_role(instance, user) if not role.has_permission('view_external_link'): return None external_url = external_link_config['url'] if not tree and -1 < external_url.find(r'#{tree.id}'): return None plot = feature.cast_to_subtype() substitutes = { 'planting_site.id': str(plot.pk), 'planting_site.custom_id': plot.owner_orig_id or '', 'tree.id': tree and str(tree.pk) or '' } class UrlTemplate(Template): delimiter = '#' pattern = ''' \#(?: (?P<escaped>\#) | # escape with repeated delimiter (?P<named>(?:{0})) | # "#foo" substitutes foo keyword {{(?P<braced>(?:{0}))}} | # "#{{foo}}" substitutes foo keyword (?P<invalid>{{}}) # requires a name ) '''.format(get_external_link_choice_pattern()) return UrlTemplate(external_url).safe_substitute(substitutes) context['external_link'] = get_external_link_url(user, plot, tree) has_tree_diameter = tree is not None and tree.diameter is not None has_tree_species_with_code = tree is not None \ and tree.species is not None and tree.species.otm_code is not None has_photo = tree is not None and len(photos) > 0 total_progress_items = 4 completed_progress_items = 1 # there is always a plot if has_tree_diameter: completed_progress_items += 1 if has_tree_species_with_code: completed_progress_items += 1 if has_photo: completed_progress_items += 1 context['progress_percent'] = int( 100 * (completed_progress_items / total_progress_items)) context['progress_messages'] = [] if not tree: context['progress_messages'].append(_('Add a tree')) if not has_tree_diameter: context['progress_messages'].append(_('Add the diameter')) if not has_tree_species_with_code: context['progress_messages'].append(_('Add the species')) if not has_photo: context['progress_messages'].append(_('Add a photo')) url_kwargs = { 'instance_url_name': instance.url_name, 'feature_id': plot.pk } if tree: url_name = 'add_photo_to_tree' url_kwargs = dict(url_kwargs.items() + [('tree_id', tree.pk)]) else: url_name = 'add_photo_to_plot' context['upload_photo_endpoint'] = reverse(url_name, kwargs=url_kwargs) context['plot'] = plot context['has_tree'] = tree is not None # Give an empty tree when there is none in order to show tree fields easily context['tree'] = tree or Tree(plot=plot, instance=instance) context['photo_upload_share_text'] = _photo_upload_share_text( plot, tree is not None) pmfs = PolygonalMapFeature.objects.filter( polygon__contains=plot.geom, mapfeature_ptr__instance_id=instance.id) if pmfs: context['containing_polygonalmapfeature'] = pmfs[0].cast_to_subtype() audits = _plot_audits(user, instance, plot) _add_audits_to_context(audits, context) _add_share_context(context, request, photos) return context
def commit_row(self): # If this row was already commit... abort if self.plot: self.status = TreeImportRow.SUCCESS self.save() # First validate if not self.validate_row(): return False # Get our data data = self.cleaned self.convert_units( data, { fields.trees.PLOT_WIDTH: self.import_event.plot_width_conversion_factor, fields.trees.PLOT_LENGTH: self.import_event.plot_length_conversion_factor, fields.trees.DIAMETER: self.import_event.diameter_conversion_factor, fields.trees.TREE_HEIGHT: self.import_event.tree_height_conversion_factor, fields.trees.CANOPY_HEIGHT: self.import_event.canopy_height_conversion_factor }) # We need the import event from treemap.models # the names of things are a bit odd here but # self.import_event -> # TreeImportEvent (importer) -> # ImportEvent (treemap) # base_treemap_import_event = self.import_event.base_import_event plot_edited = False tree_edited = False # Initially grab plot from row if it exists plot = self.plot if plot is None: plot = Plot(present=True) # Event if TREE_PRESENT is None, a tree # can still be spawned here if there is # any tree data later tree = plot.current_tree() # Check for an existing tree: if self.model_fields.OPENTREEMAP_ID_NUMBER in data: plot = Plot.objects.get( pk=data[self.model_fields.OPENTREEMAP_ID_NUMBER]) tree = plot.current_tree() else: if data.get(self.model_fields.TREE_PRESENT, False): tree_edited = True if tree is None: tree = Tree(present=True) data_owner = self.import_event.owner for modelkey, importdatakey in TreeImportRow.PLOT_MAP.iteritems(): importdata = data.get(importdatakey, None) if importdata: plot_edited = True setattr(plot, modelkey, importdata) if plot_edited: plot.last_updated_by = data_owner plot.import_event = base_treemap_import_event plot.save() for modelkey, importdatakey in TreeImportRow.TREE_MAP.iteritems(): importdata = data.get(importdatakey, None) if importdata: tree_edited = True if tree is None: tree = Tree(present=True) setattr(tree, modelkey, importdata) if tree_edited: tree.last_updated_by = data_owner tree.import_event = base_treemap_import_event tree.plot = plot tree.save() self.plot = plot self.status = TreeImportRow.SUCCESS self.save() return True
def test_result_map(self): ################################################################## # Test main result map page # Note -> This page does not depend at all on the request # p1 = Plot(geometry=Point(50, 50), last_updated_by=self.u, import_event=self.ie, present=True, width=100, length=100, data_owner=self.u) p2 = Plot(geometry=Point(60, 50), last_updated_by=self.u, import_event=self.ie, present=True, width=90, length=110, data_owner=self.u) p1.save() p2.save() # For max/min plot size p3 = Plot(geometry=Point(50, 50), last_updated_by=self.u, import_event=self.ie, present=True, width=80, length=120, data_owner=self.u) p4 = Plot(geometry=Point(60, 50), last_updated_by=self.u, import_event=self.ie, present=True, width=70, length=130, data_owner=self.u) p5 = Plot(geometry=Point(60, 50), last_updated_by=self.u, import_event=self.ie, present=True, width=60, length=70, data_owner=self.u) p3.save() p4.save() p5.save() t3 = Tree(plot=p3, species=None, last_updated_by=self.u, import_event=self.ie, present=True) t3.save() t4 = Tree(plot=p4, species=None, last_updated_by=self.u, import_event=self.ie, present=True) t4.save() t5 = Tree(plot=p5, species=None, last_updated_by=self.u, import_event=self.ie, present=True) t5.save() t1 = Tree(plot=p1, species=None, last_updated_by=self.u, import_event=self.ie) t1.present = True current_year = datetime.now().year t1.date_planted = date(1999, 9, 9) t2 = Tree(plot=p2, species=None, last_updated_by=self.u, import_event=self.ie) t1.present = True t1.save() t2.save() set_auto_now(t1, "last_updated", False) t1.last_updated = date(1999, 9, 9) t1.save() response = self.client.get("/map/") req = response.context set_auto_now(t1, "last_updated", True) # t1 and t2 should be in the latest trees exp = set([t4.pk, t5.pk]) got = set([t.pk for t in req['latest_trees']]) self.assertTrue(exp <= got) # Check to verify platting dates self.assertEquals(int(req['min_year']), 1999) self.assertEquals(int(req['current_year']), current_year) # Correct min/max plot sizes self.assertEqual(int(req['min_plot']), 60) self.assertEqual(int(req['max_plot']), 130) min_updated = mktime(t1.last_updated.timetuple()) max_updated = mktime(t2.last_updated.timetuple()) self.assertEqual(req['min_updated'], min_updated) self.assertEqual(req['max_updated'], max_updated)
class TreeTest(OTMTestCase): def setUp(self): self.p = Point(-7615441.0, 5953519.0) self.instance = make_instance(point=self.p) self.user = make_commander_user(self.instance) self.plot = Plot(geom=self.instance.center, instance=self.instance) self.plot.save_with_user(self.user) self.tree = Tree(plot=self.plot, instance=self.instance) def test_negative_diameter_fails_validation(self): self.tree.diameter = '-1' with self.assertRaises(ValidationError) as cm: self.tree.save_with_user(self.user) self.assertValidationErrorDictContainsKey(cm.exception, 'diameter') def test_too_large_diameter_fails_validation(self): self.tree.diameter = str(Species.DEFAULT_MAX_DIAMETER + 1) with self.assertRaises(ValidationError) as cm: self.tree.save_with_user(self.user) self.assertValidationErrorDictContainsKey(cm.exception, 'diameter') def test_diameter_too_large_for_species_fails_validation(self): max_diameter = 1 s = Species(genus='Ulmus', species='rubra', cultivar='Columella', instance=self.instance, max_diameter=max_diameter) s.save_with_user(self.user) self.tree.species = s self.tree.diameter = str(max_diameter + 1) with self.assertRaises(ValidationError) as cm: self.tree.save_with_user(self.user) self.assertValidationErrorDictContainsKey(cm.exception, 'diameter') def test_negative_height_fails_validation(self): self.tree.height = '-1' with self.assertRaises(ValidationError) as cm: self.tree.save_with_user(self.user) self.assertValidationErrorDictContainsKey(cm.exception, 'height') def test_too_large_height_fails_validation(self): self.tree.height = Species.DEFAULT_MAX_HEIGHT + 1 with self.assertRaises(ValidationError) as cm: self.tree.save_with_user(self.user) self.assertValidationErrorDictContainsKey(cm.exception, 'height') def test_height_too_large_for_species_fails_validation(self): max_height = 1 s = Species(genus='Ulmus', species='rubra', cultivar='Columella', instance=self.instance, max_height=max_height) s.save_with_user(self.user) self.tree.species = s self.tree.height = str(max_height + 1) with self.assertRaises(ValidationError) as cm: self.tree.save_with_user(self.user) self.assertValidationErrorDictContainsKey(cm.exception, 'height') def test_negative_canopy_height_fails_validation(self): self.tree.canopy_height = -1 with self.assertRaises(ValidationError) as cm: self.tree.save_with_user(self.user) self.assertValidationErrorDictContainsKey(cm.exception, 'canopy_height')
def context_dict_for_plot(request, plot, tree_id=None, **kwargs): context = context_dict_for_map_feature(request, plot, **kwargs) instance = request.instance user = request.user if tree_id: tree = get_object_or_404(Tree, instance=instance, plot=plot, pk=tree_id) else: tree = plot.current_tree() if tree: tree.convert_to_display_units() if tree is not None: photos = tree.photos() # can't send a regular photo qs because the API will # serialize this to JSON, which is not supported for qs context['photos'] = [context_dict_for_photo(request, photo) for photo in photos] else: photos = [] has_tree_diameter = tree is not None and tree.diameter is not None has_tree_species_with_code = tree is not None \ and tree.species is not None and tree.species.otm_code is not None has_photo = tree is not None and len(photos) > 0 total_progress_items = 4 completed_progress_items = 1 # there is always a plot if has_tree_diameter: completed_progress_items += 1 if has_tree_species_with_code: completed_progress_items += 1 if has_photo: completed_progress_items += 1 context['progress_percent'] = int(100 * ( completed_progress_items / total_progress_items)) context['progress_messages'] = [] if not tree: context['progress_messages'].append(_('Add a tree')) if not has_tree_diameter: context['progress_messages'].append(_('Add the diameter')) if not has_tree_species_with_code: context['progress_messages'].append(_('Add the species')) if not has_photo: context['progress_messages'].append(_('Add a photo')) url_kwargs = {'instance_url_name': instance.url_name, 'feature_id': plot.pk} if tree: url_name = 'add_photo_to_tree' url_kwargs = dict(url_kwargs.items() + [('tree_id', tree.pk)]) else: url_name = 'add_photo_to_plot' context['upload_photo_endpoint'] = reverse(url_name, kwargs=url_kwargs) context['plot'] = plot context['has_tree'] = tree is not None # Give an empty tree when there is none in order to show tree fields easily context['tree'] = tree or Tree(plot=plot, instance=instance) context['photo_upload_share_text'] = _photo_upload_share_text( plot, tree is not None) pmfs = PolygonalMapFeature.objects.filter(polygon__contains=plot.geom) if pmfs: context['containing_polygonalmapfeature'] = pmfs[0].cast_to_subtype() audits = _plot_audits(user, instance, plot) _add_audits_to_context(audits, context) _add_share_context(context, request, photos) return context
def setUp(self): ###### # Request/Render mock ###### def local_render_to_response(*args, **kwargs): from django.template import loader, RequestContext from django.http import HttpResponse httpresponse_kwargs = {'mimetype': kwargs.pop('mimetype', None)} hr = HttpResponse(loader.render_to_string(*args, **kwargs), **httpresponse_kwargs) if hasattr(args[1], 'dicts'): hr.request_context = args[1].dicts return hr django.shortcuts.render_to_response = local_render_to_response ###### # Content types ###### r1 = ReputationAction(name="edit verified", description="blah") r2 = ReputationAction(name="edit tree", description="blah") r3 = ReputationAction(name="Administrative Action", description="blah") r4 = ReputationAction(name="add tree", description="blah") r5 = ReputationAction(name="edit plot", description="blah") r6 = ReputationAction(name="add plot", description="blah") self.ra = [r1, r2, r3, r4, r5, r6] for r in self.ra: r.save() ###### # Set up benefit values ###### bv = BenefitValues(co2=0.02, pm10=9.41, area="InlandValleys", electricity=0.1166, voc=4.69, ozone=5.0032, natural_gas=1.25278, nox=12.79, stormwater=0.0078, sox=3.72, bvoc=4.96) bv.save() self.bv = bv dbh = "[1.0, 2.0, 3.0]" rsrc = Resource(meta_species="BDM_OTHER", electricity_dbh=dbh, co2_avoided_dbh=dbh, aq_pm10_dep_dbh=dbh, region="Sim City", aq_voc_avoided_dbh=dbh, aq_pm10_avoided_dbh=dbh, aq_ozone_dep_dbh=dbh, aq_nox_avoided_dbh=dbh, co2_storage_dbh=dbh, aq_sox_avoided_dbh=dbh, aq_sox_dep_dbh=dbh, bvoc_dbh=dbh, co2_sequestered_dbh=dbh, aq_nox_dep_dbh=dbh, hydro_interception_dbh=dbh, natural_gas_dbh=dbh) rsrc.save() self.rsrc = rsrc ###### # Users ###### u = User.objects.filter(username="******") if u: u = u[0] else: u = User.objects.create_user("jim", "*****@*****.**", "jim") u.is_staff = True u.is_superuser = True u.save() up = UserProfile(user=u) u.reputation = Reputation(user=u) u.reputation.save() self.u = u ####### # Setup geometries -> Two stacked 100x100 squares ####### n1geom = MultiPolygon( Polygon(((0, 0), (100, 0), (100, 100), (0, 100), (0, 0)))) n2geom = MultiPolygon( Polygon(((0, 101), (101, 101), (101, 200), (0, 200), (0, 101)))) n1 = Neighborhood(name="n1", region_id=2, city="c1", state="PA", county="PAC", geometry=n1geom) n2 = Neighborhood(name="n2", region_id=2, city="c2", state="NY", county="NYC", geometry=n2geom) n1.save() n2.save() z1geom = MultiPolygon( Polygon(((0, 0), (100, 0), (100, 100), (0, 100), (0, 0)))) z2geom = MultiPolygon( Polygon(((0, 100), (100, 100), (100, 200), (0, 200), (0, 100)))) z1 = ZipCode(zip="19107", geometry=z1geom) z2 = ZipCode(zip="10001", geometry=z2geom) z1.save() z2.save() exgeom1 = MultiPolygon( Polygon(((0, 0), (25, 0), (25, 25), (0, 25), (0, 0)))) ex1 = ExclusionMask(geometry=exgeom1, type="building") ex1.save() agn1 = AggregateNeighborhood(annual_stormwater_management=0.0, annual_electricity_conserved=0.0, annual_energy_conserved=0.0, annual_natural_gas_conserved=0.0, annual_air_quality_improvement=0.0, annual_co2_sequestered=0.0, annual_co2_avoided=0.0, annual_co2_reduced=0.0, total_co2_stored=0.0, annual_ozone=0.0, annual_nox=0.0, annual_pm10=0.0, annual_sox=0.0, annual_voc=0.0, annual_bvoc=0.0, total_trees=0, total_plots=0, location=n1) agn2 = AggregateNeighborhood(annual_stormwater_management=0.0, annual_electricity_conserved=0.0, annual_energy_conserved=0.0, annual_natural_gas_conserved=0.0, annual_air_quality_improvement=0.0, annual_co2_sequestered=0.0, annual_co2_avoided=0.0, annual_co2_reduced=0.0, total_co2_stored=0.0, annual_ozone=0.0, annual_nox=0.0, annual_pm10=0.0, annual_sox=0.0, annual_voc=0.0, annual_bvoc=0.0, total_trees=0, total_plots=0, location=n2) agn1.save() agn2.save() self.agn1 = agn1 self.agn2 = agn2 self.z1 = z1 self.z2 = z2 self.n1 = n1 self.n2 = n2 ###### # And we could use a few species... ###### s1 = Species(symbol="s1", genus="testus1", species="specieius1") s2 = Species(symbol="s2", genus="testus2", species="specieius2") s1.save() s2.save() self.s1 = s1 self.s2 = s2 ####### # Create some basic plots ####### ie = ImportEvent(file_name='site_add') ie.save() self.ie = ie p1_no_tree = Plot(geometry=Point(50, 50), last_updated_by=u, import_event=ie, present=True, data_owner=u) p1_no_tree.save() p2_tree = Plot(geometry=Point(51, 51), last_updated_by=u, import_event=ie, present=True, data_owner=u) p2_tree.save() p3_tree_species1 = Plot(geometry=Point(50, 100), last_updated_by=u, import_event=ie, present=True, data_owner=u) p3_tree_species1.save() p4_tree_species2 = Plot(geometry=Point(50, 150), last_updated_by=u, import_event=ie, present=True, data_owner=u) p4_tree_species2.save() t1 = Tree(plot=p2_tree, species=None, last_updated_by=u, import_event=ie) t1.present = True t1.save() t2 = Tree(plot=p3_tree_species1, species=s1, last_updated_by=u, import_event=ie) t2.present = True t2.save() t3 = Tree(plot=p4_tree_species2, species=s2, last_updated_by=u, import_event=ie) t3.present = True t3.save() self.p1_no_tree = p1_no_tree self.p2_tree = p2_tree self.p3_tree_species1 = p3_tree_species1 self.p4_tree_species2 = p4_tree_species2 self.plots = [p1_no_tree, p2_tree, p3_tree_species1, p4_tree_species2] self.t1 = t1 self.t2 = t2 self.t3 = t3
def test_add_tree_sets_updated(self): tree = Tree(diameter=10, plot=self.plot, instance=self.instance) tree.save_with_user(self.fellow) self.plot.refresh_from_db() self.assertGreater(self.plot.updated_at, self.initial_updated) self.assertEqual(self.plot.updated_by, self.fellow)