No Clara,
The unique together validator works even in the case of Primary Key. And by
looking at the view you have written, i am assuming you are pretty new to
DRF.
Some quick observations on your view:

1. Firstly, you dont need to check whether the data is a list or not. Thats
what DRF is supposed to do. If you put many = True in the serializer, then
DRF expects the data to be list otherwise it raises a validation error. If
many = True is not set then DRF expects it to be a dictionary and does all
the required validation.
2. Try to put parent id in the reques.data itself, that way you can use
serializer.is_valid() method to reduce your code to a mere 5 line code.
3. You dont have to check for duplicated datetimes and parent id, DRF
should check that for you. I tested the same scenario as yours and it's
working fine.

The updated mode:


class Parent(models.Model):
    name = models.CharField(max_length=50)

    def __str__(self):
        return self.name


class MyAttributeChangelog(models.Model):
    parent = models.ForeignKey(Parent, on_delete=models.CASCADE)
    start_datetime = models.DateField()

    installation = models.CharField(max_length=50)

    def __str__(self):
        return self.installation

    class Meta:
        ordering = ['start_datetime']
        unique_together = ['start_datetime', 'parent']

And no changes in any other part of code.

And no need to write test for serializer. Tell me if what i have said works
for you, if not then we will dive deeper into your code.
And again Clara, don't take parent id from the url, try to send it directly
in the post data.

So that your request.data looks like:
[
    {
        "start_datetime": "2018-10-29",
        "installation": "hello2",
        "parent":3
    }
]



Hope it helps.


On Mon, Oct 29, 2018 at 7:01 PM Clara Daia <claradai...@gmail.com> wrote:

