Stevy
Stevy

Reputation: 3387

Django serializer.data contains non existant value

Context

I have an API endpoint that is used for devices to perform a check-in.

When a device is not known:

API response:

{
    "customer_device_uuid": "646aaff6-debf-4281-bd7f-064dd6dc8ab8",
    "device_group": {
        "group_uuid": "ebd0990b-aeb5-46a4-9fad-82237a5a5118",
        "device_group_name": "Default",
        "color": "4286f4",
        "is_default": true
    }
}

When a device is known:

API response:

{
    "customer_device_uuid": "fbbdf1d1-766d-40a9-961f-2c5a5cb3db6e"
}

The problem

The problem is the API response when a known device performs a check-in. The returned customer_device_uuid does not exist in the database and seems like a randomly generated uuid.

I want the API response for a known device to be the same as the API response for an unknown device.

serializer.data contains the random uuid untill the else statement. From there serializer.data contains the data I need in my response.

I tried to call self.perform_create in the if block. This results in serializer.data having the correct data for the response. However, this creates a new CustomerDevice and related DeviceStatus for every check-in no matter if the device is known or unknown.

My views.py:

class DeviceCheckinViewSet(viewsets.ModelViewSet):
    serializer_class = CheckinSerializer
    queryset = CustomerDevice.objects.all()
    http_method_names = 'post'

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        # if the device is known -> just log the data
        if CustomerDevice.objects.filter(device_id_android=serializer.validated_data['device_id_android']).exists():
            self.log_device_status(request)
            headers = self.get_success_headers(serializer.data)
            return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

        # if device is not known -> link it to the customer and log the device data
        else:
            self.perform_create(serializer)
            customer_device = CustomerDevice.objects.get(device_id_android=request.data['device_id_android'])
            DeviceStatus.objects.create(
                customer_device_id=customer_device.customer_device_uuid,
                disk_space=request.data['disk_space'],
                battery_level=request.data['battery_level'],
                battery_cycles=request.data['battery_cycles'],
                battery_health=request.data['battery_health']
            )
            headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    # creates new instance with default group
    def perform_create(self, serializer):
        serializer.save(group_uuid=get_default_group())


    def log_device_status(request):
        DeviceStatus.objects.create(
            customer_device_id=request.data['customer_device_uuid'],
            disk_space=request.data['disk_space'],
            battery_level=request.data['battery_level'],
            battery_cycles=request.data['battery_cycles'],
            battery_health=request.data['battery_health']
        )

My serializers.py

class DeviceGroupSerializer(serializers.ModelSerializer):
    class Meta:
        model = DeviceGroup
        fields = ('group_uuid', 'device_group_name', 'color', 'is_default')


class CheckinSerializer(serializers.ModelSerializer):
    device_group = DeviceGroupSerializer(many=False, read_only=True, source='group_uuid')

    class Meta:
        model = CustomerDevice
        fields = ('customer_device_uuid', 'customer_uuid', 'device_id_android', 'device_group')
        extra_kwargs = {
            'customer_uuid': {'write_only': True},
            'device_id_android': {'write_only': True}
        }

Desired outcome

The below API response either when a device is known or unknown.

{
    "customer_device_uuid": "646aaff6-debf-4281-bd7f-064dd6dc8ab8",
    "device_group": {
        "group_uuid": "ebd0990b-aeb5-46a4-9fad-82237a5a5118",
        "device_group_name": "Default",
        "color": "4286f4",
        "is_default": true
    }
}

EDIT

Added DeviceGroupSerializer() to serializers.py

Relevant models.py:

# Customer table
class Customer(models.Model):
    customer_uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, db_index=True)
    customer_name = models.CharField(max_length=128, unique=True)
    users = models.ManyToManyField('auth.User', related_name='customers')

    def __str__(self):
        return self.customer_name

    class Meta:
        db_table = 'customer'


# Device group table
class DeviceGroup(models.Model):
    group_uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, db_index=True)
    customer_uuid = models.ForeignKey(Customer, on_delete=models.DO_NOTHING)
    device_group_name = models.CharField(max_length=20)
    color = models.CharField(max_length=8)
    is_default = models.BooleanField(default=False)

    def __str__(self):
        return self.device_group_name

    class Meta:
        db_table = 'device_group'


# Customer_device table
class CustomerDevice(models.Model):
    customer_uuid = models.ForeignKey(Customer, on_delete=models.CASCADE)
    customer_device_uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    group_uuid = models.ForeignKey(DeviceGroup, null=True, blank=True, on_delete=models.SET(get_default_group))
    device_id_android = models.CharField(max_length=100, blank=True, null=True)  # generated by client

    def __repr__(self):
        return '%r' % self.customer_device_uuid

    class Meta:
        db_table = 'customer_device'
        unique_together = (('customer_uuid', 'customer_device_uuid'),)  # composite key


# Device status table
class DeviceStatus(models.Model):
    device_status_uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, db_index=True)
    disk_space = models.PositiveIntegerField(blank=True, null=True)
    battery_level = models.PositiveIntegerField(blank=True, null=True)
    battery_health = models.CharField(max_length=30)
    battery_cycles = models.PositiveIntegerField(blank=True, null=True)
    customer_device = models.ForeignKey(CustomerDevice, on_delete=models.CASCADE, related_name="customer_device")

    def __repr__(self):
        return '%r' % self.device_status_uuid

    class Meta:
        db_table = 'device_status'

Upvotes: 0

Views: 64

Answers (1)

andreihondrari
andreihondrari

Reputation: 5833

I have instantiated a Customer with uuid e73141ab-883c-44bc-8ce2-21b492cab03e and a DeviceGroup with is_default set to True so that get_default_group can assign it.

Now what happens is that when I call the endpoint with:

{
  "customer_uuid": "e73141ab-883c-44bc-8ce2-21b492cab03e",
  "customer_device_uuid": "444b944a-4bcf-4ea2-8860-1d3ca82fd1d3",
  "device_id_android": "111",
  "disk_space": 10,
  "battery_level": 10,
  "battery_health": "good",
  "battery_cycles": 10
}

The response that is yielded is:

{
    "customer_device_uuid": "0d53c7d4-ce34-42ce-9dd8-5e7ea07c4438",
    "device_group": {
        "group_uuid": "5ee2f30e-bfd1-4f31-83ad-753d1d7220ad",
        "device_group_name": "",
        "color": "",
        "is_default": true
    }
}

It is exactly what you needed. My assumption is that either your get_default_group is not returning a default group.

The reason why the second time your serializer does not yield your desired output is because you are serializing the request data instead of the instance, and in the request data you don't have a group_uuid so you need to do the following:

# if the device is known -> just log the data
customer_device = CustomerDevice.objects.filter(
    device_id_android=serializer.validated_data['device_id_android']).first()
if customer_device:
    self.log_device_status(request)
    headers = self.get_success_headers(serializer.data)
    customer_device_serializer = CheckinSerializer(instance=customer_device)
    return Response(customer_device_serializer.data, status=status.HTTP_201_CREATED, headers=headers)

Upvotes: 1

Related Questions