From a11e33458fee23892999b1a37eefb0ffb031d64a Mon Sep 17 00:00:00 2001 From: Aaron Tan Date: Wed, 27 Sep 2017 15:11:38 -0400 Subject: [PATCH] Include Tower configurations into activity stream Relates #7386 of ansible-tower. Due to the uniqueness of Tower configuration datastore model, it is not fully compatible with activity stream workflow. This PR introduced setting field for activitystream model along with other changes to make Tower configuration a special case for activity streams. Signed-off-by: Aaron Tan --- awx/api/serializers.py | 7 +++++++ awx/conf/registry.py | 3 +++ awx/conf/utils.py | 12 ++++++++++- ...2_add_setting_field_for_activity_stream.py | 20 ++++++++++++++++++ awx/main/models/activity_stream.py | 3 +++ awx/main/signals.py | 12 +++++++++-- .../functional/api/test_activity_streams.py | 21 +++++++++++++++++++ 7 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 awx/main/migrations/0010_v322_add_setting_field_for_activity_stream.py diff --git a/awx/api/serializers.py b/awx/api/serializers.py index e6b1058778..fbc9db29f5 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -3797,6 +3797,11 @@ class ActivityStreamSerializer(BaseSerializer): if fk == 'schedule': rel['unified_job_template'] = thisItem.unified_job_template.get_absolute_url(self.context.get('request')) + if obj.setting and obj.setting.get('category', None): + rel['setting'] = self.reverse( + 'api:setting_singleton_detail', + kwargs={'category_slug': obj.setting['category']} + ) return rel def _get_rel(self, obj, fk): @@ -3848,6 +3853,8 @@ class ActivityStreamSerializer(BaseSerializer): username = obj.actor.username, first_name = obj.actor.first_name, last_name = obj.actor.last_name) + if obj.setting: + summary_fields['setting'] = [obj.setting] return summary_fields diff --git a/awx/conf/registry.py b/awx/conf/registry.py index a50753aecb..4e07f0d7da 100644 --- a/awx/conf/registry.py +++ b/awx/conf/registry.py @@ -120,6 +120,9 @@ class SettingsRegistry(object): def is_setting_read_only(self, setting): return bool(self._registry.get(setting, {}).get('read_only', False)) + def get_setting_category(self, setting): + return self._registry.get(setting, {}).get('category_slug', None) + def get_setting_field(self, setting, mixin_class=None, for_user=False, **kwargs): from rest_framework.fields import empty field_kwargs = {} diff --git a/awx/conf/utils.py b/awx/conf/utils.py index b780038e9f..b0d4ffb694 100755 --- a/awx/conf/utils.py +++ b/awx/conf/utils.py @@ -9,7 +9,10 @@ import shutil # RedBaron from redbaron import RedBaron, indent -__all__ = ['comment_assignments'] +# AWX +from awx.conf.registry import settings_registry + +__all__ = ['comment_assignments', 'conf_to_dict'] def comment_assignments(patterns, assignment_names, dry_run=True, backup_suffix='.old'): @@ -103,6 +106,13 @@ def comment_assignments_in_file(filename, assignment_names, dry_run=True, backup return '\n'.join(diff_lines) +def conf_to_dict(obj): + return { + 'category': settings_registry.get_setting_category(obj.key), + 'name': obj.key, + } + + if __name__ == '__main__': pattern = os.path.join(os.path.dirname(__file__), '..', 'settings', 'local_*.py') diffs = comment_assignments(pattern, ['AUTH_LDAP_ORGANIZATION_MAP']) diff --git a/awx/main/migrations/0010_v322_add_setting_field_for_activity_stream.py b/awx/main/migrations/0010_v322_add_setting_field_for_activity_stream.py new file mode 100644 index 0000000000..ad3856567a --- /dev/null +++ b/awx/main/migrations/0010_v322_add_setting_field_for_activity_stream.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations +import awx.main.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0009_v322_add_support_for_ovirt4_inventory'), + ] + + operations = [ + migrations.AddField( + model_name='activitystream', + name='setting', + field=awx.main.fields.JSONField(default=dict, blank=True), + ), + ] diff --git a/awx/main/models/activity_stream.py b/awx/main/models/activity_stream.py index 4d8a4e9709..94df2f985c 100644 --- a/awx/main/models/activity_stream.py +++ b/awx/main/models/activity_stream.py @@ -3,6 +3,7 @@ # Tower from awx.api.versioning import reverse +from awx.main.fields import JSONField # Django from django.db import models @@ -66,6 +67,8 @@ class ActivityStream(models.Model): role = models.ManyToManyField("Role", blank=True) instance_group = models.ManyToManyField("InstanceGroup", blank=True) + setting = JSONField(blank=True) + def get_absolute_url(self, request=None): return reverse('api:activity_stream_detail', kwargs={'pk': self.pk}, request=request) diff --git a/awx/main/signals.py b/awx/main/signals.py index 77fd91a5c3..1936f96c76 100644 --- a/awx/main/signals.py +++ b/awx/main/signals.py @@ -26,6 +26,8 @@ from awx.main.fields import is_implicit_parent from awx.main.consumers import emit_channel_notification +from awx.conf.utils import conf_to_dict + __all__ = [] logger = logging.getLogger('awx.main.signals') @@ -402,12 +404,15 @@ def activity_stream_create(sender, instance, created, **kwargs): object1=object1, changes=json.dumps(changes), actor=get_current_user_or_none()) - activity_entry.save() #TODO: Weird situation where cascade SETNULL doesn't work # it might actually be a good idea to remove all of these FK references since # we don't really use them anyway. if instance._meta.model_name != 'setting': # Is not conf.Setting instance + activity_entry.save() getattr(activity_entry, object1).add(instance) + else: + activity_entry.setting = conf_to_dict(instance) + activity_entry.save() def activity_stream_update(sender, instance, **kwargs): @@ -433,9 +438,12 @@ def activity_stream_update(sender, instance, **kwargs): object1=object1, changes=json.dumps(changes), actor=get_current_user_or_none()) - activity_entry.save() if instance._meta.model_name != 'setting': # Is not conf.Setting instance + activity_entry.save() getattr(activity_entry, object1).add(instance) + else: + activity_entry.setting = conf_to_dict(instance) + activity_entry.save() def activity_stream_delete(sender, instance, **kwargs): diff --git a/awx/main/tests/functional/api/test_activity_streams.py b/awx/main/tests/functional/api/test_activity_streams.py index 1396ac3b48..382add5dc6 100644 --- a/awx/main/tests/functional/api/test_activity_streams.py +++ b/awx/main/tests/functional/api/test_activity_streams.py @@ -5,6 +5,7 @@ from awx.api.versioning import reverse from awx.main.middleware import ActivityStreamMiddleware from awx.main.models.activity_stream import ActivityStream from awx.main.access import ActivityStreamAccess +from awx.conf.models import Setting def mock_feature_enabled(feature): @@ -47,6 +48,26 @@ def test_basic_fields(monkeypatch, organization, get, user, settings): assert response.data['summary_fields']['organization'][0]['name'] == 'test-org' +@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) +@pytest.mark.django_db +def test_ctint_activity_stream(monkeypatch, get, user, settings): + Setting.objects.create(key="FOO", value="bar") + settings.ACTIVITY_STREAM_ENABLED = True + u = user('admin', True) + activity_stream = ActivityStream.objects.filter(setting={'name': 'FOO', 'category': None}).latest('pk') + activity_stream.actor = u + activity_stream.save() + + aspk = activity_stream.pk + url = reverse('api:activity_stream_detail', kwargs={'pk': aspk}) + response = get(url, user('admin', True)) + + assert response.status_code == 200 + assert 'summary_fields' in response.data + assert 'setting' in response.data['summary_fields'] + assert response.data['summary_fields']['setting'][0]['name'] == 'FOO' + + @mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.django_db def test_middleware_actor_added(monkeypatch, post, get, user, settings):