Django Restframework update manytomany via through table - django

I have following models and one another ClientWarehouse. For some reasons i have to create ManyToManyField Relation via a through table named RouteInfo
class Route(models.Model):
user = models.ForeignKey(User)
warehouse = models.ManyToManyField(ClientWarehouse,
verbose_name='Warehouse Location',
through='RouteInfo',
blank=True, null=True)
class RouteInfo(models.Model):
route = models.ForeignKey(Route)
warehouse = models.ForeignKey(ClientWarehouse)
i have following serializer class writtern
class RouteInfoSerializer(serializers.ModelSerializer):
class Meta:
model = RouteInfo
class RouteSerializer(serializers.ModelSerializer):
warehouse = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Route
And Following Viewset
class RouteViewSet(viewsets.ModelViewSet):
model = Route
def get_object(self, pk):
try:
return Route.objects.get(pk=pk)
except Route.DoesNotExist:
raise Http404
def create(self, request, format=None):
r = RouteSerializer(data=request.DATA)
if r.is_valid():
r.save()
return Response(r.data, status=status.HTTP_201_CREATED)
return Response(r.errors, status=status.HTTP_400_BAD_REQUEST)
However on going through the documentation of DRF i found that it does not support write operations on a ManytoManyField via through table.
Currently when i try to save the route via the api call, it does not save the clientwarehouse, ie the RouteInfo table is not created, but the Route object is created with no clientwarehouse in it.
Is there any way i can update the through table ie RouteInfo while creating a route in Route table.

Alternatively i found that i can acomplish the same by first saving the Route Instance and the using the Route.id and warehouse to save into RouteInfo model.
Hence this turned into two step process. First saving the original model and then saving the through model.

Related

How to customise update method for ManyToMany fields in Django Rest Framework

I have a few models with ManyToMany relationships between them and I need to override the create and update method to make the POST and PUT request work in DRF.
Here's my code so far:
class CreateFolderSerializer(serializers.ModelSerializer):
class Meta:
model = Folder
fields = ("id", "title", "description", "users")
def create(self, validated_data):
users = validated_data.pop(
'users') if 'users' in validated_data else []
folder = Folder.objects.create(**validated_data)
folder.users.set(users)
return folder
This create method works perfectly.
I tried re-creating the same logic for the update method, but it doesn't work:
class FolderSerializer(serializers.ModelSerializer):
documents = DocumentSerializer(many=True, read_only=True)
class Meta:
model = Folder
fields = '__all__'
def update(self, instance, validated_data):
users = validated_data.pop('users') if 'users' in validated_data else []
instance.users.set(users)
instance.save()
return instance
When I send a PUT request, the object does not get modified at all, it gets deleted altogether.
Any clue?
Thanks a lot.
Don' t set documents as read_only:
documents = DocumentSerializer(many=True)
By calling instance.users.set(users), you replace the precedent list, for example if you pass an empty list all associated users are removed. So if you want to leave the current users associated you need to insert their primary keys in the request data (for the users key), otherwise use instance.users.add instead of instance.users.set.

Edit update all schedules except primary key in Django Rest Framework

