Over-riding update method in django modelViewSet - python

I'm having trouble defining an update method on a serializer class in django. This is my basic model
class Category(models.Model):
category = models.CharField(max_length = 100)
def __str__(self):
return self.category
class Items(models.Model):
category = models.ForeignKey(Category)
item = models.CharField(max_length = 100)
rate = models.DecimalField(max_digits = 20,decimal_places =2)
def __str__(self):
return self.item
and the below is my serializer:
class ItemSerializer(serializers.ModelSerializer):
category = serializers.CharField(source = 'category.category',read_only = True)
category_pk = serializers.IntegerField(source = 'category.pk')
class Meta:
model = Items
fields = ('pk','category','category_pk','item','rate',)
def update(self,instance,validated_data):
category_pk = validated_data.get('category_pk',instance.category_pk)
instance.category = Category.objects.get(pk = category_pk)
instance.item = validated_data.get('item',instance.item)
instance.rate = validated_data.get('rate',instance.rate)
instance.save()
return instance
I'm getting an error when using the PUT method from AJAX stating that the Item object does not have a category_pk field (but i'm not including that in the instance.save() method)
below is my AJAX request
$('#update-item').on('click',function(){
var dat = {
'category':$("#category-box").find('option[value='+cat_pk+']').text(),
'category_pk':$('#category-box').val(),
'item':$('#Items').val(),
'rate':$('#Rate').val()
};
console.log(dat);
$.ajax({
type : 'PUT',
url: 'http://127.0.0.1:8000/billing/items/'+pk+'/',
data : JSON.stringify(dat),
contentType: 'application/json',
crossDomain:true,
success : function(json){
alert('ok');
},
error : function(json){
alert('error with PUT')
}
});
});
I'm over-riding the update method because rest framework threw an error intially stating that it cant write nested fields.
KJ

By default DRF will use the pk for the category field on a ModelSerializer, but in your case you are setting the category field as read_only=True, and on your model is no category_pk field. I would change the serializer like this, and then post the pk to the category field and not the category_pk field. You can then also remove the update method override.
class ItemSerializer(serializers.ModelSerializer):
# I assume you changed the category field to readonly to display the text of the category
category_name = serializers.ReadOnlyField(source='category.category')
class Meta:
model = Items
fields = ('pk','category','category_name','item','rate',)

Related

Return a list by a POST Request

I'm new to django and python, I want to return all the objects having the foreign key provided by a post request.
this is my model:
class Product(models.Model):
name = models.CharField(max_length=200)
image = models.CharField(max_length=400)
price = models.CharField(max_length=200)
isFavorite = models.BooleanField(default=False)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
this is my serializer:
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ('id', 'name', 'image', 'price', 'isFavorite')
this is my code in views.py:
class ListProductsOfCategory(generics.ListAPIView):
serializer_class = ProductSerializer()
def post(self, request, *args, **kwargs):
# catch the category id of the products.
category_id = request.data.get("category_id", "")
# check if category id not null
if not category_id:
"""
Do action here
"""
# check if category with this id exists
if not Category.objects.filter(id=category_id).exists():
"""
Do action here
"""
selected_category = Category.objects.get(id=category_id)
# get products of this provided category.
products = Product.objects.filter(category=selected_category)
serialized_products = []
# serialize to json all product fetched
for product in products:
serializer = ProductSerializer(data={
"id": product.id,
"name": product.name,
"image": product.image,
"price": product.price,
"isFavorite": product.isFavorite
})
if serializer.is_valid(raise_exception=True):
serialized_products.append(serializer.data)
else:
return
return Response(
data=serialized_products
,
status=status.HTTP_200_OK
)
this code partially worked, its returning the below reponse.
the problem is that the primary key "id" of the product is missing, I want the response to be like that :
P.S. If anyone can enhance the code and make it less complex I'd be grateful.
Thanks in advance
You're using the serializers the wrong way round. You should pass in the instance and it will give you the serialized data; passing in the data and checking is_valid is for submitting data, not sending it. Also, you can pass in the whole queryset with many=True:
serialized_products = ProductSerializer(products, many=True)
so you don't need your for loop.
But actually DRF will even do all of this for you, because you are using a ListAPIView. All you need to do is tell it what queryset you want, which you do in the get_queryset method. So all you need is:
class ListProductsOfCategory(generics.ListAPIView):
serializer_class = ProductSerializer()
def get_queryset(self):
return Product.objects.filter(category__id=self.request.data['category_id'])

Django CreateView get_initial Foreign Key

