How to get m2m field in validated_data? - django

I've a model:
class ListingPrice(Timestamps):
price = models.ForeignKey("Price", on_delete=models.CASCADE)
location = models.ForeignKey("location", on_delete=models.CASCADE)
class Meta:
unique_together = ["price", "location"]
class Price(Timestamps):
package = models.ForeignKey("products.Package", on_delete=models.CASCADE)
locations = models.ManyToManyField("location", through="ListingPrice")
price = models.DecimalField(max_digits=11, decimal_places=3)
with a serializer:
class LocationSerializer(serializers.ModelSerializer):
name = LocalizedField()
class Meta:
model = location
fields = ['id', 'name']
class PriceSerializer(serializers.ModelSerializer):
locations = LocationSerializer(many=True, read_only=True)
class Meta:
model = Price
fields = ['package', 'locations', 'price']
def create(self, validated_data):
print("validated_data, validated_data)
and viewset:
class PriceViewSet(ModelViewSet):
queryset = Price.objects.all()
serializer_class = PriceSerializer
ordering = ['id']
permissions = {
"GET": ["view_minimum_listing_price", ],
"POST": ["add_minimum_listing_price", ],
'PUT': ['update_minimum_listing_price', ],
'DELETE': ['delete_minimum_listing_price', ],
}
In testing I'mm using the following:
data = {
"price": 12,
"package": self.package.id,
"is_enabled": False,
"location": self.location
}
response = self.client.post(path=self.url, data=data, format='json')
locations doesn't appear in validated_data?
How to get it to assign locations to the instance with post requests?
I also tried to send it with as ids list, but non works. I only field price, package, is_enabled in validated}_data, but location doesn't appear!

read_only=True means the field will be neglected in request body and will only appear in response body
locations = LocationSerializer(many=True, read_only=True)
so, remove it and you can access locations in validated_data

Related

How to post manytomany field value in Postman for API

