Django Restful API App - python

I made a new API app to serve all the API requests for my web app. So I want to first do something simple and return a DataFrame object in the form of JSON. I am also using the Django Rest Framework Library.
My urls.py
from django.conf.urls import url, include
from rest_framework import routers
from api import views
router = routers.SimpleRouter()
router.register(r'test', views.UserViewSet)
urlpatterns = [
url(r'^', include(router.urls))
]
My views.py:
class UserViewSet(APIView):
renderer_classes = (JSONRenderer, )
def get(self):
queryset = NAV.objects.filter(fund__account_class=0, transmission=3).values('valuation_period_end_date').annotate(
total_nav=Sum(F('outstanding_shares_par') * F('nav'))).order_by('valuation_period_end_date')
df = read_frame(queryset, coerce_float=True)
df.loc[:, 'valuation_period_end_date'] = pd.to_datetime(df.valuation_period_end_date)
df.loc[:, 'timestamp'] = df.valuation_period_end_date.astype(np.int64) // 10 ** 6
df.loc[:, 'total_nav'] = df.total_nav
return JsonResponse(df)
But I get the error AssertionError:base_nameargument not specified, and could not automatically determine the name from the viewset, as it does not have a.querysetattribute. I am new to Django Restful API Framework and was wondering if I am doing this right?

