github.com/blystad/deis@v0.11.0/controller/api/serializers.py (about) 1 """ 2 Classes to serialize the RESTful representation of Deis API models. 3 """ 4 5 from __future__ import unicode_literals 6 7 import re 8 9 from django.conf import settings 10 from django.contrib.auth.models import User 11 from rest_framework import serializers 12 13 from api import models 14 from api import utils 15 16 17 PROCTYPE_MATCH = re.compile(r'^(?P<type>[a-z]+)') 18 MEMLIMIT_MATCH = re.compile(r'^(?P<mem>[0-9]+[BbKkMmGg])$') 19 CPUSHARE_MATCH = re.compile(r'^(?P<cpu>[0-9]+)$') 20 TAGKEY_MATCH = re.compile(r'^[a-z]+$') 21 TAGVAL_MATCH = re.compile(r'^\w+$') 22 23 24 class OwnerSlugRelatedField(serializers.SlugRelatedField): 25 """Filter queries by owner as well as slug_field.""" 26 27 def from_native(self, data): 28 """Fetch model object from its 'native' representation. 29 TODO: request.user is not going to work in a team environment... 30 """ 31 self.queryset = self.queryset.filter(owner=self.context['request'].user) 32 return serializers.SlugRelatedField.from_native(self, data) 33 34 35 class UserSerializer(serializers.ModelSerializer): 36 """Serialize a :class:`~api.models.User` model.""" 37 38 class Meta: 39 """Metadata options for a UserSerializer.""" 40 model = User 41 read_only_fields = ('is_superuser', 'is_staff', 'groups', 42 'user_permissions', 'last_login', 'date_joined') 43 44 @property 45 def data(self): 46 """Custom data property that removes secure user fields""" 47 d = super(UserSerializer, self).data 48 for f in ('password',): 49 if f in d: 50 del d[f] 51 return d 52 53 54 class AdminUserSerializer(serializers.ModelSerializer): 55 """Serialize admin status for a :class:`~api.models.User` model.""" 56 57 class Meta: 58 model = User 59 fields = ('username', 'is_superuser') 60 read_only_fields = ('username',) 61 62 63 class ClusterSerializer(serializers.ModelSerializer): 64 """Serialize a :class:`~api.models.Cluster` model.""" 65 66 owner = serializers.Field(source='owner.username') 67 68 class Meta: 69 """Metadata options for a :class:`ClusterSerializer`.""" 70 model = models.Cluster 71 read_only_fields = ('created', 'updated') 72 73 def validate_domain(self, attrs, source): 74 value = attrs[source] 75 models.validate_domain(value) 76 return attrs 77 78 def validate_hosts(self, attrs, source): 79 value = attrs[source] 80 models.validate_comma_separated(value) 81 return attrs 82 83 84 class PushSerializer(serializers.ModelSerializer): 85 """Serialize a :class:`~api.models.Push` model.""" 86 87 owner = serializers.Field(source='owner.username') 88 app = serializers.SlugRelatedField(slug_field='id') 89 90 class Meta: 91 """Metadata options for a :class:`PushSerializer`.""" 92 model = models.Push 93 read_only_fields = ('uuid', 'created', 'updated') 94 95 96 class BuildSerializer(serializers.ModelSerializer): 97 """Serialize a :class:`~api.models.Build` model.""" 98 99 owner = serializers.Field(source='owner.username') 100 app = serializers.SlugRelatedField(slug_field='id') 101 102 class Meta: 103 """Metadata options for a :class:`BuildSerializer`.""" 104 model = models.Build 105 read_only_fields = ('uuid', 'created', 'updated') 106 107 108 class ConfigSerializer(serializers.ModelSerializer): 109 """Serialize a :class:`~api.models.Config` model.""" 110 111 owner = serializers.Field(source='owner.username') 112 app = serializers.SlugRelatedField(slug_field='id') 113 values = serializers.ModelField( 114 model_field=models.Config()._meta.get_field('values'), required=False) 115 memory = serializers.ModelField( 116 model_field=models.Config()._meta.get_field('memory'), required=False) 117 cpu = serializers.ModelField( 118 model_field=models.Config()._meta.get_field('cpu'), required=False) 119 tags = serializers.ModelField( 120 model_field=models.Config()._meta.get_field('tags'), required=False) 121 122 class Meta: 123 """Metadata options for a :class:`ConfigSerializer`.""" 124 model = models.Config 125 read_only_fields = ('uuid', 'created', 'updated') 126 127 def validate_memory(self, attrs, source): 128 for k, v in attrs.get(source, {}).items(): 129 if v is None: # use NoneType to unset a value 130 continue 131 if not re.match(PROCTYPE_MATCH, k): 132 raise serializers.ValidationError("Process types can only contain [a-z]") 133 if not re.match(MEMLIMIT_MATCH, str(v)): 134 raise serializers.ValidationError( 135 "Limit format: <number><unit>, where unit = B, K, M or G") 136 return attrs 137 138 def validate_cpu(self, attrs, source): 139 for k, v in attrs.get(source, {}).items(): 140 if v is None: # use NoneType to unset a value 141 continue 142 if not re.match(PROCTYPE_MATCH, k): 143 raise serializers.ValidationError("Process types can only contain [a-z]") 144 shares = re.match(CPUSHARE_MATCH, str(v)) 145 if not shares: 146 raise serializers.ValidationError("CPU shares must be an integer") 147 for v in shares.groupdict().values(): 148 try: 149 i = int(v) 150 except ValueError: 151 raise serializers.ValidationError("CPU shares must be an integer") 152 if i > 1024 or i < 0: 153 raise serializers.ValidationError("CPU shares must be between 0 and 1024") 154 return attrs 155 156 def validate_tags(self, attrs, source): 157 for k, v in attrs.get(source, {}).items(): 158 if v is None: # use NoneType to unset a value 159 continue 160 if not re.match(TAGKEY_MATCH, k): 161 raise serializers.ValidationError("Tag keys can only contain [a-z]") 162 if not re.match(TAGVAL_MATCH, str(v)): 163 raise serializers.ValidationError("Invalid tag value") 164 return attrs 165 166 167 class ReleaseSerializer(serializers.ModelSerializer): 168 """Serialize a :class:`~api.models.Release` model.""" 169 170 owner = serializers.Field(source='owner.username') 171 app = serializers.SlugRelatedField(slug_field='id') 172 config = serializers.SlugRelatedField(slug_field='uuid') 173 build = serializers.SlugRelatedField(slug_field='uuid') 174 175 class Meta: 176 """Metadata options for a :class:`ReleaseSerializer`.""" 177 model = models.Release 178 read_only_fields = ('uuid', 'created', 'updated') 179 180 181 class AppSerializer(serializers.ModelSerializer): 182 """Serialize a :class:`~api.models.App` model.""" 183 184 owner = serializers.Field(source='owner.username') 185 id = serializers.SlugField(default=utils.generate_app_name) 186 cluster = serializers.SlugRelatedField(slug_field='id') 187 url = serializers.Field(source='url') 188 189 class Meta: 190 """Metadata options for a :class:`AppSerializer`.""" 191 model = models.App 192 read_only_fields = ('created', 'updated') 193 194 def validate_id(self, attrs, source): 195 """ 196 Check that the ID is all lowercase and not 'deis' 197 """ 198 value = attrs[source] 199 match = re.match(r'^[a-z0-9-]+$', value) 200 if not match: 201 raise serializers.ValidationError("App IDs can only contain [a-z0-9-]") 202 if value == 'deis': 203 raise serializers.ValidationError("App IDs cannot be 'deis'") 204 return attrs 205 206 def validate_structure(self, attrs, source): 207 """ 208 Check that the structure JSON dict has non-negative ints as its values. 209 """ 210 value = attrs[source] 211 models.validate_app_structure(value) 212 return attrs 213 214 215 class ContainerSerializer(serializers.ModelSerializer): 216 """Serialize a :class:`~api.models.Container` model.""" 217 218 owner = serializers.Field(source='owner.username') 219 app = OwnerSlugRelatedField(slug_field='id') 220 release = serializers.SlugRelatedField(slug_field='uuid') 221 222 class Meta: 223 """Metadata options for a :class:`ContainerSerializer`.""" 224 model = models.Container 225 read_only_fields = ('created', 'updated') 226 227 def transform_release(self, obj, value): 228 return "v{}".format(obj.release.version) 229 230 231 class KeySerializer(serializers.ModelSerializer): 232 """Serialize a :class:`~api.models.Key` model.""" 233 234 owner = serializers.Field(source='owner.username') 235 236 class Meta: 237 """Metadata options for a KeySerializer.""" 238 model = models.Key 239 read_only_fields = ('created', 'updated') 240 241 242 class DomainSerializer(serializers.ModelSerializer): 243 """Serialize a :class:`~api.models.Domain` model.""" 244 245 owner = serializers.Field(source='owner.username') 246 app = serializers.SlugRelatedField(slug_field='id') 247 248 class Meta: 249 """Metadata options for a :class:`DomainSerializer`.""" 250 model = models.Domain 251 fields = ('domain', 'owner', 'created', 'updated', 'app') 252 read_only_fields = ('created', 'updated') 253 254 def validate_domain(self, attrs, source): 255 """ 256 Check that the hostname is valid 257 """ 258 value = attrs[source] 259 match = re.match( 260 r'^(\*\.)?(' + settings.APP_URL_REGEX + r'\.)*([a-z0-9-]+)\.([a-z0-9]{2,})$', 261 value) 262 if not match: 263 raise serializers.ValidationError( 264 "Hostname does not look like a valid hostname. " 265 "Only lowercase characters are allowed.") 266 267 if models.Domain.objects.filter(domain=value).exists(): 268 raise serializers.ValidationError( 269 "The domain {} is already in use by another app".format(value)) 270 271 domain_parts = value.split('.') 272 if domain_parts[0] == '*': 273 raise serializers.ValidationError( 274 "Adding a wildcard subdomain is currently not supported".format(value)) 275 276 return attrs