How to make an API call to another one of my views? - django

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.

Related

Django DRF, how to properly register custom URL patterns with DRF actions

Background
I have a ModelViewSet that defines several custom actions. I am using the default router in my urls.py to register the URLs. Right now, my views use the default created routes like ^images/$, ^images/{pk}/$.
In order for users to use the API using resource names with which they are familiar, I have adapted the viewset to accept multiple parameters from the URL, using the MultipleFieldLookupMixin strategy described in the docs to allow for the pattern images/{registry_host}/{repository_name}/{tag_name}.
I've created the get_object method in my viewset like so:
class ImageViewSet(viewsets.ModelViewSet):
...
def get_object(self):
special_lookup_kwargs = ['registry_host', 'repository_name', 'tag_name']
if all(arg in self.kwargs for arg in special_lookup_kwargs):
# detected the custom URL pattern; return the specified object
return Image.objects.from_special_lookup(**self.kwargs)
else: # must have received pk instead; delegate to superclass
return super().get_object()
I've also added a new URL path pattern for this:
urls.py
router = routers.DefaultRouter()
router.register(r'images', views.ImageViewSet)
# register other viewsets
...
urlpatterns = [
...,
path('images/<str:registry_host>/<path:repository_name>/<str:tag_name>', views.ImageViewSet.as_view({'get': 'retrieve',})),
path('', include(router.urls)),
]
Problem
The above all works as intended, however, I also have some extra actions in this model viewset:
#action(detail=True, methods=['GET'])
def bases(self, request, pk=None):
...
#action(detail=True, methods=['GET'])
def another_action(...):
... # and so on
With the default patterns registered by DRF, I could go to images/{pk}/<action> (like images/{pk}/bases) to trigger the extra action methods. However I cannot do this for images/{registry_host}/{repository_name}/{tag_name}/<action>. This is somewhat expected because I never registered any such URL and there's no reasonable way DRF could know about this.
I'm guessing that I can add all these paths manually with an appropriate arguments to path(...) but I'm not sure what that would be.
urlpatterns = [
...,
path('.../myaction', ???)
]
Questions
As a primary question, how do I add the actions for my URL?
However, I would like to avoid having to add new URLS every time a new #action() is added to the view. Ideally, I would like these to be registered automatically under the default path images/{pk}/<action> as well as images/{registry_host}/{repository_name}/{tag_name}/<action>.
As a secondary question, what is the appropriate way to achieve this automatically? My guess is perhaps a custom router. However, it's unclear to me how I would implement adding these additional routes for all extra (detail) actions.
Using django/drf 3.2
Another approach I see is to (ab)use url_path with kwargs like:
class ImageViewSet(viewsets.ModelViewSet):
#action(detail=False, methods=['GET'], url_path='<str:registry_host>/<path:repository_name>)/<str:tag_name>/bases')
def bases_special_lookup(...):
# detail=False to remove <pk>
#action(detail=True, methods=['GET'])
def bases(...):
...
#action(detail=False, methods=['GET'], url_path='<str:registry_host>/<path:repository_name>)/<str:tag_name>')
def retrieve_special_lookup(...):
# detail=False to remove <pk>
def retrieve(...):
...
This will create these urls:
images/<str:registry_host>/<path:repository_name>/<str:tag_name>/bases
images/<pk>/bases
images/<str:registry_host>/<path:repository_name>/<str:tag_name>
images/<pk>
Theres probably a better way of doing this, but you could try something like this:
router = routers.DefaultRouter()
router.register(r'images', views.ImageViewSet)
# register other viewsets
...
urlpatterns = [
...,
path('images/<str:registry_host>/<path:repository_name>/<str:tag_name>', views.ImageViewSet.as_view({'get': 'retrieve',})),
path('images/<str:registry_host>/<path:repository_name>/<str:tag_name>/<str:action>', views.ImageViewSet.as_view({'get': 'retrieve',})),
path('', include(router.urls)),
]
and then in your viewset:
class ImageViewSet(viewsets.ModelViewSet):
...
def get_object(self):
special_lookup_kwargs = ['registry_host', 'repository_name', 'tag_name']
if all(arg in self.kwargs for arg in special_lookup_kwargs):
action = getattr(self, self.kwargs.pop('action', ''), None)
if action:
#not sure what your action is returning so you may not want to return this
return action(self.request)
# detected the custom URL pattern; return the specified object
return Image.objects.from_special_lookup(**self.kwargs)
else: # must have received pk instead; delegate to superclass
return super().get_object()

Why am getting empty array in fetiching data using axios in react js and in urls?

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.

Django-rest-framework read url data

urls.py
from rest_framework import routers
router = routers.DefaultRouter()
router.register('fan/<str:name>', FanView)
urlpatterns = [
path(r'', include(router.urls)),
]
view.py
class FanView(viewsets.ModelViewSet):
queryset = Fan.objects.all()
serializer_class = FanSerializer
def get_queryset(self):
queryset = Fan.objects.all()
print(self.request.query_params.get('name', None))
return queryset
Hi i am trying to send name in djnago-rest-framework url.
And reading the same in my viewSet.
But, i am always getting None.
I don't wants to send data like fan/?name=foo
Please have a look
Is there any way to achive that ?
What you are trying to access is not in query_params. This is a url parameter and is stored in self.kwargs.lookup_field. You can find here how to access the url parameter.

Validate pk as int in drf viewset retrieve url

Code looks as follows:
class UserViewSet(ViewSet):
# ... Many other actions
def list(self):
# list implementation
def retrieve(self, request, pk):
# manual pk int validation
router = DefaultRouter()
router.register(r"users", UserViewSet, basename="users")
urlpatterns = router.urls
Right now pk is not validated as int therefore a request to db is made, which I want to avoid. Is there any way I can add that type of validation in urls?
I can achieve that without using router like this:
urlpatterns = [
path('users/<int:pk>/', UserViewSet.as_view({'get': 'retrieve'}),
# many other actions have to be added seperately
]
But I have many actions in my viewset and all of them have to be added separately. Is there a cleaner way to do so or a package?
Use lookup_value_regex attribute as,
class UserViewSet(ViewSet):
lookup_value_regex = '\d+'
...

Django Rest Framework - POST request invokes GET request (localhost)

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