I'm using django.rest_framework. I have a get_or_create method for a particular view,
class LocationView(views.APIView):
def get_or_create(self, request):
try:
location = Location.objects.get(country=request.data.get("country"), city=request.data.get("city"))
Response(location, status=status.HTTP_200_OK)
except Location.DoesNotExist:
serializer = LocationSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
This is the location Model,
class Location(models.Model):
country = models.CharField(max_length=255)
city = models.CharField(max_length=255, unique=True)
latitude = models.CharField(max_length=255)
longitude = models.CharField(max_length=255)
class Meta:
unique_together = ('country', 'city')
This is my url,
url(r'^location/$', LocationView.as_view(), name='location'),
When I call this endpoint in the following way,
http://127.0.0.1:8000/api/v1/bouncer/location/?country=USA&&city=Sunnyvale&&latitude=122.0363&&longitude=37.3688
This is what I get,
{
"detail": "Method \"GET\" not allowed."
}
What am I missing here.
The Method not allowed error is because, it searches for a get() method inside your API class, and it couldn't find a one.
The general format of the API class is as below
class LocationView(views.APIView):
def get(self, request):
#do something with 'GET' method
return Response("some data")
def post(self, request):
#do something with 'POST' method
return Response("some data")
If you want to invoke the get_or_create() method at some point, you can do it as any other methods,
class LocationView(views.APIView):
def get_or_create(self, request):
# do some "get or create" stuff
return "some data"
def get(self, request):
if condition:
self.get_or_create(request)
# do some stuff
return Response(" some special data related to get or create")
return Response("some data")
You need to provide separate methods for get and post. If your get method also creates an instance, then you can just call your current get_or_create inside both get and post methods.
class LocationView(views.APIView):
def get_or_create(self, request):
# your current definition here
def get(self, request):
return self.get_or_create(request)
def post(self, request):
return self.get_or_create(request)
For my situation, I was sending a request properly as my view would expect, a POST. But the issue was on http/s. I had set a permanent redirect, from http to https on my app, and so sending a POST to the http version resulted in a GET somehow when the server was redirecting.
TL;DR
HTTP instead of HTTPS
Related
I'm using drf-yasg for swagger docs, but I'm using a BaseView which is the parent class view for all other views, thus all HTTP methods are written in BaseView so that children views don't need to implement these methods.
the problem is that when I added #swagger_auto_schema to baseView methods it didn't see the child attributes but the BaseView's attributes which are empty.
The BaseView:
class BaseView(APIView):
model = models.Model
serializer_class = serializers.Serializer
description = ''
id = openapi.Parameter('id', in_=openapi.IN_QUERY, type=openapi.FORMAT_UUID)
#swagger_auto_schema(manual_parameters=[id] ,responses={200: serializer_class},operation_description=description)
def get(self, request, id=None, **kwargs):
....
#swagger_auto_schema(request_body=serializer_class)
def post(self, request, **kwargs):
....
Then let's say I have this child view:
class TestApi(BaseView):
model = test
description = 'This is test api'
serializer_class = TestSerializer
In this approach Swagger is showing empty values for description and serializer_class because it doesn't see child values how to solve this without having to configure each view with HTTP methods and #swagger_auto_schema?
Thanks in advance.
Create a python file named api_docs.py and write a description of your API View of all methods.
docs = {
'View Name': {
'post': 'your description',
'list': '....description',
}
}
after that you have to import this into your view and add this.
from django.utils.decorators import method_decorator
#method_decorator(name="HTTP method name like post put destroy", decorator=swagger_auto_schema(
tags=["App name"], operation_description=docs['ViewName']['post']))
if you are overriding any method then you have to explicitly add on
that view like this
#swagger_auto_schema(tags=["App name"], operation_description=docs['View Name']['post'])
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
I have this code of 2 views in Django. You will notice that each REST API call has a verify_login() function call that ensures that the request contains a verified JWT token. I'm wondering if there's a better way to implement this so that I don't have to have these lines specifically in every REST endpoint
verify_response = verify_login(request)
if verify_response not None:
return verify_response
I'm trying to follow the D.R.Y. (Do Not Repeat Yourself) principle of coding. It'd be nice if there was a cleaner way to represent this. I thought about maybe creating a module extending APIView that automatically has this and then all my Views extend that, but runs into the issue of having to call super().API_REQUEST() and then having to do the same if-statement check to see if it's None or not.
class PostView(APIView):
"""
View for Post object
* requires token authentication
"""
# Create post
#swagger_auto_schema(
request_body=PostSerializer,
operation_description="Create a post object"
)
def post(self, request):
verify_response = verify_login(request)
if verify_response not None:
return verify_response
serializer = PostSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# get all posts
#swagger_auto_schema(
operation_description="Get all posts from the DB"
)
def get(self, request):
verify_response = verify_login(request)
if verify_response not None:
return verify_response
posts = Post.objects.all()
serializer = PostSerializer(posts, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
You can use authentication classes alongside permission classes. If you want the authentication check to happen for all the APIs of your application, put your classes in settings.REST_FRAMEWORK. If you want it for specific APIView's, put them in permission_classes & authentication_classes class variables. Check out the doc for more details.
Example,
class JWTAuthenticataion(BaseAuthentication):
def authenticate(self, request):
... # write your JWT implementation here
# settings.py:
REST_FRAMEWORK = {
...
"DEFAULT_AUTHENTICATION_CLASSES": (
"path.to.JWTAuthentication",
),
"DEFAULT_PERMISSION_CLASSES": (
"rest_framework.permissions.IsAuthenticated",
)
}
# or
# api.py
class YourAPIView(APIView):
permission_classes = (IsAuthenticated, )
authentication_classes = (JWTAuthentication, )
...
I'm learning Django rest Framework. I know how to use Class Based Views. Why methods GET, PUT, DELETE, ETC needs the "request" parameter even if the object itself contains the same request attribute?
For example, in this code:
class ArticleDetailViewAPIView(APIView):
def get_object(self, pk):
object = get_object_or_404(Article, pk=pk)
return object
def get(self, request, pk):
object = self.get_object(pk=pk)
serializer = SerializerArticle(object)
return Response(serializer.data)
For example, if in the get method I put the line:
print("Equal: ", self.request is request)
In the console, the output is TRUE
I have two separate methods:
to load and validate a csv file FileUploadView(APIView) [PUT]
to add new objects to the database based on their uploaded file data
CsvToDatabase [POST]
file upload
class FileUploadView(APIView):
parser_classes = (MultiPartParser, FormParser)
permission_classes = (permissions.AllowAny,)
def put(self, request, format=None):
if 'file' not in request.data:
raise ParseError("Empty content")
f = request.data['file']
filename = f.name
if filename.endswith('.csv'):
file = default_storage.save(filename, f)
r = csv_file_parser(file)
status = 204
else:
status = 406
r = "File format error"
return Response(r, status=status)
create instances
class CsvToDatabase(APIView):
def post(self, request, format=None):
r_data = request.data
...
#some logic
...
serializer = VendorsCsvSerializer(data=data)
try:
serializer.is_valid(raise_exception=True)
serializer.save()
except ValidationError:
return Response({"errors": (serializer.errors,)},
status=status.HTTP_400_BAD_REQUEST)
else:
return Response(request.data, status=status.HTTP_200_OK)
urls.py
urlpatterns = [
path('csv_upload/', FileUploadView.as_view(), name='csv_upload'),
path('from_csv_create/', CsvToDatabase.as_view(), name='csv_vendor_create'),]
At the moment I am simply passing manually to Postman the result of the PUT method of FileUploadView class (Response(r, status=status) get me json ) to POST method of CsvToDatabase class.
Of course, this option is not suitable for a working application. I would like to know if there is a way to call the POST method automatically inside FileUploadView class after the .csv file is processed and json is received.
I know I can transfer a POST method to a class FileUploadView and call the self.POST method inside the PUT. But combining functionality will make testing much more complicated, so I want to use different url. If that's possible, of course.
I am trying to write an RESTful API for my event planning app using Django Rest Framework but I am having some trouble when using views that do not expect the GET HTTP method. I have read through the tutorial on the DRF site. From what I understand after reading through the tutorial and the class based view documentation on the Django site is that if there is a class based view like this (taken from the DRF tutorial)
class SnippetDetail(APIView):
"""
Retrieve, update or delete a snippet instance.
"""
def get_object(self, pk):
try:
return Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet)
return Response(serializer.data)
def put(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet, data=request.DATA)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
snippet = self.get_object(pk)
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
The different methods in the view correspond to the different HTTP Request methods. So if I have www.foo.com/bar it would do two different things based on what request method is sent to that address. So that means that I wouldn't have to specify anything else because the function that is executed is determined based on the method the URL is sent with. Is this correct?
I have this view which I tried to model after the example on the DRF site
class EventDetail(APIView):
"""
Retrieve, update or delete a event instance.
"""
def get_object(self, pk):
try:
return Event.objects.get(pk=pk)
except Event.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
event = self.get_object(pk)
serializer = EventSerializer(event)
return Response(serializer.data)
def post(self, request, format=None):
serializer = EventSerializer(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)
# def put(self, request, pk, format=None):
# event = self.get_object(pk)
# serializer = EventSerializer(event, data=request.DATA)
# if serializer.is_valid():
# serializer.save()
# return Response(serializer.data)
# return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
event = self.get_object(pk)
event.delete()
return Response(status=status.HTTP_204_NO_CONTENT
which maps to these URLs
urlpatterns = patterns('',
# Get event
url(r'^(?P<pk>\d+)/$', views.EventDetail.as_view(),
name='create_events'),
# list all events
url(r'^list/$', views.EventList.as_view(),
name='list_events'),
# url(r'^update$/(?P<pk>\d+)', #update event),
url(r'^create/$', views.EventDetail.as_view(),
name='create_events'),
# delete event
url(r'^delete$/(?P<pk>\d+)',
views.EventDetail.as_view(), name='delete_event'),
)
which I am trying to test using CURL with this command (like suggested here DELETE using CURL with encoded URL)
curl -X DELETE "http://127.0.0.1:8000/events/delete/1"
This will seem to do what it should:
[18/Oct/2014 22:41:27] "DELETE /events/delete/1 HTTP/1.1" 404 2707
But the actual record is not deleted from my database
Is there something here that I am forgetting to do to get these to get this to work properly?
You're being redundant. The HTTP method is already DELETE, so there's no /events/delete in the url. Try this:
curl -X DELETE "http://127.0.0.1:8000/events/1/"
By default, DRF's router creates detailed urls at /event/<pk> and you GET, PUT, POST and DELETE them to retrieve, update, create and delete respectively.
As mentioned by Kevin Stone, the pattern you're using isn't advisable, but if you want to use it, you'll need to fix the typo in your urls for the events/delete/ mapping.
# delete event
url(r'^delete$/(?P<pk>\d+)',
views.EventDetail.as_view(), name='delete_event'),
should be:
# delete event
url(r'^delete/(?P<pk>\d+)',
views.EventDetail.as_view(), name='delete_event'),