How to create dual prefix and read POST payload with DRF - python

I need to execute an SSH command using a POST method. The command and its parameters are specified in the POST request’s JSON payload.
I have no idea on how to acompplish this using the DefaultRouter from django REST framework
So my first question is; How do I create a route or view for this url?
POST http://127.0.0.1/datacenter/<datacenter_id>/<server_id>/ssh/
And, how do I get the data from the payload in order to work with it?
I've tried with something like this;
#detail_route(methods=['POST'])
def ssh(self, request, pk=None):
print request.data
but Im getting "Expected a Response, HttpResponse or HttpStreamingResponse to be returned from the view, but received a <type 'NoneType'>"
models.py
class Datacenter(models.Model):
# Parent data
def __unicode__(self):
return self.name
class Servers(models.Model):
datacenter = models.ForeignKey(Datacenter)
def __unicode__(self):
return self.hostname
serializers.py
class ServerSerializer(serializers.ModelSerializer):
class Meta:
model = Server
class DatacenterSerializer(serializers.ModelSerializer):
servers = ServerSerializer(many=True)
class Meta:
model = Datacenter
fields = ('id', 'servers')
views.py
class DatacenterViewSet(viewsets.ModelViewSet):
queryset = Datacenter.objects.all()
serializer_class = DatacenterSerializer
class ServerViewSet(viewsets.ModelViewSet):
queryset = Server.objects.all()
serializer_class = ServerSerializer
urls.py
router = routers.DefaultRouter()
router.register(r'Datacenter', views.DatacenterViewSet)
urlpatterns = router.urls