I have a form and I am trying to use the get_initial method to set a foreign key. The model is
class CardioRecord(models.Model):
date_uploaded = models.DateField(auto_now=True)
client = models.ForeignKey(User, on_delete=models.CASCADE)
run_dist = models.FloatField(blank=True, null=True)
run_time = models.FloatField(blank=True, null=True)
The form is
class CardioRecordForm(forms.ModelForm):
class Meta:
model = CardioRecord
fields = [
'client',
'run_dist',
'run_time',
]
labels = {
'run_dist': 'Distance (km)',
'run_time': 'Time (min)',
}
widgets = {
'client': forms.HiddenInput()
}
The view is
class CardioCreateView(CreateView):
model = CardioRecord
form_class = CardioRecordForm
template_name = 'training/cardio_form.html'
def get_initial(self):
initial = super(CardioCreateView, self).get_initial()
initial['client'] = self.request.user.pk
return initial
and the error that I am getting is
null value in column "client_id" violates not-null constraint
which looks like the initial value is not being passed to the form. How do I pass the Foreign Key to the form?
Using a hidden field doesn't stop the user from editing the value. If you want to set the value in the view, then remove it from fields in the form:
class CardioRecordForm(forms.ModelForm):
class Meta:
model = CardioRecord
fields = [
'run_dist',
'run_time',
]
Then set the value on the form's instance in the form_valid method:
class CardioCreateView(CreateView):
def form_valid(self, form):
form.instance.client = self.request.user
return super(CardioCreateView. self).form_valid(form)
You can then remove your get_initial method.

Django REST - PATCH for replacing nested object

