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!
Related
I have a view for creating blog posts which redirects the user to a form to fill the blog post content like this:
from django.views import View
from app.models import BlogPost
class CreateBlogPost(View):
def post():
new_post = BlogPost.objects.create()
return redirect(reverse('post_edit'), args=[new_post.id])
class EditBlogPost(View):
...
And my urls are the following:
from django.urls import path
from app.views import CreateBlogPost
urlpatterns = [
path('post', CreateBlogPost.as_view(), name='post_create'),
path('post/<int:pk>/edit', EditBlogPost.as_view(), name='post_edit')
]
I would like to: test the CreateBlogPost by asserting that it redirects to post/<some_id>/edit.
I've tried: using the method assertRedirects from django's SimpleTestCase, but couldn't find a way to make this work.
I'm also using pytest-django, in case that's relevant.
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
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/
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
I am new to django and I am trying to understand class views.
In urls.py (main) I have:
from django.conf.urls import url, include
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^', include('webapp.urls')),
]
in webapp folder I have:
urls.py (webapp):
from django.conf.urls import url
from webapp.views import Firstapp
urlpatterns = [
url(r'^whatever$', Firstapp.as_view()),
]
views.py (webapp):
from django.shortcuts import render
from django.views import View
from django.http import HttpResponse
class Firstapp(View):
def something(self):
return HttpResponse('Yes it works!')
As I have said, I am trying to use class views and I would appreciate if you could help me understand why class returns 405 error. Thank you. CMD returns 0 problems.
Because you are subclassing View and the only method you define is called something.
View expects you to define a method for each valid http verb. (GET, POST, HEAD etc). Since Firstapp has no such method, View.dispatch will return a response with http status 405 (Method not allowed).
dispatch(request, *args, **kwargs)
The view part of the view – the
method that accepts a request argument plus arguments, and returns a HTTP response.
The default implementation will inspect the HTTP method and attempt to delegate to a method that matches the HTTP method; a GET will be delegated to get(), a POST to post(), and so on.
By default, a HEAD request will be delegated to get(). If you need to handle HEAD requests in a different way than GET, you can override the head() method. See Supporting other HTTP methods for an example.
To fix this, change your something method:
def get(self, request):
return HttpResponse('Yes it works!')