'Django ManyToMany through field with JSON:API and Swagger

I created a serializer that has a field called products_list, which is Nested.

My problem is that I'm using Swagger autogenerated documentation with drf-yasg, and that field does not appear in the expected body for the endpoint.

I installed and copied the SWAGGER_SETTINGS on https://github.com/glowka/drf-yasg-json-api

My documentation schema code looks like this:

from drf_yasg import openapi
from drf_yasg.generators import OpenAPISchemaGenerator
from drf_yasg.views import get_schema_view


class BothHttpAndHttpsSchemaGenerator(OpenAPISchemaGenerator):
    def get_schema(self, request=None, public=False):
        schema = super().get_schema(request, public)
        schema.schemes = ["http", "https"]
        return schema


schema_view = get_schema_view(
    openapi.Info(
        title="Mobile quoter API",
        default_version='1.0.0',
        description="API managing quoting app",
        terms_of_service="https://www.google.com/policies/terms/",
    ),
    generator_class=BothHttpAndHttpsSchemaGenerator,
    public=True,
)

These are my Serializers:

class QuoteSerializer(serializers.ModelSerializer):
    """Serializer for Quotes"""

    products_list = QuoteProductsSerializer(many=True, write_only=True,
                                            required=True)
    extra_info = serializers.CharField(required=False)

    class Meta:
        model = Quote
        fields = (
            'client',
            'extra_info',
            'products_list',
            'generated_by',
            'closed_at',
            'created_at',
            'cost',
        )
        extra_kwargs = {
            'quoteproducts_set': {'write_only': True},
            'generated_by': {'read_only': True},
            'closed_at': {'read_only': True},
            'created_at': {'read_only': True},
            'cost': {'read_only': True},
        }

My models:

class Quote(SafeModel):
    client = models.ForeignKey(Client, on_delete=models.PROTECT)
    generated_by = models.ForeignKey(User, on_delete=models.PROTECT)
    closed_at = models.DateTimeField(blank=True, null=True)
    extra_info = models.TextField(blank=True)
    cost = models.DecimalField(max_digits=9, decimal_places=2)
    products = models.ManyToManyField(Product, through='QuoteProducts')
    # Quote model does not have a created_at field because SafeModel has one


class QuoteProducts(Model):
    product = models.ForeignKey(Product, on_delete=models.PROTECT)
    quote = models.ForeignKey(Quote, on_delete=models.PROTECT)
    quantity = models.IntegerField()
    price = models.DecimalField(max_digits=7, decimal_places=2)

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=('quote', 'product'),
                name='quote_product_unique_constraint',
            ),
        ]

And this is my view for Quotes:

from rest_framework import status
from rest_framework.response import Response

from mobilequoter.helper.views import CRViewSet
from mobilequoter.quote.handlers import QuoteAPIHandler
from mobilequoter.quote.models import Quote
from mobilequoter.quote.serializers.quote import QuoteSerializer


class QuoteViewSet(CRViewSet):
    """View to Create and Read Quotes."""

    queryset = Quote.objects.all()
    serializer_class = QuoteSerializer

    def create(self, request, *args, **kwargs):
        data = QuoteAPIHandler.handle(request)
        return Response(data, status=status.HTTP_201_CREATED)

Even though I override the create method, I do not think this is the reason for my problem.

As I said, my problem is that in the swagger autogenerated API documentation, this is what I get for the body of my Quote POST endpoint: enter image description here

Almost everything is good, except for the fact that the body does not says it expects a products_list field. I'd like to know what exactly is my mistake. I tried reading the documentation (https://django-rest-framework-json-api.readthedocs.io/en/stable/usage.html) , but from what I understand, it doesn't show a use case with a M2M through field.

As a side note, I believe all my problems would fix if there was a way to use JSON API when outputting data, so that it has the JSON API format, but on the input (a POST, PUT or PATCH), only receive the data like this:

enter image description here

Is that possible? And is it even a good practice?

Thank you for your time, any answer is appreciated.



Solution 1:[1]

I got it to work. On my QuoteSerializer, I replaced:

    products_list = QuoteProductsSerializer(many=True, write_only=True,
                                            required=True)

with:

    products_list = serializers.ListField(
        child=QuoteProductsSerializer(),
        write_only=True,
        required=True
    )

It shows in the documentation as expected:

enter image description here

I would like not to have to specify all those "type"s for the POST requests, and for it to infer them, but to me this is acceptable. At least the API documentation itself shows the value for type on a request.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Gustavo Lozada