How to send several fields in the response with a PUT request? - python

I would like when my PUT request is successful it returns me a response with all the fields in my PlantSerializer because currently the response returns me this:
{
"id":48,
"name":"Jar",
"width":"50",
"height":"50",
"exposure":"None",
"qr_code":"",
"garden":65,
"plant":[
7
]
}
But, I would like the response to return this instead:
{
"id":48,
"name":"Jar",
"width":"50",
"height":"50",
"exposure":"None",
"qr_code":"",
"garden":65,
"plant":[
"id":7,
"name":"Artichoke",
"image":null
]
}
How can I achieve this result?
Here is my serializers and my model class :
class Plot(models.Model):
name = models.CharField(max_length=50)
garden = models.ForeignKey('perma_gardens.Garden', on_delete=models.CASCADE)
width = models.CharField(max_length=50, blank=True, null=True)
height = models.CharField(max_length=50, blank=True, null=True)
plant = models.ManyToManyField('perma_plants.Plant', related_name='%(class)s_plant', blank=True)
# Here is my serializers :
class GardenSerializer(serializers.ModelSerializer):
class Meta:
model = Garden
fields = ('id', 'name',)
class PlantSerializer(serializers.ModelSerializer):
class Meta:
model = Plant
fields = ('id', 'name', 'image')
class ReadPlotSerializer(serializers.ModelSerializer):
garden = GardenSerializer(required=True)
plant = PlantSerializer(many=True)
id = serializers.IntegerField(read_only=True)
class Meta:
model = Plot
fields = '__all__'
read_only_fields = [fields]
class WritePlotSerializer(serializers.ModelSerializer):
class Meta:
model = Plot
fields = '__all__'
And here is my views :
class PlotViewSet(viewsets.ModelViewSet):
queryset = Plot.objects.all()
def create(self, request, *args, **kwargs):
serializer = WritePlotSerializer(data=request.data, many=isinstance(request.data, list))
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
snippet = self.get_object(pk)
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
def partial_update(self, request, *args, **kwargs):
instance = self.queryset.get(pk=kwargs.get('pk'))
serializer = WritePlotSerializer(instance, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
def get_serializer_class(self):
if self.action in ("list", "retrieve"):
return ReadPlotSerializer
return WritePlotSerializer

In the fuction partial_update you are utilizing the WritePlotSerializer, which only has the plant field implicitly through the fields=__all__ value. This is probably causing drf to use a PrimaryKeyRelatedField, and as such you don't get all the extra fields you defined in your PlantSerializer.
If I understand correctyl you want to use the WritePlotSerializer in the update but use the ReadPlotSerializer when returning the object. You probably should combine both of them in a single serializer by overriding the update method in order to support updating the nested Plant objects. Here is the related documentation.
Alternatively, if you don't want to update the Plants values you can use a slightly modified version of the ReadPlotSerializer in all calls:
class PlotSerializer(serializers.ModelSerializer):
garden = GardenSerializer(required=True, read_only=True)
plant = PlantSerializer(many=True, read_only=True)
id = serializers.IntegerField(read_only=True)
class Meta:
model = Plot
fields = '__all__'

Related

Rest Framework cant save serializer with foreign key

I have next serializers:
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = '__all__'
class PostSerializer(serializers.ModelSerializer):
category = CategorySerializer()
class Meta:
model = Post
fields = ['id', 'title', 'text', 'date', 'category']
And here is my view:
#api_view(['POST'])
def create_post(request):
serializer = PostSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
else:
return Response(serializer.errors)
return Response(serializer.data)
I want to create new Post object, but when I pass an category id at form it does not work, it is not saving my object. I tried to replace create method at my PostSerializer, to this:
def create(self, validated_data):
category_id = validated_data.pop('category')
post = Post.objects.create(**validated_data, category=category_id)
return post
but this dont work. Using postman formdata it is saying, that category field is required despite I filled it.
Here is my models:
class Category(models.Model):
name = models.CharField(max_length=512)
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length=512)
text = models.TextField()
date = models.DateTimeField(auto_now_add=True)
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='category')
You need a category object not just an id, so try this
#api_view(['POST'])
def create_post(request):
category_id = request.data['category'] # or however you are sending the id
serializer = PostSerializer(data=request.data)
if serializer.is_valid():
category = Category.objects.get(id=category_id)
serializer.save(category=category)
else:
return Response(serializer.errors)
return Response(serializer.data)
or you can do something similar in the create method of the serializer

