Skip to content
Advertisement

Access Django M2M values in queryset without looping

I receive some data through Ajax that allows me to do some filtering on a model that has some m2m relations (say Model). I get a queryset, say “content” that I then need to send back using json. Here are simplified lines of code:

content =  Models.objects.all()
content = content.values
return JsonResponse({"data":list(content)})

It was working fine but the project changed and I now need to include the values of some of the m2m related models attached to Model to each queryset result instances, problem is that content=Model.objects.all() will of course not give me these values without some looping so I am stuck. I remember using a depth option in DRF that would have done the job here but unfortunately I cannot do this now as the project is too advanced and live. Any way to add directly in the queryset the results of the m2m related values ? Many many thanks

Adding simplified models here :

class Content(models.Model):
    uuid = models.CharField(max_length=255, primary_key=True, default=uuid.uuid4, editable=False)
    pathology = models.ManyToManyField(Pathology, null=True, blank=True)
    organs = models.ManyToManyField(Organ, null=True, blank=True)
    title = models.CharField(verbose_name="Titre",
                              max_length=255, null=True, blank=False)
    document = models.ForeignKey(
        'wagtaildocs.Document',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )
    description = RichTextField(null=True, blank=True)

class Pathology(models.Model):
    title = models.CharField(max_length=255, null=True, blank=True)

    def __str__(self):
        return self.title

class Organ(models.Model):
    title = models.CharField(max_length=255, null=True, blank=True)

    def __str__(self):
        return self.title

Advertisement

Answer

Please don’t use .values(…) [Django-antipatterns], these erode the logic in the model layer. Furthere you can not use .values(…) bidirectionally: you can only convert records into dictionaries, not deserialize dictionaries into model objects.

In PostgreSQL you can make use of ArrayAgg [Django-doc], this is an aggregate that only works for PostgreSQL, and furthermore it is probably not ideal either, since again it makes adapting the serialization process cumbersome. You can use this as:

from django.contrib.postgres.aggregates import ArrayAgg

content = Models.objects.values().annotate(
    organs=ArrayAgg('organs__pk')
)
return JsonResponse({"data":list(content)})

Using The Django REST framework is however still possible with the current view. You can install this framework and define a serializer with:

from rest_framework import serializers

class ContentSerializer(serializers.ModelSerializer):
    organs = serializers.PrimaryKeyRelatedField(many=True)
    
    class Meta:
        model = Content
        fields = '__all__'

Then in the view you can work with:

contents = Models.objects.prefetch_related('organs')
serializer = ContentSerializer(contents, many=True)
return JsonResponse({'data': serializer.data})
Advertisement