django-rest-framework: how do I update a nested foreign key? My update method is not even called - django

I have something like this (I only included the relevant parts):
class ImageSerializer(serializers.ModelSerializer):
telescopes = TelescopeSerializer(many=True)
def update(self, instance, validated_data):
# In this method I would perform the update of the telescopes if needed.
# The following line is not executed.
return super().update(instance, validated_data)
class Meta:
model = Image
fields = ('title', 'telescopes',)
When I perform a GET, I get nested data just as I want, e.g.:
{
'title': 'Some image',
'telescopes': [
'id': 1,
'name': 'Foo'
]
}
Now, if I want to update this image, by changing the name but not the telescopes, I would PUT the following:
{
'title': 'A new title',
'telescopes': [
'id': 1,
'name': 'Foo'
]
}
It seems that django-rest-framework is not even calling my update method because the model validation fails (Telescope.name has a unique constraint), and django-rest-framework is validating it as if it wanted to create it?
Things worked fine when I wasn't using a nested serializer, but just a PrimaryKeyRelatedField, but I do need the nested serializer for performance reason (to avoid too many API calls).
Does anybody know what I'm missing?
Thanks!

You'll find your solution here
Django rest relations

Related

How to return a standard Django Rest Framework JSON from an enum?

I'm not very familiar with DRF and I haven't found a solution on google for this problem (most answers are about a model with a field as enum, my problem is different)
You see, we have an Enum in a Django application. Let's call it SomeValuesEnum.
class SomeValuesEnum(Enum):
ONE_VALUE = "One value"
ANOTHER_VALUE = "Another value"
What I need to do is to create a GET endpoint that returns the following
{
"count": 2,
"page_count": 1,
"next": null,
"previous": null,
"results": [
{
"value": "One value",
"name": "ONE_VALUE"
}, {
"value": "Another value",
"name": "ANOTHER_VALUE"
}
]
}
I know I need to create a serializer, but I haven't been able to create one and "feed it".
For example, I started with something like this:
class SomeValueSerializer(serializers.Serializer):
Meta:
model = SomeValuesEnum,
fields = '__all__'
and on the view:
class SomeValueListView(APIView):
serializer_class = SomeValueSerializer
def get(self, request):
choices = [{"value": target.value, "name": target.value.capitalize()} for target in SomeValuesEnum]
serializer = SomeValueSerializer(data=choices)
return Response(status=status.HTTP_200_OK, data=serializer.data)
I also tried this
class IncidentSerializer(serializers.Serializer):
name = serializers.CharField(required=False, allow_blank=True, max_length=100)
value = serializers.CharField(required=False, allow_blank=True, max_length=100)
I'm not sure if I'm failing on the creation of the serializer, or in how I invoke him on the view (or maybe both)
Any guidance on the right direction will be greatly appreciate it.
An enum is not a django model. You can have a DRF serializer for something that isn't a model, but you shouldn't give it a model field of something that isn't a model.
See here: Declaring Serializers
Not here: Model Serializers
Furthermore, you are creating a class-based view here:
SomeValueListView(APIView)
You don't necessarily need this, you could use a function based view, which may be easier for you to understand given you are new to DRF. I only say function based views are easier to understand since there isn't so much built in functionality. This can make it easier to debug for someone new to DRF. You can still use the serializer by calling it directly in the view.
See here: Function Based Views
And here: Serializing Objects
Finally...given this code:
choices = [{"value": target.value, "name": target.value.capitalize()} for target in SomeValuesEnum]
I am making the assumption that there could be multiple distinct objects going into this serializer, given that you are using a list comprehension. Either you need to call the serializer separately for each object in the array, or you call the serializer with (many=True). Pretty sure this is your main issue. Like this:
serializer = SomeValueSerializer(data=choices, many=True)
See here: Dealing with multiple objects
Also, in my experience it is better to parse incoming data to the serializer within the serializer itself, not in the view. To me it is a separation of concerns issue, but other people may feel differently. This would look something like this:
class SomeValueListView(APIView):
serializer_class = SomeValueSerializer
def get(self, request):
serializer = SomeValueSerializer(data=SomeValuesEnum)
return Response(status=status.HTTP_200_OK, data=serializer.data)
class SomeValueSerializer(serializer.Serializer):
def to_internal_value(self, data)
name = data.value.capitalize()
value = data.value
return {
'name': name,
'value': value,
}

create a django serializer to create three model instance at once

