Using django-rest framework 3.3.3
I am trying to update existing data in my database using the django-rest framework, following a previously posted solution :django-rest-framework 3.0 create or update in nested serializer
I'm fairly new to django in general, and the rest framework.
Given the following models:
class Clinic(models.Model):
name = models.TextField()
streetNumber = models.IntegerField()
unitNumber = models.CharField(max_length=10, blank=True, null=True)
streetName = models.CharField(max_length=50)
city = models.CharField(max_length=50)
province = models.CharField(max_length=3)
country = models.CharField(max_length=30)
postalCode = models.CharField(max_length=6)
phone = models.CharField(max_length=10)
def __str__(self):
return self.name
class Time (models.Model):
clinic = models.ForeignKey(Clinic, related_name='times')
appointmentTime = models.CharField(max_length=100)
def __str__(self):
return self.appointmentTime
And the following serializers:
class TimeSerializer(serializers.ModelSerializer):
class Meta:
model = Time
fields = ('appointmentTime',)
class ClinicSerializer(serializers.ModelSerializer):
times = TimeSerializer(many=True)
class Meta:
model = Clinic
fields = '__all__'
def update(self, instance, validated_data):
instance.name = validated_data['name']
instance.streetName = validated_data['streetName']
instance.unitNumber = validated_data['unitNumber']
instance.city = validated_data['city']
instance.province = validated_data['province']
instance.country = validated_data['country']
instance.postalCode = validated_data['postalCode']
instance.phone = validated_data['phone']
#print(instance)
instance.save()
times_data = [item['id'] for item in validated_data['times']]
print(times_data)
for time in instance.clinics:
if time.id not in times_data:
time.delete()
for item in validated_data['times']:
time = Time(id=item['id'], clinic=instance,
appointmentTime=item['appointmentTime'])
time.save()
return instance
And the following view:
class ClinicList(APIView):
def get(self,request):
clinics = Clinic.objects.all()
serializer = ClinicSerializer(clinics, many=True)
return Response({'results': serializer.data})
def post(self, request):
serializer = ClinicSerializer(data=request.data)
if serializer.is_valid():
serializer.update(Clinic, serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
And the following existing data:
{
"id": 2,
"times": [
{
"appointmentTime": "time3"
},
{
"appointmentTime": "time3"
}
],
"name": "Example Clinic",
"streetNumber": 123,
"unitNumber": "",
"streetName": "Example Street",
"city": "Exampletown",
"province": "Ont",
"country": "Canada",
"postalCode": "A1A1A1",
"phone": "9059059059"
}
With my JSON POST data being:
{
"id": 2,
"times": [
{
"appointmentTime": "AAAA"
},
{
"appointmentTime": "time3"
}
],
"name": "Example Clinic",
"streetNumber": 123,
"unitNumber": "",
"streetName": "Example Street",
"city": "Exampletown",
"province": "Ont",
"country": "Canada",
"postalCode": "A1A1A1",
"phone": "9059059059"
}
My problem is that I am receiving an error stating:
File"C:\Users\colin\Documents\GitHub\Walk_inExpressAPI\clinics\serializer.py",
line 31, in update
instance.save()
TypeError: save() missing 1 required positional argument: 'self'
I'm not sure what Im doing wrong here, or what value to supply to instance.save() to correct this error. I've checked that instance was an object of type Clinic.
What i'm trying to accomplish is that each Clinic can have multiple appointment times. They wont share appointmentTime objects, because I would like to be able to delete an appointentTime each time a new appointment is booked, and each clinic will require its own set of available appointmentTimes.
Hopefully you guys can help lead me in the right direction! Thanks!
You should user the method PUT in your APIView:
def put(self, request, pk):
clinics = Clinic.objects.get(pk=pk)
serializer = ClinicSerializer(clinics, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
so in you url.py should have a pk, to identify the object you want to modify:
urlpatterns = [
url(r'^snippets/$', views.ClinicList.as_view()),
url(r'^snippets/(?P<pk>[0-9]+)/$', views.ClinicList.as_view()),
]
In update method change the instance attributes like:
instance.name = validated_data.get('name',instance.name)
Try to see the example in DRF page as well.
Related
Whenever i called my booking function with a post method in that it throws me an error of 'Invalid Data Type it expected dict but its an int'. So when i debug that i found a problem in my serializers so Please help me to resolve the error
model.py
class Booking(models.Model):
details = models.ForeignKey(PersonalDetails, on_delete=models.CASCADE)
quantity = models.IntegerField(null=False)
total_amount = models.IntegerField()
show = models.ForeignKey(ShowTime, on_delete=models.CASCADE)
def __str__(self) -> str:
return self.total_amount
views.py
class BookingTicketsQuery(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, format=None):
booking_id = request.query_params.get('booking_no', None)
if booking_id is not None:
queryset = Booking.objects.get(id=booking_id)
else:
return Response("invalid", status=status.HTTP_409_CONFLICT)
seriliazer = BookingSerializer(queryset)
return Response(seriliazer.data)
def post(self, request, format=None):
recieve_data = JSONParser().parse(request)
showtime = ShowTime.objects.get(id=recieve_data['show'])
print(showtime)
if recieve_data['quantity'] > (showtime.total_seats - showtime.booked_seats):
return Response("Error: No seats available", status=status.HTTP_409_CONFLICT)
recieve_data['total_amount'] = showtime.total_price * \
recieve_data['quantity']
showtime.booked_seats += recieve_data['quantity']
showtime.save()
serializer = BookingSerializer(data=recieve_data)
if serializer.is_valid():
booking_obj = serializer.save()
return Response(booking_obj.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
serializers.py
class ShowMovieSerializer(serializers.ModelSerializer):
cinema = CinemaSerializer()
movie = MovieSerializer()
class Meta:
model = ShowTime
fields = ('id', 'show_start_time', 'show_end_time',
'total_seats', 'booked_seats', 'cinema', 'movie')
class BookingSerializer(serializers.ModelSerializer):
show = ShowMovieSerializer()
details = PersonalDetailsSerializer()
class Meta:
model = Booking
fields = ('id', 'total_amount', 'quantity', 'details', 'show')
traceback
Bad Request: /api/v1/bookings/
[19/Jul/2021 12:07:20] "POST /api/v1/bookings/ HTTP/1.1" 400 168
{
"details": {
"non_field_errors": [
"Invalid data. Expected a dictionary, but got int."
]
},
"show": {
"non_field_errors": [
"Invalid data. Expected a dictionary, but got int."
]
}
}
my post data:
{
"details": 1,
"quantity":1,
"show":1
}
You added a ShowMovieSerializer() and PersonalDetailsSerializer(). Therefore drf expects data that represents both models (nested data). What you have to do instead of posting { "details": 1, "quantity":1, "show":1 }-data you have to adjusted your data that represents your model. You are posting data with a pk of your Movie and Show.
For instance:
{
"id":....,
"total_amount": ....,
....
"movie": {
"title": ...., # fields are only for explanation
"length": ....
},
"show": {
"title": ....,
"actor": ....
}
}
If you only want to establish a relationship between you ShowTime and Booking you have to remove your serializers.
dealing with nested data
I am trying to create a simple model with foreign keys using Django rest framework.
This are the models:
class Route(models.Model):
place_origin = models.ForeignKey(
Place, null=False, on_delete=models.DO_NOTHING)
class Place(models.Model):
name = models.CharField(max_length=50, null=False)
This are the serializers for each model:
class PlaceSerializer(serializers.ModelSerializer):
class Meta:
model = Place
fields = ["id", "name"]
class RouteSerializer(serializers.ModelSerializer):
place_origin = PlaceSerializer(many=False, read_only=True)
class Meta:
model = Route
fields = ["id", "place_origin"]
This RouteSerializer has the place_origin property in order to show the place details(all the fields from it) when I am looking at the route detail. What I mean is for routes I want to display:
[
{
"id": 1,
"place_origin": {
"id": 1,
"name": "New york"
}
},
{
"id": 2,
"place_origin": {
"id": 2,
"name": "Boston"
}
}
]
And not just:
[
{
"id": 1,
"place_origin": 1
},
{
"id": 2,
"place_origin": 2
}
]
This is the view:
#api_view(['POST'])
def routes_new_route_view(request):
"""
Create a new route
"""
if request.method == "POST":
data = JSONParser().parse(request)
place_origin = Place.objects.get(id=data["place_origin"])
data["place_origin"] = PlaceSerializer(place_origin)
data["place_origin"] = data["place_origin"].data
serializer = RouteSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, status=201)
else:
return JsonResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I want to send the request from postman this way:
{
"place_origin": 3
}
But I am getting the error from the title.
Thanks for all the help!
The error is that you're trying to send data via a PlaceSerializer but this field is read_only. On the other hand, your DB expects place_origin since you precised null=False in your model. Both combined gives the error "Not NULL constraint failed".
The easiest way is to slightly modify your serializer in order to have one field for write and another for read.
class RouteSerializer(serializers.ModelSerializer):
place_origin = PlaceSerializer(many=False, read_only=True)
place = serializers.PrimaryKeyRelatedField(source="place_origin",queryset=Place.objects.all(),write_only=True)
class Meta:
model = Route
fields = ["id", "place_origin", "place"]
Here, you will use place field as a way to create the relationship with your Route instance.
I tried several ways to make this work and I couldn't.
It is very likely, because I believe it should be working. I may be missing something that I am not aware of.
I thank you for your attention.
request.data
{
"first_name": "Marcelo",
"last_name": "Wanderley",
"username": "marcelo",
"cpf": "1234",
"telefone": "99999999",
"email": "marceloa#teste.com",
"observacao": "",
"groups": [
{
"id": 2
},
{
"id": 4
}
],
"password": "111111",
"password_again": "111111",
"token_user_chain": "TMWTIeGs2t1YPpKke2RZh2tLVMuMWdLFxaFYdD",
"private_key": ""
}
'groups': [{'id': 2},{'id': 4}]
View
if serializer.is_valid():
serializer.create(validated_data=serializer.validated_data)
return Response(serializer.data, status=HTTP_201_CREATED)
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
Model
class GroupSerializer(serializers.ModelSerializer):
class Meta:
model = Group
fields = ('id',)
class serializerUser(serializers.Serializer):
class Meta:
model = User
id = serializers.PrimaryKeyRelatedField(read_only=True)
first_name = serializers.CharField()
last_name = serializers.CharField()
email = serializers.CharField(validators=[UniqueValidator(queryset=User.objects.all())])
cpf = serializers.CharField(validators=[UniqueValidator(queryset=User.objects.all())])
token_user_chain = serializers.CharField(validators=[UniqueValidator(queryset=User.objects.all())])
telefone = serializers.CharField()
groups = GroupSerializer(many=True)
observacao = serializers.CharField(allow_null=True, allow_blank=True)
password = serializers.CharField(write_only=True)
username = serializers.CharField(write_only=True,validators=[UniqueValidator(queryset=User.objects.all())])
password_again = serializers.CharField(write_only=True)
Output Print
OrderedDict([('first_name', 'Marcelo'), ('last_name', 'Wanderley'),
('email', 'marcelo#teste.com'), ('cpf', '123'), ('token_user_chain',
'TMWTIeGs2vpH7DGCYNiSCttdirrMqPFEgPnczA'), ('telefone', '999999'),
('groups', [OrderedDict(), OrderedDict()]), ('observacao', ''),
('password', '999999'), ('username', 'Marcelo Wanderley'),
('password_again', '999999')])
('groups', [OrderedDict(), OrderedDict()])
I went through the source codes, and maybe got the cause. Because it has a nested serializer, so it will exclude id field which not in _writable_fields when calling run_validation(). then return a empty OrderedDict instance.
if keep using nested serializer in order to creating instances of manytomany relation model.
groups_data = request.data.pop('groups')
if serializer.is_valid():
instance = serializer.create(validated_data=serializer.validated_data)
groups = [Group.objects.create(**kwargs) for kwargs in groups_data] #NOQA
instance.groups.set(groups)
instance.save()
return Response(serializer.data, status=HTTP_201_CREATED)
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
OR if do not need to create new instances of related model, it could use the default ManyRelatedField rather than nested serializer. request.data['groups'] is just a list of id value:
groups: [
1,
2
]
I have two related models: Product and ProductDescription. In 1 submit action user able to insert a new Product with multiple descriptions depend on the available languages. I use writable nested serializer to insert into Product and ProductDescription simultaneously. I do it by overriding create function in ProductDescriptionSerializer class, it works. However, I can only insert 1 ProductDescription at a time.
Then I tried to use this answer to create multiple model instances at once. The problem is it also creates the same Product twice instead of using the newly created Product Id to insert the next ProductDescription.
My models.py:
class Product(models.Model, ProductStatus):
product_code = models.CharField(max_length=6)
color = models.ForeignKey(ColorParent, on_delete=models.SET_NULL, null=True)
collection = models.ForeignKey(ProductCollection, on_delete=models.SET_NULL, null=True)
video = models.URLField(verbose_name='Video URL', max_length=250, null=True, blank=True)
status = models.CharField(max_length=20, choices=ProductStatus.status, default=ProductStatus.active)
class ProductDescription(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
language = models.ForeignKey(Language, on_delete=models.CASCADE)
description = models.TextField(max_length=500, null=True, blank=True)
def __str__(self):
return '%s - %s' % (self.product, self.language)
My serializers.py:
class CustomRelatedField(serializers.RelatedField):
def display_value(self, instance):
return instance
def to_representation(self, value):
return str(value)
def to_internal_value(self, data):
model = self.queryset.model
return model.objects.get(id=data)
class ProductSerializer(serializers.ModelSerializer):
collection = CustomRelatedField(queryset=ProductCollection.objects.all(), many=False)
color = CustomRelatedField(queryset=ColorParent.objects.all(), many=False)
class Meta:
model = Product
fields = ['id', 'product_code', 'collection', 'color', 'video', 'status']
class ProductDescriptionSerializer(serializers.ModelSerializer):
product = ProductSerializer()
language = CustomRelatedField(many=False, queryset=Language.objects.all())
class Meta:
model = ProductDescription
fields = ['id', 'product', 'language', 'description']
def to_representation(self, instance):
data = super().to_representation(instance)
if self.context['request'].method == 'GET':
data['product'] = instance.product.product_code
return data
return Serializer.to_representation(self, instance)
# The `.create()` method does not support writable nested fields by default.
def create(self, validated_data):
# create product data for Product model.
product_data = validated_data.pop('product')
product = Product.objects.create(**product_data)
# create ProductDescription and set product FK.
product_description = ProductDescription.objects.create(product=product, **validated_data)
# return ProductDescription instance.
return product_description
My views.py:
class CreateListModelMixin(object):
def get_serializer(self, *args, **kwargs):
if isinstance(kwargs.get('data', {}), list):
kwargs['many'] = True
return super(CreateListModelMixin, self).get_serializer(*args, **kwargs)
class ProductDescriptionView(CreateListModelMixin, viewsets.ModelViewSet):
permission_classes = [permissions.DjangoModelPermissions]
queryset = ProductDescription.objects.all()
serializer_class = ProductDescriptionSerializer
http_method_names = ['get', 'head', 'post', 'put', 'patch', 'delete']
The JSON format I use to POST data:
[
{
"product": {
"product_code": "BQ1080",
"collection": 5,
"color": 7,
"video": "https://www.youtube.com/watch?v=",
"status": "Continue"
},
"language": 1,
"description": "English description."
},
{
"product": {
"product_code": "BQ1080",
"collection": 5,
"color": 7,
"video": "https://www.youtube.com/watch?v=",
"status": "Continue"
},
"language": 2,
"description": "Vietnamese description."
}
]
It creates a duplicate Product in Product List:
[
{
"id": 26,
"product_code": "BQ1080",
"collection": 5,
"color": 7,
"video": "https://www.youtube.com/watch?v=",
"status": "Continue"
},
{
"id": 27,
"product_code": "BQ1080",
"collection": 5,
"color": 7,
"video": "https://www.youtube.com/watch?v=",
"status": "Continue"
}
]
The ProductDescription datas are correct though:
[
{
"id": 5,
"product": "BQ1080",
"language": "English",
"description": "English description."
},
{
"id": 6,
"product": "BQ1080",
"language": "Vietnam",
"description": "Vietnamese description."
}
]
To avoid duplicate product you can use get_or_create() method:
class ProductDescriptionSerializer(serializers.ModelSerializer):
...
def create(self, validated_data):
# create product data for Product model.
product_data = validated_data.pop('product')
product_code = product_data.pop("product_code")
product, _ = Product.objects.get_or_create(product_code=product_code, defaults=product_data)
# create ProductDescription and set product FK.
product_description = ProductDescription.objects.create(product=product, **validated_data)
# return ProductDescription instance.
return product_description
Note that get_or_create is prone to race condition. So if two same requests came to you service at the same time you may still have duplicate products.
I think you need to override your ProductSerializer's create method. Maybe you can try like this:
class ProductSerializer(serializers.ModelSerializer):
collection = CustomRelatedField(queryset=ProductCollection.objects.all(), many=False)
color = CustomRelatedField(queryset=ColorParent.objects.all(), many=False)
def create(self, validated_data):
instance, _ = Product.objects.get_or_create(**validated_data)
return instance
class Meta:
model = Product
fields = ['id', 'product_code', 'collection', 'color', 'video', 'status']
So that, first it will try to get if the Product exists, else create the instance(hence reducing duplicate entry).
ForeignKey not for this job you should use ManyToManyField
The problem: when I pass someModelSerializer.data as a nested data for anotherModelSerializer, and after that I pass anotherModelSerializer.data to Response, in the response I see only two fields of SomeModel instead of 5. But when I pass someModelSerializer.data directly to Response, I can see that all model fields are present.
Details below.
I have TranslationHistory model
class TranslationHistory(models.Model):
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
string = models.CharField(max_length=255)
count = models.IntegerField(default=1)
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='translation_history')
and it's TranslationHistorySerializer
class TranslationHistorySerializer(serializers.ModelSerializer):
class Meta:
model = TranslationHistory
user = serializers.PrimaryKeyRelatedField(read_only=True, default=None)
count = serializers.IntegerField(read_only=True)
def validate_user(self, value):
return self.context['request'].user
def update(self, instance, validated_data):
instance.count += 1
instance.save()
return instance
I also have a virtual entity Translation, which is not a model. It has it's own serializer.
class TranslationSerializer(serializers.Serializer):
translation_history = TranslationHistorySerializer() # nested serializer
translation = serializers.CharField()
In my view, if I do like this,
history = TranslationHistory().findByUserAndString(request.user, string)
historySerializer = TranslationHistorySerializer(history)
return Response(historySerializer.data, status=status.HTTP_200_OK)
I have the response like this.
{
"id": 18,
"user": 1,
"count": 72,
"created": "2015-07-15T15:35:50.751219Z",
"updated": "2015-07-24T15:37:04.247469Z",
"string": "hello"
}
But if in my view I do like this,
history = TranslationHistory().findByUserAndString(request.user, string)
historySerializer = TranslationHistorySerializer(history)
serializer = TranslationSerializer(
data={
'translation_history': historySerializer.data,
'translation': 'hello'
},
context={'request': request}
)
serializer.is_valid(raise_exception=True)
return Response(serializer.data, status=status.HTTP_200_OK)
I have the response like this.
{
"translation_history": {
"user": 1,
"string": "hello"
},
"translation": "hello"
}
Why am I getting only user and string fields in this case?