Alk
Alk

Reputation: 688

DRF check if an object already exist in DB when receiving a request

I have products defined by their Name and Location. Each product has a unique pair of Name/Location.

I'm writing a view to be able to create a product and I would like to first check if it exists in DB. If yes then keep the ID somewhere to return it to my front app. If no then create it and get the ID also.

From my research, overriding the perform_create method should be the solution but I can't figure out how to.

Any help would be appreciated.

urls.py

from django.conf.urls import url
from main.views import product_view

urlpatterns = [
    url(r'^products/$', product_view.ProductCreate.as_view()),
    url(r'^products/(?P<pk>[0-9]+)/$', product_view.ProductDetail.as_view()),
]

product_view.py

from rest_framework import generics
from rest_framework import permissions

from main.models import Product
from main.serializers import ProductSerializer


class ProductCreate(generics.CreateAPIView):
    """
    Create a new product.
    """

    permission_classes = (permissions.IsAuthenticated,)

    serializer_class = ProductSerializer
    queryset = Product.objects.all()

    def perform_create(self, serializer):
    if serializer.is_valid():
        product_name = serializer.validated_data['product_name']
        product_location = serializer.validated_data['product_location']

        if product_name != '':
            product_list = Product.objects.filter(
                product_name=product_name, product_location=product_location)

            if not product_list:
                product = create_product(product_name, product_location)
            else:
                product = product_list[0]

            serializer = ProductSerializer(product)
            return Response(serializer.data)
        else:
            return Response(data={'message': 'Empty product_name'}, status=status.HTTP_400_BAD_REQUEST)
    else:
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class ProductDetail(generics.RetrieveUpdateAPIView):
    """
    Retrieve, update or delete a product.
    """

    permission_classes = (permissions.IsAuthenticated,)

    serializer_class = ProductSerializer
    queryset = Product.objects.all()

serializer.py

from django.contrib.auth.models import User
from rest_framework import serializers
from main.models import Product


class ProductSerializer(serializers.ModelSerializer):

    class Meta:
        model = Product
        fields = ('id',
              'product_name',
              'product_shop',
              'product_url',
              'product_img',
              'product_location')

EDIT

product model :

class Product(models.Model):
    product_name = models.CharField(max_length=200, blank=True)
    product_shop = models.CharField(max_length=200, blank=True)
    product_url = models.CharField(max_length=400, blank=False)
    product_img = models.CharField(max_length=400, blank=True)
    product_location = models.CharField(max_length=200, blank=False)
    product_creation_date = models.DateTimeField(default=datetime.now, blank=True)
    productgroup = models.ForeignKey(ProductGroup, blank=True, null=True, on_delete=models.CASCADE)

    def __str__(self):
        return '#' + str(self.pk) + ' ' + self.product_name + ' (' + self.product_shop + ')'

A product is automatically created depending on its name and location. A specific function is handling the creation and fulfill the data.

The result I get in my front app is missing some data using this code. Here is an example using httpie :

{ "product_location": "Loc1", "product_name": "Product test", "product_img": "www.myimg.com" }

In DB the product exists and has values for product_shop and product_url, and of course has an ID.

EDIT 2

I did some more test and logged as many things as possible.

Here is my perform_create function and the results from the logger :

def perform_create(self, serializer):
        if serializer.is_valid():
            product_name = serializer.validated_data['product_name']
            product_location = serializer.validated_data['product_location']

            if product_name != '':
                product_list = Product.objects.filter(
                    product_name=product_name, product_location=product_location)

                if not product_list:
                    product = create_product(product_name, product_location)
                else:
                    product = product_list[0]

                logger.info('product id : ' + str(product.id)) # Generated automatically
                logger.info('product name : ' + product.product_name) # From the request
                logger.info('product url : ' + product.product_url) # Generated by my create_product function

                serializer = ProductSerializer(product)

                logger.info('serializer.data['id'] : ' + str(serializer.data['id']))
                return Response(serializer.data)
            else:
                return Response(data={'message': 'Empty product_name'}, status=status.HTTP_400_BAD_REQUEST)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Here are the results and they are good :

product id : 3713

product name : Product 1

product url : /products/Product1/...

serializer.data['id'] : 3713

In the result of the request I now have only the product_url and the product_location....

Is there any other way to achieve what I want?

Upvotes: 5

Views: 10029

Answers (3)

MR.NECRO
MR.NECRO

Reputation: 21

DRF Check if an object already exist or not and perform an action if it exists

Example: if you do not want an user to like the same post twice, then follow the steps below

Note:- This post is an example of viewsets.ModelViewSet, You can use the below create() method to check for an object if it exists already or not, also can perform an action if it does exist, many people confused whether to use perform_create() or create(), Well straight on point i would say with this use case use create(),

class PostLikeViewSet(viewsets.ModelViewSet):
queryset = PostLike.objects.all()
serializer_class = PostLikeSerializer
pagination_class = CustomPagination
filter_backends = [filters.OrderingFilter,filters.SearchFilter]
ordering_fields = ["created_at"]
ordering = ["-created_at"]
search_fields = ["post_id","like_id","user_id"]

**def create(self, request, *args, **kwargs):
    if PostLike.objects.filter(post_id=request.data['post_id'],user_id=request.data['user_id']):
        return Response({"Error" : "You've already liked this post!"},status=status.HTTP_400_BAD_REQUEST)
    else:
        return super().create(request, *args, **kwargs)**

Thanks for looking at this, let me know in the comments if you stuck anywhere :)

Upvotes: -1

Alk
Alk

Reputation: 688

Ok so I solved my problem NOT using perform_create. I came back to an easy APIView and defined the POST method as wanted. Works perfectly now.

class ProductCreate(APIView):
    """
    Create a new product.
    """

    permission_classes = (permissions.IsAuthenticated,)

    def post(cls, request, format=None):
        serializer = ProductSerializer(data=request.data)
        if serializer.is_valid():
           ............

Upvotes: 1

neverwalkaloner
neverwalkaloner

Reputation: 47354

You need to check is serializer is valid at first. Then you can call serializer.save() to create new object, or just create new serializer object and pass to it already existing product:

def perform_create(self, serializer):
    if serializer.is_valid():
        product_name = serializer.validated_data['product_name']
        product_location = serializer.validated_data['product_location']

        if product_name != '':
            product_list = Product.objects.filter(
                product_name=product_name, product_location=product_location)

            if not product_list:
                product = serializer.save()
            else:
                product = product_list[0]
                serializer = ProductSerializer(product)
            return Response(serializer.data)   
        else:
            return Response(data={'message': 'Empty product_name'},
                        status=status.HTTP_400_BAD_REQUEST)
    else:
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

If serializer's data is not valid you have to return serializer.errors.

Upvotes: 4

Related Questions