Work to complete AC-673, mass refactoring of the activity stream. Some work towards AC-664/662 but will complete on those issues. This also fixes issue AC-743 (Adding help text for OPTIONS requests) and AC-675 (updates where active changes to False will now be treated as deletes)

This commit is contained in:
Matthew Jones
2013-12-10 16:08:35 -05:00
parent ca87fa714f
commit 61059fa1a4
13 changed files with 1760 additions and 111 deletions

View File

@@ -188,7 +188,9 @@ class GenericAPIView(generics.GenericAPIView, APIView):
return ret
class SimpleListAPIView(generics.ListAPIView, GenericAPIView):
pass
def get_queryset(self):
return self.request.user.get_queryset(self.model)
class ListAPIView(generics.ListAPIView, GenericAPIView):
# Base class for a read-only list view.

View File

@@ -243,6 +243,7 @@ class UserSerializer(BaseSerializer):
projects = reverse('api:user_projects_list', args=(obj.pk,)),
credentials = reverse('api:user_credentials_list', args=(obj.pk,)),
permissions = reverse('api:user_permissions_list', args=(obj.pk,)),
activity_stream = reverse('api:user_activity_stream_list', args=(obj.pk,)),
))
return res
@@ -294,6 +295,7 @@ class OrganizationSerializer(BaseSerializer):
admins = reverse('api:organization_admins_list', args=(obj.pk,)),
#tags = reverse('api:organization_tags_list', args=(obj.pk,)),
teams = reverse('api:organization_teams_list', args=(obj.pk,)),
activity_stream = reverse('api:organization_activity_stream_list', args=(obj.pk,))
))
return res
@@ -321,6 +323,7 @@ class ProjectSerializer(BaseSerializer):
playbooks = reverse('api:project_playbooks', args=(obj.pk,)),
update = reverse('api:project_update_view', args=(obj.pk,)),
project_updates = reverse('api:project_updates_list', args=(obj.pk,)),
activity_list = reverse('api:project_activity_stream_list', args=(obj.pk,)),
))
if obj.credential:
res['credential'] = reverse('api:credential_detail',
@@ -420,6 +423,7 @@ class InventorySerializer(BaseSerializerWithVariables):
tree = reverse('api:inventory_tree_view', args=(obj.pk,)),
organization = reverse('api:organization_detail', args=(obj.organization.pk,)),
inventory_sources = reverse('api:inventory_inventory_sources_list', args=(obj.pk,)),
activity_stream = reverse('api:inventory_activity_stream_list', args=(obj.pk,)),
))
return res
@@ -443,6 +447,7 @@ class HostSerializer(BaseSerializerWithVariables):
all_groups = reverse('api:host_all_groups_list', args=(obj.pk,)),
job_events = reverse('api:host_job_events_list', args=(obj.pk,)),
job_host_summaries = reverse('api:host_job_host_summaries_list', args=(obj.pk,)),
activity_stream = reverse('api:host_activity_stream_list', args=(obj.pk,)),
#inventory_sources = reverse('api:host_inventory_sources_list', args=(obj.pk,)),
))
if obj.last_job:
@@ -544,6 +549,7 @@ class GroupSerializer(BaseSerializerWithVariables):
job_events = reverse('api:group_job_events_list', args=(obj.pk,)),
job_host_summaries = reverse('api:group_job_host_summaries_list', args=(obj.pk,)),
inventory_source = reverse('api:inventory_source_detail', args=(obj.inventory_source.pk,)),
activity_stream = reverse('api:group_activity_stream_list', args=(obj.pk,)),
#inventory_sources = reverse('api:group_inventory_sources_list', args=(obj.pk,)),
))
return res
@@ -631,6 +637,7 @@ class InventorySourceSerializer(BaseSerializer):
res.update(dict(
update = reverse('api:inventory_source_update_view', args=(obj.pk,)),
inventory_updates = reverse('api:inventory_source_updates_list', args=(obj.pk,)),
activity_stream = reverse('api:inventory_activity_stream_list', args=(obj.pk,)),
#hosts = reverse('api:inventory_source_hosts_list', args=(obj.pk,)),
#groups = reverse('api:inventory_source_groups_list', args=(obj.pk,)),
))
@@ -726,6 +733,7 @@ class TeamSerializer(BaseSerializer):
credentials = reverse('api:team_credentials_list', args=(obj.pk,)),
organization = reverse('api:organization_detail', args=(obj.organization.pk,)),
permissions = reverse('api:team_permissions_list', args=(obj.pk,)),
activity_stream = reverse('api:team_activity_stream_list', args=(obj.pk,)),
))
return res
@@ -802,6 +810,9 @@ class CredentialSerializer(BaseSerializer):
if obj is None:
return {}
res = super(CredentialSerializer, self).get_related(obj)
res.update(dict(
activity_stream = reverse('api:credential_activity_stream_list', args=(obj.pk,))
))
if obj.user:
res['user'] = reverse('api:user_detail', args=(obj.user.pk,))
if obj.team:
@@ -826,6 +837,7 @@ class JobTemplateSerializer(BaseSerializer):
inventory = reverse('api:inventory_detail', args=(obj.inventory.pk,)),
project = reverse('api:project_detail', args=(obj.project.pk,)),
jobs = reverse('api:job_template_jobs_list', args=(obj.pk,)),
activity_stream = reverse('api:job_template_activity_stream_list', args=(obj.pk,)),
))
if obj.credential:
res['credential'] = reverse('api:credential_detail', args=(obj.credential.pk,))
@@ -869,6 +881,7 @@ class JobSerializer(BaseSerializer):
credential = reverse('api:credential_detail', args=(obj.credential.pk,)),
job_events = reverse('api:job_job_events_list', args=(obj.pk,)),
job_host_summaries = reverse('api:job_job_host_summaries_list', args=(obj.pk,)),
activity_stream = reverse('api:job_activity_stream_list', args=(obj.pk,)),
))
if obj.job_template:
res['job_template'] = reverse('api:job_template_detail', args=(obj.job_template.pk,))
@@ -984,8 +997,22 @@ class ActivityStreamSerializer(BaseSerializer):
class Meta:
model = ActivityStream
fields = ('id', 'url', 'related', 'summary_fields', 'timestamp', 'operation', 'changes',
'object1_id', 'object1', 'object1_type', 'object2_id', 'object2', 'object2_type', 'object_relationship_type')
fields = ('id', 'url', 'related', 'summary_fields', 'timestamp', 'operation', 'changes', 'object1', 'object2')
def get_fields(self):
ret = super(ActivityStreamSerializer, self).get_fields()
for key, field in ret.items():
if key == 'changes':
field.help_text = 'A summary of the new and changed values when an object is created, updated, or deleted'
if key == 'object1':
field.help_text = 'For create, update, and delete events this is the object type that was affected. For associate and disassociate events this is the object type associated or disassociated with object2'
if key == 'object2':
field.help_text = 'Unpopulated for create, update, and delete events. For associate and disassociate events this is the object type that object1 is being associated with'
if key == 'operation':
field.help_text = 'The action taken with respect to the given object(s).'
return ret
def get_changes(self, obj):
if obj is None:
@@ -1001,47 +1028,41 @@ class ActivityStreamSerializer(BaseSerializer):
if obj is None:
return {}
rel = {}
if obj.user is not None:
rel['user'] = reverse('api:user_detail', args=(obj.user.pk,))
obj1_resolution = camelcase_to_underscore(obj.object1_type.split(".")[-1])
rel['object1'] = reverse('api:' + obj1_resolution + '_detail', args=(obj.object1_id,))
if obj.operation in ('associate', 'disassociate'):
obj2_resolution = camelcase_to_underscore(obj.object2_type.split(".")[-1])
rel['object2'] = reverse('api:' + obj2_resolution + '_detail', args=(obj.object2_id,))
if obj.actor is not None:
rel['actor'] = reverse('api:user_detail', args=(obj.actor.pk,))
for fk, _ in SUMMARIZABLE_FK_FIELDS.items():
if not hasattr(obj, fk):
continue
allm2m = getattr(obj, fk).all()
if allm2m.count() > 0:
rel[fk] = []
for thisItem in allm2m:
rel[fk].append(reverse('api:' + fk + '_detail', args=(thisItem.id,)))
return rel
def get_summary_fields(self, obj):
if obj is None:
return {}
d = super(ActivityStreamSerializer, self).get_summary_fields(obj)
try:
short_obj_type = obj.object1_type.split(".")[-1]
under_short_obj_type = camelcase_to_underscore(short_obj_type)
obj1 = eval(obj.object1_type + ".objects.get(id=" + str(obj.object1_id) + ")")
if hasattr(obj1, "name"):
d['object1'] = {'name': obj1.name, 'description': obj1.description,
'base': under_short_obj_type, 'id': obj.object1_id}
else:
d['object1'] = {'base': under_short_obj_type, 'id': obj.object1_id}
if under_short_obj_type == "host" or under_short_obj_type == "group":
d['inventory'] = {'name': obj1.inventory.name, 'id': obj1.inventory.id}
except Exception, e:
logger.error("Error getting object 1 summary: " + str(e))
try:
short_obj_type = obj.object2_type.split(".")[-1]
under_short_obj_type = camelcase_to_underscore(short_obj_type)
if obj.operation in ('associate', 'disassociate'):
obj2 = eval(obj.object2_type + ".objects.get(id=" + str(obj.object2_id) + ")")
if hasattr(obj2, "name"):
d['object2'] = {'name': obj2.name, 'description': obj2.description,
'base': under_short_obj_type, 'id': obj.object2_id}
else:
d['object2'] = {'base': under_short_obj_type, 'id': obj.object2_id}
if under_short_obj_type == "host" or under_short_obj_type == "group":
d['inventory'] = {'name': obj2.inventory.name, 'id': obj2.inventory.id}
except Exception, e:
pass
return d
summary_fields = SortedDict()
for fk, related_fields in SUMMARIZABLE_FK_FIELDS.items():
try:
if not hasattr(obj, fk):
continue
allm2m = getattr(obj, fk).all()
if allm2m.count() > 0:
summary_fields[fk] = []
for thisItem in allm2m:
thisItemDict = {}
for field in related_fields:
fval = getattr(thisItem, field, None)
if fval is not None:
thisItemDict[field] = fval
summary_fields[fk].append(thisItemDict)
except ObjectDoesNotExist:
pass
if obj.actor is not None:
summary_fields['actor'] = dict(username = obj.actor.username,
first_name = obj.actor.first_name,
last_name = obj.actor.last_name)
return summary_fields
class AuthTokenSerializer(serializers.Serializer):

