【问题标题】:DRF- Error when creating a new instance in an M2M through modelDRF-通过模型在 M2M 中创建新实例时出错
【发布时间】:2020-09-13 16:19:41
【问题描述】:

我有以下两种型号:

class User(models.Model):
    user_id = models.CharField(
        max_length=129,
        unique=True,
    )
    user_article = models.ManyToManyField(
        Article,
        through="UserArticle",
    )
    occupation = models.CharField(max_length=100, default='null')

    def __str__(self):
        return self.user_id

class Article(models.Model):
    uuid = models.UUIDField(editable=False, unique=True)
    company = models.ForeignKey(
        Company,
        on_delete=models.PROTECT,
        related_name='article_company_id',
    )
    articleType = models.ForeignKey(
        ArticleType,
        on_delete=models.PROTECT,
        related_name='type',
    )    
    date_inserted = models.DateField()    
    def __str__(self):
        return self.uuid

使用多对多关系建模,通过模型:

class UserArticle(models.Model):    
    user = models.ForeignKey(User, to_field='user_id',
                             on_delete=models.PROTECT,)
    article = models.ForeignKey(Article, to_field='uuid',
                                 on_delete=models.PROTECT,)
    posted_as = ArrayField(
        models.CharField(max_length=100, blank=True),)
    post_date = models.DateField()

    class Meta:
        db_table = "core_user_articles"

这是我的看法:

class BatchUserArticleList(mixins.ListModelMixin,
                        mixins.CreateModelMixin,
                        generics.GenericAPIView):
    queryset = UserArticle.objects.all()
    serializer_class = BatchUserArticleSerializer

    def create(self, request, *args, **kwargs):
        serializer = BatchUserArticleSerializer(data=request.data)
        if not serializer.is_valid():
            return response.Response({'Message': 'POST failed',
                                  'Errors': serializer.errors},
                                 status.HTTP_400_BAD_REQUEST)
        self.perform_create(serializer)  # equal to serializer.save()
        return response.Response(serializer.data, status.HTTP_201_CREATED)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

我面临的问题是当我想在 M2M 表中发布以下格式的数据时:

{
    "posted_as": ["news"],
    "post_date": "2020-05-26",
    "user": "jhtpo9jkj4WVQc0000GXk0zkkhv7u",
    "article": [
        "11111111",
        "22222222"
    ]
}

上面包含许多文章的列表,所以我在我的serializer 中使用了custom field,以便提取每个article,创建一个新的UserArticle 对象并将其插入到我的bulk_create 中M2M 表。我认为当传入的数据不完全映射到数据库模型时,这是要走的路,但我可能错了。因此,如果您发现这种方法有问题,请发表评论。

这是序列化程序:

class BatchUserArticleSerializer(serializers.ModelSerializer):
    article= ArticleField(source='*') #custom field

    class Meta:
        model = UserArticle
        fields = ('posted_as', 'post_date', 'user', 'article')

    def validate(self, data):    
        post_date = data['post_date']
        if post_date != date.today():
            raise serializers.ValidationError(
                'post_date: post_date is not valid',
            )
        return data

    def create(self, validated_data):
        post_as = list(map(lambda item: item, validated_data['posted_as']))
        post_date = validated_data['post_date']
        user = validated_data['user']
        list_of_articles = validated_data['article']            
        user_object = User.objects.get(user_id=user)
        articles_objects = list(map(lambda res: Article.objects.get(uuid=res), list_of_articles))    
        user_articles_to_insert = list(map(
            lambda article: UserArticle(
                posted_as=posted_as,
                post_date=post_date,
                article=article,
                user=user_object),
            articles_objects))

        try:
            created_user_articles = UserArticles.objects.bulk_create(user_articles_to_insert)
            for res in created_user_articles:
                res.save()
            return created_user_articles
        except Exception as error:
            raise Exception('Something went wrong: {0}'.format(error))

class ArticleField(serializers.Field):
    def to_representation(self, value):
        resource_repr = [value.article]
        return resource_repr

    def to_internal_value(self, data):
        internal_repr = {
            'article': data
        }
        return internal_repr

这似乎工作正常,因为我可以看到数据被正确插入到 UserArticle 表中:

id | posted_as | post_date | user | article
1  | news      | 2020-05-26 | jhtpo9jkj4WVQc0000GXk0zkkhv7u | 11111111
2  | news      | 2020-05-26 | jhtpo9jkj4WVQc0000GXk0zkkhv7u | 22222222

当代码到达这一行时,问题就来了:

response.Response(serializer.data, status.HTTP_201_CREATED)

更具体地说,我得到的错误是:

AttributeError: Got AttributeError when attempting to get a value for field `posted_as` on serializer `BatchUserArticleSerializer`.

The serializer field might be named incorrectly and not match any attribute or key on the `list` instance. Original exception text was: 'list' object has no attribute 'posted_as'.

fields.py DRF 源中def get_attribute(instance, attrs) 函数的instance = getattr(instance, attr) 行引发了原始异常错误。

我在这里错过了什么?

【问题讨论】:

    标签: python django python-3.x django-rest-framework


    【解决方案1】:

    首先,没有理由为每个批量创建的实例调用save 方法。

    第二个是异常原因。你调用create viewset 方法。它调用序列化程序create 方法,该方法必须只返回一个实例(创建的对象)。但是您的序列化程序返回列表created_user_articles。列表确实没有字段posted_as

    所以,有两种方法可以解决它。

    1. 第一个是覆盖视图中的create方法,以改变数据表示的方式。例如。为响应数据使用另一个序列化器:

      def create(self, request, *args, **kwargs):
          serializer = self.get_serializer(data=request.data)
          serializer.is_valid(raise_exception=True)
          created_user_articles = self.perform_create(serializer)
      
          # use another way to get representation
          response_data = AnotherUserArticleSerializer(created_user_articles, many=True).data
          return Response(response_data, status=status.HTTP_201_CREATED, headers=headers)
      
      def perform_create(self, serializer):
          # add return to get created objects
          return serializer.save()
      
    2. 第二个是在序列化程序的create 方法中只返回一个实例。

    【讨论】:

    • 感谢您的回答。这确实是问题所在!您能否也举一个“改变数据表示方式”的例子?你这是什么意思?
    • @kingJulian 我添加了一个示例
    • 感谢您的回复,但我不明白这个新的AnotherUserArticleSerializer 将如何工作。我知道 tt 将序列化 created_user_articles 的列表,但它将基于哪个 model
    • @kingJulian 您在序列化程序中的创建方法会创建一个UserArticles 实例。所以新的序列化器应该基于UserArticles 模型。您甚至可以使用已经存在的BatchUserArticleSerializer 作为“附加”序列化程序。但不要忘记many=Truearg。
    • 是的,你是对的。我没有注意到您回答中的 many=True 论点。谢谢。
    猜你喜欢
    • 2019-08-08
    • 2021-09-26
    • 2017-05-30
    • 2022-08-18
    • 1970-01-01
    • 2011-08-02
    • 1970-01-01
    • 2021-03-16
    相关资源
    最近更新 更多