I wanna change all fields of a json object except 'pk' in DRF. I just need to keep one json data. When adding a new data ,this one should override existing data. Is there a way to do it with django ?
my models.py
class ClientUser2(models.Model):
phone_number = models.CharField(max_length=20,unique=True)
name = models.CharField(max_length=100,blank=True)
status = models.IntegerField(default=1)
class ClientNameSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ClientUser2
fields = ('url','phone_number','name','status','pk')
my views.py
class ClientViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows messages to be viewed or edited.
"""
queryset = ClientUser2.objects.all()
serializer_class = ClientNameSerializer
and it's my api root
api_root
If you want to be able to only retrieve and update models you can use RetrieveUpdateApiView
Reference : https://www.django-rest-framework.org/api-guide/generic-views/#retrieveupdateapiview

How can I create model instance via Serializer without creating models from nested serialziers?

I have model with many links into it:
class Travel(BaseAbstractModel):
tags = models.ManyToManyField(
Tag,
related_name='travels',
)
owner = models.ForeignKey(
'users.TravelUser',
related_name='travel_owner'
)
payment = models.ForeignKey(
Payment,
related_name='travels',
)
country = models.ForeignKey(
Country,
related_name='travels,
)
........
Many of these models have only two fields with unique name and image.
I create serializer for each of these models and put them in TravelSerializer
class TravelBaseSerializer(DynamicFieldsModelSerializer):
owner = UserSerializer(required=False)
tags = TagSerializer(many=True)
payment = PaymentSerializer()
country = CountrySerializer()
Based on docs I override create() and update.
The problem is, when I sent JSON data, Django create each model from nested serializers. But I want to create only Travel instance. Also I want receive and respond serialized object not only pk field.
UPDATE
I solved this problem, put code in the answer. Now I can receive and respond with Serializer data without creating object.
But I think the DRF provides more elegant approach then I do. It is my first project with DRF, maybe I miss something and there's an easier solution.
I decide override to_internal_value() put it in custom serailizer and inherit all nested serializers from it:
class NestedRelatedSerializer(serializers.ModelSerializer):
def to_internal_value(self, data):
try:
pk = data['pk']
except (TypeError, KeyError):
# parse pk from request JSON
raise serializers.ValidationError({'_error': 'object must provide pk!'})
return pk
Get all pk from it and save in create and updated methods:
def update(self, instance, validated_data):
# If don't get instance from db, m2m field won't update immediately
# I don't understand why
instance = Travel.objects.get(pk=instance.pk)
instance.payment_id = validated_data.get('payment', instance.payment_id)
instance.country_id = validated_data.get('country', instance.country_id)
# update m2m links
instance.tags.clear()
instance.tags.add(*validated_data.get('tags'))
instance.save()
return instance
I'm not exactly sure I understand what you want to do, but could setting read_only_fields is the Meta class be what you need ?
class TravelBaseSerializer(DynamicFieldsModelSerializer):
owner = UserSerializer(required=False)
tags = TagSerializer(many=True)
payment = PaymentSerializer()
country = CountrySerializer()
class Meta:
read_only_fields = ('tags',)
See this section in the docs.

Django Rest Framework auto increment primary key value

I'm new to using the DRF so I apologize if this is a trivial question but I've had no luck finding an answer thus far.
I'm using DRF along with Angularjs to create a single page application. When I make posts to my API I get this error to create a new Task object: task_id: [This field is required.] task_id is my primary key on this object. How can I make it so that it gets incremented automatically like it would on a Django Model Form?
class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = Task
fields = ('route', 'date', 'task_id', )
class AddTask(generics.CreateAPIView):
serializer_class = TaskSerializer
def get(self, request, format=None):
response = {}
response['form'] = TaskForm().as_p()
return Response(response)
Are you using task_id in your application page? If not, then remove it from the serializer and DRF will automatically take care of this for you.
Something like this:
class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = Task
fields = ('route', 'date',)

How can I apply a filter to a nested resource in Django REST framework?

In my app I have the following models:
class Zone(models.Model):
name = models.SlugField()
class ZonePermission(models.Model):
zone = models.ForeignKey('Zone')
user = models.ForeignKey(User)
is_administrator = models.BooleanField()
is_active = models.BooleanField()
I am using Django REST framework to create a resource that returns zone details plus a nested resource showing the authenticated user's permissions for that zone. The output should be something like this:
{
"name": "test",
"current_user_zone_permission": {
"is_administrator": true,
"is_active": true
}
}
I've created serializers like so:
class ZonePermissionSerializer(serializers.ModelSerializer):
class Meta:
model = ZonePermission
fields = ('is_administrator', 'is_active')
class ZoneSerializer(serializers.HyperlinkedModelSerializer):
current_user_zone_permission = ZonePermissionSerializer(source='zonepermission_set')
class Meta:
model = Zone
fields = ('name', 'current_user_zone_permission')
The problem with this is that when I request a particular zone, the nested resource returns the ZonePermission records for all the users with permissions for that zone. Is there any way of applying a filter on request.user to the nested resource?
BTW I don't want to use a HyperlinkedIdentityField for this (to minimise http requests).
Solution
This is the solution I implemented based on the answer below. I added the following code to my serializer class:
current_user_zone_permission = serializers.SerializerMethodField('get_user_zone_permission')
def get_user_zone_permission(self, obj):
user = self.context['request'].user
zone_permission = ZonePermission.objects.get(zone=obj, user=user)
serializer = ZonePermissionSerializer(zone_permission)
return serializer.data
Thanks very much for the solution!
I'm faced with the same scenario. The best solution that I've found is to use a SerializerMethodField and have that method query and return the desired values. You can have access to request.user in that method through self.context['request'].user.
Still, this seems like a bit of a hack. I'm fairly new to DRF, so maybe someone with more experience can chime in.
You have to use filter instead of get, otherwise if multiple record return you will get Exception.
current_user_zone_permission = serializers.SerializerMethodField('get_user_zone_permission')
def get_user_zone_permission(self, obj):
user = self.context['request'].user
zone_permission = ZonePermission.objects.filter(zone=obj, user=user)
serializer = ZonePermissionSerializer(zone_permission,many=True)
return serializer.data
Now you can subclass the ListSerializer, using the method I described here: https://stackoverflow.com/a/28354281/3246023
You can subclass the ListSerializer and overwrite the to_representation method.
By default the to_representation method calls data.all() on the nested queryset. So you effectively need to make data = data.filter(**your_filters) before the method is called. Then you need to add your subclassed ListSerializer as the list_serializer_class on the meta of the nested serializer.
subclass ListSerializer, overwriting to_representation and then calling super
add subclassed ListSerializer as the meta list_serializer_class on the nested Serializer
If you're using the QuerySet / filter in multiple places, you could use a getter function on your model, and then even drop the 'source' kwarg for the Serializer / Field. DRF automatically calls functions/callables if it finds them when using it's get_attribute function.
class Zone(models.Model):
name = models.SlugField()
def current_user_zone_permission(self):
return ZonePermission.objects.get(zone=self, user=user)
I like this method because it keeps your API consistent under the hood with the api over HTTP.
class ZoneSerializer(serializers.HyperlinkedModelSerializer):
current_user_zone_permission = ZonePermissionSerializer()
class Meta:
model = Zone
fields = ('name', 'current_user_zone_permission')
Hopefully this helps some people!
Note: The names don't need to match, you can still use the source kwarg if you need/want to.
Edit: I just realised that the function on the model doesn't have access to the user or the request. So perhaps a custom model field / ListSerializer would be more suited to this task.
I would do it in one of two ways.
1) Either do it through prefetch in your view:
serializer = ZoneSerializer(Zone.objects.prefetch_related(
Prefetch('zone_permission_set',
queryset=ZonePermission.objects.filter(user=request.user),
to_attr='current_user_zone_permission'))
.get(id=pk))
2) Or do it though the .to_representation:
class ZoneSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Zone
fields = ('name',)
def to_representation(self, obj):
data = super(ZoneSerializer, self).to_representation(obj)
data['current_user_zone_permission'] = ZonePermissionSerializer(ZonePermission.objects.filter(zone=obj, user=self.context['request'].user)).data
return data