I have model Run. It represents a time period measurement of data:
class Run(models.Model):
start_time = models.DateTimeField(db_index=True)
end_time = models.DateTimeField()
chamber = models.ForeignKey(Chamber, on_delete=models.CASCADE)
class Meta:
unique_together=(('start_time', 'chamber'),)
Here is an example of a POST request data blurb for this endpoint:
{
"start_time": "2018-11-11T12:00:00Z",
"end_time": "2018-11-11T12:00:01Z",
"chamber": "My Test Chamber"
}
A Run belongs to a Chamber and has a start_time and end_time
A Run cannot share the same Chamber and start_time. If I get the same start_time and Chamber in my request data blurb, that means the Run is still ongoing and needs to be updated.
When the request comes through the first time, I create a Run object, that works fine. The sensor that is sending the requests will then send the same POST request data blurb, changing only the end_time of the data blurb, as long as a "Run" is ongoing.
The sensor does not send PUT or PATCH, it only sends POST, this may be a problem, but it's unfixable so I have to make do with POST requests.
The behaviour I desire is:
Here is Serializer:
class RunsCreateSerializer(ModelSerializer):
class Meta:
model = Run
fields = [
'id',
'chamber',
'start_time',
'end_time'
]
Here is views.py on API endpoint:
class RunsCreateAPIView(CreateAPIView):
queryset = Run.objects.all()
queryset = queryset.prefetch_related('chamber')
serializer_class = RunsCreateSerializer
permission_classes = [IsAuthenticated]
def create(self, request, *args, **kwargs):
try:
chamber = Chamber.objects.get(chamber_name=request.data["chamber"])
request.data["chamber"] = chamber.id
except:
print("Invalid chamber name")
serializer = RunsCreateSerializer(data=request.data)
print(serializer)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors)
Now, I found I had to modify "create" on the views.py a little, as the sensor sends the Chamber as a string identifier. I then have to check that string against my Chambers table and retrieve the object instance, then modify the request data to substitute the Chamber string for its ID on my DB. I'm not sure if that is the right approach but it worked for me then.
Now for my attempt at fixing this:
On serializers.py:
def create(self, validated_data):
run, created = Run.objects.update_or_create(
chamber=validated_data.get('chamber'),
start_time=validated_data.get('start_time'),
end_time=validated_data.get('end_time'))
return run
def update(self, run, validated_data):
run.chamber = validated_data.get('chamber', run.chamber)
run.start_time = validated_data.get('start_time', run.start_time)
run.created = validated_data.get('created', run.created)
run.save()
return run
I thought by doing this on my serializer I would be able to update the object, but it doesn't seem to be reaching that point (tried with a print("Sanity check") on the create and update methods, I don't see my sanity check output, so I know I'm not reaching that point).
I suspect it has to do with me overriding the create method on the view, but I'm not sure.
If you know how to update the object, and possibly have the serializer/view handle a string for Chamber name instead of having to meddle with methods to replace strings for IDs, I would greatly appreciate your help.
As you mentioned that you are getting chamber
as a string, so to handle that relationship properly, use chamber = serializer.StringRelatedField()
in your serializer. Remove any method which you have overridden in serializer/view. Just add the following in your RunsCreateAPIView
.
def perform_create(self, serializer):
chamber=serializer.validated_data.get('chamber'),
start_time=serializer.validated_data.get('start_time')
end_time=serializer.validated_data.get('end_time')
Run.objects.update_or_create(
chamber=chamber,
start_time=start_time,
defaults = {"end_time": end_time}
)
update_or_create
will automatically handle the creation or updating based on the value of chamber and start_time.