django rest framework defaultRouter without models - django

I am new to Python and Django (rest framework) and I love the DefaultRouter feature, by also giving an "API ROOT" page. But, I can only use the defaultrouter.register() method with viewsets. How to make a custom route without a model? For example I will have a "POST" route that multiplies 2 values from the body. (so body is something like {"value1":123, "value2":456}
Is it good practice to use viewsets for everything?
How should I implement a custom (multiply) function? I will have a serializer with the 2 params? And where to implement the code?
Am I doing things right?
EDIT1: I added the following code to my views.py
#api_view(['GET'])
def test1(request):
"""
Simple test
"""
data = {
"test": True
}
return Response(data, status=status.HTTP_200_OK)
Then I added the route to my urls.py. But, as expected, the hyperlink doesnt show up in the api root.
I am seeing this in the API Root:
So this is missing my function based view.
Why is there a distinction here?

Related

Is it okay to call django functions through an API?

I'm building a project and now I'm new to VueJS I'm currently learning it. and I found that you can make HTTP Requests on APIs using axios. And to make my project easy, Can I call functions on my views.py thru axios?
Like I'm fetching urls in urls.py to execute some functions on my backend.
Is it okay? I mean for security and best practices. etc.
Thanks
Absolutely ok, that's what Django is for:
urls.py:
urlpatterns = [
...
path('my_view_function/', views.my_view_function, name='my_view_function'),
...
]
views.py:
def my_view_function(request):
# unpack data:
my_value = request.GET['my_key']
# some logic:
...
# pack response:
response = json.dumps({
'some_other_key' : 'some_other_value'
})
return HttpResponse(response)
Another option is that you use signals in django, some time ago I used signals that for when a new record is created without a field, it will be completed with the algorithm you want, for example an order and automatically placed a code, you can apply it only by pointing to your models, when you want something more punctual.
#receiver(pre_save, sender=MyModel)
def my_handler(sender, **kwargs):
...
The my_handler function will only be called when an instance of MyModel is saved.
Here I leave the documentation in case you want to review it
https://docs.djangoproject.com/en/3.1/topics/signals/

Django REST - Serialize User.get_all_permissions

I am building an application with a Django Rest backend, and a VueJS front end and am working through authorization and authentication. I have the authentication working well, but am a bit stuck on letting the front end (VueJS) know what the user has authorization to do in terms of Add/Change/View/Delete for a model. For example, if a user cannot add a customer, I don't want to show the 'Add Customer button'.
Working through the Django docs, and solutions on StackOverflow, I believe the simplest way is to send the user's permissions from Django to VueJS.
The 'best'/'simplest' way I can see to get the permissions is with the following:
userModel = User.objects.get(request.user)
return User.get_all_permissions(userModel)
Where I am stuck is exactly where to put this logic and how to serialize it. Does the above belong in the View, Serializer, other? Up until now, I have only been working with Models (ModelSerializers and ModelViews), but I don't believe this falls into this category.
Thanks in advance...
You should add this logic to views, because the views are used to implement these kinds of logic.
Actually, you don't want to use serializers here, because of the response of .get_all_permissions() method is already in serialized form
Apart from that, your provided code is not good (it's clearly bad). It should be as below,
return request.user.get_all_permissions()
because, you'll get current logged-in user's instance through request.user, to get his/her permissions, you all need to call the get_all_permissions() method
Example
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
#permission_classes(IsAuthenticated, )
#api_view()
def my_view(request):
logged_in_user = request.user
return Response(data=logged_in_user.get_all_permissions())

django rest post return 405 error code

I am using django with rest framework and I try to test POST on existing object but I keep getting 405. my ViewSet looks like this:
class Agents(viewsets.ModelViewSet):
serializer_class = serializer.AgentSerializer
model = serializer_class.Meta.model
....
and in the urls:
router = routers.SimpleRouter()
router.register(r'rest/agents', api_views.Agents, "Agent")
...
urlpatterns += router.urls
I call the post request from within APITestCase class (rest testing), my post request looks like this:
response = self.client.post(url, {'available': True, 'online':True}, format='json')
and printing url shows "/chat/rest/agents/1910_1567/", while 1910_1567 is the valid id of an existing agent (I create the agent during the setup and use its id).
I've seen other questions about rest post getting 405, but all the solutions there were url-related and in my case the url is correct. I even ran the setup outside the test and accessed the url via browser to get the object and the object indeed exist. but when I try to post to it - 405.
any ideas?
thanks!
Most probably your url is somehow matching with some different url's regex, and the request is being dispatched to some other view which disallows post request. Could you mention others urls in your urls.py? Infact you can verify this by adding pdb(debugger) in dispatch method of your view. If you made it to the pdb, then you can assume I was wrong.
If that is not the case, then you can evaluate the issue from dispatch method with debugger. Just in case you have any doubt about how to do that -
class Agents(viewsets.ModelViewSet):
serializer_class = serializer.AgentSerializer
model = serializer_class.Meta.model
def dispatch(self, *args, **kwargs):
import ipdb; ipdb.set_trace()
return super(Agents, self).dispatch(*args, **kwargs)
Solution Found:-
You are passing id in url, so this request would be routed to detail view and detail view only allow GET, PUT, DELETE operations on resource since resource already exists. So to create resource, don't provide the id. Otherwise use PUT request and provide support for creation in PUT.
POST are supposed to be for creation purpose. If you want to update with a ViewSet you'll need to PUT or PATCH (partial update) instead.
Edit:
For more about this, here's some explanation on the HTTP methods used for REST API:
http://restful-api-design.readthedocs.org/en/latest/methods.html
This is also described in the DRF documentation at:
http://www.django-rest-framework.org/api-guide/routers/#simplerouter

DjangoRestFramework - How do I customize the frontend?

This is my basic view using DjangoRestFramework:
class user_list(APIView):
"""
List all users, or create a new user.
"""
def get(self, request):
users = User.objects.all()
serializer = UserSerializer(users, many=True)
return Response(serializer.data)
def post(self, request):
serializer = UserSerializer(data=request.DATA)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
When I go to the URL which calls this view (localhost:8000/CMS/users), DjangoRestFramework already has a frontend which says:
GET /CMS/users
HTTP 200 OK
Vary: Accept
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
[
{
"username": "t",
}
]
How do I customize this? I want to use AngularJS on the frontend. I know that without DjangoRestFramework, I would return an html template which would be located in my Django App directory in a folder called "Templates". I've been told that DjangoRestFramework is useful becauase I can create a RESTful API which returns JSON objects. I tried adding the following to my settings.py file:
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
)
}
but I still can't seem to figure out how I can customize the frontend of my Django application. I know that there is:
renderer_classes = (TemplateHTMLRenderer,)
in which I can return an html page in my view, like so:
return Response({'user': self.object}, template_name='user_detail.html')
but doesn't returning actual HTML pages defeat the purpose of DjangoRestFramework's ability to return JSON objects and create a RESTful API?
First off, there are some things to make Django Rest Framework play better with angular. Until I decided to try out ember, I was working through this utorial. To try to provide a useful example for your question:
Typically you want to put your DjangoRestFramework REST Api in something like /api.
If you're doing the seemingly ubiquitiious single page application, you can then have index.html get served up as a template (i.e. just have your default home view be a TemplateView with index.html as the template)
You would then have your angular html templates and your angular/jquery/etc. javsascripts and your css files as normal static files. NB that you generally don't want to have django templates generate angular code. First off, it makes things much more of a pain to debug, and secondly, the default angular and django template syntax use the same <% characters meaning you have to be careful to make sure one doesn't try to interpret the others templating code.
When you load http://yourserver/, this will bring up the index.html page which then pulls down angular stuff, which then starts making RESTful calls to your REST api to generate the data. Note that you can start mixing in stuff to use Django forms etc., but I'd recommend keeping it simple and then reading the linked article (or another) once you have the basics going.

