【问题标题】:Django Rest Framework Many to Many field related to itselfDjango Rest Framework 与自身相关的多对多字段
【发布时间】:2023-03-14 08:45:01
【问题描述】:

我有一个 AngularJS 项目,它通过 Django Rest Framework (DRF) 使用 Django 作为框架。

我创建了一个 Group 模型并为其设置了一个序列化程序类,但是我现在想在该模型上建立一个名为 related_groups 的新字段,它将引用相同的模型(Group)作为主要数组键。

不过,我不知道是否可以在序列化程序中进行自引用,我也不知道如何从前端传递相关组,这些组可以由使用的用户挑选和选择拥有该组。我希望该字段引用其他组行的主键,并遍历该组集合以建立相关的组关系。

class GroupSerializer(serializers.ModelSerializer):

class Meta:
    model = mod.Group
    fields = (
        'group_id',
        'group_name',
        'category',
        'related_groups',
    )

并且表示似乎正是我想要的:

GroupSerializer():
    group_id = IntegerField(read_only=True)
    group_name = CharField(max_length=100)
    category = CharField(max_length=256, required=False
    related_groups = PrimaryKeyRelatedField(many=True, queryset=Group.objects.all(), required=False)

模型表示如下:

class Group(models.Model):
    """
    Model definition of a Group. Groups are a collection of users (i.e.
    Contacts) that share access to a collection of objects (e.g. Shipments).
    """
    group_id = models.AutoField(primary_key=True)
    group_name = models.CharField(max_length=100)
    owner_id = models.ForeignKey('Owner', related_name='groups')
    category = models.CharField(max_length=256)
    related_groups = models.ManyToManyField('self', blank=True, null=True)
    history = HistoricalRecords()

    def __unicode__(self):
        return u'%s' % (self.group_name)

    def __str__(self):
        return '%s' % (self.group_name)

访问此模型的视图是非常简单的 CRUD 视图:

@api_view(['GET', 'PUT', 'DELETE'])
@authentication_classes((SessionAuthentication, BasicAuthentication))
@permission_classes((IsAuthenticated, HasGroupAccess))
def group_detail(request, pk, format=None):
    group, error = utils.get_by_pk(pk, mod.Group, request.user)
    if error is not None:
        return error
    if request.method == 'GET':
        serializer = ser.GroupSerializer(group)
        return Response(serializer.data)
    elif request.method == 'PUT':
        return utils.update_object_by_pk(request, pk, mod.Group,
                                         ser.GroupSerializer)
    elif request.method == 'DELETE':
        return utils.delete_object_by_pk(request.user, pk, mod.Group)

这调用了一些清理和验证方法:

def update_object_by_pk(request, pk, obj_type, serializer):
    try:
        with transaction.atomic():
            obj, error = select_for_update_by_pk(pk, obj_type, request.user)
            if error is not None:
                return error
            obj_serializer = serializer(obj, data=request.data)

            if obj_serializer.is_valid():
                obj_serializer.save()
            else:
                response = ("Attempt to serialize {} with id {} failed "
                            "with errors {}").format(str(obj_type), str(pk),
                                                     str(serializer.errors))
                return Response(response, status=status.HTTP_400_BAD_REQUEST)
    except Exception as e:
        response = ("Error attempting to update {} with ID={}. user={}, "
                    "error={}".format(str(obj_type), str(pk),
                                      str(request.user.email), str(e)))
        return Response(response, status=status.HTTP_400_BAD_REQUEST)
    else:
        resp_str = ("Successfully updated {} with ID={}".format(str(obj_type),
                                                                str(pk)))
        return Response(resp_str, status=status.HTTP_200_OK)

调用:

def select_for_update_by_pk(pk, mod_type, user):
    response = None
    obj = None
    try:
        obj = mod_type.objects.select_for_update().get(pk=pk)
    except mod_type.DoesNotExist:
        resp_str = ("{} could not be found with ID={}.".
                    format(str(mod_type), str(pk)))
        response = Response(resp_str, status=status.HTTP_404_NOT_FOUND)
    return obj, response

这只是 select_for_update() Django 方法的包装。

迁移创建了一个名为 group_related_groups 的新表,其中包含一个 ID、一个 from_group 和一个 to_group 列,Django 将其用作连接/查找来建立这些关系。

我可以单独写入该端点,但序列化程序 GroupSerializer 似乎不希望默认允许多个值通过。

因此,使用 PUT 请求 PUT 值“2”以组合 PK 为 1 是成功的。但是,尝试将['2','3']、[2,3]、2,3 和'2','3'

通过视图将其追溯到实用程序方法,我看到它是 serializer.is_valid() 请求失败,所以这让我认为这是一个 many=True 问题,但我不知道要使用哪个关系序列化程序特定的自引用 ManyToManyField 问题。

调试时,我将 serializer.is_valid() 错误输出到 syslog,如下所示:

        response = ("Attempt to serialize {} with id {} failed "
                    "with errors {}").format(str(obj_type), str(pk),
                                             str(serializer.errors))
        logger.exception(response)

我收到此异常消息作为响应:

Message: "Attempt to serialize <class 'bioapi.models.Group'> with id 1 failed with errors"

obj_serializer.error 的调试错误输出是

obj_serializer.error of {'related_groups': ['Incorrect type. Expected pk value, received str.']}

这是关于 request.data 的调试消息:

{'group_name': 'Default Guest Group', 'related_groups': [1], 'group_id': 2, 'category': 'guest'}

成功了,

<QueryDict: {'group_name': ['requestomatic'], 'related_groups':['2,2'], category': ['guest']}>

失败了。现在看这个,我想知道 Postman 表单数据格式是否是问题所在。如果是这样,我会觉得自己很愚蠢。

我是否能够使用 DRF 表示从模型到自身的多对多关系,或者我是否需要为关系表使用自定义序列化程序? DRF 的文档没有使用自引用模型,我在网上找到的所有示例都使用多个模型或多个序列化器。

是否可以在我的模型中使用 ManyToManyField,它是使用 Django Rest Framework (DRF) 及其序列化程序进行自引用的?如果有,怎么做?

【问题讨论】:

  • 请在第二次 sn-p 中解释 GroupSerializer(): 的意思
  • 一旦serializer.is_valid()失败,serializer.errors的内容是什么?它通常会很好地提示失败的原因。
  • @e4c5,使用序列化器关系文档中描述的方法检查序列化器关系,此处:django-rest-framework.org/api-guide/relations/…,关系似乎是有序的。
  • @Michael,我已经更新了我的问题以包括调试错误响应
  • @Smittles serializer 似乎是类,而不是序列化程序实例...您正在使用的视图代码是什么?

标签: angularjs django serialization django-rest-framework manytomanyfield


【解决方案1】:

看起来像嵌套序列化的情况。模型应该是 -

class Group(models.Model):
  group_id = models.IntegerField(read_only=True)
  group_name = models.CharField(max_length=100)
  category = models.CharField(max_length=256, required=False)
  related_groups = models.ForeignKey('self', required=False, related_name='children')

还有序列化器 -

class GroupSerializer(serializers.ModelSerializer):
    class Meta:
        model = Group
        fields = (
            'group_id', 'group_name', 'category', 'related_groups',
        )

class NestedGroupSerializer(serializers.ModelSerializer):
    children = GroupSerializer(many=True, read_only=True)

    class Meta:
        model = Group
        fields = ('group_id', 'group_name', 'category', 'related_groups', 'children')

然后您可以使用 NestedGroupSerializer 访问所有相关组。

默认情况下,嵌套序列化程序是只读的。如果您想支持对嵌套序列化器字段的写操作,您需要创建 create() 和/或 update() 方法,以明确指定应如何保存子关系。

希望对您有所帮助。

【讨论】:

  • 这种方法在有效性上失败了,我的也是。当传递 [2,3] 或 2,3 时,serializer.is_valid() 返回为 false。但是,单独传递 2 或 3 这样的单个整数时的体验是相同的
  • 我这里已经有一个相关组的字段 - 以及一个为 ManyToManyField 创建的表 - 那么为什么我需要“孩子”?如果我有一个与同一模型相关的 ManyToManyField,为什么还需要一个 related_name?
  • 另外,我需要有多个相关组,因此使用 ManyToManyField。使用 ManyToManyField 在数据库中创建关系表 - 非常适合快速关系匹配,其中一个 NightClub 组可能有多个 DJ 组,其中 NightClub 有一组成员,DJ 1 有另一组,DJ 2 有另一组。
【解决方案2】:

尝试使用ModelViewSet 作为视图:

class GroupViewSet(viewsets.ModelViewSet):
    queryset = models.Group.objects.all()
    serializer_class = serializers.GroupSerializer
    authentication_classes = (SessionAuthentication, BasicAuthentication)
    permission_classes = (IsAuthenticated, HasGroupAccess)

在您的urls.conf 中,类似: 从导入视图 从 rest_framework 导入路由器

router = routers.SimpleRouter()
router.register(r'group', views.GroupViewset)
urlpatterns = router.urls

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-02-09
    • 2019-11-20
    • 1970-01-01
    • 2017-07-08
    • 1970-01-01
    • 2014-11-14
    • 2021-07-09
    • 1970-01-01
    相关资源
    最近更新 更多