Home > Blockchain >  Access Django M2M values in queryset without looping
Access Django M2M values in queryset without looping

Time:06-28

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

CodePudding user response:

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})

CodePudding user response:

Willem's solution using DRF is correct yet returns only the ID of the related fields. I suppose there is a way to change this and get another field yet instead I found another way with no override needed:

class OrganSerializer(serializers.ModelSerializer):
    class Meta:
        model = Organ
        fields = (('title'),)

class PathologySerializer(serializers.ModelSerializer):
    class Meta:
        model = Pathology
        fields = (('title'),)

class ContentSerializer(serializers.ModelSerializer):
    organs = OrganSerializer(many=True, read_only=True)
    pathology = PathologySerializer(many=True, read_only=True)
    
    class Meta:
        model = Content
        fields = '__all__'
     

Rest of the solution was spot on. Didn't try the prod PGSQL yet

  • Related