Django: Validate GET request - django

I have written a few simple API endpoints (without Django REST). I have problems figuring out how to validate the input data.
class CarTypeForm(forms.Form):
car_type= forms.IntegerField(
validators=[MinValueValidator(0), MaxValueValidator(6)], required=False
)
def car_data_as_json(request):
"""
API Endpoint
"""
# Receive and validate variables from get request
car_type= request.GET.get("car_type", 0)
car_type_form = CarTypeForm()
car_type_form.car_type = int(car_type)
if car_type_form.is_valid():
do something
return JsonResponse(something)
else:
return JsonResponse({'err': 'invalid car_type'}, status=400)
I don't understand why if car_type_form.is_valid(): is not True despite the value being 5 for example.
Endpoint is called by javascript to an url: /api/car_data/?car_type=<number>
I'm using Django 2.1

You need to initialize the form with your data before calling is_valid:
car_type_form = CarTypeForm({'car_type': car_type})

Related

How to perform the delete method in drf django

How to perform the delete request in Django drf? How will I pass the params for the request?
Kindly help with the solution. I am very new in this drf-django-python programming.
class DeleteView(APIView):
def delete(self, request,format=None):
id = request.POST['book_id']
email = request.POST['email']
book = models.Book.objects.filter(book_id=id)
book_uploader = serializers.BookSerializer(book[0]).data['uploader']['email']
logged_in = request.user
print(log)
if book_uploader == logged_in :
books = models.BookUserRelationship.objects.filter(book= id, user__email=email)
books.delete()
return Response("Successfully removed", status=status.HTTP_204_NO_CONTENT)
else :
return Response("Not able to remove")
In comments you noticed that parameters will be embedded in url but you are trying to get values from POST dict.
If your url is something like
/books/id/email/
You should use request.kwargs dict like request.kwargs.get('email')
But if your url is like
/books/id/?email=someemail#google.com
id would be in request.kwargs but email in request.query_params
Notice that every url variable is in the request.query_params dict.
IMPORTANT
If you have ID url param without named group, viewset would not be able to get this from request.kwargs by name

django api post without bring json still work?

Because django rest framework did not support bulk create
So I write one
And I found a strange problem
if I POST the api with a json like :
[{'address':'1','name':'2','start':'3'},
{'address':'10','name':'20','start':'30'}]
it works!
But if I kust POST the api without bring json
I still got bulk create success message.
Why would this happen??
Where do I write wrong??
This is my API view
class BulkTestList(APIView):
def post(self, request, format=None):
duplicateList = []
for data in request.data:
message = {}
if not 'address' in data.keys():
message['address'] = [ "This field is required."]
elif not data['address']:
message["address"] = [ "This field may not be blank."]
if not 'name' in data.keys():
message["name"] = [ "This field is required."]
elif not data['name']:
message["name"]= [ "This field may not be blank."]
if not 'star' in data.keys():
message["star"] = [ "This field is required."]
elif not data['star']:
message["star"]= [ "This field may not be blank."]
if message:
return Response(message, status=status.HTTP_400_BAD_REQUEST)
for data in request.data:
address = data['address'].upper()
bulkCreateObjects = Data(address=address, name=data['name'], star=data['star'], datetime=datetime.datetime.now(pytz.utc))
bulkCreateObjects.save()
message = {"bulk create success"}
return Response(data=message, status=status.HTTP_201_CREATED)
Django REST framework doesn't have bulk out of the box but you have a third party app that does.
Your current view just doesn't call a serializer therefore you won't get the validation at any point. See http://www.django-rest-framework.org/tutorial/3-class-based-views/#rewriting-our-api-using-class-based-views
Note that for bulk you'll add a many=True to the serializer so it will be able to deal with list of data.
Your issue that the view returns a 201 "{bulk create success}" is due to the fact that you iteration over request.data does not check if reuqest.data is actually empty. A for loop over an empty list will just skip over the for block. As Linovia mentions, you need to add some validation to your view.

on data.put() i need to display to the user that the data has been successfully submitted or failure incase pof one