Django REST Framework - 405 METHOD NOT ALLOWED using SimpleRouter

I'm using the SimpleRouter tuorial within the docs.
Just to test I've created a temporary Authentication class:
class BackboneBasicAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
user = User.objects.filter(username="james")
return (user, None)
settings look like this
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'core.rest_authentication.BackboneBasicAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
),
}
Submitting a PUT request returns a 405 METHOD NOT ALLOWED
{"detail": "Method 'PUT' not allowed."}
I've tried with X-HTTP-Method-Override as well. No go.
Any ideas what I'm doing wrong?
I've spent a whole day trying to figure this out, hopefully someone can help! :)
The simple router adds the put attribute to the view for a url matching the pattern you supply with the pk added as an additional pattern element.
For example if you used:
simple_router.register('widgets/', WidgetViewSet)
The framework will create two url patterns:
'^widgets/$'
'^widgets/<?P<pk>[^/]+/$'
I am guessing that you are only trying urls that satisfy the first match for which the viewset instance will only have 'get' ('list') and 'post' ('create') support added by the framework so it will cause the error you are seeing if you try to put/patch or delete. For those methods to work you need to supply the pk so that the framework knows which widget you are modifying or deleting and so that your url matches the view that supports those methods.
This is confusing and you may choose not to use the simple_router at all if you find it too confusing. Then you can specify your own method mapping so that the rest_framework will dispatch to your put methods e.g.
url('^widgets/<?P<pk>[^/]+/$', WidgetViewSet.as_view({'put': 'update',
'get': 'retrieve',
'patch': 'partial_update',
'delete': 'destroy'}...)
To me that seems to be caused by the routed viewset not implementing or not allowing PUT requests. If it was an authentication issue, you would get a 401 UNAUTHORIZED status code.