Update only 2 field in DRF - django

I have a model and with DRF I want update only 2 field in model but when I update , 2 field are update but the other fields are empty .
#api_view(['GET', 'PUT', 'DELETE'])
def snippet_detail(request, format=None):
try:
requestd = request.POST['card_number']
requestd2 = card.models.Card(card_number=requestd)
#snippet = Card.objects.get()
print(requestd2)
credit = Card.objects.filter(card_number=requestd).values('initial_credit')
credit2 = int(credit[0]['initial_credit'])
requestcredit = int(request.POST['initial_credit'])
#print(snippet)
print(credit)
print(credit2)
except Card.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == 'PUT':
serializer = CardModelSerializer(requestd2, data=request.data, partial=True) #insted of requestd 2 was snippet
if serializer.is_valid():
#print(request.data)
new_credit= credit2 - requestcredit
comment = 'اعتبار کافی نمی باشد .'
comment2 = 'مقدار وارد شده صحیح نمی باشد'
if new_credit >= 0:
if requestcredit >0:
serializer.save()
return Response(serializer.data,status=status.HTTP_200_OK)
else:
return Response (comment2, status=status.HTTP_400_BAD_REQUEST)
else:
return Response(comment, status=status.HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

For partial update you can use PATCH method instead of using PUT
basically PUT method needs to full object for update and PATCH method only needs those fields which you want to update.
so, you need to use PATCH method to update only two fields that you want to update.
------ PUT vs PATCH -------
PUT: sends an enclosed entity of a resource to the server. If the entity already exists, the server updates its data. Otherwise, the server creates a new entity
PATCH: allows the modification of an entity of a resource. So, it can be applied to change only particular portions of an entity's data
so, in request decorator put PATCH method instead of PUT like this...
views.py
#api_view(['GET', 'PATCH', 'DELETE'])
def snippet_detail(request, format=None):

Related

Django ModelViewSet PATCH request return model fields updated

class MerchantStampCardViewSet(viewsets.ModelViewSet):
'''
A view set for listing/retrieving/updating/deleting stamp cards for the current
merchant
'''
permission_classes = (IsMerchantAndAuthenticated, )
def get_queryset(self):
if len(MerchantProfile.objects.filter(user=self.request.user)) > 0:
merchant_profile = MerchantProfile.objects.get(user=self.request.user)
if merchant_profile.merchant:
return StampCard.objects.filter(merchant=merchant_profile.merchant)
return None
def get_serializer_class(self):
if self.request.method == 'GET':
return StampCardSerializerWithRewards
else:
return StampCardSerializer
I'm trying to make this code return the fields changed in the response body. The model class has a couple fields like name, city, province, zip code and address and through the front-end the user can only change one at a time, but I want the body of the 200 response to contain the field name changed and the new value just to confirm that a change was successful and nothing went wrong.
So for example if the user changes the name to Billy. The response should be 200 and the body should say {name : 'Billy'}
How do I do this?
You can try like this:
class YourViewSet(...):
def update(self, request, *args, **kwargs):
instance = self.get_object()
current_data = self.get_serializer(instance).data # collect current data
# next few lines of the code is from default implementation
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
if getattr(instance, '_prefetched_objects_cache', None):
instance._prefetched_objects_cache = {}
updated_data = serializer.data # now we get the updated data
response_dict = dict()
for key, value in current_data:
# find the differences
if updated_data.get(key) != value:
response_dict[key] = updated_data.get(key)
return Response(response_dict) # send the difference through response
Here I have put a override on update method. Then I have collected the dictionary data from current object and updated object. Then compared them and sent differences in a dictionary as response. FYI its an untested code.

Removing the primary key in class based views ( django rest framework )

Problem :
Currently in my api/urls.py I have this line
url(r'^profile/(?P<pk>[0-9]+)/$', views.UserProfileView.as_view()),
but I want to get the profile based on request.user and so I have the code in class UserProfileView as the following :
class UserProfileView(generics.RetrieveUpdateAPIView):
serializer_class = UserProfileSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
pagination_class = LimitTenPagination
def get_queryset(self):
try:
queryset = UserProfile.objects.filter(user=self.request.user)
except:
raise APIException('No profile linked with this user')
return queryset
But If I remove the pk field from urls.py file, I get an error like this :
AssertionError at /api/profile/
Expected view UserProfileView to be called with a URL keyword argument
named "pk". Fix your URL conf, or set the .lookup_field attribute on
the view correctly.
Which is expected.
Possible solution :
I made a function based view like this :
#api_view(['GET', 'PUT'])
def user_detail(request):
"""
Retrieve, update or delete a code snippet.
"""
try:
user_profile_data = UserProfile.objects.get(user=request.user)
except:
raise APIException('No profile linked with this user')
if request.method == 'GET':
serializer = UserProfileSerializer(user_profile_data)
return Response(serializer.data)
elif request.method == 'PUT':
serializer = UserProfileSerializer(user_profile_data, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
And in the urls.py file added this line :
url(r'^me/$', views.user_detail),
This gets the work done, but I want a class based solution, so that in case I needed to use pagination_class, permission_class and other features of drf, I can easily use it.
As of now, since I need to fetch only one object, so pagination is out of question.
Thanks.
It is get_object that you need to override for a detail-based view rather than get_queryset.

Django rest framework disable field update after condition

I'm using DRF and I need to disable the update of a field if a condition on the same model is respected.
example:
class Foo(models.Model):
text = models.CharField()
checkfield = models.BooleanField(default=False)
text can be modified unless checkfield is True.
So if Foo.checkfield is True Foo.text cannot be modified via DRF API.
What is the best way to do so?
I think Advanced serializers will do what you want.
Just create your custom serializer and in your view, check the value of checkfield. If it's true, pass it the text argument so it enables the field in the serializer.
Btw, since you only need one fixed extra field to be removed or added, instead of passing the fields argument as in the example, you can pass it something like enable_text=checkfield and then add the text field to the 'fields' variable in your serializer according to the value of 'checkfield'.
update to clarify:
Define your serializer without the text field. Then in your ModelViewSet, override the update method so you get the serializer this way (I think the get_serializer() method does not allow to pass extra args):
YourSerializer(object, enable_text=True)
And, inside your serializer init method, when 'enable_text' is True, you add the text field to the self.fields attribute.
I haven't tested if this works but I think it is the way to go.
Edit with snippet and modification
I've been digging a bit with what I explained and turned out it is a bit messy for the simple modification you are trying to do. What I've come up with is just to override the update method in your ViewSet. Here is the code:
from rest_framework import viewsets, status
from rest_framework.response import Response
from models import Test, TestSerializer
class TestViewSet(viewsets.ModelViewSet):
queryset = Test.objects.all()
serializer_class = TestSerializer
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
self.object = self.get_object_or_none()
if 'enable_text' in request.DATA and request.DATA['enable_text'] == True:
request.DATA['text'] = self.object.text
serializer = self.get_serializer(self.object, data=request.DATA,
files=request.FILES, partial=partial)
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
try:
self.pre_save(serializer.object)
except ValidationError as err:
# full_clean on model instance may be called in pre_save,
# so we have to handle eventual errors.
return Response(err.message_dict, status=status.HTTP_400_BAD_REQUEST)
if self.object is None:
self.object = serializer.save(force_insert=True)
self.post_save(self.object, created=True)
return Response(serializer.data, status=status.HTTP_201_CREATED)
self.object = serializer.save(force_update=True)
self.post_save(self.object, created=False)
return Response(serializer.data, status=status.HTTP_200_OK)
This code is taken from the rest_framework source code for the UpdateMixin. Take special attention at lines if 'enable_text' in request.DATA and... and request.DATA['text'] = self.object.text. Those are the ones allowing you to do the funcionality you need. Basically:
If you send the enable_text with True along with text, text will be modified.
If you send the enable_text with False along with text, it will be ignored.
Note that this code only takes into account the value of enable_text passed in the current request. You maybe want also that if enable_text is not in the current request, to check the value of enable_text in the self.object (which is the database instance itself).

Django REST framework - serializer.object.value='x' before serializer.save() doesn't change value

I have a simple POST right now doing the following -
def post(self, request, format=None):
serializer = CalendarSerializer(data=request.DATA)
if serializer.is_valid():
serializer.object.evntmst_name='cal_test'
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I have an XML that I'm importing on post where the evntmst_name is "cal_daily". On doing a post it successfully goes through but the value for evntmst_name is not changing. After post I get the same value of "cal_daily" in the database instead of "cal_test" which it should be set to. What am I missing?
I think I figured this out by doing it another way, or the way I wanted to do it (in the view) is not possible. I've made 2 separate serializers 1 for GET and 1 for PUT/PATCH.
In the serializer for PUT/PATCH I've defined the logic to change the field value like so -
class CalendarPUTSerializer(serializers.ModelSerializer):
class Meta:
model = Evntmst
resource_name = 'evntmst'
fields = ('evntmst_id', 'evntmst_name')
def transform_evntmst_id(self, obj, value):
if obj.evntmst_id == 1939:
return 1937

How Configure Django Rest Framework to return errors with custom text

I want to customize the JSON response when adding a new item to the database it returns the following.
HTTP 400 BAD REQUEST
Content-Type: application/json Vary:
Accept Allow: POST, OPTIONS
{
"nick": [
"Users with this Nick already exists."
]
}
and
{
"nick": [
"Your username is empty"
]
}
I want it to return (This username already exists, please use a different one.)
or
"Username %s already exists", (self.nick)
I used the following sample but does not work if the value is empty or invalid.
def validate_title(self, attrs, source):
"""
Check that the blog post is about Django.
"""
value = attrs[source]
if "django" not in value.lower():
raise serializers.ValidationError("Blog post is not about Django")
return attrs
this is the JSON that gets sent to the API.
{
"name": "myname",
"nick":"",
"type_account":"1",
"email":"my-email#gmail.com",
"pass_field":"12345"
}
serializers.py
class userSerializer(serializers.ModelSerializer):
class Meta:
model = users
fields = ('nick', 'email', 'pass_field', 'type_account')
def validate_nick(self, attrs, source):
value = attrs[source]
if not value:
raise serializers.ValidationError('Username cannot be empty')
elif self.Meta.model.objects.filter(nick=value).exists():
raise serializers.ValidationError("Username "+value+" is in use")
return attrs
views.py
#api_view(['POST'])
def user_add(request):
"""
Saves a new user on the database
"""
if request.method == 'POST':
serializer = userSerializer(data=request.DATA)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
the answer to this question also adds the following as #Fiver answered
class userLoginSerializer(serializers.ModelSerializer):
nick = serializers.CharField(error_messages={'required':'Please Type a Username'})
pass_field = serializers.CharField(error_messages={'required':'Please Type a Password'})
class Meta:
model = users
fields = ('nick', 'pass_field')
I believe something like the following will work:
def validate_nick(self, attrs, source):
"""
Check that 'nick' is not already used or empty.
"""
value = attrs[source]
if not value:
raise serializers.ValidationError("Nick cannot be empty!")
elif self.Meta.model.objects.filter(nick=value).exists():
raise serializers.ValidationError("Username %s already exists", value)
return attrs
It should be possible to alter the error messages at the model level, but unfortunately REST Framework doesn't support that yet. Here is an issue dealing with the problem. It includes a suggested method for overriding the validator in the serializer.
You should use custom error handler. Follow here to setup.
Your Custom error handler should be like this:
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
# Now add the HTTP status code to the response.
if response is not None:
for field, value in response.data.items():
value = ''.join(value)
response.data = {} # Empty django's custom error
response.data['detail'] =value #customize it how you want
return response