I'm new to Django/Django REST FW (and new to this community). I've spent a lot of time reading the documentation but I'm spinning my wheels at this point. I apologize in advance for being so long-winded here.
My back end DB is Postgres. I've got 3 Models, User, Item and ShoppingList. I need Item to contain a description (the item_name field) and its location. The user will select an item and add it to the ShoppingList for that day. The idea is that the user will then "check off" the item once acquired and it'll be "removed" from the view of the ShoppingList.
Here is where I'm having trouble: I don't want to duplicate the item_name and item_location fields in the shopping_list table, but I need to display those fields in the view of the shopping list (shopping_lists.py).
There is a one-to-many relationship between Item and ShoppingList respectively. The Item object is considered a "master items table" that stores descriptions and locations for each item. The ShoppingList object holds a temporary list of these "master items". I need a queryset that contains all fields from ShoppingList and 2 or more fields from Item.
I think this would be what Django REST FW considers a Reverse Relationship. I've tried a variety of changes to my Serialzer(s) and Models (including adding the Item Serializer to the ShoppingList Serializer) and gotten a variety of errors.
models/item.py:
from django.db import models
from django.contrib.auth import get_user_model
class Item(models.Model):
item_name = models.CharField(max_length=50, db_index=True)
item_location = models.CharField(max_length=10, blank=True, db_index=True)
item_class = models.CharField(max_length=20, blank=True)
# This is a relationship with User model
shopper_id = models.ForeignKey(
get_user_model(),
on_delete=models.CASCADE
)
def __str__(self):
return f"item_name: {self.item_name}, item_location: {self.item_location}, shopper_id: {self.shopper_id}"
models/shopping_list.py:
from django.db import models
from django.contrib.auth import get_user_model
from .item import Item
class ShoppingList(models.Model):
item_num = models.ForeignKey(
'Item',
on_delete=models.DO_NOTHING # we don't want to delete the item from the "master" item list, just from this shopping list
)
# This is a relationship with user model.
shopper_id = models.ForeignKey(
get_user_model(),
on_delete=models.CASCADE # ...but we do want to delete the item if the user goes away as items are user-specific
)
item_qty = models.PositiveIntegerField()
item_complete = models.BooleanField(default=False)
added_on = models.DateField(auto_now=True)
# setting list_num to blank=True for this version
list_num = models.PositiveIntegerField(blank=True)
def __str__(self):
return f"item_num: {self.item_num}, shopper_id: {self.shopper_id}, item_qty: {self.item_qty}, item_complete: {self.item_complete}"
serializers/item.py:
from rest_framework import serializers
from ..models.item import Item
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('id', 'item_name', 'item_location', 'item_class', 'shopper_id')
serializers/shopping_list.py:
from rest_framework import serializers
from ..models.shopping_list import ShoppingList
class ShoppingListSerializer(serializers.ModelSerializer):
class Meta:
model = ShoppingList
fields = ('id', 'item_num', 'shopper_id', 'item_qty', 'item_complete', 'added_on', 'list_num')
Getting ERROR AttributeError: Manager isn't accessible via ShoppingList instances when I execute the GET method in class ShoppingListItemView in views/shopping_lists.py below:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from django.shortcuts import get_object_or_404
from rest_framework.exceptions import PermissionDenied
from ..models.shopping_list import ShoppingList
from ..serializers.shopping_list import ShoppingListSerializer
from ..models.item import Item
from ..serializers.item import ItemSerializer
class ShoppingListsView(APIView):
def get(self, request, list_num):
shopping_items = ShoppingList.objects.filter(shopper_id=request.user.id)
shopping_list_items = shopping_items.filter(list_num=list_num)
data = ShoppingListSerializer(shopping_list_items, many=True).data
return Response(data)
def post(self, request):
request.data['shopper_id'] = request.user.id
list_item = ShoppingListSerializer(data=request.data, partial=True)
if list_item.is_valid():
list_item.save()
return Response(list_item.data, status=status.HTTP_201_CREATED)
else:
return Response(list_item.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, list_num):
shopping_items = ShoppingList.objects.filter(shopper_id=request.user.id)
shopping_list_items = shopping_items.filter(list_num=list_num)
response_data = shopping_list_items.delete()
return Response(response_data, status=status.HTTP_204_NO_CONTENT)
class ShoppingListsAllView(APIView):
def get(self, request):
shopping_items = ShoppingList.objects.filter(shopper_id=request.user.id)
data = ShoppingListSerializer(shopping_items, many=True).data
return Response(data)
class ShoppingListItemView(APIView):
def get(self, request, pk):
list_item = get_object_or_404(ShoppingList, pk=pk)
if request.user != list_item.shopper_id:
raise PermissionDenied('Unauthorized, this item belongs to another shopper')
else:
list_entry = list_item.objects.select_related('Item').get(id=pk)
print(list_entry)
data = ShoppingListSerializer(list_item).data
return Response(data)
def delete(self, request, pk):
list_item = get_object_or_404(ShoppingList, pk=pk)
if request.user != list_item.shopper_id:
raise PermissionDenied('Unauthorized, this item belongs to another shopper')
else:
list_item.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
def patch(self, request, pk):
list_item = get_object_or_404(ShoppingList, pk=pk)
if request.user != list_item.shopper_id:
raise PermissionDenied('Unauthorized, this item belongs to another shopper')
else:
request.data['shopper_id'] = request.user.id
updated_list_item = ShoppingListSerializer(list_item, data=request.data, partial=True)
if updated_list_item.is_valid():
updated_list_item.save()
return Response(updated_list_item.data)
else:
return Response(updated_item.errors, status=status.HTTP_400_BAD_REQUEST)