When I try to access http://localhost:8000/api/articles/ it works fine.
When I try to access http://localhost:8000/api/articles/1 it works fine.
When I try to access http://localhost:8000/api/articles/create Django thinks that I am trying to perform a GET request ('get': 'retrieve'). What am I doing wrong?
errors invalid literal for int() with base 10: 'create'
urls.py
urlpatterns = [
path('', ArticleViewSet.as_view({'get': 'list'}), name='list'),
path('<pk>/', ArticleViewSet.as_view({'get': 'retrieve'}), name='detail'),
path('create/', ArticleViewSet.as_view({'post': 'create'}) ,name='create'),
]
views.py
class ArticleViewSet(ViewSet):
queryset = Article.objects.all()
def list(self, request):
articles = query_filter(request, ArticleViewSet.queryset)
serializer = ArticleSerializer(articles, many=True)
articles = formatter(serializer.data)
return Response(articles)
def retrieve(self, request, pk=None):
article = get_object_or_404(ArticleViewSet.queryset, pk=pk)
serializer = ArticleSerializer(article, many=False)
article = formatter([serializer.data])
return Response(article)
def create(self, request):
articles = ArticleViewSet.queryset
articles.create(title=request.data['title'], body=request.data['body'])
article = articles.last()
serializer = ArticleSerializer(article, many=False)
article = formatter([serializer.data])
return Response(article)
Also when I switch the positions of retrieve and create in the urlpatterns shown below, I get this error "detail": "Method \"GET\" not allowed.".
urlpatterns = [
path('', ArticleViewSet.as_view({'get': 'list'}), name='list'),
path('create/', ArticleViewSet.as_view({'post': 'create'}), name='create'),
path('<pk>/', ArticleViewSet.as_view({'get': 'retrieve'}), name='detail'),
]
When you are trying with http://localhost:8000/api/articles/create, you are actually making GET request. That is why you are seeing the error("detail": "Method \"GET\" not allowed.".). If you want to make post request, then you need to use api tools like postman. If you use postman, try like this:
And your second url pattern is correct. because if you keep <pk>/ before create/, django interprets that you are calling <pk>/(should be <int:pk>)url with argument create(which is a string), when you are actually calling the create method. And when it tries to convert it to integer(as primary key is an autofield), it throws invalid literal for int() with base 10: 'create' exception.
Create method doesn't not support get action that's why you're getting error {"detail": "Method \"GET\" not allowed."}. Alternative you can try ModelViewset that provides default create(), retrieve(), update(), partial_update(), destroy() and list() actions.
Either you can create get_serailzer() method using that you can get browsable API with JSON and HTML form through that you can do POST action.
class ArticleViewSet(viewsets.ViewSet):
queryset = Article.objects.all()
def get_serializer(self, *args, **kwargs):
return ArticleSerializer(*args, **kwargs)
def create(self, request, *args, **kwargs):
articles = ArticleViewSet.queryset
articles.create(title=request.data['title'], body=request.data['body'])
article = articles.last()
**serializer = self.get_serializer(article, many=False)**
from pyreadline.logger import formatter
article = formatter([serializer.data])
return Response(article)
Browsable API Image
Solution: I was using slashes at the end and that when I was sending request I wasn't adding slashes at the end. So simply I deleted end slashes from django urls
Related
I Created a view for article model and I have the data in my database but when I go on url then it's showing empty array
my below code
view.py
class ArticleView(RetrieveAPIView, RetrieveUpdateAPIView, RetrieveDestroyAPIView):
serializer_class = ArticleSerializer
permission_classes = [AllowAny]
pagination_class = ArticleSizeLimitPagination
def get_queryset(self):
return Article.objects.filter().order_by('-updated_on')
def get_object(self):
try:
return Article.objects.get(id=self.request.data['id'], is_deleted=False)
except Exception:
return None
def perform_create(self, serializer):
return serializer.save(user=self.request.user)
def destroy(self, request, *args, **kwargs):
try:
instance = self.get_object()
instance.is_deleted = True
instance.save()
return Response(data='delete success')
except Http404:
return Response(status=status.HTTP_204_NO_CONTENT)
urls.py
urlpatterns = [
path('', ArticleView.as_view()),
path('<uuid:pk>/', ArticleView.as_view()),
]
When I run the url then
HTTP 200 OK
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
{
"sub_title": "",
"upvote": null,
"title": ""
}
Where did I do wrong? Is there issue in def function for RetrieveAPIView?
You created a view with selected mixins. You will be able to get one instance, update or delete. Is this what you want? Here you have the list of available mixins: DRF docs.
When you open the URL in the browser you probably get the DRF browsable API. It will show you nice forms to interact with your endpoints. If you want to get the article from the endpoint, you should provide URL with id (pk (where pk is primary key)).
Your URLs looks wrong. Please try:
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(
r"articles",
ArticleView
)
urlpatterns = [
url(r"/", include(router.urls)),
]
I would recommend you to use ModelViewSet instead of building view from mixins. It should be easier.
class ArticleView(ModelViewSet):
Please make it works with ModelViewSet (easier solution) and then try to narrow down with a harder solution, by defining mixins.
I am working on a simple investment-tracker app, which should get stock prices from an api and display them nicely for the user. I am having trouble, however to pass the necessary data through to the API call.
views.py
class PortfolioData(APIView):
authentication_classes = []
permission_classes = []
def get(self, request, tickers ,format=None):
# how do I pass the tickers?
stock_data = get_multiple_stock_details(tickers) # returns JSON response
return Response(stock_data)
#login_required
def portfolio(request):
user = request.user
user_portfolio = Portfolio.objects.filter(user=user).first()
return render(request, 'app/portfolio.html', {'portfolio':user_portfolio})
urls.py
urlpatterns = [
path('', views.home, name="homepage"),
path('api/portfolio/data/', views.PortfolioData.as_view(),
name="portfolio-data"),
path('portfolio/', views.portfolio, name='portfolio'),
]
On the frontend I would make an ajax call to my PortfolioData view, in order to be able to process the data on the frontend. My biggest issue is how to pass the needed parameters.
I tried to get the ticker symbols from the frontend using jQuery and then pass that to the endpoint url but I am not sure if this is the best way to go about this.
You can try something like this,,,
urls.py
'''
You should bind two different url with same view. One with dynamic variable and another without it.
'''
urlpatterns = [
path('', views.home, name="homepage"),
path('api/portfolio/', views.PortfolioData.as_view(), name='api_portfolio'), # use unique name for each URL
path('api/portfolio/<tickers>/', views.PortfolioData.as_view(), name='api_portfolio_data'), # use unique name for each URL
path('portfolio/', views.portfolio, name='portfolio'),
]
views.py
class PortfolioData(APIView):
authentication_classes = []
permission_classes = []
def get(self, request, tickers=None ,format=None):
if tickers is None:
# GET /api/portfolio/
print('No tickers parameter in URL')
# implement your logic when tickers is not passed.
return Response(JSON_DATA)
# GET /api/portfolio/tickers1/
# implement your logic when tickers is passed.
stock_data = get_multiple_stock_details(tickers) # returns JSON response
return Response(stock_data)
Now, when make HTTP GET request (Does not matter, it's AJAX call or not).
http://localhost:8000/api/portfolio/
tickers variable will be None this case.
http://localhost:8000/api/portfolio/ticker1/
tickers variable will be ticker1 str this case.
Hope, it helps you.
I have a class based view which needs to accept a Form submission. I am trying to auto-populate some of the form fields using the primary key within the URL path (e.g. /main/video/play/135). The class based view is based on FormView, the code I have makes the pk available in context if I use TemplateView, but that is not particularly good for handling forms.
urls.py
app_name = 'main'
urlpatterns = [
#path('', views.index, name='index'),
path('video/<int:pk>', views.VideoDetailView.as_view(), name='detail'),
path('video/preview/<int:pk>', views.VideoPreview.as_view(), name='preview'),
path('player', views.PlayerListView.as_view(), name='player_list'),
path('video/play/<int:pk>/', views.VideoPlayView.as_view(), name='play'),
path('', views.VideoListView.as_view(), name="video_list")
]
Relevant class from views.py:
class VideoPlayView(FormView):
template_name = "main/video_play.html"
form_class = VideoPlayForm
initial = {}
http_method_names = ['get', 'post']
def get_initial(self, **kwargs):
initial = super().get_initial()
#initial['video'] = pk
initial['watch_date'] = datetime.date.today()
return initial
def get_context_data(self, **kwargs):
kc = kwargs.copy()
context = super().get_context_data(**kwargs)
video = Video.objects.get(context['pk'])
context['video'] = video
context['test'] = kc
self.initial['video'] = video.pk
context['viewers'] = Viewer.objects.all()
context['players'] = Player.objects.filter(ready=True)
return context
def form_valid(self, form):
return HttpResponse("Done")
I get a key error at the line:
video = Video.objects.get(context['pk'])
Viewing the debug info on the error page indicates that the context does not have the pk value stored within it.
If I change the base class to TemplateView with a FormMixin I don't get this key error (but I do have problems POSTing the form data), so I know that the code is basically okay. My understanding is that the FormView class should populate context in the same way as the TemplateView class.
Any idea why FormView behaves this way, and how can I get this working?
If you want pk from the URL, self.kwargs['pk'] will work in all Django generic class-based-views.
In TemplateView, the get() method passes kwargs to the get_context_data method, so you can use context['pk']. The FormView get() method calls get_context_data() without passing any kwargs, so that won't work.
i have tried to delete a single ManuscriptItem instance using Postman to perform my API requests on against the view below:
class ManuscriptViewSet(viewsets.ModelViewSet):
"""Handles creating, reading and updating items."""
authentication_classes = (TokenAuthentication,)
serializer_class = serializers.ManuscriptItemSerializer
permission_classes = (permissions.PostOwnManuscript, IsAuthenticated,)
def perform_create(self, serializer):
"""Sets the user profile to the logged in user."""
serializer.save(author=self.request.user)
def get_queryset(self):
"""
This view should return a list of all the manuscripts
for the currently authenticated user.
"""
user = self.request.user
return models.ManuscriptItem.objects.filter(author=user)
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)
def perform_destroy(self, instance):
instance.delete()
The destroy and perform destroy functions are what I have attempted without success. This is what it returns when i tried:
{
"detail": "Method \"DELETE\" not allowed." }
This is how my URLs are currently registered:
router = DefaultRouter()
router.register('manuscripts', views.ManuscriptViewSet, base_name="manuscripts") # auto basename for models
router.register('manuscriptlibrary', views.ManuscriptLibraryViewSet, base_name="manuscript_library")
router.register('manuscriptsettings', views.ManuscriptSettingsViewSet)
urlpatterns = [
url(r'', include(router.urls))
]
I'm i modifying the ModelViewSet wrong do i need to use another approach because of the nature of ModelViewSet? i expected it to work on Postman when i used an Authorized user to Delete a ManuscriptItem instance. In the docs it said Destroy() method can be used.
Additional information
The URL used is:
http://localhost:8000/manuscripts-api/manuscripts/
The model instance to be deleted from:
class ManuscriptItem(models.Model):
"""Represents a single manuscript's content"""
author = models.ForeignKey('accounts_api.UserProfile', on_delete=models.CASCADE)
title = models.CharField(max_length=255)
content = models.CharField(max_length=99999999)
def __str__(self):
"""Django uses when it needs to convert the object to a string"""
return str(self.id)
The way i have tried sending delete requests on postman with json:
{
"manuscript": 7,
}
Results: Delete Method not allowed
{
"id": 7,
"author": 5,
"title": "niceone",
"content": "niceone"
}
Results: Delete Method not allowed
Additional Questions/Info:
Don't i need to specify the router register with a pk? I tried this but didnt work either:
router.register('manuscripts/{pk}/$', views.ManuscriptViewSet, base_name="manuscript_detail")
Postman says:
Allow →GET, POST, HEAD, OPTIONS
The issue here is that you send DELETE request to the wrong url. Look at the DefaultRouter docs. It generates automatically your urls within your viewset:
Look closely at the DELETE method. It is on the {prefix}/{lookup}/[.format] url pattern. This means that your corresponding router url is manuscripts/<manuscript_id>/, but you try to send DELETE request to manuscripts/ only, which is the above pattern. You see directly from the table that the allowed HTTP methods there are GET and POST only. That's why you receive MethodNotAllowed.
The solution to your problem is not to pass the manuscript_id as a JSON body of the request
{
"manuscript": 7,
}
But to pass it directly to the url:
DELETE http://localhost:8000/manuscripts-api/manuscripts/7/
And you just register your viewset like:
router.register(r'manuscripts', ManuscriptViewSet.as_view(), name='manuscripts')
As you see, DRF generates the urls automatically for you.
from rest_framework.response import Response
from rest_framework import status
def destroy(self, request, *args, **kwargs):
try:
instance = self.get_object()
self.perform_destroy(instance)
except Http404:
pass
return Response(status=status.HTTP_204_NO_CONTENT)
use this and it will work
I use httpie to test my api,when I text
localhost:8000/users/
it show the user list,then i text
localhost:8000/users/jack/
it still show the user list,not the user detail,it's something wrong with my code?
url.py
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^', include('rest_framework.urls',namespace='rest_framework')),
url(r'regist/', Regist.as_view()),
url(r'users/', UserList.as_view()),
url(r'users/(?P<username>[a-zA-Z0-9]+)/$', UserDetail.as_view()),
]
views.py
class UserDetail(generics.ListAPIView):
serializer_class= UserSeriallizer
def get_queryset(self):
username = self.kwargs['username']
user=User.objects.filter(username=username)
return user
class UserList(APIView):
def get(self, request):
users = User.objects.all()
serializer = UserSeriallizer(users, many=True)
return Response(serializer.data)
Problem in your urls, you need to close r'users/$, because Django can't go further users/ without $
And why you use ListAPIView for retrieving single object?
You need RetrieveAPIView or RetrieveUpdateAPIView if you want change the data. And change your view like so:
class UserDetail(RetrieveAPIView):
lookup_field = 'username'
queryset = User.objects.all()
You don't need get_queryset at all
About mixins