Skip to content

Instantly share code, notes, and snippets.

@marteinn
Last active June 25, 2019 07:49
Show Gist options
  • Save marteinn/777a07241a7ed24ba65b69e319d52df5 to your computer and use it in GitHub Desktop.
Save marteinn/777a07241a7ed24ba65b69e319d52df5 to your computer and use it in GitHub Desktop.
Serializers - 7 Tips

Index

  1. Överanvänd inte SerializerMethodField
  2. När du mappar om fält, glöm inte bort "source"
  3. Undvik att overrida lifecycle i vyer
  4. Om du inte använder ModelSerializer, se över vad du döper dina argument
  5. Var sparsam med context och använd bara i nödfall
  6. Se context som ett argument och använd bara fallback i undantagsfall
  7. Har du logik i en serialiserare, skriv ett test!

1. Överanvänd inte SerializerMethodField

Använd dom bara när du vill transformera data.

  • 👎 här är SerializerMethodField omotiverad

    class UserSerializer(serializers.ModelSerializer):
        id = serializers.SerializerMethodField()
      
        class Meta:
            model = User
            fields = ["id"]
      
        def get_id(self, obj):
            return obj.id
  • 👍 här är allt deklarerat i metaklassen

    class UserSerializer(serializers.ModelSerializer):
        class Meta:
            model = User
            fields = ["id"]
  • 👍 här använder vi SerializerMethodField för att transformerar

    class UserSerializer(serializers.ModelSerializer):
        full_name = serializers.SerializerMethodField()
        
        class Meta:
            model = User
            fields = ["full_name"]
        
        def get_full_name(self, obj):
            return f"{obj.first_name} {obj.last_name}"

2. När du mappar om fält, glöm inte bort "source"

  • 👎 onödig SerializerMethodField

    class ProductSerializer(serializers.ModelSerializer):
        title = serializers.SerializerMethodField()
        
        class Meta:
            model = Product
            fields = ["contact_email"]
      
        def get_title(self, obj):
            return obj.text
  • 👍 här använder vi source för att mappa om fältnamnet

    class ProductSerializer(serializers.ModelSerializer):
        title = serializers.CharField(source="text")
    
        class Meta:
            model = Product
            fields = ["title"]
  • 👎 onödig SerializerMethodField

    class ProductSerializer(serializers.ModelSerializer):
        producer = serializers.SerializerMethodField()
    
        class Meta:
            model = Product
            fields = ["producer"]
    
        def get_producer(self, obj):
            return obj.company.producer
  • 👍 här använder vi också nästling fast genom relationen

    class ProductSerializer(serializers.ModelSerializer):
        producer = serializers.CharField(source="company.producer")
    
        class Meta:
            model = Product
            fields = ["producer"]

3. Undvik att overrida lifecycle i vyer

...serializers kan göra jobbet

  • 👎 här implementerar vi i vyn vad som redan finns i serialiseraren

    # my_app/views.py
    class ProductCreateView(CreateAPIView):
        def post(self, request, *args, **kwargs):
            serializer = ProductSerializer(data=request.data)
            serializer.is_valid(raise_exception=True)
            serializer.save()
    
            return Response({}, status=status.HTTP_201_CREATED)
    
    # my_app/serializers.py
    class ProductSerializer(ModelSerializer):
        class Meta:
            model = Product
            fields = ("title", "price", "color")
  • 👍 här utnyttjar vi mappningen mellan CreateAPIView och ModelSerializer för att skapa en ny modell

    # my_app/views.py
    class ProductCreateView(CreateAPIView):
        serializer_class = ProductSerializer
    
    # my_app/serializers.py
    class ProductSerializer(ModelSerializer):
        class Meta:
            model = Product
            fields = ("title", "price", "color")

4. Om du inte använder ModelSerializer, se över hur du döper dina argument

  • 👎 vad innebär obj i get_last_logged_in?

    class CreateUserSerializer(serializers.Serializer):
        def get_last_logged_in(self, obj):
            return obj.history.order_by("create").first()
  • 👍 genom att ersätta obj med user så blir det tydligare vad det är för data vi jobbar med

    class CreateUserSerializer(serializers.Serializer):
        def get_last_logged_in(self, user):
            return user.history.order_by("create").first()

