how to update serializer.validated_data in DRF - django-rest-framework

good day
me need update validated_data dict
example
class ProductsApiView(APIView):
permission_classes = [AllowAny]
def post(self, request):
serializer = ProductSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
product = serializer.save()
print(serializer.validated_data)
return Response({'message': 'created'}, status=status.HTTP_201_CREATED)
class ProductSerializer(serializers.ModelSerializer):
def create(self, validated_data):
validated_data['my_data'] = 'my_new_data'
return super(ProductSerializer, self).create(validated_data)
class Meta:
model = Product
fields = ['id', 'title']
when i make print(serializer.validated_data)
OrderedDict([('title', 'testttt')])
why in dict no my data "my_data" ?

You can provide arbitrary additional context by passing a context argument when instantiating the serializer. For example:
serializer = AccountSerializer(account, context={'request': request})
serializer.data
The context dictionary can be used within any serializer field logic, such as a custom .to_representation() method, by accessing the self.context attribute.
Reference

Related

Django rest filter data by field

Controller:
class TicketMessageSerializerView(generics.RetrieveAPIView):
queryset = TicketMessage.objects.all()
serializer_class = TicketMessageSerializer
How to filter the list of messages by the ticket_id field, the value of which is taken from the url:
path('api/tickets/<int:ticket_id>/messages/',
views.TicketMessageSerializerView.as_view()),
In that case you work with a ListAPIView (since you return a list of objects), and you can override the get_queryset method to specify what TicketMessages that should be returned, so:
class TicketMessageSerializerView(generics.ListAPIView):
queryset = TicketMessage.objects.all()
serializer_class = TicketMessageSerializer
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).filter(
ticket_id=self.kwargs['ticket_id']
)

AttributeError: 'collections.OrderedDict' object has no attribute 'model_id' and 'model_id' is missing from visible fields

Something strange happened: I was defining an endpoint and initially two fields were visible in the API form: model_id and payload, as given in the model definition:
### models.py:
class CarModel(models.Model):
model_id = models.CharField(max_length=10, primary_key=True)
name = models.CharField(max_length=40)
active = models.BooleanField(default=True)
def __str__(self):
return self.model_id
class Calculator(models.Model):
model = models.ForeignKey(CarModel, on_delete=models.CASCADE)
payload = models.TextField()
def model_id(self):
return self.model.model_id
def __str__(self):
return f"Calculations for {self.model.name}"
### serializers.py:
class CalculatorSerializer(serializers.ModelSerializer):
model_id = serializers.SerializerMethodField()
class Meta:
model = Calculator
fields = ['model_id', 'payload']
def get_model_id(self, obj):
return obj.model_id()
### views.py:
class CalculatorViewSet(viewsets.ModelViewSet):
serializer_class = CalculatorSerializer
queryset = Calculator.objects.all()
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
return Response(f"{serializer.data.upper()}", status=status.HTTP_200_OK)
So, both fields were visible, but POST requests ended in the AttributeError: 'collections.OrderedDict' object has no attribute 'model_id'. Trying to fix that, I eventually and accidentally removed model_id from view - it doesn't display in DRF's forms. And the AttributeError still persists.
What is wrong with this piece of code?
OK, it turns out that defining fields in this manner:
fields = '__all__'
makes also the model_id visible. Still, no idea why explicit insert doesn't work.
In case of the other issue, the AttributeError, I had to pull the value out of an OrderedDict. Modified method looks like this:
def get_model_id(self, obj):
return obj["model"].model_id
Beside that, I found one more error inside views.py's create method: serializer.data won't implement upper() method; some key, in my case serializer.data['payload'], has to be referenced, so for example:
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
res = {
"payload": f"{serializer.data['payload'].upper()}"
}
return Response(res, status=status.HTTP_200_OK)

DRF Serializer custom create

