0

I am doing a delivery API REST usign Django and Django Rest Framework.

In the Meal app there is an ERROR that has been for days and I cannot solve it. I do not know how to fix it.

Thanks you in advance.

This is the bug shown in postman:

{
    "store": {
        "non_field_errors": [
            "Invalid data. Expected a dictionary, but got Store."
        ]
}

Meal/models/meals.py

class Meal(models.Model):
    '''Meals models.'''

    store = models.ForeignKey(
        Store, 
        on_delete=models.CASCADE,
        )

    name = models.CharField(max_length=120)
    slugname = models.SlugField(
        unique=True,
        max_length=120,
        )
    description = models.TextField(max_length=300, blank=True)
    price = models.DecimalField(
        'meal price',
        max_digits=5,
        decimal_places=2
        )
    picture = models.ImageField(
        'meal picture',
        upload_to='meals/pictures/',
        blank=True,
        null=True,
        help_text="Price max up to $999.99"
    )

    # Status
    is_available = models.BooleanField(
        'Meal available in menu',
        default=True,
        help_text='Show is the items is available for customers'
    )

    # Stats
    rating = models.FloatField(
        default=5.0,
        help_text="Meal's rating based on client califications"
    )

Meal/views/meals.py

class MealViewSet(mixins.ListModelMixin,
                    mixins.CreateModelMixin,
                    mixins.RetrieveModelMixin,
                    mixins.DestroyModelMixin,
                    viewsets.GenericViewSet):
    '''Meals view set.
    '''

    serializer_class = MealModelSerializer
    lookup_field = 'slugname'
    search_fields = ('slugname', 'name')


    # Method call every time this MealViewSet is instanced
    def dispatch(self, request, *args, **kwargs):
        '''Verify that the store exists.
        Add the URL input <store_slugname> to the Meal model field store(FK).
        '''

        store_slugname = kwargs['store_slugname']
        self.store = get_object_or_404(Store, store_slugname=store_slugname)

        return super(MealViewSet, self).dispatch(request, *args, **kwargs)


    def get_queryset(self):
        '''Get Store's available meals'''

        return Meal.objects.filter(
            store=self.store,
            is_available=True
        )

    def create(self, request, *args, **kwargs):
        '''Assign Meal to a Store (received in the URL input <store_slugname>)
        '''
        store = self.store # Got from the dispatcher
        request.data['store'] = store
        serializer = MealModelSerializer(
            store,
            data=request.data
        )
        # import pdb; pdb.set_trace()
        serializer.is_valid(raise_exception=True)
        serializer.save()
        data = serializer.data
        return Response(data, status=status.HTTP_201_CREATED)

and Meal/serializers/meals.py

class MealModelSerializer(serializers.ModelSerializer):
    '''Meal model serializer.'''

    store = StoreModelSerializer()

    class Meta:
        """Meta class."""

        model = Meal
        fields = (
            'store',
            'name',
            'slugname',
            'description',
            'price',
            'picture',
            'rating'
        )
        read_only_fields = (
            'rating',
        )

The debugger in the terminal shows this: (Store=dominospizza is a valid and previous Store created)

Pdb) store
<Store: dominospizza>
(Pdb) store.__dict__
{'_state': <django.db.models.base.ModelState object at 0x7fd539882c10>, 'id': 1, 'name': 'Peperoni large pizza', 'store_slugname': 'dominospizza', 'about': '', 'picture': '', 'pickup_address': 'Urdesa cerca de casa de Agapito', 'is_active': True, 'is_open': True, 'orders_dispatched': 0, 'reputation': 5.0}
(Pdb) request.data
{'name': 'Peperoni large pizza', 'slugname': 'LPizzaPeperoni', 'price': '9.00', 'store': <Store: dominospizza>}
xavier
  • 57
  • 2
  • 8

2 Answers2

1

First off,

Try to follow REST as much as possible. If you want to filter the backend entities based on a field value, provide them in query params. You can automate filtering using django-filters. Refer this.

Secondly,

You don't need to override the create method of the ViewSet to achieve that. Serializers are made for this. Modify the serializer code as below,

class MealModelSerializer(serializers.ModelSerializer):
    '''Meal model serializer.'''

    store = StoreModelSerializer()
    store_id = models.PrimaryKeyRelatedField(
        queryset=models.Store.objects.all(),
        write_only=True,
        source="store",
        required=False
    )
    store_slogname = models.SlugRelatedField(
        queryset=models.Store.objects.all(),
        write_only=True,
        slug_field="store_slugname"
        source="store",
        required=False
    )

    class Meta:
        """Meta class."""
        model = Meal
        fields = (
            'store',
            'name',
            'slugname',
            'description',
            'price',
            'picture',
            'rating'
        )
        read_only_fields = (
            'rating',
        )

But remember, the standard practice is to pass id's instead of names. Make sure the Store field store_slugname is unique.

From the frontend, you can pass store_id or store_slugname in the POST, PUT, PATCH body. The serializer automatically looks up the DB and validates the data. You don't need to override any method.

Edit #1:

You can use django-rest-framework-filters feature AllLookupsFilter to provide all types of filtering at frontend level.

Example:

/api/{version}/meals/?store__store_slugname=<value>
/api/{version}/meals/?store__store_slugname__contains=<value>
/api/{version}/meals/?store__store_slugname__in=<value1,value2,value3>

To sum up, your viewset will look something like this,

class MealViewSet(
    mixins.ListModelMixin,
    mixins.CreateModelMixin,
    mixins.RetrieveModelMixin,
    mixins.DestroyModelMixin,
    viewsets.GenericViewSet
):
    '''Meals view set.'''

    serializer_class = MealModelSerializer
    lookup_field = 'slugname'
    search_fields = ('slugname', 'name')
    filter_class = MealFilter

Hope this helps!

Crash0v3rrid3
  • 518
  • 2
  • 6
0

please try to change this in MealViewSet.create:

request.data['store'] = store

by this:

request.data['store'] = store.id

This is because i think default ModelSerializer fks are solved like a primarykeyrelatedfield, then its expecting a pk not the instance.

Diego Puente
  • 1,964
  • 20
  • 23
  • Your contribution has worked. Now, I've update the serializer (look the updated post, just adding store = StoreModelSerializer() ) and there's a new error (look in the updated post). – xavier Apr 28 '20 at 18:35
  • Why you did that? You are not expecting the store like a dict, you are getting the store from dispatch method. If you are using this serializer to serializer the Meal Object too, you have to change in the first line of create method `store = self.store # Got from the dispatcher` by this `store = StoreModelSerializer(self.store).data` – Diego Puente Apr 28 '20 at 18:46