Your current code seems to be nearly-complete. Assuming that you have a function that does the actual execution (let's call it "run_ssh_command"), a rudimentary version of your view could look something like this:
#detail_route(methods=['POST'])
def ssh(self, request):
input = json.loads(request.data) # I'd use a serializer here
output = run_ssh_command(input['command']) # or whatever the field name is
return Response(json.dumps({'result': output}),
content_type="application/json")
Some caveats:
Make sure you use proper authentication,
Keep in mind that some SSH commands might take a while to run OR they may just hang (E.G., waiting for input),
Take a look at http://www.django-rest-framework.org/tutorial/2-requests-and-responses/ for an example on how to use Serializers for request validation.

Related

Easy Thumbnails - How to test a view that contains with ThumbnailerImageField in DRF

I have a model called "Post" that looks for example like this:
# models.py
from django.db import models
from easy_thumbnails.fields import ThumbnailerImageField
class Post(models.Model):
name = models.CharField(max_length=255)
cover = ThumbnailerImageField(upload_to='posts')
Then I have a serializer for the model:
# serializers.py
class PostSerializer(serializers.ModelSerializer):
cover = ThumbnailSerializer(alias='small')
class Meta:
model = Post
fields = ['id', 'name', 'cover']
Using the Thumbnail serializer:
from rest_framework import serializers
from easy_thumbnails.templatetags.thumbnail import thumbnail_url
class ThumbnailSerializer(serializers.ImageField):
""" Serializer for thumbnail creation using easy-thumbnails (Needed when using Django Rest Framework) """
def __init__(self, alias, *args, **kwargs):
super().__init__(*args, **kwargs)
self.read_only = True
self.alias = alias
def to_representation(self, value):
if not value:
return None
url = thumbnail_url(value, self.alias)
request = self.context.get('request', None)
if request is not None:
return request.build_absolute_uri(url)
return url
Finally I have a view:
# views.py
class PostView(generics.RetrieveAPIView):
queryset = Post.objects.filter(enabled=True)
serializer_class = PostSerializer
Now inside my test I try creating a post and fetching the data (im using PyTest):
# tests.py
def test_post_endpoint(client):
post = Post.objects.create(
name="Post 1",
cover="posts/test_image.jpg",
)
response = client.get('/posts/')
assert response.status_code == 200
print(response.data['cover'])
# This prints: http://testserver/posts/
# Instead of: http://testserver/posts/test_image.small.jpg
I also tried using:
cover=SimpleUploadedFile(
name='test_image.jpg',
content=open(image_path, 'rb').read(),
content_type='image/jpeg'
)
But this ended up uploading the image to S3 which I dont want since its just a test and it should not upload anything to the cloud.
How can I get a proper response for the cover data? Something like this:
'http://testserver/posts/test_image.small.jpg'
It seems you want to modify Django's MEDIA_ROOT and MEDIA_URL path in the setting.py for test purposes. These values can be specified based on environment variables, like this:
# in settings.py
import os
...
if os.environ.get("TEST_ENV", '') == 'TRUE':
MEDIA_URL = 'http://testserver'
# or something like that
else:
MEDIA_URL = '<your default url>'
before getting started with tests, the environment variable should be set:
export TEST_ENV=TRUE
this blog post might be helpful.
also, Django's docs on how to handle files with MEDIA_URL may help.
this third-party package for splitting Django settings may also help.

DRF list endoint returns 404 while there are objects present in db

There's some problem when I'm trying to retrieve a list of objects.
Here is my code (I've reduced it for simplicity)
# Models.py
class TournamentType(models.Model):
name = models.CharField(max_length=255)
# Serializers.py
class TournamentTypeSerializer(serializers.ModelSerializer):
class Meta:
model = models.TournamentType
fields = ('id', 'name')
# Views.py
class TournamentTypeViewSet(ReadOnlyModelViewSet):
queryset = TournamentType.objects.all()
serializer_class = serializers.TournamentTypeSerializer
# Urls.py
if settings.DEBUG:
router = DefaultRouter()
else:
router = SimpleRouter()
router.register("tournament_types", views.TournamentTypeViewSet)
app_name = "tournaments"
urlpatterns = router.urls
In Swagger I end up seeing 2 request types:
http://localhost:8000/api/tournaments/tournament_types/{id}
http://localhost:8000/api/tournaments/tournament_types/
First one (with ID) returns object as expected.
But the second one, which is supposed to return a list, returns a 404 response.
I've tried specifying a request type by adding this to the view:
def list(self, request):
queryset = TournamentType.objects.all()
serializer = serializers.TournamentTypeSerializer(queryset, many=True)
return Response(serializer.data)
But result is still the same.
What am I missing? I've checked all the docs, and everything seems to be in place.
UPDATE
I've managed to make a method that returns the list of objects but the endpoint looks odd and it's definitely a crutch rather than a proper solution:
# Views.py
class TournamentTypeViewSet(ReadOnlyModelViewSet):
queryset = TournamentType.objects.all()
serializer_class = serializers.TournamentTypeSerializer
#swagger_auto_schema(method="get")
#action(detail=True, methods=["get"])
def tt(self, request, *args, **kwargs):
queryset = TournamentType.objects.all()
serializer = serializers.TournamentTypeSerializer(queryset, many=True)
return Response(serializer.data)
/api/tournaments/tournament-types/{id}/tt/
It returns the list but it requires some input to work, it can be anything,
it doesn't seem to check that id argument.
If I send a request to /api/tournaments/tournament-types/tt/, it returns same 404 response, not an error.
Your code is correct, it worked for me.
I think you're doing something wrong in urls.py of the main project folder or you've changed something in settings.py
Restart your IDE and try running again. Make sure to check the urls.py of main project folder.
I've found the way to fix it. The problem was in routes. Previously my tournaments/urls.py looked like this:
router = DefaultRouter()
router.register("tournament-types", TournamentTypeViewSet)
app_name = "tournaments"
urlpatterns = router.urls
And then it was passed on to the main router.py.
But when I put it directly into the main file, it started working:
router = DefaultRouter()
router.register("tournament-types", TournamentTypeViewSet)
app_name = "app_name"
urlpatterns = [path("tournaments/", include('core.tournament.urls'))]
urlpatterns += router.urls
Not sure why the first version doesn't work though.

Return a Http Response In get_queryset

Im using get_queryset, in ListAPIView
I want to check the user's access token, before providing the list, I done the below but the issue is that get_query set does not return a Response, is there a way to return a response, or I should use an alternative :
this my class in the views.py :
class ListProductsOfCategory(generics.ListAPIView):
serializer_class = ProductSerializer
lookup_url_kwarg = 'category_id'
def get_queryset(self):
# I get the token here from the headers
token = self.request.META.get("HTTP_TOKEN", "")
if not token:
return Response(
data={
"message": "no token!"
},
status=status.HTTP_400_BAD_REQUEST
)
if not UserAccess.objects.filter(accessToken=token).exists():
return Response(
data={
"message": "invalid token!"
},
status=status.HTTP_400_BAD_REQUEST
)
category_id = self.kwargs.get(self.lookup_url_kwarg)
return Product.objects.filter(category_id=category_id)
note that everything is working perfect If I removed the token related part.
thanks in advance.
after last update this is the repsonse :
I'd suggest you to move check token logic into dispatch() method. It's a better place than get_queryset. Or even better to write your own authentication class in order to share it between views.
With some fixes (see updated get_queryset()) it can be:
UPDATE
I think you can go with built-in restframework.exceptions.AuthenticationFailed.
If you are not satisfied with default DRF exceptions you can create your own custom exceptions. For example somewhere in exceptions.py:
from rest_framework.exceptions import APIException
class MissingTokenException(APIException):
status_code = 400
default_detail = 'Your request does not contain token'
default_code = 'no_token'
class InvalidTokenException(APIException):
status_code = 400
default_detail = 'Your request contain invalid token'
default_code = 'invalid_token'
Then you can use them in views.py:
from rest_framework import serializers
from .exceptions import MissingTokenException, InvalidTokenException
class ListProductsOfCategory(generics.ListAPIView):
serializer_class = ProductSerializer
lookup_url_kwarg = 'category_id'
def dispatch(self, *args, **kwargs):
token = self.request.META.get("HTTP_TOKEN", "")
if not token:
raise MissingTokenException
if not UserAccess.objects.filter(accessToken=token).exists():
raise InvalidTokenException
return super().dispatch(*args, **kwargs)
def get_queryset(self):
qs = super().get_queryset()
category_id = self.kwargs.get(self.lookup_url_kwarg)
return qs.filter(category_id=category_id)
I'm not 100% if I'm getting this right, but I believe you can just use the regular authentication mechanisms that DRF provides. In this particular example, I think this section of the DRF docs should show you how to do it the "DRF" way: Setting Authentication Scheme
If you add the TokenAuthentication scheme to your application, you don't need to verify the token in your get_queryset method, but you can just use decorators to restrict access for function-based views or permission_classes for class-based views:
View-based
I guess this is what you'd be most interested in.
class ListProductsOfCategory(generics.ListAPIView):
serializer_class = ProductSerializer
lookup_url_kwarg = 'category_id'
authentication_classes = (TokenAuthentication, ) # Add others if desired
permission_classes = (IsAuthenticated,)
Route-based
If you only want to restrict access for some of your routes (e.g. only post or detail views), then you can write your own permission class. For example, see this question here: How to add django rest framework permissions on specific method only ?

Djangorest framework same Generic Create View with GET and POST

I'm using a Generic CreateAPIView to save a model in the database. Here's my code:
class AppointmentCreateAPIView(generics.CreateAPIView):
permission_classes = (AppointmentCreatePermission,)
queryset = Appointment.objects.all()
serializer_class = AppointmentSerializer
And in my urls.py file, I have this:
urlpatterns[
url(r'^appointments/create', AppointmentCreateAPIView.as_view()),
]
This url obviously supports the POST operation. However, I want to use this same url to handle a GET request, which would fetch the data necessary to populate the appointment creation form. I understand that I can use separate urls for get and post, but that's not what I'm looking for. Is it possible that I keep the same url, but with different HTTP Verb, the view would be able to handle both GET and POST request?
You can do this by manually adding get method to your view, it would look something like this. Code below probably will not work, but will give you general idea.
from rest_framework.response import Response
class AppointmentCreateAPIView(generics.CreateAPIView):
permission_classes = (AppointmentCreatePermission,)
queryset = Appointment.objects.all()
serializer_class = AppointmentSerializer
def get(self, request, *args, **kwargs):
serializer = AppointmentSerializer({your_data})
return Response(serializer.data)

How to pass more than one variables to modelViewSet in django rest framework?

I am using http://www.django-rest-framework.org/
I have the scenario where I want to pass two or more variables based on that I need to fetch data from database. In the following code only pk is there which I want to replace with two other fields in database.
Also please suggest how can I write my urlconfig the same.
Views.py
class ExampleViewSet(viewsets.ReadOnlyModelViewSet):
model = myTable
def list(self, request):
queryset = myTable.objects.all()
serializer = mySerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
queryset = myTable.objects.all()
s = get_object_or_404(queryset, pk=pk)
serializer = mySerializer(s)
return Response(serializer.data)
Serializer.py
class Serializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = myTable
fields = ('attr1', 'attr2', 'attr3')
Here is how you would do it with the recent Django REST Framework.
Assuming your variables are in the resource URLs like so:
GET /parent/:id/child/
GET /parent/:id/child/:id/
Then:
urls.py:
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'parent/(?P<parent_id>.+)/child', views.ExampleViewSet)
urlpatterns = router.urls
views.py:
class ExampleViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = Serializer
def get_queryset(self):
parent = self.kwargs['parent']
return myTable.objects.filter(parent=parent)
Where the 'parent' in the queryset part is your parent object. You may need to adjust it a little, of course, but the idea is encapsulated in the kwargs.
This solution will also save you a little code and you can make it into a full blown ModelViewSet just by subclassing it.
Hope that helps.
More here: DRF Filtering against the URL.
Here is an example of how you might implement what you want:
class ExampleViewSet(viewsets.ReadOnlyModelViewSet):
# This code saves you from repeating yourself
queryset = myTable.objects.all()
serializer_class = mySerializer
def list(self, request, *args, **kwargs):
# Get your variables from request
var1 = request.QUERY_DICT.get('var1_name', None) # for GET requests
var2 = request.DATA.get('var2_name', None) # for POST requests
if var1 is not None:
# Get your data according to the variable var1
data = self.get_queryset().filter(var1)
serialized_data = self.get_serializer(data, many=True)
return Response(serialized_data.data)
if var2 is not None:
# Do as you need for var2
return Response(...)
# Default behaviour : call parent
return super(ExampleViewSet, self).list(request, *args, **kwargs)
def retrieve(self, request, *args, **kwargs):
# Same for retrieve
# 1. get your variable xyz from the request
# 2. Get your object based on your variable's value
s = myTable.objects.get(varX=xyz)
# 3. Serialize it and send it as a response
serialized_data = self.get_serializer(s)
return Response(serialized_data.data)
# 4. Don't forget to treat the case when your variable is None (call parent method)
As for the urlconf, it depends on how you want to send your variables (get, post or through the url).
Hope this helps.
urls.py
url(
regex=r'^teach/(?P<pk>\d+?)/(?P<pk1>\d+?)/$',
view=teach_update.as_view(),
name='teach'
)
Templates
<td><a href="/teach/{{tid}}/{{i.id}}"><button type="button" class="btn
btn-warning">Update</button></a></td>
Views.py
class teach_update(view):
def get(self,request,**kwargs):
dist=self.kwargs['pk']
ddd=self.kwargs['pk1']

Categories

Resources