View File

@@ -17,6 +17,7 @@ organization_urls = patterns('awx.api.views',
url(r'^(?P<pk>[0-9]+)/inventories/$', 'organization_inventories_list'),
url(r'^(?P<pk>[0-9]+)/projects/$', 'organization_projects_list'),
url(r'^(?P<pk>[0-9]+)/teams/$', 'organization_teams_list'),
url(r'^(?P<pk>[0-9]+)/activity_stream/$', 'organization_activity_stream_list'),
)
user_urls = patterns('awx.api.views',
@@ -28,6 +29,7 @@ user_urls = patterns('awx.api.views',
url(r'^(?P<pk>[0-9]+)/projects/$', 'user_projects_list'),
url(r'^(?P<pk>[0-9]+)/credentials/$', 'user_credentials_list'),
url(r'^(?P<pk>[0-9]+)/permissions/$', 'user_permissions_list'),
url(r'^(?P<pk>[0-9]+)/activity_stream/$', 'user_activity_stream_list'),
)
project_urls = patterns('awx.api.views',
@@ -38,6 +40,7 @@ project_urls = patterns('awx.api.views',
url(r'^(?P<pk>[0-9]+)/teams/$', 'project_teams_list'),
url(r'^(?P<pk>[0-9]+)/update/$', 'project_update_view'),
url(r'^(?P<pk>[0-9]+)/project_updates/$', 'project_updates_list'),
url(r'^(?P<pk>[0-9]+)/activity_stream/$', 'project_activity_stream_list'),
)
project_update_urls = patterns('awx.api.views',
@@ -52,6 +55,7 @@ team_urls = patterns('awx.api.views',
url(r'^(?P<pk>[0-9]+)/users/$', 'team_users_list'),
url(r'^(?P<pk>[0-9]+)/credentials/$', 'team_credentials_list'),
url(r'^(?P<pk>[0-9]+)/permissions/$', 'team_permissions_list'),
url(r'^(?P<pk>[0-9]+)/activity_stream/$', 'team_activity_stream_list'),
)
inventory_urls = patterns('awx.api.views',
@@ -64,6 +68,7 @@ inventory_urls = patterns('awx.api.views',
url(r'^(?P<pk>[0-9]+)/script/$', 'inventory_script_view'),
url(r'^(?P<pk>[0-9]+)/tree/$', 'inventory_tree_view'),
url(r'^(?P<pk>[0-9]+)/inventory_sources/$', 'inventory_inventory_sources_list'),
url(r'^(?P<pk>[0-9]+)/activity_stream/$', 'inventory_activity_stream_list'),
)
host_urls = patterns('awx.api.views',
@@ -74,6 +79,7 @@ host_urls = patterns('awx.api.views',
url(r'^(?P<pk>[0-9]+)/all_groups/$', 'host_all_groups_list'),
url(r'^(?P<pk>[0-9]+)/job_events/', 'host_job_events_list'),
url(r'^(?P<pk>[0-9]+)/job_host_summaries/$', 'host_job_host_summaries_list'),
url(r'^(?P<pk>[0-9]+)/activity_stream/$', 'host_activity_stream_list'),
#url(r'^(?P<pk>[0-9]+)/inventory_sources/$', 'host_inventory_sources_list'),
)
@@ -87,6 +93,7 @@ group_urls = patterns('awx.api.views',
url(r'^(?P<pk>[0-9]+)/job_events/$', 'group_job_events_list'),
url(r'^(?P<pk>[0-9]+)/job_host_summaries/$', 'group_job_host_summaries_list'),
url(r'^(?P<pk>[0-9]+)/potential_children/$', 'group_potential_children_list'),
url(r'^(?P<pk>[0-9]+)/activity_stream/$', 'group_activity_stream_list'),
#url(r'^(?P<pk>[0-9]+)/inventory_sources/$', 'group_inventory_sources_list'),
)
@@ -95,6 +102,7 @@ inventory_source_urls = patterns('awx.api.views',
url(r'^(?P<pk>[0-9]+)/$', 'inventory_source_detail'),
url(r'^(?P<pk>[0-9]+)/update/$', 'inventory_source_update_view'),
url(r'^(?P<pk>[0-9]+)/inventory_updates/$', 'inventory_source_updates_list'),
url(r'^(?P<pk>[0-9]+)/activity_stream/$', 'inventory_source_activity_stream_list'),
#url(r'^(?P<pk>[0-9]+)/groups/$', 'inventory_source_groups_list'),
#url(r'^(?P<pk>[0-9]+)/hosts/$', 'inventory_source_hosts_list'),
)
@@ -106,6 +114,7 @@ inventory_update_urls = patterns('awx.api.views',
credential_urls = patterns('awx.api.views',
url(r'^$', 'credential_list'),
url(r'^(?P<pk>[0-9]+)/activity_stream/$', 'credential_activity_stream_list'),
url(r'^(?P<pk>[0-9]+)/$', 'credential_detail'),
# See also credentials resources on users/teams.
)
@@ -119,6 +128,7 @@ job_template_urls = patterns('awx.api.views',
url(r'^(?P<pk>[0-9]+)/$', 'job_template_detail'),
url(r'^(?P<pk>[0-9]+)/jobs/$', 'job_template_jobs_list'),
url(r'^(?P<pk>[0-9]+)/callback/$', 'job_template_callback'),
url(r'^(?P<pk>[0-9]+)/activity_stream/$', 'job_template_activity_stream_list'),
)
job_urls = patterns('awx.api.views',
@@ -128,6 +138,7 @@ job_urls = patterns('awx.api.views',
url(r'^(?P<pk>[0-9]+)/cancel/$', 'job_cancel'),
url(r'^(?P<pk>[0-9]+)/job_host_summaries/$', 'job_job_host_summaries_list'),
url(r'^(?P<pk>[0-9]+)/job_events/$', 'job_job_events_list'),
url(r'^(?P<pk>[0-9]+)/activity_stream/$', 'job_activity_stream_list'),
)
job_host_summary_urls = patterns('awx.api.views',

View File

@@ -301,6 +301,13 @@ class OrganizationTeamsList(SubListCreateAPIView):
relationship = 'teams'
parent_key = 'organization'
class OrganizationActivityStreamList(SubListAPIView):
model = ActivityStream
serializer_class = ActivityStreamSerializer
parent_model = Organization
relationship = 'activitystream_set'
class TeamList(ListCreateAPIView):
model = Team
@@ -352,6 +359,13 @@ class TeamCredentialsList(SubListCreateAPIView):
relationship = 'credentials'
parent_key = 'team'
class TeamActivityStreamList(SubListAPIView):
model = ActivityStream
serializer_class = ActivityStreamSerializer
parent_model = Team
relationship = 'activitystream_set'
class ProjectList(ListCreateAPIView):
model = Project
@@ -390,6 +404,13 @@ class ProjectTeamsList(SubListCreateAPIView):
parent_model = Project
relationship = 'teams'
class ProjectActivityStreamList(SubListAPIView):
model = ActivityStream
serializer_class = ActivityStreamSerializer
parent_model = Project
relationship = 'activitystream_set'
class ProjectUpdatesList(SubListAPIView):
model = ProjectUpdate
@@ -513,6 +534,20 @@ class UserAdminOfOrganizationsList(SubListAPIView):
parent_model = User
relationship = 'admin_of_organizations'
class UserActivityStreamList(SubListAPIView):
model = ActivityStream
serializer_class = ActivityStreamSerializer
parent_model = User
relationship = 'activitystream_set'
def get_queryset(self):
parent = self.get_parent_object()
self.check_parent_access(parent)
qs = self.request.user.get_queryset(self.model)
return qs.filter(Q(actor=parent) | Q(user__in=[parent]))
class UserDetail(RetrieveUpdateDestroyAPIView):
model = User
@@ -545,6 +580,13 @@ class CredentialDetail(RetrieveUpdateDestroyAPIView):
model = Credential
serializer_class = CredentialSerializer
class CredentialActivityStreamList(SubListAPIView):
model = ActivityStream
serializer_class = ActivityStreamSerializer
parent_model = Credential
relationship = 'activitystream_set'
class PermissionDetail(RetrieveUpdateDestroyAPIView):
model = Permission
@@ -560,6 +602,13 @@ class InventoryDetail(RetrieveUpdateDestroyAPIView):
model = Inventory
serializer_class = InventorySerializer
class InventoryActivityStreamList(SubListAPIView):
model = ActivityStream
serializer_class = ActivityStreamSerializer
parent_model = Inventory
relationship = 'activitystream_set'
class HostList(ListCreateAPIView):
model = Host
@@ -601,6 +650,13 @@ class HostAllGroupsList(SubListAPIView):
sublist_qs = parent.all_groups.distinct()
return qs & sublist_qs
class HostActivityStreamList(SubListAPIView):
model = ActivityStream
serializer_class = ActivityStreamSerializer
parent_model = Host
relationship = 'activitystream_set'
class GroupList(ListCreateAPIView):
model = Group
@@ -679,6 +735,14 @@ class GroupAllHostsList(SubListAPIView):
sublist_qs = parent.all_hosts.distinct()
return qs & sublist_qs
class GroupActivityStreamList(SubListAPIView):
model = ActivityStream
serializer_class = ActivityStreamSerializer
parent_model = Group
relationship = 'activitystream_set'
class GroupDetail(RetrieveUpdateDestroyAPIView):
model = Group
@@ -829,6 +893,14 @@ class InventorySourceDetail(RetrieveUpdateAPIView):
serializer_class = InventorySourceSerializer
new_in_14 = True
class InventorySourceActivityStreamList(SubListAPIView):
model = ActivityStream
serializer_class = ActivityStreamSerializer
parent_model = InventorySource
relationship = 'activitystream_set'
class InventorySourceUpdatesList(SubListAPIView):
model = InventoryUpdate
@@ -899,6 +971,14 @@ class JobTemplateDetail(RetrieveUpdateDestroyAPIView):
model = JobTemplate
serializer_class = JobTemplateSerializer
class JobTemplateActivityStreamList(SubListAPIView):
model = ActivityStream
serializer_class = ActivityStreamSerializer
parent_model = JobTemplate
relationship = 'activitystream_set'
class JobTemplateCallback(GenericAPIView):
model = JobTemplate
@@ -1031,6 +1111,13 @@ class JobDetail(RetrieveUpdateDestroyAPIView):
return self.http_method_not_allowed(request, *args, **kwargs)
return super(JobDetail, self).update(request, *args, **kwargs)
class JobActivityStreamList(SubListAPIView):
model = ActivityStream
serializer_class = ActivityStreamSerializer
parent_model = Job
relationship = 'activitystream_set'
class JobStart(GenericAPIView):
model = Job
@@ -1171,27 +1258,6 @@ class ActivityStreamList(SimpleListAPIView):
model = ActivityStream
serializer_class = ActivityStreamSerializer
def get_queryset(self):
initial_qs = super(ActivityStreamList, self).get_queryset()
if not self.request.user.is_superuser:
return initial_qs.none()
all_qs = Q()
all_obj1_types = [x.object1_type for x in ActivityStream.objects.order_by('object1_type').distinct('object1_type')]
all_obj2_types = [x.object2_type for x in ActivityStream.objects.order_by('object2_type').distinct('object2_type')]
all_types = list(set(all_obj1_types + all_obj2_types))
for this_type in all_types:
try:
type_qs = get_user_queryset(self.request.user, eval(this_type))
ids = [t.id for t in type_qs]
if len(ids) > 0:
all_qs = all_qs | (Q(object1_type=this_type) & Q(object1_id__in=ids))
all_qs = all_qs | (Q(object2_type=this_type) & Q(object2_id__in=ids))
except Exception, e:
logger.warn("Error: " + str(e))
continue
initial_qs = initial_qs.filter(all_qs)
return initial_qs
class ActivityStreamDetail(RetrieveAPIView):
model = ActivityStream