Django REST Framework 返回关系数据的建议

Suggestions for returning relational data in Django REST Framework

我是 Django/Django REST FW 的新手(也是这个社区的新手)。我花了很多时间阅读文档,但此时我正在转动轮子。在这里啰嗦了先说声抱歉

我的后端数据库是 Postgres。我有 3 个模型,UserItemShoppingList。我需要 Item 来包含描述(item_name 字段)及其位置。用户将 select 一个项目并将其添加到当天的 ShoppingList。这个想法是,一旦获得该项目,用户将“勾选”该项目,并将其从 ShoppingList.

的视图中“移除”

这是我遇到问题的地方:我不想在 shopping_list table 中复制 item_nameitem_location 字段,但我需要在购物清单的视图中显示这些字段 (shopping_lists.py)。

ItemShoppingList分别是一对多的关系。 Item 对象被视为“主项目 table”,它存储每个项目的描述和位置。 ShoppingList 对象保存这些“主项”的临时列表。 我需要一个查询集,其中包含 ShoppingList 中的所有字段和 Item 中的 2 个或更多字段。

我认为这就是 Django REST FW 认为的反向关系。我已尝试对我的序列化程序和模型进行各种更改(包括将项目序列化程序添加到 ShoppingList 序列化程序)并遇到各种错误。

models/item.py:

from django.db import models
from django.contrib.auth import get_user_model

class Item(models.Model):
    item_name = models.CharField(max_length=50, db_index=True)
    item_location = models.CharField(max_length=10, blank=True, db_index=True)
    item_class = models.CharField(max_length=20, blank=True)
    # This is a relationship with User model
    shopper_id = models.ForeignKey(
        get_user_model(),
        on_delete=models.CASCADE
    )

    def __str__(self):
        return f"item_name: {self.item_name}, item_location: {self.item_location}, shopper_id: {self.shopper_id}"

models/shopping_list.py:

from django.db import models
from django.contrib.auth import get_user_model
from .item import Item

class ShoppingList(models.Model):
    item_num = models.ForeignKey(
        'Item',
        on_delete=models.DO_NOTHING # we don't want to delete the item from the "master" item list, just from this shopping list
    )
    # This is a relationship with user model. 
    shopper_id = models.ForeignKey(
        get_user_model(),
        on_delete=models.CASCADE # ...but we do want to delete the item if the user goes away as items are user-specific
    )
    item_qty = models.PositiveIntegerField()
    item_complete = models.BooleanField(default=False)
    added_on = models.DateField(auto_now=True)
    # setting list_num to blank=True for this version
    list_num = models.PositiveIntegerField(blank=True)

    def __str__(self):
        return f"item_num: {self.item_num}, shopper_id: {self.shopper_id}, item_qty: {self.item_qty}, item_complete: {self.item_complete}"    

serializers/item.py:

from rest_framework import serializers
from ..models.item import Item

class ItemSerializer(serializers.ModelSerializer):
    class Meta:
        model = Item
        fields = ('id', 'item_name', 'item_location', 'item_class', 'shopper_id')

serializers/shopping_list.py:

from rest_framework import serializers
from ..models.shopping_list import ShoppingList

class ShoppingListSerializer(serializers.ModelSerializer):
    class Meta:
        model = ShoppingList
        fields = ('id', 'item_num', 'shopper_id', 'item_qty', 'item_complete', 'added_on', 'list_num')

获取错误 AttributeError: Manager isn't accessible via ShoppingList instances 当我在下面 views/shopping_lists.py 中的 class ShoppingListItemView 中执行 GET 方法时:

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from django.shortcuts import get_object_or_404
from rest_framework.exceptions import PermissionDenied
from ..models.shopping_list import ShoppingList
from ..serializers.shopping_list import ShoppingListSerializer
from ..models.item import Item
from ..serializers.item import ItemSerializer

class ShoppingListsView(APIView):
    def get(self, request, list_num):
        shopping_items = ShoppingList.objects.filter(shopper_id=request.user.id)
        shopping_list_items = shopping_items.filter(list_num=list_num)
        data = ShoppingListSerializer(shopping_list_items, many=True).data
        return Response(data)

    def post(self, request):
        request.data['shopper_id'] = request.user.id
        list_item = ShoppingListSerializer(data=request.data, partial=True)
        if list_item.is_valid():
            list_item.save()
            return Response(list_item.data, status=status.HTTP_201_CREATED)
        else:
            return Response(list_item.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, list_num):
        shopping_items = ShoppingList.objects.filter(shopper_id=request.user.id)
        shopping_list_items = shopping_items.filter(list_num=list_num)
        response_data = shopping_list_items.delete()
        return Response(response_data, status=status.HTTP_204_NO_CONTENT)

class ShoppingListsAllView(APIView):
    def get(self, request):
        shopping_items = ShoppingList.objects.filter(shopper_id=request.user.id)
        data = ShoppingListSerializer(shopping_items, many=True).data
        return Response(data)

class ShoppingListItemView(APIView):
    def get(self, request, pk):
        list_item = get_object_or_404(ShoppingList, pk=pk)
        if request.user != list_item.shopper_id:
            raise PermissionDenied('Unauthorized, this item belongs to another shopper')
        else:
            list_entry = list_item.objects.select_related('Item').get(id=pk)
            print(list_entry)
            data = ShoppingListSerializer(list_item).data
            return Response(data)

    def delete(self, request, pk):
        list_item = get_object_or_404(ShoppingList, pk=pk)
        if request.user != list_item.shopper_id:
            raise PermissionDenied('Unauthorized, this item belongs to another shopper')
        else:
            list_item.delete()
            return Response(status=status.HTTP_204_NO_CONTENT)

    def patch(self, request, pk):
        list_item = get_object_or_404(ShoppingList, pk=pk)
        if request.user != list_item.shopper_id:
            raise PermissionDenied('Unauthorized, this item belongs to another shopper')
        else:
            request.data['shopper_id'] = request.user.id
            updated_list_item = ShoppingListSerializer(list_item, data=request.data, partial=True)
            if updated_list_item.is_valid():
                updated_list_item.save()
                return Response(updated_list_item.data)
            else:
                return Response(updated_item.errors, status=status.HTTP_400_BAD_REQUEST)
        

对于反向关系,您应该在定义模型或使用后缀 _set 时使用 related_name。

The related_name attribute specifies the name of the reverse relation from the User model back to your model. If you don't specify a related_name, Django automatically creates one using the name of your model with the suffix _set

如果您只想在 ShoppingList 中显示商品的几个属性,您可以在序列化程序中使用 SerializerMethodField 方法

这会像 -

class ShoppingListSerializer(serializers.ModelSerializer):
    itemProperty1 = serializers.SerializerMethodField()
    itemProperty2 = serializers.SerializerMethodField()

    class Meta:
        model = ShoppingList
        fields = ('id', "itemProperty1", "itemProperty2", 'more_fields')
   
    def get_itemProperty1(self, instance):
        return instance.item.anyPropertyOfItem if instance.item else ''

    def get_itemProperty2(self, instance):
        return instance.item.anyPropertyOfItem if instance.item.else ''

anyPropertyOfItem 可以是物品模型中的任何东西。 以这种方式设置您的序列化程序,您的 ShoppingList 视图将自动显示 2 个新字段。

或者您也可以在模型中借助 @属性 定义只读字段以获取所需字段。

如果你想在ShoppingList视图中显示商品的所有属性,你可以写在这里,将编辑我的答案。您需要使用 related_name 并在 Shoppinglist 序列化程序中获取项目序列化程序作为额外字段。