mirror of
https://github.com/ansible/awx.git
synced 2026-03-07 11:41:08 -03:30
Merge pull request #198 from cchurch/options_meta_updates
Update field metadata returned in OPTIONS response
This commit is contained in:
@@ -217,7 +217,7 @@ class GenericAPIView(generics.GenericAPIView, APIView):
|
|||||||
for field, meta in fields.items():
|
for field, meta in fields.items():
|
||||||
if not isinstance(meta, dict):
|
if not isinstance(meta, dict):
|
||||||
continue
|
continue
|
||||||
if meta.get('read_only', False):
|
if meta.pop('read_only', False):
|
||||||
fields.pop(field)
|
fields.pop(field)
|
||||||
if 'GET' in self.allowed_methods:
|
if 'GET' in self.allowed_methods:
|
||||||
cloned_request = clone_request(request, 'GET')
|
cloned_request = clone_request(request, 'GET')
|
||||||
@@ -242,12 +242,19 @@ class GenericAPIView(generics.GenericAPIView, APIView):
|
|||||||
serializer = self.get_serializer()
|
serializer = self.get_serializer()
|
||||||
actions['GET'] = serializer.metadata()
|
actions['GET'] = serializer.metadata()
|
||||||
if hasattr(serializer, 'get_types'):
|
if hasattr(serializer, 'get_types'):
|
||||||
# Inject the type field choices into GET options as well as on
|
|
||||||
# the metadata itself.
|
|
||||||
if 'type' in actions['GET']:
|
|
||||||
actions['GET']['type']['type'] = 'multiple choice'
|
|
||||||
actions['GET']['type']['choices'] = serializer.get_type_choices()
|
|
||||||
ret['types'] = serializer.get_types()
|
ret['types'] = serializer.get_types()
|
||||||
|
# Remove fields labeled as write_only, remove field attributes
|
||||||
|
# that aren't relevant for retrieving data.
|
||||||
|
for field, meta in actions['GET'].items():
|
||||||
|
if not isinstance(meta, dict):
|
||||||
|
continue
|
||||||
|
meta.pop('required', None)
|
||||||
|
meta.pop('read_only', None)
|
||||||
|
meta.pop('default', None)
|
||||||
|
meta.pop('min_length', None)
|
||||||
|
meta.pop('max_length', None)
|
||||||
|
if meta.pop('write_only', False):
|
||||||
|
actions['GET'].pop(field)
|
||||||
if actions:
|
if actions:
|
||||||
ret['actions'] = actions
|
ret['actions'] = actions
|
||||||
if getattr(self, 'search_fields', None):
|
if getattr(self, 'search_fields', None):
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
# Python
|
# Python
|
||||||
|
import functools
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import logging
|
import logging
|
||||||
@@ -86,6 +87,25 @@ SUMMARIZABLE_FK_FIELDS = {
|
|||||||
'source_script': ('name', 'description'),
|
'source_script': ('name', 'description'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Monkeypatch REST framework to include default value and write_only flag in
|
||||||
|
# field metadata.
|
||||||
|
def add_metadata_default(f):
|
||||||
|
@functools.wraps(f)
|
||||||
|
def _add_metadata_default(self, *args, **kwargs):
|
||||||
|
metadata = f(self, *args, **kwargs)
|
||||||
|
if hasattr(self, 'get_default_value'):
|
||||||
|
default = self.get_default_value()
|
||||||
|
if default is None and metadata.get('type', '') != 'field':
|
||||||
|
default = getattr(self, 'empty', None)
|
||||||
|
if default or not getattr(self, 'required', False):
|
||||||
|
metadata['default'] = default
|
||||||
|
if getattr(self, 'write_only', False):
|
||||||
|
metadata['write_only'] = True
|
||||||
|
return metadata
|
||||||
|
return _add_metadata_default
|
||||||
|
|
||||||
|
fields.Field.metadata = add_metadata_default(fields.Field.metadata)
|
||||||
|
|
||||||
class ChoiceField(fields.ChoiceField):
|
class ChoiceField(fields.ChoiceField):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@@ -93,7 +113,7 @@ class ChoiceField(fields.ChoiceField):
|
|||||||
if not self.required:
|
if not self.required:
|
||||||
# Remove extra blank option if one is already present (for writable
|
# Remove extra blank option if one is already present (for writable
|
||||||
# field) or if present at all for read-only fields.
|
# field) or if present at all for read-only fields.
|
||||||
if ([x[0] for x in self.choices].count(u'') > 1 or self.read_only) \
|
if ([x[0] for x in self.choices].count(u'') > 1 or self.get_default_value() != u'' or self.read_only) \
|
||||||
and BLANK_CHOICE_DASH[0] in self.choices:
|
and BLANK_CHOICE_DASH[0] in self.choices:
|
||||||
self.choices = [x for x in self.choices
|
self.choices = [x for x in self.choices
|
||||||
if x != BLANK_CHOICE_DASH[0]]
|
if x != BLANK_CHOICE_DASH[0]]
|
||||||
@@ -205,7 +225,7 @@ class BaseSerializer(serializers.ModelSerializer):
|
|||||||
field.help_text = 'Database ID for this %s.' % unicode(opts.verbose_name)
|
field.help_text = 'Database ID for this %s.' % unicode(opts.verbose_name)
|
||||||
elif key == 'type':
|
elif key == 'type':
|
||||||
field.help_text = 'Data type for this %s.' % unicode(opts.verbose_name)
|
field.help_text = 'Data type for this %s.' % unicode(opts.verbose_name)
|
||||||
field.type_label = 'string'
|
field.type_label = 'multiple choice'
|
||||||
elif key == 'url':
|
elif key == 'url':
|
||||||
field.help_text = 'URL for this %s.' % unicode(opts.verbose_name)
|
field.help_text = 'URL for this %s.' % unicode(opts.verbose_name)
|
||||||
field.type_label = 'string'
|
field.type_label = 'string'
|
||||||
@@ -371,6 +391,17 @@ class BaseSerializer(serializers.ModelSerializer):
|
|||||||
ret.pop(parent_key, None)
|
ret.pop(parent_key, None)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def metadata(self):
|
||||||
|
fields = super(BaseSerializer, self).metadata()
|
||||||
|
for field, meta in fields.items():
|
||||||
|
if not isinstance(meta, dict):
|
||||||
|
continue
|
||||||
|
if field == 'type':
|
||||||
|
meta['choices'] = self.get_type_choices()
|
||||||
|
#if meta.get('type', '') == 'field':
|
||||||
|
# meta['type'] = 'id'
|
||||||
|
return fields
|
||||||
|
|
||||||
|
|
||||||
class UnifiedJobTemplateSerializer(BaseSerializer):
|
class UnifiedJobTemplateSerializer(BaseSerializer):
|
||||||
|
|
||||||
@@ -413,8 +444,9 @@ class UnifiedJobTemplateSerializer(BaseSerializer):
|
|||||||
|
|
||||||
class UnifiedJobSerializer(BaseSerializer):
|
class UnifiedJobSerializer(BaseSerializer):
|
||||||
|
|
||||||
result_stdout = serializers.Field(source='result_stdout')
|
result_stdout = serializers.CharField(source='result_stdout', label='result stdout', read_only=True)
|
||||||
unified_job_template = serializers.Field(source='unified_job_template_id')
|
unified_job_template = serializers.Field(source='unified_job_template_id', label='unified job template')
|
||||||
|
job_env = serializers.CharField(source='job_env', label='job env', read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = UnifiedJob
|
model = UnifiedJob
|
||||||
@@ -521,9 +553,9 @@ class UnifiedJobStdoutSerializer(UnifiedJobSerializer):
|
|||||||
|
|
||||||
class UserSerializer(BaseSerializer):
|
class UserSerializer(BaseSerializer):
|
||||||
|
|
||||||
password = serializers.WritableField(required=False, default='',
|
password = serializers.CharField(required=False, default='', write_only=True,
|
||||||
help_text='Write-only field used to change the password.')
|
help_text='Write-only field used to change the password.')
|
||||||
ldap_dn = serializers.Field(source='profile.ldap_dn')
|
ldap_dn = serializers.CharField(source='profile.ldap_dn', read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
@@ -664,10 +696,10 @@ class ProjectOptionsSerializer(BaseSerializer):
|
|||||||
class ProjectSerializer(UnifiedJobTemplateSerializer, ProjectOptionsSerializer):
|
class ProjectSerializer(UnifiedJobTemplateSerializer, ProjectOptionsSerializer):
|
||||||
|
|
||||||
playbooks = serializers.Field(source='playbooks', help_text='Array of playbooks available within this project.')
|
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')
|
scm_delete_on_next_update = serializers.BooleanField(source='scm_delete_on_next_update', read_only=True)
|
||||||
status = ChoiceField(source='status', choices=Project.PROJECT_STATUS_CHOICES, read_only=True, required=False)
|
status = ChoiceField(source='status', choices=Project.PROJECT_STATUS_CHOICES, read_only=True, required=False)
|
||||||
last_update_failed = serializers.Field(source='last_update_failed')
|
last_update_failed = serializers.BooleanField(source='last_update_failed', read_only=True)
|
||||||
last_updated = serializers.Field(source='last_updated')
|
last_updated = serializers.DateTimeField(source='last_updated', read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Project
|
model = Project
|
||||||
@@ -1114,8 +1146,8 @@ class InventorySourceOptionsSerializer(BaseSerializer):
|
|||||||
class InventorySourceSerializer(UnifiedJobTemplateSerializer, InventorySourceOptionsSerializer):
|
class InventorySourceSerializer(UnifiedJobTemplateSerializer, InventorySourceOptionsSerializer):
|
||||||
|
|
||||||
status = ChoiceField(source='status', choices=InventorySource.INVENTORY_SOURCE_STATUS_CHOICES, read_only=True, required=False)
|
status = ChoiceField(source='status', choices=InventorySource.INVENTORY_SOURCE_STATUS_CHOICES, read_only=True, required=False)
|
||||||
last_update_failed = serializers.Field(source='last_update_failed')
|
last_update_failed = serializers.BooleanField(source='last_update_failed', read_only=True)
|
||||||
last_updated = serializers.Field(source='last_updated')
|
last_updated = serializers.DateTimeField(source='last_updated', read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InventorySource
|
model = InventorySource
|
||||||
@@ -1273,11 +1305,11 @@ class CredentialSerializer(BaseSerializer):
|
|||||||
|
|
||||||
# FIXME: may want to make some of these filtered based on user accessing
|
# FIXME: may want to make some of these filtered based on user accessing
|
||||||
|
|
||||||
password = serializers.WritableField(required=False, default='')
|
password = serializers.CharField(required=False, default='')
|
||||||
ssh_key_data = serializers.WritableField(required=False, default='')
|
ssh_key_data = serializers.CharField(required=False, default='')
|
||||||
ssh_key_unlock = serializers.WritableField(required=False, default='')
|
ssh_key_unlock = serializers.CharField(required=False, default='')
|
||||||
become_password = serializers.WritableField(required=False, default='')
|
become_password = serializers.CharField(required=False, default='')
|
||||||
vault_password = serializers.WritableField(required=False, default='')
|
vault_password = serializers.CharField(required=False, default='')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Credential
|
model = Credential
|
||||||
@@ -1512,6 +1544,7 @@ class JobCancelSerializer(JobSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class JobRelaunchSerializer(JobSerializer):
|
class JobRelaunchSerializer(JobSerializer):
|
||||||
|
|
||||||
passwords_needed_to_start = serializers.SerializerMethodField('get_passwords_needed_to_start')
|
passwords_needed_to_start = serializers.SerializerMethodField('get_passwords_needed_to_start')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -1687,8 +1720,8 @@ class JobHostSummarySerializer(BaseSerializer):
|
|||||||
|
|
||||||
class JobEventSerializer(BaseSerializer):
|
class JobEventSerializer(BaseSerializer):
|
||||||
|
|
||||||
event_display = serializers.Field(source='get_event_display2')
|
event_display = serializers.CharField(source='get_event_display2', read_only=True)
|
||||||
event_level = serializers.Field(source='event_level')
|
event_level = serializers.IntegerField(source='event_level', read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = JobEvent
|
model = JobEvent
|
||||||
@@ -1724,7 +1757,7 @@ class JobEventSerializer(BaseSerializer):
|
|||||||
|
|
||||||
class AdHocCommandEventSerializer(BaseSerializer):
|
class AdHocCommandEventSerializer(BaseSerializer):
|
||||||
|
|
||||||
event_display = serializers.Field(source='get_event_display')
|
event_display = serializers.CharField(source='get_event_display', read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AdHocCommandEvent
|
model = AdHocCommandEvent
|
||||||
@@ -1742,8 +1775,9 @@ class AdHocCommandEventSerializer(BaseSerializer):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
class JobLaunchSerializer(BaseSerializer):
|
class JobLaunchSerializer(BaseSerializer):
|
||||||
|
|
||||||
passwords_needed_to_start = serializers.Field(source='passwords_needed_to_start')
|
passwords_needed_to_start = serializers.Field(source='passwords_needed_to_start')
|
||||||
can_start_without_user_input = serializers.Field(source='can_start_without_user_input')
|
can_start_without_user_input = serializers.BooleanField(source='can_start_without_user_input', read_only=True)
|
||||||
variables_needed_to_start = serializers.Field(source='variables_needed_to_start')
|
variables_needed_to_start = serializers.Field(source='variables_needed_to_start')
|
||||||
credential_needed_to_start = serializers.SerializerMethodField('get_credential_needed_to_start')
|
credential_needed_to_start = serializers.SerializerMethodField('get_credential_needed_to_start')
|
||||||
survey_enabled = serializers.SerializerMethodField('get_survey_enabled')
|
survey_enabled = serializers.SerializerMethodField('get_survey_enabled')
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% for fn, fm in serializer_fields.items %}{% spaceless %}
|
{% for fn, fm in serializer_fields.items %}{% spaceless %}
|
||||||
{% if not write_only or not fm.read_only %}
|
{% if not write_only or not fm.read_only %}
|
||||||
* `{{ fn }}`: {{ fm.help_text|capfirst }} ({{ fm.type }}{% if fm.required %}, required{% endif %}{% if fm.read_only %}, read-only{% endif %})
|
* `{{ fn }}`: {{ fm.help_text|capfirst }} ({{ fm.type }}{% if write_only and fm.required %}, required{% endif %}{% if write_only and fm.read_only %}, read-only{% endif %}{% if write_only and not fm.choices and not fm.required %}, default=`{% if fm.type == "string" or fm.type == "email" %}"{% firstof fm.default "" %}"{% else %}{{ fm.default }}{% endif %}`{% endif %}){% if fm.choices %}{% for c in fm.choices %}
|
||||||
{% endif %}
|
- `{% if c.0 == "" %}""{% else %}{{ c.0 }}{% endif %}`{% if c.1 != c.0 %}: {{ c.1 }}{% endif %}{% if write_only and c.0 == fm.default %} (default){% endif %}{% endfor %}{% endif %}{% endif %}
|
||||||
{% endspaceless %}
|
{% endspaceless %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -734,6 +734,7 @@ class InventorySourceOptions(BaseModel):
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
SOURCE_CHOICES = [
|
SOURCE_CHOICES = [
|
||||||
|
('', _('Manual')),
|
||||||
('file', _('Local File, Directory or Script')),
|
('file', _('Local File, Directory or Script')),
|
||||||
('rax', _('Rackspace Cloud Servers')),
|
('rax', _('Rackspace Cloud Servers')),
|
||||||
('ec2', _('Amazon EC2')),
|
('ec2', _('Amazon EC2')),
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ class JobOptions(BaseModel):
|
|||||||
job_type = models.CharField(
|
job_type = models.CharField(
|
||||||
max_length=64,
|
max_length=64,
|
||||||
choices=JOB_TYPE_CHOICES,
|
choices=JOB_TYPE_CHOICES,
|
||||||
|
default='run',
|
||||||
)
|
)
|
||||||
inventory = models.ForeignKey(
|
inventory = models.ForeignKey(
|
||||||
'Inventory',
|
'Inventory',
|
||||||
|
|||||||
@@ -203,6 +203,7 @@ class Project(UnifiedJobTemplate, ProjectOptions):
|
|||||||
)
|
)
|
||||||
scm_update_cache_timeout = models.PositiveIntegerField(
|
scm_update_cache_timeout = models.PositiveIntegerField(
|
||||||
default=0,
|
default=0,
|
||||||
|
blank=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ class JobTemplateTest(BaseJobTestMixin, django.test.TestCase):
|
|||||||
|
|
||||||
# Test that all required fields are really required.
|
# Test that all required fields are really required.
|
||||||
data['name'] = 'another new job template'
|
data['name'] = 'another new job template'
|
||||||
for field in ('name', 'job_type', 'inventory', 'project', 'playbook'):
|
for field in ('name', 'inventory', 'project', 'playbook'):
|
||||||
with self.current_user(self.user_sue):
|
with self.current_user(self.user_sue):
|
||||||
d = dict(data.items())
|
d = dict(data.items())
|
||||||
d.pop(field)
|
d.pop(field)
|
||||||
|
|||||||
Reference in New Issue
Block a user