Merge pull request #198 from cchurch/options_meta_updates

Update field metadata returned in OPTIONS response
This commit is contained in:
Chris Church 2015-05-12 17:24:31 -04:00
commit 693b63ef04
7 changed files with 74 additions and 30 deletions

View File

@ -217,7 +217,7 @@ class GenericAPIView(generics.GenericAPIView, APIView):
for field, meta in fields.items():
if not isinstance(meta, dict):
continue
if meta.get('read_only', False):
if meta.pop('read_only', False):
fields.pop(field)
if 'GET' in self.allowed_methods:
cloned_request = clone_request(request, 'GET')
@ -242,12 +242,19 @@ class GenericAPIView(generics.GenericAPIView, APIView):
serializer = self.get_serializer()
actions['GET'] = serializer.metadata()
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()
# 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:
ret['actions'] = actions
if getattr(self, 'search_fields', None):

View File

@ -2,6 +2,7 @@
# All Rights Reserved.
# Python
import functools
import json
import re
import logging
@ -86,6 +87,25 @@ SUMMARIZABLE_FK_FIELDS = {
'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):
def __init__(self, *args, **kwargs):
@ -93,7 +113,7 @@ class ChoiceField(fields.ChoiceField):
if not self.required:
# Remove extra blank option if one is already present (for writable
# 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:
self.choices = [x for x in self.choices
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)
elif key == 'type':
field.help_text = 'Data type for this %s.' % unicode(opts.verbose_name)
field.type_label = 'string'
field.type_label = 'multiple choice'
elif key == 'url':
field.help_text = 'URL for this %s.' % unicode(opts.verbose_name)
field.type_label = 'string'
@ -371,6 +391,17 @@ class BaseSerializer(serializers.ModelSerializer):
ret.pop(parent_key, None)
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):
@ -413,8 +444,9 @@ class UnifiedJobTemplateSerializer(BaseSerializer):
class UnifiedJobSerializer(BaseSerializer):
result_stdout = serializers.Field(source='result_stdout')
unified_job_template = serializers.Field(source='unified_job_template_id')
result_stdout = serializers.CharField(source='result_stdout', label='result stdout', read_only=True)
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:
model = UnifiedJob
@ -521,9 +553,9 @@ class UnifiedJobStdoutSerializer(UnifiedJobSerializer):
class UserSerializer(BaseSerializer):
password = serializers.WritableField(required=False, default='',
help_text='Write-only field used to change the password.')
ldap_dn = serializers.Field(source='profile.ldap_dn')
password = serializers.CharField(required=False, default='', write_only=True,
help_text='Write-only field used to change the password.')
ldap_dn = serializers.CharField(source='profile.ldap_dn', read_only=True)
class Meta:
model = User
@ -664,10 +696,10 @@ class ProjectOptionsSerializer(BaseSerializer):
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')
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)
last_update_failed = serializers.Field(source='last_update_failed')
last_updated = serializers.Field(source='last_updated')
last_update_failed = serializers.BooleanField(source='last_update_failed', read_only=True)
last_updated = serializers.DateTimeField(source='last_updated', read_only=True)
class Meta:
model = Project
@ -1114,8 +1146,8 @@ class InventorySourceOptionsSerializer(BaseSerializer):
class InventorySourceSerializer(UnifiedJobTemplateSerializer, InventorySourceOptionsSerializer):
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_updated = serializers.Field(source='last_updated')
last_update_failed = serializers.BooleanField(source='last_update_failed', read_only=True)
last_updated = serializers.DateTimeField(source='last_updated', read_only=True)
class Meta:
model = InventorySource
@ -1273,11 +1305,11 @@ class CredentialSerializer(BaseSerializer):
# FIXME: may want to make some of these filtered based on user accessing
password = serializers.WritableField(required=False, default='')
ssh_key_data = serializers.WritableField(required=False, default='')
ssh_key_unlock = serializers.WritableField(required=False, default='')
become_password = serializers.WritableField(required=False, default='')
vault_password = serializers.WritableField(required=False, default='')
password = serializers.CharField(required=False, default='')
ssh_key_data = serializers.CharField(required=False, default='')
ssh_key_unlock = serializers.CharField(required=False, default='')
become_password = serializers.CharField(required=False, default='')
vault_password = serializers.CharField(required=False, default='')
class Meta:
model = Credential
@ -1512,6 +1544,7 @@ class JobCancelSerializer(JobSerializer):
class JobRelaunchSerializer(JobSerializer):
passwords_needed_to_start = serializers.SerializerMethodField('get_passwords_needed_to_start')
class Meta:
@ -1687,8 +1720,8 @@ class JobHostSummarySerializer(BaseSerializer):
class JobEventSerializer(BaseSerializer):
event_display = serializers.Field(source='get_event_display2')
event_level = serializers.Field(source='event_level')
event_display = serializers.CharField(source='get_event_display2', read_only=True)
event_level = serializers.IntegerField(source='event_level', read_only=True)
class Meta:
model = JobEvent
@ -1724,7 +1757,7 @@ class JobEventSerializer(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:
model = AdHocCommandEvent
@ -1742,8 +1775,9 @@ class AdHocCommandEventSerializer(BaseSerializer):
return res
class JobLaunchSerializer(BaseSerializer):
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')
credential_needed_to_start = serializers.SerializerMethodField('get_credential_needed_to_start')
survey_enabled = serializers.SerializerMethodField('get_survey_enabled')

View File

@ -1,6 +1,6 @@
{% for fn, fm in serializer_fields.items %}{% spaceless %}
{% 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 %})
{% 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 %}
- `{% 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 %}
{% endfor %}

View File

@ -734,6 +734,7 @@ class InventorySourceOptions(BaseModel):
'''
SOURCE_CHOICES = [
('', _('Manual')),
('file', _('Local File, Directory or Script')),
('rax', _('Rackspace Cloud Servers')),
('ec2', _('Amazon EC2')),

View File

@ -41,6 +41,7 @@ class JobOptions(BaseModel):
job_type = models.CharField(
max_length=64,
choices=JOB_TYPE_CHOICES,
default='run',
)
inventory = models.ForeignKey(
'Inventory',

View File

@ -203,6 +203,7 @@ class Project(UnifiedJobTemplate, ProjectOptions):
)
scm_update_cache_timeout = models.PositiveIntegerField(
default=0,
blank=True,
)
@classmethod

View File

@ -280,7 +280,7 @@ class JobTemplateTest(BaseJobTestMixin, django.test.TestCase):
# Test that all required fields are really required.
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):
d = dict(data.items())
d.pop(field)