【问题标题】:Nested serializers and data representation嵌套序列化器和数据表示
【发布时间】:2022-01-10 11:57:00
【问题描述】:

在 django 项目上工作时,我有点卡在通过 API 表示数据的问题上。事实上,在设计模型时,数据模型非常简单:我有一对多的关系 A--> B 因此我为对象 B 添加了一个 FK。

对象 B 有一个布尔属性“活动”。

我想进行 API 调用以列出所有具有至少一个关联对象 B 且 active = true 的 A 对象。

API 可能是这样的:

/api/objectA/?ObjectB.active=True

这是我的代码:

型号:

from django.contrib.postgres.fields import ArrayField
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver


class Startup(models.Model):
    header = models.CharField("Header", max_length=255)
    title = models.CharField("Title", max_length=255)
    description = models.CharField("description", max_length=255)
    # TODO Change this to options instead of array
    tags = ArrayField(models.CharField(max_length=10, blank=True), size=5)
    # TODO Images to be stored in aws only url will be in DB
    card_image = models.ImageField(upload_to='media/images/cards')
    logo_image = models.ImageField(upload_to='media/images/logos')
    main_img = models.ImageField(upload_to='media/images/main', null=True)
    createdAt = models.DateTimeField("Created At", auto_now_add=True)

    def __str__(self):
        return self.title


class Investment(models.Model):
    # TODO change the name of Investment to fund round in back and front
    # TODO all price to be checked for max digits and decimal places

    startup = models.ForeignKey(Startup, related_name='startup_investments', on_delete=models.CASCADE, default="1")
    # Use the related_name as a serializer bale for investments inside startup serializer
    Investment_title = models.CharField("Investment_title", max_length=255, default="Missing Title")
    collected_amount = models.DecimalField(max_digits=12, decimal_places=2)
    goal_percentage = models.IntegerField(default=0)
    number_of_investors = models.IntegerField(default=0)
    days_left = models.IntegerField()
    active = models.BooleanField(default=False)
    # TODO Need to update this to prevent linking to a non existing startup
    createdAt = models.DateTimeField("Created At", auto_now_add=True)

    def clean(self):
        """Validate that the startup does not have already an active Investment """
        if self.active:
            qs = Investment.objects.filter(active=True).filter(startup=self.startup)
            if self.pk is not None:
                qs = qs.exclude(pk=self.pk)
                if qs:
                    raise ValidationError(message="An active investment already exists for this startup")

    def __str__(self):
        return self.Investment_title

序列化器:

from rest_framework import serializers
from .models import Startup, Investment


class InvestmentSerializer(serializers.ModelSerializer):
    class Meta:

        model = Investment
        fields = ('id', 'Investment_title', 'collected_amount', 'goal_percentage', 'number_of_investors',
                  'days_left', 'active')


class StartupSerializer(serializers.ModelSerializer):
    startup_investments = InvestmentSerializer(many=True, read_only=True)

    class Meta:
        model = Startup
        fields = ('id', 'header', 'title', 'description', 'tags', 'card_image',
                  'logo_image', 'main_img', 'startup_investments')

观看次数:

from django_filters import rest_framework as filters
from rest_framework.viewsets import ModelViewSet
from rest_framework_extensions.mixins import NestedViewSetMixin

from .serializers import *


class StartUpViewSet(NestedViewSetMixin, ModelViewSet):
    """
      Class that provides List, Retrieve, Create, Update, Partial Update and Destroy  actions for startups.
      It also include a filter by startup status
    """
    model = Startup
    queryset = Startup.objects.all()
    serializer_class = StartupSerializer


class InvestmentViewSet(NestedViewSetMixin, ModelViewSet):
    """
     Class that provides List, Retrieve, Create, Update, Partial Update and Destroy  actions for Investments.
     It also include a active and investment title
    """
    model = Investment
    serializer_class = InvestmentSerializer
    queryset = Investment.objects.all()
    filter_backends = (filters.DjangoFilterBackend,)
    filterset_fields = ('active', 'Investment_title')

路由器:

router = ExtendedSimpleRouter()
(
    router.register(r'api/investments', views.InvestmentViewSet, basename='investment'),
    router.register(r'api/startups', views.StartUpViewSet, basename='startup')
          .register(r'investments', views.InvestmentViewSet, basename='startups_investment',
                    parents_query_lookups=['startup']),

)

感谢您的帮助。

【问题讨论】:

  • 我猜你已经设置了一个 APIView 一个序列化器并且你正在处理一个查询集。在这种情况下,你必须使用对象 A 上的注释和组来创建一个查询集
  • 感谢 marco by annotate 你的意思是添加一个动作? @动作?
  • 不,我的意思是在视图中你必须设置一个 get_queryset 来查询你的模型,在查询中你必须使用一种叫做 annotate 的方法,在 Django 中也用于对某些数据进行分组字段..如果您发布一些代码,它会更容易修复
  • 当然很抱歉应该早点这样做,我已经编辑了我的问题!

标签: django api django-rest-framework


【解决方案1】:

您好,我发布此答案希望对其他人有所帮助,因为我花了两天时间来完成这项工作!

class ActiveStartupSerializer(serializers.ListSerializer):

    def to_representation(self, data):
        """List all startups with one active investment"""
        data = data.filter(startup_investments__active=True)
        return super(ActiveStartupSerializer, self).to_representation(data)

    class Meta:
        model = Startup
        fields = ('id', 'header', 'title', 'description', 'tags', 'card_image',
                  'logo_image', 'main_img', 'startup_investments')


class InvestmentSerializer(serializers.ModelSerializer):
    class Meta:

        model = Investment
        fields = ('id', 'Investment_title', 'collected_amount', 'goal_percentage', 'number_of_investors',
                  'days_left', 'active')


class StartupSerializer(serializers.ModelSerializer):
    startup_investments = InvestmentSerializer(many=True, read_only=True)

    class Meta:
        model = Startup
        list_serializer_class = ActiveStartupSerializer
        fields = ('id', 'header', 'title', 'description', 'tags', 'card_image',
                  'logo_image', 'main_img', 'startup_investments')

【讨论】:

    【解决方案2】:

    我会尝试这样的事情:

    class StartUpViewSet(NestedViewSetMixin, ModelViewSet):
    
        model = Startup
        #queryset = Startup.objects.all()
        serializer_class = StartupSerializer
        
        def get_queryset(self):
            Startup.objects.annotate(active_investments=Count('startup_investments', filter=Q(startup_investments__active=True)).filter(active_investments__gt=0)
    

    【讨论】:

    • 谢谢,因为我使用的是扩展的简单路由器,并且添加的操作是一个列表方法,我希望将操作添加到 API 浏览器,但事实并非如此,我是否必须明确注册此操作?
    • 谢谢,我仍然需要改变一些东西,但由于我不是 django 专家,我不确定我应该添加什么,我得到 'Q' object is not callable when hit the get_active_startups API
    • 我已将此添加到我的导入中以使 Q 正常工作:from django.db.models import Q
    猜你喜欢
    • 2019-10-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-07
    • 2017-07-28
    • 2021-05-08
    • 2020-08-24
    • 1970-01-01
    相关资源
    最近更新 更多