I don't know if I'm doing this the right way, but I'm having problems with saving some relations with DRF serializers.
Let's say I have a MessageBoard and some MessageBoardPosts
Post has a serializer like:
class MessageBoardPostSerializer(serializers.ModelSerializer):
class Meta:
model = MessageBoardPost
fields = '__all__'
I want to add a Post by posting to /api/messageBoards/[PK]/create_post/
To do this, I added an action to the MessageBoard Viewset:
#action(detail=True, methods=['post'], permission_classes=[MatchesMessageboardVisibility])
def create_post(self, request, pk=None):
# Messageboard to post to
messageBoard = MessageBoard.objects.get(pk=pk)
if messageBoard is not None:
serializer = MessageBoardPostSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
Since MessageBoard is a required FK on a post, I get validation errors when creating the post in the viewset.
What's the best way to solve this?
Serializer (i assume that "message_board" is ForeignKey in MessageBoardPost model):
class MessageBoardPostSerializer(serializers.ModelSerializer):
# this is read only by default which means serializer will not require that field
message_board = serializers.StringRelatedField()
class Meta:
model = MessageBoardPost
fields = '__all__'
Viewset action:
#action(detail=True, methods=['post'], permission_classes=[MatchesMessageboardVisibility])
def create_post(self, request, pk=None):
# Messageboard to post to
messageBoard = get_object_or_404(MessageBoard, pk=pk)
serializer = MessageBoardPostSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
# here you can pass data without validation directly to the save method
serializer.save(message_board=messageBoard)
return Response(serializer.data)
you should write your variables in snake_case style like "message_board" etc.
for more advanced crud you should make url like /api/message-boards/PK/posts/ where you will POST your posts and message_board should be prefetched in overrited initial method
You can pass the MessageBoard as context to the serializer:
views.py
from django.shortcuts import get_object_or_404
#action(detail=True, methods=['post'], permission_classes=[MatchesMessageboardVisibility])
def create_post(self, request, pk=None):
# Messageboard to post to
message_board = get_object_or_404(MessageBoard, pk=pk)
context = {'message_board': message_board}
serializer = MessageBoardPostSerializer(data=request.data, context=context)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
serializers.py
class MessageBoardPostSerializer(serializers.ModelSerializer):
class Meta:
model = MessageBoardPost
fields = '__all__'
def create(self, validated_data):
post = MessageBoardPost(**validated_data)
post.message_board = self.context['message_board']
post.save()
return post

DRF name undefined in custom serializer

When I try to hit my api/atoms/ endpoint in the browser, I am getting a name undefined error in the views.py file, but it has a base name in urls.
Note: this is a non-model serializer and a ViewSet.
error
...views.py", line 74, in list
instance = atom.values(), many=True)
NameError: name 'atoms' is not defined
views.py
class AtomViewSet(viewsets.ViewSet):
serializer_class = AtomSerializer
def list(self, request):
serializer = AtomSerializer(
instance = atoms.values(), many=True) #<-------------
return Response(serializer.data)
urls.py
# for viewsets in views.py
router = DefaultRouter()
router.register(r'snippets', views.SnippetViewSet)
router.register(r'atoms', views.AtomViewSet, base_name='atoms')
urlpatterns = [
path('', views.api_root),
path('', include(router.urls)),
]
serializer.py
class AtomSerializer(serializers.Serializer):
uid = UniqueIdProperty()
created_at = DateTimeProperty()
updated_at = DateTimeProperty()
charge = IntegerProperty()
mass = FloatProperty()
def create(self, validated_data):
return Atom(id=None, **validated_data)
def update(self, instance, validated_data):
for field, value in validated_data.items():
setattr(instance, field, value)
return instance
This is a basic python NameError exception raised when a local or global name is not found.
The variable atoms is not defined in the list() method or globally, that's why the python interpreter raised the exception.
In your code, you'd write atoms.values(), which forces me to think that you are dealing with a QuerySet, which might be an Atom model.
class AtomViewSet(viewsets.ViewSet):
serializer_class = AtomSerializer
def list(self, request):
serializer = AtomSerializer(instance=Atom.objects.all(), many=True)
return Response(serializer.data)
Note: this is a non-model serializer and a ViewSet.
You are doing create and update operations in your AtomSerializer class, and those are directly connected to the model. I don't see any particular reason that pulls you back from using a ModelSerializer here. Apart from that, you are using the routers, which become a good choice when you deal with the CRUD operations, hence I strongly suggest you use the combination of ModelViewset and ModelSerializer in your code.
In your views.py you did not define atom, you need to define it first before using it or else you will get that error.
class AtomViewSet(viewsets.ViewSet):
serializer_class = AtomSerializer
def list(self, request):
# You need to define the atom first before passing it to your AtomSerializer
atoms = [] # or atom = Atom.objects.all()
serializer = AtomSerializer(
data=atoms,
many=True
)
return Response(serializer.data)

Django REST Framework: passing context to a nested serializer

