# Python import os import re import logging import urllib.parse as urlparse from collections import OrderedDict # Django from django.core.validators import URLValidator, _lazy_re_compile from django.utils.translation import ugettext_lazy as _ # Django REST Framework from rest_framework.fields import ( # noqa BooleanField, CharField, ChoiceField, DictField, DateTimeField, EmailField, IntegerField, ListField, NullBooleanField ) logger = logging.getLogger('awx.conf.fields') # Use DRF fields to convert/validate settings: # - to_representation(obj) should convert a native Python object to a primitive # serializable type. This primitive type will be what is presented in the API # and stored in the JSON field in the datbase. # - to_internal_value(data) should convert the primitive type back into the # appropriate Python type to be used in settings. class CharField(CharField): def to_representation(self, value): # django_rest_frameworks' default CharField implementation casts `None` # to a string `"None"`: # # https://github.com/tomchristie/django-rest-framework/blob/cbad236f6d817d992873cd4df6527d46ab243ed1/rest_framework/fields.py#L761 if value is None: return None return super(CharField, self).to_representation(value) class IntegerField(IntegerField): def get_value(self, dictionary): ret = super(IntegerField, self).get_value(dictionary) # Handle UI corner case if ret == '' and self.allow_null and not getattr(self, 'allow_blank', False): return None return ret class StringListField(ListField): child = CharField() def to_representation(self, value): if value is None and self.allow_null: return None return super(StringListField, self).to_representation(value) class StringListBooleanField(ListField): default_error_messages = { 'type_error': _('Expected None, True, False, a string or list of strings but got {input_type} instead.'), } child = CharField() def to_representation(self, value): try: if isinstance(value, (list, tuple)): return super(StringListBooleanField, self).to_representation(value) elif value in NullBooleanField.TRUE_VALUES: return True elif value in NullBooleanField.FALSE_VALUES: return False elif value in NullBooleanField.NULL_VALUES: return None elif isinstance(value, str): return self.child.to_representation(value) except TypeError: pass self.fail('type_error', input_type=type(value)) def to_internal_value(self, data): try: if isinstance(data, (list, tuple)): return super(StringListBooleanField, self).to_internal_value(data) elif data in NullBooleanField.TRUE_VALUES: return True elif data in NullBooleanField.FALSE_VALUES: return False elif data in NullBooleanField.NULL_VALUES: return None elif isinstance(data, str): return self.child.run_validation(data) except TypeError: pass self.fail('type_error', input_type=type(data)) class StringListPathField(StringListField): default_error_messages = { 'type_error': _('Expected list of strings but got {input_type} instead.'), 'path_error': _('{path} is not a valid path choice.'), } def to_internal_value(self, paths): if isinstance(paths, (list, tuple)): for p in paths: if not isinstance(p, str): self.fail('type_error', input_type=type(p)) if not os.path.exists(p): self.fail('path_error', path=p) return super(StringListPathField, self).to_internal_value(sorted({os.path.normpath(path) for path in paths})) else: self.fail('type_error', input_type=type(paths)) class URLField(CharField): # these lines set up a custom regex that allow numbers in the # top-level domain tld_re = ( r'\.' # dot r'(?!-)' # can't start with a dash r'(?:[a-z' + URLValidator.ul + r'0-9' + '-]{2,63}' # domain label, this line was changed from the original URLValidator r'|xn--[a-z0-9]{1,59})' # or punycode label r'(? 2: self.fail('type_error', input_type=type(x)) return super(ListTuplesField, self).to_internal_value(data) else: self.fail('type_error', input_type=type(data))