I have a few related models and I wanted to have related object information nested into the read so I followed the example outlined here:
http://www.django-rest-framework.org/api-guide/relations/#nested-relationships
A couple of my serializers look like this:
class ShowSerializer(serializers.ModelSerializer):
artists = ArtistSerializer(many=True)
venue = VenueSerializer()
class Meta:
model = Show
class GuestSerializer(serializers.ModelSerializer):
show = serializers.RelatedField()
class Meta:
model = Guest
And when I read them (GET), they're perfect, the output is something like:
"results": [
{
"id": 1,
"show": {
"id": 2,
"artists": [
{
"id": 13,
"name": "Some Artist"
}
],
"venue": {
"id": 3,
"name": "Some Venue",
},
"date": "2015-11-20"
},
"first_name": "Amanda",
"last_name": "Hugankiss",
}
]
The problem I'm having is when I POST, this configuration seems to expect an entire nested structure just as above, where I'd prefer to specify an id for a relationship on POST, like so:
{
"show": 16,
"first_name": "Maya",
"last_name": "Normusbutt",
}
Is there a way to configure my serializers/view (by the way, I'm using ModelViewSet) to do this?
Related
I'm learning how to use Django Rest Framework and I came out with a question that I can't get to answer.
I understood that the concept of nested serializer but I think there should be a way to get a specific field in an "upper level" instead of "digging" trough levels. Let me better explain what I mean.
I have Users which then are classified into 2 categories Customers (which are the default User category) and Drivers. All the Users have a Profile with their picture so I would like to obtain the name and the image in an API. The only solution I've been managing to find is the following
class OrderProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ("id", "image")
class OrderUserSerializer(serializers.ModelSerializer):
profile = OrderProfileSerializer()
class Meta:
model = User
fields = ("id", "profile")
class OrderCustomerSerializer(serializers.ModelSerializer):
name = serializers.ReadOnlyField(source="get_full_name")
profile = OrderProfileSerializer()
class Meta:
model = User
fields = ("id", "name", "profile")
class OrderDriverSerializer(serializers.ModelSerializer):
name = serializers.ReadOnlyField(source="user.get_full_name")
user = OrderUserSerializer()
class Meta:
model = Driver
fields = ("id", "name", "user")
This is the JSON response
{
"shipping": {
"id": 25,
"customer": {
"id": 14,
"name": "Test Customer",
"profile": {
"id": 10,
"image": "/media/profile_pics/default.jpg"
}
},
"driver": {
"id": 1,
"name": "Test Driver",
"user": {
"id": 28,
"profile": {
"id": 11,
"image": "/media/profile_pics/default.jpg"
}
}
}
}
}
I can make it to work anyway, but I believe that to learn I should always push for a more "elegant" solution rather than "keep the easiest". So I would like to achieve the following response:
{
"shipping": {
"id": 25,
"customer": {
"id": 14,
"name": "Test Customer",
"image": "/media/profile_pics/default.jpg"
},
"driver": {
"id": 1,
"name": "Test Driver",
"image": "/media/profile_pics/default.jpg"
}
}
}
Thank you for any tips/help/suggest that you can provide
Instead of using:
profile = OrderProfileSerializer()
re-write to:
image = serializers.SerializerMethodField()
def get_image(self, obj):
# obj is the customer object
# return what u need (like this example) or write a model inner method
return obj.profile.image
This would align nicely to the json you want
Do the same thing for Driver and User
The problem in my code is when I make any updates for my objects especially the Geometry Model. My code will update every object in this Model with the same values. While I am tying is to update each row with its values instead of updating the model with the same values.
I have tried several ways but the problem still occurs.
def update(self, instance, validated_data):
geo = address_data.get('geo')
lat = geo.pop('lat')
lng = geo.pop('lng')
...
gathers = geometry.objects.update(lat=lat, lng=lng)
address = Address.objects.update(address=address, country=country, description=description, geo=gathers )
...
user_data = validated_data.pop('user')
username = user_data.pop('username')
user = User.objects.update(username=username)
gather = userProfile.objects.update(address=address, user=user)
return instance.
class geometry(models.Model):
lat = models.IntegerField(default='')
lng = models.IntegerField(default='')
class Address(models.Model):
...
geo = models.OneToOneField(geometry, on_delete=models.CASCADE, default='')
class userProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, name='user', primary_key=True)
address = models.OneToOneField(Address, on_delete=models.CASCADE, name='address')
The problem is my code updating the whole rows in the model with the same values.
{
"address": {
"id": 1,
"address": "2,lot chragua",
"country": "Morocco",
"description": "dfvfdv",
"geo": {
"id": 1,
"lat": 471,
"lng": 39
}
},
"user": {
"id": 1,
"username": "sdfvedfbf",
"first_name": "",
"last_name": "",
"email": "",
"date_joined": "2019-01-19T11:31:00.415990Z",
"last_login": null
}
},
{
"address": {
"id": 2,
"address": "2.Lot Chraga",
"country": "Morocco",
"description": "sfvsfv",
"geo": {
"id": 2,
"lat": 471,
"lng": 39
}
},
"user": {
"id": 2,
"username": "svscwdc",
"first_name": "",
"last_name": "",
"email": "",
"date_joined": "2019-01-19T11:36:50.266225Z",
"last_login": null
}
}
The .update() method is used to update multiple objects. As you found out, it will update all objects in a queryset.
If I understand you correctly you want to update a single instance, but from your code, it is not clear to me which instance that is.
You can filter the queryset, as #ozcanyarimdunya suggested:
geometry.objects.filter(pk=geo_pk).update(...)
It should be noted that .update() doesn't call .save() on your model or emit any signals. To update a single object, it is usually preferable to assign the new values to the instance and call save, like this:
geo = geometry.objects.get(pk=geo_pk)
geo.lat = lat
geo.lon = lon
geo.save()
On a side note, the common convention is to capitalize class names (Geometry instead of geometry).
I have two models, Appointment and EmployeeEvent. I need to get data from these models and combine the result in to a single get api request.
urls.py
url(r'^calenderevents', calender_events)
views.py
#api_view(['GET'])
def calender_events(request):
queryset1 = Appointment.objects.all()
queryset2 = EmployeeEvent.objects.all()
return Response({'Appointments':json.loads(serializers.serialize('json', queryset1)), 'EmployeeEvents': json.loads(serializers.serialize('json', queryset2))})
When I call the API, I am getting the result, but it includes some unwanted keys like "pk", "model", "fields" etc. Also in the appoinments result, I need the full customer object instead of the customer id. Is there any way to specify the CustomerSerializer along with the query set?
Results am getting
{
"Appointments": [
{
"pk": "33a0fffb-326e-4566-bfb4-b146a87a4f3f",
"model": "appointment.appointment",
"fields": {
"customer": "25503315-8bac-4070-87c1-86bf0630c846",
"status": "Requested",
"description": "Assigned appointment",
}
},
{
"pk": "9da806f5-77f1-41e6-a745-7be3f79d6f7a",
"model": "appointment.appointment",
"fields": {
"customer": "25503315-8bac-4070-87c1-86bf0630c846",
"status": "Requested",
"description": "Assigned appointment",
}
}
],
"EmployeeEvents": [
{
"pk": "f76b5de0-1ab8-4ac3-947d-15ba8941d97d",
"model": "employee_event.employeeevent",
"fields": {
"event_name": "New Event",
"start_date": "2017-02-17",
"end_date": "2017-02-22"
}
},
{
"pk": "56f02290-370e-426c-951e-a93c57fde681",
"model": "employee_event.employeeevent",
"fields": {
"event_name": "New Event",
"start_date": "2017-02-02",
"end_date": "2017-03-22"
}
}
]
}
Expected Result
{
"Appointments": [
{
"id": "33a0fffb-326e-4566-bfb4-b146a87a4f3f",
"customer": {
"id": "25503315-8bac-4070-87c1-86bf0630c846",
"firstname": "Customre 1",
"photo_url": "imagepath",
},
"status": "Requested",
"description": "Assigned appointment"
},
{
"id": "9da806f5-77f1-41e6-a745-7be3f79d6f7a",
"customer": {
"id": "15ba8941d97d-8bac-4070-87c1-86bf0630c846",
"firstname": "Customre 2",
"photo_url": "imagepath",
},
"status": "Requested",
"description": "Assigned appointment"
},
}
],
"EmployeeEvents": [
{
"id": "f76b5de0-1ab8-4ac3-947d-15ba8941d97d",
"event_name": "New Event 1",
"start_date": "2017-02-17",
"end_date": "2017-02-22"
},
{
"id": "56f02290-370e-426c-951e-a93c57fde681",
"event_name": "New Event 2”,
"start_date": "2017-02-17",
"end_date": "2017-02-22"
}
]
}
You need to write a serializer to display the data in the desired format. Read the excellent tutorial to guide you though it properly. But if you want a quick hacky answer, then do something like this:
serializer = AppointmentSerializer(Appointment.objects.all(), many=True)
return Response(serializer.data)
Where the serializer looks something like this:
class AppointmentSerializer(serializers.ModelSerializer):
customer = CustomerSerializer(required=False, allow_null=True)
class Meta:
model = Appointment
fields = ('id', 'customer', 'status', 'etc...')
related_object = 'customer'
class CustomerSerializer(serializers.ModelSerializer):
class Meta:
model = Customer
fields = ('id', 'first_name', 'etc...')
Edit: updated to include example of a related object
I have a model that has a JSON field extra_data that contains other fields that might be added to the model. From the beginning it is not known how many fields will be added apart from the compulsory ones, which is why I introduced the extra_data field.
With the usual rest framework serialization I currently have something like this:
[
{
"code": "1",
"name": "Moscow",
"extra_data": {
"type": "Region"
}
},
{
"code": "2",
"name": "Tatarstan",
"extra_data": {
"type": "Republic",
"capital": "Kazan"
}
}
]
But what I need something is like this:
[
{
"code": "1",
"name": "Moscow",
"type": "City"
},
{
"code": "2",
"name": "Tatarstan",
"type": "Republic",
"capital": "Kazan"
}
]
Please I need help, I'm new to django
The serializer itself I don't think can do this, since you do not know how many fields are there. But once you get the serializer.data, you may update your dict like:
serializer_data = serializer.data
extra_data = serializer_data.pop('extra_data')
serializer_data.update(extra_data)
return serializer_data
I'm no expert on Django so I'm not telling you for sure that there is no way of doing that withing the serializer, but none that I can think of
Lets say I have a model:
class MyModel(models.Model):
name = models.CharField(max_length=100)
description= models.TextField()
...
Then I created ModelViewSet with HyperLinkedSerializer, so when I call my /api/mymodels endpint I get responses like this:
{
"count": 2,
"next": null,
"previous": null,
"results": [
{ "name": "somename", "description": "desc"},
{ "name": "someothername", "description": "asdasd"},
]
}
and when I call /api/mymodels/1 I get:
{ "name": "somename", "description": "asdasd"}
but what I would like to get is:
{
"metadata":{ ...},
"results": { "name": "somename", "description": "desc"}
}
And I would like to use this format for all models at my website, so I dont want to change every viewset, I want to implement it in (most likely) one class and then use it for all my viewsets.
So my question is: which renderer or serializer or other class (Im really not sure) should I alter or create to get this behavior of json response?
The first response appears to be a paginated response, which is determined by the pagination serializer. You can create a custom pagination serializer that will use a custom format. You are looking for something similar to the following:
class MetadataSerialier(pagination.BasePaginationSerializer):
count = serializers.Field(source='paginator.count')
next = NextPageField(source='*')
previous = PreviousPageField(source='*')
class CustomPaginationSerializer(pagination.BasePaginationSerializer):
metadata = MetadataSerializer(source='*')
This should give you an output similar to the following:
{
"metadata": {
"count": 2,
"next": null,
"previous": null
},
"results": [
{ "name": "somename", "description": "desc"},
{ "name": "someothername", "description": "asdasd"}
]
}
The pagination serializer can be set globally through your settings, as described in the documentation.
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_SERIALIZER_CLASS': {
'full.path.to.CustomPaginationSerializer',
}
}