[v2,1/6] REST: Support embedded serializers without context

Message ID 20180409210256.19649-2-stephen@that.guru
State New
Headers show
Series
  • Add 'Event.payload' field
Related show

Commit Message

Stephen Finucane April 9, 2018, 9:02 p.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 | 58 ++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 45 insertions(+), 13 deletions(-)

Patch

diff --git a/patchwork/api/embedded.py b/patchwork/api/embedded.py
index d79724c4..982a0d26 100644
--- a/patchwork/api/embedded.py
+++ b/patchwork/api/embedded.py
@@ -25,13 +25,44 @@  nested fields.
 
 from rest_framework.serializers import CharField
 from rest_framework.serializers import SerializerMethodField
+from rest_framework.serializers import URLField
 
 from patchwork.api.base import BaseHyperlinkedModelSerializer
-from patchwork.api.base import CheckHyperlinkedIdentityField
 from patchwork import models
 
 
-class MboxMixin(BaseHyperlinkedModelSerializer):
+class ModelSerializer(BaseHyperlinkedModelSerializer):
+    """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(BaseHyperlinkedModelSerializer):
 
     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, BaseHyperlinkedModelSerializer):
+class BundleSerializer(MboxMixin, ModelSerializer):
 
     class Meta:
         model = models.Bundle
@@ -56,9 +88,9 @@  class BundleSerializer(MboxMixin, BaseHyperlinkedModelSerializer):
         }
 
 
-class CheckSerializer(BaseHyperlinkedModelSerializer):
+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(BaseHyperlinkedModelSerializer):
         }
 
 
-class CoverLetterSerializer(MboxMixin, BaseHyperlinkedModelSerializer):
+class CoverLetterSerializer(MboxMixin, ModelSerializer):
 
     class Meta:
         model = models.CoverLetter
@@ -89,7 +121,7 @@  class CoverLetterSerializer(MboxMixin, BaseHyperlinkedModelSerializer):
         }
 
 
-class PatchSerializer(MboxMixin, BaseHyperlinkedModelSerializer):
+class PatchSerializer(MboxMixin, ModelSerializer):
 
     class Meta:
         model = models.Patch
@@ -100,7 +132,7 @@  class PatchSerializer(MboxMixin, BaseHyperlinkedModelSerializer):
         }
 
 
-class PersonSerializer(BaseHyperlinkedModelSerializer):
+class PersonSerializer(ModelSerializer):
 
     class Meta:
         model = models.Person
@@ -111,7 +143,7 @@  class PersonSerializer(BaseHyperlinkedModelSerializer):
         }
 
 
-class ProjectSerializer(BaseHyperlinkedModelSerializer):
+class ProjectSerializer(ModelSerializer):
 
     link_name = CharField(max_length=255, source='linkname')
     list_id = CharField(max_length=255, source='listid')
@@ -127,7 +159,7 @@  class ProjectSerializer(BaseHyperlinkedModelSerializer):
         }
 
 
-class SeriesSerializer(MboxMixin, BaseHyperlinkedModelSerializer):
+class SeriesSerializer(MboxMixin, ModelSerializer):
 
     class Meta:
         model = models.Series
@@ -138,7 +170,7 @@  class SeriesSerializer(MboxMixin, BaseHyperlinkedModelSerializer):
         }
 
 
-class UserSerializer(BaseHyperlinkedModelSerializer):
+class UserSerializer(ModelSerializer):
 
     class Meta:
         model = models.User
@@ -149,7 +181,7 @@  class UserSerializer(BaseHyperlinkedModelSerializer):
         }
 
 
-class UserProfileSerializer(BaseHyperlinkedModelSerializer):
+class UserProfileSerializer(ModelSerializer):
 
     username = CharField(source='user.username')
     first_name = CharField(source='user.first_name')