How to add additional fields on the base of inputs in django rest framework mongoengine - python

I am developing an API, using django-rest-framework-mongoengine with MongoDb, I want to append additional fields to the request from the serializer on the base of user inputs, for example If user enters keyword=#rohit49khatri, I want to append two more fields to the request by manipulating keyword, like type=username, username=rohit49khatri
Here's my code:
Serializer
class SocialFeedCreateSerializer(DocumentSerializer):
type = 'username'
class Meta:
model = SocialFeedSearchTerm
fields = [
'keyword',
'type',
]
read_only_fields = [
'type'
]
View
class SocialFeedCreateAPIView(CreateAPIView):
queryset = SocialFeed.objects.all()
serializer_class = SocialFeedCreateSerializer
def perform_create(self, serializer):
print(self.request.POST.get('type'))
But when I print type parameter, It gives None
Please help me save some time. Thanks.

for the additional question: how to get type parameter?
# access it via `django rest framework request`
self.request.data.get('type', None)
# or via `django request`
self.request.request.POST.get('type', None)
for the original question:
situation 1) IMHO for you situation, perform_create can handle it:
def perform_create(self, serializer):
foo = self.request.data.get('foo', None)
bar = extract_bar_from_foo(foo)
serializer.save(
additional_foo='abc',
additional_bar=bar,
)
situation 2) If you need to manipulate it before the data goes to serializer (so that the manipulated data will pass through serializer validation):
class SocialFeedCreateAPIView(CreateAPIView):
queryset = SocialFeed.objects.all()
serializer_class = SocialFeedCreateSerializer
def create(self, request, *args, **kwargs):
# you can check the original snipeet in rest_framework/mixin
# original: serializer = self.get_serializer(data=request.data)
request_data = self.get_create_data() if hasattr(self, 'get_create_data') else request.data
serializer = self.get_serializer(data=request_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)
def get_create_data(self):
data = self.request.data.copy()
# manipulte your data
data['foo'] = 'foo'
return data
situation 3) If you do need to manipulate the request:
(here's just an example, you can try to find out another place to manipulate the request.)
class SocialFeedCreateAPIView(CreateAPIView):
queryset = SocialFeed.objects.all()
serializer_class = SocialFeedCreateSerializer
def initial(self, request, *args, **kwargs):
# or any other condition you want
if self.request.method.lower() == 'post':
data = self.request.data
# manipulate it
data['foo'] = 'foo'
request._request.POST = data
return super(SocialFeedCreateAPIView, self).initial(request, *args, **kwargs)

Related

How can I fix my PATCH request to append to ArrayField, rather than delete and replace the whole object?

I have a problem with my PATCH request instance. Currently, data that is being PATCHED and sent from a request is overriding every item in my list of strings ArrayField inside my model object.
I need my patch request behavior to append to the rest of the items in ArrayField object, not delete/override.
How can I go about doing that?
I assume I need to override the patch method within RetrieveUpdateAPIView,
so I've started out here:
def patch(self, request, **kwargs):
item = self.kwargs.get('slug')
serializer = StockListSerializer(item, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
Response(serializer, status=status.HTTP_200_OK)
return Response(status=status.HTTP_400_BAD_REQUEST)
serializer.py:
class StringArrayField(serializers.ListField):
def to_representation(self, obj):
obj = super().to_representation(obj)
return ",".join([str(element) for element in obj])
def to_internal_value(self, data):
data = data.split(",")
return super().to_internal_value(data)
class StockListSerializer(serializers.ModelSerializer):
stock_list = StringArrayField()
class Meta:
model = Bucket
fields = ("stock_list",)
view.py
class EditBucketSymbols(generics.RetrieveUpdateAPIView):
permission_classes = [IsAuthenticated]
serializer_class = StockListSerializer
queryset = Bucket.objects.all()
def get_object(self, queryset=queryset, **kwargs):
item = self.kwargs.get('slug')
return get_object_or_404(Bucket, slug=item)
url.py:
path('bucket/symbols/<str:slug>/', EditBucketSymbols.as_view(), name='editsymbols')
model.py:
stock_list = ArrayField(models.CharField(max_length=6,null=True),size=30,null=True, blank=True)
You can make use of the field-lever validation technique here,
class StockListSerializer(serializers.ModelSerializer):
stock_list = StringArrayField()
class Meta:
model = Bucket
fields = ("stock_list",)
def validate_stock_list(self, stock_list):
existing_stock_list = []
if self.instance and self.instance.stock_list:
# Patch or Put request
existing_stock_list = self.instance.stock_list
return existing_stock_list + stock_list
I would do this within the .validate() method of the serializer. something like:
def validate(self, validated_data):
# Make sure that this only runs for Patch requests
if self.context["request"].method == "PATCH"
# Get list of existing stocks from instance and append incoming list
stock_list = self.instance.stock_list + validated_data["stock_list"]
# Replace data
validated_data["stock_list"] = stock_list
return validated_data

How to create multiple objects in serializers create method?

I am trying to upload a csv file and then using it to populate a table in the database (creating multiple objects).
serializers.py:
def instantiate_batch_objects(data_list, user):
return [
WorkData(
work=db_obj['work'],
recordTime=db_obj['recordTime'],
user=user
) for db_obj in data_list
]
class FileUploadSerializer(serializers.ModelSerializer):
filedata = serializers.FileField(write_only=True)
class Meta:
model = WorkData
fields = ['user', 'filedata']
def create(self, validated_data):
file = validated_data.pop('filedata')
data_list = csv_file_parser(file)
batch = instantiate_batch_objects(data_list, validated_data['user'])
work_data_objects = WorkData.objects.bulk_create(batch)
# print(work_data_objects[0])
return work_data_objects
views.py:
class FileUploadView(generics.CreateAPIView):
queryset = WorkData.objects.all()
permission_classes = [IsAuthenticated]
serializer_class = FileUploadSerializer
# I guess, this is not need for my case.
def get_serializer(self, *args, **kwargs):
print(kwargs.get('data'))
if isinstance(kwargs.get('data', {}), list):
kwargs['many'] = True
return super().get_serializer(*args, **kwargs)
models.py
class WorkData(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='work_data',
)
work = models.IntegerField(blank=False, null=False)
recordTime = models.DateTimeField(blank=False, null=True)
When I upload the file and post it I get this error:
Got AttributeError when attempting to get a value for field user on serializer FileUploadSerializer. The serializer field might be named incorrectly and not match any attribute or key on the list instance. Original exception text was: 'list' object has no attribute 'user'.
But I can see table is populated successfully in the database. What should I return from create method of FileUploadSerializer?
OK, after trying an example myself I was able to reproduce the errors, I have a better understanding of why this is happing now.
First, let's put the implementation of create() on the view class here
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.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)
The original error of Got AttributeError when attempting to get a value for field user... etc happened because the create() in the FileUploadView is returning serializer.data which is expecting fields user and filedata but create() on FileUploadSerializer is returning a list of objects so you can see now why this is happening.
You can solve this by overriding create() on FileUploadView and serialize the returned serializer data with a WorkDataSerializer that you will create
For ex:
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
workData = WorkDataSerializer(data=serializer.data)
return Response(workData.data, status=status.HTTP_201_CREATED, headers=headers)
OR, you can do it on serializer level - which I prefer -
For example:
class FileUploadSerializer(serializers.ModelSerializer):
filedata = serializers.FileField(write_only=True)
created_objects_from_file = serializers.SerializerMethodField()
def get_created_objects_from_file(self, obj):
file = self.validated_data.pop('filedata')
data_list = csv_file_parser(file)
batch = instantiate_batch_objects(data_list, self.validated_data['user'])
work_data_objects = WorkData.objects.bulk_create(batch)
return WorkDataSerializer(work_data_objects, many = True).data
class Meta:
model = WorkData
fields = ['user', 'filedata']
class WorkDataSerializer(serializers.Serializer):
# fields of WorkData model you want to return
This should work with no problems, note that SerializerMethodField is read_only by default
see https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield

How do I override viewsets.ModelViewSet in Django REST Framework?

So my model is simple as
class Face(models.Model):
uid = models.CharField(max_length=510, primary_key=True)
photo = models.ImageField(upload_to='face_photos')
serializer
class FaceSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Face
fields = ['uid', 'photo']
extra_kwargs = {'uid': {'required': True}, 'photo': {'required': True}}
and view should be something like
class FaceViewSet(viewsets.ModelViewSet):
queryset = Face.objects.all()
serializer_class = FaceSerializer
permission_classes = [permissions.AllowAny]
And it works. However:
I don't want list, update, delete options. Only POST and GET.
I want to add my logic on post, so if uid exists then update, else create... as well other processing.
I want custom response after the POST.
How do I achieve this all not loosing all the goodies that viewsets.ModelViewSet provides, like validations, auto generated HTML fields in Rest API web view, etc?
This worked for me:
class FaceViewSet(mixins.CreateModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
"""
API endpoint for adding and processing new client (by uid) face
"""
queryset = Face.objects.all()
serializer_class = FaceSerializer
permission_classes = [permissions.AllowAny]
def create(self, request):
if "uid" in request.POST:
try:
instance = Face.objects.get(pk=request.POST['uid'])
serializer = FaceSerializer(
instance=instance,
data=request.data
)
except Face.DoesNotExist:
serializer = FaceSerializer(data=request.data)
else:
serializer = FaceSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = FaceSerializer(instance=instance)
return Response(serializer.data)
for number one you can use this:
http_method_names = ['get','post']
buth get method also cintains list method

Get Authenticated user on many=True serializer Viewset

I'm writing a rest api using Django Rest Framework, I have an endpoint to create objects on POST method and this method is overridden in order to allow bulk adding. However, the object is an "intermediate table" between Pacient and Symptoms and in order to create it I need to provide the pacient object or id and the same for the symptom. I get the Symptom id in the request, so that's not an issue, however the pacient is the authenticated user (who's making the request). Now, how do I edit the create method in the serializer in order to do that?
Here's my view:
class PacienteSintomaViewSet(viewsets.ModelViewSet):
serializer_class = SintomaPacienteSerializer
queryset = SintomaPaciente.objects.all()
permission_classes = (IsAuthenticated, )
http_method_names = ['post', 'get']
def create(self, request, *args, **kwargs):
many = True if isinstance(request.data, list) else False
serializer = SintomaPacienteSerializer(data=request.data, many=many)
if serializer.is_valid():
sintomas_paciente_lista = [SintomaPaciente(**data) for data in serializer.validated_data]
print(serializer.validated_data)
SintomaPaciente.objects.bulk_create(sintomas_paciente_lista)
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors,status=status.HTTP_400_BAD_REQUEST)
And this is my serializer:
class SintomaPacienteSerializer(serializers.ModelSerializer):
def create(self, validated_data):
sintoma_paciente = SintomaPaciente.objects.create(
sintoma_id=self.validated_data['sintoma_id'],
paciente_id=THIS NEEDS TO BE FILLED,
data=self.validated_data['data'],
intensidade=self.validated_data['intensidade'],
)
return sintoma_paciente
class Meta:
model = SintomaPaciente
fields = ('id', 'sintoma_id', 'paciente_id', 'intensidade', 'data',
'primeiro_dia', 'ativo')
There is two way.
First one, you can pass your user to serializer inside context, and use it in serializer:
in your view:
def create(self, request, *args, **kwargs):
many = True if isinstance(request.data, list) else False
serializer = SintomaPacienteSerializer(data=request.data, many=many,context={'user':request.user})
in your serializer you can access this user with self.context['user']
Second way, you don't need to pass user to serializer again. Also If you already override the create method in your View, you don't need to override create method in serializer. I think it is wrong logically. Anyway, you can use your user when create object in view:
def create(self, request, *args, **kwargs):
many = True if isinstance(request.data, list) else False
serializer = SintomaPacienteSerializer(data=request.data, many=many)
if serializer.is_valid():
sintomas_paciente_lista = [SintomaPaciente(**data,paciente_id=request.user.id) for data in serializer.validated_data]
print(serializer.validated_data)
....

How better way to create a function to pass a JSON by serializer in django rest?

I'm studying django rest framework and would like the create a function. In this function, I need to pass a list in JSON and update by serializer.
For help I wrote a code example below.
Serialzer example:
class GarageViewSet(viewsets.ModelViewSet):
queryset = Garage.objects.all()
serializer_class = GarageSerializer
model = Garage
class CarViewSet(RestrictedQuerysetMixin, viewsets.ModelViewSet):
queryset = Car.objects.all()
serializer_class = CarSerializer
model = Car
Well. I need to update a car list through the garage serializer. I'm thinking anything like this:
(example view)
class GarageViewSet(viewsets.ModelViewSet):
queryset = Garage.objects.all()
serializer_class = GarageSerializer
model = Garage
#action(detail=True, methods=['put'])
def update_car(self, request):
...
serializer = CarSerializer(queryset, many=True)
...
return Response(serializer.data)
Attempt 1:
Searching and reading the doc, I tried this way:
#action(methods=['put'], detail=False)
def update_car(self, request, *args, **kwargs):
if request.method == 'PUT':
data = JSONParser().parse(request)
serializer = CarSerializer(data=data, many=True)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data)
return JsonResponse(serializer.errors, status=400)
But I received this error:
non_field_errors:
["Expected a list of items but got type "dict"."]
Attempt 2:
With a #fxgx I tried too:
def update_car(self, request):
serializer = CarSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
validated_data = dict(list(serializer.validated_data.items()))
queryset = Car.objects.update(**validated_data)
return Response(CarSerializer(queryset, many=True).data)
But I received this error:
{
"detail": "Not found."
}
DRF serializers do not support bulk updates, you must pass an object instance to serializer to update it. What you can do is serializing data using serializer, updating objects with validated data and then serializing objects again to get response data:
class GarageViewSet(viewsets.ModelViewSet):
queryset = Garage.objects.all()
serializer_class = GarageSerializer
model = Garage
#action(detail=False, methods=['put'])
def update_car(self, request):
...
# Use partial=True for partial updates.
serializer = CarSerializer(data=request.data)
# Validate data.
serializer.is_valid(raise_exception=True)
# Get validated data in dictionary format.
validated_data = dict(list(serializer.validated_data.items()))
# Update objects
quertset.update(**validated_data)
...
return Response(CarSerializer(queryset, many=True).data)

Categories

Resources