I'm trying to replace one tested object another using PATCH request. For My experiments I use Postman. I send PATCH request with parameter user_status and value:
{"id": 1, "status_type": {"id": 2, "name": "new_status_type_name"},
"name": "new_status_name"}
I wrote update method for updating my ResultSerializer, but it doesn't work. Now I'm debuging it and I see that variable validated_data doesn't contain my new user_status. user_status is an empty OrderedDict:
ipdb> validated_data['user_status']
OrderedDict()
I checked my request and I see that user_status is a list with one element - string.
ipdb> self.context['request'].data
<QueryDict: {'user_status': ['{"id": 1, "status_type": {"id": 2, "name": "new_status_type_name"}, "name": "new_status_name"}']}>
How can I replace one another nested objects? Thank you for your help.
I have next Models:
class UserStatus(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(unique=True, max_length=255)
status_type = models.ForeignKey('StatusType', blank=True, null=True)
class Meta:
managed = False
db_table = 'user_status'
def __str__(self):
return self.name
class StatusType(models.Model):
id = models.SmallIntegerField(primary_key=True)
name = models.CharField(unique=True, max_length=256)
class Meta:
managed = False
db_table = 'status_type'
def __unicode__(self):
return self.name
class Result(models.Model):
id = models.BigIntegerField(primary_key=True)
user_status = models.ForeignKey('UserStatus', blank=True, null=True)
class Meta:
managed = False
db_table = 'result'
Serializers:
class UserStatusSerializer(serializers.ModelSerializer):
id = serializers.IntegerField()
name = serializers.CharField()
status_type = StatusTypeSerializer()
class Meta:
model = app.models.UserStatus
class StatusTypeSerializer(serializers.ModelSerializer):
id = serializers.IntegerField()
name = serializers.CharField()
class Meta:
model = app.models.StatusType
class ResultSerializer(serializers.ModelSerializer):
user_status = UserStatusSerializer(many=False)
def update(self, instance, validated_data, *args, **kwargs):
import ipdb; ipdb.set_trace()
instance.user_status = validated_data.get('user_status', instance.user_status)
instance.save()
return instance
class Meta:
model = app.models.Result
Views:
class StatusTypeViewSet(viewsets.ModelViewSet):
queryset = app.models.StatusType.objects.all()
serializer_class = app.serializers.StatusTypeSerializer
class UserStatusViewSet(viewsets.ModelViewSet):
queryset = app.models.UserStatus.objects.all()
serializer_class = app.serializers.UserStatusSerializer
class ResultViewSet(viewsets.ModelViewSet):
queryset = app.models.Result.objects.all()
serializer_class = app.serializers.ResultSerializer
After reading of this article Please. Don't Patch Like An Idiot.
I understood that I should use the list for patching, but Django REST doesn't want to use list, only objects. Ok, now I use next dict for pathcing:
{"op": "update", "field": "user_status_id", "new_value": 2}
and next code for sending this list:
import json
import requests
payload = json.dumps({"op": "update", "field": "user_status_id", "new_value": 2})
headers = {'content-type': "application/json"}
response = requests.patch(url, data=payload, headers=headers)
Then I changed serializers.py and views.py. I'll show you only main code without checking and other stufs :).
views.py (added the method perform_update):
class ResultViewSet(viewsets.ModelViewSet):
queryset = app.models.Result.objects.all()
serializer_class = app.serializers.ResultSerializer
def perform_update(self, serializer):
new_value = self.request.data.get('new_value')
user_status_ins = app.models.UserStatus.objects.get(id=new_value)
serializer.save(user_status=user_status_ins)
serializers.py (added the method update):
class ResultSerializer(serializers.ModelSerializer):
user_status = UserStatusSerializer(many=False)
def update(self, instance, validated_data, *args, **kwargs):
for field in validated_data.keys():
setattr(instance, field, validated_data.get(field))
instance.save()
return instance
class Meta:
model = app.models.Result
If you found better solution, please tell me.

Post to Django rest framework API but always get a 'This field is required' error

I'm using Django rest framework 3.2.1, the GET is perfect, but POST does not work.
This is the Model:
class Sample(models.Model):
ownerBuilding = models.ForeignKey(Building)
coordinateX = models.IntegerField(default=0, help_text='X coordinate for this sampling point located in a building')
coordinateY = models.IntegerField(default=0, help_text='Y coordinate for this sampling point located in a building')
creation_Time = models.DateTimeField(auto_now_add=True)
description = models.TextField(null=True,
help_text='additional description for this sample point.')
class Meta:
unique_together = ('ownerBuilding', 'coordinateX','coordinateY')
def __str__(self):
return "Sample for building " + str(self.ownerBuilding)
The Serializer:
class SampleSerializer(serializers.HyperlinkedModelSerializer):
ownerBuilding = serializers.HyperlinkedRelatedField(many=False, read_only=True, view_name='building-detail')
class Meta:
model = Sample
fields = ('url', 'ownerBuilding', 'coordinateX', 'coordinateY', 'creation_Time','description')
The View:
class SampleList(generics.ListCreateAPIView):
queryset = Sample.objects.all()
serializer_class = SampleSerializer
permission_classes = (permissions.IsAuthenticated, IsTechniciansGroupOrReadOnly,)
def get_queryset(self):
queryset = Sample.objects.all()
ownerBuildingId = self.request.query_params.get('ownerBuildingId', None)
if ownerBuildingId is not None:
queryset = queryset.filter(ownerBuilding=ownerBuildingId)
return queryset
When I test the POST to this API with data:
{"ownerBuilding":"http://rest.xxxxxxxx.xyz:8090/buildings/1/","coordinateX":33,"coordinateY":44,"description":"5dfadfasdfsadf5"}
I always get this error:
{
"ownerBuilding": [
"This field is required."
]
}
anyone could help?
the http://rest.xxxxxxxx.xyz:8090/buildings/1/ exists.
[edit0]:
if I POST with:
{"coordinateX":33,"coordinateY":44,"description":"5dfadfasdfsadf5"}
I still get the same result.
Serializer fields are required by default.
Also, DRF ModelSerializer (and HyperlinkedModelSerializer) adds UniqueTogetherValidators for all model's unique_together constraints. This implicitly makes all fields in the constraint required, with exception for fields for which defaults are set. See doc.
ownerBuilding is read only on the serializer:
ownerBuilding = serializers.HyperlinkedRelatedField(many=False, \
read_only=True, view_name='building-detail')
but you don't provide a defualt nor set the value manually, so this field is treated as empty, hence the error message.
Either remove the read_only or set a default.

Django rest frame getting values from all tables where the PK is referenced as FK

With my parent model - StudioProfile, other models have a foreign key relationship to StudioProfile. How can I get all related table data when an API request is made to my StudioProfile serializer. Below are my models, views and serializers,
class StudioProfile(models.Model):
name = models.CharField(max_length = 120)
address_1 = models.CharField(max_length = 200)
address_2 = models.CharField(max_length = 200)
class StudioServices(models.Model):
studio_profile = models.ForeignKey(StudioProfile, related_name = "services")
service_name = models.CharField(max_length = 50)
class StudioPicture(models.Model):
studio_profile = models.ForeignKey(StudioProfile, related_name = "pic_of_studio")
picture = models.ImageField(upload_to = 'img_gallery', null = True, blank = True)
Serializers.py
class StudioServicesSerializer(serializers.ModelSerializer):
class Meta:
model = StudioServices
fields = ('studio_profile', 'service_name')
class StudioPicSerializer(serializers.ModelSerializer):
class Meta:
model = StudioPicture
fields = ('picture')
class StudioProfileSerializer(serializers.ModelSerializer):
services = StudioServicesSerializer(source = "StudioServices")
pic_of_studio = StudioPicSerializer(source = "StudioPicture")
class Meta:
model = StudioProfile
fields = ( 'address_1', 'address_2','services','pic_of_studio' )
views.py
class StudioProfile(ListAPIView):
permission_classes = (ReadWithoutAuthentication,)
serializer_class = StudioProfileSerializer
queryset = StudioProfile.objects.select_related().filter(id = 1)
Am not able to get the data. What am I doing wrong here? When I do a request to StudioProfile class how can I get all the related entries.
Traceback:
Got AttributeError when attempting to get a value for field service_name on serializer StudioProfileSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the StudioProfile instance.
Original exception text was: 'StudioProfile' object has no attribute 'StudioServices'.
I think that you might need to include many=true and change the source to the be related_name in the StudioProfileSerializer:
class StudioProfileSerializer(serializers.ModelSerializer):
services = StudioServicesSerializer(many = true, source = "services")
pic_of_studio = StudioPicSerializer(many = true, source = "pic_of_studio")
class Meta:
model = StudioProfile
fields = ( 'address_1', 'address_2','services','pic_of_studio' )

Categories

Resources