github.com/spg/deis@v1.7.3/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]+(MB|KB|GB|[BKMG]))$', re.IGNORECASE)
    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.viewitems():
   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.viewitems():
   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().viewvalues():
   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.viewitems():
   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      fingerprint = serializers.CharField(read_only=True)
   219      created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True)
   220      updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True)
   221  
   222      class Meta:
   223          """Metadata options for a KeySerializer."""
   224          model = models.Key
   225  
   226  
   227  class DomainSerializer(ModelSerializer):
   228      """Serialize a :class:`~api.models.Domain` model."""
   229  
   230      app = serializers.SlugRelatedField(slug_field='id', queryset=models.App.objects.all())
   231      owner = serializers.ReadOnlyField(source='owner.username')
   232      created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True)
   233      updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True)
   234  
   235      class Meta:
   236          """Metadata options for a :class:`DomainSerializer`."""
   237          model = models.Domain
   238          fields = ['uuid', 'owner', 'created', 'updated', 'app', 'domain']
   239  
   240      def validate_domain(self, value):
   241          """
   242          Check that the hostname is valid
   243          """
   244          if len(value) > 255:
   245              raise serializers.ValidationError('Hostname must be 255 characters or less.')
   246          if value[-1:] == ".":
   247              value = value[:-1]  # strip exactly one dot from the right, if present
   248          labels = value.split('.')
   249          if 'xip.io' in value:
   250              return value
   251          if labels[0] == '*':
   252              raise serializers.ValidationError(
   253                  'Adding a wildcard subdomain is currently not supported.')
   254          allowed = re.compile("^(?!-)[a-z0-9-]{1,63}(?<!-)$", re.IGNORECASE)
   255          for label in labels:
   256              match = allowed.match(label)
   257              if not match or '--' in label or label.isdigit() or \
   258                 len(labels) == 1 and any(char.isdigit() for char in label):
   259                  raise serializers.ValidationError('Hostname does not look valid.')
   260          if models.Domain.objects.filter(domain=value).exists():
   261              raise serializers.ValidationError(
   262                  "The domain {} is already in use by another app".format(value))
   263          return value
   264  
   265  
   266  class CertificateSerializer(ModelSerializer):
   267      """Serialize a :class:`~api.models.Cert` model."""
   268  
   269      owner = serializers.ReadOnlyField(source='owner.username')
   270      expires = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True)
   271      created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True)
   272      updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True)
   273  
   274      class Meta:
   275          """Metadata options for a DomainCertSerializer."""
   276          model = models.Certificate
   277          extra_kwargs = {'certificate': {'write_only': True},
   278                          'key': {'write_only': True},
   279                          'common_name': {'required': False}}
   280          read_only_fields = ['expires', 'created', 'updated']
   281  
   282  
   283  class PushSerializer(ModelSerializer):
   284      """Serialize a :class:`~api.models.Push` model."""
   285  
   286      app = serializers.SlugRelatedField(slug_field='id', queryset=models.App.objects.all())
   287      owner = serializers.ReadOnlyField(source='owner.username')
   288      created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True)
   289      updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True)
   290  
   291      class Meta:
   292          """Metadata options for a :class:`PushSerializer`."""
   293          model = models.Push
   294          fields = ['uuid', 'owner', 'app', 'sha', 'fingerprint', 'receive_user', 'receive_repo',
   295                    'ssh_connection', 'ssh_original_command', 'created', 'updated']