Update a field value in model Django Rest - python

serializers.SerializerMethodField() is only meant to read value ie
retrieve, it will not update the lang value.
I want to create an API that can update a field value. Mainly, I want to change the field lang in the User model. Here is my code
models.py
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, related_name='profile', on_delete=models.CASCADE)
member_since = models.DateTimeField(default=timezone.now)
lang = models.CharField(max_length = 5, default = 'en')
def get_lang(self):
return self.lang
serializers.py
from django.contrib.auth import get_user_model
User = get_user_model()
class ProfileRetrieveUpdateSerializer(serializers.ModelSerializer):
member_since = serializers.SerializerMethodField()
lang = serializers.SerializerMethodField()
class Meta:
model = User
fields = [
'member_since','lang'
]
def get_member_since(self, obj):
return obj.profile.member_since.date()
def get_lang(self, obj):
return obj.profile.get_lang()
def update(self, instance, validated_data):
print(validated_data)
setattr(instance.profile, "lang", validated_data.get("lang",instance.profile.lang))
instance.save()
return instance
views.py
from django.contrib.auth import get_user_model
from .serializers import (
CurrentUserDetailSerializer, ProfileRetrieveUpdateSerializer,
UserLoginSerializer, UserSerializerWithToken,
)
User = get_user_model()
class ProfileRetrieveUpdateAPIView(RetrieveUpdateAPIView):
"""
View that returns user profile data.
"""
permission_classes = [AllowAny]
queryset = User.objects.all()
serializer_class = ProfileRetrieveUpdateSerializer
lookup_field = 'username'
lookup_url_kwarg = 'username'
axios.patch(url, {'lang': 'fr') (additional, I don't think it's wrong here)
When I call API, I get that the validated_data value is {}!
I think the problem is in the serializers.py module.

lang = serializers.CharField(source = 'get_lang')
def update(self, instance, validated_data):
setattr(instance.profile, "lang", validated_data.get("get_lang"))
instance.save()
return instance
reason: serializers.SerializerMethodField() is only meant to read value ie retrieve, it will not update lang value.

Related

Use a related field in lookup_field in Django REST framework

I have a user model and a profile model that is created automatically when a user is registered. In my view for retrieving the user profile, I am trying to get the user profile using the username but DRF uses pk by default. So I added a lookup field with the username but DRF can't seem to resolve it:
my profile model:
class Profile(TimeStampedModel):
user = models.OneToOneField('User', on_delete=models.CASCADE, related_name='customer_profile')
bio = models.TextField(blank=True)
image = models.URLField(blank=True)
def __str__(self):
return self.user.username
my profile serializer:
class ProfileSerializer(serializers.ModelSerializer):
username = serializers.StringRelatedField(source='user.username', read_only=True)
bio = serializers.CharField(allow_blank=True, allow_null=True)
image = serializers.SerializerMethodField()
class Meta:
model = Profile
fields = ['username', 'bio', 'image']
def get_image(self, obj):
if obj.image:
return obj.image
return 'https://static.productionready.io/images/smiley-cyrus.jpg'
my profile retrieving view:
class ProfileRetrieveAPIView(generics.RetrieveAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
lookup_field = 'username'
def retrive(self, request, username, *args, **kwargs):
try:
profile = Profile.objects.select_related('user').get(
user__username = username
)
except Profile.DoesNotExist:
ProfileDoesNotExist
serializer = self.serializer_class(profile)
return Response(serializer.data, status=status.HTTP_200_OK)
my profile endpoint:
urlpatterns = [
# ....
path('profiles/<str:username>', views.ProfileRetrieveAPIView.as_view()),
]
I am getting the error:
Cannot resolve keyword 'username' into field. Choices are: bio, created_at, id, image, updated_at, user, user_id
What am I doing wrong?
Use nested serialzer.
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username',)
And in your profile serializer:
username = UserSerializer(many=False, read_only=True)
Or use SerializerMethodField()
username = SerializerMethodField()
And define whats in in:
def username(self,obj):
//code
You can use this
class ProfileSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username', read_only=True)
bio = serializers.CharField(allow_blank=True, allow_null=True)
image = serializers.SerializerMethodField()
class Meta:
model = Profile
fields = ['username', 'bio', 'image']
def get_image(self, obj):
if obj.image:
return obj.image
return 'https://static.productionready.io/images/smiley-cyrus.jpg'

How to add permissions for "class UserRetrtieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView)"?

I've created a model using an abstract user class model class for Online flight ticket booking. I'm new to this so haven't added many functionalities to it.
I'm sharing my model.py, admin.py, serializer.py, view.py.
My question:
In views.py -> class UserRetrtieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView)
I want to give access for GET PUT DELETE for only ADMIN and USER who created this profile(owner). I'm using postman to check endpoints.
"Please do check my abstract user model if there is any mistake".
permission for "BookViewSet" and "BookingRetrtieveUpdateDestroyAPIView" I only want ADMIN and USER owner who created this booking to view or modify it.
I only want to get list of passengers associated by that particular user in "PassengerListCreateAPIView(generics.ListCreateAPIView):"
#models.py
import email
from pyexpat import model
from django.db import models
from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import (
BaseUserManager, AbstractBaseUser
)
GENDER_CHOICES = (
(0, 'male'),
(1, 'female'),
(2, 'not specified'),)
class UserManager(BaseUserManager):
def create_user(self, email, name,contact_number,gender,address,state,city,country,pincode,dob ,password=None, password2=None):
if not email:
raise ValueError('User must have an email address')
user = self.model(
email=self.normalize_email(email),
name=name,
contact_number=contact_number,
gender=gender,
address=address,
state=state,
city=city,
country=country,
pincode=pincode,
dob=dob,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, name,contact_number,gender,address,state,city,country,pincode,dob , password=None):
user = self.create_user(
email,
name=name,
contact_number=contact_number,
gender=gender,
address=address,
state=state,
city=city,
country=country,
pincode=pincode,
dob=dob,
password=password,
)
user.is_admin = True
user.save(using=self._db)
return user
class User(AbstractBaseUser):
email = models.EmailField(verbose_name='Email',max_length=255,unique=True)
name = models.CharField(max_length=200)
contact_number= models.IntegerField()
gender = models.IntegerField(choices=GENDER_CHOICES)
address= models.CharField(max_length=100)
state=models.CharField(max_length=100)
city=models.CharField(max_length=100)
country=models.CharField(max_length=100)
pincode= models.IntegerField()
dob = models.DateField()
# is_staff = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['name','contact_number','gender','address','state','city','country','pincode','dob']
def __str__(self):
return self.email
def has_perm(self, perm, obj=None):
"Does the user have a specific permission?"
# Simplest possible answer: Yes, always
return self.is_admin
def has_module_perms(self, app_label):
"Does the user have permissions to view the app `app_label`?"
# Simplest possible answer: Yes, always
return True
#property
def is_staff(self):
"Is the user a member of staff?"
# Simplest possible answer: All admins are staff
return self.is_admin
# Create your models here.
class Airport(models.Model):
Airport_name=models.CharField(max_length=100)
country=models.CharField(max_length=100)
def __str__(self):
return self.Airport_name
class Flight(models.Model):
flight_number = models.CharField(max_length=100,unique=True)
depart_date_time = models.DateTimeField(auto_now_add=True)
arrival_date_time = models.DateTimeField(auto_now_add=True)
origin = models.CharField(max_length=100, blank=True, default='')
destination = models.CharField(max_length=100, blank=True, default='')
price = models.IntegerField()
airline_name = models.CharField(max_length=100, blank=True, default='')
total_seats = models.IntegerField()
available_seats = models.IntegerField()
airport=models.ForeignKey(Airport,on_delete=models.CASCADE)
def __str__(self):
return str(self.flight_number)
class Passenger(models.Model):
name = models.CharField(max_length=100,blank=True, default='')
contact_number= models.IntegerField()
email = models.EmailField(max_length=254)
gender = models.IntegerField(choices=GENDER_CHOICES)
age= models.IntegerField()
user=models.ForeignKey(User,on_delete=models.CASCADE)
def __str__(self):
return self.name
class Booking(models.Model):
user =models.ForeignKey(User,on_delete=models.CASCADE)
flights =models.ForeignKey(Flight,on_delete=models.CASCADE)
passenger =models.ManyToManyField(Passenger)
booking_number= models.CharField(max_length= 100,default=0, blank= True)
booking_time = models.DateTimeField(auto_now_add=True)
no_of_passengers= models.IntegerField(default=0, blank= True)
def __str__(self):
return self.booking_number
Corresponding serializer
#serializers.py
from rest_framework import serializers
from myapp.models import Airport, Flight, User, Passenger, Booking
class UserRegistrationSerializer(serializers.ModelSerializer):
# We are writing this becoz we need confirm password field in our Registratin Request
password2 = serializers.CharField(style={'input_type':'password'}, write_only=True)
class Meta:
model = User
fields=['email','name','contact_number','gender','address','state','city','country','pincode','dob','password', 'password2']
extra_kwargs={
'password':{'write_only':True}
}
# Validating Password and Confirm Password while Registration
def validate(self, attrs):
password = attrs.get('password')
password2 = attrs.get('password2')
if password != password2:
raise serializers.ValidationError("Password and Confirm Password doesn't match")
return attrs
def create(self, validate_data):
return User.objects.create_user(**validate_data)
class UserLoginSerializer(serializers.ModelSerializer):
email = serializers.EmailField(max_length=255)
class Meta:
model = User
fields = ['email', 'password']
# class UserProfileSerializer(serializers.ModelSerializer):
# class Meta:
# model = User
# fields = '__all__'
class AirportSerializer(serializers.ModelSerializer):
class Meta:
model = Airport
fields = '__all__'
class FlightSerializer(serializers.ModelSerializer):
class Meta:
model = Flight
fields = '__all__'
class UserSerializer(serializers.ModelSerializer):
class Meta:
model= User
fields = '__all__'
class PassengerSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=False)
class Meta:
model= Passenger
fields = '__all__'
class BookingSerializer(serializers.ModelSerializer):
class Meta:
model= Booking
fields = '__all__'
#views.py
from django.shortcuts import render
from django.http import Http404, JsonResponse
#from django.http import HttpResponse, JsonResponse
from rest_framework.views import APIView
from rest_framework.response import Response
#from rest_framework.parsers import JSONParser
from django.contrib.auth import authenticate
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import SAFE_METHODS, BasePermission,IsAuthenticatedOrReadOnly,IsAuthenticated, IsAdminUser, DjangoModelPermissionsOrAnonReadOnly, DjangoModelPermissions
from myapp.renderers import UserRenderer
from rest_framework import status
from rest_framework import permissions
from rest_framework import generics
from myapp.models import Airport, Flight, User, Passenger, Booking
from myapp.serializers import *
from myapp.permissions import IsOwnerOrAdmin
from rest_framework import viewsets
def get_tokens_for_user(user):
refresh = RefreshToken.for_user(user)
return {
'refresh': str(refresh),
'access': str(refresh.access_token),
}
# Create your views here.
class UserRegistrationView(APIView):
renderer_classes = [UserRenderer]
def post(self, request, format=None):
serializer = UserRegistrationSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.save()
token = get_tokens_for_user(user)
return Response({'token':token, 'msg':'Registration Successful'}, status=status.HTTP_201_CREATED)
class UserLoginView(APIView):
renderer_classes = [UserRenderer]
def post(self, request, format=None):
serializer = UserLoginSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
email = serializer.data.get('email')
password = serializer.data.get('password')
user = authenticate(email=email, password=password)
if user is not None:
token = get_tokens_for_user(user)
return Response({'token':token, 'msg':'Login Success'}, status=status.HTTP_200_OK)
else:
return Response({'errors':{'non_field_errors':['Email or Password is not Valid']}}, status=status.HTTP_404_NOT_FOUND)
class UserProfileView(APIView):
renderer_classes = [UserRenderer]
permission_classes = [IsAuthenticated]
def get(self, request, format=None):
serializer = UserSerializer(request.user)
return Response(serializer.data, status=status.HTTP_200_OK)
class UserListCreateAPIView(generics.ListCreateAPIView):
permission_classes = [IsUserOrIsAdmin]
queryset = User.objects.all()
serializer_class = UserSerializer
class UserRetrtieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
permission_classes = [IsUserOrIsAdmin]
queryset = User.objects.all()
serializer_class = UserSerializer
class FlightListCreateAPIView(generics.ListCreateAPIView):
permission_classes = [IsAdminUser | IsAuthenticatedOrReadOnly]
queryset = Flight.objects.all()
serializer_class = FlightSerializer
class FlightRetrtieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
permission_classes = [IsAdminUser]
queryset = Flight.objects.all()
serializer_class = FlightSerializer
class AirportListCreateAPIView(generics.ListCreateAPIView):
permission_classes = [IsAdminUser | IsAuthenticatedOrReadOnly]
queryset = Airport.objects.all()
serializer_class = AirportSerializer
class AirportRetrtieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
permission_classes = [IsAdminUser]
queryset = Airport.objects.all()
serializer_class = AirportSerializer
class PassengerListCreateAPIView(generics.ListCreateAPIView):
permission_classes = [IsUserOrIsAdminPassenger]
queryset = Passenger.objects.all()
serializer_class = PassengerSerializer
class PassengerRetrtieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
permission_classes = [IsUserOrIsAdminPassenger]
queryset = Passenger.objects.all()
serializer_class = PassengerSerializer
class BookingRetrtieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
permission_classes = [IsUserOrIsAdmin]
# queryset = Booking.objects.all()
# serializer_class = BookingSerializer
def get_queryset(self):
if self.request.user.is_staff == False:
user_data= self.request.user
book = Booking.objects.filter(user= user_data)
return book
else:
book = Booking.objects.all()
return book
serializer_class = BookingSerializer
class BookViewSet(viewsets.ModelViewSet):
permission_classes = [IsUserOrIsAdmin]
# queryset = Book.objects.all()
serializer_class = BookingSerializer
# print(serializer_class)
def get_queryset(self):
if self.request.user.is_staff == False:
user_data= self.request.user
book = Booking.objects.filter(user= user_data)
return book
else:
book = Booking.objects.all()
return book
# book = Booking.objects.all()
def create(self, request, *args, **kwargs):
data = request.data
user = User.objects.get(id= request.user.pk)
# user = User.objects.get(id=data["user"])
flightdetails = Flight.objects.get(id=data["flights"])
# bookingdetails = Booking.objects.get(no_of_passengers=data["no_of_passengers"])
new_book = Booking.objects.create(
booking_number= data["booking_number"],
no_of_passengers= data["no_of_passengers"],
user=user,
flights=flightdetails,
)
new_book.save()
for passenger in data["passenger"]:
passenger_book= Passenger.objects.create(
user = user,
name= passenger["name"],
contact_number = passenger["contact_number"],
email = passenger["email"],
gender = passenger["gender"],
age = passenger["age"]
)
new_book.passenger.add(passenger_book)
if flightdetails.available_seats < len(data["passenger"]):
return Response({"data": "No seats available", "status": status.HTTP_400_BAD_REQUEST})
update_seats = flightdetails.available_seats - data["no_of_passengers"]
flightdetails.available_seats = update_seats
flightdetails.save()
serializers = BookingSerializer(new_book)
return Response({"data": serializers.data, "status": status.HTTP_201_CREATED})
#urls.py
from django.urls import path, include
from myapp.views import *
from rest_framework import routers
router = routers.DefaultRouter()
router.register('booking', BookViewSet, basename='MyModel')
urlpatterns = [
path('register/', UserRegistrationView.as_view(), name='register'),
path('login/', UserLoginView.as_view(), name='login'),
path('profile/', UserProfileView.as_view(), name='profile'),
path('flight/', FlightListCreateAPIView.as_view()),
path('flight_info/<int:pk>/', FlightRetrtieveUpdateDestroyAPIView.as_view()),
path('customer/', UserListCreateAPIView.as_view()),
path('customer_info/<int:pk>/', UserRetrtieveUpdateDestroyAPIView.as_view()),
path('passenger/', PassengerListCreateAPIView.as_view()),
path('passenger_info/<int:pk>/', PassengerRetrtieveUpdateDestroyAPIView.as_view()),
path('booking_info/<int:pk>/', BookingRetrtieveUpdateDestroyAPIView.as_view()),
#path('booking/', BookingAPIView.as_view()),
path('', include(router.urls)),
]
You'll need to create a custom permission for this. Since the permission is dependent on the User instance, you'll want to implement has_object_permission(), returning True if the user is_staff or if the user is the same as the User they're trying to access:
from django.contrib.auth import get_user_model
from rest_framework.permissions import BasePermission
User = get_user_model()
class IsUserOrIsAdmin(BasePermission):
"""Allow access to the respective User object and to admin users."""
def has_object_permission(self, request, view, obj):
return (request.user and request.user.is_staff) or (
isinstance(obj, User) and request.user == obj
)
You can then set your view's permission_classes to your custom permission:
class UserRetrtieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
permission_classes = [IsUserOrIsAdmin]
queryset = User.objects.all()
serializer_class = UserSerializer