> Hello, Dipen
>
> Thank you for taking the time to help me. I had changed the names of the
> model's fields in an attempt to make the relations clearer but ended up
> making it more confusing, I suppose haha.
>
> The unique_together relationship was supposed to be between the
> 'start_datetime' and the 'parent' fields:
>
> class MyAttributeChangelog(models.Model):
>     start_datetime = models.DateField()
>
>     parent = models.ForeignKey(ParentModel, on_delete=models.CASCADE)
>
> def __str__(self):
> return self.installation
>
> class Meta:
> ordering = ['start_datetime']
> unique_together = ['start_datetime', 'parent']
>
>  Perhaps the fact that 'parent' is a FK is key to why I'm getting the
> error?
>
> This is my view code, with the provisional validation of the
> unique_together constraint:
>
> class MyChangelogViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
>     lookup_field = 'id'
>     queryset = MyChangelog.objects.all()
>     serializer_class = MyChangelogSerializer
>
>     def get_queryset(self):
>         return MyChangelog.objects.filter(parent=self.kwargs['parent_id'])
>
>     def get_serializer(self, *args, **kwargs):
>         if isinstance(kwargs.get('data', {}), list):
>             kwargs['many'] = True
>
>         return super().get_serializer(*args, **kwargs)
>
>     # Perform a rollback in case of an error
>     @transaction.atomic
>     def create(self, request, *args, **kwargs):
>         data = request.data
>         if type(data) is list:
>             for item in data:
>                 # Adds parent id from URL
>                 item['parent'] = self.kwargs['parent_id']
>         else:
>             raise ParseError(detail='Must be a list')
>         serializer = self.get_serializer(data=data)
>         serializer.is_valid(raise_exception=True)
>
>         # Checks for duplicated datetimes
>         dts_set = set()
>         for item in serializer.validated_data:
>             if item['start_datetime'] in dts_set:
>                 raise ValidationError(detail='start_datetime must be unique.')
>             else:
>                 dts_set.add(item['start_datetime'])
>
>         self.get_queryset().delete()
>         serializer.save()
>
>         return Response(serializer.data, status=status.HTTP_201_CREATED)
>
>
> I added that for to check for duplicated 'start_datetime's, and it works
> because 'parent' is always the same (you can see that it is added to the
> dicts inside the view, from the kwarg provided by drf-nested-routers).
>
> I shall write a test for the serializer instead of the view to narrow the
> error source down.
>
> Em seg, 29 de out de 2018 às 09:00, Dipen bhatt <dipenbhat...@gmail.com>
> escreveu:
>
>> Hey there Clara,
>> Hope i am not too late, I created the exact scenario that you have
>> described but it is working perfectly fine with me.
>> Could you provide more of your source code, such as your view also.
>>
>> My model:
>>
>> class MyAttributeChangelog(models.Model):
>>     # parent = models.ForeignKey(ParentModel, on_delete=models.CASCADE)
>>     start_datetime = models.DateField()
>>
>>     installation = models.CharField(max_length=50)
>>
>>     def __str__(self):
>>         return self.installation
>>
>>     class Meta:
>>         ordering = ['start_datetime']
>>         unique_together = ['start_datetime', 'installation']
>>
>>
>> My serializer
>>
>>
>> class MyAttributeChangelogSerializer(serializers.ModelSerializer):
>>
>>     class Meta:
>>         model = MyAttributeChangelog
>>         fields = ('__all__')
>>
>>
>> My viewset:
>>
>>
>> class ClaraView(viewsets.ViewSet):
>>
>>
>>     def create(self, request):
>>
>>         ser = MyAttributeChangelogSerializer(data=request.data, many=True)
>>
>>         if ser.is_valid():
>>             ser.save()
>>             return Response(ser.data)
>>         return Response("Invalid", status=HTTP_400_BAD_REQUEST)
>>
>>
>> I may have some wrong attribute specification.
>>
>>
>>
>> On Fri, Oct 26, 2018 at 7:51 PM Clara Daia <claradai...@gmail.com> wrote:
>>
>>> Hello,
>>>
>>> I have a view that can create multiple instances of a model at once. The
>>> model has a unique_together constraint and I made a ModelSerializer based
>>> on it, something like
>>>
>>> class MyAttributeChangelog(models.Model):
>>>     parent = models.ForeignKey(ParentModel, on_delete=models.CASCADE)
>>>     start_datetime = models.DateTimeField()
>>>     ...
>>>     class Meta:
>>>         ordering = ['start_datetime']
>>>         unique_together = ['start_datetime', 'installation']
>>>
>>> class MyAttributeChangelogSerializer(serializers.ModelSerializer):
>>>     ...
>>>     class Meta:
>>>         model = MyAttributeChangelog
>>>         fields = ('id', 'parent', 'start_datetime', ...)
>>>
>>> The view receives a list of dicts and uses the serializer (with
>>> many=True) to validate and save them. It works fine, except for the
>>> enforcement of the unique_together constraint. In the following test I
>>> get a IntegrityError rather than a bad request response, which was the
>>> expected:
>>>
>>> def test_update_changelog_list_with_duplicate_datetimes(self):
>>> """
>>> ...
>>> """
>>> parent = Parent.objects.get(...)
>>>
>>> url = f'/api/parents/{parent.id}/my_attribute_changelog/'
>>> data = [
>>>     {
>>>         'start_datetime': datetime(2018, 9, 20, 9, 30, 20,
>>>
>>>  tzinfo=timezone.get_current_timezone())
>>>         ...
>>>     },
>>>     {
>>>         'start_datetime': datetime(2018, 9, 20, 9, 30, 20,
>>>
>>>  tzinfo=timezone.get_current_timezone())
>>>         ...
>>>     }
>>> ]
>>> response = self.client.post(url, data, format='json')
>>>
>>> self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
>>> ...
>>>
>>> I tried adding the UniqueTogetherValidator explicitly to the serializer
>>> class, despite my understanding that it would be inferred from the Model,
>>> but the error persists.
>>>
>>> I know I can rather easily validate this in the view, iterating through
>>> the items, but I think the validator should take care of that. Am I missing
>>> something?
>>>
>>> Best regards.
>>>
>>> --
>>> You received this message because you are subscribed to the Google
>>> Groups "Django REST framework" group.
>>> To unsubscribe from this group and stop receiving emails from it, send
>>> an email to django-rest-framework+unsubscr...@googlegroups.com.
>>> For more options, visit https://groups.google.com/d/optout.
>>>
>> --
>> You received this message because you are subscribed to the Google Groups
>> "Django REST framework" group.
>> To unsubscribe from this group and stop receiving emails from it, send an
>> email to django-rest-framework+unsubscr...@googlegroups.com.
>> For more options, visit https://groups.google.com/d/optout.
>>
> --
> You received this message because you are subscribed to the Google Groups
> "Django REST framework" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to django-rest-framework+unsubscr...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.
>

-- 
You received this message because you are subscribed to the Google Groups 
"Django REST framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-rest-framework+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to