github.com/amrnt/deis@v1.3.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 209 def get_release(self, obj): 210 return "v{}".format(obj.release.version) 211 212 213 class KeySerializer(ModelSerializer): 214 """Serialize a :class:`~api.models.Key` model.""" 215 216 owner = serializers.ReadOnlyField(source='owner.username') 217 created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 218 updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 219 220 class Meta: 221 """Metadata options for a KeySerializer.""" 222 model = models.Key 223 224 225 class DomainSerializer(ModelSerializer): 226 """Serialize a :class:`~api.models.Domain` model.""" 227 228 app = serializers.SlugRelatedField(slug_field='id', queryset=models.App.objects.all()) 229 owner = serializers.ReadOnlyField(source='owner.username') 230 created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 231 updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 232 233 class Meta: 234 """Metadata options for a :class:`DomainSerializer`.""" 235 model = models.Domain 236 fields = ['uuid', 'owner', 'created', 'updated', 'app', 'domain'] 237 238 def validate_domain(self, value): 239 """ 240 Check that the hostname is valid 241 """ 242 match = re.match( 243 r'^(\*\.)?(' + settings.APP_URL_REGEX + r'\.)*([a-z0-9-]+)\.([a-z0-9]{2,})$', 244 value) 245 if not match: 246 raise serializers.ValidationError( 247 "Hostname does not look like a valid hostname. " 248 "Only lowercase characters are allowed.") 249 250 if models.Domain.objects.filter(domain=value).exists(): 251 raise serializers.ValidationError( 252 "The domain {} is already in use by another app".format(value)) 253 254 domain_parts = value.split('.') 255 if domain_parts[0] == '*': 256 raise serializers.ValidationError( 257 "Adding a wildcard subdomain is currently not supported".format(value)) 258 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']