How do I create a database entry with an intermediary table field in my serializer class?

Our project is a fictional webshop where you can put puppies 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).
Also our authorization is via a JWT token, that's why the self.request.user apparently has to be manually set and isn't set automatically.
The only thing that should be possible to create via a POST are new orders. These should automatically get a date and the user posting it should also automatically be set. The total_price of an order should also be calculated so that the only things that need to be sent by the client are a list of puppies and their respective amounts.
POST in React:
async function createOrder(auth, puppies) {
auth = auth.token
puppies = puppies.map(element =>
element = {'id' : element.puppy.id, 'amount': element.amount}
)
const res = await fetch(REACT_APP_BACKEND_URL + `/shop/orders/`, {
method: 'POST',
headers: {
"Content-Type": "application/json",
"Authorization": "JWT " + auth
},
body: JSON.stringify({ puppies })
})
return res.json()
console.log results in: [{"id":2,"amount":1},{"id":3,"amount":1}]
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, null=True)
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()
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
from .permissions import IsOwner
#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):
queryset = Order.objects.all()
permission_classes = (permissions.IsAuthenticated,)
serializer_class = OrderSerializer
def get_queryset(self):
return Order.objects.all().filter(user=self.request.user)
def perform_create(self, serializer):
print('Creating new order...')
serializer.save(user=self.request.user)
return Response(serializer.data)
class OrderDetail(generics.RetrieveAPIView):
permission_classes = (permissions.IsAuthenticated,)
queryset = Order.objects.all()
serializer_class = OrderSerializer
def get_queryset(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
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 to generate token on login ...
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')
order = serializers.ReadOnlyField(source='order.id')
class Meta:
model = PuppyOrder
fields = ('order', 'puppy', 'amount')
class OrderSerializer(serializers.ModelSerializer):
user = serializers.ReadOnlyField(source='user.username')
puppies = PuppyOrderSerializer(source='puppyorder_set', many=True)
def create(self, validated_data):
print(validated_data)
puppies = validated_data.pop("puppyorder_set")
order = Order.objects.create(**validated_data)
total_price = 0
for puppy in puppies:
puppy_id = puppy.get("id")
amount = puppy.get("amount")
puppy_instance = Puppy.objects.get(pk=puppy_id)
total_price += puppy_instance.price
PuppyOrder(order=order, puppy=puppy_instance, amount=amount).save()
order.save()
return Order
class Meta:
model = Order
fields = ('id', 'total_price', 'puppies', 'date', 'user')
This code results in an error: "Direct assignment to the forward side of a many-to-many set is prohibited. Use puppies.set() instead."
Please tell me what I'm doing wrong
There are many things you're not getting quite right. First, the authentication. Please checkout DRF-JWT or similar libraries and use their authentication classes or atleast see how to create yours so that you don't have your authentication littered all over your code.
As for the perform_create in your view, you don't really have to check serializer.is_valid() there. It is already checked in create before perform_create is called. And that method is not meant to return an API response as far as I know. It's just meant to perform any other extra actions you want before calling serializer.save(). For most basic usages, it is never overriden. You can also pass in your user and the date there to the save method or do that in the serializer.
Now, in the serializer's create method, you do not need to access initial_data. All you need should be in validated_data. First, you should pop the puppies from validated_data before calling objects.create, else, it wouldn't be able to handle it. Then you can now create the PuppyOrder objects using the earlier on popped puppies.
This is how you add extra data to the serializer's validated_data in the view's perform_create:
serializer.save(date=datetime.today(), user=self.request.user, total_price=total_price)
You can do the same in the serializer's create method by just addind the items directly in the validated data. It's just a normal dictionary afterall:
validated_data['user'] = self.context['request'].user
validated_data['date'] = datetime.today()
validated_data['total_price'] = total_price
EDIT: Using a non model serializer for PuppyOrder
Since the order and PuppyOrder is not yet created at the time the request is being sent, the following line in the OrderSerializer would result to an error because the the serializer expects actual PuppyOrders
puppies = PuppyOrderSerializer(source='puppyorder_set', many=True)
Instead you can use non model serializer like this(assuming the puppy already exists):
class PuppyOrderCreateSerializer(serializers.Serializer):
puppy = serializers.PrimaryKeyRelatedField(queryset=Puppy.objects.all())
amount = serializers.IntegerField()
Then you can use this serializer in yuur OrderSerializer:
puppies_data = PuppyOrderCreateSerializer(many=True)

Replace PrimaryKeyRelatedField with another field

I have models which consist in a User model and a Present one. A User can have multiple Present but a Present has a unique User:
My models.py is:
from django.db import models
from django.contrib.auth.models import User
class Present(models.Model):
name = models.CharField(max_length=15)
price = models.FloatField()
link = models.CharField(max_length=15)
isAlreadyBought = models.BooleanField()
user = models.ForeignKey(User, related_name='presents', on_delete=models.CASCADE)
My serializers.py are:
from django.contrib.auth.models import User, Group
from rest_framework import serializers
from blog.models import Present, Location
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
presents = serializers.PrimaryKeyRelatedField(many=True, queryset=Present.objects.all(), required=False)
class Meta:
model = User
fields = ('username', 'password', 'email', 'presents')
def create(self, validated_data):
user = super().create(validated_data)
if 'password' in validated_data:
user.set_password(validated_data['password'])
user.save()
return user
class PresentSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), read_only=False, many=False)
class Meta:
model = Present
fields = ('name', 'link', 'price', 'isAlreadyBought', 'user')
def create(self, validated_data):
return Present.objects.create(**validated_data)
Currently, if I want to get the all the presents associate with a given User, I use the primary key (in views.py):
class PresentsOfUser(viewsets.ModelViewSet):
queryset = Present.objects.all().filter(user=33)
serializer_class = PresentSerializer
However, I would rather use the username field of the User instead of the primary key.
I have tried using a SlugRelatedField but I am not sure this is the right way to achieve my goal:
class PresentSerializer(serializers.ModelSerializer):
user = serializers.SlugRelatedField(queryset=User.objects.all(), slug_field='username', read_only=False, many=False)
class Meta:
model = Present
fields = ('name', 'link', 'price', 'isAlreadyBought', 'user')
def create(self, validated_data):
return Present.objects.create(**validated_data)
And with this modification, I now use the following View to get the user 'Marcel' whose id is 33:
class PresentsOfUser(viewsets.ModelViewSet):
queryset = Present.objects.all().filter(user='Marcel')
serializer_class = PresentSerializer
But in this case, I get:
ValueError: invalid literal for int() with base 10: 'Marcel'
But if I replace user='Marcel' by user=33 (as previously), I get:
[{"name":"Nintendo","link":"fake_link","price":50.8,"isAlreadyBought":true,"user":"Marcel"},{"name":"Gamecube","link":"fake_link","price":50.8,"isAlreadyBought":true,"user":"Marcel"}]
where the user field is now the username and not the user's id.
However, I do not understand why filtering with user='Marcel' fails...
I ended up by overriding the get_querysetmethod while keeping the PrimaryKeyRelatedField in my serializer (with user__username='Marcel'as mishbah suggested):
class PresentsOfUser(viewsets.ModelViewSet):
serializer_class = PresentSerializer
def get_queryset(self):
"""
Optionally restricts the returned purchases to a given user,
by filtering against a `username` query parameter in the URL.
"""
queryset = Present.objects.all()
username = self.kwargs['user']
if username is not None:
queryset = queryset.filter(user__username=username)
if len(queryset) == 0:
raise Http404
return queryset
It also works when replacing PrimaryKeyRelatedField by SlugRelatedField:
user = serializers.SlugRelatedField(queryset=User.objects.all(), slug_field='username', read_only=False, many=False)
and adding to_field='username'in the ForeignKey of my Present model:
user = models.ForeignKey(User, related_name='presents', to_field='username', on_delete=models.CASCADE)
I think your issue is w/ this line:
queryset = Present.objects.all().filter(user='Marcel')
With the assumption Marcel is username with pk => 33
You can't filter using a string, instead something like this:
queryset = Present.objects.all().filter(user__username='Marcel')
Hope that helps.