{
"product_name": "CRVRVgfhghg",
"product_price": "0.01",
"product_location": "KIKUYU,KENYA",
"product_description": "VFVFVFVFVFVF",
"product_category_name": "livestock",
"product_farmer_name": "james",
"product_category_data": {
"product_category_name": "livestock",
"product_category_description": "livestock one"
},
"product_product_file_data": {
"product_file_name": "ok"
}
}
i have three tables: product_category,product and product_product_files...what i want is to populate all the three tables at once using one view and url pattern... is there a way i can do this using serializers??
I think what you are looking for is the following documentation DRF writable nested serializers.
Looking at this you'll see that they state the following:
'By default nested serializers are read-only. If you want to support write-operations to a nested serializer field you'll need to create create() and/or update() methods in order to explicitly specify how the child relationships should be saved:'
This is the example they use:
class TrackSerializer(serializers.ModelSerializer):
class Meta:
model = Track
fields = ['order', 'title', 'duration']
class AlbumSerializer(serializers.ModelSerializer):
tracks = TrackSerializer(many=True)
class Meta:
model = Album
fields = ['album_name', 'artist', 'tracks']
def create(self, validated_data):
tracks_data = validated_data.pop('tracks')
album = Album.objects.create(**validated_data)
for track_data in tracks_data:
Track.objects.create(album=album, **track_data)
return album
The data they put in then should look like this:
data = {
'album_name': 'The Grey Album',
'artist': 'Danger Mouse',
'tracks': [
{'order': 1, 'title': 'Public Service Announcement', 'duration': 245},
{'order': 2, 'title': 'What More Can I Say', 'duration': 264},
{'order': 3, 'title': 'Encore', 'duration': 159},
],
Looking at your code-snippet I would guess these models are related in a One-to-one relationship. In this case many=False which is also the default. You could do that for both models.
I think you would be able to get to the right code with this information, if not please let me know.

Partially updating a model with BooleanFields with djangorestframework

I have a model like this
class Status(models.Model):
is_working = models.BooleanField(
default=None,
null=True
)
is_at_home = models.BooleanField(
default=None,
null=True
)
with the corresponding serializer
class StatusSerializer(ModelSerializer):
class Meta:
model = Status
fields = [
"is_working",
"is_at_home"
]
using the default ModelViewSet
class StatusViewSet(viewsets.ModelViewSet):
"""
"""
serializer_class = StatusSerializer
queryset = Status.objects.all()
Whenever I partially update a Status, by e.g calling the put method on the API, all other fields are reset to False instead of keeping their old value.
Say, I have a Status that looks like this:
{
"id": 1,
"is_working": null,
"is_at_home": null,
}
If I call put using the following JSON:
{
"is_working": true
}
my data now looks like this
{
"id": 1,
"is_working": true,
"is_at_home": false <-- GOT UPDATED
}
I however, just want to update the is_working field, so that my desired result would be:
{
"id": 1,
"is_working": true,
"is_at_home": null
}
This probably has to do with the fact that HTML forms don't supply a value for unchecked fields. I, however, don't use any HTML forms as I'm purely consuming the API through JSON requests.
Using the serializer as is, I'd need to perform an extra get request prior to updating the model, just to get the state of the fields I don't want to update.
Is there a way around that?
First off, for partially updating you need to have a PATCH request and not PUT.
Since you are using ModelViewSet drf should automatically recognize that and set partial=True and update only the fields which were sent in the api payload.
ModelViewSet doc- DRF
You can make your is_at_home field read only in serializer as PUT method is updating it. Try this in your serializer.Hope this will work for you.
class StatusSerializer(ModelSerializer):
class Meta:
model = Status
fields = [
"is_working",
"is_at_home"
]
extra_kwargs = {
'id': {'read_only': True},
'is_at_home': {'read_only': True},
}

Django Rest Framework - Updating a foreign key

I am a bit frustrated with this problem using the Django Rest Framework:
I am using a viewset, with a custom serializer. This serializer has its depth set to 1. When i query this viewset I get the correct representation of data for example:
data = {
id: 1,
issue_name: 'This is a problem',
status: {
id: 3,
name: 'todo'
}
}
The problem comes in when I need to update the status. For example if I want to select another status for this issue, for example:
status_new = {
id: 4,
name: 'done'
}
I send the following PATCH back to the server, this is the output:
data = {
id: 1,
issue_name: 'This is a problem',
status: {
id: 4,
name: 'done'
}
}
However, the status does not get updated. Infact, it is not even a part of the validated_data dictionary. I have read that nested relations are read-only. Could someone please tell me what I need to do this in a simple way?
Would really be obliged.
Thanks in advance
As stated in the documentation, you will need to write your own create() and update() methods in your serializer to support writable nested data.
You will also need to explicitly add the status field instead of using the depth argument otherwise I believe it won't be automatically added to validated_data.
EDIT: Maybe I was a bit short on the details: what you want to do is override update in ModelIssueSerializer. This will basically intercept the PATCH/PUT requests on the serializer level. Then get the new status and assign it to the instance like this:
class StatusSerializer(serializers.ModelSerializer):
class Meta:
model = Status
class ModelIssueSerializer(serializers.ModelSerializer):
status = StatusSerializer()
# ...
def update(self, instance, validated_data):
status = validated_data.pop('status')
instance.status_id = status.id
# ... plus any other fields you may want to update
return instance
The reason I mentioned in the comment that you might need to add a StatusSerializer field is for getting status into validated_data. If I remember correctly, if you only use depth then nested objects might not get serialized inside the update() / create() methods (although I might be mistaken on that). In any case, adding the StatusSerializer field is just the explicit form of using depth=1
I usually use custom field for such cases.
class StatusField(serializers.Field):
def to_representation(self, value):
return StatusSerializer(value).data
def to_internal_value(self, data):
try:
return Status.objects.filter(id=data['id']).first()
except (AttributeError, KeyError):
pass
And then in main serializer:
class IssueSerializer(serializers.ModelSerializer):
status = StatusField()
class Meta:
model = MyIssueModel
fields = (
'issue_name',
'status',
)
I would assume that your models mimic your serializer's data. Also, I would assume that you have a one to many relation with the status(es) but you don't need to create them via the issue serializer, you have a different endpoint for that. In such a case, you might get away with a SlugRelatedField.
from rest_framework import serializers
class StatusSerializer(serializers.ModelSerializer):
class Meta:
model = MyStatusModel
fields = (
'id',
'status',
)
class IssueSerializer(serializers.ModelSerializer):
status = serializers.SlugRelatedField(slug_field='status', queryset=MyStatusModel.objects.all())
class Meta:
model = MyIssueModel
fields = (
'issue_name',
'status',
)
Another valid solution would be to leave here the foreign key value and deal with the display name on the front-end, via a ui-select or select2 component - the RESTfull approach: you are handling Issue objects which have references to Status objects. In an Angular front-end app, you would query all the statuses from the back-end on a specific route and then you will display the proper descriptive name based on the foreign key value form Issue.
Let me know how is this working out for you.

How to hydrate ToOneField and ToManyField in tastypie

I have some tastypie resources, and I'm dehydrating so I can return resources' slugs (names) in the results, but I'm having trouble re-hydrating them.
I tried setting, e.g., bundle.data['profile'] = { 'name': bundle.data['profile'] } but I couldn't get that to work, it looks as though it needs all of the data in order to build the object, not just some fields
In this example, hydrate_profile works, but it depends on bundle.obj.profile to be already loaded. I'm not sure I can always assume that.
In the case of dehydrate_roles, I don't know what should I return, returning a list of Models or a QuerySet doesn't work, it looks like the right thing to do (in both methods) would be to return the correct resource uri, in both cases, but I don't know how to build it without too much hardcoding: I'd need to retrieve the original Model classes from the fields (or at least from the Resource class), in order to obtain the object, in order to get it's PK and then build the uri.. sounds too far-fetched to me.
So: what should I be doing? Is my hydrate_profile implementation right, or will it come bite me in the butt? what should I return in hydrate_roles?
class MinionResource(ModelResource):
"""
Resource for :models:`inventory.minions.models.Minion`
"""
roles = fields.ToManyField(RoleResource, 'roles')
profile = fields.ForeignKey(clouds_api.CloudProfileResource, 'profile')
class Meta:
queryset = models.Minion.objects.all()
resource_name = 'minion/minion'
filtering = {
'id': ALL,
'name': ALL,
'roles': ALL_WITH_RELATIONS,
'profile': ALL_WITH_RELATIONS,
}
def dehydrate_profile(self, bundle):
return bundle.obj.profile.name
def hydrate_profile(self, bundle):
bundle.data['profile'] = bundle.obj.profile
return bundle
def dehydrate_roles(self, bundle):
return list(bundle.obj.roles.all().values_list('name', flat=True))
I can't exactly answer what is right, but as far as I know, on Profile part, you will get problem when you create new object that doesn't have profile linked yet.
However, what I regularly do is to only define if I want information or not.
class MinionResource(ModelResource):
roles = fields.ToManyField(RoleResource, 'roles', full=True)
profile = fields.ForeignKey(clouds_api.CloudProfileResource, 'profile', full=True)
This should be enough already. However, if you don't like to be this way, you can do this.
class MinionResource(ModelResource):
roles = fields.ListField()
profile = fields.ForeignKey(clouds_api.CloudProfileResource, 'profile', full=True)
def dehydrate_roles(self, bundle):
return map(str, bundle.obj.roles.all())