am using python and google app engine majorly on jinja2 templates
i could like when a user registers a new account, they get a popup indicating that their registration is successful of even any alert on the very interface before moving to the next registration step.
def post(self):
user = (str(users.get_current_user().email()))
userquery = Users.query(Users.email == user)
count = userquery.count()
if count == 0:
#test if user is admin or employee
qry = Users.query()
count = qry.count()
if count == 0:
privilage = 'admin'
db_put = Users(
f_name=self.request.get("f_name"),
l_name = self.request.get("l_name"),
org = self.request.get("org"),
email=users.get_current_user().email(),
privilage = privilage
)
db_put.put()
How are you calling this POST method? Are you sending a form there directly (use method 1) or is this being done with an AJAX call (use method 2)?
Method 1
You can redirect to a GET page where you render a template with a success or error message for Jinja to use. This would however involve a page change.
import webapp2
class MyHandler(webapp2.RequestHandler):
def get(self): # Let's assume /someurl is mapped to this handler.
template_values = {}
notification = self.request.get('notification')
if notification:
template_values['notification'] = notification
self.response.set_status(200)
self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
# Need to get the template from jinja and set it as template variable.
self.response.out.write(template.render(template_values))
def post(self):
# Do all your stuff here.
self.redirect('/someurl?notification=Success')
Alternatively you can set the parameters directly on the request instead of passing them as URI parameters:
def post(self):
# Do all your stuff here.
self.redirect('/someurl, params={'notification': 'Success'})
Method 2
In this method you can send back a JSON response with a success or error message. The caller (whatever function in your javascript that submitted the request to the backend) can use that to render a butterbar message or other popup notification of your choosing:
import json
import webapp2
class MyHandler(webapp2.RequestHandler):
def post(self):
# Do all your stuff here.
self.response.set_status(200)
self.response.headers['Content-Type'] = 'application/json; charset=utf-8'
self.response.headers['Content-Disposition'] = 'attachment'
self.response.out.write(json.JsonEncoder(sort_keys=True).encode('Success'))
For the latter, make sure you think about cross-site scripting (XSS) vulnerabilities and perhaps add a JSON prefix.

Django backend receives one less param than sent by frontend

I have a small web app with AngularJS front-end and Django ReST in the back. There's a strange hitch going on when I make POST request to the web service: the browser console clearly shows 3 parameters being sent, but the backend logging reports only 2 params received. The result is that the server throws a code 500 error due to a bad database lookup.
Here's the code:
Client
var b = newQuesForm.username.value;
$http.post('/myapp/questions/new', {username:b,title:q.title,description:q.description}).
success(function(data, status, headers, config) {
$http.get('/myapp/questions').success(function(data){
$scope.questions = data;
q = null;
$scope.newQuesForm.$setPristine();
}).error(function(data, status, headers, config) {
console.log(headers+data);
});
}).
error(function(data, status, headers, config) {
console.log(headers+data);
});
Both my manual logging and the dev console show a string like:
{"username":"admin","description":"What's your name?","title":"question 1"}
Server
class CreateQuestionSerializer(serializers.Serializer):
author = UserSerializer(required=False)
title = serializers.CharField(max_length=150)
description = serializers.CharField(max_length=350)
def create(self, data):
q= Question()
d = data
q.title = d.get('title')
q.description = d.get("description")
q.author = User.objects.get(username=d.get('username'))
q.save()
return q
Server-side logging shows the username parameter never succeeds in making the trip, and thus I end up with code 500 and error message:
User matching query does not exist. (No user with id=none)
What's causing some of the data to get lost?
So it turns out the problem was really with the serialization of fields, as #nikhiln began to point out. I followed his lead to refactor the code, moving the create() method to api.py, rather than serializers.py, and stopped relying altogether on the client-side data for the user's identity, something that was a bit silly in the first place (passing User to a hidden input in the view, and then harvesting the username from there and passing it back to the server in the AJAX params). Here's the new code, that works perfectly:
class QuestionCreate(generics.CreateAPIView):
model = Question
serializer_class = CreateQuestionSerializer
def create(self, request,*args,**kwargs):
q= Question()
d = request.data
q.title = d.get('title')
q.description = d.get("description")
q.author = request.user
q.save()
if q.pk:
return Response({'id':q.pk,'author':q.author.username}, status=status.HTTP_201_CREATED)
return Response({'error':'record not created'}, status=status.HTTP_400_BAD_REQUEST)
So here, I do it the right way: pull the User from the request param directly in the backend.

Convert POST to PUT with Tastypie

Full Disclosure: Cross posted to Tastypie Google Group
I have a situation where I have limited control over what is being sent to my api. Essentially there are two webservices that I need to be able to accept POST data from. Both use plain POST actions with urlencoded data (basic form submission essentially).
Thinking about it in "curl" terms it's like:
curl --data "id=1&foo=2" http://path/to/api
My problem is that I can't update records using POST. So I need to adjust the model resource (I believe) such that if an ID is specified, the POST acts as a PUT instead of a POST.
api.py
class urlencodeSerializer(Serializer):
formats = ['json', 'jsonp', 'xml', 'yaml', 'html', 'plist', 'urlencoded']
content_types = {
'json': 'application/json',
'jsonp': 'text/javascript',
'xml': 'application/xml',
'yaml': 'text/yaml',
'html': 'text/html',
'plist': 'application/x-plist',
'urlencoded': 'application/x-www-form-urlencoded',
}
# cheating
def to_urlencoded(self,content):
pass
# this comes from an old patch on github, it was never implemented
def from_urlencoded(self, data,options=None):
""" handles basic formencoded url posts """
qs = dict((k, v if len(v)>1 else v[0] )
for k, v in urlparse.parse_qs(data).iteritems())
return qs
class FooResource(ModelResource):
class Meta:
queryset = Foo.objects.all() # "id" = models.AutoField(primary_key=True)
resource_name = 'foo'
authorization = Authorization() # only temporary, I know.
serializer = urlencodeSerializer()
urls.py
foo_resource = FooResource
...
url(r'^api/',include(foo_resource.urls)),
)
In #tastypie on Freenode, Ghost[], suggested that I overwrite post_list() by creating a function in the model resource like so, however, I have not been successful in using this as yet.
def post_list(self, request, **kwargs):
if request.POST.get('id'):
return self.put_detail(request,**kwargs)
else:
return super(YourResource, self).post_list(request,**kwargs)
Unfortunately this method isn't working for me. I'm hoping the larger community could provide some guidance or a solution for this problem.
Note: I cannot overwrite the headers that come from the client (as per: http://django-tastypie.readthedocs.org/en/latest/resources.html#using-put-delete-patch-in-unsupported-places)
I had a similar problem on user creation where I wasn't able to check if the record already existed. I ended up creating a custom validation method which validated if the user didn't exist in which case post would work fine. If the user did exist I updated the record from the validation method. The api still returns a 400 response but the record is updated. It feels a bit hacky but...
from tastypie.validation import Validation
class MyValidation(Validation):
def is_valid(self, bundle, request=None):
errors = {}
#if this dict is empty validation passes.
my_foo = foo.objects.filter(id=1)
if not len(my_foo) == 0: #if object exists
foo[0].foo = 'bar' #so existing object updated
errors['status'] = 'object updated' #this will be returned in the api response
return errors
#so errors is empty if object does not exist and validation passes. Otherwise object
#updated and response notifies you of this
class FooResource(ModelResource):
class Meta:
queryset = Foo.objects.all() # "id" = models.AutoField(primary_key=True)
validation = MyValidation()
With Cathal's recommendation I was able to utilize a validation function to update the records I needed. While this does not return a valid code... it works.
from tastypie.validation import Validation
import string # wrapping in int() doesn't work
class Validator(Validation):
def __init__(self,**kwargs):
pass
def is_valid(self,bundle,request=None):
if string.atoi(bundle.data['id']) in Foo.objects.values_list('id',flat=True):
# ... update code here
else:
return {}
Make sure you specify the validation = Validator() in the ModelResource meta.