I am using Django REST Framework
request.data = '{"id": "10", "user": "tom"}'
I want to add extra attribute like "age": "30" before sending it to further like
request.data = new_data
response = super().post(request, *args, **kwargs)
I have two issues
Why request.data is coming as string rather than dict
How can i update the request.data
In case your API is APIView then you should use update function to expand your request data object without losing the data sent from the client-side.
request.data.update({"id": "10", "user": "tom"})
request.data should be an immutable QueryDict, rather than a string. If you need to modify it:
if isinstance(request.data, QueryDict): # optional
request.data._mutable = True
request.data['age'] = "30"
The only reason you might check if it's an instance of QueryDict is so it's easier to unit test with a regular dict.
A good friend just took me to school on a much simpler approach than I illustrate above
class CreateSomething(CreateAPIView):
model = Something
queryset = Something.objects.all()
serializer_class = SomethingSerializer
perform_create(self,serializer):
def perform_create(self,serializer):
ip = self.get_ip()
## magic here: add kwargs for extra fields to write to db
serializer.save(ip_addr=ip)
def get_ip(self):
x_forwarded_for = self.request.META.get('HTTP_X_FORWARDED_FOR',None)
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = self.request.META.get('REMOTE_ADDR',None)
return ip
class SomethingSerializer(serializers.ModelSerializer):
email = serializers.EmailField(validators=[UniqueValidator(queryset=Something.objects.all())])
fieldA = serializers.CharField()
fieldB = serializers.CharField()
class Meta:
model = Customer2
fields = ['email','fieldA','fieldB','ip_addr']
read_only_fields = ['ip_addr']
Generally request in drf views is rest_framework.request.Request instance. Following to it's source code (djangorestframework==3.8.2):
#property
def data(self):
if not _hasattr(self, '_full_data'):
self._load_data_and_files()
return self._full_data
You can do:
request._full_data = your_data
It looks like a json string. To convert it to a dict you should do:
import json
data = json.loads(request.data)
then you can add extra attributes:
data['age'] = 30
Then you will have to make a new request because it seem like you cant just change the old one. This assumes that you are posting to /notes/:
from rest_framework.test import APIRequestFactory
factory = APIRequestFactory()
request = factory.post('/notes/', data, format='json')
If you are afraid of altering your request object, then use deep copy to copy the object and then you can easily alter it.
Usage::
from copy import deepcopy
# here is your other code and stuffs
data = deepcopy(request.data)
feel free to alter the data as you want as it is now mutable.
So far, this is my preferred way of altering if not using a generic view.
For any drawbacks of this method, please comment below!
If your endpoint is implemented with a DRF (Django REST Framework) ViewSet the solution can be to implement the corresponding serializer class's to_internal_value method and modify the data there.
class MyModelViewSet(viewsets.ModelViewSet):
authentication_classes = ...
...
serializer_class = MyModelSerializer
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ('id', 'user', ...)
def to_internal_value(self, data):
instance = super(MyModelSerializer, self).to_internal_value(data)
if "lastModified" in data:
# instance["id"] = 10 # That's sketchy though
instance["user"] = "tom"
return instance
According to your comment:
"because before posting i need to chnage the field names aqs required by the API"
You should be using the Field's source argument instead.
This will make error messages more consistent otherwise your user will face errors with field names they didn't provide.
I have dealt with this differently. I override the CreateAPIView create method, as follows
class IPAnnotatedObject(CreateAPIView):
model = IPAnnotatedObject
queryset = IPAnnotatedObject.objects.all()
serializer_class = IPAnnotatedObject
def create(self, request, *args, **kwargs):
request.data['ip_addr'] = self.get_ip()
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
## perform_create calls serializer.save() which calls the serializer's create() method
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def get_ip(self):
x_forwarded_for = self.request.META.get('HTTP_X_FORWARDED_FOR',None)
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = self.request.META.get('REMOTE_ADDR',None)
return ip
The corresponding serializer class looks like follows
class IPAnnotatedObjectSerializer(serializers.ModelSerializer):
email = serializers.EmailField(validators=[UniqueValidator(queryset=IPAnnotatedObject.objects.all())])
password = serializers.CharField(write_only=True)
ip_addr = serializers.IPAddressField(write_only=True)
class Meta:
model = IPAnnotatedObject
fields = ['email','password','created_ip']
def create(self, validated_data):
email, password, created_ip = validated_data['email'], validated_data['password'],validated_data['created_ip']
try:
ipAnnoObject = IPAnnotatedObject.objects.create(email=email,password=make_password(password),ip_addr=ip_addr)
except Exception as e:
# you can think of better error handler
pass
return ipAnnoOjbect
I did the following:
import json
data = json.dumps(request.data)
data = json.loads(data)
data['age'] = 100
now use variable data instead of request.data.
Though other answers are good but i just wanted to add one thing here;
We have now two ways to update a request.data object But before that, check if it is a QueryDict (as already mentioned by #mikebridge);
from django.http.request import QueryDict
if isInstance(request.data, QueryDict):
request.data._mutable = True
After that, to update the request.data, first way is;
request.data.update({'key': 'new_value'})
this will work fine, but say if the request.data['key'] which you want to update is a list, then the value will not get changed completely by the new_value but it will get appended to the older list which may cause trouble and you may not get the desired result.
So to overcome that, i.e, to completely change the value of some key, use the second method;
request.data['key'] = 'new_value'
This will change the value of request.data['key'] completely to new_value.
May be override is_valid in your serializer class will be more readable
class YourSerializer
# Don't forget it
age = serializers.IntegerField()
def is_valid(self, raise_exception: bool = ...) -> bool:
self.initial_data["age"] = 30
return super().is_valid(raise_exception)
Related
I have a requirement which I would like to allow multiple files to be uploaded within the same post request to create an object. I currently have a method of doing this, but after looking at some other examples it doesn't appear to be intended way to do it.
models.py
class Analyzer(models.Model):
name = models.CharField(max_length=100, editable=False, unique=True)
class Atomic(models.Model):
name = models.CharField(max_length=20, unique=True)
class Submission(models.Model):
class Meta:
ordering = ['-updated_at']
issued_at = models.DateTimeField(auto_now_add=True, editable=False)
completed = models.BooleanField(default=False)
analyzers = models.ManyToManyField(Analyzer, related_name='submissions')
atomic = models.ForeignKey(Atomic, verbose_name='Atomic datatype', related_name='submission', on_delete=models.CASCADE)
class BinaryFile(models.Model):
class Meta:
verbose_name = 'Binary file'
verbose_name_plural = 'Binary files'
def __str__(self):
return self.file.name
submission = models.ForeignKey(Submission, on_delete=models.CASCADE, related_name='binary_files')
file = models.FileField(upload_to='uploads/binary/')
serializers.py
class BinaryFileSerializer(serializers.ModelSerializer):
class Meta:
model = models.BinaryFile
fields = '__all__'
class SubmissionCreateSerializer(serializers.ModelSerializer):
class Meta:
model = models.Submission
fields = ['id', 'completed', 'atomic', 'analyzers', 'binary_files']
id = serializers.ReadOnlyField()
completed = serializers.ReadOnlyField()
atomic = serializers.PrimaryKeyRelatedField(many=False, queryset=models.Atomic.objects.all()
analyzers = serializers.PrimaryKeyRelatedField(many=True, queryset=models.Analyzer.objects.all()
binary_files = BinaryFileSerializer(required=True, many=True)
def validate(self, data):
# # I dont really like manually taking invalidated input!!
data['binary_files'] = self.initial_data.getlist('binary_files')
return data
def create(self, validated_data):
submission = models.Submission.objects.create(
atomic=validated_data['atomic']
)
submission.analyzers.set(validated_data['analyzers'])
# # Serialize the files - this seems too late to be doing this!
for file in validated_data['binary_files']:
binary_file = BinaryFileSerializer(
data={'file': file, 'submission': submission.id}
)
if binary_file.is_valid():
binary_file.save()
return submission
Main question: While the above works, the child serializer (BinaryFileSerializer) doesn't get called until I explicitly call it in create(), which is after the validation should have occurred. Why does this never get called?
I also don't like the fact I have to manually do a self.initial_data.getlist('binary_files') and manually add it to data - this should have already been added and validated, no?
My thought is that as I defined binary_files = BinaryFileSerializer, this serializer should be called to validate that particular fields input?
FYI, I'm using the following to test POST uploads:
curl -F "binary_files=#file2.txt" -F "binary_files=#file2.txt" -F "atomic=7" -F "analyzers=12" -H "Accept: application/json; indent=4" http://127.0.0.1:8000/api/submit/
TIA!
Update: The question is now, if a validate() funciton is added to the BinaryFileSerializer, why does it not get called?
Possible duplicate --- Django REST: Uploading and serializing multiple images.
From the DRF Writable Nested Serializer doc,
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.
From this, it's clear that the child serializer (BinaryFileSerializer) won't call its own create() method unless explicitly called.
The aim of your HTTP POST request is to create new Submission instance (and BinaryFile instance). The creation process undergoes in the create() method of the SubmissionCreateSerializer serializer, which is you'd overridden. So, it will act/execute as per your code.
UPDATE-1
Things to remember
1. AFAIK, we can't send nested multipart/form-data
2. Here I'm only trying to implementing the least case scenario
3. I'm tested this solution with POSTMAN rest api test tool.
4. This method may be complex (until we found a better one).
5. Assuming your view class is subclass of ModelViewSet class
What I'm going to do?
1. Since we can't send the files/data in a nested fashion, we have to send it flat mode.
image-1
2. Override the __init__() method of the SubmissionSerializer serializer and dynamically add as much FileField() attribute according to the request.FILES data.We could somehow use ListSerializer or ListField here. Unfortunately I couldn't find out a way :(
# init method of "SubmissionSerializer"
def __init__(self, *args, **kwargs):
file_fields = kwargs.pop('file_fields', None)
super().__init__(*args, **kwargs)
if file_fields:
field_update_dict = {field: serializers.FileField(required=False, write_only=True) for field in file_fields}
self.fields.update(**field_update_dict)
So, what id file_fields here? Since the form-data is a key-value pair, every file data must be associated with a key. Here in image-1, you could see file_1 and file_2.
3. Now we need to pass the file_fields values from the view. Since this operation is creating new instance, we need to override the create() method of the API class.
# complete view code
from rest_framework import status
from rest_framework import viewsets
class SubmissionAPI(viewsets.ModelViewSet):
queryset = Submission.objects.all()
serializer_class = SubmissionSerializer
def create(self, request, *args, **kwargs):
# main thing starts
file_fields = list(request.FILES.keys()) # list to be passed to the serializer
serializer = self.get_serializer(data=request.data, file_fields=file_fields)
# main thing ends
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)
4. Now, all values will be serialized properly. It's time to override the create() method of the SubmissionSerializer() to map the relations
def create(self, validated_data):
from django.core.files.uploadedfile import InMemoryUploadedFile
validated_data_copy = validated_data.copy()
validated_files = []
for key, value in validated_data_copy.items():
if isinstance(value, InMemoryUploadedFile):
validated_files.append(value)
validated_data.pop(key)
submission_instance = super().create(validated_data)
for file in validated_files:
BinaryFile.objects.create(submission=submission_instance, file=file)
return submission_instance
5. That's it!!!
Complete Code Snippet
# serializers.py
from rest_framework import serializers
from django.core.files.uploadedfile import InMemoryUploadedFile
class SubmissionSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
file_fields = kwargs.pop('file_fields', None)
super().__init__(*args, **kwargs)
if file_fields:
field_update_dict = {field: serializers.FileField(required=False, write_only=True) for field in file_fields}
self.fields.update(**field_update_dict)
def create(self, validated_data):
validated_data_copy = validated_data.copy()
validated_files = []
for key, value in validated_data_copy.items():
if isinstance(value, InMemoryUploadedFile):
validated_files.append(value)
validated_data.pop(key)
submission_instance = super().create(validated_data)
for file in validated_files:
BinaryFile.objects.create(submission=submission_instance, file=file)
return submission_instance
class Meta:
model = Submission
fields = '__all__'
# views.py
from rest_framework import status
from rest_framework import viewsets
class SubmissionAPI(viewsets.ModelViewSet):
queryset = Submission.objects.all()
serializer_class = SubmissionSerializer
def create(self, request, *args, **kwargs):
# main thing starts
file_fields = list(request.FILES.keys()) # list to be passed to the serializer
serializer = self.get_serializer(data=request.data, file_fields=file_fields)
# main thing ends
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)
Screenhots and other stuffs
1. POSTMAN console
2. Django Shell
In [2]: Submission.objects.all()
Out[2]: <QuerySet [<Submission: Submission object>]>
In [3]: sub_obj = Submission.objects.all()[0]
In [4]: sub_obj
Out[4]: <Submission: Submission object>
In [5]: sub_obj.__dict__
Out[5]:
{'_state': <django.db.models.base.ModelState at 0x7f529a7ea240>,
'id': 5,
'issued_at': datetime.datetime(2019, 3, 27, 8, 45, 42, 193943, tzinfo=<UTC>),
'completed': False,
'atomic_id': 1}
In [6]: sub_obj.binary_files.all()
Out[6]: <QuerySet [<BinaryFile: uploads/binary/logo-800.png>, <BinaryFile: uploads/binary/Doc.pdf>, <BinaryFile: uploads/binary/invoice_2018_11_29_04_57_53.pdf>, <BinaryFile: uploads/binary/Screenshot_from_2019-02-13_16-22-53.png>]>
In [7]: for _ in sub_obj.binary_files.all():
...: print(_)
...:
uploads/binary/logo-800.png
uploads/binary/Doc.pdf
uploads/binary/invoice_2018_11_29_04_57_53.pdf
uploads/binary/Screenshot_from_2019-02-13_16-22-53.png
3. Django Admin Screenhot
I'm trying to serialize some objects whose data is stored in 2 databases, linked by common UUIDs. The second database DB2 stores personal data, so it is run as a segregated microservice to comply with various privacy laws. I receive the data as a decoded list of dicts (rather than an actual queryset of model instances). How can I adapt the ModelSerializer to serialize this data?
Here's a minimal example of interacting with DB2 to get the personal data:
# returns a list of dict objects, approx representing PersonalData.__dict__
# `custom_filter` is a wrapper for the Microservice API using `requests`
personal_data = Microservice.objects.custom_filter(uuid__in=uuids)
And here's a minimal way of serializing it, including the date of birth:
class PersonalDataSerializer(serializers.Serializer):
uuid = serializers.UUIDField() # common UUID in DB1 and DB2
dob = serializers.DateField() # personal, so can't be stored in DB1
In my application, I need to serialize the Person queryset, and related personal_data, into one JSON array.
class PersonSerializer(serializers.ModelSerializer):
dob = serializers.SerializerMethodField()
# can't use RelatedField for `dob` because the relationship isn't
# codified in the RDBMS, due to it being a separate Microservice.
class Meta:
model = Person
# A Person object has `uuid` and `date_joined` fields.
# The `dob` comes from the personal_data, fetched from the Microservice
fields = ('uuid', 'date_joined', 'dob',)
def get_dob(self):
raise NotImplementedError # for the moment
I don't know if there's a nice DRF way to link the two. I definitely don't want to be sending (potentially thousands of) individual requests to the microservice by including a single request in get_dob. The actual view just looks like this:
class PersonList(generics.ListAPIView):
model = Person
serializer_class = PersonSerializer
def get_queryset(self):
self.kwargs.get('some_filter_criteria')
return Person.objects.filter(some_filter_criteria)
Where should the logic go to link the microservice data into the serializer, and what should it look like?
I suggest you to override the serializer and your list method.
Serializer:
class PersonSerializer(models.Serializer):
personal_data = serializers.DictField()
class Meta:
model = Person
make a function to add personal_data dictionary to persons object. Use this method before giving the list of person objects to the serializer.
def prepare_persons(persons):
person_ids = [p.uuid for p in persons]
personal_data_list = Microservice.objects.custom_filter(uuid__in=person_ids)
personal_data_dict = {pd['uuid']: pd for pd in personal_data_list}
for p in persons:
p.personal_data = personal_data_dict[p.id]
return persons
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
page = prepare_persons(page)
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
else:
persons = prepare_persons(queryset)
serializer = self.get_serializer(persons, many=True)
return Response(serializer.data)
Because you want to only hit your database one time, a good way to add your extra data to your queryset is by adding a custom version of ListModelMixin to your ViewSet that includes extra context:
class PersonList(generics.ListAPIView):
...
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
# Pseudo-code for filtering, adjust to work for your use case
filter_criteria = self.kwargs.get('some_filter_criteria')
personal_data = Microservice.objects.custom_filter(filter_criteria)
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(
page,
many=True,
context={'personal_data': personal_data}
)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(
queryset,
many=True,
context={'personal_data': personal_data}
)
return Response(serializer.data)
Then, access the extra context in your serializer by overriding the to_representation method:
def to_representation(self, instance):
"""Add `personal_data` to the object from the Microservice"""
ret = super().to_representation(instance)
personal_data = self.context['personal_data']
ret['personal_data'] = personal_data[instance.uuid]
return ret
The ViewSets do everything that I want, but I am finding that if I want to pass extra context to a template (with TemplateHTMLRenderer) then I will have to get at the functions that give responses.. (like list(), create(), etc)
The only way I can see to get into these is to completely redefine them in my ViewSet, but it seems that there should be an easy way to add a bit of context to the Template without having to redefine a whole set of methods...
class LanguageViewSet(viewsets.ModelViewSet):
"""Viewset for Language objects, use the proper HTTP methods to modify them"""
# TODO: add permissions for this view?
queryset = Language.objects.all()
serializer_class = LanguageSerializer
filter_backends = (filters.DjangoFilterBackend, )
filter_fields = ('name', 'active')
Right now my code is looking like this but I will be wanting to add different context to the responses and I am trying to avoid redefining an entire method for such a small change. like this...
class LanguageViewSet(viewsets.ModelViewSet):
"""Viewset for Language objects, use the proper HTTP methods to modify them"""
# TODO: add permissions for this view?
queryset = Language.objects.all()
serializer_class = LanguageSerializer
filter_backends = (filters.DjangoFilterBackend, )
filter_fields = ('name', 'active')
def list(self, **kwargs):
"""Redefinition of list"""
..blah blah everything that list does
return Response({"foo": "bar"}, template_name="index.html")
I faced the same issue and resolved in a slightly different way in Django Rest Framework (DRF) 3.x. I believe it is not necessary to override the relatively complex render method on the TemplateHTMLRenderer class, but only the much simpler method get_template_context (or: resolve_context in earlier versions of DRF).
The procedure is as follows:
Override the get_renderer_context method on your ViewSet (as already suggested):
def get_renderer_context(self):
context = super().get_renderer_context()
context['foo'] = 'bar'
return context
Subclass TemplateHTMLRenderer, but only override the get_template_context method instead of the entire render method (render calls self.get_template_context to retrieve the final context to pass to the template):
class ModifiedTemplateHTMLRenderer(TemplateHTMLRenderer):
def get_template_context(self, data, renderer_context):
"""
Override of TemplateHTMLRenderer class method to display
extra context in the template, which is otherwise omitted.
"""
response = renderer_context['response']
if response.exception:
data['status_code'] = response.status_code
return data
else:
context = data
# pop keys which we do not need in the template
keys_to_delete = ['request', 'response', 'args', 'kwargs']
for item in keys_to_delete:
renderer_context.pop(item)
for key, value in renderer_context.items():
if key not in context:
context[key] = value
return context
{{ foo }} is now available as a template variable - as are all other variables added in get_renderer_context.
Although I disagree with 'pleasedontbelong' in principle, I agree with him on the fact that the extra contextual data ought to be emitted from the serializer. That seems to be the cleanest way since the serializer would be returning a native python data type which all renderers would know how to render.
Heres how it would look like:
ViewSet:
class LanguageViewSet(viewsets.ModelViewSet):
queryset = Language.objects.all()
serializer_class = LanguageSerializer
filter_backends = (filters.DjangoFilterBackend, )
filter_fields = ('name', 'active')
def get_serializer_context(self):
context = super().get_serializer_context()
context['foo'] = 'bar'
return context
Serializer:
class YourSerializer(serializers.Serializer):
field = serializers.CharField()
def to_representation(self, instance):
ret = super().to_representation(instance)
# Access self.context here to add contextual data into ret
ret['foo'] = self.context['foo']
return ret
Now, foo should be available inside your template.
Another way to achieve this, in case you don't wish to mess with your serializers, would be to create a custom TemplateHTMLRenderer.
class TemplateHTMLRendererWithContext(TemplateHTMLRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
# We can't really call super in this case, since we need to modify the inner working a bit
renderer_context = renderer_context or {}
view = renderer_context.pop('view')
request = renderer_context.pop('request')
response = renderer_context.pop('response')
view_kwargs = renderer_context.pop('kwargs')
view_args = renderer_context.pop('args')
if response.exception:
template = self.get_exception_template(response)
else:
template_names = self.get_template_names(response, view)
template = self.resolve_template(template_names)
context = self.resolve_context(data, request, response, render_context)
return template_render(template, context, request=request)
def resolve_context(self, data, request, response, render_context):
if response.exception:
data['status_code'] = response.status_code
data.update(render_context)
return data
To add data into the context, ViewSets provide a get_renderer_context method.
class LanguageViewSet(viewsets.ModelViewSet):
queryset = Language.objects.all()
serializer_class = LanguageSerializer
filter_backends = (filters.DjangoFilterBackend, )
filter_fields = ('name', 'active')
def get_renderer_context(self):
context = super().get_renderer_context()
context['foo'] = 'bar'
return context
{'foo': 'bar'} should now be available in your template.
Good morning everybody!
I've a problem with django REST ModelSerializer.
This is my serializer:
class TransactionPayOutSerializer(serializers.ModelSerializer):
author = serializers.SlugRelatedField(queryset=Users.objects.all(), slug_field='unique_id', allow_null=True)
wallet = serializers.SlugRelatedField(queryset=Wallet.objects.all(), slug_field='wallet_id')
bank_account = serializers.SlugRelatedField(queryset=BankAccount.objects.all(), slug_field='bank_account_id')
class Meta:
model = PayOut
fields = ('author', 'wallet', 'amount',
'currency', 'bank_account')
def __init__(self, **kwargs):
if 'instance' in kwargs:
super(TransactionPayOutSerializer,self).__init__(kwargs.get('instance'))
else:
self.merchant = kwargs.pop('merchant')
super(TransactionPayOutSerializer, self).__init__(**kwargs)
def validate(self, data):
print 'Inside serializer validate(), data:'
print data
wallet = data['wallet']
user_wallet = UserWallets.objects.get(wallet=wallet)
if self.merchant != user_wallet.merchant:
raise serializers.ValidationError("Wallet not found")
wallet.fetch()
if wallet.balance < data['amount']:
raise serializers.ValidationError("Not enough founds in wallet")
return data
def create(self, validated_data):
print 'Inside TransactionPayOutSerializer create():'
print 'Validated data:'
print validated_data
pay_out = PayOut(**validated_data)
user_wallet = UserWallets.objects.get(wallet=validated_data['wallet'])
pay_out.author = user_wallet.user
pay_out.merchant = self.merchant
pay_out.save()
return pay_out
It works perfectly but my problem is:
When the serializer is used for a get request (serializer.data) it's perfect.
But when the serializer is used for a post request (TransactionPayOutSerializer(data=request.DATA)) it absolutely wants the 'user' parameter in the JSON.
What is the best way to keep serialzer.data return 'user' representation making TransactionPayOutSerializer(data=request.DATA) works without receiving 'user' as parameter inside JSON post request?
I should use simple serializers instead of ModelSerializers?
Thanks in advance!
Serializer Fields
read_only Set this to True to ensure that the field is used when
serializing a representation, but is not used when creating or
updating an instance during deserialization.
Defaults to False
write_only Set this to True to ensure that the field may be used when
updating or creating an instance, but is not included when serializing
the representation.
Defaults to False
By default, when asking a list of a model like "/cars", django rest outputs all of the model data. I want it to output only pk's on a list request, and full model data on a detail request. I am using ModelSerializer and ModelViewSet.
PS. Is it supposed to be like this by design? Pulling so much unneeded data seems like such a waste.
Thanks :)
You can simply override the 'list' method of viewset to get desired response like this:
from rest_framework.response import Response
def list(self, request, *args, **kwargs):
pks = []
qs = self.get_queryset()
for obj in qs:
pks.append(obj.pk)
return Response(data=pks)
I customized the code to suit my requirement. I created the following 2 mixins. Make your APIListView extend FieldFilterMixin and your serializer extend SerializerFieldsMixin. Pass fl in GET request with comma separated values to get the required fields in response.
class SerializerFieldsMixin(object):
"""
Return only the fields asked for.
Don't return any extra fields in serializer.
"""
def get_fields(self):
all_fields = super(SerializerFieldsMixin,self).get_fields()
asked_fields = self.context.get('asked_fields')
if not asked_fields:
return all_fields
all_fields = OrderedDict([(k,v) for k,v in all_fields.items() if k in asked_fields])
return all_fields
class FieldFilterMixin(object):
"""
To be used with List/Retrieve views.
Set class attribute fields for the fields you want to display.
Or override get_required_fields to customize.
"""
def get_required_fields(self):
if self.request.GET.has_key("fl"):
return self.request.GET["fl"]
return []
def get_serializer_context(self):
methods_to_act_on = ["GET","HEAD"]
context = super(FieldFilterMixin,self).get_serializer_context()
asked_fields = self.get_required_fields()
if asked_fields and self.request.method in methods_to_act_on:
context["asked_fields"] = asked_fields
return context