0
votes

I am trying to develop a REST API with django-rest-framework for updating a django model.

I want to unit test it with the following unit test

from rest_framework.test import APITestCase
class PatchInvestmentTest(APITestCase):

    def test_repartition(self):

        investment = Investment.objects.create()
        sponsor1 = Investment.objects.create(InvestmentSponsor, name='A')
        sponsor2 = Investment.objects.create(InvestmentSponsor, name='B')

        url = reverse('investments:investments-detail', args=[investment.id])
        data = {
            'sponsorships': [
                {'sponsor': sponsor1.id, 'percentage': 80},
                {'sponsor': sponsor2.id, 'percentage': 10},
            ]
        }

        print("> data", data)

        response = self.client.patch(url, data=data)

        self.assertEqual(response.status_code, status.HTTP_200_OK)

        self.assertEqual(1, Investment.objects.count())
        investment = Investment.objects.all()[0]
        # It fails below : no investments are created
        self.assertEqual(len(investment.sponsorships()), 2) 

The model can be summed up with

class Investment(models.Model):
    # ... a few fields

    def sponsorships(self):
        return self.investmentsponsorship_set.all().order_by('sponsor__ordering', 'sponsor__name')


class InvestmentSponsor(models.Model):
    name = models.CharField(max_length=200, verbose_name=_('name'))
    ordering = models.IntegerField(default=0)

    class Meta:
        ordering = ('ordering', 'name', )


class InvestmentSponsorship(models.Model):
    sponsor = models.ForeignKey(InvestmentSponsor)
    investment = models.ForeignKey(Investment)
    percentage = models.DecimalField(max_digits=5, decimal_places=2)

The api is using rest-framework base classes

class InvestmentViewSet(viewsets.ModelViewSet):
    model = Investment

    def get_serializer_class(self):
        serializers_class_map = {
            'default': InvestmentSerializer,
            'partial_update': PartialUpdateInvestmentSerializer,
        }
        return serializers_class_map.get(self.action, serializers_class_map['default'])

    def perform_update(self, serializer):
         serializer.save()

Then I expect to get and handle the "sponsorhips" data in the serializers

class InvestmentSponsorshipSerializer(serializers.ModelSerializer):

    class Meta:
        model = models.InvestmentSponsorship
        fields = ('sponsor', 'percentage', )


class PartialUpdateInvestmentSerializer(serializers.ModelSerializer):
    sponsorships = InvestmentSponsorshipSerializer(many=True)

    class Meta:
        model = models.Investment
        fields = (
            'id', '... others', 'sponsorships',
        )

    def validate_sponsorships(self, value):
        print("validate_sponsorships", value)
        return value

    def update(self, instance, validated_data):
        """update only fields in data"""

        data = validated_data.copy()

        print("*** DATA", validated_data)

        instance.save()

        return instance

The problem is that the data I received from the serializer is empty

 > data {'sponsorships': [{'sponsor': 1, 'percentage': 80}, {'sponsor': 2, 'percentage': 10}]}
 validate_sponsorships []
 *** DATA {'sponsorships': []}

This seems to occur only when unit testing. It seems to work from the dango-rest-framework admin.

I've tried to investigate why I don't received the data as validated_data in the update with no success yet.

Any idea?

2
Have you tried defining a related_name='sponsorships' for investment foreign key on InvestmentSponsorship model ? (you would have to get rid of the sponsorship method on Investment). If that works, you can had ordering on InvestmentSponsorship Meta.Michael Rigoni
thanks for suggestion which is valid. Unfortunately, it doesn't change anything for this problemluc

2 Answers

2
votes

You should add format parameter when calling patch:

        response = self.client.patch(url, data=data, format='json')

Default multipart format does not support nesting I think.

0
votes

I found a solution by using regular django unit-test class. A bit more difficult but it works

from django.test import TestCase

class PatchInvestmentTest(TestCase):

    def test_repartition(self):

        investment = Investment.objects.create()

        sponsor1 = mommy.make(InvestmentSponsor, name='A', ordering=3)
        sponsor2 = mommy.make(InvestmentSponsor, name='B', ordering=2)

        url = reverse('investments:investments-detail', args=[investment.id])

        data = {
            "sponsorships": [
                {"sponsor": sponsor2.id, "percentage": 80},
                {"sponsor": sponsor1.id, "percentage": 10},
            ]
        }

        date_as_json = json.dumps(data)

        response = self.client.patch(url, data=date_as_json, content_type="application/json")

        self.assertEqual(response.status_code, status.HTTP_200_OK)

        self.assertEqual(1, Investment.objects.count())
        investment = Investment.objects.all()[0]
        self.assertEqual(len(investment.sponsorships()), 2)