I'm trying to make a rest API for a blog app using Django-rest-framework. I'm a beginner and it's hard for me to understand how to implement that system. I made an intermediary model for making connections between followers and followings and serializers for users. But my API showing absolutely wrong following and followers for each user and endpoints for following and unfollowing not working also. https://github.com/romon267/blog_api - my code
Models.py:
class UserFollowing(models.Model):
class Meta:
constraints= [
models.UniqueConstraint(fields=['user_id', 'following_user_id'], name='unique_following')
]
ordering = ['-created']
user_id = models.ForeignKey('auth.User', related_name='following', on_delete=models.SET_NULL, null=True,blank=True)
following_user_id = models.ForeignKey('auth.User', related_name='followers', on_delete=models.SET_NULL, null=True,blank=True)
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f'{self.user_id} is following {self.following_user_id}'
serializers.py:
class UserSerializer(serializers.HyperlinkedModelSerializer):
following = serializers.HyperlinkedRelatedField(many=True, view_name='user-detail', read_only=True)
followers = serializers.HyperlinkedRelatedField(many=True, view_name='user-detail', read_only=True)
posts = serializers.HyperlinkedRelatedField(many=True, view_name='post-detail', read_only=True)
class Meta:
model = User
fields = ['url', 'id', 'username', 'posts', 'following', 'followers']
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
class UserFollowingSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = UserFollowing
fields = '__all__'
class FollowingSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = UserFollowing
fields = ['id', 'following_user_id', 'created']
class FollowersSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = UserFollowing
fields = ['id', 'user_id', 'created']
views.py:
class UserViewSet(viewsets.ReadOnlyModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserFollowingViewSet(viewsets.ModelViewSet):
queryset = UserFollowing.objects.all()
serializer_class = UserFollowingSerializer
class UserFollow(APIView):
"""
Retrieve, update or delete a snippet instance.
"""
def get_object(self, pk):
try:
return User.objects.get(pk=pk)
except User.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
user = self.get_object(pk)
serializer = UserSerializer(user)
return Response(serializer.data)
def post(self, request, pk, format=None):
user = request.user
follow = self.get_object(pk)
UserFollowing.objects.create(user_id=user.id, following_user_id = follow.id)
serializer = UserSerializer(follow)
return Response(serializer.data)
def delete(self, request, pk, format=None):
user = request.user
follow = self.get_object(pk)
UserFollowing.objects.delete(user_id=user.id, following_user_id = follow.id)
serializer = UserSerializer(follow)
return Response(serializer.data)
Related
here i am trying to add custom permissions
1.user can rate the movie once
2.Users can add a movie and other people, except the creator, can rate it.
i have written the custom permission class in the permission.py but still it not doing what i want
but it is going wrong .can please some help one
models.py
from django.contrib.auth.models import User
from django.core.validators import MinValueValidator, MaxValueValidator
class Movie(models.Model):
title = models.CharField(max_length=128)
director = models.CharField(max_length=128)
added_by = models.ForeignKey(User, related_name="movie", on_delete=models.CASCADE, null=True)
added_at = models.DateTimeField(auto_now_add=True)
# rating=models.IntegerField()
class Meta:
db_table = "Movie"
class Rating(models.Model):
movie = models.ForeignKey(Movie, on_delete=models.CASCADE)
rating = models.IntegerField(validators=[MinValueValidator(0),MaxValueValidator(5)])
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user_rating')
class Meta:
db_table = "Rating"
views.py
from rest_framework.response import Response
from rest_framework.decorators import permission_classes
from rest_framework.permissions import IsAuthenticated
from knox.models import AuthToken
from TestApp.models import Movie, Rating
from TestApp.serializer import UserSerializer, RegisterSerializer, LoginSerializer, MovieSerializer, RatingSerializer
from TestApp.permissions import *
class UserAPIView(generics.RetrieveAPIView):
permission_classes = [
permissions.IsAuthenticated,
]
serializer_class = UserSerializer
def get_object(self):
return self.request.user
class RegisterAPIView(generics.GenericAPIView):
serializer_class = RegisterSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.save()
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": AuthToken.objects.create(user)[1]
})
class LoginAPIView(generics.GenericAPIView):
serializer_class = LoginSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": AuthToken.objects.create(user)[1]
})
class MovieAPIView(generics.ListCreateAPIView):
serializer_class = MovieSerializer
def get_queryset(self):
return Movie.objects.all()
#permission_classes([IsAuthenticated])
def perform_create(self, serializer):
serializer.save(added_by=self.request.user)
class RatingAPIView(generics.CreateAPIView):
serializer_class = RatingSerializer
#permission_classes([IsAuthenticated,UserPermission])
def perform_create(self, serializer):
serializer.save(user=self.request.user)
serializers.py
from django.contrib.auth import authenticate
from django.contrib.auth.models import User
from rest_framework import serializers
from TestApp.models import Movie, Rating
from urllib import request
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email')
class RegisterSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email', 'password')
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
user = User.objects.create_user(validated_data['username'], validated_data['email'], validated_data['password'])
return user
class LoginSerializer(serializers.Serializer):
username = serializers.CharField()
password = serializers.CharField()
def validate(self, data):
user = authenticate(**data)
if user and user.is_active:
return user
raise serializers.ValidationError("Wrong Credentials")
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = ['id', 'title', 'director']
class RatingSerializer(serializers.ModelSerializer):
# id=MovieSerializer(read_only=True)
class Meta:
model = Rating
fields = ['id', 'movie','rating','user']
permissions.py
from TestApp.models import *
class UserPermission(BasePermission):
message = 'Adding ratings by same user not allowed.'
def has_permission(self, request, view,attrs):
allowed_methods = ['POST', 'PATCH']
#validated_data = super().validate(attrs)
user = self.context['request'].user
if request.method in SAFE_METHODS and Rating.objects.filter(movie=validated_data['movie'], user=user).exists():
return True
else:
return False
class Add(BasePermission):
def has_permission(self, request, view):
allowed_methods = ['POST', 'PATCH']
user = Movie.objects.get(id=user_id)
t = Rating.objects.filter(added_by_id=user)
if request.method in allowed_methods:
if t == user:
return False
else:
return True
else:
return True
def has_permission(self, request, view,attrs):
allowed_methods = ['POST', 'PATCH']
#validated_data = super().validate(attrs)
user = self.context['request'].user
ratings = Rating.objects.filter(movie=validated_data['movie'], user=user)
if validated_data['movie'].added_by == user:
return False
elif request.method in SAFE_METHODS and not ratings.exists():
return True
else:
return False
If you dont want to allow user to rate twice then user not Rating.objects.filter().exists() i.e if object doesn’t not exists then return True
here i want to join the two tables and i want that once the user is logged in then he/she can rate a movie and only one user can rate the movie and if the same user tries to rate it again then it should show an error message
i have used knox for 3rd party authentication and i want to join the movie and rating table
models.py
from django.db import models
from django.contrib.auth.models import User
from django.core.validators import MinValueValidator, MaxValueValidator
class Movie(models.Model):
title = models.CharField(max_length=128)
director = models.CharField(max_length=128)
added_by = models.ForeignKey(User, related_name="movies", on_delete=models.CASCADE, null=True)
added_at = models.DateTimeField(auto_now_add=True)
# rating=models.IntegerField()
class Meta:
db_table = "Movie"
class Rating(models.Model):
movies=models.CharField(max_length=128)
rating = models.IntegerField(validators=[MinValueValidator(0),
MaxValueValidator(5)])
class Meta:
db_table = "Rating"
Views.py
from rest_framework.response import Response
from rest_framework.decorators import permission_classes
from rest_framework.permissions import IsAuthenticated
from knox.models import AuthToken
from TestApp.models import Movie, Rating
from TestApp.serializer import UserSerializer, RegisterSerializer, LoginSerializer, MovieSerializer, RatingSerializer
from django.shortcuts import render
# , RegisterSerializer, LoginSerializer, MovieSerializer
class UserAPIView(generics.RetrieveAPIView):
permission_classes = [
permissions.IsAuthenticated,
]
serializer_class = UserSerializer
def get_object(self):
return self.request.user
class RegisterAPIView(generics.GenericAPIView):
serializer_class = RegisterSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.save()
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": AuthToken.objects.create(user)[1]
})
class LoginAPIView(generics.GenericAPIView):
serializer_class = LoginSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": AuthToken.objects.create(user)[1]
})
class MovieAPIView(generics.ListCreateAPIView):
serializer_class = MovieSerializer
def get_queryset(self):
return Movie.objects.all()
#permission_classes([IsAuthenticated])
def perform_create(self, serializer):
serializer.save(added_by=self.request.user)
class RatingAPIView(generics.ListCreateAPIView):
serializer_class = RatingSerializer
def get_queryset(self):
return Rating.objects.all()
"""def create(self,serializer):
return Rating.objects.all()
user=self.request.user
if not user:
return ("Please Authenticate Yourself")
else:
return Response(Rating.objects.all().prefetch_related('movies'))"""
serializers.py
from django.contrib.auth.models import User
from rest_framework import serializers
from TestApp.models import Movie, Rating
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email')
class RegisterSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email', 'password')
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
user = User.objects.create_user(validated_data['username'], validated_data['email'], validated_data['password'])
return user
class LoginSerializer(serializers.Serializer):
username = serializers.CharField()
password = serializers.CharField()
def validate(self, data):
user = authenticate(**data)
if user and user.is_active:
return user
raise serializers.ValidationError("Wrong Credentials")
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = ['id', 'title', 'director']
class RatingSerializer(serializers.ModelSerializer):
#id=MovieSerializer(read_only=True)
class Meta:
model = Rating
fields = ['id','movies', 'rating']
please help how to perform join of the two table and how can i implement once user only one review
thanks in advance
In your Rating model you will have to add a ForeignKey to your User model. Also you need to make your movies field as a ForeignKey to link it to the Movies model
models.py
class Rating(models.Model):
movies=models.ForeignKey(Movies, on_delete=models.CASCADE)
rating = models.IntegerField(validators=[MinValueValidator(0),
MaxValueValidator(5)])
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user_rating)
class Meta:
db_table = "Rating"
Now inside your serializer's validation method you can validate if the user has already rated the movie or not
serializes.py
class RatingSerializer(serializers.ModelSerializer):
def validate(self, attrs):
validated_data = super().validate(attrs)
user = self.context['request'].user
if Ratings.objects.filter(movies=validated_data['movies'],user=user).exists():
raise serializers.ValidationError('User already rated the movie')
return validated_data
class Meta:
model = Rating
fields = ['id','movies', 'user', 'rating']
Then to get the average rating for a movie you can do
from django.db.models import Avg
Ratings.objects.filter(movies__name='movie_name').aggregrate(Avg('rating'))
Django will take care of joining the tables and filtering the results
I'm trying to create an order by sending it as a POST request via my DRF API but I keep getting ""POST /shop/orders/ HTTP/1.1" 400 81" errors.
The error says "{"total_price":["This field is required."],"puppies":["This field is required."]}" even though I'm supplying it.
The GET is working fine but I only have orders that I manually wrote in the DB.
Our project is a fictional webshop where you can put dog babies in your cart and order them. The three main models that we have are Users, Orders and Puppies. Because every order has a list of puppies and every puppy has its own amount we needed an intermediary model for the two as a simple ManyToManyField apparently can't handle extra columns. This makes things much more complicated (at least for someone who is new to Django).
I can't find anything on the internet for this special case. I have been trying to get this to work for almost a week now and I don't see what I'm doing wrong.
models.py:
from django.db import models
from django.conf import settings
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
class Puppy(models.Model):
name = models.CharField(max_length=30)
price = models.DecimalField(max_digits=6, decimal_places=2)
image_url = models.CharField(max_length=200)
age = models.IntegerField(null=True)
weight = models.IntegerField(null=True)
description_de = models.CharField(max_length=500, null=True)
description_en = models.CharField(max_length=500, null=True)
def __str__(self):
return self.name
class Order(models.Model):
total_price = models.DecimalField(max_digits=9, decimal_places=2)
puppies = models.ManyToManyField(Puppy, through='PuppyOrder')
date = models.DateTimeField(auto_now_add=True, blank=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='orders')
def __str__(self):
return str(self.id)
class PuppyOrder(models.Model):
order = models.ForeignKey(Order, on_delete=models.CASCADE)
puppy = models.ForeignKey(Puppy, on_delete=models.CASCADE)
amount = models.IntegerField()
def __str__(self):
return str(self.id)
class Address(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
country = models.CharField(max_length=30, blank=True)
street = models.CharField(max_length=30, blank=True)
zip = models.CharField(max_length=10, blank=True)
city = models.CharField(max_length=30, blank=True)
#receiver(post_save, sender=User)
def create_user_address(sender, instance, created, **kwargs):
if created:
Address.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_address(sender, instance, **kwargs):
instance.address.save()
Fetch in react:
async function createOrder(order) {
order = order.map(element =>
element = {'puppy' : element.puppy.id, 'amount': element.amount}
)
let orderReq = {"total_price": "500.00", "puppies": order}
console.log(JSON.stringify(orderReq))
const res = await fetch(REACT_APP_BACKEND_URL + `/shop/orders/`, {
method: 'POST',
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ orderReq })
})
return res.json()
}
Console.log results in: {"total_price":"500.00","puppies":[{"puppy":2,"amount":3},{"puppy":4,"amount":4}]}
"500.00" is just a placeholder value. Next step will be finding a place to calculate the total_price.
views.py:
from shop.models import Puppy, Order
from django.contrib.auth.models import User
from rest_framework import permissions, status, viewsets, generics
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse
from rest_framework.exceptions import ValidationError
from rest_framework_jwt.serializers import VerifyJSONWebTokenSerializer
from .serializers import UserSerializer, UserSerializerWithToken, OrderSerializer, PuppySerializer
#api_view(['GET'])
def api_root(request, format=None):
return Response({
'users': reverse('user-list', request=request, format=format),
'orders': reverse('order-list', request=request, format=format),
'puppies': reverse('puppy-list', request=request, format=format),
})
class OrderList(generics.ListCreateAPIView):
print("Test")
queryset = Order.objects.all()
permission_classes = (permissions.AllowAny,)
serializer_class = OrderSerializer
def get_queryset(self):
if self.request.user is None or self.request.user.is_anonymous:
self.request.user = get_request_user_and_validate_token(self)
return Order.objects.all().filter(user=self.request.user)
def perform_create(self, serializer):
print(self.request.data)
if self.request.user is None or self.request.user.is_anonymous:
self.reqest.user = get_request_user_and_validate_token(self)
if serializer.is_valid():
print('Creating new order...')
serializer.create(user=self.request.user, puppies=self.request.data)
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class OrderDetail(generics.RetrieveAPIView):
permission_classes = (permissions.AllowAny,)
queryset = Order.objects.all()
serializer_class = OrderSerializer
def get_queryset(self):
if self.request.user is None or self.request.user.is_anonymous:
self.request.user = get_request_user_and_validate_token(self)
return Order.objects.all().filter(user=self.request.user)
class UserList(generics.ListAPIView):
queryset = User.objects.all().select_related('address')
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all().select_related('address')
serializer_class = UserSerializer
class PuppyList(generics.ListAPIView):
permission_classes = (permissions.AllowAny,)
queryset = Puppy.objects.all()
serializer_class = PuppySerializer
class PuppyDetail(generics.RetrieveAPIView):
permission_classes = (permissions.AllowAny,)
queryset = Puppy.objects.all()
serializer_class = PuppySerializer
def get_request_user_and_validate_token(self):
print(self.request.user)
token = self.request.META.get('HTTP_AUTHORIZATION', " ").split(' ')[1]
data = {'token': token}
try:
valid_data = VerifyJSONWebTokenSerializer().validate(data)
print(valid_data['user'])
return valid_data['user']
except ValidationError as v:
print("validation error", v)
serializers.py
from rest_framework import serializers
from rest_framework_jwt.settings import api_settings
from django.contrib.auth.models import User
from django.db import models
from .models import Order, Puppy, PuppyOrder
class UserSerializer(serializers.ModelSerializer):
orders = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = User
fields = ('id', 'username', 'orders')
class UserSerializerWithToken(serializers.ModelSerializer):
# Code dealing with JWT token creation ...
class PuppySerializer(serializers.ModelSerializer):
description = serializers.SerializerMethodField()
def get_description(self, puppy):
return {'DE': puppy.description_de, 'EN': puppy.description_en}
class Meta:
model = Puppy
fields = ('id', 'name', 'price', 'image_url', 'age', 'weight', 'description')
class PuppyOrderSerializer(serializers.ModelSerializer):
puppy = serializers.ReadOnlyField(source='puppy.id')
class Meta:
model = PuppyOrder
fields = ('puppy', 'amount')
class OrderSerializer(serializers.ModelSerializer):
puppies = PuppyOrderSerializer(source='puppyorder_set', many=True)
user = serializers.ReadOnlyField(source='user.username')
total_price = "100.00"
class Meta:
model = Order
fields = ('id', 'total_price', 'puppies', 'date', 'user')
Any help would be much appreciated!
I can't get a valid response, always get this error
{"required": "This field is required.", "null": "This field may not be null.", "not_a_list": "Expected a list of items but got type \"{input_type}\".", "empty": "This list may not be empty."}
Here is some code (models, serializers, views)
models
class Task(models.Model):
name = models.CharField(max_length=50)
description = models.CharField(max_length=500, blank=True, default='')
pub_datetime = models.DateTimeField()
priority = models.CharField(choices=PRIORITY_CHOICES, max_length=1)
status = models.BooleanField(default=False)
owner = models.ForeignKey('auth.User', on_delete=models.CASCADE)
agenda = models.ForeignKey(Agenda, on_delete=models.CASCADE, blank=True, default=None, null=True)
def __str__(self):
return self.name
serializers
class TaskSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
agenda = serializers.ReadOnlyField(source='agenda.name')
class Meta:
model = Task
fields = ('id', 'name', 'description', 'pub_datetime', 'priority', 'status', 'owner', 'agenda')
views
class TaskListView(generics.ListCreateAPIView):
serializer_class = TaskSerializer
permission_classes = (permissions.IsAuthenticated,)
renderer_classes = [JSONRenderer, TemplateHTMLRenderer]
template_name = 'task_list.html'
def get(self, request, *args, **kwargs):
queryset = Task.objects.filter(owner=self.request.user)
if self.request.accepted_renderer.format == 'html':
return Response({'tasks': queryset})
else:
serializer = TaskSerializer(data=queryset, many=True)
if serializer.is_valid():
return JsonResponse(serializer.data, safe=False)
else:
return JsonResponse(serializer.error_messages)
def post(self, request, *args, **kwargs):
name = request.data['name']
description = request.data['description']
priority = request.data['priority']
new_task = Task.objects.create(name=name, description=description, pub_datetime=datetime.datetime.now(),
priority=priority, status=False, owner=self.request.user)
new_task.save()
return redirect('task-list')
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
I'm not sure, at what place I'm wrong. Can someone help?
just text to avoid errors
The queryset or object instance should be passed to serializer as instance, not data, so:
queryset = Task.objects.filter(owner=self.request.user)
serializer = TaskSerializer(instance=queryset, many=True)
Also, I believe that serializer can't validate instance, so in this case you should be able to just return return JsonResponse(serializer.data)
first, in models.py
class UserComment(models.Model):
user = models.ForeignKey(User)
rate = models.IntegerField()
description = models.CharField(max_length=512)
createTime = models.DateTimeField(auto_now=True)
def __unicode__(self):
return '<UserComment {%s %d}>' % (self.user.username, self.rate)
then, serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email', )
class UserCommentSerializer(serializers.ModelSerializer):
user = UserSerializer(required=False)
class Meta:
model = UserComment
views.py
class UserCommentViewSet(viewsets.ModelViewSet):
queryset = UserComment.objects.all()
serializer_class = UserCommentSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly, )
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.DATA, files=request.FILES)
serializer.is_valid()
print serializer.errors
print serializer.data
return super(UserCommentViewSet, self).create(request, *args, **kwargs)
then i post json data
{"user":{"id":"1","username":"watsy"},"rate":"5","description":"hello"}
i think,it will work. and insert it to db, but i get errors.
{"user": [{"username": ["User with this Username already exists."]}]}
>_<, I have no idea.
You need to make few changes to your serializer:
class UserCommentSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = UserComment
depth = 1
Now pass this JSON dict in your request:
{"user":"1", "rate":"5", "description":"hello"}