0

Je dois compter le nombre d'enfants dont un objet dispose et renvoyer cette valeur dans mon API via le sérialiseur d'objet. J'ai également besoin de compter un sous-ensemble de ces objets enfants.Django rest_framework: nombre d'objets enfants dans le sérialiseur

J'ai un objet Tâche avec des enfants Asignees. Dans mon API lorsque je fais une recherche les tâches que je veux avoir l'ensemble de données suivantes retourné:

[ 
    { label: "Cross the bridge", 
     count_assigned: 5, 
     count_completed: 3 }, 
    { label: "Build a fire", 
     count_assigned: 5, 
     count_completed: 2 } 
] 

Comment puis-je faire cela? J'ai trouvé la méthode .annotate() mais ce résultat n'est pas disponible dans la classe sérialiseur.

models.py

class Task(models.Model): 
    label   = models.CharField(max_length=255,null=False,blank=False) 

class Assignee(models.model): 
    task   = models.ForeignKey(Task, related_name='assignees', on_delete=models.CASCADE, blank=True, null=True) 
    person  = models.ForeignKey(Person, on_delete=models.CASCADE, blank=True, null=True) 
    completed = models.DateTimeField(null=True,blank=True) 

serializers.py

from rest_framework import serializers 

from .models import Task, Assignee 
from people.serializers import PersonSerializer 

class AssigneeSerializer(serializers.ModelSerializer): 
    id = serializers.ReadOnlyField() 
    person = PersonSerializer(read_only=True) 

    class Meta: 
     model = Assignee 

     fields = ('id','task','person','completed') 
     read_only_fields = ['id'] 


class TaskSerializer(serializers.ModelSerializer): 
    id = serializers.ReadOnlyField() 

    class Meta: 
     model = Task 

     fields = ('id', 'label') 
     read_only_fields = ['id'] 
+0

Qu'est-ce que votre point de vue ressemble-t-API? Utilisez-vous un spectateur? Si oui [cette réponse] (https://stackoverflow.com/a/36697562/104349) (notez, pas la réponse acceptée sur cette question) fera ce que vous voulez. –

Répondre

1

La façon proposée

class TaskSerializer(serializers.ModelSerializer): 
    id = serializers.ReadOnlyField() 
    count_assigned = serializers.SerializerMethodField() 
    count_completed = serializers.SerializerMethodField() 

    class Meta: 
     model = Task 
     fields = ('id', 'label', 'count_assigned', 'count_completed') 

    def get_count_assigned(self, obj): 
     return obj.assignees.count() 

    def get_count_completed(self, obj): 
     return obj.assignees.exclude(completed__isnull=True).count() 

http://www.django-rest-framework.org/api-guide/fields/#serializermethodfield

+0

Ok, cela a bien fonctionné, mais a soulevé un autre problème. Je reçois les données dans le format spécifié. Maintenant, je veux modifier cet objet javascript sur le client dit, par exemple. changez l'étiquette et renvoyez le même objet javascript pour mettre à jour la base de données et j'obtiens une erreur: 'django.core.exceptions.FieldDoesNotExist: La tâche n'a pas de champ nommé 'count_assigned'' – Codewise

+0

Les champs (" count_assigned "," count_completed ") sont des champs en lecture seule. Cela signifie qu'ils sont destinés à être utilisés uniquement pour générer une représentation de données de sérialiseur. Ils ne peuvent pas être utilisés pour modifier l'enregistrement de la base de données. Il serait judicieux de supprimer ces champs des paramètres de l'API dans javascript avant d'essayer de mettre à jour. – Avatar

0

Si je comprends votre logique correctement, vous pouvez essayer

dans serializers

class TaskSerializer(serializers.ModelSerializer): 
    count_assigned = serializers.IntegerField(read_only=True) 
    count_completed = serializers.IntegerField(read_only=True) 

puis par queryset :

from django.db.models import Count, Case, When, IntegerField 

qs = Task.objects.annotate(
     count_completed=Count(Case(
      When(assignees__completed__isnull=False, then=1), 
      output_field=IntegerField(), 
     )) 
    ).annotate(count_assigned=Count('assignees')) 

serializer = TaskSerializer(qs, many=True) 

Ou terriblement inefficace dans les modèles:

from django.utils.functional import cached_property 

class Task(models.Model): 

@cached_property 
def all_assignee(self): 
    return self.assignees.all() 

def count_assigned(self): 
    return self.all_assignee.count() 

def count_completed(self): 
    return self.all_assignee.filter(completed__isnull=False).count() 
+2

Cela pourrait fonctionner mais c'est terriblement inefficace. –

+0

@DanielRoseman je vous remercie pour le commentaire –