Hybrid
Hybrid

Reputation: 7049

How Do I Include ForeignKey in django-rest-framework POST

So I have tried to make a browsable API via django-rest-framework (DRF), but I have had some issues nesting serializers. So far, I am able to include the Sport and Category fields/foreignkeys into my Article, but when I try to POST via the API, I get an error saying as follows:

Got a TypeError when calling Article.objects.create(). This may be because you have a writable field on the serializer class that is not a valid argument to Article.objects.create(). You may need to make the field read-only, or override the ArticleSerializer.create() method to handle this correctly. Original exception text was: int() argument must be a string, a bytes-like object or a number, not 'ArticleSport'.

Here are my files:

models.py

[...]

class ArticleSport(TimeStampedModel):
    title = models.CharField(max_length=20, blank=False)
    slug = AutoSlugField(populate_from='title', unique=True, always_update=True)
    parent = models.ForeignKey('self', blank=True, null=True, related_name='children')  # TODO: Add on_delete?

    uuid = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)

    def __str__(self):
        return '{0}'.format(self.title)

    #class Meta:  # TODO: Migrate live
        #unique_together = ('title', 'parent')


class ArticleCategory(TimeStampedModel):
    title = models.CharField(max_length=20, blank=False)
    slug = AutoSlugField(populate_from='title', unique=True, always_update=True)
    parent = models.ForeignKey('self', blank=True, null=True, related_name='children')  # TODO: Add on_delete?

    uuid = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)

    def __str__(self):
        return '{0}'.format(self.title)

    class Meta:
        verbose_name_plural = 'article categories'
        #unique_together = ('title', 'parent')  # TODO: Migrate live


class Article(TimeStampedModel):
    DEFAULT_FEATURED_IMAGE = settings.STATIC_URL + 'images/defaults/default-featured-image.png'

    title = models.CharField(max_length=160, blank=False)
    slug = AutoSlugField(populate_from='title', unique=True, always_update=True)
    sport = models.ForeignKey(ArticleSport, on_delete=models.CASCADE, related_name='articleAsArticleSport')
    category = models.ForeignKey(ArticleCategory, on_delete=models.CASCADE, related_name='articleAsArticleCategory')
    featured_image = models.ImageField(upload_to=PathAndUniqueFilename('featured-images/'), blank=True)
    featured_image_caption = models.CharField(max_length=100, blank=True)
    views = models.IntegerField(default=0)

    uuid = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)

    def get_absolute_url(self):
        return reverse('main:article_specific', args=[self.slug])  # TODO: Remove if standalone pages are removed

    def get_featured_image(self):
        if self.featured_image:
            return self.featured_image.url
        else:
            return self.DEFAULT_FEATURED_IMAGE

    def get_comment_count(self):
        return ArticleComment.objects.filter(article=self).count()

    def __str__(self):
        return '{0}'.format(self.title)

[...]

urls.py

[...]

class ArticleSportSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = ArticleSport
        fields = ('id', 'title', 'parent', 'created', 'modified')


class ArticleCategorySerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = ArticleCategory
        fields = ('id', 'title', 'parent', 'created', 'modified')


class ArticleSerializer(serializers.HyperlinkedModelSerializer):
    sport = ArticleSportSerializer(read_only=True)
    sport_id = serializers.PrimaryKeyRelatedField(queryset=ArticleSport.objects.all(), write_only=True)
    category = ArticleCategorySerializer(read_only=True)
    category_id = serializers.PrimaryKeyRelatedField(queryset=ArticleCategory.objects.all(), write_only=True)
    modified = serializers.HiddenField(default=timezone.now())  #TODO: Figure out how to implement this

    class Meta:
        model = Article
        fields = ('id', 'title', 'sport', 'sport_id', 'category', 'category_id', 'featured_image', 'featured_image_caption', 'views', 'created', 'modified')

[...]

Sample POST to API:

{
    "title": "This is a test Title",
    "sport_id": 1,
    "category_id": 1,
    "featured_image": null,
    "featured_image_caption": "",
    "views": null,
    "modified": null
}

Upvotes: 3

Views: 5793

Answers (2)

Hybrid
Hybrid

Reputation: 7049

So I was able to answer this question by using some of Carter_Smith's advice - I am not 100% sure why this worked, but I added this create() method to my ArticleSerializer, and it worked:

def create(self, validated_data):
    # Override default `.create()` method in order to properly add `sport` and `category` into the model
    sport = validated_data.pop('sport_id')
    category = validated_data.pop('category_id')
    article = Article.objects.create(sport=sport, category=category, **validated_data)
    return article

My guess is that the PrimaryKeyRelatedField() tries to resolve sport_id and category_id as kwarg fields based on their name, when they should be just sport and category, and so overriding .create() allows you to fix that, while still allowing for a read_only field for sport and category. Hope this helps anyone else who has the same issue.

Upvotes: 3

Carter Smith
Carter Smith

Reputation: 17

You need to override the serializer's create method to accommodate your POST request. This probably isn't what you're looking for, but you haven't included your sample request so we haven't got much to go off of.

This would've been a comment had I been of high enough reputation.

Upvotes: 1

Related Questions