I have the following serializer setup to take in a postcode and return a list of service objects:
class ServiceSearchSerializer(Serializer):
area = CharField(max_length=16)
services = DictField(child=JSONField(), read_only=True)
def validate_area(self, value):
if re.match('^[A-Z]{1,2}[0-9][A-Z0-9]? ?([0-9][A-Z]{2})?$', value) is None:
raise ValidationError("This is not a valid postcode.")
And I tried to use this to create an API endpoint which out take in the area.
class ServiceSearchAPI(GenericAPIView):
serializer_class = ServiceSearchSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
if serializer.is_valid(raise_exception=True):
area = serializer.data['area']
return Response(area)
However, when trying to get the area from the serializer, it returns None. But the value for request.data['area'] is correct. Does anyone know why this is happening?
I just checked the docs and they specify returning the value. Try returning it like this:
def validate_area(self, value):
if re.match('^[A-Z]{1,2}[0-9][A-Z0-9]? ?([0-9][A-Z]{2})?$', value) is None:
raise ValidationError("This is not a valid postcode.")
return value
Related
I'm a beginner to Django, i have written a class-based API view with mixin. the functionality is simple i.e fetch the data of the given id.Im pasting the code below.
class GenericAPi(generics.GenericAPIView,mixins.ListModelMixin,mixins.RetrieveModelMixin):
serializer_class=ArticleSerializer
queryset=Article.objects.all()
lookup_field="id"
def get(self,request,id):
if id:
data=self.retrieve(request)
return Response({"data":data.data,"status":data.status_code})
else:
return self.list(request)
this is the response I'm getting
{"id":5,"title":"loream","author":"me"}
then I navigate to the retrieve function in the mixin, to make some changes in the response.
def retrieve(self, request, *args, **kwargs):
print('Retrieving')
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response({"result":serializer.data})
and then I make a call to API, but still, I'm getting the same response.
How to customize the response in the retrieve function itself.
I need response like this.
{"result":{"id":5,"title":"loream","author":"ipsum"}}
I think you don't have to customize retrieve function in the RetrieveModelMixin.
class GenericAPi(generics.GenericAPIView,mixins.ListModelMixin,mixins.RetrieveModelMixin):
serializer_class=ArticleSerializer
queryset=Article.objects.all()
lookup_field="id"
def get(self, request, id):
if id:
try:
article = Article.objects.get(pk = id)
return Response({"result": ArticleSerializer(article).data})
except Article.DoesNotExist:
return Response(status = status.HTTP_404_NOT_FOUND)
return
return self.list(request, *args, **kwargs)
I'm working on small project using Django Rest Framework, i would like to delete multiple IDs but i get always an error when i send a delete request by sending IDs /1,2,3,4 as a string, i get id must be an integer.
this is my code,
class UpdateDeleteContact(APIView):
def get(self, request, pk):
contactObject = get_object_or_404(Contact, pk=pk)
serializeContactObject = ContactSerializer(contactObject)
return Response(serializeContactObject.data)
def delete(self, request, pk):
delete_id = request.get('deleteid', None)
if not delete_id:
return Response(status=status.HTTP_404_NOT_FOUND)
for i in delete_id.split(','):
get_object_or_404(User, pk=int(i)).delete()
return Response(status=status.HTTP_204_NO_CONTENT)
can someone give me an example how to bulk delete
This code will enable you to send multiple ids through delete method and receive them as string.
path('url/<str:pk_ids>/', views.UpdateDeleteContact.as_view()),
#view.py
class UpdateDeleteContact(APIView):
def get(self, request, pk_ids):
ids = [int(pk) for pk in pk_ids.split(',')]
contactObject = Contact.objects.filter(id__in=ids)
serializeContactObject = ContactSerializer(contactObject, many=True)
return Response(serializeContactObject.data)
def delete(self, request, pk_ids):
ids = [int(pk) for pk in pk_ids.split(',')]
for i in ids:
get_object_or_404(User, pk=i).delete()
return Response(status=status.HTTP_204_NO_CONTENT)
But for me it is not recommended since the defined url can interfere with other methods like retrieve.
Another solution that I can offer you is to enter a parameter in the url, let's call it "pk_ids", with the different ids separated by commas.
def delete(self, request, pk):
pk_ids = request.query_params.get('pk_ids', None)
if pk_ids:
for i in pk_ids.split(','):
get_object_or_404(User, pk=int(i)).delete()
else:
get_object_or_404(User, pk=pk).delete()
return Response(status=status.HTTP_204_NO_CONTENT)
So you should call the url like
url.com/url/?pk_ids=1,2,3,4
Try with:
User.objects.filter(id__in=[int(x) for x in delete_id.split(',') if x]).delete()
New, more explained...
Your code doesn't work like a restful API... Delete is only for one object as a rule. You can read this thread
As Sohaib said in the comments, you can use the post function and retrieve id list from body, or as a string like your example.
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.
So, according to the docs, SerializerMethodField is a read-only field.
Well in my case, it's interfering with my write:
# old value is 2.5
data={'score': 1.7}
serializer = ScoreTraitSerializer(
score_trait, data=data, partial=True)
if serializer.is_valid():
new_score_trait = serializer.save()
Now if I inspect the new_score_trait, my score is still 2.5.
The serializer looks as such:
score = serializers.SerializerMethodField()
def get_score(self, obj):
if isinstance(obj.score, decimal.Decimal):
return float(obj.score)
else:
return obj.score
If I comment out my SerializerMethodField, I can save the new decimal value (but can't serialize it).
So ... am I using my serializer correctly? Why does my write to the serializer hitting the SerializerMethodField?
Thanks in advance
SerializerMethodField is a read-only field.Only used for to_representation, it's used for list/retrieve not create/update.
the serializer field score must conflict with model field score,try change it to:
float_score = serializers.SerializerMethodField(required=False)
def get_float_score (self, obj):
if isinstance(obj.score, decimal.Decimal):
return float(obj.score)
else:
return obj.score
See the source code you will know why:
class SerializerMethodField(Field):
"""
A read-only field that get its representation from calling a method on the
parent serializer class. The method called will be of the form
"get_{field_name}", and should take a single argument, which is the
object being serialized.
For example:
class ExampleSerializer(self):
extra_info = SerializerMethodField()
def get_extra_info(self, obj):
return ... # Calculate some data to return.
"""
def __init__(self, method_name=None, **kwargs):
self.method_name = method_name
kwargs['source'] = '*'
kwargs['read_only'] = True
super(SerializerMethodField, self).__init__(**kwargs)
I am building a TemplateView with 2 forms, one to allow user to select the customer (CustomerForm) and another to add the order (OrderForm) for the customer.
Code:
class DisplayOrdersView(TemplateView):
template_name = 'orders/orders_details_form.html'
def get_context_data(self, **kwargs):
context = kwargs
context['shippingdetailsform'] = ShippingDetailsForm(prefix='shippingdetailsform')
context['ordersform'] = OrdersForm(prefix='ordersform')
return context
def dispatch(self, request, *args, **kwargs):
return super(DisplayOrdersView, self).dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
profile=request.user.get_profile()
if context['shippingdetailsform'].is_valid():
instance = context['shippingdetailsform'].save(commit=False)
instance.profile = profile
instance.save()
messages.success(request, 'orders for {0} saved'.format(profile))
elif context['ordersform'].is_valid():
instance = ordersform.save(commit=False)
shippingdetails, created = shippingdetails.objects.get_or_create(profile=profile)
shippingdetails.save()
instance.user = customer
instance.save()
messages.success(request, 'orders details for {0} saved.'.format(profile))
else:
messages.error(request, 'Error(s) saving form')
return self.render_to_response(context)
Firstly, I can't seem to load any existing data into the forms. Assuming a onetoone relationship between UserProfile->ShippingDetails (fk: UserProfile)->Orders (fk:ShippingDetails), how can I query the appropriate variables into the form on load?
Also, how can I save the data? It throws an error when saving and I have been unable to retrieve useful debug information.
Is my approach correct for having multiple forms in a templateview?
You're not passing the POST data into the forms at any point. You need to do this when you instantiate them. I would move the instantiation out of get_context_data and do it in get and post: the first as you have it now, and the second passing request.POST.
Also note that you probably want to check both forms are valid before saving either of them, rather than checking and saving each in turn. The way you have it now, if the first one is valid it won't even check the second, let alone save it, so you won't get any errors on the template if the first is valid but the second is invalid.