In my models.py, I create a field that is supposed to be non-editable by the user:
request_status = models.CharField(
max_length = 100,
default = 'received',
editable = False
)
In the views.py file, I override the create() method (from the generics.ListCreateAPIView and CreateModelMixin superclasses) to activate a docker container and, if it is successfully done, I want to modify the request_status field to something like container_activated and then send back the HTTP response to the user.
So... is there a way of modifying a non-editable field? Is there a better way of setting up the model instead?
My best guess is that there is a way of modifying the data that gets registered in the API DB through one of the lines of the standard create() method (non-editable fields do not appear in the data property):
serializer = self.get_serializer(data = request.data)
serializer.is_valid(raise_exception = True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
editable=False doesn't mean you can't edit the field at all, it only means it won't show up in the admin or modelforms by default. In DRF, it means it will be automatically set as read_only; but you can override this simply defining the field manually.
class MySerializer(serializers.ModelSerializer):
request_status = serializers.CharField(max_length=100, default='received')
class Meta:
...
This is kind of a circumvention but it is working more or less well. I changed my field back to have editable = True and then I simply forced it to be a default locked (hardcoded) value. In the end, inside the create() of the views.py class, you put something like:
def create(
self,
request,
*args,
**kwargs
):
# Settings of the POST request
req_data = request.data.dict()
req_data['request_status'] = 'not yet processed'
# Is everything OK?
if processing_OK == 0:
req_data['request_status'] = 'processed'
# Save the data
serializer = self.get_serializer(data = req_data)
serializer.is_valid(raise_exception = True)
serializer.save() # `self.perform_create(serializer)` originally
headers = self.get_success_headers(serializer.data)
# Return Response
return Response(
data = serializer.data,
status = status.HTTP_201_CREATED,
headers = headers
)
The key part was understanding that you can override what goes into the HTTP POST inside the API by altering the dictionary from request.data.dict(). Then you simply pass that dict (usually it's a QueryDict actually) inside the self.get_serializer() and follow the standard create() method.
If you configure editable = False, you're not able to force the changes in the way I posted here. There's probably a better way of doing this in the end.
Related
I'm in a situation where I want to change the serializer field depending on a condition. Where the condition comes doesn't matter but I want to be able to switch between serializer fields as the following example:
class EntrySerializer(serializers.ModelSerializer):
# author defined from ModelSerializer
def get_author_serializer(self):
request = self.context.get('request')
GET = getattr(request, 'GET', {})
if request and GET and GET.get('include_author')=='true':
author_serializer = UserSerializer()
else:
author_serializer = serializers.PrimaryKeyRelatedField(
read_only=True, default=serializers.CurrentUserDefault()
)
return author_serialize
Of course this doesn't work because get_FIELD_serializer doesn't exist, but I'm looking for the simplest solution that can do this.
I've tried writing author as a property in a naive attempt but it didn't work.
I am aware that I can write multiple EntrySerializers and use get_serializer_class but that is just too much boilerplate code for such a small customization.
If you just want to change a serializers' field based on a condition, you could do something like this.
class MySerializer(serializers.ModelSerializer):
author = serializers.SerializerMethodField()
def get_author(self, instance):
# your condition here
if your_condition:
return instance.author
return 'hello'
Check the docs for SerializerMethodField
https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield
I have a variable named email in view.
I want to access this in ManageSerializer.
How can I pass this argument in serializer and get there?
views.py
email = 'xyz#gmail.com'
interviewData = Manage.objects.
filter(catcher_id = userCheck['id'], acceptation = '1').
filter(invitation_date__gte = dateToday)[:5];
serializer = ManageSerializers(interviewData, many = True)
Maybe pass it as kwargs:
ManageSerializers(interviewData, many = True, email= email)
You can access this in the init of the Serializer, using something like:kwargs.pop('email')
OR
You can pass the context to the Serializer like this.
ManageSerializers(interviewData, many = True, context={'email': email})
and you can access the context in Serializer like self.context['email']
You should pass it in context variable:
serializer = ManageSerializers(interviewData, many=True, context={'email': email})
Docs: Including extra context
I'm trying to implement partial_update with Django Rest Framework but I need some clarification because I'm stuck.
Why do we need to specify partial=True?
In my understanding, we could easily update Demo object inside of partial_update method. What is the purpose of this?
What is inside of serialized variable?
What is inside of serialized variable in partial_update method? Is that a Demo object? What function is called behind the scenes?
How would one finish the implementation here?
Viewset
class DemoViewSet(viewsets.ModelViewSet):
serializer_class = DemoSerializer
def partial_update(self, request, pk=None):
serialized = DemoSerializer(request.user, data=request.data, partial=True)
return Response(status=status.HTTP_202_ACCEPTED)
Serializer
class DemoSerializer(serializers.ModelSerializer):
class Meta:
model = Demo
fields = '__all__'
def update(self, instance, validated_data):
print 'this - here'
demo = Demo.objects.get(pk=instance.id)
Demo.objects.filter(pk=instance.id)\
.update(**validated_data)
return demo
I when digging into the source code of rest_framework and got the following findings:
For question 1. Why do we need to specify partial=True?
This question is related to HTTP verbs.
PUT: The PUT method replaces all current representations of the target resource with the request payload.
PATCH: The PATCH method is used to apply partial modifications to a resource.
Generally speaking, partial is used to check whether the fields in the model is needed to do field validation when client submitting data to the view.
For example, we have a Book model like this, pls note both of the name and author_name fields are mandatory (not null & not blank).
class Book(models.Model):
name = models.CharField('name of the book', max_length=100)
author_name = models.CharField('the name of the author', max_length=50)
# Create a new instance for testing
Book.objects.create(name='Python in a nut shell', author_name='Alex Martelli')
For some scenarios, we may only need to update part of the fields in the model, e.g., we only need to update name field in the Book. So for this case, client will only submit the name field with new value to the view. The data submit from the client may look like this:
{"pk": 1, name: "PYTHON IN A NUT SHELL"}
But you may have notice that our model definition does not allow author_name to be blank. So we have to use partial_update instead of update. So the rest framework will not perform field validation check for the fields which is missing in the request data.
For testing purpose, you can create two views for both update and partial_update, and you will get more understanding what I just said.
Example:
views.py
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import UpdateModelMixin
from rest_framework.viewsets import ModelViewSet
from rest_framework import serializers
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
class BookUpdateView(GenericAPIView, UpdateModelMixin):
'''
Book update API, need to submit both `name` and `author_name` fields
At the same time, or django will prevent to do update for field missing
'''
queryset = Book.objects.all()
serializer_class = BookSerializer
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
class BookPartialUpdateView(GenericAPIView, UpdateModelMixin):
'''
You just need to provide the field which is to be modified.
'''
queryset = Book.objects.all()
serializer_class = BookSerializer
def put(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
urls.py
urlpatterns = patterns('',
url(r'^book/update/(?P<pk>\d+)/$', BookUpdateView.as_view(), name='book_update'),
url(r'^book/update-partial/(?P<pk>\d+)/$', BookPartialUpdateView.as_view(), name='book_partial_update'),
)
Data to submit
{"pk": 1, name: "PYTHON IN A NUT SHELL"}
When you submit the above json to the /book/update/1/, you will got the following error with HTTP_STATUS_CODE=400:
{
"author_name": [
"This field is required."
]
}
But when you submit the above json to /book/update-partial/1/, you will got HTTP_STATUS_CODE=200 with following response,
{
"id": 1,
"name": "PYTHON IN A NUT SHELL",
"author_name": "Alex Martelli"
}
For question 2. What is inside of serialized variable?
serialized is a object wrapping the model instance as a serialisable object. and you can use this serialized to generate a plain JSON string with serialized.data .
For question 3. How would one finish the implementation here?
I think you can answer yourself when you have read the answer above, and you should have known when to use update and when to used partial_update.
If you still have any question, feel free to ask. I just read part of the source code of the rest framework, and may have not understand very deeply for some terms, and please point it out when it is wrong...
For partial update - PATCH http method
For full update - PUT http method
When doing an update with DRF, you are supposed to send request data that includes values for all (required) fields. This is at least the case when the request is via the PUT http method. From what I understand, you want to update one or at least not all model instance fields. In this case make a request with the PATCH http method. Django rest framework (DRF) will take care of it out of the box.
Example (with token auth):
curl -i -X PATCH -d '{"name":"my favorite banana"}' -H "Content-Type: application/json" -H 'Authorization: Token <some token>' http://localhost:8000/bananas/
So simple, just override init method of your serializer like that:
def __init__(self, *args, **kwargs):
kwargs['partial'] = True
super(DemoSerializer, self).__init__(*args, **kwargs)
Just a quick note as it seems that nobody has already pointed this out:
serialized = DemoSerializer(request.user, data=request.data, partial=True)
The first argument of DemoSerializer should be a Demo instance, not a user (at least if you use DRF 3.6.2 like me).
I don't know what you are trying to do, but this is a working example:
def partial_update(self, request, *args, **kwargs):
response_with_updated_instance = super(DemoViewSet, self).partial_update(request, *args, **kwargs)
Demo.objects.my_func(request.user, self.get_object())
return response_with_updated_instance
I do the partial update and then I do other things calling my_func and passing the current user and the demo instance already updated.
Hope this helps.
I had an issue where my multi-attribute/field validation in a rest_framework serializer was working with a POST /resources/ request but failing with a PATCH /resources/ request. It failed in the PATCH case because it was only looking for values in the supplied attrs dict and not falling back to values in self.instance. Adding a method get_attr_or_default to do that fallback seems to have worked:
class EmailSerializer(serializers.ModelSerializer):
def get_attr_or_default(self, attr, attrs, default=''):
"""Return the value of key ``attr`` in the dict ``attrs``; if that is
not present, return the value of the attribute ``attr`` in
``self.instance``; otherwise return ``default``.
"""
return attrs.get(attr, getattr(self.instance, attr, ''))
def validate(self, attrs):
"""Ensure that either a) there is a body or b) there is a valid template
reference and template context.
"""
existing_body = self.get_attr_or_default('body', attrs).strip()
if existing_body:
return attrs
template = self.get_attr_or_default('template', attrs)
templatecontext = self.get_attr_or_default('templatecontext', attrs)
if template and templatecontext:
try:
render_template(template.data, templatecontext)
return attrs
except TemplateRendererException as err:
raise serializers.ValidationError(str(err))
raise serializers.ValidationError(NO_BODY_OR_TEMPLATE_ERROR_MSG)
I don't know why, but for me, the only way to solve it was to override the validate method in the Serializer class.
Maybe it's related to the fact that I'm using MongoDB with Djongo
class DemoSerializer(serializers.ModelSerializer):
def validate(self, attrs):
self._kwargs["partial"] = True
return super().validate(attrs)
You forgot serializer.save()
You can finish it the following way . . .
class DemoViewSet(viewsets.ModelViewSet):
serializer_class = DemoSerializer
def partial_update(self, request, pk=None):
serializer = DemoSerializer(request.user, data=request.data, partial=True)
serializer.save()
serializer.is_valid(raise_exception=True)
return Response(serializer.data)
Also, you shouldn't need to override the update method in the serializer.
I have a django-rest-framework REST API with hierarchical resources. I want to be able to create subobjects by POSTing to /v1/objects/<pk>/subobjects/ and have it automatically set the foreign key on the new subobject to the pk kwarg from the URL without having to put it in the payload. Currently, the serializer is causing a 400 error, because it expects the object foreign key to be in the payload, but it shouldn't be considered optional either. The URL of the subobjects is /v1/subobjects/<pk>/ (since the key of the parent isn't necessary to identify it), so it is still required if I want to PUT an existing resource.
Should I just make it so that you POST to /v1/subobjects/ with the parent in the payload to add subobjects, or is there a clean way to pass the pk kwarg from the URL to the serializer? I'm using HyperlinkedModelSerializer and ModelViewSet as my respective base classes. Is there some recommended way of doing this? So far the only idea I had was to completely re-implement the ViewSets and make a custom Serializer class whose get_default_fields() comes from a dictionary that is passed in from the ViewSet, populated by its kwargs. This seems quite involved for something that I would have thought is completely run-of-the-mill, so I can't help but think I'm missing something. Every REST API I've ever seen that has writable endpoints has this kind of URL-based argument inference, so the fact that django-rest-framework doesn't seem to be able to do it at all seems strange.
Make the parent object serializer field read_only. It's not optional but it's not coming from the request data either. Instead you pull the pk/slug from the URL in pre_save()...
# Assuming list and detail URLs like:
# /v1/objects/<parent_pk>/subobjects/
# /v1/objects/<parent_pk>/subobjects/<pk>/
def pre_save(self, obj):
parent = models.MainObject.objects.get(pk=self.kwargs['parent_pk'])
obj.parent = parent
Here's what I've done to solve it, although it would be nice if there was a more general way to do it, since it's such a common URL pattern. First I created a mixin for my ViewSets that redefined the create method:
class CreatePartialModelMixin(object):
def initial_instance(self, request):
return None
def create(self, request, *args, **kwargs):
instance = self.initial_instance(request)
serializer = self.get_serializer(
instance=instance, data=request.DATA, files=request.FILES,
partial=True)
if serializer.is_valid():
self.pre_save(serializer.object)
self.object = serializer.save(force_insert=True)
self.post_save(self.object, created=True)
headers = self.get_success_headers(serializer.data)
return Response(
serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Mostly it is copied and pasted from CreateModelMixin, but it defines an initial_instance method that we can override in subclasses to provide a starting point for the serializer, which is set up to do a partial deserialization. Then I can do, for example,
class SubObjectViewSet(CreatePartialModelMixin, viewsets.ModelViewSet):
# ....
def initial_instance(self, request):
instance = models.SubObject(owner=request.user)
if 'pk' in self.kwargs:
parent = models.MainObject.objects.get(pk=self.kwargs['pk'])
instance.parent = parent
return instance
(I realize I don't actually need to do a .get on the pk to associate it on the model, but in my case I'm exposing the slug rather than the primary key in the public API)
If you're using ModelSerializer (which is implemented by HyperlinkedModelSerializer) it's as easy as implementing the restore_object() method:
class MySerializer(serializers.ModelSerializer):
def restore_object(self, attrs, instance=None):
if instance is None:
# If `instance` is `None`, it means we're creating
# a new object, so we set the `parent_id` field.
attrs['parent_id'] = self.context['view'].kwargs['parent_pk']
return super(MySerializer, self).restore_object(attrs, instance)
# ...
restore_object() is used to deserialize a dictionary of attributes into an object instance. ModelSerializer implements this method and creates/updates the instance for the model you specified in the Meta class. If the given instance is None it means the object still has to be created, so you just add the parent_id attribute on the attrs argument and call super().
So this way you don't have to specify a read-only field, or have a custom view/serializer.
More information:
http://www.django-rest-framework.org/api-guide/serializers#declaring-serializers
Maybe a bit late, but i guess this drf nested routers library could be helpful for that operation.
I'm uploading files and storing metadata in a db. Part of the metadata is the file name itself. However, somewhere down the line, the filename seems to not be getting saved! I will paste only what I think are relevant parts of the code to keep this short.
class UploadFile(models.Model):
...
theFile = models.FileField(upload_to = "Genius/Uploads/", null = True)
filename = models.CharField(max_length = 50, blank = True, null = False)
class UploadFileForm(ModelForm):
class Meta:
model = UploadFile
fields = ('title', 'theFile', 'date_uploaded',) # Don't prompt for filename
def files_upload(request):
if request.method == 'POST':
form = UploadFileForm(request.POST, request.FILES)
if form.is_valid():
form.filename = request.FILES['theFile'].name # TODO: sanitize!
# form.filename = 'foo'
form.save()
return HttpResponseRedirect('/files/upload/successful/')
else:
form = UploadFileForm()
return render_to_response('files/upload_file.html', { 'form': form })
I have checked the value of request.FILES['theFile'].name before & after saving the form. For whatever reason it is intact but never seems to make it into the DB.
That's because form.filename is the form field, not the value it will be saving.
You are looking for something like this:
class UploadFileForm(ModelForm):
def save(self, commit=True):
instance = ModelForm.save(self, commit=False)
instance.filename = self.files['theFile'].name
if commit:
instance.save()
return instance
class Meta:
model = UploadFile
fields = ('title', 'theFile', 'date_uploaded',) # Don't prompt for filename
Alternative solution:
upload_file = form.save(commit=False)
upload_file.filename = request.FILES['theFile'].name
upload_file.save()
Form field values aren't accessed via attributes on the form. So setting 'form.filename' doesn't set the value to be saved in the filename field. Instead, set the value on the instance returned by form.save().
upload_file = form.save(commit=False)
upload_file.filename = filename
upload_file.save()
I just wanted to add that, in the future, you might try to avoid putting such business logic on the model form. While WoLpH's answer is correct and a great example of how to handle additional model instance processing through ModelForm, particular cases of having fields dependent on other fields data is handled in the Model, Form and ModelForm API through their respected clean() methods and is mentioned in several places in the official reference docs (here's one on forms, though the same holds true for the Model and ModelForm APIs).
In your case this would mean:
import os
class UploadFile(models.Model):
# ...
def clean(self):
# Field data has already been populated by this point.
# Note that `FieldFile` inherits from `File` and that
# `File.name` is actually the full path to the file
# so we need to get the base path component sans the extension
path, extension = os.path.splitext(self.thefile.file.name)
self.filename = os.path.basename(path)
And that's about it! If you properly set the editable attribute on your model fields, you'll find that you can rely on Django to automatically generate the ModelForm for the UploadFile model. That means you don't have to define a ModelForm for the generic create, update views or your model's ModelAdmin and that's less lines of code to manage!
The general rule of thumb is that you think twice about whether overriding default behavior is ever justified or self-contained, especially when your working at the far end of the business logic chain, otherwise you may feel the wrath of unexpected flops.