Django REST framework non-model serializer and BooleanField - django

I seem to have hit a wall full of puzzling results when trying to deal with the following use case:
URL: '^api/event/(?P<pk>[0-9]+)/registration$'
payload: {"registered": "true"} or {"registered": "false"}
I retrieve the event object corresponding to the given pk, and then based on that I want:
in a GET request to retrieve whether the authenticated user is registered or not
in a PUT to change the registration state.
Everything works fine until the point where I want to process the incoming payload in the PUT request. I've tried creating a serializer like this:
class RegistrationSerializer(serializers.Serializer):
registered = fields.BooleanField()
and call it from an APIView's put method with:
serializer = RegistrationSerializer(data=request.DATA)
but it doesn't work and serializer.data always contains `{"registered": False}
From a shell I tried another isolated test:
>>> rs = RegistrationSerializer(data={'registered':True})
>>> rs
<app.serializers.RegistrationSerializer object at 0x10a08cc10>
>>> rs.data
{'registered': False}
What am I doing wrong? What would be the best way to handle this use case?

You need to call rs.is_valid() first, before accessing rs.data.
Really the framework ought to raise an exception if you don't do so.

Related

How to create a custom GET method that matches a certain URL pattern?

I am using a DefaultRouter and have a custom method in it. Right now I am passing values as POST but I want to pass as GET with the pattern like example.com/wallets/balance/<customerID>/. I am using ViewSet.
My current urls.py looks like:
router = routers.DefaultRouter()
router.register('wallets', views.WalletView, basename='WalletModel')
router.register('wallets/balance/1/', views.custom_balance,basename='CustomBalanceModel') # This crashes
and models.py
def custom_balance(id):
return Response({'status': 'OK', 'data': 'success'}, status=status.HTTP_200_OK)
class WalletView(viewsets.ModelViewSet):
.....
When I did this I used a .js file to do the get and post
Something like this to get all data. Then I passed it to the template using AngularJS.
You could do this in an AJAX .get call in the tag inside the template as well if you're not using AngularJS or VueJS for example.
function HeatTreatController($scope, $http) {
$scope.Foodata =[];
var q
$http.get('/wallets/balance/').then(function(response){
$scope.Foodata = response.data;
$scope.Fooworkorder = response.data.workOrder;
});
}
Foodata would then be everything sent by your serializer through the viewset.
workorder is just the workOrder value (models.py workOrder field) accessed by the dot operator. Just like when you have a QuerySet and you want to get sub fields of your model.
wallets/balance has to be an accesible API URL.
In your Django app you should be able to go to this URL and see the data.
Now if you want to get 1 specific piece of data from a GET
function FooController($scope, $http) {
$scope.Foodata =[];
var q
$http.get('/wallets/balance/'+customer.id +'/').then(function(response){
$scope.Foodata = response.data;
$scope.workorder = response.data.workOrder;
});
}
If I am understanding your question correctly it would not be strictly necessary to separate URL like router.register('wallets/balance/1/', views.custom_balance,basename='CustomBalanceModel').
You can GET and POST through router.register('wallets', views.WalletView, basename='WalletModel')
Here is some more Django documentation on ViewSets
So in this cast if you're already doing a POST request somehow in your code you can instead do a GET request in the same fashion.
If you have the time to learn, I learned all this from Pluralsight:
https://www.pluralsight.com/courses/django-angularjs-web-development
If you have a day or two you can learn it all.
I also recommend this Max Goodridge video. They're all pretty good

Is it bad to pass my entire `request` object to my `Models.py` for validations in Django when using django-messages?

