github.com/jiasir/deis@v1.12.2/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  CONFIGKEY_MATCH = re.compile(r'^[a-z_]+[a-z0-9_]*$', re.IGNORECASE)
    25  
    26  
    27  class JSONFieldSerializer(serializers.Field):
    28      """
    29      A Django REST framework serializer for JSON data.
    30      """
    31  
    32      def to_representation(self, obj):
    33          """Serialize the field's JSON data, for read operations."""
    34          return obj
    35  
    36      def to_internal_value(self, data):
    37          """Deserialize the field's JSON data, for write operations."""
    38          try:
    39              val = json.loads(data)
    40          except TypeError:
    41              val = data
    42          return val
    43  
    44  
    45  class JSONIntFieldSerializer(JSONFieldSerializer):
    46      """
    47      A JSON serializer that coerces its data to integers.
    48      """
    49  
    50      def to_internal_value(self, data):
    51          """Deserialize the field's JSON integer data."""
    52          field = super(JSONIntFieldSerializer, self).to_internal_value(data)
    53  
    54          for k, v in field.viewitems():
    55              if v is not None:  # NoneType is used to unset a value
    56                  try:
    57                      field[k] = int(v)
    58                  except ValueError:
    59                      field[k] = v
    60                      # Do nothing, the validator will catch this later
    61          return field
    62  
    63  
    64  class JSONStringFieldSerializer(JSONFieldSerializer):
    65      """
    66      A JSON serializer that coerces its data to strings.
    67      """
    68  
    69      def to_internal_value(self, data):
    70          """Deserialize the field's JSON string data."""
    71          field = super(JSONStringFieldSerializer, self).to_internal_value(data)
    72  
    73          for k, v in field.viewitems():
    74              if v is not None:  # NoneType is used to unset a value
    75                  field[k] = unicode(v)
    76  
    77          return field
    78  
    79  
    80  class ModelSerializer(serializers.ModelSerializer):
    81  
    82      uuid = serializers.ReadOnlyField()
    83  
    84      def get_validators(self):
    85          """
    86          Hack to remove DRF's UniqueTogetherValidator when it concerns the UUID.
    87  
    88          See https://github.com/deis/deis/pull/2898#discussion_r23105147
    89          """
    90          validators = super(ModelSerializer, self).get_validators()
    91          for v in validators:
    92              if isinstance(v, UniqueTogetherValidator) and 'uuid' in v.fields:
    93                  validators.remove(v)
    94          return validators
    95  
    96  
    97  class UserSerializer(serializers.ModelSerializer):
    98      class Meta:
    99          model = User
   100          fields = ['email', 'username', 'password', 'first_name', 'last_name', 'is_superuser',
   101                    'is_staff', 'groups', 'user_permissions', 'last_login', 'date_joined',
   102                    'is_active']
   103          read_only_fields = ['is_superuser', 'is_staff', 'groups',
   104                              'user_permissions', 'last_login', 'date_joined', 'is_active']
   105          extra_kwargs = {'password': {'write_only': True}}
   106  
   107      def create(self, validated_data):
   108          now = timezone.now()
   109          user = User(
   110              email=validated_data.get('email'),
   111              username=validated_data.get('username'),
   112              last_login=now,
   113              date_joined=now,
   114              is_active=True
   115          )
   116          if validated_data.get('first_name'):
   117              user.first_name = validated_data['first_name']
   118          if validated_data.get('last_name'):
   119              user.last_name = validated_data['last_name']
   120          user.set_password(validated_data['password'])
   121          # Make the first signup an admin / superuser
   122          if not User.objects.filter(is_superuser=True).exists():
   123              user.is_superuser = user.is_staff = True
   124          user.save()
   125          return user
   126  
   127  
   128  class AdminUserSerializer(serializers.ModelSerializer):
   129      """Serialize admin status for a User model."""
   130  
   131      class Meta:
   132          model = User
   133          fields = ['username', 'is_superuser']
   134          read_only_fields = ['username']
   135  
   136  
   137  class AppSerializer(ModelSerializer):
   138      """Serialize a :class:`~api.models.App` model."""
   139  
   140      owner = serializers.ReadOnlyField(source='owner.username')
   141      structure = JSONFieldSerializer(required=False)
   142      created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True)
   143      updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True)
   144  
   145      class Meta:
   146          """Metadata options for a :class:`AppSerializer`."""
   147          model = models.App
   148          fields = ['uuid', 'id', 'owner', 'url', 'structure', 'created', 'updated']
   149          read_only_fields = ['uuid']
   150  
   151  
   152  class BuildSerializer(ModelSerializer):
   153      """Serialize a :class:`~api.models.Build` model."""
   154  
   155      app = serializers.SlugRelatedField(slug_field='id', queryset=models.App.objects.all())
   156      owner = serializers.ReadOnlyField(source='owner.username')
   157      procfile = JSONFieldSerializer(required=False)
   158      created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True)
   159      updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True)
   160  
   161      class Meta:
   162          """Metadata options for a :class:`BuildSerializer`."""
   163          model = models.Build
   164          fields = ['owner', 'app', 'image', 'sha', 'procfile', 'dockerfile', 'created',
   165                    'updated', 'uuid']
   166          read_only_fields = ['uuid']
   167  
   168  
   169  class ConfigSerializer(ModelSerializer):
   170      """Serialize a :class:`~api.models.Config` model."""
   171  
   172      app = serializers.SlugRelatedField(slug_field='id', queryset=models.App.objects.all())
   173      owner = serializers.ReadOnlyField(source='owner.username')
   174      values = JSONStringFieldSerializer(required=False)
   175      memory = JSONStringFieldSerializer(required=False)
   176      cpu = JSONIntFieldSerializer(required=False)
   177      tags = JSONStringFieldSerializer(required=False)
   178      created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True)
   179      updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True)
   180  
   181      class Meta:
   182          """Metadata options for a :class:`ConfigSerializer`."""
   183          model = models.Config
   184  
   185      def validate_values(self, value):
   186          for k, v in value.viewitems():
   187              if not re.match(CONFIGKEY_MATCH, k):
   188                  raise serializers.ValidationError(
   189                      "Config keys must start with a letter or underscore and "
   190                      "only contain [A-z0-9_]")
   191          return value
   192  
   193      def validate_memory(self, value):
   194          for k, v in value.viewitems():
   195              if v is None:  # use NoneType to unset a value
   196                  continue
   197              if not re.match(PROCTYPE_MATCH, k):
   198                  raise serializers.ValidationError("Process types can only contain [a-z]")
   199              if not re.match(MEMLIMIT_MATCH, str(v)):
   200                  raise serializers.ValidationError(
   201                      "Limit format: <number><unit>, where unit = B, K, M or G")
   202          return value
   203  
   204      def validate_cpu(self, value):
   205          for k, v in value.viewitems():
   206              if v is None:  # use NoneType to unset a value
   207                  continue
   208              if not re.match(PROCTYPE_MATCH, k):
   209                  raise serializers.ValidationError("Process types can only contain [a-z]")
   210              shares = re.match(CPUSHARE_MATCH, str(v))
   211              if not shares:
   212                  raise serializers.ValidationError("CPU shares must be an integer")
   213              for v in shares.groupdict().viewvalues():
   214                  try:
   215                      i = int(v)
   216                  except ValueError:
   217                      raise serializers.ValidationError("CPU shares must be an integer")
   218                  if i > 1024 or i < 0:
   219                      raise serializers.ValidationError("CPU shares must be between 0 and 1024")
   220          return value
   221  
   222      def validate_tags(self, value):
   223          for k, v in value.viewitems():
   224              if v is None:  # use NoneType to unset a value
   225                  continue
   226              if not re.match(TAGKEY_MATCH, k):
   227                  raise serializers.ValidationError("Tag keys can only contain [a-z]")
   228              if not re.match(TAGVAL_MATCH, str(v)):
   229                  raise serializers.ValidationError("Invalid tag value")
   230          return value
   231  
   232  
   233  class ReleaseSerializer(ModelSerializer):
   234      """Serialize a :class:`~api.models.Release` model."""
   235  
   236      app = serializers.SlugRelatedField(slug_field='id', queryset=models.App.objects.all())
   237      owner = serializers.ReadOnlyField(source='owner.username')
   238      created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True)
   239      updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True)
   240  
   241      class Meta:
   242          """Metadata options for a :class:`ReleaseSerializer`."""
   243          model = models.Release
   244  
   245  
   246  class ContainerSerializer(ModelSerializer):
   247      """Serialize a :class:`~api.models.Container` model."""
   248  
   249      app = serializers.SlugRelatedField(slug_field='id', queryset=models.App.objects.all())
   250      owner = serializers.ReadOnlyField(source='owner.username')
   251      created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True)
   252      updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True)
   253      release = serializers.SerializerMethodField()
   254  
   255      class Meta:
   256          """Metadata options for a :class:`ContainerSerializer`."""
   257          model = models.Container
   258          fields = ['owner', 'app', 'release', 'type', 'num', 'state', 'created', 'updated', 'uuid']
   259  
   260      def get_release(self, obj):
   261          return "v{}".format(obj.release.version)
   262  
   263  
   264  class KeySerializer(ModelSerializer):
   265      """Serialize a :class:`~api.models.Key` model."""
   266  
   267      owner = serializers.ReadOnlyField(source='owner.username')
   268      fingerprint = serializers.CharField(read_only=True)
   269      created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True)
   270      updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True)
   271  
   272      class Meta:
   273          """Metadata options for a KeySerializer."""
   274          model = models.Key
   275  
   276  
   277  class DomainSerializer(ModelSerializer):
   278      """Serialize a :class:`~api.models.Domain` model."""
   279  
   280      app = serializers.SlugRelatedField(slug_field='id', queryset=models.App.objects.all())
   281      owner = serializers.ReadOnlyField(source='owner.username')
   282      created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True)
   283      updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True)
   284  
   285      class Meta:
   286          """Metadata options for a :class:`DomainSerializer`."""
   287          model = models.Domain
   288          fields = ['uuid', 'owner', 'created', 'updated', 'app', 'domain']
   289  
   290      def validate_domain(self, value):
   291          """
   292          Check that the hostname is valid
   293          """
   294          if len(value) > 255:
   295              raise serializers.ValidationError('Hostname must be 255 characters or less.')
   296          if value[-1:] == ".":
   297              value = value[:-1]  # strip exactly one dot from the right, if present
   298          labels = value.split('.')
   299          if 'xip.io' in value:
   300              return value
   301          if labels[0] == '*':
   302              raise serializers.ValidationError(
   303                  'Adding a wildcard subdomain is currently not supported.')
   304          allowed = re.compile("^(?!-)[a-z0-9-]{1,63}(?<!-)$", re.IGNORECASE)
   305          for label in labels:
   306              match = allowed.match(label)
   307              if not match or '--' in label or label.isdigit() or \
   308                 len(labels) == 1 and any(char.isdigit() for char in label):
   309                  raise serializers.ValidationError('Hostname does not look valid.')
   310          if models.Domain.objects.filter(domain=value).exists():
   311              raise serializers.ValidationError(
   312                  "The domain {} is already in use by another app".format(value))
   313          return value
   314  
   315  
   316  class CertificateSerializer(ModelSerializer):
   317      """Serialize a :class:`~api.models.Cert` model."""
   318  
   319      owner = serializers.ReadOnlyField(source='owner.username')
   320      expires = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True)
   321      created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True)
   322      updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True)
   323  
   324      class Meta:
   325          """Metadata options for a DomainCertSerializer."""
   326          model = models.Certificate
   327          extra_kwargs = {'certificate': {'write_only': True},
   328                          'key': {'write_only': True},
   329                          'common_name': {'required': False}}
   330          read_only_fields = ['expires', 'created', 'updated']
   331  
   332  
   333  class PushSerializer(ModelSerializer):
   334      """Serialize a :class:`~api.models.Push` model."""
   335  
   336      app = serializers.SlugRelatedField(slug_field='id', queryset=models.App.objects.all())
   337      owner = serializers.ReadOnlyField(source='owner.username')
   338      created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True)
   339      updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True)
   340  
   341      class Meta:
   342          """Metadata options for a :class:`PushSerializer`."""
   343          model = models.Push
   344          fields = ['uuid', 'owner', 'app', 'sha', 'fingerprint', 'receive_user', 'receive_repo',
   345                    'ssh_connection', 'ssh_original_command', 'created', 'updated']