python – Django Restframework: a nested serializer with custom behavior

I'm trying to train with Django and the Django REST framework.
As an example of a toy, I want to configure a server on which I can post the automated test results.
In the simplest version, a test result contains the following information:

  • a test identifier, which is unique and will never change
  • a test name, which can change from time to time
  • a verdict, which can only take one of the predefined values ​​"PASS", "FAIL" or "SKIPPED"

I came with three different possible solutions. Everyone works, but I'm not sure of the best approach because I lack experience.

The implementation must fulfill the following conditions:

  • POST request containing a verdict other than PASS, FAIL or SKIPPED -> HTTP 400
  • POST request with a new test id not yet used -> create and use a new test instance
  • POST request with an existing test identifier but with a different test name -> retrieve the existing testcase instance for this identifier and update the name in the database
  • Request GET -> returns the list of all test results with the test ID box, the name of the test case and the verdict

For models, I have the following code:

models.py:

django.db import templates


Class Testcase (models.Model):
uid = models.CharField (max_length = 10, unique = True)
name = models.CharField (max_length = 50)

def __str __ (auto):
returns f "{self.uid: <10} {self.name:<50}"


class Verdict(models.Model):
    value = models.CharField(max_length=10, unique=True)

    def __str__(self):
        return f"{self.value:>ten}"


Testresult class (models.Model):
testcase = models.ForeignKey (Testcase, related_name = & # 39; results & # 39 ;, on_delete = models.CASCADE) # many-to-one
verdict = models.ForeignKey (Verdict, on_delete = models.CASCADE) # many-to-one

def __str __ (auto):
return f "{self.testcase}: {self.verdict}"

views.py:

from the import view sets rest_framework, status
from rest_framework.response import Answer

of .models import Testresult, Testcase, Verdict
from .serializers import StandardTestresultSerializer

CustomTestresultViewSet class (viewsets.ModelViewSet):
queryset = Testresult.objects.all ()
serializer_class = StandardTestresultSerializer

def create (self, request, * arguments, ** kwargs):
try:
verdict = Verdict.objects.get (value = request.data["verdict.value"])
except (Verdict.DoesNotExist, KeyError):
return Response (status = status.HTTP_400_BAD_REQUEST)

testcase = self._create_or_update_testcase (request)

testresult = Testresult.objects.create (verdict = verdict, testcase = testcase)
return Response (StandardTestresultSerializer (testresult) .data)

def _create_or_update_testcase (auto, request):
uid = request.data.get ("testcase.uid")
name = request.data.get ("testcase.name")
try:
testcase = Testcase.objects.get (uid = uid)
if testcase.name! = name:
testcase.name = name
testcase.save ()
except Testcase.DoesNotExist:
testcase = Testcase.objects.create (** request.data["testcase"])
test return

sérialiseurs.py:

since the import serializers rest_framework

of .models import Testresult, Verdict, Testcase


StandardVerdictSerializer class (serializers.ModelSerializer):
Meta class:
model = verdict
fields = (& # 39;,)


StandardTestcaseSerializer class (serializers.ModelSerializer):
Meta class:
model = Testcase
fields = ('uid', & nbsp; name & # 39;)


StandardTestresultSerializer class (serializers.ModelSerializer):
testcase = StandardTestcaseSerializer ()
verdict = StandardVerdictSerializer ()

Meta class:
model = Testresult
fields = (& # 39; testcase & # 39 ;, verdict & # 39;)

What I like in this approach

The logic of finding / creating / updating Verdict and Testcase instances and returning error states resides in the View class.
From what I've understood up to now, serializers are not really meant to be used as a way to recover existing instances.

What I do not like

Validation of entry that is ignored for Verdict and Testcase, normally done by serializers. This is not a problem at the moment, but if (for example) the Verdict model gets a new field date it could become more important.


views.py:

StandardTestresultViewSet class (viewsets.ModelViewSet):
queryset = Testresult.objects.all ()
serializer_class = UnvalidatedTestresultSerializer

sérialiseurs.py:

Class UnvalidatedVerdictSerializer (serializers.ModelSerializer):
Meta class:
model = verdict
fields = (& # 39;,)
extra_kwargs = {
& # 39 ;: & gt; validators & # 39 ;: []}
}

def create (self, validated_data):
print ("entered create")
try:
returns Verdict.objects.get (** validated_data)
except ObjectDoesNotExist:
print ("in exception handler")
raise serializers.ValidationError (detail = "Unknown result value", code = status.HTTP_400_BAD_REQUEST)


class UnvalidatedTestcaseSerializer (serializers.ModelSerializer):
Meta class:
model = Testcase
fields = ('uid', & nbsp; name & # 39;)
extra_kwargs = {
& # 39; uid: {& # 39; validators & # 39 ;: []}
}

def create (self, validated_data):
uid = validated_data.get (uid & # 39;)
name = validated_data.get (& # 39; name)
try:
testcase = Testcase.objects.get (uid = uid)
if testcase.name! = name:
print ("Update testcase name")
testcase.name = name
testcase.save ()
other:
print ("The Testcase name matches, do not update")
except ObjectDoesNotExist:
testcase = Testcase.objects.create (** validated_data)
print ("New instance created:", testcase)
test return


class UnvalidatedTestresultSerializer (serializers.ModelSerializer):
testcase = UnvalidatedTestcaseSerializer ()
verdict = UnvalidatedVerdictSerializer ()

Meta class:
model = Testresult
fields = (& # 39; testcase & # 39 ;, verdict & # 39;)

def create (self, validated_data):
verdict_data = validated_data.pop (& # 39; verdict & # 39;)
serialized_verdict = UnvalidatedVerdictSerializer (data = verdict_data)
serialized_verdict.is_valid (raise_exception = True)
verdict_instance = serialized_verdict.save ()

testcase_data = validated_data.pop (& # 39; testcase & # 39;)
serialized_testcase = UnvalidatedTestcaseSerializer (data = testcase_data)
serialized_testcase.is_valid (raise_exception = True)
testcase_instance = serialized_testcase.save ()

returns Testresult.objects.create (testcase = instance_test, verdict = instance_vertit)

What I like

ModelSerializer validation mechanisms can be used.

What I do not like

create the methods do not actually create model instances, but simply retrieve and modify them as needed.
GET requests will also use "unvalidated" serializers, which I do not see as a problem at the moment, but perhaps not a good practice as well.


In this approach, the same serializer classes as those shown in Take One and Take Two will be used. I only give the code of the View class.

views.py:

Class DynamicTestresultViewSet (viewsets.ModelViewSet):
queryset = Testresult.objects.all ()

def get_serializer_class (auto):
if self.request.method == "POST":
return UnvalidatedTestresultSerializer
other:
returns StandardTestresultSerializer

What I like

Serializers allow you to analyze and validate the input data of the application. GET requests will be served with the help of the "normal" serializer.

What I do not like

Requires six serializer classes instead of three.


Which approach would you suggest or would you do in a totally different way?