Reputation: 6180
I have the following two models:
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
and
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
which are modeled with a many-to-many relationship, using this through model:
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"
Here's my view:
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)
The problem I'm facing is when I want to POST data, of the following format, in the M2M table:
{
"posted_as": ["news"],
"post_date": "2020-05-26",
"user": "jhtpo9jkj4WVQc0000GXk0zkkhv7u",
"article": [
"11111111",
"22222222"
]
}
The above contains a list of many articles so I used a custom field
in my serializer
in order to extract each article
, create a new UserArticle
object and insert it, using bulk_create
, into my M2M table. I think that's the way to go when the incoming data do not map exactly to the DB model, but I might be wrong. So please comment if you see something off with this approach.
Here is the serializer:
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))
and
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
This seems to work ok as I can see data being correctly inserted in the UserArticle
table:
id | posted_as | post_date | user | article
1 | news | 2020-05-26 | jhtpo9jkj4WVQc0000GXk0zkkhv7u | 11111111
2 | news | 2020-05-26 | jhtpo9jkj4WVQc0000GXk0zkkhv7u | 22222222
The problem comes when code reaches this line:
response.Response(serializer.data, status.HTTP_201_CREATED)
and more specifically, the error I'm getting is:
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'.
The original exception error is raised at the instance = getattr(instance, attr)
line of the def get_attribute(instance, attrs)
function in the fields.py
DRF source.
What am I missing here?
Upvotes: 2
Views: 242
Reputation: 1604
First of all, there is no reason to call save
method for each of bulk-created instances.
Second one is reason of exception. You call create
viewset method. it calling serializers create
method which must return only one instance (created object). but your serializer returns list created_user_articles
. List really have no field posted_as
.
So, there is two ways to fix it.
First one is override create
method in view, to change the way of data representation. For ex. use another serializer for response data:
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()
Second one is return only one instance in create
method of your serializer.
Upvotes: 2