restfulframework引用多对多外键
记录一下工作中遇到的问题
最近在写restfulframework,感觉还是很便利的
首先贴一下文档地址
https://www.django-rest-framework.org/api-guide/filtering/
https://www.django-rest-framework.org/api-guide/serializers/
https://www.django-rest-framework.org/api-guide/relations/#manytomanyfields-with-a-through-model
使用GernricViewSet可以便捷的新增接口,在类中定义queryset指定模型,用serializer_class指定序列化类,用pagination_class指定分页类,再用filter_backends和filter_class做筛选,可以解决大部分curd问题,如下
class GitLabCommit(mixins.ListModelMixin, mixins.RetrieveModelMixin,viewsets.GenericViewSet): #加RetriveModelMixin可以查询特定模型的信息 queryset = GitCommit.objects.all() serializer_class = CommitSerializer pagination_class = MyPageNumberPagination filter_backends = (DjangoFilterBackend,) filter_class = UserCommitFilter
其中模型类无需多言,序列化类形如下
class CommitSerializer(serializers.ModelSerializer): author_email = serializers.CharField(source='author.email') branch_name = serializers.CharField(source = "branch.branchName") branch_project_name = serializers.CharField(source="branch.project.projectName") class Meta: model = GitCommit fields = "__all__"
fields这里也可以写成元组的形式
fields = ("StaffId","name","staff_name")
分页类类似这样
class MyCursorPagination(pagination.CursorPagination): """ Cursor 光标分页 性能高,安全 """ page_size = 9 ordering = '-update_time' page_size_query_param = "pages" max_page_size = 20 cursor_query_description = '页面' page_size_query_description = '每页条数' class MyPageNumberPagination(pagination.PageNumberPagination): """ 普通分页,数据量越大性能越差 """ page_size = 11 page_size_query_param = 'size' page_query_param = 'page' max_page_size = 20
这是过滤器类,可以直接定义查找字段或者通过方法进行复杂查找
class UserCommitFilter(filters.FilterSet): user_id = filters.NumberFilter(field_name='author__StaffId', lookup_expr='exact') start_date = filters.DateFilter(field_name='commitDate', lookup_expr='gte') end_date = filters.DateFilter(field_name='commitDate', lookup_expr='lt') commit_sum = filters.NumberFilter(method="get_sum") def get_sum(self,queryset,name,values): if values == 1: return queryset.annotate(total_addLines = Sum("addLines"),total_delLins = Sum("delLines"),total_totalLins = Sum("totalLines"))
这里有一个问题:如果序列化所涉及的模型是关联模型怎么办呢?
可以参考这个
https://zhuanlan.zhihu.com/p/27667372
这里特别说一下,对于多对多模型,可以通过嵌套来进行关联,如下
模型定义
class Staff(BaseTable): StaffId = models.IntegerField(primary_key=True, help_text="工号") email = models.CharField(max_length=50,default="",null=True,help_text="邮箱") name = models.CharField(max_length=50,default="",null=True,help_text="姓名") department = models.ForeignKey(Department,on_delete=models.CASCADE) def __str__(self): return "%s:%s"%(self.StaffId,self.name) class Meta: db_table = "gitlab_measure_staff" class GitGroup(BaseTable): id = models.AutoField(primary_key=True, help_text="ID") name = models.CharField(max_length=100,default="",null=True,help_text="组名称") members = models.ManyToManyField(Staff) class Meta: db_table = "gitlab_measure_gitgroup"
序列化
class StaffSerializer(serializers.ModelSerializer): staff_name = serializers.CharField(source="name") class Meta: model = Staff fields = ("StaffId","name","staff_name") class GitGroupSerializer(serializers.ModelSerializer): members = StaffSerializer(many=True,read_only=True) class Meta: model = GitGroup fields = ("id","name","members")
如果此时又有一个project类中的group关联到gitgroup,希望在展示的时候展示出组中所有成员该怎么办呢?
这里可以使用depth指定查询的深度
class ProjectSerializer(serializers.ModelSerializer): gitGroup_name = serializers.CharField(source='gitGroup.name') gitGroup_id = serializers.CharField(source="gitGroup.id") department_name = serializers.CharField(source="department.name") class Meta: model = GitProject fields = "__all__" depth = 2
这样在结果中就能看到展示的组和成员了,因为在serilizers.ModelSerializer中的get_field()方法中会根据调用self.build_field,将depth传入,build_field方法会调用self.buid_nested_field方法来,再返回一个ModelSerializer类,再在外层函数中循环调用来获取层层对象,最多不超过10层
for field_name in field_names: # If the field is explicitly declared on the class then use that. if field_name in declared_fields: fields[field_name] = declared_fields[field_name] continue extra_field_kwargs = extra_kwargs.get(field_name, {}) source = extra_field_kwargs.get('source', '*') if source == '*': source = field_name # Determine the serializer field class and keyword arguments. field_class, field_kwargs = self.build_field( source, info, model, depth ) # Include any kwargs defined in `Meta.extra_kwargs` field_kwargs = self.include_extra_kwargs( field_kwargs, extra_field_kwargs ) # Create the serializer field. fields[field_name] = field_class(**field_kwargs)
def build_field(self, field_name, info, model_class, nested_depth): """ Return a two tuple of (cls, kwargs) to build a serializer field with. """ if field_name in info.fields_and_pk: model_field = info.fields_and_pk[field_name] return self.build_standard_field(field_name, model_field) elif field_name in info.relations: relation_info = info.relations[field_name] if not nested_depth: return self.build_relational_field(field_name, relation_info) else: return self.build_nested_field(field_name, relation_info, nested_depth) elif hasattr(model_class, field_name): return self.build_property_field(field_name, model_class) elif field_name == self.url_field_name: return self.build_url_field(field_name, model_class) return self.build_unknown_field(field_name, model_class)
def build_nested_field(self, field_name, relation_info, nested_depth): """ Create nested fields for forward and reverse relationships. """ class NestedSerializer(ModelSerializer): class Meta: model = relation_info.related_model depth = nested_depth - 1 fields = '__all__' field_class = NestedSerializer field_kwargs = get_nested_relation_kwargs(relation_info) return field_class, field_kwargs