Related
I'm using the djangorestframework module to set up an API to update/read my models. I have these models ...
from django.db import models
from address.models import AddressField
from phonenumber_field.modelfields import PhoneNumberField
from address.models import State
from address.models import Country
class CoopTypeManager(models.Manager):
def get_by_natural_key(self, name):
return self.get_or_create(name=name)[0]
class CoopType(models.Model):
name = models.CharField(max_length=200, null=False)
objects = CoopTypeManager()
class Meta:
unique_together = ("name",)
class Coop(models.Model):
name = models.CharField(max_length=250, null=False)
type = models.ForeignKey(CoopType, on_delete=None)
address = AddressField(on_delete=models.CASCADE)
enabled = models.BooleanField(default=True, null=False)
phone = PhoneNumberField(null=True)
email = models.EmailField(null=True)
web_site = models.TextField()
and I have these view classes ...
class CoopList(APIView):
"""
List all coops, or create a new coop.
"""
def get(self, request, format=None):
coops = Coop.objects.all()
serializer = CoopSerializer(coops, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = CoopSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class CoopDetail(APIView):
"""
Retrieve, update or delete a coop instance.
"""
def get_object(self, pk):
try:
return Coop.objects.get(pk=pk)
raise Http404
def get(self, request, pk, format=None):
coop = self.get_object(pk)
serializer = CoopSerializer(coop)
return Response(serializer.data)
def put(self, request, pk, format=None):
coop = self.get_object(pk)
serializer = CoopSerializer(coop, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
coop = self.get_object(pk)
coop.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
Problem is, I would like to submit JSON like this
{
"name": "1872",
"type": {
"name": "Coworking Space"
},
with the intention being that for the dependent CoopType model, I would either create one of use an existing one before creating my Coop model. However, right now, submitting the above results in a 400 response ...
{"type":["Incorrect type. Expected pk value, received dict."]
How should I modify my view class to accommodate what I'm trying to do?
Edit: the serializers ...
from rest_framework import serializers
from maps.models import Coop, CoopType
from address.models import Address, AddressField, Locality, State, Country
class CoopSerializer(serializers.ModelSerializer):
class Meta:
model = Coop
fields = ['id', 'name', 'type', 'address', 'enabled', 'phone', 'email', 'web_site']
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['type'] = CoopTypeSerializer(instance.type).data
rep['address'] = AddressSerializer(instance.address).data
return rep
def create(self, validated_data):
"""
Create and return a new `Snippet` instance, given the validated data.
"""
return Coop.objects.create(**validated_data)
def update(self, instance, validated_data):
"""
Update and return an existing `Coop` instance, given the validated data.
"""
instance.name = validated_data.get('name', instance.name)
instance.type = validated_data.get('type', instance.type)
instance.address = validated_data.get('address', instance.address)
instance.enabled = validated_data.get('enabled', instance.enabled)
instance.phone = validated_data.get('phone', instance.phone)
instance.email = validated_data.get('email', instance.email)
instance.web_site = validated_data.get('web_site', instance.web_site)
instance.web_site = validated_data.get('web_site', instance.web_site)
instance.save()
return instance
class CoopTypeSerializer(serializers.ModelSerializer):
class Meta:
model = CoopType
fields = ['id', 'name']
def create(self, validated_data):
"""
Create and return a new `CoopType` instance, given the validated data.
"""
return CoopType.objects.create(**validated_data)
def update(self, instance, validated_data):
"""
Update and return an existing `CoopType` instance, given the validated data.
"""
instance.name = validated_data.get('name', instance.name)
instance.save()
return instance
Edit 2: The curl request ...
#!/bin/bash
read -d '' req << EOF
{
"name": "1872",
"type": {
"name": "Coworking Space"
},
"address": {
"id": 1,
"street_number": "222",
"route": "1212",
"raw": "222 W. Merchandise Mart Plaza, Suite 1212",
"formatted": "222 W. Merchandise Mart Plaza, Suite 1212",
"latitude": 41.88802611,
"longitude": -87.63612199,
"locality": {
"id": 29,
"name": "Chicago",
"postal_code": "60654",
"state": {
"id": 1,
"name": "IL",
"code": "",
"country": {
"id": 484,
"name": "United States",
"code": "US"
}
}
}
},
"enabled": true,
"phone": null,
"email": null,
"web_site": "http://www.1871.com/"
}
EOF
echo $req
curl -v --header "Content-type: application/json" --data "$req" --request POST "http://127.0.0.1/coops/"
This should work:
class CoopTypeField(serializers.PrimaryKeyRelatedField):
queryset = CoopType.objects
def to_internal_value(self, data):
if type(data) == dict:
cooptype, created = CoopType.objects.get_or_create(**data)
# Replace the dict with the ID of the newly obtained object
data = cooptype.pk
return super().to_internal_value(data)
class CoopSerializer(serializers.ModelSerializer):
type = CoopTypeField()
# ... the rest of this class is unchanged
Where the changes are:
Define a custom CoopTypeField(). The to_internal_value() method would ordinarily be expecting an ID - so we override it to accept data in the form of a dictionary and convert this to an ID (by getting or creating a CoopType) and then pass this ID to the parent class method.
Define a type on the CoopSerializer that uses this new CoopTypeField().
Now your CoopSerializer will accept data in two forms:
{'name': 'Coop 1', 'web_site': 'http://example.com', 'type': {'name': 'Coop Type 1'}}
or
{'name': 'Coop 1', 'web_site': 'http://example.com', 'type': 1}
(I've omitted the other fields that Coop requires for brevity).
Based on the current serializer, the CoopType needs to be made ahead of time (or fetched ahead of time if it exists) so that the id can be passed along. If you want to still make the CoopType with nested data in the Coop serializer (or use it to find the CoopType to use), Django Rest Framework talks about how in their docs under Writable nested serializers.
Oh boy... There is so much we could improve upon here. So let's start with your question first.
#tredzko(https://stackoverflow.com/a/60310659/3627387) was right and you should look into https://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers
By the look of your CoopType model I see that name field is unique.
from rest_framework import serializers
from maps.models import Coop, CoopType
from address.models import Address, AddressField, Locality, State, Country
class CoopTypeSerializer(serializers.ModelSerializer):
class Meta:
model = CoopType
fields = ['id', 'name']
class CoopSerializer(serializers.ModelSerializer):
# type field should be defined here instead of `to_representation`
type = CoopTypeSerializer()
class Meta:
model = Coop
fields = ['id', 'name', 'type', 'address', 'enabled', 'phone', 'email', 'web_site']
def to_representation(self, instance):
# this is the correct way of extending to_representation
# we set update self.fields and after that
# Serializer class handles everything automatically
self.fields['address'] = AddressSerializer(read_only=True)
return super().to_representation(instance)
def validate_type(self, value):
coop_type, __ = CoopType.objects.get_or_create(**coop_type_data)
return coop_type
Something along this lines should work.
Now let's dive into other improvements
I suggest using ModelViewSet(https://www.django-rest-framework.org/api-guide/viewsets/#modelviewset)
So you could do this(see below) instead of your views code
from rest_framework import viewsets
class CoopViewSet(viewsets.ModelViewSet):
serializer_class = CoopSerializer
queryset = Coop.objects.all().select_related('type')
Your CoopType model is defined in an odd way, I think you wanted to actually do this.
class CoopType(models.Model):
# https://docs.djangoproject.com/en/3.0/ref/models/fields/#unique
name = models.CharField(max_length=200, null=False, unique=True)
class Meta:
pass
I am following this solution on how to get specific fields from a django model:
Select specific fields in Django get_object_or_404
from django.core import serializers as djangoserializer # module 'rest_framework.serializers' has no attribute 'serialize'
class ProjectDetailApiView(APIView):
authentication_classes = (authentication.SessionAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
def get(self, request, slug=None, format=None):
project_instance = get_object_or_404(Project.objects.only('project_title', 'project_post'), slug=slug)
data = djangoserializer.serialize('json', [ project_instance, ], fields=('project_title','project_post'))
user = self.request.user
updated = False
viewed = False
if not user in project_instance.project_views.all():
viewed = True
project_instance.project_views.add(user)
updated = True
data = {
"project": data,
"updated":updated,
"viewed":viewed
}
return Response(data)
Output:
{
"project": "[{\"model\": \"webdata.project\", \"pk\": 4, \"fields\": {\"project_title\": \"Project 4\", \"project_post\": \"Blabla\"}}]",
"updated": true,
"viewed": false
}
Desired Output:
{
"project_title": "Project 4",
"project_post": "Blabla",
"updated": true,
"viewed": false
}
Thank you
Use DRF's Serializer instead of Django's built-in serializer.
# serializers.py
from rest_framework import serializers
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = ('project_title', 'project_post', 'updated', 'viewed')
# views.py
class ProjectDetailApiView(APIView):
authentication_classes = (authentication.SessionAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
def get(self, request, slug=None, format=None):
project_instance = get_object_or_404(Project, slug=slug)
serializer = ProjectSerializer(project_instance)
return Response(serializer.data)
The serialization of regular dictionaries is very good in python.
So instead of configuring the serializer - why not just create a python dictionary with the desired data? (That's how I do it for simple things that I need only in one place.)
data = {
"project_title": project_instance.project_title,
"project_post": project_instance.project_post,
"updated":updated,
"viewed":viewed
}
return JSONResponse(data)
You haven't posted the Project model, I'm just supposing from the serializer config that the fields are named project_title and project_post.
This will return a response with status 200, mimetype application/json and the data dict as valid JSON.
I'm a student studying django rest framework
I'm making a simple sns with django rest framework
I need follower-following system. So, I tried to make it but there is some trouble
First this is my user model with AbstractBaseUser and PermissionsMixin
class User(AbstractBaseUser, PermissionsMixin):
user_id = models.CharField(max_length=100, unique=True, primary_key=True)
name = models.CharField(max_length=100)
created_at = models.DateTimeField(auto_now_add=True)
is_staff = models.BooleanField(default=False)
followers = models.ManyToManyField('self', related_name='follower',blank=True)
following = models.ManyToManyField('self', related_name='following',blank=True)
profile_image = models.ImageField(blank=True)
the field followers is people who follows me and following is whom i follow
When i add following with this APIView class
class AddFollower(APIView):
permission_classes = [IsAuthenticated, ]
def post(self, requset, format=None):
user = User.objects.get(user_id=self.request.data.get('user_id'))
follow = User.objects.get(user_id=self.request.data.get('follow'))
user.following.add(follow)
user.save()
follow.followers.add(user)
follow.save()
print(str(user) + ", " + str(follow))
return JsonResponse({'status':status.HTTP_200_OK, 'data':"", 'message':"follow"+str(follow.user_id)})
The user_id is me and the follow is whom i want to follow
I want to add follow to user_id's following field and add user_id to follow's followers field
But it does not work
What i want for result is like this (with user information api)
{
"followers": [],
"following": [
"some user"
],
}
some user's user info
{
"followers": [
"user above"
],
"following": [
],
}
But real result is like this
{
"followers": [
"some user"
],
"following": [
"some user"
],
}
some user's user info
{
"followers": [
"user above"
],
"following": [
"user above"
],
}
this is not what i want
I have no idea with this problem i need some help
Thank you
I would design it in different way.
I would not add the information to the User model but explicitly create another table to store information about "followers" and "following".
Schema of the table would be:
class UserFollowing(models.Model):
user_id = models.ForeignKey("User", related_name="following")
following_user_id = models.ForeignKey("User", related_name="followers")
# You can even add info about when user started following
created = models.DateTimeField(auto_now_add=True)
Now, in your post method implementation, you would do only this:
UserFollowing.objects.create(user_id=user.id,
following_user_id=follow.id)
And then, you can access following and followers easily:
user = User.objects.get(id=1) # it is just example with id 1
user.following.all()
user.followers.all()
And you can then create constraint so user cannot follow the same user twice. But i leave this up to you ( hint: unique_together )
The above solutions are fine and optimal, but I would like to supply a detailed solution for anyone who wants to implement such functionality.
The intermediary Model
from django.contrib.auth import get_user_model
UserModel = get_user_model()
class UserFollowing(models.Model):
user_id = models.ForeignKey(UserModel, related_name="following", on_delete=models.CASCADE)
following_user_id = models.ForeignKey(UserModel, related_name="followers", on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True, db_index=True)
class Meta:
constraints = [
models.UniqueConstraint(fields=['user_id','following_user_id'], name="unique_followers")
]
ordering = ["-created"]
def __str__(self):
f"{self.user_id} follows {self.following_user_id}"
THE SERIALIZER for follow and unfollow
Your View for follow and unfollow
class UserFollowingViewSet(viewsets.ModelViewSet):
permission_classes = (IsAuthenticatedOrReadOnly,)
serializer_class = UserFollowingSerializer
queryset = models.UserFollowing.objects.all()
Custom FollowersSerializer and FollowingSerializer
class FollowingSerializer(serializers.ModelSerializer):
class Meta:
model = UserFollowing
fields = ("id", "following_user_id", "created")
class FollowersSerializer(serializers.ModelSerializer):
class Meta:
model = UserFollowing
fields = ("id", "user_id", "created")
Your UserSerializer
from django.contrib.auth import get_user_model
User = get_user_model()
class UserSerializer(serializers.ModelSerializer):
following = serializers.SerializerMethodField()
followers = serializers.SerializerMethodField()
class Meta:
model = User
fields = (
"id",
"email",
"username",
"following",
"followers",
)
extra_kwargs = {"password": {"write_only": True}}
def get_following(self, obj):
return FollowingSerializer(obj.following.all(), many=True).data
def get_followers(self, obj):
return FollowersSerializer(obj.followers.all(), many=True).data
models.py
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
followers = models.ManyToManyField('self', symmetrical=False,
blank=True)
def count_followers(self):
return self.followers.count()
def count_following(self):
return User.objects.filter(followers=self).count()
I created a follow and unfollow system similar to Instagram.
Functionality:
If private account enable --> send follow request.
If user in block list --> they can not follow of opposite user.
User can check pending request, sended request list,blocked user
list,etc.
Let's start :
Models.py:
class Profile(models.Model):
user = models.OneToOneField(to = User,on_delete=models.CASCADE,related_name='profile')
.......
private_account = models.BooleanField(default = False)
followers = models.ManyToManyField('self',blank=True,related_name='user_followers',symmetrical=False)
following = models.ManyToManyField('self',blank=True,related_name='user_following',symmetrical=False)
panding_request = models.ManyToManyField('self',blank=True,related_name='pandingRequest',symmetrical=False)
blocked_user = models.ManyToManyField('self',blank=True,related_name='user_blocked',symmetrical=False)
created_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return '%s' %(self.user)
Here you can follow, unfollow, send follow request, accept follow request,
decline request and you can remove your follower.
From the front end side pass opposite user profile id and type
of action(follow,unfollow..).
views.py:
class FollowUnfollowView(APIView):
permission_classes = [IsAuthenticated]
def current_profile(self):
try:
return Profile.objects.get(user = self.request.user)
except Profile.DoesNotExist:
raise Http404
def other_profile(self,pk):
try:
return Profile.objects.get(id = pk)
except Profile.DoesNotExist:
raise Http404
def post(self, request,format=None):
pk = request.data.get('id') # Here pk is opposite user's profile ID
req_type = request.data.get('type')
current_profile = self.current_profile()
other_profile = self.other_profile(pk)
if req_type == 'follow':
if other_profile.private_account:
other_profile.panding_request.add(current_profile)
return Response({"Requested" : "Follow request has been send!!"},status=status.HTTP_200_OK)
else:
if other_profile.blocked_user.filter(id = current_profile.id).exists():
return Response({"Following Fail" : "You can not follow this profile becuase your ID blocked by this user!!"},status=status.HTTP_400_BAD_REQUEST)
current_profile.following.add(other_profile)
other_profile.followers.add(current_profile)
return Response({"Following" : "Following success!!"},status=status.HTTP_200_OK)
elif req_type == 'accept':
current_profile.followers.add(other_profile)
other_profile.following.add(current_profile)
current_profile.panding_request.remove(other_profile)
return Response({"Accepted" : "Follow request successfuly accespted!!"},status=status.HTTP_200_OK)
elif req_type == 'decline':
current_profile.panding_request.remove(other_profile)
return Response({"Decline" : "Follow request successfully declined!!"},status=status.HTTP_200_OK)
elif req_type == 'unfollow':
current_profile.following.remove(other_profile)
other_profile.followers.remove(current_profile)
return Response({"Unfollow" : "Unfollow success!!"},status=status.HTTP_200_OK)
elif req_type == 'remove': # You can remove your follower
current_profile.followers.remove(other_profile)
other_profile.following.remove(current_profile)
return Response({"Remove Success" : "Successfuly removed your follower!!"},status=status.HTTP_200_OK)
# Here we can fetch followers,following detail and blocked user,pending request,sended request..
def patch(self, request,format=None):
req_type = request.data.get('type')
if req_type == 'follow_detail':
serializer = FollowerSerializer(self.current_profile())
return Response({"data" : serializer.data},status=status.HTTP_200_OK)
elif req_type == 'block_pending':
serializer = BlockPendinSerializer(self.current_profile())
pf = list(Profile.objects.filter(panding_request = self.current_profile().id).values('id','user__username','profile_pic','overall_pr'))
return Response({"data" : serializer.data,"Sended Request" :pf},status=status.HTTP_200_OK)
# You can block and unblock user
def put(self, request,format=None):
pk = request.data.get('id') # Here pk is oppisite user's profile ID
req_type = request.data.get('type')
if req_type == 'block':
self.current_profile().blocked_user.add(self.other_profile(pk))
return Response({"Blocked" : "This user blocked successfuly"},status=status.HTTP_200_OK)
elif req_type == 'unblock':
self.current_profile().blocked_user.remove(self.other_profile(pk))
return Response({"Unblocked" : "This user unblocked successfuly"},status=status.HTTP_200_OK)
serializers.py:
class EachUserSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username')
class Meta:
model = Profile
fields = ('id','username','profile_pic')
read_only_fields = ('id','username','profile_pic')
class FollowerSerializer(serializers.ModelSerializer):
followers = EachUserSerializer(many=True, read_only= True)
following = EachUserSerializer(many=True,read_only=True)
class Meta:
model = Profile
fields = ('followers','following')
read_only_fields = ('followers','following')
class BlockPendinSerializer(serializers.ModelSerializer):
panding_request = EachUserSerializer(many=True, read_only= True)
blocked_user = EachUserSerializer(many=True,read_only=True)
class Meta:
model = Profile
fields = ('panding_request','blocked_user')
read_only_fields = ('panding_request','blocked_user')
urls.py:
from django.urls.conf import path
from .views import *
urlpatterns = [
.....
path('follow_unfollow/',FollowUnfollowView.as_view(),name = "follow_unfollow"),
]
If any doubt of any step please comment. I will briefly describe.
This is how i solved my problem.
there is a good answer above, but someone need a detail for it. so i'm writing this
I removed field followers and following in User model. And Created new model UserFollowing.
You can see this model in Enthusiast Martin's answer. I didn't use any serializer to create object.
Just two views (Follow, UnFollow) were needed.
In Follow View
UserFollowing.objects.create(user_id=user.id, following_user_id=follow.id)
with this we can create Following-Follower relationship.
The way to use this relationship
In User Information View
following_data = UserFollowingSerializer(qs.following.all(), many=True)
followers_data = UserFollowingSerializer(qs.followers.all(), many=True)
return JsonResponse({'status':status.HTTP_200_OK, 'data':{'user':serializer.data, 'following':following_data.data, 'followers':followers_data.data}, "message":"success"})
I usually use JsonResponse for response.
serializer.data and qs are user object
In UserFolowingSerializer
fields = '__all__'
I used this.
Very good answers. For me it would be either #GST Talib's answer or a very similar one without using self
following = models.ManyToManyField('User', blank=True, related_name='followers')
That way when you add user1.following.add(user2) you can see the relationship being reflected in user2.followers
And no need for extra code.
My models are:
class User(models.Model):
id = models.AutoField(primary_key=True)
email = models.EmailField()
class Lawyer(models.Model):
user = models.OnetoOneField(User)
class Session(models.Model):
name = models.CharField()
lawyer = models.ForeignKey(Lawyer)
I am trying to create multiple objects with a list serializer for Session.
From the app side they don't have the id of lawyer, but have the email of each lawyer. How can I write a list serializer where I can take the following json input and use email to fetch lawyer to store multiple session objects?
The json input sent will be like:
[
{
"name": "sess1",
"email": "lawyer1#gmail.com",
},
{
"name": "sess1",
"email": "lawyer1#gmail.com",
}
]
You can do it in this way but I think email should be unique=True.
Then use a serializer like this:
from django.utils.translation import ugettext_lazy as _
class SessionCreateManySerializer(serializers.ModelSerializer):
email = serializers.SlugRelatedField(
source='lawyer',
slug_field='user__email',
queryset=Lawyer.objects.all(),
write_only=True,
error_messages={"does_not_exist": _('lawyer with email={value} does not exist.')}
)
class Meta:
model = Session
fields = ('name', 'email')
and a generic create view (just override get_serializer and place many=True in kwargs ):
from rest_framework.response import Response
from rest_framework import generics
class SessionCreateManyView(generics.CreateAPIView):
serializer_class = SessionCreateManySerializer
def get_serializer(self, *args, **kwargs):
kwargs['many'] = True
return super(SessionCreateManyView, self).get_serializer(*args, **kwargs)
You can use bulk creation as well:
# serializers.py
from myapp.models import Session
from rest_framework import serializers
class SessionSerializer(serializers.Serializer):
email = serializers.EmailField(required=True)
name = serializers.CharField(required=True)
def validate_email(self, email):
lawyer = Lawyer.objects.filter(user__email=email).first()
if not lawyer:
raise ValidationError(detail="user dose not exist.")
return lawyer
def create(self, validated_data):
return Session.objects.create(name=validated_data.get('name'), lawyer=validated_data.get('email'))
and in your api.py file allow bulk creation:
# api.py
from rest_framework import generics
class SessionCreateAPIView(generics.CreateAPIView):
"""Allows bulk creation of a resource."""
def get_serializer(self, *args, **kwargs):
if isinstance(kwargs.get('data', {}), list):
kwargs['many'] = True
return super().get_serializer(*args, **kwargs)
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?
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.
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():
............