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