github.com/rvaralda/deis@v1.4.1/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 django.utils import timezone 13 from rest_framework import serializers 14 from rest_framework.validators import UniqueTogetherValidator 15 16 from api import models 17 18 19 PROCTYPE_MATCH = re.compile(r'^(?P<type>[a-z]+)') 20 MEMLIMIT_MATCH = re.compile(r'^(?P<mem>[0-9]+[BbKkMmGg])$') 21 CPUSHARE_MATCH = re.compile(r'^(?P<cpu>[0-9]+)$') 22 TAGKEY_MATCH = re.compile(r'^[a-z]+$') 23 TAGVAL_MATCH = re.compile(r'^\w+$') 24 25 26 class JSONFieldSerializer(serializers.Field): 27 def to_representation(self, obj): 28 return obj 29 30 def to_internal_value(self, data): 31 try: 32 val = json.loads(data) 33 except TypeError: 34 val = data 35 return val 36 37 38 class ModelSerializer(serializers.ModelSerializer): 39 40 uuid = serializers.ReadOnlyField() 41 42 def get_validators(self): 43 """ 44 Hack to remove DRF's UniqueTogetherValidator when it concerns the UUID. 45 46 See https://github.com/deis/deis/pull/2898#discussion_r23105147 47 """ 48 validators = super(ModelSerializer, self).get_validators() 49 for v in validators: 50 if isinstance(v, UniqueTogetherValidator) and 'uuid' in v.fields: 51 validators.remove(v) 52 return validators 53 54 55 class UserSerializer(serializers.ModelSerializer): 56 class Meta: 57 model = User 58 fields = ['email', 'username', 'password', 'first_name', 'last_name', 'is_superuser', 59 'is_staff', 'groups', 'user_permissions', 'last_login', 'date_joined', 60 'is_active'] 61 read_only_fields = ['is_superuser', 'is_staff', 'groups', 62 'user_permissions', 'last_login', 'date_joined', 'is_active'] 63 extra_kwargs = {'password': {'write_only': True}} 64 65 def create(self, validated_data): 66 now = timezone.now() 67 user = User( 68 email=validated_data.get('email'), 69 username=validated_data.get('username'), 70 last_login=now, 71 date_joined=now, 72 is_active=True 73 ) 74 if validated_data.get('first_name'): 75 user.first_name = validated_data['first_name'] 76 if validated_data.get('last_name'): 77 user.last_name = validated_data['last_name'] 78 user.set_password(validated_data['password']) 79 # Make the first signup an admin / superuser 80 if not User.objects.filter(is_superuser=True).exists(): 81 user.is_superuser = user.is_staff = True 82 user.save() 83 return user 84 85 86 class AdminUserSerializer(serializers.ModelSerializer): 87 """Serialize admin status for a User model.""" 88 89 class Meta: 90 model = User 91 fields = ['username', 'is_superuser'] 92 read_only_fields = ['username'] 93 94 95 class AppSerializer(ModelSerializer): 96 """Serialize a :class:`~api.models.App` model.""" 97 98 owner = serializers.ReadOnlyField(source='owner.username') 99 structure = JSONFieldSerializer(required=False) 100 created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 101 updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 102 103 class Meta: 104 """Metadata options for a :class:`AppSerializer`.""" 105 model = models.App 106 fields = ['uuid', 'id', 'owner', 'url', 'structure', 'created', 'updated'] 107 read_only_fields = ['uuid'] 108 109 110 class BuildSerializer(ModelSerializer): 111 """Serialize a :class:`~api.models.Build` model.""" 112 113 app = serializers.SlugRelatedField(slug_field='id', queryset=models.App.objects.all()) 114 owner = serializers.ReadOnlyField(source='owner.username') 115 procfile = JSONFieldSerializer(required=False) 116 created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 117 updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 118 119 class Meta: 120 """Metadata options for a :class:`BuildSerializer`.""" 121 model = models.Build 122 fields = ['owner', 'app', 'image', 'sha', 'procfile', 'dockerfile', 'created', 123 'updated', 'uuid'] 124 read_only_fields = ['uuid'] 125 126 127 class ConfigSerializer(ModelSerializer): 128 """Serialize a :class:`~api.models.Config` model.""" 129 130 app = serializers.SlugRelatedField(slug_field='id', queryset=models.App.objects.all()) 131 owner = serializers.ReadOnlyField(source='owner.username') 132 values = JSONFieldSerializer(required=False) 133 memory = JSONFieldSerializer(required=False) 134 cpu = JSONFieldSerializer(required=False) 135 tags = JSONFieldSerializer(required=False) 136 created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 137 updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 138 139 class Meta: 140 """Metadata options for a :class:`ConfigSerializer`.""" 141 model = models.Config 142 143 def validate_memory(self, value): 144 for k, v in value.items(): 145 if v is None: # use NoneType to unset a value 146 continue 147 if not re.match(PROCTYPE_MATCH, k): 148 raise serializers.ValidationError("Process types can only contain [a-z]") 149 if not re.match(MEMLIMIT_MATCH, str(v)): 150 raise serializers.ValidationError( 151 "Limit format: <number><unit>, where unit = B, K, M or G") 152 return value 153 154 def validate_cpu(self, value): 155 for k, v in value.items(): 156 if v is None: # use NoneType to unset a value 157 continue 158 if not re.match(PROCTYPE_MATCH, k): 159 raise serializers.ValidationError("Process types can only contain [a-z]") 160 shares = re.match(CPUSHARE_MATCH, str(v)) 161 if not shares: 162 raise serializers.ValidationError("CPU shares must be an integer") 163 for v in shares.groupdict().values(): 164 try: 165 i = int(v) 166 except ValueError: 167 raise serializers.ValidationError("CPU shares must be an integer") 168 if i > 1024 or i < 0: 169 raise serializers.ValidationError("CPU shares must be between 0 and 1024") 170 return value 171 172 def validate_tags(self, value): 173 for k, v in value.items(): 174 if v is None: # use NoneType to unset a value 175 continue 176 if not re.match(TAGKEY_MATCH, k): 177 raise serializers.ValidationError("Tag keys can only contain [a-z]") 178 if not re.match(TAGVAL_MATCH, str(v)): 179 raise serializers.ValidationError("Invalid tag value") 180 return value 181 182 183 class ReleaseSerializer(ModelSerializer): 184 """Serialize a :class:`~api.models.Release` model.""" 185 186 app = serializers.SlugRelatedField(slug_field='id', queryset=models.App.objects.all()) 187 owner = serializers.ReadOnlyField(source='owner.username') 188 created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 189 updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 190 191 class Meta: 192 """Metadata options for a :class:`ReleaseSerializer`.""" 193 model = models.Release 194 195 196 class ContainerSerializer(ModelSerializer): 197 """Serialize a :class:`~api.models.Container` model.""" 198 199 app = serializers.SlugRelatedField(slug_field='id', queryset=models.App.objects.all()) 200 owner = serializers.ReadOnlyField(source='owner.username') 201 created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 202 updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 203 release = serializers.SerializerMethodField() 204 205 class Meta: 206 """Metadata options for a :class:`ContainerSerializer`.""" 207 model = models.Container 208 fields = ['owner', 'app', 'release', 'type', 'num', 'state', 'created', 'updated', 'uuid'] 209 210 def get_release(self, obj): 211 return "v{}".format(obj.release.version) 212 213 214 class KeySerializer(ModelSerializer): 215 """Serialize a :class:`~api.models.Key` model.""" 216 217 owner = serializers.ReadOnlyField(source='owner.username') 218 created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 219 updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 220 221 class Meta: 222 """Metadata options for a KeySerializer.""" 223 model = models.Key 224 225 226 class DomainSerializer(ModelSerializer): 227 """Serialize a :class:`~api.models.Domain` model.""" 228 229 app = serializers.SlugRelatedField(slug_field='id', queryset=models.App.objects.all()) 230 owner = serializers.ReadOnlyField(source='owner.username') 231 created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 232 updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 233 234 class Meta: 235 """Metadata options for a :class:`DomainSerializer`.""" 236 model = models.Domain 237 fields = ['uuid', 'owner', 'created', 'updated', 'app', 'domain'] 238 239 def validate_domain(self, value): 240 """ 241 Check that the hostname is valid 242 """ 243 if len(value) > 255: 244 raise serializers.ValidationError('Hostname must be 255 characters or less.') 245 if value[-1:] == ".": 246 value = value[:-1] # strip exactly one dot from the right, if present 247 labels = value.split('.') 248 if labels[0] == '*': 249 raise serializers.ValidationError( 250 'Adding a wildcard subdomain is currently not supported.') 251 allowed = re.compile("^(?!-)[a-z0-9-]{1,63}(?<!-)$", re.IGNORECASE) 252 for label in labels: 253 match = allowed.match(label) 254 if not match or '--' in label or label[-1].isdigit() or label.isdigit(): 255 raise serializers.ValidationError('Hostname does not look valid.') 256 if models.Domain.objects.filter(domain=value).exists(): 257 raise serializers.ValidationError( 258 "The domain {} is already in use by another app".format(value)) 259 return value 260 261 262 class PushSerializer(ModelSerializer): 263 """Serialize a :class:`~api.models.Push` model.""" 264 265 app = serializers.SlugRelatedField(slug_field='id', queryset=models.App.objects.all()) 266 owner = serializers.ReadOnlyField(source='owner.username') 267 created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 268 updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 269 270 class Meta: 271 """Metadata options for a :class:`PushSerializer`.""" 272 model = models.Push 273 fields = ['uuid', 'owner', 'app', 'sha', 'fingerprint', 'receive_user', 'receive_repo', 274 'ssh_connection', 'ssh_original_command', 'created', 'updated']