diff --git a/entity/constants.py b/entity/constants.py new file mode 100644 index 0000000..9103d13 --- /dev/null +++ b/entity/constants.py @@ -0,0 +1,2 @@ + +LOGIC_STRING_OPERATORS = {'&', '|', '!'} diff --git a/entity/models.py b/entity/models.py index 3852642..ba0dcf4 100644 --- a/entity/models.py +++ b/entity/models.py @@ -11,6 +11,7 @@ from python3_utils import compare_on_attr from functools import reduce +from entity.constants import LOGIC_STRING_OPERATORS from entity.exceptions import InvalidLogicStringException @@ -454,18 +455,25 @@ def _process_kmatch(self, kmatch, full_set): Every item is 2 elements - the operator and the value or list of values """ entity_ids = set() - operators = {'&', '|', '!'} if isinstance(kmatch, set): return kmatch - if len(kmatch) == 2 and kmatch[0] not in operators: + # We can always assume operator + list where the list is either sets or another operator + list + if len(kmatch) != 2 or kmatch[0] not in LOGIC_STRING_OPERATORS: return kmatch + # Apply the operator to the rest of the sets if kmatch[0] == '&': - entity_ids = self._process_kmatch(kmatch[1][0], full_set) & self._process_kmatch(kmatch[1][1], full_set) + # Add the first element to the set + entity_ids.update(self._process_kmatch(kmatch[1][0], full_set)) + for next_element in kmatch[1][1:]: + entity_ids &= self._process_kmatch(next_element, full_set) elif kmatch[0] == '|': - entity_ids = self._process_kmatch(kmatch[1][0], full_set) | self._process_kmatch(kmatch[1][1], full_set) + # Add the first element to the set + entity_ids.update(self._process_kmatch(kmatch[1][0], full_set)) + for next_element in kmatch[1][1:]: + entity_ids |= self._process_kmatch(next_element, full_set) elif kmatch[0] == '!': entity_ids = full_set - self._process_kmatch(kmatch[1], full_set) @@ -526,6 +534,7 @@ def get_all_entities(self, membership_cache=None, entities_by_kind=None, return_ def get_entity_ids_from_logic_string(self, entities_by_kind, memberships): entity_kind_id = memberships[0][1] full_set = set(entities_by_kind[entity_kind_id]['all']) + try: filter_tree = ast.parse(self.logic_string.lower()) except: diff --git a/entity/tests/model_tests.py b/entity/tests/model_tests.py index 5e4e297..7dc3a2d 100644 --- a/entity/tests/model_tests.py +++ b/entity/tests/model_tests.py @@ -831,44 +831,77 @@ def test_logic_string(self): def test_logic_string_not(self): """ Verifies that the universal set is properly fetched and used to NOT a set - Group A: 0, 1, 2 - NOT(A) = 3, 4, 5, 6, 7, 8 + Location A: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + Role A: 0, 1 + Role B: 2, 3 + Role C: 4, 5 + Role D: 6, 7 Memberships: - 1. User in Group A + 1. Accounts in Location A + 2. Accounts in Role A + 3. Accounts in Role B + 4. Accounts in Role C + 5. Accounts in Role D + + Logic: 1 AND NOT(2 OR 3 OR 4 OR 5) + Breakdown: + (0, 1, 2, 3, 4, 5, 6, 7, 8, 9) AND NOT((0, 1) OR (2, 3) OR (4, 5) OR (6, 7)) + (0, 1, 2, 3, 4, 5, 6, 7, 8, 9) AND NOT(0, 1, 2, 3, 4, 5, 6, 7) + (0, 1, 2, 3, 4, 5, 6, 7, 8, 9) AND (8, 9) + (8, 9) + """ + logic_string = '1 AND NOT(2 OR 3 OR 4 OR 5)' + + # Create entity kinds + location_kind = G(EntityKind) + role_kind = G(EntityKind) + account_kind = G(EntityKind) + + location_a = G(Entity, entity_kind=location_kind) + role_a = G(Entity, entity_kind=role_kind) + role_b = G(Entity, entity_kind=role_kind) + role_c = G(Entity, entity_kind=role_kind) + role_d = G(Entity, entity_kind=role_kind) - Logic: NOT(1) - (3, 4, 5, 6, 7, 8) - """ - super_entity_kind = G(EntityKind) - sub_entity_kind = G(EntityKind) - super_entity_a = G(Entity, entity_kind=super_entity_kind) sub_entities = [ - G(Entity, entity_kind=sub_entity_kind) + G(Entity, entity_kind=account_kind) for _ in range(10) ] - # Create the relationships + # Create the role relationships relationships = [ - EntityRelationship(sub_entity=sub_entities[0], super_entity=super_entity_a), - EntityRelationship(sub_entity=sub_entities[1], super_entity=super_entity_a), - EntityRelationship(sub_entity=sub_entities[2], super_entity=super_entity_a), + EntityRelationship(sub_entity=sub_entities[0], super_entity=role_a), + EntityRelationship(sub_entity=sub_entities[1], super_entity=role_a), + EntityRelationship(sub_entity=sub_entities[2], super_entity=role_b), + EntityRelationship(sub_entity=sub_entities[3], super_entity=role_b), + EntityRelationship(sub_entity=sub_entities[4], super_entity=role_c), + EntityRelationship(sub_entity=sub_entities[5], super_entity=role_c), + EntityRelationship(sub_entity=sub_entities[6], super_entity=role_d), + EntityRelationship(sub_entity=sub_entities[7], super_entity=role_d), ] + + # Create location relationships + for sub_entity in sub_entities: + relationships.append(EntityRelationship(sub_entity=sub_entity, super_entity=location_a)) + EntityRelationship.objects.bulk_create(relationships) # Create the entity group - entity_group = G(EntityGroup, logic_string='NOT(1)') + entity_group = G(EntityGroup, logic_string=logic_string) - # Create the membership - G(EntityGroupMembership, entity_group=entity_group, sub_entity_kind=sub_entity_kind, entity=super_entity_a) + # Create the memberships + G( + EntityGroupMembership, + entity_group=entity_group, sub_entity_kind=account_kind, entity=location_a, sort_order=1 + ) + G(EntityGroupMembership, entity_group=entity_group, sub_entity_kind=account_kind, entity=role_a, sort_order=2) + G(EntityGroupMembership, entity_group=entity_group, sub_entity_kind=account_kind, entity=role_b, sort_order=3) + G(EntityGroupMembership, entity_group=entity_group, sub_entity_kind=account_kind, entity=role_c, sort_order=4) + G(EntityGroupMembership, entity_group=entity_group, sub_entity_kind=account_kind, entity=role_d, sort_order=5) entity_ids = entity_group.get_all_entities() self.assertEqual(entity_ids, set([ - sub_entities[3].id, - sub_entities[4].id, - sub_entities[5].id, - sub_entities[6].id, - sub_entities[7].id, sub_entities[8].id, sub_entities[9].id, ])) diff --git a/entity/version.py b/entity/version.py index b133e0c..ac1df1f 100644 --- a/entity/version.py +++ b/entity/version.py @@ -1 +1 @@ -__version__ = '6.2.1' +__version__ = '6.2.2' diff --git a/release_notes.md b/release_notes.md index 088cc5d..cd92e35 100644 --- a/release_notes.md +++ b/release_notes.md @@ -1,5 +1,9 @@ ## Release Notes +- 6.2.2: + - Fixed entity group logic string for logic sets containing more than 2 items being operated on +- 6.2.1: + - Rebumped because publish messed up - 6.2.0: - Add support for boolean logic strings to apply to entity group memberships - 6.1.1: