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',)