I've a pair of parent-child models/serializers/viewsets - Tool and ToolInput:
models.py:
class Tool(models.Model):
id = models.CharField(max_length=10000, primary_key=True, default=uuid.uuid4, editable=False)
base_command = jsonfield.JSONField(verbose_name="baseCommand")
class ToolInput(models.Model):
tool = models.ForeignKey(Tool, related_name="inputs", on_delete=models.CASCADE)
id = models.CharField(max_length=10000, primary_key=True)
label = models.CharField(max_length=10000, null=True, blank=True)
description = models.CharField(max_length=10000, null=True, blank=True)
type = jsonfield.JSONField()
serializers.py
class ToolSerializer(WritableNestedModelSerializerMixin,
serializers.HyperlinkedModelSerializer):
id = serializers.CharField()
inputs = ToolInputSerializer(many=True)
baseCommand = serializers.JSONField(source="base_command")
class Meta:
model = Tool
fields = ('id', 'inputs', 'baseCommand')
class ToolInputSerializer(WritableNestedModelSerializerMixin,
serializers.HyperlinkedModelSerializer):
class Meta:
model = ToolInput
fields = ('id', 'label', 'description', 'type')
views.py:
class ToolViewSet(viewsets.ModelViewSet):
queryset = Tool.objects.all()
lookup_field = 'id'
serializer_class = ToolSerializer
class ToolInputViewSet(viewsets.ModelViewSet):
lookup_field = 'id'
serializer_class = ToolInputSerializer
def get_queryset(self):
tool_id = self.kwargs['tool_id']
return ToolInput.objects.filter(tool_id=tool_id)
def get_serializer_context(self):
context = super(ToolInputViewSet, self).get_serializer_context()
context["tool"] = Tool.objects.get(id=self.kwargs['tool_id'])
return context
As you can see, I use ToolInputSerializer both as a standalone serializer for ToolInputViewSet and as a nested serializer within ToolViewSet.
When ToolInputSerializer is used as a nested serializer in ToolViewSet, it somehow automagically receives the value of tool argument and assigns it to ToolInput model's tool field (by the way, I feel that it's a totally wrong behavior from architectural point of view - there's no such field as tool on ToolInputSerializer at all and DRF's filling the respective model's field - it should bail out with a Field Does Not Exist error IMO and at least require a write-only field tool on serializer).
But when I use it as a standalone serializer in ToolInputViewSet, I want to assign the value of ToolInput model's tool field to Tool instance, determined by tool_id url parameter, received by ToolInputViewSet in kwargs.
I'm trying to pass the value of that field with serializer context, overriding ToolInputViewSet.get_serializer_context() method, but it's not working. How to do that properly?
Sidenote: I'm pretty tired of the messy and inconsistent, non-uniform automagic of DRF's context handling that pierces layers of Model-Serializer-Field-View architecture. It really needs to be more explicit and customizable.
As for the context, I still don't know how to make it work.
As for how nested serializers work, this is my bad: as you can see, I inherit all the ViewSets from my custom WritableNestedModelSerializerMixin, where I've overridden create() and update() methods to work with nested data structures, so this is my tinkering.
So, as a workaround, I created a separate StandaldonToolInputSerializer and modified ToolInputViewSet, adding the missing tool field to serializer and automatically patching request.data with the Tool reference:
serializers.py
class StandaloneToolInputSerializer(serializers.HyperlinkedModelSerializer):
tool = serializers.PrimaryKeyRelatedField(
write_only=True,
many=False,
queryset=Tool.objects.all()
)
inputBinding = serializers.JSONField(source="input_binding")
class Meta:
model = ToolInput
fields = ('id', 'tool', 'label', 'description', 'type', 'inputBinding')
views.py
class ToolInputViewSet(viewsets.ModelViewSet):
'''
Describes a Tool input.
'''
lookup_field = 'id'
serializer_class = StandaloneToolInputSerializer
def get_queryset(self):
tool_id = self.kwargs['tool_id']
return ToolInput.objects.filter(tool_id=tool_id)
def create(self, request, *args, **kwargs):
request.data["tool"] = self.kwargs['tool_id']
return super(ToolInputViewSet, self).create(request, *args, **kwargs)
def update(self, request, *args, **kwargs):
request.data["tool"] = self.kwargs['tool_id']
return super(ToolInputViewSet, self).update(request, *args, **kwargs)

Resources