5. Var sparsam med context och använd bara i nödfall

Onödig context

  • 👎 requesten har inget med serialiseraren att göra, detta kan man göra någon annan stans

    # views.py
    def get_activity_page(self, request, activity_id):
        activity = Activity.objects.get(id=activity_id)
        return ActivitySerializer(
            activity, context={"request": request}
        ).data
        
    # my_app/serializers.py
    class ActivitySerializer(ModelSerializer):
        ...
        def get_domain(self, obj):
            request = self.context.get("request")
            site = request.site
            return site.domain
  • 👍 domain har inget med activity att göra, så vi returerar den i nivån ovanför

    # views.py
    def get_activity_page(self, request, activity_id):
        activity = Activity.objects.get(id=activity_id)
        return {
            "activity": ActivitySerializer(activity).data,
            "domain": request.site.domain,
        }
        
    # my_app/serializers.py
    class ActivitySerializer(ModelSerializer):
        ...

Undvik att passa ner onödiga abstraktioner

  • 👎 request är ett onödigt lager för att nå property user

    # helpers.py
    def activity_to_props(self):
        return ActivitySerializer(
            activity, context={"request": self.request}
        ).data
        
    # my_app/serializers.py
    class ActivitySerializer(ModelSerializer):
        ...
        def get_has_activity(self, obj):
            request = self.context.get("request")
            user = request.user
            
            return UserActivity.objects.exists(user=user)
  • 👍 vi passar här in endast det serialiseraren behöver för att fungera

    # helpers.py
    def activity_to_props(self):
        return ActivitySerializer(
            activity, context={"user": self.request.user}
        ).data
        
    # my_app/serializers.py
    class ActivitySerializer(ModelSerializer):
        ...
        def get_has_activity(self, obj):
            user = self.context.get("user")
            return UserActivity.objects.exists(user=user)

6. Se context som ett argument och använd bara fallback i undantagsfall

  • 👎 genom att falla tillbaka på None när user inte finns gör vi det svårare att felsöka get_has_activity då den nu har två skäl att returera False.

    # my_app/serializers.py
    class ActivitySerializer(ModelSerializer):
        ...
        def get_has_activity(self, obj):
            user = self.context.get("user", None)
            if not user:
                return False
            return UserActivity.objects.exists(user=user)
  • 👍 nu finns det bara en anledning till get_has_activity att returera False. Vi kommer också att märka om user ej är nerpassad genom en exception, vilket ger oss ett tydligare kontrakt mot serialiseraren.

    # my_app/serializers.py
    class ActivitySerializer(ModelSerializer):
        ...
        def get_has_activity(self, obj):
            user = self.context.get("user")
            return UserActivity.objects.exists(user=user)

7. Har du logik i en serialiserare, skriv ett test!

  • Har du fält i din serialiserare med logik, skriv ett test för att underlätta refakturering.

    # my_app/serializers.py
    class OrderSerializer(ModelSerializer):
        ...
        def get_shipping_cost(self, obj):
            if obj.shipping_option.pickup_in_store:
                return 0
    
            if obj.shipping_option.type == ShippingType.INSTABOX:
                return 0
    
            weight = obj.total_weight
            if weight > 2:
                return 200
            return 50
  • Ett test kan se ut så här:

    from django.test import TestCase
    
    from .serializers import OrderSerializer
    from .factories import OrderFactory
    
    def OrderShippingTest(TestCase):
    
    def test_that_order_shipping_cost_is_correct(self):
        self.assertEqual(
            OrderSerializer(
                OrderFactory(shipping_option__pickup_in_store=True)
            ).data["shipping_cost"], 0
        )
        self.assertEqual(
            OrderSerializer(
                OrderFactory(shipping_option__type="INSTABOX")
            ).data["shipping_cost"], 0
        )
        self.assertEqual(
            OrderSerializer(
                OrderFactory(shipping_option__type="INSTABOX", total_weight=3)
            ).data["shipping_cost"], 0
        )

Övrigt

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment