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