DRF: how to change the value of the model fields before saving to the database

If I need to change some field values before saving to the database as I think models method clear() is suitable. But I can't call him despite all my efforts.
For example fields email I need set to lowercase and fields nda I need set as null
models.py
class Vendors(models.Model):
nda = models.DateField(blank=True, null=True)
parent = models.OneToOneField('Vendors', models.DO_NOTHING, blank=True, null=True)
def clean(self):
if self.nda == "":
self.nda = None
class VendorContacts(models.Model):
....
vendor = models.ForeignKey('Vendors', related_name='contacts', on_delete=models.CASCADE)
email = models.CharField(max_length=80, blank=True, null=True, unique=True)
def clean(self):
if self.email:
self.email = self.email.lower()
serializer.py
class VendorContactSerializer(serializers.ModelSerializer):
class Meta:
model = VendorContacts
fields = (
...
'email',)
class VendorsSerializer(serializers.ModelSerializer):
contacts = VendorContactSerializer(many=True)
class Meta:
model = Vendors
fields = (...
'nda',
'contacts',
)
def create(self, validated_data):
contact_data = validated_data.pop('contacts')
vendor = Vendors.objects.create(**validated_data)
for data in contact_data:
VendorContacts.objects.create(vendor=vendor, **data)
return vendor
views.py
class VendorsCreateView(APIView):
"""Create new vendor instances from form"""
permission_classes = (permissions.AllowAny,)
serializer_class = VendorsSerializer
def post(self, request, *args, **kwargs):
serializer = VendorsSerializer(data=request.data)
try:
serializer.is_valid(raise_exception=True)
serializer.save()
except ValidationError:
return Response({"errors": (serializer.errors,)},
status=status.HTTP_400_BAD_REQUEST)
else:
return Response(request.data, status=status.HTTP_200_OK)
As I learned from the documentation
Django Rest Framework serializers do not call the Model.clean when
validating model serializers
In dealing with this problem, I found two ways to solve it.
1. using the custom method at serializer. For my case, it looks like
class VendorsSerializer(serializers.ModelSerializer):
contacts = VendorContactSerializer(many=True)
class Meta:
model = Vendors
fields = (...
'nda',
'contacts',
)
def create(self, validated_data):
contact_data = validated_data.pop('contacts')
vendor = Vendors.objects.create(**validated_data)
for data in contact_data:
VendorContacts.objects.create(vendor=vendor, **data)
return vendor
def validate(self, attrs):
instance = Vendors(**attrs)
instance.clean()
return attrs
Using full_clean() method. For me, it looks like
class VendorsSerializer(serializers.ModelSerializer):
contacts = VendorContactSerializer(many=True)
class Meta:
model = Vendors
fields = (...
'nda',
'contacts',
)
def create(self, validated_data):
contact_data = validated_data.pop('contacts')
vendor = Vendors(**validated_data)
vendor.full_clean()
vendor.save()
for data in contact_data:
VendorContacts.objects.create(vendor=vendor, **data)
return vendor
But in both cases, the clean() method is not called. I really don't understand what I'm doing wrong.
In my case I had the same problem but with validation feature
I used the way below and it works for me (not excludes the way found above):
class CustomViewClass(APIView):
def post(self, request, format=None):
prepared_data_variable = 'some data in needed format'
serializer = CustomSerializer(data=request.data)
if serializer.is_valid(self):
serializer.validated_data['field_name'] = prepared_data_variable
serializer.save()
return Response(data=serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
This string is key for my solution serializer.validated_data['field_name'] = prepared_data_variable
For DRF you can change your serializer before save as below...
First of all, you should check that serializer is valid or not, and if it is valid then change the required object of the serializer and then save that serializer.
if serializer.is_valid():
serializer.object.user_id = 15 # For example
serializer.save()
UPD!
views.py
class VendorsCreateView(APIView):
"""Create new vendor instances from form"""
permission_classes = (permissions.AllowAny,)
serializer_class = VendorsSerializer
def post(self, request, *args, **kwargs):
data = request.data
if data['nda'] == '':
data['nda'] = None
for contact in data['contacts']:
if contact['email']:
print(contact['email'])
contact['email'] = contact['email'].lower()
serializer = VendorsSerializer(data=request.data)
try:
serializer.is_valid(raise_exception=True)
serializer.save()
except ValidationError:
return Response({"errors": (serializer.errors,)},
status=status.HTTP_400_BAD_REQUEST)
To answer your question: just override save() method for your models as written in docs. There you can assign any values to your model instance directly before saving it in database.
Also, you should probably use models.EmailField for your email fields which will get rid of your lower() check.

Return mean of values in models instead of every value

I am trying to make a Movie project in django. I would like to have one view for film's ratings that can take single rate_value but by GET returns mean of rating values for a film.
I have no idea where should I start. I tried some changes in view and serializer but didnt work. Here are:
views.py:
class RateListView(generics.ListCreateAPIView):
permission_classes = [AllowAny]
serializer_class = RateSerializer
lookup_url_kwarg = 'rateid'
def get_queryset(self):
rateid = self.kwargs.get(self.lookup_url_kwarg)
queryset = Rate.objects.filter(film = rateid)
# .aggregate(Avg('rate_value'))
if queryset.exists():
return queryset
else:
raise Http404
def post(self, request, *args, **kwargs):
serializer = RateSerializer(data = request.data)
if serializer.is_valid():
if Film.objects.filter(pk = self.kwargs.get(self.lookup_url_kwarg)).exists():
serializer.save(film_id = self.kwargs.get(self.lookup_url_kwarg))
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
raise Http404
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
serializer.py
class RateSerializer(serializers.ModelSerializer):
class Meta:
model = Rate
fields = ('rate_value',)
and model.py
class Rate(models.Model):
film = models.ForeignKey(Film, on_delete = models.CASCADE)
rate_value = models.IntegerField(validators = [
MinValueValidator(1),
MaxValueValidator(10)
]
)
def __str__(self):
return str(self.film.title) + str(self.rate_value)
Right now it returns single values correctly.
Try something like this:
serializers.py
class RateListSerializer(serializers.Serializer):
avg_rate_value = serializers.FloatField()
class RateSerializer(serializers.ModelSerializer):
class Meta:
model = Rate
fields = ("rate_value", "film")
views.py
class RateListView(generics.ListCreateAPIView):
permission_classes = [AllowAny]
def get_queryset(self):
if self.action == "list":
queryset = Film.objects.all()
film_id = self.request.query_params.get("film_id", None)
if film_id is not None:
queryset = queryset.filter(id=film_id)
return queryset.annotate(avg_rate_value=Avg("rate__rate_value"))
return Rate.objects.all()
def get_seializer_class(self):
if self.action == "list":
return RateListSerializer
return RateSerializer
class Film(models.Model:
...
#property
def mean_rating(self):
ratings = self.ratings.all().values_list('rate_value', flat=True)
if ratings:
return sum(ratings)/len(ratings)
# return a separate default value here if no ratings
class Rate(models.Model):
film = models.ForeignKey(Film, on_delete = models.CASCADE, related_name='ratings')
rate_value = models.IntegerField(validators = [
MinValueValidator(1),
MaxValueValidator(10)
]
)
...
class FilmSerializer(serializers.ModelSerializer):
class Meta:
model = Film
fields = ('id','name','mean_rating',)
Without getting into the architecture of your app, here's a pattern that can be used. The Film object has a computed property that calls ratings, the related_name supplied to a film rating, to get all related ratings and returns the mean.
Properties of models can be used as serializer fields as long as then provide serializable values.
For more on related_name for models in Django, see here - What is `related_name` used for in Django?

Django Rest Framework I can't use Serializer save model of have foreign key

I'm a Django Rest Framework and Django newbie
i can use random data to make stages but i can't use serializer to add new stages.
My model and serializer
class Stage(models.Model):
class Meta:
db_table = 'stage'
stage_id = models.AutoField(primary_key=True)
stage_name = models.CharField(max_length=64, null=False)
company = models.ForeignKey(
Company,
db_column='id',
on_delete=models.CASCADE,
)
class StageSerializer(ModelSerializer):
stage_id = IntegerField(read_only=True)
class Meta:
model = Stage
fields = [
'stage_id',
'stage_name',
'company',
]
def update(self, instance, validated_data):
pass
def create(self, validated_data):
# create stages
stage = create_stage(**validated_data)
return stage
view.py
class StageListAPIView(APIView):
def post(self, request, company_id):
data = request.data.copy()
company = get_company_by_id(company_id)
data['company'] = company.pk
serializer = StageSerializer(data=data)
if not serializer.is_valid(raise_exception=True):
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
new_data = serializer.validated_data
serializer.save(company=company)
return Response(new_data, status=HTTP_200_OK)
request.data
<QueryDict: {'stage_name': ['kAkSdKq9Gt'], 'company': [6]}>
i will receive error:
TypeError: Object of type Company is not JSON serializable
i can't understand it and i don't know how to use serializer to save foreign key.
You need to serialize the Company instance before you can include it in your StageSerializer.
A simple example would be something like
class CompanySerializer(ModelSerializer):
class Meta:
model = Company
fields = '__all__'
And then to include that in your StageSerializer:
class StageSerializer(ModelSerializer):
stage_id = IntegerField(read_only=True)
company = CompanySerializer(source='company', read_only=True)
class Meta:
model = Stage
fields = [
'stage_id',
'stage_name',
'company',
]

How to update a related model after another model is created?

I need to automatically update my Account's amount when a Transaction is created.
I have Transaction model's model.py:
class Transaction(models.Model):
user = models.ForeignKey(User, default=None)
account = models.ForeignKey(Account, default=None)
...
Its serializers.py:
class TransactionSerializer(serializers.ModelSerializer):
class Meta:
model = Transaction
fields = ('id', 'user', 'account_id', 'category_id', 'name', 'amount', 'description', 'created_at')
def create(self, validated_data):
return Transaction.objects.create(**validated_data)
And its views.py:
class TransactionList(APIView):
def get(self, request):
user_id = request.user.pk
transactions = Transaction.objects.filter(user_id=user_id).order_by('-created_at')
serializer = TransactionSerializer(transactions, many=True)
return Response(serializer.data)
def post(self, request):
account_id = request.data['account_id']
category_id = request.data['category_id']
serializer = TransactionSerializer(data=request.data)
if serializer.is_valid():
serializer.save(user=request.user, account_id=account_id, category_id=category_id)
self.update_account(request)
return Response(serializer.data, status=HTTP_201_CREATED)
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
def update_account(self, request):
account_id = request.data['account_id']
category_id = request.data['category_id']
account = Account.objects.get(pk=account_id)
category = Category.objects.get(pk=category_id)
if category.type == 'expense':
account.amount = (account.amount - int(self.request['amount']))
else:
account.amount = (account.amount + int(self.request['amount']))
# Then what?
I thought of creating a custom method that will be executed within the condition if serializer is valid, that would get the account and category by their id. I could, so far, display the current values they have, such as amount and name, but after that, I don't know what to do. I'm imagining I need to use my AccountSerializer, but I'm not sure.
The easiest way would be to override the save method of your Transaction model:
class Transaction(models.Model):
user = models.ForeignKey(User, default=None)
account = models.ForeignKey(Account, default=None)
...
def save(self, *args, **kwargs):
super(Transaction, self).save(*args, **kwargs)
# Update self.account
See Django documentation for details.

Categories

Resources