Django-rest-framework how to validate JSON object (not JSON value) - python

I'm currently trying to make a post request with only data field in models.py:
class Filter(models.Model):
class Meta:
verbose_name_plural = "Filter"
verbose_name = "Filter"
db_table= "listing_filter"
data = JSONField(default={})
user = models.ForeignKey('backend.user', on_delete=models.CASCADE)
I have the following Serializer , i use JSONField following the drf document docs:
class FilterSerializer(serializers.ModelSerializer):
data = serializers.JSONField(required=True)
user = serializers.CharField(required=False)
class Meta:
model = Filter
fields = ('__all__')
and use it in the APIView:
class FilterView(APIView):
def post(self, request):
login_user = request.user
received_json_data=json.loads(request.body)
valid_ser = FilterSerializer(data=received_json_data)
if valid_ser.is_valid():
post_data = received_json_data["data"]
filter = Filter.objects.create(data=post_data, user=login_user)
filter.save()
return JsonResponse({'code':'200','data': filter.id}, status=200)
else:
return JsonResponse({'code':'400','errors':valid_ser.errors}, status=400)
When i send the following data in body it worked and saved the object:
{
"data": {
"http://example.org/about": {
"http://purl.org/dc/terms/title": [
{
"type": "literal",
"value": "Anna's Homepage"
}
]
}
}
}
But when i send data as a string(which is not a json object) it still save, how do i prevent this ?
{
"data" : "abc"
}

Related

Why my django server throws an error of invalid datatype?

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

django.db.utils.IntegrityError: NOT NULL constraint failed fom Postman

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.

django-rest-framework getting followers using related_name of ManyToManyField

So I am creating follower system but there is a problem. Everything is working properly. but in follower serializer I want the username's and other details of users.
models.py
class Follow(models.Model):
user = models.OneToOneField(User,related_name="user" ,on_delete=models.CASCADE)
''' to obtain user ' eeee = User.objects.first() , eeee.user' '''
following = models.ManyToManyField(User,related_name='following_user',blank=True)
''' to obtain followers ' eeee.following_user.all()' '''
''' to obtain following ' eeee.user.following.all()' '''
def __str__(self):
return self.user.username
In field following , user.following.all() is used to get the user in manytomany field of request.user and
following_user.all() is used get all the users who has added request.user in their following field.
serializers.py
class FollowerSerializer(ModelSerializer):
user = UserSerializer(many=False)
follower = SerializerMethodField()
class Meta:
model = Follow
fields = ('user','follower')
def get_follower(self, obj):
context = self.context
request = context.get("request")
return request.user.following_user.all().values()
Here I am serializing all the user who has added request.user in their following field
views.py
class FollowerView(RetrieveAPIView):
queryset = Follow.objects.all()
serializer_class = FollowerSerializer
permission_classes = [IsAuthenticated]
lookup_field = 'id'
api
{
"user": {
"name": "eeee"
},
"is_follower": [
{
"id": 2,
"user_id": 9
},
{
"id": 5,
"user_id": 16
},
{
"id": 3,
"user_id": 10
}
]
}
These is the api I am getting of all the user who has added request.user in their following list.
the problem here is I am getting the pk of the user from the key user_id. But I want there username and other information like email, full name of the user who has that primary key. So how can I achieve that?
Modify the get_follower method in serializers.py as follows:
def get_follower(self, obj):
context = self.context
request = context.get("request")
qs = request.user.following_user.all()
data = [{'id': obj.pk, 'user_id': obj.user_id, 'name': obj.req_field} for obj in qs]
return data

Django nested serializer with serializermethodfield

Context
I have an API endpoint api/v1/checkin/ that returns the current DeviceGroup and the AppVersions for an App that have to be active.
Problem
The endpoint currently returns data along with the correctly filtered AppVersions like this:
{
"customer_device_uuid": "8d252b78-6785-42ea-9aee-b6f9e0f870b5",
"device_group": {
"group_uuid": "869b409d-f281-492e-bb62-d3168aea4394",
"device_group_name": "Default",
"color": "#0a2f45",
"is_default": true,
"app_versions": [
"2.0",
"1.1"
]
}
}
Goal
I want the app_versions in the response to contain more data like this:
{
"customer_device_uuid": "8d252b78-6785-42ea-9aee-b6f9e0f870b5",
"device_group": {
"group_uuid": "869b409d-f281-492e-bb62-d3168aea4394",
"device_group_name": "Default",
"color": "#0a2f45",
"is_default": true,
"app_versions": [
{
"app_version_uuid": "UUID here",
"app_version_name": "1.1",
"package_id": "package name here",
"auto_start": false,
"version_code": 1,
"version_name": "0.1",
"source": "link to file here"
}, ...
]
}
}
Serializers
# serializers.py
class AppVersionSerializer(serializers.ModelSerializer):
auto_start = serializers.BooleanField(source='app_uuid.auto_start')
class Meta:
model = AppVersion
fields = ('app_version_uuid', 'app_version_name', 'package_id', 'auto_start', 'version_code', 'version_name',
'source')
class DeviceGroupSerializer(serializers.ModelSerializer):
app_versions = serializers.SerializerMethodField(read_only=True)
# filters the app versions per app
def get_app_versions(self, model):
qs = model.get_current_app_versions()
return [o.app_version_name for o in qs]
class Meta:
model = DeviceGroup
fields = ('group_uuid', 'device_group_name', 'color', 'is_default', 'app_versions')
class CheckinSerializer(serializers.ModelSerializer):
device_group = DeviceGroupSerializer(many=False, read_only=True, source='group_uuid')
class Meta:
model = CustomerDevice
fields = ('customer_device_uuid', 'customer_uuid', 'device_id_android', 'device_group')
extra_kwargs = {
'customer_uuid': {'write_only': True},
'device_id_android': {'write_only': True}
}
I am guessing that I have to change the get_app_versions() in order to achieve my goal but I have no idea how.
What should I change in order to get the response I want?
EDIT
The get_current_app_versions method that does the filtering
# models.py
def get_current_app_versions(self):
return (
AppVersion.objects
.filter(appversiondevicegrouplink__release_date__lt=timezone.now())
.filter(appversiondevicegrouplink__device_group=self)
.order_by('app_uuid__app_uuid', '-appversiondevicegrouplink__release_date')
.distinct('app_uuid__app_uuid')
)
You are correct in assuming that you will have to change get_app_versions and instead of returning a list in the line return [o.app_version_name for o in qs] you will need to return a list of dictionaries.
You will need to create a full serializer for the AppVersions model. and then in your get_app_versions properly serialize you return values by passing them into your new serializer which contains all the fields you would like to return return AppVersionSerializer2(qs, many=True).data.
You may have to override serialization of certain fields as they may not be handled well by the serializer automatically.

Django Rest Framework, some fields are absent when nesting serializers

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?

Categories