Having trouble finding a totally clear answer to this question. In my learning, I was taught to not pass along the entire request object when trying to pass data from views.py to models.py (when using a django manager).
However, in my current scenario, I'm trying to setup validation methods in my models.py (using a Manager) whereby I utilize django-messages (https://docs.djangoproject.com/en/1.11/ref/contrib/messages/), to whom one requirement is the request object itself when generating custom errors, such as:
messages.add_message(request, REG_ERR, 'First and last name are required must be at least 2 characters.', extra_tags="reg_errors")
I am trying to keep all validations, error message generation, and creation or retrieval actions inside of my models.py using a manager (https://docs.djangoproject.com/en/1.11/topics/db/managers/), and simply hand back the relevant data to my views.py.
To achieve this, in my views.py, I create a dictionary with the full request object itself, I send this off for all validations/creations, and then check for errors upon the return, as a False is returned if any validation errors were flagged. Otherwise the success page will load with the new user.
views.py:
def register(request):
if request.method == "POST":
# Validate registration data submitted from registration form:
validated = User.objects.register_validate(request)
# If validation fails, load index page with request object
# (which `django messaging` has attached errors to):
if validated == False:
print "User could not be registered."
# Send back index with updated request object:
return render(request, "logreg/index.html")
# If validation successful, create new user and send it along with success page:
else:
# Load success page with `validated` user (already returned as a `dict` obj.)
return render(request, "logreg/success.html", validated)
models.py:
# Note: this function snippet is part of `class UserManager(models.Manager)`:
def register_validate(self, request):
# Check if first_name or last_name is less than 2 characters:
if len(request.POST["first_name"]) < 2 or len(request.POST["last_name"]) < 2:
# Add error to Django's error messaging:
messages.add_message(request, REG_ERR, 'First and last name are required must be at least 2 characters.', extra_tags="reg_errors")
# ... more validations ...
# Get current errors to check if any exist:
errors = get_messages(request)
# If no validation errors, hash password, create user and send new user back:
if len(errors) == 0:
# Hash Password:
hashed_pwd = bcrypt.hashpw(request.POST["password"].encode(), bcrypt.gensalt(14))
# Create new validated User:
validated_user = {
"logged_in_user": User(first_name=request.POST["first_name"], last_name=request.POST["last_name"], email=request.POST["email"], password=hashed_pwd)
}
# Save new User:
validated_user["logged_in_user"].save()
# Send newly created validated User back:
return validated_user
else:
return False
Question:
Previously I had extracted all data from the request object via request.POST["my_data"] (did not pass the entire object but extracted what I needed into a custom dict), and was custom generating error messages. However, I wanted to practicing using django-messages, (of which requires the request object as a parameter, as this is what it attaches the errors to). Because I wanted all validations to occur in models.py, I sent my entire request object over (to then extract form data, but to also create new messages with django-messages).
Does passing the entire request object cause major drops in performance or is this bad practices? The fact that my learning lesson went so far out of the way to stress not passing the entire request object was a little confusing to me, especially considering to generate errors in my models.py via django-messages, I need access to the full object.
Any ideas?
Note: The other thought I had was to have my validation functions return a list of errors, to which I could iterate over and generate the django-messages errors (and using the request object in views.py), but this would place more logic/crunching on the controller, and was trying to keep everything nice and tight in the Manager (in models.py) for simplicity/organization.
Thanks in advance for reading, and let me know if I need to clarify any information here presented...was trying to provide a minimal chunk of my code to give context.
Attaching the entire request object does not necessarily hit performance, but it does open up security issues, as if the entire request is being sent to models.py, a spoofed request could interfere with intended functions.
From talking with other developers in a slack channel, it sounds like most people generate a list of errors in models.py, which is handed back to views.py, in which they invoke the django-messages module and create their messages. The primary argument is to limit access to the request object itself, keeping it from models.py and mitigating risk from spoofed requests.

Using Django Rest Framework, is it possible to get results internally on server within Django?

I have a complex DRF ViewSet that supports paging, filtering, sorting, etc. that backends a grid view. To build an "export" capability, I need to be able to take the same querystring that my endpoint uses, such as:
?obj_id=129&ordering=latitude&city__icontains=nap
And be able to, in Django, send that string into DRF somehow and get the fully-modified queryset after all the view's filters, sorts, etc are applied (the same way as the GET did). I could use the fully-rendered json response or some interim filter-applied queryset. Is it possible to use DRF in this way?
You should write a CSV renderer for your viewset and get that content-type to export the CSV.
There's even one already available.
Yes you could, if you already have a request object i.e. If you want to use this DRF viewset into another view which has the request object:
def another_view(request):
# make a copy of the `GET` attribute of request object
request.GET = request.GET.copy()
# now you can set the query params on this GET object
# ?obj_id=129&ordering=latitude&city__icontains=nap
request.GET['obj_id'] = 129
request.GET['ordering'] = 'latitude'
request.GET['city__icontains'] = 'nap'
# you can also set paging options in similar way
# now instantiate the viewset
vs = DRFViewset.as_view()
# call the view for response
# set kwargs as you need
response = vs(request, *args, **kwargs)
# response.data will have what you want here

Django app treats HTTP request as HttpRequest object, not django-rest Request object

I have the following code
class UserPollPassedView(generics.UpdateAPIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = UsersPollUpdateSerializer
def update(self, request, *args, **kwargs):
request.data['date_passed'] = datetime.datetime.now()
request.data['passed'] = True
return super(UserPollPassedView, self).update(request, *args, **kwargs)
When I try to access this method through such curl instrument like hurl.it, I get QueryDict is immutable error, because I'm trying to modify the content of request.data, assuming that request is Django-rest's Request object, but it's not, it is actually native Django HttpRequest object. When I explicitly define Content-type: application\json in request header, it gives me another JSON parse error. When I use httpie tool, everything goes smooth and clean, though it sends the same application\json header by default. I suppose, the difference is still in headers, but I can't understand what exactly I should change.
I also tried explicitly typecast request from HttpRequest to Request by adding the following line right before accessing request.data
request = self.initialize_request(request)
but of no use, it gives same "JSON parse error - Expecting value: line 1 column 1 (char 0)" even if request is initially Request object from httpie.
Would appreciate any clue
I don't know why you think the object is not the DRF request. Since you are using a DRF view, you get a DRF request. Django's own request object doesn't even have a data attribute.
The request variable is definitely holding a DRF Request object, and not a Django HttpRequest object. You would be getting an error about data not existing on the HttpRequest if this was not the case.
The problem is that request.data is a QueryDict object when the data is submitted as form data (like through a browser), which should be close to what you would get from request.POST/request.GET/request.FILES. This is probably why you thought it was a Django HttpRequest, because this is usually an issue people see after trying to modify any of these objects.
You are trying to modify request.data which isn't recommended and should really be treated as an immutable dictionary. Usually this is a sign that you should be setting these keys somewhere else, usually on the serializer layer or by overriding perform_update as a hook), instead of modifying the incoming request data.

Django Rest Framework testing save POST request data

I'm writing some tests for my Django Rest Framework and trying to keep them as simple as possible. Before, I was creating objects using factory boy in order to have saved objects available for GET requests.
Why are my POST requests in the tests not creating an actual object in my test database? Everything works fine using the actual API, but I can't get the POST in the tests to save the object to make it available for GET requests. Is there something I'm missing?
from rest_framework import status
from rest_framework.test import APITestCase
# from .factories import InterestFactory
class APITestMixin(object):
"""
mixin to perform the default API Test functionality
"""
api_root = '/v1/'
model_url = ''
data = {}
def get_endpoint(self):
"""
return the API endpoint
"""
url = self.api_root + self.model_url
return url
def test_create_object(self):
"""
create a new object
"""
response = self.client.post(self.get_endpoint(), self.data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.data, self.data)
# this passes the test and the response says the object was created
def test_get_objects(self):
"""
get a list of objects
"""
response = self.client.get(self.get_endpoint())
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, self.data)
# this test fails and says the response is empty [] with no objects
class InterestTests(APITestCase, APITestMixin):
def setUp(self):
self.model_url = 'interests/'
self.data = {
'id': 1,
'name': 'hiking',
}
# self.interest = InterestFactory.create(name='travel')
"""
if I create the object with factory boy, the object is
there. But I don't want to have to do this - I want to use
the data that was created in the POST request
"""
You can see the couple lines of commented out code which are the object that I need to create through factory boy because the object does not get created and saved (although the create test does pass and say the object is created).
I didn't post any of the model, serializer or viewsets code because the actual API works, this is a question specific to the test.
First of all, Django TestCase (APITestCase's base class) encloses the test code in a database transaction that is rolled back at the end of the test (refer). That's why test_get_objects cannot see objects which created in test_create_object
Then, from (Django Testing Docs)
Having tests altering each others data, or having tests that depend on another test altering data are inherently fragile.
The first reason came into my mind is that you cannot rely on the execution order of tests. For now, the order within a TestCase seems to be alphabetical. test_create_object just happened to be executed before test_get_objects. If you change the method name to test_z_create_object, test_get_objects will go first. So better to make each test independent
Solution for your case, if you anyway don't want database reset after each test, use APISimpleTestCase
More recommended, group tests. E.g., rename test_create_object, test_get_objects to subtest_create_object, subtest_get_objects. Then create another test method to invoke the two tests as needed