[RFC,2/4] REST: Support embedded serializers without context

Message ID 20180110004024.5551-3-stephen@that.guru
State Under Review
Headers show
Series
  • Add 'Event.payload' field
Related show

Commit Message

Stephen Finucane Jan. 10, 2018, 12:40 a.m.
The use of 'HyperlinkedIdentityField' in a serializer requires the
presence of a request context so that it's possible to get URL
information. In general, this is no issue. However, going forward, this
will not be guaranteed. Support serialization without this information
by simply setting this field to None (null).

Signed-off-by: Stephen Finucane <stephen@that.guru>
---
 patchwork/api/embedded.py | 60 ++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 46 insertions(+), 14 deletions(-)

Patch

diff --git a/patchwork/api/embedded.py b/patchwork/api/embedded.py
index 7b5090a0..ef7df3d1 100644
--- a/patchwork/api/embedded.py
+++ b/patchwork/api/embedded.py
@@ -24,14 +24,45 @@  nested fields.
 """
 
 from rest_framework.serializers import CharField
-from rest_framework.serializers import HyperlinkedModelSerializer
+from rest_framework.serializers import URLField
+from rest_framework.serializers import ModelSerializer as BaseSerializer
 from rest_framework.serializers import SerializerMethodField
 
-from patchwork.api.base import CheckHyperlinkedIdentityField
 from patchwork import models
 
 
-class MboxMixin(HyperlinkedModelSerializer):
+class ModelSerializer(BaseSerializer):
+    """The base serializer for all other embedded serializers.
+
+    This is a variant of ``ModelSerializer`` that doesn't use
+    ``HyperlinkedIndentityField`` for the ``url`` attribute. This is because in
+    some cases (creation of events via signals) we do not have sufficient
+    context to figure out the absolute URL.
+    """
+
+    def build_url_field(self, field_name, model_class):
+        """Create a field representing the object's own URL.
+
+        This variant only uses a ``HyperLinkedIdentityField`` if suitable
+        context is available. If not, it sets said field to None for later
+        population.
+        """
+        if not self.context:
+            return URLField, {'default': None}
+
+        return super(ModelSerializer, self).build_url_field(
+            field_name, model_class)
+
+    def get_extra_kwargs(self):
+        extra_kwargs = super(ModelSerializer, self).get_extra_kwargs()
+
+        if not self.context:
+            extra_kwargs.pop('url', None)
+
+        return extra_kwargs
+
+
+class MboxMixin(ModelSerializer):
     """Embed an link to the mbox URL.
 
     This field is just way too useful to leave out of even the embedded
@@ -42,10 +73,11 @@  class MboxMixin(HyperlinkedModelSerializer):
 
     def get_mbox(self, instance):
         request = self.context.get('request')
-        return request.build_absolute_uri(instance.get_mbox_url())
+        if request:
+            return request.build_absolute_uri(instance.get_mbox_url())
 
 
-class BundleSerializer(MboxMixin, HyperlinkedModelSerializer):
+class BundleSerializer(MboxMixin, ModelSerializer):
 
     class Meta:
         model = models.Bundle
@@ -56,9 +88,9 @@  class BundleSerializer(MboxMixin, HyperlinkedModelSerializer):
         }
 
 
-class CheckSerializer(HyperlinkedModelSerializer):
+class CheckSerializer(ModelSerializer):
 
-    url = CheckHyperlinkedIdentityField('api-check-detail')
+    url = URLField(required=False)
 
     def to_representation(self, instance):
         data = super(CheckSerializer, self).to_representation(instance)
@@ -75,7 +107,7 @@  class CheckSerializer(HyperlinkedModelSerializer):
         }
 
 
-class CoverLetterSerializer(MboxMixin, HyperlinkedModelSerializer):
+class CoverLetterSerializer(MboxMixin, ModelSerializer):
 
     class Meta:
         model = models.CoverLetter
@@ -86,7 +118,7 @@  class CoverLetterSerializer(MboxMixin, HyperlinkedModelSerializer):
         }
 
 
-class PatchSerializer(MboxMixin, HyperlinkedModelSerializer):
+class PatchSerializer(MboxMixin, ModelSerializer):
 
     class Meta:
         model = models.Patch
@@ -97,7 +129,7 @@  class PatchSerializer(MboxMixin, HyperlinkedModelSerializer):
         }
 
 
-class PersonSerializer(HyperlinkedModelSerializer):
+class PersonSerializer(ModelSerializer):
 
     class Meta:
         model = models.Person
@@ -108,7 +140,7 @@  class PersonSerializer(HyperlinkedModelSerializer):
         }
 
 
-class ProjectSerializer(HyperlinkedModelSerializer):
+class ProjectSerializer(ModelSerializer):
 
     link_name = CharField(max_length=255, source='linkname')
     list_id = CharField(max_length=255, source='listid')
@@ -124,7 +156,7 @@  class ProjectSerializer(HyperlinkedModelSerializer):
         }
 
 
-class SeriesSerializer(MboxMixin, HyperlinkedModelSerializer):
+class SeriesSerializer(MboxMixin, ModelSerializer):
 
     class Meta:
         model = models.Series
@@ -135,7 +167,7 @@  class SeriesSerializer(MboxMixin, HyperlinkedModelSerializer):
         }
 
 
-class UserSerializer(HyperlinkedModelSerializer):
+class UserSerializer(ModelSerializer):
 
     class Meta:
         model = models.User
@@ -146,7 +178,7 @@  class UserSerializer(HyperlinkedModelSerializer):
         }
 
 
-class UserProfileSerializer(HyperlinkedModelSerializer):
+class UserProfileSerializer(ModelSerializer):
 
     username = CharField(source='user.username')
     first_name = CharField(source='user.first_name')