1
0
mirror of https://github.com/ansible/awx.git synced 2024-10-31 15:21:13 +03:00

Moved queryset filtering/ordering out of BaseList into the CustomFilterBackend. Added job_host_summaries sublists for jobs and hosts, add detail view for job host summary, with tests.

This commit is contained in:
Chris Church 2013-05-14 19:17:09 -04:00
parent 5c6895e606
commit 7183549289
7 changed files with 187 additions and 88 deletions

View File

@ -31,28 +31,13 @@ class BaseList(generics.ListCreateAPIView):
# serializer_class = SerializerClass
def post(self, request, *args, **kwargs):
# FIXME: Should inherit from generics.ListAPIView if not postable.
postable = getattr(self.__class__, 'postable', True)
if not postable:
return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)
return super(BaseList, self).post(request, *args, **kwargs)
def get_queryset(self):
base = self._get_queryset()
model = self.__class__.model
qs = None
if model == User:
qs = base.filter(is_active=True)
elif model in [ Tag, AuditTrail, JobEvent ]:
qs = base
else:
qs = self._get_queryset().filter(active=True)
order = self.request.QUERY_PARAMS.get('order', None)
if order:
qs = qs.order_by(order)
return qs
# NOTE: Moved filtering from get_queryset into custom filter backend.
class BaseSubList(BaseList):

View File

@ -13,12 +13,20 @@ class CustomFilterBackend(object):
terms = {}
order_by = None
for key, value in request.GET.items():
# Filtering by is_active/active that was previously in BaseList.
qs = queryset
for field in queryset.model._meta.fields:
if field.name == 'is_active':
qs = qs.filter(is_active=True)
elif field.name == 'active':
qs = qs.filter(active=True)
for key, value in request.QUERY_PARAMS.items():
if key in [ 'page', 'page_size', 'format' ]:
continue
if key == 'order_by':
if key in ('order', 'order_by'):
order_by = value
continue
@ -29,7 +37,7 @@ class CustomFilterBackend(object):
terms[key2] = value
qs = queryset.filter(**terms)
qs = qs.filter(**terms)
if order_by:
qs = qs.order_by(order_by)

View File

@ -802,6 +802,9 @@ class JobHostSummary(models.Model):
(self.host.name, self.changed, self.dark, self.failures, self.ok,
self.processed, self.skipped)
def get_absolute_url(self):
return reverse('main:job_host_summary_detail', args=(self.pk,))
def save(self, *args, **kwargs):
super(JobHostSummary, self).save(*args, **kwargs)
self.update_host_last_job_summary()

View File

@ -207,11 +207,12 @@ class HostSerializer(BaseSerializer):
variable_data = reverse('main:hosts_variable_detail', args=(obj.pk,)),
inventory = reverse('main:inventory_detail', args=(obj.inventory.pk,)),
job_events = reverse('main:host_job_event_list', args=(obj.pk,)),
job_host_summaries = reverse('main:host_job_host_summary_list', args=(obj.pk,)),
))
if obj.last_job:
res['last_job'] = reverse('main:job_detail', args=(obj.last_job.pk,))
#if obj.last_job_host_summary:
# res['last_job_host_summary'] = reverse('main:job_host_summary_detail', args=(obj.last_job_host_summary.pk,))
if obj.last_job_host_summary:
res['last_job_host_summary'] = reverse('main:job_host_summary_detail', args=(obj.last_job_host_summary.pk,))
# NICE TO HAVE: possible reverse resource to show what groups the host is in
return res
@ -391,7 +392,7 @@ class JobSerializer(BaseSerializer):
project = reverse('main:projects_detail', args=(obj.project.pk,)),
credential = reverse('main:credentials_detail', args=(obj.credential.pk,)),
job_events = reverse('main:job_job_event_list', args=(obj.pk,)),
#hosts = reverse('main:job_???', args=(obj.pk,)),
job_host_summaries = reverse('main:job_job_host_summary_list', args=(obj.pk,)),
))
if obj.job_template:
res['job_template'] = reverse('main:job_template_detail', args=(obj.job_template.pk,))
@ -427,14 +428,15 @@ class JobHostSummarySerializer(BaseSerializer):
class Meta:
model = JobHostSummary
fields = ('id', 'url', 'job', 'host', 'changed', 'dark', 'failures',
'ok', 'processed', 'skipped', 'related')
fields = ('id', 'url', 'job', 'host', 'summary_fields', 'related',
'changed', 'dark', 'failures', 'ok', 'processed', 'skipped')
def get_related(self, obj):
res = super(JobHostSummarySerializer, self).get_related(obj)
res['job'] = reverse('main:job_detail', args=(obj.job.pk,))
if obj.host:
res['host'] = reverse('main:hosts_detail', args=(obj.host.pk,))
res.update(dict(
job=reverse('main:job_detail', args=(obj.job.pk,)),
host=reverse('main:hosts_detail', args=(obj.host.pk,))
))
return res
class JobEventSerializer(BaseSerializer):
@ -442,7 +444,7 @@ class JobEventSerializer(BaseSerializer):
class Meta:
model = JobEvent
fields = ('id', 'url', 'job', 'event', 'event_data', 'failed', 'host',
'related')
'related', 'summary_fields')
def get_related(self, obj):
res = super(JobEventSerializer, self).get_related(obj)

