From 13f0b440c3383131ecaaf6321a3103c8299d374b Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Thu, 26 May 2016 12:45:48 -0400 Subject: [PATCH 1/5] discover content type when object2 is a role --- awx/main/signals.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/awx/main/signals.py b/awx/main/signals.py index f7b1845ae9..c5a99840ce 100644 --- a/awx/main/signals.py +++ b/awx/main/signals.py @@ -379,11 +379,16 @@ def activity_stream_associate(sender, instance, **kwargs): obj1 = instance object1=camelcase_to_underscore(obj1.__class__.__name__) obj_rel = sender.__module__ + "." + sender.__name__ + for entity_acted in kwargs['pk_set']: obj2 = kwargs['model'] obj2_id = entity_acted obj2_actual = obj2.objects.get(id=obj2_id) - object2 = camelcase_to_underscore(obj2.__name__) + if isinstance(obj2_actual, Role) and hasattr(obj2_actual, 'content_object'): + obj2_actual = obj2_actual.content_object + object2 = camelcase_to_underscore(obj2_actual.__class__.__name__) + else: + object2 = camelcase_to_underscore(obj2.__name__) # Skip recording any inventory source, or system job template changes here. if isinstance(obj1, InventorySource) or isinstance(obj2_actual, InventorySource): continue From 2e0686a1730898a7e7b15f1e5d9d567e5a0bcdcf Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Thu, 26 May 2016 12:46:16 -0400 Subject: [PATCH 2/5] tests for object2 type when it is a role --- awx/main/tests/factories/tower.py | 28 +++++++++++++++++-- .../functional/api/test_activity_streams.py | 19 +++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/awx/main/tests/factories/tower.py b/awx/main/tests/factories/tower.py index 2a9d493efe..a72e1ac246 100644 --- a/awx/main/tests/factories/tower.py +++ b/awx/main/tests/factories/tower.py @@ -211,15 +211,38 @@ def create_job_template(name, **kwargs): job_type=job_type) def create_organization(name, **kwargs): - Objects = namedtuple("Objects", "organization,teams,users,superusers,projects,labels,notification_templates") + artifacts = [ + "organization", + "teams", + "users", + "superusers", + "projects", + "labels", + "notification_templates", + "inventories", + ] + + for k in kwargs.keys(): + if k not in artifacts: + raise RuntimeError('{} is not a valid argument'.format(k)) + + Objects = namedtuple("Objects", ",".join(artifacts)) projects = {} + inventories = {} labels = {} notification_templates = {} persisted = kwargs.get('persisted', True) org = mk_organization(name, '%s-desc'.format(name), persisted=persisted) + if 'inventories' in kwargs: + for i in kwargs['inventories']: + if type(i) is Inventory: + inventories[i.name] = i + else: + inventories[i] = mk_inventory(i, organization=org, persisted=persisted) + if 'projects' in kwargs: for p in kwargs['projects']: if type(p) is Project: @@ -253,7 +276,8 @@ def create_organization(name, **kwargs): teams=_Mapped(teams), projects=_Mapped(projects), labels=_Mapped(labels), - notification_templates=_Mapped(notification_templates)) + notification_templates=_Mapped(notification_templates), + inventories=_Mapped(inventories)) def create_notification_template(name, **kwargs): Objects = namedtuple("Objects", "notification_template,organization,users,superusers,teams") diff --git a/awx/main/tests/functional/api/test_activity_streams.py b/awx/main/tests/functional/api/test_activity_streams.py index 43e809afb9..3bcb0f8264 100644 --- a/awx/main/tests/functional/api/test_activity_streams.py +++ b/awx/main/tests/functional/api/test_activity_streams.py @@ -131,3 +131,22 @@ def test_stream_queryset_hides_shows_items( assert queryset.filter(host__pk=host.pk, operation='create').count() == 1 assert queryset.filter(team__pk=team.pk, operation='create').count() == 1 assert queryset.filter(notification_template__pk=notification_template.pk, operation='create').count() == 1 + +@pytest.mark.django_db +def test_stream_user_direct_role_updates(get, post, organization_factory): + objects = organization_factory('test_org', + superusers=['admin'], + users=['test'], + inventories=['inv1']) + + url = reverse('api:user_roles_list', args=(objects.users.test.pk,)) + post(url, dict(id=objects.inventories.inv1.read_role.pk), objects.superusers.admin) + + activity_stream = ActivityStream.objects.filter( + inventory__pk=objects.inventories.inv1.pk, + user__pk=objects.users.test.pk).first() + url = reverse('api:activity_stream_detail', args=(activity_stream.pk,)) + response = get(url, objects.users.test) + + assert response.data['object1'] == 'user' + assert response.data['object2'] == 'inventory' From e6c76d094194f2661e92767a888b49c439a8f956 Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Thu, 26 May 2016 14:42:19 -0400 Subject: [PATCH 3/5] fixup objects creation helpers --- awx/main/tests/factories/tower.py | 65 ++++++++++++++++++------------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/awx/main/tests/factories/tower.py b/awx/main/tests/factories/tower.py index a72e1ac246..3220f4c78b 100644 --- a/awx/main/tests/factories/tower.py +++ b/awx/main/tests/factories/tower.py @@ -27,6 +27,17 @@ from .fixtures import ( from .exc import NotUnique +def generate_objects(artifacts, kwargs): + '''generate_objects takes a list of artifacts that are supported by + a create function and compares it to the kwargs passed in to the create + function. If a kwarg is found that is not in the artifacts list a RuntimeError + is raised. + ''' + for k in kwargs.keys(): + if k not in artifacts: + raise RuntimeError('{} is not a valid argument'.format(k)) + return namedtuple("Objects", ",".join(artifacts)) + def generate_role_objects(objects): '''generate_role_objects assembles a dictionary of all possible objects by name. It will raise an exception if any of the objects share a name due to the fact that @@ -167,15 +178,19 @@ class _Mapped(object): # or encapsulated by specific factory fixtures in a conftest # -def create_job_template(name, **kwargs): - Objects = namedtuple("Objects", "job_template, inventory, project, credential, job_type") +def create_job_template(name, roles=None, persisted=True, **kwargs): + Objects = generate_objects(["job_template", + "organization", + "inventory", + "project", + "credential", + "job_type",], kwargs) org = None proj = None inv = None cred = None job_type = kwargs.get('job_type', 'run') - persisted = kwargs.get('persisted', True) if 'organization' in kwargs: org = kwargs['organization'] @@ -202,37 +217,28 @@ def create_job_template(name, **kwargs): job_type=job_type, persisted=persisted) role_objects = generate_role_objects([org, proj, inv, cred]) - apply_roles(kwargs.get('roles'), role_objects, persisted) + apply_roles(roles, role_objects, persisted) return Objects(job_template=jt, project=proj, inventory=inv, credential=cred, - job_type=job_type) + job_type=job_type, + organization=org,) -def create_organization(name, **kwargs): - artifacts = [ - "organization", - "teams", - "users", - "superusers", - "projects", - "labels", - "notification_templates", - "inventories", - ] - - for k in kwargs.keys(): - if k not in artifacts: - raise RuntimeError('{} is not a valid argument'.format(k)) - - Objects = namedtuple("Objects", ",".join(artifacts)) +def create_organization(name, roles=None, persisted=True, **kwargs): + Objects = generate_objects(["organization", + "teams", "users", + "superusers", + "projects", + "labels", + "notification_templates", + "inventories",], kwargs) projects = {} inventories = {} labels = {} notification_templates = {} - persisted = kwargs.get('persisted', True) org = mk_organization(name, '%s-desc'.format(name), persisted=persisted) @@ -269,7 +275,7 @@ def create_organization(name, **kwargs): notification_templates[nt] = mk_notification_template(nt, organization=org, persisted=persisted) role_objects = generate_role_objects([org, superusers, users, teams, projects, labels, notification_templates]) - apply_roles(kwargs.get('roles'), role_objects, persisted) + apply_roles(roles, role_objects, persisted) return Objects(organization=org, superusers=_Mapped(superusers), users=_Mapped(users), @@ -279,11 +285,14 @@ def create_organization(name, **kwargs): notification_templates=_Mapped(notification_templates), inventories=_Mapped(inventories)) -def create_notification_template(name, **kwargs): - Objects = namedtuple("Objects", "notification_template,organization,users,superusers,teams") +def create_notification_template(name, roles=None, persisted=True, **kwargs): + Objects = generate_objects(["notification_template", + "organization", + "users", + "superusers", + "teams",], kwargs) organization = None - persisted = kwargs.get('persisted', True) if 'organization' in kwargs: org = kwargs['organization'] @@ -296,7 +305,7 @@ def create_notification_template(name, **kwargs): users = generate_users(organization, teams, False, persisted, users=kwargs.get('users')) role_objects = generate_role_objects([organization, notification_template]) - apply_roles(kwargs.get('roles'), role_objects, persisted) + apply_roles(roles, role_objects, persisted) return Objects(notification_template=notification_template, organization=organization, users=_Mapped(users), From 61208f6a7704d3820f4f0977460cbebc0d351fd6 Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Thu, 26 May 2016 15:24:25 -0400 Subject: [PATCH 4/5] content_object can be None, that is what we care about --- awx/main/signals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/main/signals.py b/awx/main/signals.py index c5a99840ce..86dff57df2 100644 --- a/awx/main/signals.py +++ b/awx/main/signals.py @@ -384,7 +384,7 @@ def activity_stream_associate(sender, instance, **kwargs): obj2 = kwargs['model'] obj2_id = entity_acted obj2_actual = obj2.objects.get(id=obj2_id) - if isinstance(obj2_actual, Role) and hasattr(obj2_actual, 'content_object'): + if isinstance(obj2_actual, Role) and obj2_actual.content_object is not None: obj2_actual = obj2_actual.content_object object2 = camelcase_to_underscore(obj2_actual.__class__.__name__) else: From 31d3e3ec55bec0c7bac8e954f4933e451cebae63 Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Thu, 26 May 2016 16:09:38 -0400 Subject: [PATCH 5/5] mock activity stream license and make query more specific --- awx/main/tests/functional/api/test_activity_streams.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/awx/main/tests/functional/api/test_activity_streams.py b/awx/main/tests/functional/api/test_activity_streams.py index 3bcb0f8264..39990064d7 100644 --- a/awx/main/tests/functional/api/test_activity_streams.py +++ b/awx/main/tests/functional/api/test_activity_streams.py @@ -133,6 +133,7 @@ def test_stream_queryset_hides_shows_items( assert queryset.filter(notification_template__pk=notification_template.pk, operation='create').count() == 1 @pytest.mark.django_db +@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) def test_stream_user_direct_role_updates(get, post, organization_factory): objects = organization_factory('test_org', superusers=['admin'], @@ -144,7 +145,8 @@ def test_stream_user_direct_role_updates(get, post, organization_factory): activity_stream = ActivityStream.objects.filter( inventory__pk=objects.inventories.inv1.pk, - user__pk=objects.users.test.pk).first() + user__pk=objects.users.test.pk, + role__pk=objects.inventories.inv1.read_role.pk).first() url = reverse('api:activity_stream_detail', args=(activity_stream.pk,)) response = get(url, objects.users.test)