django restframework PrimaryKeyRelatedField筛选的困惑
一.在开发某运动app时,遇见以下情况
1.部分表内容如下:
class Sports(models.Model): ''' 运动表 ''' school = models.ForeignKey(Schools, verbose_name='学校', on_delete=models.CASCADE) sport_name = models.CharField(max_length=30, verbose_name='运动项目') image = models.ImageField(upload_to='sports/%Y/%m', verbose_name='运动封面图') add_time = models.DateTimeField(default=datetime.now, verbose_name='添加时间') class Meta: verbose_name = '运动项目' verbose_name_plural = verbose_name unique_together = ('school', 'sport_name') def __str__(self): return self.sport_name class Schedule(models.Model): ''' 发起约运动 ''' Status = ((1, '已完成'), (2, '待人加入'), (3, '已取消')) user = models.ForeignKey(UserProfile, on_delete=models.CASCADE, verbose_name='发起人') join_type = models.BooleanField(verbose_name='是否发起人') now_people = models.IntegerField(verbose_name='已有人数', null=True, blank=True) school = models.ForeignKey(Schools, on_delete=models.CASCADE, verbose_name='学校', default='') sport = models.ForeignKey(Sports, on_delete=models.CASCADE, verbose_name='运动项目') address = models.CharField(verbose_name='约定地点', max_length=100) sport_time = models.DateTimeField(verbose_name='约定运动开始时间') sport_end_time = models.DateTimeField(verbose_name='约定运动结束时间', default='') people_nums = models.IntegerField(verbose_name='人数') add_time = models.DateTimeField(default=datetime.now, verbose_name='添加时间') status = models.IntegerField(choices=Status, verbose_name='状态') class Meta: verbose_name = '发起约运动' verbose_name_plural = verbose_name def __str__(self): return self.sport.sport_name + '-' + str(self.sport_time) class UserProfile(AbstractUser): ''' 用户表 ''' name = models.CharField(max_length=30, null=True, blank=True, verbose_name='姓名') image = models.ImageField(upload_to='users/', default='', null=True, blank=True, verbose_name='头像') birthday = models.DateField(null=True, blank=True, verbose_name='出生年月') mobile = models.CharField(max_length=11, verbose_name='电话', null=True, blank=True) student_id = models.CharField(max_length=32, verbose_name='学号', default='', null=True, blank=True) gender = models.CharField(max_length=6, choices=(('male', '男'), ('fmale', '女')), default='男', verbose_name='性别') email = models.EmailField(verbose_name='邮箱') school = models.ForeignKey(Schools, verbose_name='学校', on_delete=models.CASCADE) institude = models.ForeignKey(Schools, verbose_name='学院', on_delete=models.CASCADE, related_name='institude') profession = models.ForeignKey(Schools, verbose_name='专业', on_delete=models.CASCADE, related_name='profession') add_time = models.DateTimeField(default=datetime.now, verbose_name='添加时间') class Meta(): verbose_name = '用户' verbose_name_plural = verbose_name def __str__(self): if self.name: return self.name else: return self.username class Schedule(models.Model): ''' 发起约运动 ''' Status = ((1, '已完成'), (2, '待人加入'), (3, '已取消')) user = models.ForeignKey(UserProfile, on_delete=models.CASCADE, verbose_name='发起人') join_type = models.BooleanField(verbose_name='是否发起人') now_people = models.IntegerField(verbose_name='已有人数', null=True, blank=True) school = models.ForeignKey(Schools, on_delete=models.CASCADE, verbose_name='学校', default='') sport = models.ForeignKey(Sports, on_delete=models.CASCADE, verbose_name='运动项目') address = models.CharField(verbose_name='约定地点', max_length=100) sport_time = models.DateTimeField(verbose_name='约定运动开始时间') sport_end_time = models.DateTimeField(verbose_name='约定运动结束时间', default='') people_nums = models.IntegerField(verbose_name='人数') add_time = models.DateTimeField(default=datetime.now, verbose_name='添加时间') status = models.IntegerField(choices=Status, verbose_name='状态') class Meta: verbose_name = '发起约运动' verbose_name_plural = verbose_name def __str__(self): return self.sport.sport_name + '-' + str(self.sport_time)
2.序列化想实现的功能:
在序列化Schedule表时,sport是一个外键字段,想筛选sport的外键school是当前已登录的用户的school字段下的该学校所有运动项目(即筛选为queryset=Sports.objects.filter(school=user.school)),看似很简单,但该如何在字段中获取当前用户呐。
首先想到,把该字段设计为SerializerMethodField字段,即如下(该方法确实能实现筛选,但这不是我要的结果,我想在序列化时有该字段,并且能选择筛选出的数据中的一个并添加新的数据):
#失败方法
sport = serializers.SerializerMethodField() def get_sport(self, obj): all_sport = Sports.objects.filter(school=self.context['request'].user.school).values_list('pk',flat=True) json_all = SportsSerializer(all_sport, many=True, context={'request': self.context['request']}).data return all_sport
然后就有点麻烦,想从写view中的get_queryset(),也是只能在list或retrieve中才能筛选,而序列化添加仍是一个问题;又想到从写一个类或者函数专门筛选,但是又增大了获取当前用户的难度,有点恼火,咋办,看看源码它写了哪些方法,真看不太懂,只能了解一点。
看到了RelatedField中的get_queryset()方法,好像重写它有点作用:
def get_queryset(self): queryset = self.queryset if isinstance(queryset, (QuerySet, Manager)): # Ensure queryset is re-evaluated whenever used. # Note that actually a `Manager` class may also be used as the # queryset argument. This occurs on ModelSerializer fields, # as it allows us to generate a more expressive 'repr' output # for the field. # Eg: 'MyRelationship(queryset=ExampleModel.objects.all())' queryset = queryset.all() return queryset
三.目前最终方案:
于是勉强写吧,重写该方法确实有用,只能说功能完成了,但还真得改进:
class SportPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField): def get_queryset(self): queryset = self.queryset # if isinstance(queryset, (QuerySet, Manager)): # Ensure queryset is re-evaluated whenever used. # Note that actually a `Manager` class may also be used as the # queryset argument. This occurs on ModelSerializer fields, # as it allows us to generate a more expressive 'repr' output # for the field. # Eg: 'MyRelationship(queryset=ExampleModel.objects.all())' queryset = queryset.filter(school=self.context['request'].user.school) return queryset sport = SportPrimaryKeyRelatedField(queryset=Sports.objects.all(), label="运动")