0

Really struggling with this and it seems like it should be really straight forward. Basically, I am just trying to the products associated with a user, and then the short_name for each of those products to be rendered on the FE.

I have a table that has the user_id and prod_id. The table can look like this:

user_id | prod_id
-----------------
2       | 42
2       | 2
2       | 21
13      | 7
13      | 17
13      | 2

The user_id is a models.ForeignKey to the User table and the prod_id is a models.ForeignKey to Products table. Better yet, here is the model:

# ./home/models.py
from django.contrib.auth import get_user_model
from django.db import models

User = get_user_model()

class Product(models.Model):
    id = models.AutoField(primary_key=True)
    code = models.CharField(unique=True, max_length=16)
    name = models.CharField(max_length=255, blank=True, null=True)
    short_name = models.CharField(max_length=128)
    updated = models.DateTimeField(blank=True, null=True)
    created = models.DateTimeField(blank=True, null=True)   

    def __str__(self):
        return self.short_name

    class Meta:
        db_table = 'Product'

class UserToProduct(models.Model):
    user = models.ForeignKey(User, related_name='user_name', db_column='user_id', null=True)
    prod = models.ForeignKey(Product, related_name='prod_name', db_column='prod_id', null=True)

    class Meta:
        db_table = 'User_to_Product'
        unique_together = ('user', 'prod')

What should be happening is the React front-end sends the user_id based on who is logged in to the Django/DRF back-end. This is happening just fine. Here is the related view:

# ./home/views.py
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.status import HTTP_200_OK, HTTP_400_BAD_REQUEST
from rest_framework.views import APIView

from .serializers import GetProductSerializer
from .models import Home

class GetHomeAPIView(APIView):
    permission_classes = [IsAuthenticated]

    def post(self, request, *args, **kwargs):
        serializer = GetProductSerializer(data=request.data)
        if serializer.is_valid(raise_exception=True):
            return Response(serializer.data, status=HTTP_200_OK)
        return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)

From there, it should just be pulling the list of products related to them and then reading in the short_name from the Product model, or really just an object where I can parse out the short_name.

Here is the serializer:

# ./home/serializers.py
from rest_framework.serializers import (
    IntegerField,
    ModelSerializer, 
    SerializerMethodField,
    StringRelatedField, 
)

from .models import *

class GetProductSerializer(ModelSerializer):
    prod_name = StringRelatedField(many=True, required=False)
    product = SerializerMethodField('get_products')

    class Meta:
        model = UserToProduct
        fields = ('user', 'prod', 'prod_name')

    def get_products(self, obj):
        products = UserToProduct.objects.filter(user=data['user'])
        return products

This particular variation results in: NameError: name 'data' is not defined.

Change get_products() to have:

# for testing reasons
products = UserToProduct.objects.filter(user=2) 

This results in: KeyError: 'prod_name'

Anyway, I have spent more hours on this than I care to admit. This is some really basic stuff. I've tried dozens of variations. I'll keep working on it and posting what I've tried and the errors.

Also, this is the documentation I am referencing that I have read through dozens of times:

http://www.django-rest-framework.org/api-guide/relations/

http://www.django-rest-framework.org/tutorial/5-relationships-and-hyperlinked-apis/

Referenced countless other SO question/answers regarding this too and just not working.

EDIT: Probably have tried a few dozen more things and able to finally get a serialized response from the server; however, it isn't bringing in the short_name. Only modifications I have made:

# ./home/models.py
class Product(models.Model)
    ...
    users = models.ManyToManyField(User, through='UserToProduct')

# ./home/serializers.py
class GetProductSerializer(ModelSerializer):
    user = IntegerField() # have to do this otherwise it returns the username and not the id
    prod = SerializerMethodField()

    class Meta:
        model = UserToProduct
        fields = [
            'user',
            'prod',

        ]

    def get_prod(self, obj):
        prod = UserToProduct.objects.filter(user=obj['user']).values('prod')
        print(prod)
        return prod

Also, important to note is just leaving the get_prod() as:

prod = UserToProduct.objects.filter(user=obj['user'])

Results in a : TypeError: Object of type 'UserToProduct' is not JSON serializable. However, it does list the short_name in print(prod).

cjones
  • 8,384
  • 17
  • 81
  • 175

1 Answers1

0

Ok, finally think I got it working. It is incredibly slow though and takes about 5 seconds to return the information which really doesn't seem right. That is way too long for the amount of data.

Anyway, this is what got it working for me:

# ./home/views.py
...
def post(self, request, *args, **kwargs):
    user = request.data['user']
    serializer = GetProductSerializer(UserToProduct.objects.filter(user=user).prefetch_related('prod'), many=True)
    return Response(serializer.data)

# ./home/serializers.py
class GetProductSerializer(ModelSerializer):
    prod = CharField() # otherwise it was using prod_id number

    class Meta:
        model = UserToProduct
        fields = [
            'prod'
        ]

Way more simple than I was trying... just got out to sort out the speed issue.

EDIT: Fixed the speed issue thanks to the help of this SO post:

Optimizing database queries in Django REST framework

Modified my query to the following:

UserToProduct.objects.filter(user=user).prefetch_related('prod')

And it went down from 4.75 seconds to 1.5 seconds. Yee-haw.

cjones
  • 8,384
  • 17
  • 81
  • 175