It looks like you need to specify a basename when registering the view on your router. Try this (Change 'User' to something like the name of the model you are querying:
router.register(r'test', views.UserViewSet, 'User')
For more info on this see here:
http://www.django-rest-framework.org/api-guide/routers/

Related

How to use django cache with REST Framework?

I'm fairly new to Django and try to get a working backend. So far, my backend is working fine, but I don't know, how to respond with custom data:
What I have:
Angular Frontend
Django (REST) backend
A working cache
What I want to achieve:
I want to call an endpoint (lets say backend/weather). When I call this endpoint, I want to check my cache for a weather value. If there is one, I want to send it to the client. If there is none, I want to make an API call, store the retrieved value in my cache and send the value to the client. How can I do this. This is what I have so far:
views.py
from rest_framework import viewsets
from .models import Weather
from .serializers import WeatherSerializer
from django.core.cache import cache
from urllib.request import urlopen
import json
class WeatherView(APIView):
def get(self):
if(cache.get('temp') == None):
url = 'https://api.openweathermap.org/data/2.5/forecast?q=' + '...';
serialized_data = urlopen(url).read()
data = json.loads(serialized_data)
print(data)
cache.set('weatherdata', data, 3600)
else:
data = cache.get('weatherdata')
serializer_class = WeatherSerializer(data)
responseJSON = JSONRenderer().render(serializer_class.data)
return Response(responseJSON)
serializers.py
from rest_framework import serializers
from .models import Weather
class WeatherSerializer(serializers.Serializer):
temp = serializers.DecimalField(max_digits=4, decimal_places=2)
iconName = serializers.CharField()
urls.py
from django.urls import path, include
from . import views
from rest_framework import routers
from django.conf import settings
from django.conf.urls.static import static
router = routers.DefaultRouter()
urlpatterns = [
path('', include(router.urls)),
path('weather', views.WeatherView)
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
I get the data from the API. My cache setting and getting are also working. But I don't know how to respond with custom data. I'm getting the following error when calling backend/weather:
TypeError at /weather
__init__() takes 1 positional argument but 2 were given
Request Method: GET
Request URL: http://127.0.0.1:8000/weather
Django Version: 2.2.7
Exception Type: TypeError
Exception Value:
__init__() takes 1 positional argument but 2 were given
Thank you in advance!

how to reverse the URL of a ViewSet's custom action in django restframework

I have defined a custom action for a ViewSet
from rest_framework import viewsets
class UserViewSet(viewsets.ModelViewSet):
#action(methods=['get'], detail=False, permission_classes=[permissions.AllowAny])
def gender(self, request):
....
And the viewset is registered to url in the conventional way
from django.conf.urls import url, include
from rest_framework import routers
from api import views
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet, base_name='myuser')
urlpatterns = [
url(r'^', include(router.urls)),
]
The URL /api/users/gender/ works. But I don't know how to get it using reverse in unit test. (I can surely hard code this URL, but it'll be nice to get it from code)
According to the django documentation, the following code should work
reverse('admin:app_list', kwargs={'app_label': 'auth'})
# '/admin/auth/'
But I tried the following and they don't work
reverse('myuser-list', kwargs={'app_label':'gender'})
# errors out
reverse('myuser-list', args=('gender',))
# '/api/users.gender'
In the django-restframework documentation, there is a function called reverse_action. However, my attempts didn't work
from api.views import UserViewSet
a = UserViewSet()
a.reverse_action('gender') # error out
from django.http import HttpRequest
req = HttpRequest()
req.method = 'GET'
a.reverse_action('gender', request=req) # still error out
What is the proper way to reverse the URL of that action?
You can use reverse just add to viewset's basename action:
reverse('myuser-gender')
See related part of docs.
You can print all reversible url names of a given router at startup with the get_urls() method
In urls.py after the last item is registered to the router = DefaultRouter() variable, you can add:
#router = routers.DefaultRouter()
#router.register(r'users', UserViewSet)
#...
import pprint
pprint.pprint(router.get_urls())
The next time your app is initialized it will print to stdout something like
[<RegexURLPattern user-list ^users/$>,
<RegexURLPattern user-list ^users\.(?P<format>[a-z0-9]+)/?$>,
<RegexURLPattern user-detail ^users/(?P<pk>[^/.]+)/$>,
<RegexURLPattern user-detail ^users/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$>,
#...
]
where 'user-list' and 'user-detail' are the names that can be given to reverse(...)
Based on DRF Doc.
from rest_framework import routers
router = routers.DefaultRouter()
view = UserViewSet()
view.basename = router.get_default_basename(UserViewSet)
view.request = None
or you can set request if you want.
view.request = req
In the end, you can get a reverse action URL and use it.
url = view.reverse_action('gender', args=[])
If you want to use UserViewset().reverse_action() specifically, you can do this by assigning a basename and request = None to your ViewSet:
from rest_framework import viewsets
class UserViewSet(viewsets.ModelViewSet):
basename = 'user'
request = None
#action(methods=['get'], detail=False, permission_classes=[permissions.AllowAny])
def gender(self, request):
....
and in urls.py:
router.register('user', UserViewSet, basename=UserViewSet.basename)
Then calling url = UserViewset().reverse_action('gender') or url = UserViewset().reverse_action(UserViewSet().gender.url_name) will return the correct url.
Edit:
Above method only works when calling reverse_action() only once because the ViewSetMixin.as_view() method overrides basename on instantiation. This can be solved by adding a custom subclass of GenericViewSet (or ModelViewSet if preferred) like so:
from django.utils.decorators import classonlymethod
from rest_framework.viewsets import GenericViewSet
class ReversibleViewSet(GenericViewSet):
basename = None
request = None
#classonlymethod
def as_view(cls, actions=None, **initkwargs):
basename = cls.basename
view = super().as_view(actions, **initkwargs)
cls.basename = basename
return view
and subclassing this class for the specific ViewSets, setting basename as desired.
The answer is reverse('myuser-gender').
Note! But remember that DRF will replace _ in the action name with -. That means if the action name is my_pretty_action in the reverse you should use reverse(app-my-pretty-action).
According to your code you would like to get the list of users you use
reverse("myuser-list")
details
reverse("myuser-detail",kwargs={"id":1})
In here you might notice i use list or detail by adding it to the reverse so if you add appname you can just input it thats because we are using viewsets

Import a class into the urls.py in Django

