AC-1040 Work on unified job serializers.

This commit is contained in:
Chris Church
2014-03-25 22:04:37 -04:00
parent 62a74f9c84
commit 8ad72426b4
6 changed files with 311 additions and 256 deletions

View File

@@ -33,9 +33,6 @@ from awx.main.utils import update_scm_url, camelcase_to_underscore
logger = logging.getLogger('awx.api.serializers')
BASE_FIELDS = ('id', 'type', 'url', 'related', 'summary_fields', 'created',
'modified', 'name', 'description')
# Fields that should be summarized regardless of object type.
DEFAULT_SUMMARY_FIELDS = ('name', 'description',)
@@ -65,13 +62,15 @@ SUMMARIZABLE_FK_FIELDS = {
'has_inventory_sources'),
'project': DEFAULT_SUMMARY_FIELDS + ('status',),
'credential': DEFAULT_SUMMARY_FIELDS + ('kind', 'cloud'),
'cloud_credential': DEFAULT_SUMMARY_FIELDS + ('kind', 'cloud'),
'permission': DEFAULT_SUMMARY_FIELDS,
'job': DEFAULT_SUMMARY_FIELDS + ('status', 'failed',),
'job_template': DEFAULT_SUMMARY_FIELDS,
'last_job': DEFAULT_SUMMARY_FIELDS + ('status', 'failed',),
'last_job': DEFAULT_SUMMARY_FIELDS + ('status', 'failed', 'license_error'),
'last_job_host_summary': DEFAULT_SUMMARY_FIELDS + ('failed',),
'last_update': DEFAULT_SUMMARY_FIELDS + ('status', 'failed', 'license_error'),
'current_update': DEFAULT_SUMMARY_FIELDS + ('status', 'failed', 'license_error'),
'current_job': DEFAULT_SUMMARY_FIELDS + ('status', 'failed', 'license_error'),
'inventory_source': ('source', 'last_updated', 'status'),
}
@@ -98,13 +97,87 @@ class ChoiceField(fields.ChoiceField):
serializers.ChoiceField = ChoiceField
class BaseSerializerMetaclass(serializers.SerializerMetaclass):
'''
Custom metaclass to enable attribute inheritance from Meta objects on
serializer base classes.
Also allows for inheriting or updating field lists from base class(es):
class Meta:
# Inherit all fields from base class.
fields = ('*',)
# Inherit all fields from base class and add 'foo'.
fields = ('*', 'foo')
# Inherit all fields from base class except 'bar'.
fields = ('*', '-bar')
# Define fields as 'foo' and 'bar'; ignore base class fields.
fields = ('foo', 'bar')
'''
@classmethod
def _update_meta(self, base, meta, other=None):
for attr in dir(other):
if attr.startswith('_'):
continue
val = getattr(other, attr)
# Special handling for lists of strings (field names).
if isinstance(val, (list, tuple)) and all([isinstance(x, basestring) for x in val]):
new_vals = []
except_vals = []
if base: # Merge values from all bases.
new_vals.extend([x for x in getattr(meta, attr, [])])
for v in val:
if not base and v == '*': # Inherit all values from previous base(es).
new_vals.extend([x for x in getattr(meta, attr, [])])
elif not base and v.startswith('-'): # Except these values.
except_vals.append(v[1:])
else:
new_vals.append(v)
val = []
for v in new_vals:
if v not in except_vals and v not in val:
val.append(v)
setattr(meta, attr, val)
def __new__(cls, name, bases, attrs):
meta = type('Meta', (object,), {})
for base in bases:
cls._update_meta(base, meta, getattr(base, 'Meta', None))
cls._update_meta(None, meta, attrs.get('Meta', None))
attrs['Meta'] = meta
return super(BaseSerializerMetaclass, cls).__new__(cls, name, bases, attrs)
class BaseSerializerOptions(serializers.ModelSerializerOptions):
def __init__(self, meta):
super(BaseSerializerOptions, self).__init__(meta)
self.summary_fields = getattr(meta, 'summary_fields', ())
self.summarizable_fields = getattr(meta, 'summarizable_fields', ())
class BaseSerializer(serializers.ModelSerializer):
__metaclass__ = BaseSerializerMetaclass
_options_class = BaseSerializerOptions
class Meta:
fields = ('id', 'type', 'url', 'related', 'summary_fields', 'created',
'modified', 'name', 'description')
summary_fields = () # FIXME: List of field names from this serializer that should be used when included as part of another's summary_fields.
summarizable_fields = () # FIXME: List of field names on this serializer that should be included in summary_fields.
# add the URL and related resources
type = serializers.SerializerMethodField('get_type')
url = serializers.SerializerMethodField('get_url')
related = serializers.SerializerMethodField('get_related')
summary_fields = serializers.SerializerMethodField('get_summary_fields')
related = serializers.SerializerMethodField('_get_related')
summary_fields = serializers.SerializerMethodField('_get_summary_fields')
# make certain fields read only
created = serializers.SerializerMethodField('get_created')
@@ -149,6 +222,9 @@ class BaseSerializer(serializers.ModelSerializer):
else:
return obj.get_absolute_url()
def _get_related(self, obj):
return {} if obj is None else self.get_related(obj)
def get_related(self, obj):
res = SortedDict()
if getattr(obj, 'created_by', None) and obj.created_by.is_active:
@@ -157,6 +233,9 @@ class BaseSerializer(serializers.ModelSerializer):
res['modified_by'] = reverse('api:user_detail', args=(obj.modified_by.pk,))
return res
def _get_summary_fields(self, obj):
return {} if obj is None else self.get_summary_fields(obj)
def get_summary_fields(self, obj):
# Return values for certain fields on related objects, to simplify
# displaying lists of items without additional API requests.
@@ -166,6 +245,8 @@ class BaseSerializer(serializers.ModelSerializer):
fkval = getattr(obj, fk, None)
if fkval is None:
continue
if fkval == obj:
continue
if hasattr(fkval, 'active') and not fkval.active:
continue
if hasattr(fkval, 'is_active') and not fkval.is_active:
@@ -205,6 +286,43 @@ class BaseSerializer(serializers.ModelSerializer):
return obj.active
class UnifiedJobTemplateSerializer(BaseSerializer):
class Meta:
model = UnifiedJobTemplate
fields = ('*', 'last_job_run', 'last_job_failed', 'has_schedules',
'next_job_run', 'status')
def get_related(self, obj):
res = super(UnifiedJobTemplateSerializer, self).get_related(obj)
if obj.current_job and obj.current_job.active:
res['current_job'] = obj.current_job.get_absolute_url()
if obj.last_job and obj.last_job.active:
res['last_job'] = obj.last_job.get_absolute_url()
if obj.next_schedule and obj.next_schedule.active:
res['next_schedule'] = obj.next_schedule.get_absolute_url()
return res
class UnifiedJobSerializer(BaseSerializer):
result_stdout = serializers.Field(source='result_stdout')
class Meta:
model = UnifiedJob
fields = ('*', 'unified_job_template', 'launch_type', 'status',
'failed', 'started', 'finished', 'elapsed', 'job_args',
'job_cwd', 'job_env', 'result_stdout', 'result_traceback')
def get_related(self, obj):
res = super(UnifiedJobSerializer, self).get_related(obj)
if obj.unified_job_template and obj.unified_job_template.active:
res['unified_job_template'] = obj.unified_job_template.get_absolute_url()
if obj.schedule and obj.schedule.active:
res['schedule'] = obj.schedule.get_absolute_url()
return res
class BaseTaskSerializer(BaseSerializer):
job_env = serializers.SerializerMethodField('get_job_env')
@@ -224,9 +342,9 @@ class UserSerializer(BaseSerializer):
class Meta:
model = User
fields = ('id', 'type', 'url', 'related', 'created', 'username',
'first_name', 'last_name', 'email', 'is_superuser',
'password', 'ldap_dn')
fields = ('*', '-name', '-description', '-modified',
'-summary_fields', 'username', 'first_name', 'last_name',
'email', 'is_superuser', 'password', 'ldap_dn')
def to_native(self, obj):
ret = super(UserSerializer, self).to_native(obj)
@@ -261,8 +379,6 @@ class UserSerializer(BaseSerializer):
return super(UserSerializer, self).save_object(obj, **kwargs)
def get_related(self, obj):
if obj is None:
return {}
res = super(UserSerializer, self).get_related(obj)
res.update(dict(
teams = reverse('api:user_teams_list', args=(obj.pk,)),
@@ -309,69 +425,32 @@ class OrganizationSerializer(BaseSerializer):
class Meta:
model = Organization
fields = BASE_FIELDS
fields = ('*',)
def get_related(self, obj):
if obj is None:
return {}
res = super(OrganizationSerializer, self).get_related(obj)
res.update(dict(
#audit_trail = reverse('api:organization_audit_trail_list', args=(obj.pk,)),
projects = reverse('api:organization_projects_list', args=(obj.pk,)),
inventories = reverse('api:organization_inventories_list', args=(obj.pk,)),
users = reverse('api:organization_users_list', args=(obj.pk,)),
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
class ProjectSerializer(BaseSerializer):
playbooks = serializers.Field(source='playbooks', help_text='Array of playbooks available within this project.')
scm_delete_on_next_update = serializers.Field(source='scm_delete_on_next_update')
last_update_failed = serializers.Field(source='last_update_failed')
last_updated = serializers.Field(source='last_updated')
class ProjectOptionsSerializer(BaseSerializer):
class Meta:
model = Project
fields = BASE_FIELDS + ('local_path', 'scm_type', 'scm_url',
'scm_branch', 'scm_clean',
'scm_delete_on_update', 'scm_delete_on_next_update',
'scm_update_on_launch', 'credential',
'last_job_failed', 'status', 'last_job_run') +\
('last_update_failed', 'last_updated',) # Backwards compatibility
fields = ('*', 'local_path', 'scm_type', 'scm_url', 'scm_branch',
'scm_clean', 'scm_delete_on_update', 'credential')
def get_related(self, obj):
if obj is None:
return {}
res = super(ProjectSerializer, self).get_related(obj)
res.update(dict(
organizations = reverse('api:project_organizations_list', args=(obj.pk,)),
teams = reverse('api:project_teams_list', args=(obj.pk,)),
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_stream = reverse('api:project_activity_stream_list', args=(obj.pk,)),
))
res = super(ProjectOptionsSerializer, self).get_related(obj)
if obj.credential and obj.credential.active:
res['credential'] = reverse('api:credential_detail',
args=(obj.credential.pk,))
if obj.current_job:
res['current_job'] = reverse('api:project_update_detail',
args=(obj.current_job.pk,))
if obj.last_job:
res['last_job'] = reverse('api:project_update_detail',
args=(obj.last_job.pk,))
# Backwards compatibility.
if obj.current_update:
res['current_update'] = reverse('api:project_update_detail',
args=(obj.current_update.pk,))
if obj.last_update:
res['last_update'] = reverse('api:project_update_detail',
args=(obj.last_update.pk,))
return res
def validate_local_path(self, attrs, source):
@@ -391,12 +470,44 @@ class ProjectSerializer(BaseSerializer):
return attrs
def to_native(self, obj):
ret = super(ProjectSerializer, self).to_native(obj)
ret = super(ProjectOptionsSerializer, self).to_native(obj)
if obj is not None and 'credential' in ret and (not obj.credential or not obj.credential.active):
ret['credential'] = None
return ret
class ProjectSerializer(UnifiedJobTemplateSerializer, ProjectOptionsSerializer):
playbooks = serializers.Field(source='playbooks', help_text='Array of playbooks available within this project.')
scm_delete_on_next_update = serializers.Field(source='scm_delete_on_next_update')
last_update_failed = serializers.Field(source='last_update_failed')
last_updated = serializers.Field(source='last_updated')
class Meta:
model = Project
fields = ('*', 'scm_delete_on_next_update', 'scm_update_on_launch') + \
('last_update_failed', 'last_updated') # Backwards compatibility
def get_related(self, obj):
res = super(ProjectSerializer, self).get_related(obj)
res.update(dict(
organizations = reverse('api:project_organizations_list', args=(obj.pk,)),
teams = reverse('api:project_teams_list', args=(obj.pk,)),
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_stream = reverse('api:project_activity_stream_list', args=(obj.pk,)),
))
# Backwards compatibility.
if obj.current_update:
res['current_update'] = reverse('api:project_update_detail',
args=(obj.current_update.pk,))
if obj.last_update:
res['last_update'] = reverse('api:project_update_detail',
args=(obj.last_update.pk,))
return res
class ProjectPlaybooksSerializer(ProjectSerializer):
class Meta:
@@ -408,19 +519,13 @@ class ProjectPlaybooksSerializer(ProjectSerializer):
return ret.get('playbooks', [])
class ProjectUpdateSerializer(BaseTaskSerializer):
result_stdout = serializers.Field(source='result_stdout')
class ProjectUpdateSerializer(UnifiedJobSerializer, ProjectOptionsSerializer):
class Meta:
model = ProjectUpdate
fields = ('id', 'type', 'url', 'related', 'summary_fields', 'created',
'modified', 'project', 'status', 'failed', 'result_stdout',
'result_traceback', 'job_args', 'job_cwd', 'job_env')
fields = ('*', 'project')
def get_related(self, obj):
if obj is None:
return {}
res = super(ProjectUpdateSerializer, self).get_related(obj)
res.update(dict(
project = reverse('api:project_detail', args=(obj.project.pk,)),
@@ -446,17 +551,12 @@ class InventorySerializer(BaseSerializerWithVariables):
class Meta:
model = Inventory
fields = BASE_FIELDS + ('organization', 'variables',
'has_active_failures', 'total_hosts',
'hosts_with_active_failures', 'total_groups',
'groups_with_active_failures',
'has_inventory_sources',
'total_inventory_sources',
'inventory_sources_with_failures',)
fields = ('*', 'organization', 'variables', 'has_active_failures',
'total_hosts', 'hosts_with_active_failures', 'total_groups',
'groups_with_active_failures', 'has_inventory_sources',
'total_inventory_sources', 'inventory_sources_with_failures')
def get_related(self, obj):
if obj is None:
return {}
res = super(InventorySerializer, self).get_related(obj)
res.update(dict(
hosts = reverse('api:inventory_hosts_list', args=(obj.pk,)),
@@ -481,19 +581,14 @@ class InventorySerializer(BaseSerializerWithVariables):
class HostSerializer(BaseSerializerWithVariables):
# Allow the serializer to treat these fields as read-only
last_job = serializers.PrimaryKeyRelatedField(read_only=True)
last_job_host_summary = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Host
fields = BASE_FIELDS + ('inventory', 'enabled', 'instance_id', 'variables',
'has_active_failures', 'has_inventory_sources',
'last_job', 'last_job_host_summary')
fields = ('*', 'inventory', 'enabled', 'instance_id', 'variables',
'has_active_failures', 'has_inventory_sources', 'last_job',
'last_job_host_summary')
readonly_fields = ('last_job', 'last_job_host_summary')
def get_related(self, obj):
if obj is None:
return {}
res = super(HostSerializer, self).get_related(obj)
res.update(dict(
variable_data = reverse('api:host_variable_data', args=(obj.pk,)),
@@ -513,8 +608,6 @@ class HostSerializer(BaseSerializerWithVariables):
return res
def get_summary_fields(self, obj):
if obj is None:
return {}
d = super(HostSerializer, self).get_summary_fields(obj)
try:
d['last_job']['job_template_id'] = obj.last_job.job_template.id
@@ -600,14 +693,11 @@ class GroupSerializer(BaseSerializerWithVariables):
class Meta:
model = Group
fields = BASE_FIELDS + ('inventory', 'variables', 'has_active_failures',
'total_hosts', 'hosts_with_active_failures',
'total_groups', 'groups_with_active_failures',
'has_inventory_sources')
fields = ('*', 'inventory', 'variables', 'has_active_failures',
'total_hosts', 'hosts_with_active_failures', 'total_groups',
'groups_with_active_failures', 'has_inventory_sources')
def get_related(self, obj):
if obj is None:
return {}
res = super(GroupSerializer, self).get_related(obj)
res.update(dict(
variable_data = reverse('api:group_variable_data', args=(obj.pk,)),
@@ -645,10 +735,7 @@ class GroupTreeSerializer(GroupSerializer):
class Meta:
model = Group
fields = BASE_FIELDS + ('inventory', 'variables', 'has_active_failures',
'total_hosts', 'hosts_with_active_failures',
'total_groups', 'groups_with_active_failures',
'has_inventory_sources', 'children')
fields = ('*', 'children')
def get_children(self, obj):
if obj is None:
@@ -661,6 +748,9 @@ class GroupTreeSerializer(GroupSerializer):
class BaseVariableDataSerializer(BaseSerializer):
class Meta:
fields = ('variables',)
def to_native(self, obj):
if obj is None:
return {}
@@ -679,78 +769,33 @@ class InventoryVariableDataSerializer(BaseVariableDataSerializer):
class Meta:
model = Inventory
fields = ('variables',)
class HostVariableDataSerializer(BaseVariableDataSerializer):
class Meta:
model = Host
fields = ('variables',)
class GroupVariableDataSerializer(BaseVariableDataSerializer):
class Meta:
model = Group
fields = ('variables',)
class InventorySourceSerializer(BaseSerializer):
class InventorySourceOptionsSerializer(BaseSerializer):
#source_password = serializers.WritableField(required=False, default='')
last_update_failed = serializers.Field(source='last_update_failed')
last_updated = serializers.Field(source='last_updated')
class Meta:
model = InventorySource
fields = ('id', 'type', 'url', 'related', 'summary_fields', 'created',
'modified', 'inventory', 'group', 'source', 'source_path',
'source_vars', 'credential', 'source_regions', 'overwrite',
'overwrite_vars', 'update_on_launch', 'last_job_failed',
'status', 'last_job_run') + \
('last_update_failed', 'last_updated') # Backwards compatibility.
read_only_fields = ('inventory', 'group')
fields = ('*', 'source', 'source_path', 'source_vars', 'credential',
'source_regions', 'overwrite', 'overwrite_vars')
def get_related(self, obj):
if obj is None:
return {}
res = super(InventorySourceSerializer, self).get_related(obj)
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,)),
))
if obj.inventory and obj.inventory.active:
res['inventory'] = reverse('api:inventory_detail', args=(obj.inventory.pk,))
if obj.group and obj.group.active:
res['group'] = reverse('api:group_detail', args=(obj.group.pk,))
res = super(InventorySourceOptionsSerializer, self).get_related(obj)
if obj.credential and obj.credential.active:
res['credential'] = reverse('api:credential_detail',
args=(obj.credential.pk,))
if obj.current_job:
res['current_job'] = reverse('api:inventory_update_detail',
args=(obj.current_job.pk,))
if obj.last_job:
res['last_job'] = reverse('api:inventory_update_detail',
args=(obj.last_job.pk,))
# Backwards compatibility.
if obj.current_update:
res['current_update'] = reverse('api:inventory_update_detail',
args=(obj.current_update.pk,))
if obj.last_update:
res['last_update'] = reverse('api:inventory_update_detail',
args=(obj.last_update.pk,))
return res
def get_summary_fields(self, obj):
if obj is None:
return {}
d = super(InventorySourceSerializer, self).get_summary_fields(obj)
return d
def validate_source(self, attrs, source):
src = attrs.get(source, '')
obj = self.object
@@ -777,12 +822,55 @@ class InventorySourceSerializer(BaseSerializer):
return attrs
def metadata(self):
metadata = super(InventorySourceSerializer, self).metadata()
metadata = super(InventorySourceOptionsSerializer, self).metadata()
field_opts = metadata.get('source_regions', {})
field_opts['ec2_region_choices'] = self.opts.model.get_ec2_region_choices()
field_opts['rax_region_choices'] = self.opts.model.get_rax_region_choices()
return metadata
def to_native(self, obj):
ret = super(InventorySourceOptionsSerializer, self).to_native(obj)
if obj is None:
return ret
if 'credential' in ret and (not obj.credential or not obj.credential.active):
ret['credential'] = None
return ret
class InventorySourceSerializer(UnifiedJobTemplateSerializer, InventorySourceOptionsSerializer):
last_update_failed = serializers.Field(source='last_update_failed')
last_updated = serializers.Field(source='last_updated')
class Meta:
model = InventorySource
fields = ('*', 'inventory', 'group', 'update_on_launch',
'update_cache_timeout') + \
('last_update_failed', 'last_updated') # Backwards compatibility.
read_only_fields = ('*', 'name', 'inventory', 'group')
def get_related(self, obj):
res = super(InventorySourceSerializer, self).get_related(obj)
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,)),
))
if obj.inventory and obj.inventory.active:
res['inventory'] = reverse('api:inventory_detail', args=(obj.inventory.pk,))
if obj.group and obj.group.active:
res['group'] = reverse('api:group_detail', args=(obj.group.pk,))
# Backwards compatibility.
if obj.current_update:
res['current_update'] = reverse('api:inventory_update_detail',
args=(obj.current_update.pk,))
if obj.last_update:
res['last_update'] = reverse('api:inventory_update_detail',
args=(obj.last_update.pk,))
return res
def to_native(self, obj):
ret = super(InventorySourceSerializer, self).to_native(obj)
if obj is None:
@@ -791,25 +879,16 @@ class InventorySourceSerializer(BaseSerializer):
ret['inventory'] = None
if 'group' in ret and (not obj.group or not obj.group.active):
ret['group'] = None
if 'credential' in ret and (not obj.credential or not obj.credential.active):
ret['credential'] = None
return ret
class InventoryUpdateSerializer(BaseTaskSerializer):
result_stdout = serializers.Field(source='result_stdout')
class InventoryUpdateSerializer(UnifiedJobSerializer, InventorySourceOptionsSerializer):
class Meta:
model = InventoryUpdate
fields = ('id', 'type', 'url', 'related', 'summary_fields', 'created',
'modified', 'inventory_source', 'status', 'failed',
'result_stdout', 'result_traceback', 'job_args', 'job_cwd',
'job_env', 'license_error')
fields = ('*', 'inventory_source', 'license_error')
def get_related(self, obj):
if obj is None:
return {}
res = super(InventoryUpdateSerializer, self).get_related(obj)
res.update(dict(
inventory_source = reverse('api:inventory_source_detail', args=(obj.inventory_source.pk,)),
@@ -822,11 +901,9 @@ class TeamSerializer(BaseSerializer):
class Meta:
model = Team
fields = BASE_FIELDS + ('organization',)
fields = ('*', 'organization')
def get_related(self, obj):
if obj is None:
return {}
res = super(TeamSerializer, self).get_related(obj)
res.update(dict(
projects = reverse('api:team_projects_list', args=(obj.pk,)),
@@ -835,7 +912,7 @@ class TeamSerializer(BaseSerializer):
permissions = reverse('api:team_permissions_list', args=(obj.pk,)),
activity_stream = reverse('api:team_activity_stream_list', args=(obj.pk,)),
))
if obj.organization:
if obj.organization and obj.organization.active:
res['organization'] = reverse('api:organization_detail', args=(obj.organization.pk,))
return res
@@ -850,20 +927,18 @@ class PermissionSerializer(BaseSerializer):
class Meta:
model = Permission
fields = BASE_FIELDS + ('user', 'team', 'project', 'inventory',
'permission_type',)
fields = ('*', 'user', 'team', 'project', 'inventory',
'permission_type')
def get_related(self, obj):
if obj is None:
return {}
res = super(PermissionSerializer, self).get_related(obj)
if obj.user:
if obj.user and obj.user.is_active:
res['user'] = reverse('api:user_detail', args=(obj.user.pk,))
if obj.team:
if obj.team and obj.team.active:
res['team'] = reverse('api:team_detail', args=(obj.team.pk,))
if obj.project:
if obj.project and obj.project.active:
res['project'] = reverse('api:project_detail', args=(obj.project.pk,))
if obj.inventory:
if obj.inventory and obj.inventory.active:
res['inventory'] = reverse('api:inventory_detail', args=(obj.inventory.pk,))
return res
@@ -908,9 +983,9 @@ class CredentialSerializer(BaseSerializer):
class Meta:
model = Credential
fields = BASE_FIELDS + ('user', 'team', 'kind', 'cloud', 'username',
'password', 'ssh_key_data', 'ssh_key_unlock',
'sudo_username', 'sudo_password',)
fields = ('*', 'user', 'team', 'kind', 'cloud', 'username',
'password', 'ssh_key_data', 'ssh_key_unlock',
'sudo_username', 'sudo_password')
def to_native(self, obj):
ret = super(CredentialSerializer, self).to_native(obj)
@@ -933,8 +1008,6 @@ class CredentialSerializer(BaseSerializer):
return instance
def get_related(self, obj):
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,))
@@ -946,25 +1019,15 @@ class CredentialSerializer(BaseSerializer):
return res
class JobTemplateSerializer(BaseSerializer):
class JobOptionsSerializer(BaseSerializer):
class Meta:
model = JobTemplate
fields = BASE_FIELDS + ('job_type', 'inventory', 'project', 'playbook',
'credential', 'cloud_credential', 'forks',
'limit', 'verbosity', 'extra_vars', 'job_tags',
'host_config_key')
fields = ('*', 'job_type', 'inventory', 'project', 'playbook',
'credential', 'cloud_credential', 'forks', 'limit',
'verbosity', 'extra_vars', 'job_tags')
def get_related(self, obj):
if obj is None:
return {}
res = super(JobTemplateSerializer, self).get_related(obj)
res.update(dict(
jobs = reverse('api:job_template_jobs_list', args=(obj.pk,)),
activity_stream = reverse('api:job_template_activity_stream_list', args=(obj.pk,)),
))
if obj is None:
return ret
res = super(JobOptionsSerializer, self).get_related(obj)
if obj.inventory and obj.inventory.active:
res['inventory'] = reverse('api:inventory_detail', args=(obj.inventory.pk,))
if obj.project and obj.project.active:
@@ -974,12 +1037,10 @@ class JobTemplateSerializer(BaseSerializer):
if obj.cloud_credential and obj.cloud_credential.active:
res['cloud_credential'] = reverse('api:credential_detail',
args=(obj.cloud_credential.pk,))
if obj.host_config_key:
res['callback'] = reverse('api:job_template_callback', args=(obj.pk,))
return res
def to_native(self, obj):
ret = super(JobTemplateSerializer, self).to_native(obj)
ret = super(JobOptionsSerializer, self).to_native(obj)
if obj is None:
return ret
if 'inventory' in ret and (not obj.inventory or not obj.inventory.active):
@@ -1002,24 +1063,32 @@ class JobTemplateSerializer(BaseSerializer):
return attrs
class JobSerializer(BaseTaskSerializer):
class JobTemplateSerializer(UnifiedJobTemplateSerializer, JobOptionsSerializer):
class Meta:
model = JobTemplate
fields = ('*', 'host_config_key')
def get_related(self, obj):
res = super(JobTemplateSerializer, self).get_related(obj)
res.update(dict(
jobs = reverse('api:job_template_jobs_list', args=(obj.pk,)),
activity_stream = reverse('api:job_template_activity_stream_list', args=(obj.pk,)),
))
if obj.host_config_key:
res['callback'] = reverse('api:job_template_callback', args=(obj.pk,))
return res
class JobSerializer(UnifiedJobSerializer, JobOptionsSerializer):
passwords_needed_to_start = serializers.Field(source='passwords_needed_to_start')
result_stdout = serializers.Field(source='result_stdout')
class Meta:
model = Job
fields = ('id', 'type', 'url', 'related', 'summary_fields', 'created',
'modified', 'job_template', 'job_type', 'inventory',
'project', 'playbook', 'credential', 'cloud_credential',
'forks', 'limit', 'verbosity', 'extra_vars',
'job_tags', 'launch_type', 'status', 'failed', 'result_stdout',
'result_traceback', 'passwords_needed_to_start', 'job_args',
'job_cwd', 'job_env')
fields = ('*', 'job_template', 'passwords_needed_to_start')
def get_related(self, obj):
if obj is None:
return {}
res = super(JobSerializer, self).get_related(obj)
res.update(dict(
job_events = reverse('api:job_job_events_list', args=(obj.pk,)),
@@ -1029,18 +1098,6 @@ class JobSerializer(BaseTaskSerializer):
if obj.job_template and obj.job_template.active:
res['job_template'] = reverse('api:job_template_detail',
args=(obj.job_template.pk,))
if obj.inventory and obj.inventory.active:
res['inventory'] = reverse('api:inventory_detail',
args=(obj.inventory.pk,))
if obj.project and obj.project.active:
res['project'] = reverse('api:project_detail',
args=(obj.project.pk,))
if obj.credential and obj.credential.active:
res['credential'] = reverse('api:credential_detail',
args=(obj.credential.pk,))
if obj.cloud_credential and obj.cloud_credential.active:
res['cloud_credential'] = reverse('api:credential_detail',
args=(obj.cloud_credential.pk,))
if obj.can_start or True:
res['start'] = reverse('api:job_start', args=(obj.pk,))
if obj.can_cancel or True:
@@ -1080,16 +1137,6 @@ class JobSerializer(BaseTaskSerializer):
return ret
if 'job_template' in ret and (not obj.job_template or not obj.job_template.active):
ret['job_template'] = None
if 'inventory' in ret and (not obj.inventory or not obj.inventory.active):
ret['inventory'] = None
if 'project' in ret and (not obj.project or not obj.project.active):
ret['project'] = None
if 'playbook' in ret:
ret['playbook'] = ''
if 'credential' in ret and (not obj.credential or not obj.credential.active):
ret['credential'] = None
if 'cloud_credential' in ret and (not obj.cloud_credential or not obj.cloud_credential.active):
ret['cloud_credential'] = None
return ret
@@ -1097,26 +1144,17 @@ class JobListSerializer(JobSerializer):
class Meta:
model = Job
fields = ('id', 'type', 'url', 'related', 'summary_fields', 'created',
'modified', 'job_template', 'job_type', 'inventory',
'project', 'playbook', 'credential', 'cloud_credential',
'forks', 'limit', 'verbosity', 'extra_vars',
'job_tags', 'launch_type', 'status', 'failed',
'result_traceback', 'passwords_needed_to_start', 'job_args',
'job_cwd', 'job_env')
fields = ('*', '-result_stdout')
class JobHostSummarySerializer(BaseSerializer):
class Meta:
model = JobHostSummary
fields = ('id', 'type', 'url', 'job', 'host', 'created', 'modified',
'summary_fields', 'related', 'changed', 'dark', 'failures',
'ok', 'processed', 'skipped', 'failed')
fields = ('*', '-name', '-description', 'job', 'host', 'changed',
'dark', 'failures', 'ok', 'processed', 'skipped', 'failed')
def get_related(self, obj):
if obj is None:
return {}
res = super(JobHostSummarySerializer, self).get_related(obj)
res.update(dict(
job=reverse('api:job_detail', args=(obj.job.pk,)),
@@ -1125,8 +1163,6 @@ class JobHostSummarySerializer(BaseSerializer):
return res
def get_summary_fields(self, obj):
if obj is None:
return {}
d = super(JobHostSummarySerializer, self).get_summary_fields(obj)
try:
d['job']['job_template_id'] = obj.job.job_template.id
@@ -1143,14 +1179,11 @@ class JobEventSerializer(BaseSerializer):
class Meta:
model = JobEvent
fields = ('id', 'type', 'url', 'created', 'modified', 'job', 'event',
fields = ('*', '-name', '-description', 'job', 'event',
'event_display', 'event_data', 'event_level', 'failed',
'changed', 'host', 'related', 'summary_fields', 'parent',
'play', 'task')
'changed', 'host', 'parent', 'play', 'task')
def get_related(self, obj):
if obj is None:
return {}
res = super(JobEventSerializer, self).get_related(obj)
res.update(dict(
job = reverse('api:job_detail', args=(obj.job.pk,)),
@@ -1167,8 +1200,6 @@ class JobEventSerializer(BaseSerializer):
return res
def get_summary_fields(self, obj):
if obj is None:
return {}
d = super(JobEventSerializer, self).get_summary_fields(obj)
try:
d['job']['job_template_id'] = obj.job.job_template.id
@@ -1177,13 +1208,31 @@ class JobEventSerializer(BaseSerializer):
pass
return d
class ScheduleSerializer(BaseSerializer):
class Meta:
model = Schedule
fields = ('*', 'unified_job_template', 'enabled', 'dtstart', 'dtend',
'rrule', 'next_run')
def get_related(self, obj):
res = super(ScheduleSerializer, self).get_related(obj)
res.update(dict(
#unified_jobs = reverse('api:schedule_unified_jobs_list', args=(obj.pk,)),
))
if obj.unified_job_template and obj.unified_job_template.active:
res['unified_job_template'] = obj.unified_job_template.get_absolute_url()
return res
class ActivityStreamSerializer(BaseSerializer):
changes = serializers.SerializerMethodField('get_changes')
class Meta:
model = ActivityStream
fields = ('id', 'type', 'url', 'related', 'summary_fields',
fields = ('*', '-name', '-description', '-created', '-modified',
'timestamp', 'operation', 'changes', 'object1', 'object2')
def get_fields(self):
@@ -1197,10 +1246,8 @@ class ActivityStreamSerializer(BaseSerializer):
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:
return {}
@@ -1214,8 +1261,6 @@ class ActivityStreamSerializer(BaseSerializer):
return {}
def get_related(self, obj):
if obj is None:
return {}
rel = {}
if obj.actor is not None:
rel['actor'] = reverse('api:user_detail', args=(obj.actor.pk,))
@@ -1267,6 +1312,7 @@ class ActivityStreamSerializer(BaseSerializer):
last_name = obj.actor.last_name)
return summary_fields
class AuthTokenSerializer(serializers.Serializer):
username = serializers.CharField()

View File

@@ -631,8 +631,8 @@ class InventorySource(UnifiedJobTemplate, InventorySourceOptions):
@classmethod
def _get_unified_job_field_names(cls):
return ['source', 'source_path', 'source_vars', 'credential',
'source_regions', 'overwrite', 'overwrite_vars']
return ['name', 'description', 'source', 'source_path', 'source_vars',
'credential', 'source_regions', 'overwrite', 'overwrite_vars']
def save(self, *args, **kwargs):
# If update_fields has been specified, add our field names to it,

View File

@@ -151,9 +151,9 @@ class JobTemplate(UnifiedJobTemplate, JobOptions):
@classmethod
def _get_unified_job_field_names(cls):
return ['job_type', 'inventory', 'project', 'playbook', 'credential',
'cloud_credential', 'forks', 'limit', 'verbosity',
'extra_vars', 'job_tags']
return ['name', 'description', 'job_type', 'inventory', 'project',
'playbook', 'credential', 'cloud_credential', 'forks',
'limit', 'verbosity', 'extra_vars', 'job_tags']
def create_job(self, **kwargs):
'''

View File

@@ -219,8 +219,9 @@ class Project(UnifiedJobTemplate, ProjectOptions):
@classmethod
def _get_unified_job_field_names(cls):
return ['local_path', 'scm_type', 'scm_url', 'scm_branch',
'scm_clean', 'scm_delete_on_update', 'credential']
return ['name', 'description', 'local_path', 'scm_type', 'scm_url',
'scm_branch', 'scm_clean', 'scm_delete_on_update',
'credential']
def save(self, *args, **kwargs):
new_instance = not bool(self.pk)

View File

@@ -185,10 +185,16 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique):
@classmethod
def _get_unified_job_class(cls):
'''
Return subclass of UnifiedJob that is created from this template.
'''
raise NotImplementedError # Implement in subclass.
@classmethod
def _get_unified_job_field_names(cls):
'''
Return field names that should be copied from template to new job.
'''
raise NotImplementedError # Implement in subclass.
def _create_unified_job_instance(self, **kwargs):

View File

@@ -460,7 +460,9 @@ class JobTemplateTest(BaseJobTestMixin, django.test.TestCase):
'modified', 'name', 'description', 'job_type',
'inventory', 'project', 'playbook', 'credential',
'cloud_credential', 'forks', 'limit', 'verbosity',
'extra_vars', 'job_tags', 'host_config_key',)
'extra_vars', 'job_tags', 'host_config_key',
'status', 'next_job_run', 'has_schedules',
'last_job_run', 'last_job_failed')
def test_get_job_template_list(self):
url = reverse('api:job_template_list')