I need to update my user in REST framework
views.py
class UserUpdate(APIView):
permission_classes = (permissions.IsAuthenticated,)
def post(self,request):
user=User.objects.get(id=request.user.id)
try:
user_serializer=UserSerializer(request.user,data=request.data, partial=True)
if user_serializer.is_valid():
user_serializer.save()
return Response(user_serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(user_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
except:
return JsonResponse({'status':0,'message':'Error on user update'})
serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'first_name', 'last_name', 'avatar']
models.py
class User(AbstractUser):
fb_userid = models.CharField(max_length=256)
avatar = models.ImageField(upload_to='avatars/', blank=True, null=True)
response:
DETAIL: Key (username)=() already exists.
def post(self,request):
user_serializer=UserSerializer(request.user, data=request.data, partial=True)
if user_serializer.is_valid():
user_serializer.save()
return Response(user_serializer.data, status=status.HTTP_200_OK)
else:
return Response(user_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
#YKH is right this code is good there may error in your POST data.
In your image, you are passing two parameters in Header. It could possible you are passing the wrong header.
Content-Type should not be for form-data
I found this post where someone has a similar problem as you: Django Rest Framework unable to parse multipart/form data
It seems on your picture that you are putting something into Headers tab. Postman is taking care of that for you, so you shouldn't define anything there. Could you try again without setting anything in the headers?
Related
I was remaking a social media site as a revision of Django and the rest framework, I didn't want to use the django default linear id count and didn't like how long the uuid library's ids was, so I used the shortuuid library. I've used them on the posts and the comments just to keep the anonymity of the count of both posts and comments. On the posts side everything works for the CRUD stuff (which should be proof that the issue isn't from the shortuuid library, as far as I know), although with the comments the Create Retrieve works perfectly but the Update Destroy doesn't. so here is the code we are working with:
starting with the models to know what kind of data we are working with (models.py):
from shortuuid.django_fields import ShortUUIDField
... # posts likes etc
class Comment(models.Model):
id = ShortUUIDField(primary_key=True, length=8, max_length=10)
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
body = models.TextField(max_length=350)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
active = models.BooleanField(default=True)
class Meta:
ordering = ['created']
def __str__(self):
return f'on {self.post} by {self.user}'
objects = models.Manager()
serializers.py:
class CommentSerializer(ModelSerializer):
username = SerializerMethodField()
def get_username(self, comment):
return str(comment.user)
class Meta:
model = Comment
fields = ['id', 'user', 'post', 'username', 'body', 'created', 'updated']
read_only_fields = ['id', 'post', 'user', 'username']
now with the routing (urls.py):
from django.urls import path
from .views import *
urlpatterns = [
...
path('<str:pk>/comments/' , Comments),
path('<str:pk>/comments/create/', CreateComment),
path('<str:pk>/comments/<str:cm>/', ModifyComment),
# pk = post ID
# cm = comment ID
]
views.py:
class ModifyComment(generics.RetrieveUpdateDestroyAPIView):
serializer_class = CommentSerializer
permission_classes = [permissions.AllowAny]
def get_queryset(self):
post = Post.objects.get(pk=self.kwargs['pk'])
comment = Comment.objects.get(post=post, pk=self.kwargs['cm'])
return comment
def perform_update(self, serializer):
print(Post.objects.all())
post = Post.objects.get(pk=self.kwargs['pk'])
comment = Comment.objects.filter(pk=self.kwargs['cm'], post=post)
if self.request.user != comment.user:
raise ValidationError('you can\'t edit another user\'s post')
if comment.exists():
serializer.save(user=self.request.user, comment=comment)
else:
raise ValidationError('the comment doesnt exist lol')
def delete(self, request, *args, **kwargs):
comment = Comment.objects.filter(user=self.request.user, pk=self.kwargs['cm'])
if comment.exists():
return self.destroy(request, *args, **kwargs)
else:
raise ValidationError("you can\'t delete another user\'s post")
ModifyComment = ModifyComment.as_view()
and the response to going to the url '<str:pk>/comments/<str:cm>/' comment of some post we get this:
side note, the perform_update function doesn't seem to be called ever, even putting a print statement at the beginning of the function doesn't get printed so the issue may have to do with the get_queryset even though I've tried using the normal queryset=Comment.object.all() and making the get_queryset function return the comment with the correct params but I couldn't make it work
For individual objects you need to overwrite the get_object method.
You are performing the request GET /str:pk/comments/str:cm/, this calls the retrieve method on the view, which in turn calls get_object. The default behaviour is trying to find a Comment object with id equal to pk since it's the first argument, since you need to filter through a different model you need to overwrite it.
classy drf is a good website for seing how the internals of the clases work.
Models.py
from django.contrib.auth.models import User
class Conversation(models.Model):
participants = models.ManyToManyField(User, related_name="conversation")
Urls.py
path('api/conversations/', views.ConversationListView.as_view(),
name='conversation-list'),
Serializers.py
class ConversationSerializer(serializers.ModelSerializer):
participants = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(),
many=True)
class Meta:
model = Conversation
fields = ['id', 'participants']
Views.py
class ConversationListView(APIView):
def get(self, request, format=None):
conversations = Conversation.objects.all()
serializer = ConversationSerializer(
conversations, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = ConversationSerializer(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)
So I would like to make a POST request on "api/conversations/" with this object:
{"participants": [2,3]} or {"participants": [3,2]}
BUT I would like to check first whether a conversation with participants are both User(id=2) and User(id=3) exist or not. If exists, I need to raise an error. If not exist, create a new conversation. So there is only one conversation between User(id=2) and User(id=3).
What I know so far is I have to make validate_participants(self, value) in the serializer. But I still can't figure out what is the logic to check it. I've tried using Conversation.objects.filter(participants__in=[2,3]) but I think it doesn't work because it does not return the conversation object that has both User(id=2) and User(id=3) as participants.
This code solves my problem. Add this to ConversationSerializer
def validate_participants(self, value):
query1 = Conversation.objects.filter(participants=value[0])
query2 = Conversation.objects.filter(participants=value[1])
if query1.intersection(query2).exists():
raise serializers.ValidationError(
"Conversation with these participants has exist")
return value
my models.py is :
class clients(models.Model):
client_id = models.IntegerField(unique=True, primary_key=True )
'
'
money = models.IntegerField(default = 0)
class transfermoney(models.Model):
first_client_id = models.IntegerField()
second_client_id = models.IntegerField()
amountofmoney = models.PositiveIntegerField()
time = models.TimeField(auto_now=True)
date = models.DateField(auto_now=True)
my serializers.py is :
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = clients
fields = ('__all__')
class moneytransfer(serializers.ModelSerializer):
class Meta:
model = transfermoney
fields = ('__all__')
my views.py is :
class transferingmoney(APIView):
def post(self,request):
serializer = moneytransfer(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)
i'm using django rest framework , and what i want is ,everytime when i make a post request into "transfermoney" model , it take the "first_client_id" and search for it into the "client_id" in the "clients" model and add the "amountofmoney" from "transfermoney" model to the "money" field in "clients" model , and the same for the "second_client_id"
please how can i do that ?
Ok, the question of how to do it completely correct is too big to discuss it here.
I will only answer your question.
But the thing I need to mention - store money amounts in Decimal fields, not in Integer - that's just bad practice. Something like:
models.DecimalField(max_digits=6, decimal_places=2)
will do. Now, to the subject of the question.
First of all, I suggest you add custom 'validate()' method in your moneytransfer serializer and add checks on the client_ids there. You are using integer fields (for some reason I guess) in your transfermoney model instead of Foreign Keys. Because of that, the validate method of the serializeк won't check for you if these ids actually exist, so it's better to add some validation. Then it will be called in your View and return False if your custom checks will fail. If I understood it correctly you want something like:
class BillingRecordSerializer(serializers.ModelSerializer):
def validate(self, data):
try:
clients.objects.get(client_id = data['first_client_id'])
clients.objects.get(client_id = data['second_client_id'])
except clients.DoesNotExist:
raise serializers.ValidationError("One of the clients does not exist")
return data
class Meta:
fields = ('__all__')
model = transfermoney
With this, you will be sure that both the clients are present in your DB.
Then, you can do something like:
class transferingmoney(APIView):
def post(self,request):
data=request.data
serializer = moneytransfer(data=data)
if serializer.is_valid():
serializer.save()
client_1 = clients.objects.get(client_id=data['first_client_id'])
client_2 = clients.objects.get(client_id=data['second_client_id'])
client_1.money -= data['amountofmoney']
client_2.money += data['amountofmoney']
client_1.save()
client_2.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
You can also create a separate method for the logic I have added in the view to make code clear and reusable. Personally i'd prefer to override save() method of transfermoney model to do it there. And then it will be automatically called on the first save() of this model. But it all depends on what place you will be more comfortable with and what is the logic behind your code.
And I want to mention this is obviously not the right way to deal with money transactions, but describing the right way is way over the topic of the question and also there are a lot of examples out there.
P.S. I wrote the code right here, so there could иу some typos in it.
I'd like to post to my Django server using post so I can add a todo item. Here is the model:
class Todo(models.Model):
title = models.CharField(max_length=200);
text = models.TextField()
completed = models.BooleanField(default=False)
created_at = models.DateTimeField(default=datetime.now, blank = True )
def __str__(self):
return self.title
And serializers:
class TodoSerializer(serializers.ModelSerializer):
class Meta:
model = Todo
fields = ("id", 'title','text', 'completed', 'created_at')
And view:
class TodoList(APIView):
def get(self,request):
todo=Todo.objects.all()
serializer=TodoSerializer(todo,many=True)
return Response(serializer.data)
def post(self,request):
Todo.objects.create(
title=request.POST.get('title'),
text=request.POST.get('text'))
return HttpResponse(status=201)
My post request is
{ "title": "new title",
"text": "a test text"}
And it told me
IntegrityError at /todos/
(1048, "Column 'title' cannot be null")
As a newbie at Django, I don't understand this error. Any ideas?
You need to access request.data instead of request.POST,
def post(self,request):
serializer = TodoSerializer(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)
Since you've asked about other methods besides post in the comments, I'll show an example of a ModelViewSet that will allow you to post to add a Todo, as well as provide support for retrieving, updating, and deleting your Todo's.
Recommended reading:
http://www.django-rest-framework.org/api-guide/viewsets/#modelviewset
from rest_framework.viewsets import ModelViewSet
from todo.models import Todo
from todo.serializers import TodoSerializer
class TodoViewSet(ModelViewSet):
queryset = Todo.objects.all()
serializer_class = TodoSerializer
The ModelViewSet class will provide you with a default implementation of view methods to list, create, retrieve, update (whole or partial update), and delete Todo's. These actions are mapped to certain methods for different urls, get is mapped to list and retrieve, post is mapped to create, put and patch are mapped to update and partial_update, and delete is mapped to destroy.
Then in your urls.py, include the TodoViewSet using TodoViewSet.as_view(...):
from django.conf.urls import url
from todo.views import TodoViewSet
urlpatterns = [
url(
r'^todos/$',
TodoViewSet.as_view({'get': 'list', 'post': 'create'}),
name='todo-list',
),
url(
r'^todos/(?P<pk>\d+)/$',
TodoViewSet.as_view({'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'}),
name='todo-detail',
)
]
Here we are explicitly stating the mapping of request methods to view actions that I mentioned before.
Instead of creating like this, You can always use serializers for the same
data_serializer = TodoSerializer(data=request.data)
if data_Serializer.is_valid():
data_Serializer.save()
for put request :
todo_item = Todo.objects.get(id=id) // Need to get that element
data_serializer = TodoSerializer(instance=todo_item,data=request.data, partial=True)
if data_Serializer.is_valid():
data_Serializer.save()
else:
print data_Serializer.errors
for delete:
todo_item = Todo.objects.get(id=id) // Need to get that element
todo_item.delete()
I'd like to create a SpiderService with a foreign key request.user.
models:
from django.contrib.auth.models import User
class SpiderService(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=20)
serializers:
class SpiderServiceSerializer(serializers.ModelSerializer):
user = serializers.ReadOnlyField(source='user.id')
class Meta:
model = SpiderService
fields = ('id', 'user', 'name')
class SpiderServiceListSerializer(viewsets.ModelViewSet):
queryset = SpiderService.objects.all()
serializer_class = SpiderService
views:
class SpiderServiceList(APIView):
def get(self, request, format=None):
services = SpiderService.objects.all()
serializer = SpiderServiceSerializer(services, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = SpiderServiceSerializer(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)
def perform_create(elf, serializer):
serializer.save(user=self.request.user)
when I post
{
"name": "2"
}
to the api url, got 500 error:
[01/Feb/2016 15:39:55] "POST /services/ HTTP/1.1" 500 17105
IntegrityError: NOT NULL constraint failed: spmanage_spiderservice.user_id
I have read Serializer relations and found I should not write code as above. The official example is equal to remove user field in SpiderServiceSerializer, and add a spiderservice field to a UserSerializer.Seems aim to achieve the syntax like user.spiderservice_set in django models, which do not fit my purpose.
I just want to create a spiderservice object with request.user.
How to solve it?
You need to add the current user as an extra parameter to the Serializer's save method:
serializer.save(user=request.user)
do you have any Account serializer for the user model ??
if so, in your SpiderServiceSerializer try this :
user = AccountSerializer(read_only=True, required=False)