# Copyright (c) 2013 AnsibleWorks, Inc. # All Rights Reserved # Django from django.contrib.auth.models import User from django.core.urlresolvers import reverse from django.core.exceptions import ObjectDoesNotExist # Django REST framework from rest_framework import serializers, pagination from rest_framework.templatetags.rest_framework import replace_query_param # Ansible Commander from lib.main.models import * BASE_FIELDS = ('id', 'url', 'related', 'summary_fields', 'created', 'creation_date', 'name', 'description') class NextPageField(pagination.NextPageField): ''' makes the pagination relative URL not full URL ''' def to_native(self, value): if not value.has_next(): return None page = value.next_page_number() request = self.context.get('request') url = request and request.get_full_path() or '' return replace_query_param(url, self.page_field, page) class PreviousPageField(pagination.NextPageField): ''' makes the pagination relative URL not full URL ''' def to_native(self, value): if not value.has_previous(): return None page = value.previous_page_number() request = self.context.get('request') url = request and request.get_full_path() or '' return replace_query_param(url, self.page_field, page) class PaginationSerializer(pagination.BasePaginationSerializer): ''' Custom pagination serializer to output only URL path (without host/port). ''' count = serializers.Field(source='paginator.count') next = NextPageField(source='*') previous = PreviousPageField(source='*') # objects that if found we should add summary info for them SUMMARIZABLE_FKS = ( 'organization', 'host', 'group', 'inventory', 'project', 'team', 'job', 'job_template', 'credential', 'permission' ) # fields that should be summarized regardless of object type SUMMARIZABLE_FIELDS = ( 'name', 'username', 'first_name', 'last_name', 'description' ) class BaseSerializer(serializers.ModelSerializer): # add the URL and related resources url = serializers.SerializerMethodField('get_absolute_url') related = serializers.SerializerMethodField('get_related') summary_fields = serializers.SerializerMethodField('get_summary_fields') # make certain fields read only created = serializers.SerializerMethodField('get_created') creation_date = serializers.SerializerMethodField('get_creation_date') # FIXME: temporarily left this field in case anything uses it.. should be removed. active = serializers.SerializerMethodField('get_active') def get_absolute_url(self, obj): if isinstance(obj, User): return reverse('main:users_detail', args=(obj.pk,)) else: return obj.get_absolute_url() def get_related(self, obj): res = dict() if getattr(obj, 'created_by', None): res['created_by'] = reverse('main:users_detail', args=(obj.created_by.pk,)) return res def get_summary_fields(self, obj): # return the names (at least) for various fields, so we don't have to write this # method for each object. summary_fields = {} for fk in SUMMARIZABLE_FKS: try: fkval = getattr(obj, fk, None) if fkval is not None: summary_fields[fk] = {} for field in SUMMARIZABLE_FIELDS: fval = getattr(fkval, field, None) if fval is not None: summary_fields[fk][field] = fval # Can be raised by the reverse accessor for a OneToOneField. except ObjectDoesNotExist: pass return summary_fields def get_creation_date(self, obj): if isinstance(obj, User): return obj.date_joined.date() else: return obj.created.date() def get_created(self, obj): if isinstance(obj, User): return obj.date_joined else: return obj.created def get_active(self, obj): if isinstance(obj, User): return obj.is_active else: return obj.active class OrganizationSerializer(BaseSerializer): class Meta: model = Organization fields = BASE_FIELDS def get_related(self, obj): res = super(OrganizationSerializer, self).get_related(obj) res.update(dict( audit_trail = reverse('main:organizations_audit_trail_list', args=(obj.pk,)), projects = reverse('main:organizations_projects_list', args=(obj.pk,)), inventories = reverse('main:organizations_inventories_list', args=(obj.pk,)), users = reverse('main:organizations_users_list', args=(obj.pk,)), admins = reverse('main:organizations_admins_list', args=(obj.pk,)), tags = reverse('main:organizations_tags_list', args=(obj.pk,)), teams = reverse('main:organizations_teams_list', args=(obj.pk,)), )) return res class AuditTrailSerializer(BaseSerializer): class Meta: model = AuditTrail fields = ('url', 'id', 'modified_by', 'delta', 'detail', 'comment') def get_related(self, obj): res = super(AuditTrailSerializer, self).get_related(obj) if obj.modified_by: res['modified_by'] = reverse('main:users_detail', args=(obj.modified_by.pk,)) return res class ProjectSerializer(BaseSerializer): playbooks = serializers.Field(source='playbooks') local_path_choices = serializers.SerializerMethodField('get_local_path_choices') class Meta: model = Project fields = BASE_FIELDS + ('local_path', 'local_path_choices') # 'default_playbook', 'scm_type') def get_related(self, obj): res = super(ProjectSerializer, self).get_related(obj) res.update(dict( organizations = reverse('main:projects_organizations_list', args=(obj.pk,)), playbooks = reverse('main:projects_detail_playbooks', args=(obj.pk,)), )) return res def get_local_path_choices(self, obj): return Project.get_local_path_choices() class ProjectPlaybooksSerializer(ProjectSerializer): class Meta: model = Project fields = ('playbooks',) def to_native(self, obj): ret = super(ProjectPlaybooksSerializer, self).to_native(obj) return ret.get('playbooks', []) class InventorySerializer(BaseSerializer): class Meta: model = Inventory fields = BASE_FIELDS + ('organization',) def get_related(self, obj): res = super(InventorySerializer, self).get_related(obj) res.update(dict( hosts = reverse('main:inventory_hosts_list', args=(obj.pk,)), groups = reverse('main:inventory_groups_list', args=(obj.pk,)), root_groups = reverse('main:inventory_root_groups_list', args=(obj.pk,)), organization = reverse('main:organizations_detail', args=(obj.organization.pk,)), )) return res class HostSerializer(BaseSerializer): class Meta: model = Host fields = BASE_FIELDS + ('inventory',) def get_related(self, obj): res = super(HostSerializer, self).get_related(obj) res.update(dict( 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,)) # NICE TO HAVE: possible reverse resource to show what groups the host is in return res class GroupSerializer(BaseSerializer): class Meta: model = Group fields = BASE_FIELDS + ('inventory',) def get_related(self, obj): res = super(GroupSerializer, self).get_related(obj) res.update(dict( variable_data = reverse('main:groups_variable_detail', args=(obj.pk,)), hosts = reverse('main:groups_hosts_list', args=(obj.pk,)), children = reverse('main:groups_children_list', args=(obj.pk,)), all_hosts = reverse('main:groups_all_hosts_list', args=(obj.pk,)), inventory = reverse('main:inventory_detail', args=(obj.inventory.pk,)), job_events = reverse('main:group_job_event_list', args=(obj.pk,)), job_host_summaries = reverse('main:group_job_host_summary_list', args=(obj.pk,)), )) return res class TeamSerializer(BaseSerializer): class Meta: model = Team fields = BASE_FIELDS + ('organization',) def get_related(self, obj): res = super(TeamSerializer, self).get_related(obj) res.update(dict( projects = reverse('main:teams_projects_list', args=(obj.pk,)), users = reverse('main:teams_users_list', args=(obj.pk,)), credentials = reverse('main:teams_credentials_list', args=(obj.pk,)), organization = reverse('main:organizations_detail', args=(obj.organization.pk,)), permissions = reverse('main:teams_permissions_list', args=(obj.pk,)), )) return res class PermissionSerializer(BaseSerializer): class Meta: model = Permission fields = BASE_FIELDS + ('user', 'team', 'project', 'inventory', 'permission_type',) def get_related(self, obj): res = super(PermissionSerializer, self).get_related(obj) if obj.user: res['user'] = reverse('main:users_detail', args=(obj.user.pk,)) if obj.team: res['team'] = reverse('main:teams_detail', args=(obj.team.pk,)) if obj.project: res['project'] = reverse('main:projects_detail', args=(obj.project.pk,)) if obj.inventory: res['inventory'] = reverse('main:inventory_detail', args=(obj.inventory.pk,)) return res class CredentialSerializer(BaseSerializer): # FIXME: may want to make some of these filtered based on user accessing class Meta: model = Credential fields = BASE_FIELDS + ('ssh_username', 'ssh_password', 'ssh_key_data', 'ssh_key_unlock', 'sudo_username', 'sudo_password', 'user', 'team',) def get_related(self, obj): res = super(CredentialSerializer, self).get_related(obj) if obj.user: res['user'] = reverse('main:users_detail', args=(obj.user.pk,)) if obj.team: res['team'] = reverse('main:teams_detail', args=(obj.team.pk,)) return res def validate(self, attrs): ''' some fields cannot be changed once written ''' if self.object is not None: # this is an update if self.object.user != attrs['user']: raise serializers.ValidationError("user cannot be changed") if self.object.team != attrs['team']: raise serializers.ValidationError("team cannot be changed") return attrs class UserSerializer(BaseSerializer): class Meta: model = User fields = ('id', 'url', 'related', 'created', 'creation_date', 'username', 'first_name', 'last_name', 'email', 'is_active', 'is_superuser',) def get_related(self, obj): res = super(UserSerializer, self).get_related(obj) res.update(dict( teams = reverse('main:users_teams_list', args=(obj.pk,)), organizations = reverse('main:users_organizations_list', args=(obj.pk,)), admin_of_organizations = reverse('main:users_admin_organizations_list', args=(obj.pk,)), projects = reverse('main:users_projects_list', args=(obj.pk,)), credentials = reverse('main:users_credentials_list', args=(obj.pk,)), permissions = reverse('main:users_permissions_list', args=(obj.pk,)), )) return res class TagSerializer(BaseSerializer): class Meta: model = Tag fields = ('id', 'url', 'name') def get_related(self, obj): res = super(TagSerializer, self).get_related(obj) res.pop('created_by', None) return res class VariableDataSerializer(BaseSerializer): class Meta: model = VariableData fields = BASE_FIELDS + ('data',) def get_related(self, obj): res = super(VariableDataSerializer, self).get_related(obj) try: res['host'] = reverse('main:hosts_detail', args=(obj.host.pk,)) except Host.DoesNotExist: pass try: res['group'] = reverse('main:groups_detail', args=(obj.group.pk,)) except Group.DoesNotExist: pass return res class JobTemplateSerializer(BaseSerializer): class Meta: model = JobTemplate fields = BASE_FIELDS + ('job_type', 'inventory', 'project', 'playbook', 'credential', 'forks', 'limit', 'verbosity', 'extra_vars') def get_related(self, obj): res = super(JobTemplateSerializer, self).get_related(obj) res.update(dict( inventory = reverse('main:inventory_detail', args=(obj.inventory.pk,)), project = reverse('main:projects_detail', args=(obj.project.pk,)), jobs = reverse('main:job_template_job_list', args=(obj.pk,)), )) if obj.credential: res['credential'] = reverse('main:credentials_detail', args=(obj.credential.pk,)) return res def validate_playbook(self, attrs, source): project = attrs.get('project', None) playbook = attrs.get('playbook', '') if project and playbook and playbook not in project.playbooks: raise serializers.ValidationError('Playbook not found for project') return attrs class JobSerializer(BaseSerializer): passwords_needed_to_start = serializers.Field(source='get_passwords_needed_to_start') class Meta: model = Job fields = BASE_FIELDS + ('job_template', 'job_type', 'inventory', 'project', 'playbook', 'credential', 'forks', 'limit', 'verbosity', 'extra_vars', 'status', 'failed', 'result_stdout', 'result_traceback', 'passwords_needed_to_start') def get_related(self, obj): res = super(JobSerializer, self).get_related(obj) res.update(dict( inventory = reverse('main:inventory_detail', args=(obj.inventory.pk,)), 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,)), 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,)) if obj.can_start or True: res['start'] = reverse('main:job_start', args=(obj.pk,)) if obj.can_cancel or True: res['cancel'] = reverse('main:job_cancel', args=(obj.pk,)) return res def from_native(self, data, files): # When creating a new job and a job template is specified, populate any # fields not provided in data from the job template. if not self.object and isinstance(data, dict) and 'job_template' in data: try: job_template = JobTemplate.objects.get(pk=data['job_template']) except JobTemplate.DoesNotExist: self._errors = {'job_template': 'Invalid job template'} return # Don't auto-populate name or description. data.setdefault('job_type', job_template.job_type) data.setdefault('inventory', job_template.inventory.pk) data.setdefault('project', job_template.project.pk) data.setdefault('playbook', job_template.playbook) if job_template.credential: data.setdefault('credential', job_template.credential.pk) data.setdefault('forks', job_template.forks) data.setdefault('limit', job_template.limit) data.setdefault('verbosity', job_template.verbosity) data.setdefault('extra_vars', job_template.extra_vars) return super(JobSerializer, self).from_native(data, files) class JobHostSummarySerializer(BaseSerializer): class Meta: model = JobHostSummary 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.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): class Meta: model = JobEvent fields = ('id', 'url', 'created', 'job', 'event', 'event_data', 'failed', 'host', 'related', 'summary_fields') def get_related(self, obj): res = super(JobEventSerializer, self).get_related(obj) res.update(dict( job = reverse('main:job_detail', args=(obj.job.pk,)), )) if obj.host: res['host'] = reverse('main:hosts_detail', args=(obj.host.pk,)) return res