ClassViews and ChoiceField

Hi,

I’m new to Django and Danjo REST Framework and I’m trying to create a simple app that has two models related and a choice field.

So, I have this:

models.py

class Category(models.Model):
    title = models.CharField(max_length=255, db_index=True)

class MenuItem(models.Model):
    title = models.CharField(max_length=255, db_index=True)
    price = models.DecimalField(max_digits=6, decimal_places=2, db_index=True)
    category = models.ForeignKey(Category, on_delete=models.PROTECT, default=1)

serializers.py

class CategorySerializer (serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = ['id','title']
        


class MenuItemSerializer(serializers.ModelSerializer):
    category = CategorySerializer(read_only=True)
    category_id = serializers.ChoiceField(write_only=True, choices=Category.objects.values_list('id','title'))
    class Meta:
        model = MenuItem
        fields = ['id','title','price','category', 'category_id']

views.py

class CategoriesView(generics.ListCreateAPIView):
    queryset = Category.objects.all()
    serializer_class = CategorySerializer

class MenuItemView(generics.ListCreateAPIView):
    queryset = MenuItem.objects.select_related('category').all()
    serializer_class = MenuItemSerializer
    def get(self, request):
        items = MenuItem.objects.select_related('category').all()
        serialized_item = MenuItemSerializer(items, many=True)
        return Response(serialized_item.data, status.HTTP_200_OK)

class SingleMenuItem(generics.RetrieveUpdateDestroyAPIView):
    queryset = MenuItem.objects.select_related('category').all()
    serializer_class = MenuItemSerializer
    def get(self, request, pk):
        item = get_object_or_404(MenuItem, pk=pk)
        serialized_item = MenuItemSerializer(item)
        return Response(serialized_item.data, status.HTTP_200_OK)

urls.py

urlpatterns = [
    path('menu-items', views.MenuItemView().as_view()),
    path('menu-items/<int:pk>', views.SingleMenuItem().as_view()),
    path('category', views.CategoriesView.as_view()),
]

This works fine, but when I try to access:

http://127.0.0.1:8000/api/menu-items/2

for example, and the category set to this item is 3, it shows the value of category 1 and not 3, and no mater what value I set, it always shows the first item of Category and not the one that actualy is on the dropdown.

I know that HTML has the “selected” option for the “select” form, but I’m really strugling in how to implement this in Django.

Hello.
Please format your code using code brackets next time in order to be more readable.

The main issue is with how you’re handling the category_id in your serializer and also a problem with your URL pattern.

  1. Fix your MenuItemSerializer. The problem was that you were using ChoiceField but defining the choices dynamically at import time, which can cause problems. Instead, it is much better to use an IntegerField and validating it.
class MenuItemSerializer(serializers.ModelSerializer):
    category = CategorySerializer(read_only=True)
    category_id = serializers.IntegerField(write_only=True)
    
    class Meta:
        model = MenuItem
        fields = ['id', 'title', 'price', 'category', 'category_id']
    
    def validate_category_id(self, value):
        if not Category.objects.filter(id=value).exists():
            raise serializers.ValidationError("Invalid category id")
        return value
  1. URL Patterns - The pattern 'menu-items/int:pk' is incorrect syntax - Django expects <int:pk> with angle brackets.
# urls.py
urlpatterns = [
    path('menu-items/', views.MenuItemView.as_view()),
    path('menu-items/<int:pk>/', views.SingleMenuItem.as_view()),
    path('category/', views.CategoriesView.as_view()),
]

Sorry, it’s the first time I write here. I fixed that now. Thanks for observing this.

Humm, doing that way, Category will not be a human readeable Drop Down, which is the intent of my question. I need to have a Drop Down, with words on it, and that’s the ForeignKey to another model (by it’s id).

Well, this, I think is because of the lack of using brackets here, as you observed. Proabably the site removed it, but the angle brackets are in my code.

Regards,

You need to have a Dropdown in your frontend - html template? Please post your html template code if so.

I don’t have one. I’m seeing the template created by the generics view from DRF.
The idea here is to understand this better and learn how to do that. I’ll choose some frontend lib latter, but need the backend to use this auto generated html now.

Something like this?

# serializers.py
from rest_framework import serializers
from .models import Category, MenuItem

class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = ['id', 'title']

class MenuItemSerializer(serializers.ModelSerializer):
    # Show the full category object in read operations
    category = CategorySerializer(read_only=True)
    
    # Present a dropdown of category choices
    category_id = serializers.PrimaryKeyRelatedField(
        queryset=Category.objects.all(),
        write_only=True,
        source='category'
    )
    
    class Meta:
        model = MenuItem
        fields = ['id', 'title', 'price', 'category', 'category_id']

Yes, this do exactly what my code does.
The only problem that I was looking to solve is that the dropdown is set to the first entry and not the entry of the category_id retrieved.

Let me make it more clear on an example:

I have a MenuItem that has it’s id (let’s say 2), title, price and the category_id is 3
On the Category model I have the entries { 1, "Main "}, { 2 , “Sidedishes” }, {3, “Appetizers” }

When I load the URL to get the MenuItem with pk = 2, it shows the form with the information filled but the dropdown shows “Main”. What I want is that the dropdown show “Appetizers” in this moment and when the user clicks, it see’s the other two categories above on the list.

In terms of HTML, I’m seeking to do this:

What’s today:

  <select id="category" name="category">
    <option value="1">Main</option>
    <option value="2">Sidedishes</option>
    <option value="3">Appetizers</option>
  </select>

What I need:

  <select id="category" name="category">
    <option value="1">Main</option>
    <option value="2">Sidedishes</option>
    <option value="3" selected>Appetizers</option>
  </select>

(note the “selected” in the third option)

Hope this make it clear.
Thanks for all your time and help on this!

So, your question now is regarding the frontend implementation of your dropdown based on your API endpoints. It is not a django related question. You can use javascript, ajax and HTML for that: https://www.youtube.com/watch?v=vBWxf0MMO7M