View File

@ -868,11 +868,81 @@ class JobStartCancelTest(BaseJobTestMixin, django.test.TransactionTestCase):
# FIXME: Test with other users.
def test_get_job_host_list(self):
pass
def test_get_job_results(self):
# Start/run a job and then access its results via the API.
job = self.job_ops_east_run
job.start()
def test_get_job_job_event_list(self):
pass
# Check that the job detail has been updated.
url = reverse('main:job_detail', args=(job.pk,))
with self.current_user(self.user_sue):
response = self.get(url)
self.assertEqual(response['status'], 'successful')
self.assertTrue(response['result_stdout'])
# Test job events for completed job.
url = reverse('main:job_job_event_list', args=(job.pk,))
with self.current_user(self.user_sue):
response = self.get(url)
qs = job.job_events.all()
self.assertTrue(qs.count())
self.check_pagination_and_size(response, qs.count())
self.check_list_ids(response, qs)
# Test individual job event detail records.
host_ids = set()
for job_event in job.job_events.all():
if job_event.host:
host_ids.add(job_event.host.pk)
url = reverse('main:job_event_detail', args=(job_event.pk,))
with self.current_user(self.user_sue):
response = self.get(url)
# Also test job event list for each host.
for host in Host.objects.filter(pk__in=host_ids):
url = reverse('main:host_job_event_list', args=(host.pk,))
with self.current_user(self.user_sue):
response = self.get(url)
qs = host.job_events.all()
self.assertTrue(qs.count())
self.check_pagination_and_size(response, qs.count())
self.check_list_ids(response, qs)
# Test global job event list.
url = reverse('main:job_event_list')
with self.current_user(self.user_sue):
response = self.get(url)
qs = JobEvent.objects.all()
self.assertTrue(qs.count())
self.check_pagination_and_size(response, qs.count())
self.check_list_ids(response, qs)
# Test job host summaries for completed job.
url = reverse('main:job_job_host_summary_list', args=(job.pk,))
with self.current_user(self.user_sue):
response = self.get(url)
qs = job.job_host_summaries.all()
self.assertTrue(qs.count())
self.check_pagination_and_size(response, qs.count())
self.check_list_ids(response, qs)
# Every host referenced by a job_event should be present as a job
# host summary record.
self.assertEqual(host_ids,
set(qs.values_list('host__pk', flat=True)))
# Test individual job host summary records.
for job_host_summary in job.job_host_summaries.all():
url = reverse('main:job_host_summary_detail',
args=(job_host_summary.pk,))
with self.current_user(self.user_sue):
response = self.get(url)
# Test job host summaries for each host.
for host in Host.objects.filter(pk__in=host_ids):
url = reverse('main:host_job_host_summary_list', args=(host.pk,))
with self.current_user(self.user_sue):
response = self.get(url)
qs = host.job_host_summaries.all()
self.assertTrue(qs.count())
self.check_pagination_and_size(response, qs.count())
self.check_list_ids(response, qs)

View File