In the last few weeks I've switched from developing an application that processes a simple XML file, then writes its contents to an Oracle DB (cx_Oracle) and outputs in HTML, to using a Django framework. The Django framework switch wasn't necessary, but since I have an opportunity to develop something by using Django, I thought why not as it is a new area for me and can't damage my CV.
Anyway, I'm having issues with knowing what to write in my urls.py file when importing a Class from the views.py file. Here are the current contents:
urls.py
from myproj.views import Pymat_program
pymatProgram = Pymat_program()
urlpatterns = (
url(r'^pymat/$', pymatProgram.abc),
)
views.py
class Pymat_program:
def abc(self, request):
test = "<html><body>Random text.</body></html>"
return HttpResponse(test)
I've tried various permutations of using request, not using request, and also how the class is called in the url tuple, all to no avail. When I use a definition outside of the class (i.e. not in any class), then this is correctly displayed in HTML.
You don't want to wrap your program in a class. (In general, in Python you should treat modules like, say, Java might treat classes with static members only.)
There are two approaches, really:
Function-based views
urls.py
from myproj.views import abc_view
urlpatterns = (
url(r'^pymat/$', abc_view),
)
views.py
def abc_view(request):
test = "<html><body>Random text.</body></html>"
return HttpResponse(test)
Class-based views
urls.py
from myproj.views import AbcView
urlpatterns = (
url(r'^pymat/$', AbcView.as_view()),
)
views.py
from django.views.generic import View
class AbcView(View):
def get(self, request, *args, **kwargs):
test = "<html><body>Random text.</body></html>"
return HttpResponse(test)
Since you are using class based views in Django, so you have to call this class in URL like:
from myproj.views import Pymat_program
pymatProgram = Pymat_program()
urlpatterns = (
url(r'^pymat/$', pymatProgram.as_view()),
)
and then use get or post methods name in class like:
class Pymat_program:
model = ModelName
def post(self, request, *args, **kwargs):
....
2nd way is to use function based views:
urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^articles/2003/$', views.view_name),
]
views.py
from django.http import HttpResponse
import datetime
def view_name(request):
now = datetime.datetime.now()
html = "<html><body>It is now %s.</body></html>" % now
return HttpResponse(html)
for more details look into class based views docs

Custom comments for viewset in api documentation

I have django rest framework api application and generate documentation for it. I use api view with viewset and I have custom method. In urls I use routers. For generate documentation I use DRF Docs.
My view:
class UserViewSet(viewsets.ModelViewSet):
"""View for user object.
"""
...
#detail_route(methods=["post"])
def set_password(self, request, pk=None):
"""View to set new password
"""
...
urls:
from django.conf.urls import url, include
from rest_framework.routers import DefaultRouter
from accounts.views import UserViewSet
__all__ = ["accounts_urlpatterns"]
accounts_router = DefaultRouter()
accounts_router.register(r'users', UserViewSet)
accounts_urlpatterns = [
url(r'^accounts/', include(accounts_router.urls))
]
My api documentation result:
I want custom comments for set_password method and display different fields because I use for this method other serializer. How can I do that?

How to work with Database over SOAP in Django?

Currently I have a Django project, let's call it Backend. There is a folder api and there I have this resource declared using Django-Tastypie:
from django.contrib.auth.models import User
from tastypie.resources import ModelResource, ALL
from tastypie.authentication import BasicAuthentication
from tastypie.authorization import DjangoAuthorization
from tastypie.cache import SimpleCache
from tastypie.throttle import CacheDBThrottle
class UserResource(ModelResource):
class Meta:
queryset = User.objects.all()
resource_name = 'user'
excludes = ['email', 'password', 'is_staff', 'is_superuser']
authentication = BasicAuthentication()
authorization = DjangoAuthorization()
cache = SimpleCache()
throttle = CacheDBThrottle()
filtering = {
'username' : ALL,
'date_joined' : ['gt','lt','gte','lte','range']
}
With proper routing rules in place, if I access an URL like http://127.0.0.1:8000/api/v1/user?format=json, I am supposed to get back some info on users in json format, but from my local database. I do not want to use any local database, except maybe for testing with some dummy data. I want such a get request to result in a SOAP request to a certain remote server with my logged in session's username and password.
I already have a standalone Python application, where I can perform a SOAP request and get a SOAP response back using SUDS and pre-downloaded WSDL files. Now I want to include this functionality in my Dhango project in such a way, that I change the settings in settings.py in the project, but I do not have to modify the applications inside the project.
You can define your own Custom managers that use your standalone Python application.
From tastypie documentation. Adding this code to your urls.py should work:
from tastypie.api import Api
from my_app.api.resources import UserResource
v1_api = Api(api_name='v1')
v1_api.register(UserResource())
#old urlpatterns here
urlpatterns += patterns('',
(r'^api/', include(v1_api.urls)),
)

Categories

Resources