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):