django rest PrimaryKeyRelatedField(allow_empty=False, many=True, queryset=App.objects.all()) is not JSON serializabl

I have some nested serializers, and I'm going to get the users objects in a JSON format through http requests, using following snippet:
import requests
r = requests.get('http://127.0.0.1:8000/accounts/users',
auth=("admin", "mat"))
But here is what it returns:
PrimaryKeyRelatedField(allow_empty=False, many=True, queryset=App.objects.all()) is not JSON serializabl
I tried a lot of approached include using to_representation method and inheriting from serializers.RelatedField. But always I get the same result.
It seems that I'm doing something wrong. I'd be appreciate if you have any suggestion regarding this?
Here are serializers:
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('name',
'popularity')
class CategorySerializer(serializers.ModelSerializer):
tags = TagSerializer(many=True)
class Meta:
model = Category
fields = ('en_name',
'fa_name',
'tags')
class CpSerializer(serializers.ModelSerializer):
class Meta:
model = CP
fields = ('en_name',
'fa_name',
'id_number')
class AppSerializer(serializers.ModelSerializer):
category = CategorySerializer()
cp = CpSerializer()
class Meta:
model = App
fields = ('en_name',
'fa_name',
'package_name',
'build_number',
'downloads',
'cp',
'category')
class UserAppSerializer(serializers.ModelSerializer):
class Meta:
model = UserApps
app = AppSerializer() # or even serializers.StringRelatedField()
fields = ('status', 'user_rate', 'comment', 'app')
def to_representation(self, instance):
return None
class UserSerializer(serializers.HyperlinkedModelSerializer):
id_number = serializers.CharField(read_only=True, source='userprofile.id_number')
apps = serializers.SerializerMethodField()
class Meta:
model = User
fields = ('username',
'password',
'first_name',
'last_name',
'email',
'id_number',
'apps')
def get_apps(self, obj):
if obj.username != "admin":
return
else:
apps = UserAppSerializer(read_only=True,
many=True)
return apps
class UserProfileSerializer(serializers.HyperlinkedModelSerializer):
user = UserSerializer()
class Meta:
model = UserProfile
fields = ('user', 'id_number')
And here is the get method of my view:
def get(self, request):
print("call get method on {}".format(self.__class__.__name__))
if request.user.username == 'admin':
users = User.objects.all()
lookup_field = 'username'
serializer = UserSerializer(users, many=True)
return Response(serializer.data)
I've defined the apps field in UserProfile as following:
apps = models.ManyToManyField('UserApps')
And in UserApps the app as following:
app = models.ManyToManyField(Application)
And in Application the cp and category as:
cp = models.ForeignKey('cp.CP')
category = models.ForeignKey(Category)
And in category the tags has been defined as:
tags = models.ManyToManyField('Tag')
def get_apps(self, obj):
if obj.username != "admin":
return
else:
apps = UserAppSerializer(read_only=True,
many=True)
return apps
This function is incorrect.
that function is getting user instance as parameter obj and is supposed to return value for app field.
However the function doesn't do anything with the user instance and instead of returning value return an empty instance of UserAppSerializer.
also
app = models.ManyToManyField(Application)
This should've been called apps for clarity
apps = models.ManyToManyField(Application)
Then your serializer function can be re-written like this
def get_apps(self, obj):
if obj.username != "admin":
return
else:
return UserAppSerializer(instance=obj.apps,
read_only=True, many=True).data

Categories