¿Cómo me deshago de las consultas duplicadas como en la captura de pantalla?
Tengo dos modelos de la siguiente manera,
class Genre(MPTTModel): name = models.CharField(max_length=50, unique=True) parent = TreeForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children') def __str__(self): return self.name class Game(models.Model): name = models.CharField(max_length=50) genre = models.ManyToManyField(Genre, blank=True, related_name='games') def __str__(self): return self.namey tener un serializador y vistas,
class GameSerializer(serializers.ModelSerializer): class Meta: model = Game exclude = ['genre', ] class GenreGameSerializer(serializers.ModelSerializer): children = RecursiveField(many=True) games = GameSerializer(many=True,) class Meta: model = Genre fields = ['id', 'name', 'children', 'games'] class GamesByGenreAPI(APIView): queryset = Genre.objects.root_nodes() serializer_class = GenreGameSerializer def get(self, request, *args, **kwargs): ser = GenreGameSerializer(data=Genre.objects.root_nodes() .prefetch_related('children__children', 'games'), many=True) if ser.is_valid(): pass return Response(ser.data) así que básicamente el modelo poblado cuando se serializa se ve así 
El resultado es el que esperaba, pero no hay consultas duplicadas para cada género. ¿Cómo puedo arreglarlo? Gracias..
aquí hay un pegado https://pastebin.com/xfRdBaF4 con todo el código, si desea reproducir el problema.
También agreguepath('games/', GamesByGenreAPI.as_view()),en urls.py que se omite al pegar.
intenté registrar consultas para verificar si su problema con la barra de herramientas de depuración, pero NO, las consultas están duplicadas ... aquí está la captura de pantalla. 
Aquí está mi enfoque sobre cómo superar las múltiples consultas que se realizan.
from collections import defaultdict from rest_framework.serializers import SerializerMethodField class GameSerializer(serializers.ModelSerializer): class Meta: model = Game exclude = ['genre', ] class GenreGameSerializer(serializers.ModelSerializer): children = SerializerMethodField(source='get_children') games = GameSerializer(many=True) class Meta: model = Genre fields = ['id', 'name', 'games'] def get_children(self, obj): # get genre childrens from context and pass it to same serializer # no extra queries are done, since we alredy have the instances children = self.context['children'].get(obj.id, []) serializer = GenreGameSerializer(children, many=True, context=self.context) return serializer.data class GamesByGenreAPI(APIView): queryset = Genre.objects.root_nodes() serializer_class = GenreGameSerializer def get(self, request, *args, **kwargs): # gather genres from queryset class attribute and prefetch games genres = self.get_queryset().prefetch_related('games') # gather all descendants of root nodes and prefetch games genre_descendants = genres.get_descendants().prefetch_related('games') # create a dictionary with key parent and value list of children # this will not require extra queries children_dict = defaultdict(list) for descendant in descendants: children_dict[descendant.parent_id].append(descendant) # add the dictionary as context for serializer context = self.get_serializer_context() context['children'] = children_dict # send the context to serializer ser = GenreGameSerializer(data=genres, context=context, many=True) return Response(ser.data) La clase GamesByGenreAPI se puede escribir mejor reemplazando self.get_queryset() y self.get_serializer_context() pero traté de mantenerlo en un método para una mejor comprensión.
El prefetch_related solo funciona en el árbol de nivel raíz, porque solo se especifica en esa consulta. Los elementos secundarios posteriores obtenidos por una nueva consulta generada con RecursiveField no tienen estos objetos relacionados precargados . Probablemente pueda sobrescribir las consultas en RecursiveField , pero creo que ejecuta una nueva consulta separada en cada atributo de childrens encontrados.
Bueno, si desea reducir las consultas a solo 3, necesitará obtener todos los datos de los modelos y construir manualmente la matriz de salida de forma recursiva.
Este es un código muy sucio, pierdes la "magia de Django" y, como en el comentario, creo que es una pérdida de tiempo en la mayoría de los escenarios.
class GamesByGenreAPI(APIView): def get(self, request, *args, **kwargs): games = {} for g in Game.objects.all(): games[g.pk] = { 'id': g.id, 'name': g.name } TreeGame = Game.genre.through tree_game = {} for tg in TreeGame.objects.all(): if tg.genre_id not in tree_game: tree_game[tg.genre_id] = [] tree_game[tg.genre_id].append(tg.game_id) childrens = {} roots = [] for g in Genre.objects.all(): if g.level == 0: roots.append(g) else: if g.parent_id not in childrens: childrens[g.parent_id] = [] childrens[g.parent_id].append(g) def _get_data_from_tree_branch(game): branch_data = { 'id': game.pk, 'name': game.name } if game.pk in childrens: # Move up if you need a children array in every response branch_data['children'] = [] for c in childrens[game.pk]: branch_data['children'].append( _get_data_from_tree_branch(c) ) if game.pk in tree_game: # Move up if you need a games array in every response branch_data['games'] = [] for rel in tree_game[game.pk]: branch_data['games'].append(games[rel]) return branch_data data = [] for g in roots: data.append(_get_data_from_tree_branch(g)) return Response(data)A partir de la salida de la barra de herramientas de depuración, asumiré que tiene dos niveles de anidamiento en su modelo de Género (raíz, Nivel 1). No sé si el Nivel 1 tiene hijos, es decir, hay géneros de Nivel 2, ya que no puedo ver los resultados de la consulta (pero esto no es relevante para el problema actual).
Los Géneros de nivel raíz son (1, 4, 7), el Nivel 1 son (2, 3, 5, 6, 8, 9). La captación previa funcionó para estas búsquedas prefetch_related("children__children") ya que las consultas se agrupan en dos consultas separadas, como debería ser.
La siguiente consulta para juegos relacionados con géneros de nivel raíz ( prefetch_related("games") ) también se obtiene previamente. Es la cuarta consulta en la salida de la barra de herramientas de depuración.
Las siguientes consultas, como puede ver, obtienen los juegos para cada uno de los géneros del Nivel 1 en una consulta separada, que supongo que se activan desde los campos del serializador, ya que no hay búsquedas especificadas en la vista, que podrían precargar esos registros. Agregar otra búsqueda de captación previa dirigida a esos registros debería resolver el problema.
ser = GenreGameSerializer(data=Genre.objects.root_nodes() .prefetch_related( 'children__children', 'games' # prefetching games for Level 1 genres 'children__games'), many=True)Tenga en cuenta que si hay más géneros anidados, se debe aplicar la misma lógica para cada nivel de anidamiento. Por ejemplo, si hay géneros de Nivel 2, entonces debería buscar los juegos relacionados para esos géneros con:
ser = GenreGameSerializer(data=Genre.objects.root_nodes() .prefetch_related( 'children__children', 'games' 'children__games', 'children__children__games'), many=True)