优化Django REST Framework演员统计接口:解决数据重复与统计需求
优化Django REST Framework电影演员API方案
一、需求明确
需要实现以下三个核心功能:
- 统计指定演员参演电影的各类型数量(格式如
{'Drama':2, 'Horror':3}) - 解决接口返回数据随参演电影数量重复的问题
- 获取该演员的热门类型(含名称,支持并列)、最常合作搭档的完整信息(ID、姓名、合作次数)
二、基础模型假设(基于常见电影数据集设计)
from django.db import models class Actor(models.Model): id = models.IntegerField(primary_key=True) name = models.CharField(max_length=100) # 其他字段(如出生日期、简介等) class Genre(models.Model): id = models.IntegerField(primary_key=True) name = models.CharField(max_length=50, unique=True) class Movie(models.Model): id = models.IntegerField(primary_key=True) title = models.CharField(max_length=200) # 其他字段(如上映日期、评分等) class MovieActor(models.Model): movie = models.ForeignKey(Movie, on_delete=models.CASCADE, related_name='actors') actor = models.ForeignKey(Actor, on_delete=models.CASCADE, related_name='movies') role = models.CharField(max_length=100, blank=True) class MovieGenre(models.Model): movie = models.ForeignKey(Movie, on_delete=models.CASCADE, related_name='genres') genre = models.ForeignKey(Genre, on_delete=models.CASCADE, related_name='movies')
三、核心优化实现
1. 替换View解决数据重复问题
原ListAPIView会返回每条电影的重复演员数据,改用APIView返回聚合统计结果,彻底避免重复:
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from django.db.models import Count from .models import Actor, MovieActor, MovieGenre from .serializers import ActorStatsSerializer class ActorStatsView(APIView): def get(self, request, actor_id): try: actor = Actor.objects.get(id=actor_id) except Actor.DoesNotExist: return Response({"error": "演员不存在"}, status=status.HTTP_404_NOT_FOUND) # 后续统计逻辑见下文 return Response(serializer.data, status=status.HTTP_200_OK)
2. 统计各类型电影数量
通过Django ORM的annotate和values直接关联类型名称,生成键值对格式的统计结果:
# 在ActorStatsView的get方法中添加 genre_stats = MovieGenre.objects.filter( movie__actors__actor=actor ).values('genre__name').annotate( count=Count('genre__id') ).order_by('-count') # 转换为需求格式的字典 genre_counts = {item['genre__name']: item['count'] for item in genre_stats}
3. 获取支持并列的热门类型
先提取最大类型数量,再筛选所有符合该数量的类型:
# 在ActorStatsView的get方法中添加 top_genres = [] if genre_stats: max_count = genre_stats[0]['count'] top_genres = [item['genre__name'] for item in genre_stats if item['count'] == max_count]
4. 获取最常合作搭档(含完整信息)
通过关联表统计共同参演的演员,排除自身后按合作次数排序,支持并列:
# 在ActorStatsView的get方法中添加 co_actor_stats = MovieActor.objects.filter( movie__actors__actor=actor ).exclude(actor=actor).values( 'actor__id', 'actor__name' ).annotate( 合作次数=Count('movie__id', distinct=True) ).order_by('-合作次数') top_co_actors = [] if co_actor_stats: max_co_count = co_actor_stats[0]['合作次数'] top_co_actors = [ { 'id': item['actor__id'], 'name': item['actor__name'], '合作次数': item['合作次数'] } for item in co_actor_stats if item['合作次数'] == max_co_count ]
5. 专用序列化器设计
创建针对统计结果的序列化器,避免与基础演员序列化器混淆:
from rest_framework import serializers class CoActorSerializer(serializers.Serializer): id = serializers.IntegerField() name = serializers.CharField() 合作次数 = serializers.IntegerField() class ActorStatsSerializer(serializers.Serializer): actor_id = serializers.IntegerField(source='id') actor_name = serializers.CharField(source='name') genre_counts = serializers.DictField(child=serializers.IntegerField()) top_genres = serializers.ListField(child=serializers.CharField()) top_co_actors = serializers.ListField(child=CoActorSerializer())
6. URL配置更新
替换原getActors路由,指向新的统计接口:
from django.urls import path from .views import ActorStatsView urlpatterns = [ path('actors/<int:actor_id>/stats/', ActorStatsView.as_view(), name='actor-stats'), ]
四、完整View实现
整合所有逻辑后的最终View代码:
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from django.db.models import Count from .models import Actor, MovieActor, MovieGenre from .serializers import ActorStatsSerializer class ActorStatsView(APIView): def get(self, request, actor_id): try: actor = Actor.objects.get(id=actor_id) except Actor.DoesNotExist: return Response({"error": "演员不存在"}, status=status.HTTP_404_NOT_FOUND) # 统计各类型数量 genre_stats = MovieGenre.objects.filter( movie__actors__actor=actor ).values('genre__name').annotate( count=Count('genre__id') ).order_by('-count') genre_counts = {item['genre__name']: item['count'] for item in genre_stats} # 获取热门类型(支持并列) top_genres = [] if genre_stats: max_count = genre_stats[0]['count'] top_genres = [item['genre__name'] for item in genre_stats if item['count'] == max_count] # 获取最常合作搭档 co_actor_stats = MovieActor.objects.filter( movie__actors__actor=actor ).exclude(actor=actor).values( 'actor__id', 'actor__name' ).annotate( 合作次数=Count('movie__id', distinct=True) ).order_by('-合作次数') top_co_actors = [] if co_actor_stats: max_co_count = co_actor_stats[0]['合作次数'] top_co_actors = [ { 'id': item['actor__id'], 'name': item['actor__name'], '合作次数': item['合作次数'] } for item in co_actor_stats if item['合作次数'] == max_co_count ] # 序列化返回 serializer_data = { 'actor_id': actor.id, 'actor_name': actor.name, 'genre_counts': genre_counts, 'top_genres': top_genres, 'top_co_actors': top_co_actors } serializer = ActorStatsSerializer(data=serializer_data) serializer.is_valid(raise_exception=True) return Response(serializer.data, status=status.HTTP_200_OK)
五、关键优化说明
- 数据重复解决:放弃
ListAPIView的逐条电影返回,改用聚合统计结果,彻底消除重复 - 类型名称关联:通过
values('genre__name')直接关联类型名称,避免仅返回ID的问题 - 并列场景处理:先获取最大统计值,再筛选所有符合条件的条目,支持多个热门类型/搭档
- 性能优化:使用
distinct=True避免重复统计同一部电影的合作关系,降低数据库查询压力
内容的提问来源于stack exchange,提问作者Vagner
相关产品推荐
相关产品推荐