I have a field which is ManyToMany. I would like to enter the value in POSTMAN for API post operation. But everytime It says: "This field is required." even though I provided the value.
Models:
class Day(models.Model):
day_name = models.CharField(
_("Day Name"), max_length=255, null=True, blank=True)
def __str__(self):
return self.day_name
class TutorProfile(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
tutor_availablility = models.ManyToManyField(
Day,blank=True)
Serializer:
class DaySerializer(serializers.ModelSerializer):
class Meta:
model = Day
fields = '__all__'
class TutorProfileSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(
read_only=True, default=serializers.CurrentUserDefault(), source='user.username')
image_url = serializers.SerializerMethodField('get_image_url')
tutor_availablility = DaySerializer(many=True)
class Meta:
model = TutorProfile
fields = '__all__'
Viewsets:
#authentication_classes([TokenAuthentication])
#permission_classes([IsAuthenticated])
class TutorprofileViewSet(ModelViewSet):
serializer_class = TutorProfileSerializer
http_method_names = ["post", "delete", "get"]
queryset = TutorProfile.objects.all()
With the following models.py (notice that your current Day.__str__ can raise an exception if day_name does not exist):
class Day(models.Model):
day_name = models.CharField(_("Day Name"), max_length=255, blank=True, null=True)
def __str__(self):
return self.day_name if self.day_name else "Unnamed"
class TutorProfile(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
tutor_availability = models.ManyToManyField(Day, blank=True)
You do not need to explicitly add tutor_availability nor user as serializer fields:
class DaySerializer(serializers.ModelSerializer):
class Meta:
model = Day
fields = "__all__"
class TutorProfileSerializer(serializers.ModelSerializer):
# Omitting `image_url` as not reflected in `models.py`
# image_url = serializers.SerializerMethodField('get_image_url')
class Meta:
model = TutorProfile
fields = "__all__"
With this viewset:
#authentication_classes([TokenAuthentication])
#permission_classes([IsAuthenticated])
class TutorProfileViewSet(ModelViewSet):
serializer_class = TutorProfileSerializer
http_method_names = ["post", "delete", "get"]
queryset = TutorProfile.objects.all()
Then, after creating days with IDs 1 and 2 in admin, by sending the tutor_availability field as you are doing it, it should work. Request:
{
"user": 1,
"tutor_availability": [1, 2]
}
Response:
{
"id": 1,
"user": 1,
"tutor_availability": [
1,
2
]
}
Notice as well that I've changed availablility to availability and that it may be unsafe to allow authenticated users to pass the user field in the request, you may want to infer that from the user who makes the request.
In your TutorProfileSerializer you are using the DaySerializer for tutor_availablility field so when you do a post request your post action will wait for a list of dict, what you you need to do in first is to delete this line : from your TutorProfileSerializer and it will works.
tutor_availablility = DaySerializer(many=True)
If you still have the problem then you need to verify the validate method of the TutorProfileSerializer.
And if it works but you want a list of dict(of Day object) for GET request, you need to override the get_serializer_class() of your ViewSet and create two serializers one for post request and a second for get request:
#authentication_classes([TokenAuthentication])
#permission_classes([IsAuthenticated])
class TutorprofileViewSet(ModelViewSet):
serializer_class = TutorProfileSerializer
http_method_names = ["post", "delete", "get"]
queryset = TutorProfile.objects.all()
def get_serializer_class(self):
if self.action.method == 'GET':
return TutorGETProfileSerializer
return super(TutorprofileViewSet, self).get_serializer_class()
and the 2 serializers:
class TutorGETProfileSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(
read_only=True, default=serializers.CurrentUserDefault(), source='user.username')
image_url = serializers.SerializerMethodField('get_image_url')
tutor_availablility = DaySerializer(many=True)
class Meta:
model = TutorProfile
fields = '__all__'
class TutorProfileSerializer(serializers.ModelSerializer):
class Meta:
model = TutorProfile
fields = '__all__'
read_only_fields = ('user',)

I have passed __all__ in fields of meta class and still i am getting back only one field. suggest a better way to fix it I want all the field there

I have included the serializer as well as the views.py, I am new so it might be possible that i made a very silly mistake here, which I am not able to figure out, please review this code and make me understand how to resolve this issue.
serializer.py
class ServiceSerializer(serializers.RelatedField):
def to_representation(self, value):
return value.name
class Meta:
model = Service
fields = ('name')
class SaloonSerializer(serializers.Serializer):
services = ServiceSerializer(read_only = True, many=True)
class Meta:
model = Saloon
fields = (
'name', 'services'
)
Here in the field of SaloonSerializers I have tried multiple things like only name field but still if get just one output which i have attache at the end of this post.
views.py
#api_view(['GET'])
def saloon_list(request):
if request.method=="GET":
saloons = Saloon.objects.all()
serializer = SaloonSerializer(saloons, many=True)
return Response(serializer.data)
models.py
class Service(models.Model):
name = models.CharField(max_length=200, blank=False)
price = models.IntegerField(blank=False)
time = models.IntegerField(blank=False)
def __str__(self):
return self.name
class Saloon(models.Model):
name = models.CharField(max_length = 200, blank=False)
services = models.ManyToManyField(Service)
def __str__(self):
return self.name
output:
[
{
"services": [
"Cutting",
"Shaving",
"Eye Brow",
"Waxing",
"Facial massage"
]
},
{
"services": [
"Cutting",
"Shaving",
"Facial massage"
]
}
]
You need to work with a ModelSerializer: a simple serializer does not care about a Meta, and will only serialize items for which you have specified a serializer field yourself.
You thus should rewrite this to:
# ModelSerializer ↓
class SaloonSerializer(serializers.ModelSerializer):
services = ServiceSerializer(read_only = True, many=True)
class Meta:
model = Saloon
fields = (
'name', 'services'
)

Allow related field in post request in DRF

I have created model with many to many relationship and I have join table when I keep additional variable for it:
class BorderStatus(models.Model):
STATUS_CHOICES = [("OP", "OPEN"), ("SEMI", "CAUTION"), ("CLOSED", "CLOSED")]
origin_country = models.ForeignKey(OriginCountry, on_delete=models.CASCADE, default="0")
destination = models.ForeignKey(Country, on_delete=models.CASCADE, default="0")
status = models.CharField(max_length=6, choices=STATUS_CHOICES, default="CLOSED")
extra = 1
class Meta:
unique_together = [("destination", "origin_country")]
verbose_name_plural = "Border Statuses"
def __str__(self):
return (
f"{self.origin_country.origin_country.name} -> {self.destination.name}"
f" ({self.status})"
)
Other models:
# Create your models here.
class Country(models.Model):
name = models.CharField(max_length=100, unique=True, verbose_name='Country')
class Meta:
verbose_name_plural = "Countries"
def __str__(self):
return self.name
class OriginCountry(models.Model):
origin_country = models.ForeignKey(
Country, related_name="origins", on_delete=models.CASCADE
)
destinations = models.ManyToManyField(
Country, related_name="destinations", through="BorderStatus"
)
class Meta:
verbose_name_plural = "Origin Countries"
def __str__(self):
return self.origin_country.name
Here is my serializer for the endpoint:
class BorderStatusEditorSerializer(serializers.ModelSerializer):
"""Create serializer for editing single connection based on origin and destination name- to change status"""
origin_country = serializers.StringRelatedField(read_only=True)
destination = serializers.StringRelatedField(read_only=True)
class Meta:
model = BorderStatus
fields = ('origin_country', 'destination', 'status')
And my endpoint:
class BorderStatusViewSet(viewsets.ModelViewSet):
queryset = BorderStatus.objects.all()
serializer_class = BorderStatusEditorSerializer
filter_backends = (DjangoFilterBackend,)
filter_fields=('origin_country','destination')
The problem Im having is that I cant create any new combination for the BorderStatus model in this serializer via post request.
If I remove the lines:
origin_country = serializers.StringRelatedField(read_only=True)
destination = serializers.StringRelatedField(read_only=True)
Then the form will work, but then I wont have the string representation of those variables, instead I get IDs.
Is there any way to allow request to accept origin_country and destination while being related fields?
EDIT:
To clarify how OriginCountry works, it is has a nested field:
[{ "id": 1
"origin_country": "Canada",
"dest_country": [
{
"id": 1,
"name": "France",
"status": "CLOSED"
},
{
"id": 2,
"name": "Canada",
"status": "OP"
}
]
},
]
You can try to override perform_create method of the viewset to make the necessary adjustments on-the-fly when new entry is posted:
class BorderStatusViewSet(viewsets.ModelViewSet):
queryset = BorderStatus.objects.all()
serializer_class = BorderStatusEditorSerializer
filter_backends = (DjangoFilterBackend,)
filter_fields=('origin_country','destination')
def perform_create(self, serializer):
origin_country, _ = models.Country.get_or_create(name=self.request.data.get('origin_country')
destination, _ = models.Country.get_or_create(name=self.request.data.get('destination')
return serializer.save(origin_country=origin_country, destination=destination)
Maybe you will also need to adjust your serializer to have:
class CountrySerializer(serializers.ModelSerializer):
class Meta:
model = Country
fields = ['name']
class BorderStatusEditorSerializer(serializers.ModelSerializer):
origin_country = CountrySerializer()
destination = CountrySerializer()
...
Yes, I will try to give this combination.
You get this error because of Incorrect Type exception. Django checks data type validation on the serializer. For example here your dest_country returns a list of dicts but in your model it is a primary key (pk)
That's why on post django says : pk value expected, list received
But you can solve this error by using two different serializers (one to post another by default)
1. Create two different serializers
class BorderStatusEditorSerializer(serializers.ModelSerializer):
"""The First serialiser by default"""
origin_country = serializers.StringRelatedField(read_only=True)
destination = serializers.StringRelatedField(read_only=True)
class Meta:
model = BorderStatus
fields = ('origin_country', 'destination', 'status')
class BorderStatusEditorCreateSerializer(serializers.ModelSerializer):
"""The Second serialiser for create"""
class Meta:
model = BorderStatus
fields = ('origin_country', 'destination', 'status')
2.Add get_serializer_class method to for Viewset
class BorderStatusViewSet(viewsets.ModelViewSet):
queryset = BorderStatus.objects.all()
filter_backends = (DjangoFilterBackend,)
filter_fields=('origin_country','destination')
serializer_classes = {
'create': BorderStatusEditorCreateSerializer, # serializer used on post
}
default_serializer_class = BorderStatusEditorSerializer # Your default serializer
def get_serializer_class(self):
return self.serializer_classes.get(self.action, self.default_serializer_class)

Serializing joined tables in serializers rest framework

So i am trying to serialize multiple joined tables with django serializers. I cant find a way to do this. The query being executed is raw sql.
The models are as below
class UserDetail(models.Model):
user = models.OneToOneField(User, on_delete = models.CASCADE)
mobile_number = models.IntegerField()
national_id = models.CharField(max_length = 30)
date_created = models.DateTimeField(auto_now_add = True)
address = models.CharField(max_length = 250)
merchant_name = models.CharField(null = True, max_length = 30)
class Account(models.Model):
user = models.OneToOneField(User, on_delete = models.CASCADE)
account_number = models.BigIntegerField()
balance = models.FloatField()
account_type = models.ForeignKey(AccountType, on_delete = models.CASCADE)
The json for the expected result should as below
{
"userdetail": {
"mobile_number":""
},
"account": {
"account_number":""
},
"user": {
"first_name": "",
"last_name": "",
"email":""
}
}
The raw sql query is as below
queryset = Account.objects.raw('''SELECT auth_user.first_name,
auth_user.id,
auth_user.last_name,
auth_user.email,
authentication_userdetail.mobile_number,
authentication_account.account_number
FROM
public.auth_user,
public.authentication_account,
public.authentication_userdetail
WHERE
auth_user.id = authentication_userdetail.user_id
AND
auth_user.id = authentication_account.user_id
''')
If there is an alternative way to do this without using raw sql i would greatly appreciate it as im not a fan of executing raw sql queries with django ORM
Tried working with this solution but i cant seem to understand the way the queryset was serialized
Cross-table serialization Django REST Framework
Edited
class UserDetailSerializer(serializers.ModelSerializer):
class Meta:
model = UserDetail
fields = ('mobile_number',)
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = ('account_number',)
class AccountInfoSerializer(serializers.ModelSerializer):
user_detail = UserDetailSerializer()
account = AccountSerializer()
user = serializers.SerializerMethodField()
class Meta:
model = User
fields = ('user_detail', 'account', 'user')
def get_user(self, obj):
return {
'first_name': 'obj.first_name',
'last_name': 'obj.last_name',
'email': 'obj.email',
}
Code for the view
serializer_class = AccountInfoSerializer
def get_queryset(self, *args, ** kwargs):
user_id = self.request.query_params.get('user_id', None)
queryset = None
if user_id is not '':
queryset = UserDetail.objects.raw()
return queryset
you can try such solution:
from rest_framework import serializers
class UserDetailSerializer(serializers.ModelSerializer):
class Meta:
model = UserDetail
fields = ('mobile_number',)
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = ('account_number',)
class UserSerializer(serializers.ModelSerializer):
userdetail = UserDetailSerializer()
account = AccountSerializer()
user = serializers.SerializerMethodField()
class Meta:
model = User
fields = ('userdetail', 'account', 'user')
def get_user(self, obj):
return {
'first_name': 'obj.first_name',
'last_name': 'obj.last_name',
'email': 'obj.email',
}

Save nested object with serializer in django rest framework

I'm getting a Json response to save it in my database, I need to get the items in line_items object. My Serializer and View works fine if I remove the line_items attribute in the model, but when I try to get that object and save it in the database nothing happens. Maybe I'm missing something in my serializer?
Json Structure:
{
"id": 123456,
"email": "jon#doe.ca",
"created_at": "2017-03-29T15:56:48-04:00",
"line_items": [
{
"id": 56789,
"title": "Aviator sunglasses",
"quantity": 1
},
{
"id": 98765,
"title": "Mid-century lounger",
"quantity": 1
}
]
}
My model:
class Line(models.Model):
title = models.CharField(max_length=100)
quantity = models.IntegerField()
class Order(models.Model):
name = models.CharField(max_length=255)
created_at = models.DateTimeField()
total_price = models.DecimalField(max_digits=6,decimal_places=2)
line_items = models.ForeignKey(Line)
My Serializer:
class OrderSerializer(ModelSerializer):
class Meta:
model = Order
fields = '__all__'
My View:
#api_view(['POST'])
def orders(request):
if request.method == 'POST':
json_str = json.dumps(request.data)
resp = json.loads(json_str)
serializer = OrderSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
You have a list in the field-key line_items in your response, as per your models you can't accommodate the data in the tables, what you need is ManyToMany relation between Order and Line,
class Order(models.Model):
name = models.CharField(max_length=255)
created_at = models.DateTimeField()
total_price = models.DecimalField(max_digits=6,decimal_places=2)
line_items = models.ManyToManyField(Line)
and in your serializer,
class OrderSerializer(ModelSerializer):
class Meta:
model = Order
fields = [f.name for f in model._meta.fields] + ['line_items']
def create(self, validated_data):
line_items = validated_data.pop('line_items')
instance = super(OrderSerializer, self).create(validated_data)
for item in line_items:
instance.line_items.add(item['id'])
instance.save()
return instance
Just add depth=1 in your serializer. It will do-
class OrderSerializer(ModelSerializer):
class Meta:
depth = 1
fields = '__all__'
I think you should add an explicit line_items field to OrderSerializer. Like this:
class OrderSerializer(ModelSerializer):
line_items = LineSerializer(many=True)
class Meta:
model = Order
fields = '__all__'