如何在Django REST Framework的API中展示TabularInline?
影院排期管理系统API开发及TabularInline展示方案
嘿,我来帮你搞定影院排期系统的API开发,还有你问的DRF里怎么实现类似TabularInline的展示——毕竟Django Admin的Inline在后台用着爽,但API里得换个思路来搞~
一、先梳理&补全你的模型关系
从你给的代码来看,核心模型的逻辑已经有了:
Movie是影片主表,存标题、简介、年份这些基础信息Exhibition关联单部影片,记录它的上映起止日期Schedule应该是具体的场次信息(你代码没写完,我补个常见的结构方便后续示例):
class Schedule(models.Model): CINE_DERBY = 'CD' CINE_CASAFO = 'CC' CINEMA_CHOICES = [ (CINE_DERBY, 'Cine Derby'), (CINE_CASAFO, 'Cine Casafo'), ] exhibition = models.ForeignKey(Exhibition, verbose_name='Exibição', on_delete=models.CASCADE, related_name='schedules') cinema = models.CharField('Cinema', max_length=2, choices=CINEMA_CHOICES) show_time = models.DateTimeField('Horário de sessão') available_seats = models.IntegerField('Assentos disponíveis')
这里给exhibition外键加了related_name='schedules',后续查询和序列化会更顺手。
二、DRF中实现类似TabularInline的嵌套展示
Django Admin的TabularInline是在父模型页面展示关联子模型的表格,放到DRF里,我们用嵌套序列化器就能实现同样的效果——让父资源的API响应里直接包含关联的子资源列表。
1. 编写嵌套序列化器
我们从最底层的场次开始往上写:
第一步:场次(Schedule)序列化器
from rest_framework import serializers from .models import Movie, Exhibition, Schedule class ScheduleSerializer(serializers.ModelSerializer): class Meta: model = Schedule fields = ['id', 'cinema', 'show_time', 'available_seats'] # 把影院的缩写转换成友好名称返回给前端 extra_kwargs = {'cinema': {'source': 'get_cinema_display'}}
第二步:上映排期(Exhibition)序列化器
这里把场次列表嵌套进去,就像Admin里把Schedule作为Exhibition的TabularInline一样:
class ExhibitionSerializer(serializers.ModelSerializer): # many=True表示是多个场次,read_only=True如果不需要通过Exhibition创建场次的话 schedules = ScheduleSerializer(many=True, read_only=True) # 直接返回影片的标题(对应Movie的__str__方法,建议你给Movie加个__str__返回title) movie = serializers.StringRelatedField() class Meta: model = Exhibition fields = ['id', 'movie', 'start', 'finish', 'schedules']
第三步:影片(Movie)序列化器(可选)
如果想在影片的API里直接展示所有上映排期和对应场次,继续嵌套就行:
class MovieSerializer(serializers.ModelSerializer): exhibitions = ExhibitionSerializer(many=True, read_only=True) class Meta: model = Movie fields = ['id', 'title', 'synopsis', 'year', 'exhibitions']
2. 视图层快速搭建API
用DRF的视图集(ViewSet)来快速生成CRUD接口:
from rest_framework import viewsets from .models import Movie, Exhibition, Schedule from .serializers import MovieSerializer, ExhibitionSerializer, ScheduleSerializer class MovieViewSet(viewsets.ReadOnlyModelViewSet): queryset = Movie.objects.all().prefetch_related('exhibition_set') serializer_class = MovieSerializer class ExhibitionViewSet(viewsets.ModelViewSet): # 预加载场次数据,避免N+1查询问题 queryset = Exhibition.objects.all().select_related('movie').prefetch_related('schedules') serializer_class = ExhibitionSerializer class ScheduleViewSet(viewsets.ModelViewSet): queryset = Schedule.objects.all().select_related('exhibition__movie') serializer_class = ScheduleSerializer
这里的select_related和prefetch_related一定要加,不然数据量大的时候API会慢到离谱。
3. 实现可编辑的嵌套序列化器(可选)
如果需要像Admin Inline那样,创建/更新上映排期的时候同时处理场次,重写序列化器的create和update方法就行:
class ExhibitionSerializer(serializers.ModelSerializer): schedules = ScheduleSerializer(many=True) class Meta: model = Exhibition fields = ['id', 'movie', 'start', 'finish', 'schedules'] def create(self, validated_data): # 先提取场次数据,再创建排期 schedules_data = validated_data.pop('schedules') exhibition = Exhibition.objects.create(**validated_data) for schedule_data in schedules_data: Schedule.objects.create(exhibition=exhibition, **schedule_data) return exhibition def update(self, instance, validated_data): schedules_data = validated_data.pop('schedules') # 更新排期本身的字段 instance.movie = validated_data.get('movie', instance.movie) instance.start = validated_data.get('start', instance.start) instance.finish = validated_data.get('finish', instance.finish) instance.save() # 更新现有场次、创建新场次、删除未提交的旧场次 existing_schedules = {s.id: s for s in instance.schedules.all()} for schedule_data in schedules_data: schedule_id = schedule_data.get('id') if schedule_id and schedule_id in existing_schedules: # 更新已有场次 schedule = existing_schedules[schedule_id] schedule.cinema = schedule_data.get('cinema', schedule.cinema) schedule.show_time = schedule_data.get('show_time', schedule.show_time) schedule.available_seats = schedule_data.get('available_seats', schedule.available_seats) schedule.save() else: # 创建新场次 Schedule.objects.create(exhibition=instance, **schedule_data) # 删除不在提交数据里的旧场次(按需开启) for schedule_id in existing_schedules: if schedule_id not in [s.get('id') for s in schedules_data if 'id' in s]: existing_schedules[schedule_id].delete() return instance
三、额外优化小 tips
- 权限控制:如果API需要登录才能访问,加个
permission_classes = [IsAuthenticated]到视图集里就行,也可以自定义权限逻辑。 - 过滤排序:装个
django-filter库,就能实现按影片标题、影院、上映时间过滤的功能,前端体验会好很多。 - 分页:给视图集加个
pagination_class = PageNumberPagination,避免一次性返回太多数据。
内容的提问来源于stack exchange,提问作者LSM
相关产品推荐
相关产品推荐

