I'm implementing a stock shoes manager with REST architecture using Django + Django rest.
Im using a custom Router inherited from DefaultRouter to serve my endpoints.
In the /resources/id
endpoint Ive added one more verb, POST
that is called by custom_create
method.
Here you can see this custom_create
method:
viewsets.py
class ShoeViewSet(viewsets.ModelViewSet):
queryset = Shoe.objects.all()
filter_class = ShoeFilter
def get_serializer_class(self):
if self.action == 'custom_create':
return StockPostSerializer
else:
return ShoeSerializer
def custom_create(self, request, *args, **kwargs):
data = {}
data['shoe'] = kwargs['pk']
data['size'] = request.data.get('size')
data['amount'] = request.data.get('amount')
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
I needed to do this because I have two models, below you can see my 3 Serializers:
serializers.py
class StockSerializer(serializers.ModelSerializer):
class Meta:
model = Stock
fields = ['size', 'amount']
class ShoeSerializer(serializers.ModelSerializer):
stock = StockSerializer(many=True, read_only=True)
class Meta:
model = Shoe
fields = ['description', 'provider', 'type', 'cost_price','sale_price','total_amount', 'stock']
class StockPostSerializer(serializers.ModelSerializer):
class Meta:
model = Stock
fields = ['shoe','size', 'amount']
The retrieve
(GET
verb) method of this endpoint expects data serialized by ShoeSerializer
, but the custom_create
method insert data using the StockPostSerializer
. How can I return a response with a different data that was inserted ?
When I try to insert with this endpoint I recieve this error message, but when I refresh the page I realize that the content was inserted (If i use postman instead of de DRF frontend I dont get any error message, works fine).
How can my custom_create
method Responses correctly ?
You can check my github, the names will be a bit different because I translated it here so that it is easier for you to understand.
PS: As you may have noticed I am not a native speaker of the English language, so it is very difficult to express myself here but I am trying my best, and learning more and more. If my question contains grammar / concordance errors please correct them but you do not have to refuse me so I'm trying to learn!
A ViewSet class is simply a type of class-based View, that does not provide any method handlers such as . get() or . post() , and instead provides actions such as . list() and . create() .
perform_create is called within the create method to call the serializer for creation once it's known the serialization is valid. Specifically, serializer.save() Code from the source - when in doubt check it: class CreateModelMixin(object): """ Create a model instance. """
perform_create. perform create method is used to add extra information when creating a new object. perform_create() method will not execute if you override create() method.
Django REST Framework is a wrapper over default Django Framework, basically used to create APIs of various kinds. There are three stages before creating a API through REST framework, Converting a Model's data to JSON/XML format (Serialization), Rendering this data to the view, Creating a URL for mapping to the viewset.
I finally managed to sort this out, and in a much more elegant way than I had been trying beforehand.
What I need to do is: add new stock instances, for this I had created a new route for POST in the endpoint resources/id.
So I was able to reuse the Default Router, delete the custom_create
method, and just modified the serializers.py file.
It looks like this:
serializers.py
class StockSerializer(serializers.ModelSerializer):
class Meta:
model = Stock
fields = ['size', 'amount']
class ShoeSerializer(serializers.ModelSerializer):
stock = StockSerializer(many=True)
def update(self, instance, validated_data):
instance.description = validated_data.get(
'description', instance.description)
instance.provider = validated_data.get(
'provider', instance.provider)
instance.type = validated_data.get('type', instance.type)
instance.cost_price = validated_data.get(
'cost_price', instance.cost_price)
instance.salve_price = validated_data.get(
'sale_price', instance.sale_price)
stock = instance.stock.all()
stock_data = validated_data.get('stock', [])
for item_data in stock_data:
item_id = item_data.get('size', None)
if item_id is not None:
item_db = stock.get(size=item_id)
item_db.size = item_data.get('size', item_db.size)
item_db.amount = item_data.get('amount',item_db.amount)
item_db.save()
else:
Estoque.objects.create(
shoe = instance,
size = item_data['size'],
amount = item_data['amount']
)
instance.save()
return instance
class Meta:
model = Shoe
fields = ['_id','description', 'provider', 'type', 'cost_price','sale_price','total_amount', 'stock']
Now, via PATCH
verb I can add new Stock instances and alter existing stock instances. Thank you for the support!
If I understood correctly by looking at your code, in this case specifically, you don't need the StockPostSerializer
. You can acheive the result you want by changing StockSerializer
as follows:
class StockSerializer(serializers.ModelSerializer):
class Meta:
model = Stock
fields = ['shoe', 'size', 'amount']
extra_kwargs = {'shoe': {'write_only': True}}
I greatly apologize if I misunderstood your question.
EDIT:
Forgot to say. Using this serializer you don't need any extra route on your ModelViewSet
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With