Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 :

  • Request : http POST http://127.0.0.1:8000/products/ product_name="Product test" product_location="Loc1" product_img="www.myimg.com"

  • Result : HTTP/1.0 201 Created Allow: POST, OPTIONS Content-Length: 247 Content-Type: application/json Date: Thu, 08 Mar 2018 13:58:18 GMT Server: WSGIServer/0.2 CPython/3.5.3 Vary: Accept X-Frame-Options: SAMEORIGIN

{ "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?

like image 416
Alk Avatar asked Mar 06 '18 15:03

Alk


2 Answers

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.

like image 116
neverwalkaloner Avatar answered Oct 10 '22 23:10

neverwalkaloner


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():
           ............
like image 23
Alk Avatar answered Oct 10 '22 23:10

Alk