3

I have an issue when making a GET request on this endpoint: /api/vacancy/{hotel_id}/. It should return vacancy information for a specific Hotel ID.

In my schema, the Vacancy model contains the following properties:

  • hotel_id as a foreign key.
  • room_id as a foreign key.
  • vacancy as integer.
  • rented as integer.

In order to get the relevant information, I intended on calling the HotelDetailsSerializer through the Vacancy Viewset, thinking it might be easier than having to filter the Vacancy table according to the hotel_id value provided in the URL, as it would automatically provide the Vacancy collection related to that specific Hotel ID.

Making a GET request on http://localhost:8000/api/hotels/1 (as shown below) provides the information I need. However when I'm trying to get this response via the Vacancy Viewset with this end-point http://localhost:8000/api/vacancies/1 I get the following error:

TypeError at /api/vacancies/1/
'int' object is not iterable

So is what I'm trying to do even possible in the first place, or is there a better way or some best practise to apply here?

Thanks for your responses!

Here is my Vacancy Serializer file:

from rest_framework import serializers
from ..models.model_vacancy import Vacancy
from .serializers_room import *
from .serializers_hotel import *


class VacancyIndexSerializer(serializers.ModelSerializer):
    """
    Serializer listing all Vacancies models from DB
    """

    room_id = RoomIndexSerializer(allow_null=True)

    class Meta:
        model = Vacancy
        fields = [
            'room_id',
            'hotel_id',
            'vacancy',
            'rented'
        ]

class VacancyDetailsSerializer(serializers.ModelSerializer):
    """
    Serializer showing details of a Vacancy model from DB
    """

    room_id = RoomDetailsSerializer()

    class Meta:
        model = Vacancy
        fields = [
            'room_id',
            'vacancy',
            'rented'
        ]
  
  
class VacancyCreateSerializer(serializers.ModelSerializer):
    """
    Serializer to create a Vacancy model in DB
    """

    class Meta:
        model = Vacancy
        fields = [
            'room_id',
            'hotel_id',
            'vacancy',
            'rented'
        ]

    def create(self, validated_data):
        vacancy = Vacancy.objects.create(**validated_data)
        return stock

class VacancyUpdateSerializer(serializers.ModelSerializer):
    """
    Serializer declaring fields to be provided to update the Vacancy instance.
    """

    class Meta:
        model = Vacancy
        fields = [
            'room_id',
            'hotel_id',
            'vacancy',
            'rented'
        ]

Its Viewset in which I use another Serializer for the retrieve class:

from rest_framework import viewsets
from ..models.model_vacancy import Vacancy
from ..serializers.serializers_vacancy import *
from ..serializers.serializers_hotel import *


class VacancyViewSet(viewsets.ModelViewSet):
    """
    Vacancy ViewSet calling various serializers depending on request type (GET, PUT etc.)
    """
    queryset = Vacancy.objects.order_by('id')

    # mapping serializer into the action
    serializer_classes = {
        'list': VacancyIndexSerializer,
        'retrieve': HotelDetailsSerializer,
        'create': VacancyCreateSerializer,
        'update': VacancyUpdateSerializer,
        'partial_update': VacancyUpdateSerializer
  
    }

    # Your default serializer
    default_serializer_class = VacancyIndexSerializer

    def get_serializer_class(self):
        """
        Method to detect request type (GET, PUT etc.) and select corresponding serializer.
        """
        return self.serializer_classes.get(self.action, self.default_serializer_class)

And here is my other Serializer:

from rest_framework import serializers
from ..models.model_hotel import Hotel
from .serializers_vacancy import *


class HotelIndexSerializer(serializers.ModelSerializer):
    """
    Serializer listing all Hotels models from DB
    """

    class Meta:
        model = Hotel
        fields = [
            'id',
            'name',
        ]

class HotelDetailsSerializer(serializers.ModelSerializer):
    """
    Serializer showing details of a Hotel model from DB
    """

    vacancy = VacancyDetailsSerializer(many=True)

    class Meta:
        model = Hotel
        fields = [
            'vacancy'
        ]
  
class HotelCreateSerializer(serializers.ModelSerializer):
    """
    Serializer to create a Hotel model in DB
    """

    class Meta:
        model = Hotel
        fields = [
            'name',
        ]

class HotelUpdateSerializer(serializers.ModelSerializer):
    """
    Serializer declaring fields to be provided to update the Hotel instance.
    """

    class Meta:
        model = Hotel
        fields = [
            'name',
        ]

In Postman when I'm requesting a GET on http://localhost:8000/api/hotels/1 I get:

{
    "vacancy": [
        {
            "room_id": {
                "room": "first",
                "name": "first one",
                "description": "description"
            },
            "vacancy": 10,
            "rented": 0
        },
        {
            "room_id": {
                "room": "second",
                "name": "second one",
                "description": "description"
            },
            "vacancy": 10,
            "rented": 0
        }
    ]
}
MaxenceP
  • 291
  • 2
  • 12
Seb
  • 363
  • 1
  • 18
  • 1
    Please don't make more work for other people by vandalizing your posts. By posting on the Stack Exchange network, you've granted a non-revocable right, under the [CC BY-SA 4.0 license](https://creativecommons.org/licenses/by-sa/4.0/), for Stack Exchange to distribute that content (i.e. regardless of your future choices). By Stack Exchange policy, the non-vandalized version of the post is the one which is distributed. Thus, any vandalism will be reverted. If you want to know more about deleting a post please see: [How does deleting work?](https://meta.stackexchange.com/q/5221) – Nick Jan 06 '21 at 04:26

1 Answers1

2

You can't force ModelViewset to return a hotel for retreive actions when it is configured for a vacancy model.

By specifying HotelDetailsSerializer there you produce an error cause when you fetch /api/vacancies/1/ it is a vacancy instance with ID=1 that's being passed to that serialzier which is obviously not right, you should only pass hotel to HotelDetailsSerializer.

Now, a hotel can have multiple vacancies assigned to it (you showed it yourself in /api/hotels/1 response). So if you want to fetch stocks by hotel_id then a request should return multiple vacancies for that bar, not 1.

An API to fetch multiple vacancies is GET /api/vacancies/, all you need is to pass a query param to filter it like this: GET /api/vacancies/?hotel=1

You can achieve it with django-filter

After installing it as stated in docs, add it to your VacancyViewSet:

filterset_fields = ['hotel']
MaxenceP
  • 291
  • 2
  • 12
GProst
  • 9,229
  • 3
  • 25
  • 47
  • Thanks for your reply! Missed the fact that a call on an endpoint, you actually pass an model's instance to the serializer. Thought I'd be able to just get the primary key from the url and from there make sure the serializer called the proper model. – Seb Sep 27 '20 at 12:58
  • Such url wouldn't be a good practice for REST architecture. Fetching `/api/stocks/1/` a person would expect it to return a stock with id=1, not a bar. – GProst Sep 27 '20 at 13:01
  • Totally agree on this and not what I usually do. But unfortunately in this specific case I don't have a choice, it's an imposed constraint. – Seb Sep 27 '20 at 13:28
  • Hello @GProst, I have tried what you mentioned by using the Stock Index serializer but by filtering with a queryset because I can't pass a param in the url (another constraint). So I don't have that error anymore but it doesn't return the children's collection but only the first corresponding one, or sometimes not even. Any hint on how to solve this? – Seb Oct 05 '20 at 12:08