I have two models:
class Manufacturer(models.Model):
name = models.CharField(max_length = 100)
class Car(models.Model):
manufacturer = models.ForeignKey(Manufacturer, blank = True, null = True)
My serialisers:
class ManufacturerSerializer(serializers.ModelSerializer):
class Meta:
model = Manufacturer
fields = ('id', 'name')
class CarSerializer(serializers.ModelSerializer):
manufacturer = Manufact
class Meta:
model = Car
fields = ('id', 'name', 'manufacturer')
def validate(self, attrs):
try:
attrs['manufacturer'] = Manufacturer.objects.get(pk = attrs['manufacturer'])
except Manufacturer.DoesNotExist:
raise ValidationError(_('Not found'))
return attrs
My views:
class CarList(generics.ListCreateAPIView):
queryset = Car.objects.all()
serializer_class = CarSerializer
When I try to add a new Car calling POST /cars/ I get a validation error manufacturer is a required field. Car model expects manufacturer field to be Manufacturer object but to make messages small I pass it the manufacturer_id instead.
I know that should raise a ValidationError so to fix this I added a validate(..) to my CarSerializer so during validation I check if Manufacturer by this ID exists and I assign it to attrs.
Problem is this validate(..) is never called I even tried adding a post() method to CarList view and manually calling is_valid() to no success.
But I still get validation errors which I assume are coming from the Model.
It doesn't work because you are complicating things. The ManufacturerSerializer is not necessary (for this view anyway). By default the 'manufacturer' field will be represented as a PrimaryKeyRelatedField which will resolve your manufacturer_id to a Manufacturer object automatically, so you don't need your validate method either.
Updated serializers:
class CarPostSerializer(serializers.ModelSerializer):
class Meta:
model = Car
fields = ('id', 'name', 'manufacturer')
class CarGetSerializer(CarPostSerializer):
manufacturer = ManufacturerSerializer()
Updated view:
class CarList(generics.ListCreateAPIView):
queryset = Car.objects.all()
def get_serializer_class(self):
if self.request.method == 'POST':
return CarPostSerializer
else:
return CarGetSerializer
Related
I have Contact model to list the followers of an User object, I try to filter the contacts of a User but I still could not manage get a correct queryset. My Contact model is simple with two ForeignKey:
class Contact(models.Model):
user_from = models.ForeignKey(User,related_name='rel_from_set', on_delete=models.CASCADE,)
user_to = models.ForeignKey(User,related_name='rel_to_set', on_delete=models.CASCADE,)
def __str__(self):
return '{} follow {}'.format(self.user_from, self.user_to)
I have created serializers for User and Contact:
##Contact Serializer
class ContactsSerializer(serializers.ModelSerializer):
user_from = serializers.StringRelatedField(read_only=True)
user_to = serializers.StringRelatedField(read_only=True)
class Meta:
model = Contact
fields = ["user_from", "user_to"]
##UserSerializer
class UserInformationSerializer(serializers.ModelSerializer):
followers = ContactsSerializer(many=True, read_only=True)
class Meta:
model = User
fields = ['first_name', 'last_name', 'followers']
And try to make a query through views:
class FollowerListView(APIView):
queryset = Contact.objects.all()
serializer_class = ContactsSerializer
lookup_field = "username"
def get(self, request, format=None, slug=None):
kwarg_username = self.kwargs.get("slug")
user = User.objects.filter(is_active=1).filter(username=kwarg_username)
print(user.username)
contacts = Contact.objects.filter(user_to=user.id)
serializer = ContactsSerializer(contacts)
return Response(serializer.data)
Now I get error message:
AttributeError at /api/member/ytsejam/followers/
'QuerySet' object has no attribute 'username'
print(user.username)
If i try print(user) I can see the user an Object.
Can you guide me how to correct?
Thanks
filter will always return a queryset. If you expect to retrieve one single item, use get.
So that it looks like that:
def get(self, request, format=None, slug=None):
kwarg_username = self.kwargs.get("slug")
user = User.objects.filter(is_active=1).get(username=kwarg_username)
print(user.username)
contacts = Contact.objects.filter(user_to=user.id)
serializer = ContactsSerializer(contacts)
return Response(serializer.data)
You could, of course, do this on one take:
User.objects.get(is_active=1, username=kwarg_username)
But beware, if there are two rows in your model that would satisfy this call, Django will throw an error. Best make sure that the username has a unique constraint.
I am in a issue in which my api is creating a duplicate data as I am just passing ingredient name and its restaurant not Pk etc. So to prevent this thing I made a
class Meta:
unique_together = ('restaurant' ,'name')
constraint in my model . Before this everything was fine just duplicate entries were creating. Now after adding this constraint its saying 'Restaurant field is required' and my serializer is not valid.
My Ingredient model is like this
class Ingredient(models.Model):
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE )
name = models.CharField(max_length=255 ,)
class Meta:
unique_together = ('restaurant' ,'name')
def __str__(self):
return str(self.name)
and my Ingredient Serializer is like
class IngredientsSerializer(serializers.ModelSerializer):
restaurant = RestaurantSerializer(required=False)
class Meta:
model = Ingredient
fields = '__all__'
def create(self, validated_data):
restaurant = validated_data.get('restaurant')
name = validated_data.get('name', None)
ingredient = Ingredient.objects.create(restaurant=restaurant, name=name)
return ingredient
And my view.py for serialize is like
#permission_classes([AllowAny])
class CreateIngredients(APIView):
def post(self, request):
serializer = IngredientsSerializer(data=request.data)
if serializer.is_valid():
restaurant=Restaurant.objects.get(id=request.POST['restaurant'])
obj_article = serializer.save(restaurant=restaurant)
return Response(success_response(data='none', msg='Ingredient added'), status=status.HTTP_200_OK)
Looks like you are not sending restaurant in your request.
Even though in your serializer you've defined that restaurant isn't required.
restaurant = RestaurantSerializer(required=False)
What makes it invalid is the create method. In there you have
ingredient = Ingredient.objects.create(restaurant=restaurant, name=name)
which uses objects create method (your restaurant argument here is probably None) and since you've defined unique together with restaurant and name this means neither of them can be None.
I create some dotted source field in my serializer. I did it cause have to display name value of foreign key not pk value. But when I trying to POST from frontend djang throws this : AssertionError at /api/my-api/
The .create() method does not support writable dotted-source fields by default.
Write an explicit .create() method for serializer MySerializer, or set read_only=True on dotted-source serializer fields.
So, when I set read_only = True my POST from frontend to request null for every field from dotted-source serializer fields.
This is my serializer:
class FcaWorksSerializer(serializers.ModelSerializer):
fell_form = serializers.CharField(source="fell_form.name" )
#...
main_type = serializers.CharField(source="main_type.name")
class Meta:
model = FcaWorks
fields = ('id_fca','wkod', 'main_type','fell_form','fell_type','kind',\
'sortiment','vol_drew','use_type','fca_res','ed_izm','vol_les','act_name',\
'obj_type','use_area','indicator','comment','date_report')
How I can to solve this problem?
Override the __init__() method of the serializer to adjust the serializer condition
class FcaWorksSerializer(serializers.ModelSerializer):
fell_form = serializers.CharField()
# ...
main_type = serializers.CharField()
class Meta:
model = FcaWorks
fields = ('id_fca', 'wkod', 'main_type', 'fell_form', 'fell_type', 'kind',
'sortiment', 'vol_drew', 'use_type', 'fca_res', 'ed_izm', 'vol_les', 'act_name',
'obj_type', 'use_area', 'indicator', 'comment', 'date_report')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.context['request'].method == 'GET':
self.fields['fell_form'].source = "fell_form.name"
self.fields['main_type'].source = "main_type.name"
def create(self, validated_data):
# here you will get the data
fell_form = validated_data['fell_form']
main_type = validated_data['main_type']
From the docs, there are multiple ways to deal with ForeignKey relations. You don't have to make your own create method if the Foreignkey relations are not "many-to-many".
In your case you can use one of the following:
SlugRelatedField
PrimaryKeyRelatedField
class FcaWorksSerializer(serializers.ModelSerializer):
fell_form = serializers.SlugRelatedField(slug_field="name", queryset = ***"""The queryset that fell_form came from"""*** )
#...
main_type = serializers.SlugRelatedField(slug_field="name", queryset = ***"""The queryset main_type came from"""***)
class Meta:
model = FcaWorks
fields = ('id_fca','wkod', 'main_type','fell_form','fell_type','kind',\
'sortiment','vol_drew','use_type','fca_res','ed_izm','vol_les','act_name',\
'obj_type','use_area','indicator','comment','date_report')
Then PrimaryKeyRelated Field usage:
class FcaWorksSerializer(serializers.ModelSerializer):
fell_form = serializers.PrimaryKeyRelatedField(source ="fell_form.name", queryset = ***"""The queryset that fell_form came from"""*** )
#...
main_type = serializers.PrimaryKeyRelatedField(source="main_type.name", queryset = ***"""The queryset main_type came from"""***)
This has worked for me when I had the same problem, however like previously stated for "Many-to-Many" field you have to explicitly write the create and update methods.
I want to save a sent json data to db by django-rest-framework.
the problem is, not saving the relation and returns error.
The bellow snippet is my models:
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, related_name='profile', on_delete=models.CASCADE)
name = models.CharField(max_length=30)
family = models.CharField(max_length=50)
class Klass(models.Model):
title = models.CharField(max_length=50)
description = models.CharField(max_length=500)
teacher = models.ForeignKey(Profile, related_name='teacher', on_delete=models.CASCADE)
I use below serializer for serializing/deserializing the Klass model.
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ('pk', 'name', 'family')
class KlassSerializer(serializers.ModelSerializer):
teacher = ProfileSerializer()
class Meta:
model = Klass
fields = ('id', 'title', 'description', 'teacher')
now when I prepare a JSON object and send it to the view, it returns error. the below is the view class:
class KlassView(APIView):
"""for SELECT, INSERT Queries"""
def get(self, request, pk):
# somthing
#csrf_exempt
def post(self,request, pk=None):
"""For Creating A Class"""
serializer = KlassSerializer(data=request.data)
if serializer.is_valid():
teacher = ProfileSerializer(request.data['teacher']['pk'])
serializer.teacher = teacher.data
serializer.save()
return Response({'data': serializer.data})
else:
return Response({'data': serializer.errors})
and the error is:
The .create() method does not support writable nested fields by default.
Write an explicit .create() method for serializer mainp.serializers.KlassSerializer, or set read_only=True on nested serializer fields.
How can I save relation in KlassSerializer in order to save to db?
At first change your serializer like below:
class KlassSerializer(serializers.ModelSerializer):
# teacher = ProfileSerializer() # No need to this!
class Meta:
model = Klass
# fields = ('id', 'title', 'description', 'teacher')
fields = ('id', 'title', 'description') # Omit teacher
Then get profile from requested user and pass it to your serializer:
def post(self,request, pk=None):
"""For Creating A Class"""
serializer = KlassSerializer(data=request.data)
if serializer.is_valid():
teacher = ProfileSerializer(request.data['teacher']['pk'])
serializer.teacher = teacher.data
serializer.save(teacher=request.user.profile) # Retrieve teacher and stroe
return Response({'data': serializer.data})
else:
return Response({'data': serializer.errors})
Just override the create method of ModelSerializer in KlassSerializer.
class KlassSerializer(serializers.ModelSerializer):
teacher = ProfileSerializer()
class Meta:
model = Klass
fields = ('id', 'title', 'description', 'teacher')
def create(self, validated_data):
profile = Profile.objects.filter(pk=validated_data['teacher']['pk'])
if profile:
k = Klass()
k.teacher = profile
...
I am trying to figure out the best way to add annotated fields, such as any aggregated (calculated) fields to DRF (Model)Serializers. My use case is simply a situation where an endpoint returns fields that are NOT stored in a database but calculated from a database.
Let's look at the following example:
models.py
class IceCreamCompany(models.Model):
name = models.CharField(primary_key = True, max_length = 255)
class IceCreamTruck(models.Model):
company = models.ForeignKey('IceCreamCompany', related_name='trucks')
capacity = models.IntegerField()
serializers.py
class IceCreamCompanySerializer(serializers.ModelSerializer):
class Meta:
model = IceCreamCompany
desired JSON output:
[
{
"name": "Pete's Ice Cream",
"total_trucks": 20,
"total_capacity": 4000
},
...
]
I have a couple solutions that work, but each have some issues.
Option 1: add getters to model and use SerializerMethodFields
models.py
class IceCreamCompany(models.Model):
name = models.CharField(primary_key=True, max_length=255)
def get_total_trucks(self):
return self.trucks.count()
def get_total_capacity(self):
return self.trucks.aggregate(Sum('capacity'))['capacity__sum']
serializers.py
class IceCreamCompanySerializer(serializers.ModelSerializer):
def get_total_trucks(self, obj):
return obj.get_total_trucks
def get_total_capacity(self, obj):
return obj.get_total_capacity
total_trucks = SerializerMethodField()
total_capacity = SerializerMethodField()
class Meta:
model = IceCreamCompany
fields = ('name', 'total_trucks', 'total_capacity')
The above code can perhaps be refactored a bit, but it won't change the fact that this option will perform 2 extra SQL queries per IceCreamCompany which is not very efficient.
Option 2: annotate in ViewSet.get_queryset
models.py as originally described.
views.py
class IceCreamCompanyViewSet(viewsets.ModelViewSet):
queryset = IceCreamCompany.objects.all()
serializer_class = IceCreamCompanySerializer
def get_queryset(self):
return IceCreamCompany.objects.annotate(
total_trucks = Count('trucks'),
total_capacity = Sum('trucks__capacity')
)
This will get the aggregated fields in a single SQL query but I'm not sure how I would add them to the Serializer as DRF doesn't magically know that I've annotated these fields in the QuerySet. If I add total_trucks and total_capacity to the serializer, it will throw an error about these fields not being present on the Model.
Option 2 can be made work without a serializer by using a View but if the model contains a lot of fields, and only some are required to be in the JSON, it would be a somewhat ugly hack to build the endpoint without a serializer.
Possible solution:
views.py
class IceCreamCompanyViewSet(viewsets.ModelViewSet):
queryset = IceCreamCompany.objects.all()
serializer_class = IceCreamCompanySerializer
def get_queryset(self):
return IceCreamCompany.objects.annotate(
total_trucks=Count('trucks'),
total_capacity=Sum('trucks__capacity')
)
serializers.py
class IceCreamCompanySerializer(serializers.ModelSerializer):
total_trucks = serializers.IntegerField()
total_capacity = serializers.IntegerField()
class Meta:
model = IceCreamCompany
fields = ('name', 'total_trucks', 'total_capacity')
By using Serializer fields I got a small example to work. The fields must be declared as the serializer's class attributes so DRF won't throw an error about them not existing in the IceCreamCompany model.
I made a slight simplification of elnygreen's answer by annotating the queryset when I defined it. Then I don't need to override get_queryset().
# views.py
class IceCreamCompanyViewSet(viewsets.ModelViewSet):
queryset = IceCreamCompany.objects.annotate(
total_trucks=Count('trucks'),
total_capacity=Sum('trucks__capacity'))
serializer_class = IceCreamCompanySerializer
# serializers.py
class IceCreamCompanySerializer(serializers.ModelSerializer):
total_trucks = serializers.IntegerField()
total_capacity = serializers.IntegerField()
class Meta:
model = IceCreamCompany
fields = ('name', 'total_trucks', 'total_capacity')
As elnygreen said, the fields must be declared as the serializer's class attributes to avoid an error about them not existing in the IceCreamCompany model.
You can hack the ModelSerializer constructor to modify the queryset it's passed by a view or viewset.
class IceCreamCompanySerializer(serializers.ModelSerializer):
total_trucks = serializers.IntegerField(readonly=True)
total_capacity = serializers.IntegerField(readonly=True)
class Meta:
model = IceCreamCompany
fields = ('name', 'total_trucks', 'total_capacity')
def __new__(cls, *args, **kwargs):
if args and isinstance(args[0], QuerySet):
queryset = cls._build_queryset(args[0])
args = (queryset, ) + args[1:]
return super().__new__(cls, *args, **kwargs)
#classmethod
def _build_queryset(cls, queryset):
# modify the queryset here
return queryset.annotate(
total_trucks=...,
total_capacity=...,
)
There is no significance in the name _build_queryset (it's not overriding anything), it just allows us to keep the bloat out of the constructor.