@ -68,6 +68,7 @@ hosts_urls = patterns('lib.main.views',
url(r'^(?P<pk>[0-9]+)/$', 'hosts_detail'),
url(r'^(?P<pk>[0-9]+)/variable_data/$', 'hosts_variable_detail'),
url(r'^(?P<pk>[0-9]+)/job_events/', 'host_job_event_list'),
url(r'^(?P<pk>[0-9]+)/job_host_summaries/$', 'host_job_host_summary_list'),
)
groups_urls = patterns('lib.main.views',
@ -105,14 +106,18 @@ jobs_urls = patterns('lib.main.views',
url(r'^(?P<pk>[0-9]+)/$', 'job_detail'),
url(r'^(?P<pk>[0-9]+)/start/$', 'job_start'),
url(r'^(?P<pk>[0-9]+)/cancel/$', 'job_cancel'),
url(r'^(?P<pk>[0-9]+)/hosts/$', 'job_hosts_list'),
url(r'^(?P<pk>[0-9]+)/successful_hosts/$', 'jobs_successful_hosts_list'),
url(r'^(?P<pk>[0-9]+)/changed_hosts/$', 'jobs_changed_hosts_list'),
url(r'^(?P<pk>[0-9]+)/failed_hosts/$', 'jobs_failed_hosts_list'),
url(r'^(?P<pk>[0-9]+)/unreachable_hosts/$', 'jobs_unreachable_hosts_list'),
url(r'^(?P<pk>[0-9]+)/job_host_summaries/$', 'job_job_host_summary_list'),
#url(r'^(?P<pk>[0-9]+)/successful_hosts/$', 'jobs_successful_hosts_list'),
#url(r'^(?P<pk>[0-9]+)/changed_hosts/$', 'jobs_changed_hosts_list'),
#url(r'^(?P<pk>[0-9]+)/failed_hosts/$', 'jobs_failed_hosts_list'),
#url(r'^(?P<pk>[0-9]+)/unreachable_hosts/$', 'jobs_unreachable_hosts_list'),
url(r'^(?P<pk>[0-9]+)/job_events/$', 'job_job_event_list'),
)
job_host_summary_urls = patterns('lib.main.views',
url(r'^(?P<pk>[0-9]+)/$', 'job_host_summary_detail'),
)
job_events_urls = patterns('lib.main.views',
url(r'^$', 'job_event_list'),
url(r'^(?P<pk>[0-9]+)/$', 'job_event_detail'),
@ -140,6 +145,7 @@ v1_urls = patterns('lib.main.views',
url(r'^permissions/', include(permissions_urls)),
url(r'^job_templates/', include(job_templates_urls)),
url(r'^jobs/', include(jobs_urls)),
url(r'^job_host_summaries/', include(job_host_summary_urls)),
url(r'^job_events/', include(job_events_urls)),
url(r'^tags/', include(tags_urls)),
)

View File

@ -95,7 +95,7 @@ class OrganizationsList(BaseList):
# I am an admin of the organization
# I am a member of the organization
def _get_queryset(self):
def get_queryset(self):
''' I can see organizations when I am a superuser, or I am an admin or user in that organization '''
base = Organization.objects
if self.request.user.is_superuser:
@ -121,7 +121,7 @@ class OrganizationsAuditTrailList(BaseSubList):
relationship = 'audit_trail'
postable = False
def _get_queryset(self):
def get_queryset(self):
''' to list tags in the organization, I must be a superuser or org admin '''
organization = Organization.objects.get(pk=self.kwargs['pk'])
if not (self.request.user.is_superuser or self.request.user in organization.admins.all()):
@ -138,7 +138,7 @@ class OrganizationsInventoriesList(BaseSubList):
relationship = 'inventories'
postable = False
def _get_queryset(self):
def get_queryset(self):
''' to list inventories in the organization, I must be a superuser or org admin '''
organization = Organization.objects.get(pk=self.kwargs['pk'])
if not (self.request.user.is_superuser or self.request.user in organization.admins.all()):
@ -157,7 +157,7 @@ class OrganizationsUsersList(BaseSubList):
inject_primary_key_on_post_as = 'organization'
filter_fields = ('username',)
def _get_queryset(self):
def get_queryset(self):
''' to list users in the organization, I must be a superuser or org admin '''
organization = Organization.objects.get(pk=self.kwargs['pk'])
if not self.request.user.is_superuser and not self.request.user in organization.admins.all():
@ -175,7 +175,7 @@ class OrganizationsAdminsList(BaseSubList):
inject_primary_key_on_post_as = 'organization'
filter_fields = ('username',)
def _get_queryset(self):
def get_queryset(self):
''' to list admins in the organization, I must be a superuser or org admin '''
organization = Organization.objects.get(pk=self.kwargs['pk'])
if not self.request.user.is_superuser and not self.request.user in organization.admins.all():
@ -193,7 +193,7 @@ class OrganizationsProjectsList(BaseSubList):
inject_primary_key_on_post_as = 'organization'
filter_fields = ('name',)
def _get_queryset(self):
def get_queryset(self):
''' to list projects in the organization, I must be a superuser or org admin '''
organization = Organization.objects.get(pk=self.kwargs['pk'])
if not (self.request.user.is_superuser or self.request.user in organization.admins.all()):
@ -211,7 +211,7 @@ class OrganizationsTagsList(BaseSubList):
inject_primary_key_on_post_as = 'organization'
filter_fields = ('name',)
def _get_queryset(self):
def get_queryset(self):
''' to list tags in the organization, I must be a superuser or org admin '''
organization = Organization.objects.get(pk=self.kwargs['pk'])
if not (self.request.user.is_superuser or self.request.user in organization.admins.all()):
@ -231,7 +231,7 @@ class OrganizationsTeamsList(BaseSubList):
severable = False
filter_fields = ('name',)
def _get_queryset(self):
def get_queryset(self):
''' to list users in the organization, I must be a superuser or org admin '''
organization = Organization.objects.get(pk=self.kwargs['pk'])
if not self.request.user.is_superuser and not self.request.user in organization.admins.all():
@ -250,7 +250,7 @@ class TeamsList(BaseList):
# I am an admin of the organization that the team is
# I am on that team
def _get_queryset(self):
def get_queryset(self):
''' I can see organizations when I am a superuser, or I am an admin or user in that organization '''
base = Team.objects
if self.request.user.is_superuser:
@ -279,7 +279,7 @@ class TeamsUsersList(BaseSubList):
severable = True
filter_fields = ('username',)
def _get_queryset(self):
def get_queryset(self):
# FIXME: audit all BaseSubLists to check for permissions on the original object too
'team members can see the whole team, as can org admins or superusers'
team = Team.objects.get(pk=self.kwargs['pk'])
@ -301,7 +301,7 @@ class TeamsPermissionsList(BaseSubList):
filter_fields = ('name',)
inject_primary_key_on_post_as = 'team'
def _get_queryset(self):
def get_queryset(self):
team = Team.objects.get(pk=self.kwargs['pk'])
base = Permission.objects.filter(team = team)
#if Team.can_user_administrate(self.request.user, team, None):
@ -326,7 +326,7 @@ class TeamsProjectsList(BaseSubList):
# FIXME: filter_fields is no longer used, think we can remove these references everywhere given new custom filtering -- MPD
filter_fields = ('name',)
def _get_queryset(self):
def get_queryset(self):
team = Team.objects.get(pk=self.kwargs['pk'])
base = team.projects.all()
if self.request.user.is_superuser or self.request.user in team.organization.admins.all():
@ -347,7 +347,7 @@ class TeamsCredentialsList(BaseSubList):
inject_primary_key_on_post_as = 'team'
filter_fields = ('name',)
def _get_queryset(self):
def get_queryset(self):
team = Team.objects.get(pk=self.kwargs['pk'])
#if not Team.can_user_administrate(self.request.user, team, None):
if not check_user_access(self.request.user, Team, 'change', team, None):
@ -371,7 +371,7 @@ class ProjectsList(BaseList):
# I am an admin of the organization that contains the project
# I am a member of a team that also contains the project
def _get_queryset(self):
def get_queryset(self):
''' I can see organizations when I am a superuser, or I am an admin or user in that organization '''
base = Project.objects
if self.request.user.is_superuser:
@ -406,7 +406,7 @@ class ProjectsOrganizationsList(BaseSubList):
postable = False
filter_fields = ('name',)
def _get_queryset(self):
def get_queryset(self):
project = Project.objects.get(pk=self.kwargs['pk'])
if not self.request.user.is_superuser:
raise PermissionDenied()
@ -435,7 +435,7 @@ class UsersList(BaseList):
user.save()
return result
def _get_queryset(self):
def get_queryset(self):
''' I can see user records when I'm a superuser, I'm that user, I'm their org admin, or I'm on a team with that user '''
base = User.objects
if self.request.user.is_superuser:
@ -458,7 +458,7 @@ class UsersMeList(BaseList):
def post(self, request, *args, **kwargs):
raise PermissionDenied()
def _get_queryset(self):
def get_queryset(self):
''' a quick way to find my user record '''
return User.objects.filter(pk=self.request.user.pk)
@ -472,7 +472,7 @@ class UsersTeamsList(BaseSubList):
postable = False
filter_fields = ('name',)
def _get_queryset(self):
def get_queryset(self):
user = User.objects.get(pk=self.kwargs['pk'])
#if not UserHelper.can_user_administrate(self.request.user, user, None):
if not check_user_access(self.request.user, User, 'change', user, None):
@ -490,7 +490,7 @@ class UsersPermissionsList(BaseSubList):
filter_fields = ('name',)
inject_primary_key_on_post_as = 'user'
def _get_queryset(self):
def get_queryset(self):
user = User.objects.get(pk=self.kwargs['pk'])
#if not UserHelper.can_user_administrate(self.request.user, user, None):
if not check_user_access(self.request.user, User, 'change', user, None):
@ -507,7 +507,7 @@ class UsersProjectsList(BaseSubList):
postable = False
filter_fields = ('name',)
def _get_queryset(self):
def get_queryset(self):
user = User.objects.get(pk=self.kwargs['pk'])
#if not UserHelper.can_user_administrate(self.request.user, user, None):
if not check_user_access(self.request.user, User, 'change', user, None):
@ -526,7 +526,7 @@ class UsersCredentialsList(BaseSubList):
inject_primary_key_on_post_as = 'user'
filter_fields = ('name',)
def _get_queryset(self):
def get_queryset(self):
user = User.objects.get(pk=self.kwargs['pk'])
#if not UserHelper.can_user_administrate(self.request.user, user, None):
if not check_user_access(self.request.user, User, 'change', user, None):
@ -546,7 +546,7 @@ class UsersOrganizationsList(BaseSubList):
postable = False
filter_fields = ('name',)
def _get_queryset(self):
def get_queryset(self):
user = User.objects.get(pk=self.kwargs['pk'])
#if not UserHelper.can_user_administrate(self.request.user, user, None):
if not check_user_access(self.request.user, User, 'change', user, None):
@ -563,7 +563,7 @@ class UsersAdminOrganizationsList(BaseSubList):
postable = False
filter_fields = ('name',)
def _get_queryset(self):
def get_queryset(self):
user = User.objects.get(pk=self.kwargs['pk'])
#if not UserHelper.can_user_administrate(self.request.user, user, None):
if not check_user_access(self.request.user, User, 'change', user, None):
@ -593,7 +593,7 @@ class CredentialsList(BaseList):
permission_classes = (CustomRbac,)
postable = False
def _get_queryset(self):
def get_queryset(self):
return get_user_queryset(self.request.user, self.model)
class CredentialsDetail(BaseDetail):
@ -629,7 +629,7 @@ class InventoryList(BaseList):
).distinct()
return admin_of | has_user_perms | has_team_perms
def _get_queryset(self):
def get_queryset(self):
''' I can see inventory when I'm a superuser, an org admin of the inventory, or I have permissions on it '''
base = Inventory.objects
return self._filter_queryset(base)
@ -647,7 +647,7 @@ class HostsList(BaseList):
permission_classes = (CustomRbac,)
filter_fields = ('name',)
def _get_queryset(self):
def get_queryset(self):
'''
I can see hosts when:
I'm a superuser,
@ -689,7 +689,7 @@ class InventoryHostsList(BaseSubList):
severable = False
filter_fields = ('name',)
def _get_queryset(self):
def get_queryset(self):
inventory = Inventory.objects.get(pk=self.kwargs['pk'])
base = inventory.hosts
# FIXME: verify that you can can_read permission on the inventory is required
@ -702,7 +702,7 @@ class GroupsList(BaseList):
permission_classes = (CustomRbac,)
filter_fields = ('name',)
def _get_queryset(self):
def get_queryset(self):
'''
I can see groups when:
I'm a superuser,
@ -734,7 +734,7 @@ class GroupsChildrenList(BaseSubList):
inject_primary_key_on_post_as = 'parent'
filter_fields = ('name',)
def _get_queryset(self):
def get_queryset(self):
# FIXME: this is the mostly the same as GroupsList, share code similar to how done with Host and Group objects.
@ -768,7 +768,7 @@ class GroupsHostsList(BaseSubList):
inject_primary_key_on_post_as = 'group'
filter_fields = ('name',)
def _get_queryset(self):
def get_queryset(self):
parent = Group.objects.get(pk=self.kwargs['pk'])
@ -812,7 +812,7 @@ class GroupsAllHostsList(BaseSubList):
result = result | self._child_hosts(child)
return result
def _get_queryset(self):
def get_queryset(self):
parent = Group.objects.get(pk=self.kwargs['pk'])
@ -856,7 +856,7 @@ class InventoryGroupsList(BaseSubList):
severable = False
filter_fields = ('name',)
def _get_queryset(self):
def get_queryset(self):
# FIXME: share code with inventory filter queryset methods (make that a classmethod)
inventory = Inventory.objects.get(pk=self.kwargs['pk'])
base = inventory.groups
@ -916,7 +916,7 @@ class JobTemplateList(BaseList):
permission_classes = (CustomRbac,)
filter_fields = ('name',)
def _get_queryset(self):
def get_queryset(self):
return get_user_queryset(self.request.user, self.model)
class JobTemplateDetail(BaseDetail):
@ -940,8 +940,8 @@ class JobTemplateJobList(BaseSubList):
severable = False
#filter_fields = ('name',)
def _get_queryset(self):
# FIxME: Verify read permission on the job template.
def get_queryset(self):
# FIXME: Verify read permission on the job template.
job_template = get_object_or_404(JobTemplate, pk=self.kwargs['pk'])
return job_template.jobs
@ -951,7 +951,7 @@ class JobList(BaseList):
serializer_class = JobSerializer
permission_classes = (CustomRbac,)
def _get_queryset(self):
def get_queryset(self):
return self.model.objects.all() # FIXME
class JobDetail(BaseDetail):
@ -1012,20 +1012,45 @@ class JobCancel(generics.RetrieveAPIView):
else:
return Response(status=405)
class JobHostsList(BaseSubList):
pass
class HostJobHostSummaryList(generics.ListAPIView):
class JobsSuccessfulHostsList(BaseSubList):
pass
model = JobHostSummary
serializer_class = JobHostSummarySerializer
permission_classes = (CustomRbac,)
parent_model = Host
relationship = 'job_host_summaries'
class JobsChangedHostsList(BaseSubList):
pass
def get_name(self):
return 'Job Host Summary List'
class JobsFailedHostsList(BaseSubList):
pass
def get_queryset(self):
# FIXME: Verify read permission on the host and job.
host = get_object_or_404(Host, pk=self.kwargs['pk'])
return host.job_host_summaries
class JobsUnreachableHostsList(BaseSubList):
pass
class JobJobHostSummaryList(generics.ListAPIView):
model = JobHostSummary
serializer_class = JobHostSummarySerializer
permission_classes = (CustomRbac,)
parent_model = Job
relationship = 'job_host_summaries'
def get_name(self):
return 'Job Host Summary List'
def get_queryset(self):
# FIXME: Verify read permission on the host and job.
job = get_object_or_404(Job, pk=self.kwargs['pk'])
return job.job_host_summaries
# FIXME: Subclasses of XJobHostSummaryList for failed/successful/etc.
class JobHostSummaryDetail(generics.RetrieveAPIView):
model = JobHostSummary
serializer_class = JobHostSummarySerializer
permission_classes = (CustomRbac,)
class JobEventList(BaseList):
@ -1033,10 +1058,10 @@ class JobEventList(BaseList):
serializer_class = JobEventSerializer
permission_classes = (CustomRbac,)
def _get_queryset(self):
def get_queryset(self):
return self.model.objects.all() # FIXME
class JobEventDetail(BaseDetail):
class JobEventDetail(generics.RetrieveAPIView):
model = JobEvent
serializer_class = JobEventSerializer
@ -1052,7 +1077,7 @@ class JobJobEventList(BaseSubList):
postable = False
severable = False
def _get_queryset(self):
def get_queryset(self):
job = get_object_or_404(Job, pk=self.kwargs['pk'])
# FIXME: Verify read permission on the job.
return job.job_events
@ -1067,7 +1092,7 @@ class HostJobEventList(BaseSubList):
postable = False
severable = False
def _get_queryset(self):
def get_queryset(self):
host = get_object_or_404(Host, pk=self.kwargs['pk'])
# FIXME: Verify read permission on the host.
return host.job_events