Reputation: 688
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?
Upvotes: 5
Views: 10029
Reputation: 21
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)**
Upvotes: -1
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
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