I have the following API endpoints already created and working fine:
urls.py:
router = DefaultRouter()
router.register(r'countries', views.CountriesViewSet,
base_name='datapoints')
router.register(r'languages', views.LanguageViewSet,
base_name='records')
Now, I need to create a new endpoint, where I can retrieve the countries associated with one language (let's suppose that one country has just one associated language).
For that purpose, I would create a new endpoint with the following URL pattern:
/myApp/languages/<language_id>/countries/
How could I express that pattern with the DefaultRouter that I'm already using?
You can benefit from DRF routers' extra actions capabilities and especially the #action method decorator:
from rest_framework.viewsets import ModelViewSet
from rest_framework.response import Response
from rest_framework.decorators import action
from rest_framework.generics import get_object_or_404
from .serializers import CountrySerializer
class LanguageViewSet(ModelViewSet):
# ...
#action(detail=True, methods=['get']):
def countries(self, request, pk=None):
language = get_object_or_404(Language, pk=pk)
# Note:
# In the next line, have in mind the
# related_name
# specified in models
# between Country & Language
countries = language.countries.all()
# ___________________^
serializer = CountrySerializer(countries, many=True)
return Response(data=serializer.data)
Having this, your current router specification should stay the same and you will have your desired route already registered.
Related
I'm trying to add an API using the Django REST framework to an existing codebase which uses the Django OAuth2 toolkit. The existing views make use of the fact that the OAuth2 toolkit's backend modifies the behavior of Django's login_required decorator so that it uses OAuth2 authentication. The function-based views look similar to the one in the tutorial example:
from django.contrib.auth.decorators import login_required
from django.http.response import HttpResponse
#login_required()
def secret_page(request, *args, **kwargs):
return HttpResponse('Secret contents!', status=200)
I'm trying to adapt the example given on https://django-oauth-toolkit.readthedocs.io/en/latest/rest-framework/getting_started.html to my situation, but I'm finding it's not an exact correspondence, because the example uses the DRF's ModelViewSet classes whereas my current view uses a (less-integrated) generic class-based view:
from rest_framework import generics
from ..models import Session
from ..serializers import SessionSerializer
class SessionDetail(generics.UpdateAPIView):
queryset = Session.objects.all()
serializer_class = SessionSerializer
where I've set the default permission class to IsAuthenticated in settings.py:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
This should allow a Session object to be updated using PATCH requests, but it appears to return a 403 Forbidden response for use cases similar to the ones for the current views decorated with login_required.
How can I update the SessionDetail view so that it behaves the same as a function view decorated with login_required? I suspect that I need to use the TokenHasReadWriteScope permission class, since this appears to be the only one for which the required_scopes is optional. (The views decorated with login_required don't provide required_scopes either).
Would it be like this:
from rest_framework import generics
from rest_framework.permissions import IsAuthenticated
from oauth2_provider.contrib.rest_framework import OAuth2Authentication, TokenHasReadWriteScope
from ..models import Session
from ..serializers import SessionSerializer
class SessionDetail(generics.UpdateAPIView):
authentication_classes = [OAuth2Authentication]
permission_classes = [TokenHasReadWriteScope]
queryset = Session.objects.all()
serializer_class = SessionSerializer
Also, would I have to update my REST_FRAMEWORK setting with a DEFAULT_AUTHENTICATION_CLASSES like this:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
'DEFAULT_AUTHENTICATION_CLASSES': (
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
)
}
Right now if I run the server and try to make a patch request with the browsable API, I get a 401 Unauthorized error:
Also if I Log in using a username and password that I just created using python manage.py createsuperuser, it goes straight back to this 401 page instead of one with a form to make a PATCH request as I'd expect.
Any ideas on how to fix this?
I am using wagtail as a REST backend for a website. The website is built using react and fetches data via wagtails API v2.
The SPA website needs to be able to show previews of pages in wagtail. My thought was to override serve_preview on the page model and simply seralize the new page as JSON and write it to a cache which could be accessed by my frontend. But im having trouble serializing my page to json. All attempts made feel very "hackish"
I've made several attempts using extentions of wagtails built in serializers but without success:
Atempt 1:
def serve_preview(self, request, mode_name):
from wagtail.api.v2.endpoints import PagesAPIEndpoint
endpoint = PagesAPIEndpoint()
setattr(request, 'wagtailapi_router',
WagtailAPIRouter('wagtailapi_v2'))
endpoint.request = request
endpoint.action = None
endpoint.kwargs = {'slug': self.slug, 'pk': self.pk}
endpoint.lookup_field = 'pk'
serializer = endpoint.get_serializer(self)
Feels very ugly to use router here and set a bunch of attrs
Attempt 2:
def serve_preview(self, request, mode_name):
from wagtail.api.v2.endpoints import PagesAPIEndpoint
fields = PagesAPIEndpoint.get_available_fields(self)
if hasattr(self, 'api_fields'):
fields.extend(self.api_fields)
serializer_class = get_serializer_class(
type(self), fields, meta_fields=[PagesAPIEndpoint.meta_fields], base=PageSerializer)
serializer = serializer_class(self)
Better but i get context issues:
Traceback (most recent call last):
...
File "/usr/local/lib/python3.5/site-packages/wagtail/api/v2/serializers.py", line 92, in to_representation
self.context['view'].seen_types[name] = page.specific_class
KeyError: 'view'
Any toughts?
Solved it by diving through the source code.
First define an empty dummy view:
class DummyView(GenericViewSet):
def __init__(self, *args, **kwargs):
super(DummyView, self).__init__(*args, **kwargs)
# seen_types is a mapping of type name strings (format: "app_label.ModelName")
# to model classes. When an object is serialised in the API, its model
# is added to this mapping. This is used by the Admin API which appends a
# summary of the used types to the response.
self.seen_types = OrderedDict()
Then use this view and set the context of your serializer manually. Im also using the same router as in my api in my context. It has methods which are called by the PageSerializer to resolve some fields. Kinda strange it is so tightly coupled with the wagtail api but at least this works:
def serve_preview(self, request, mode_name):
import starrepublic.api as StarApi
fields = StarApi.PagesAPIEndpoint.get_available_fields(self)
if hasattr(self, 'api_fields'):
fields.extend(self.api_fields)
serializer_class = get_serializer_class(
type(self), fields, meta_fields=[StarApi.PagesAPIEndpoint.meta_fields], base=PageSerializer)
serializer = serializer_class(
self, context={'request': request, 'view': DummyView(), 'router': StarApi.api_router})
Dont forget to import:
from wagtail.api.v2.serializers import get_serializer_class
from rest_framework.viewsets import GenericViewSet
from rest_framework import status
from rest_framework.response import Response
from django.http import JsonResponse
from django.http import HttpResponse
Possibly a non-answer answer, but I too have had challenges in the area of DRF, Wagtail's layering on top of DRF, and the need to cache json results (DRF has no built-in caching as far as I can tell, so that's an additional challenge). In a recent project, I ended up just building a list of dictionaries in a view and sending them back out with HttpResponse(), bypassing DRF and Wagtail API altogether. The code ended up simple, readable, and was easy to cache:
import json
from django.http import HttpResponse
from django.core.cache import cache
data = cache.get('mydata')
if not data:
datalist = []
for foo in bar:
somedata = {}
# Populate somedata, "serializing" fields manually...
datalist.append(somedata)
# Cache for a week.
data = datalist
cache.set('mydata', datalist, 60 * 60 * 24 * 7)
return HttpResponse(json.dumps(data), content_type='application/json')
Not as elegant as using the pre-built REST framework, but sometimes the simpler approach is just more productive...
I would like to create a web api with Python and the Django Rest framework. The tutorials that I have read so far incorporate models and serializers to process and store data. I was wondering if there's a simpler way to process data that is post-ed to my api and then return a JSON response without storing any data.
Currently, this is my urls.py
from django.conf.urls import url
from rest_framework import routers
from core.views import StudentViewSet, UniversityViewSet, TestViewSet
router = routers.DefaultRouter()
router.register(r'students', StudentViewSet)
router.register(r'universities', UniversityViewSet)
router.register(r'other', TestViewSet,"other")
urlpatterns = router.urls
and this is my views.py
from rest_framework import viewsets
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import University, Student
from .serializers import UniversitySerializer, StudentSerializer
import json
from django.http import HttpResponse
class StudentViewSet(viewsets.ModelViewSet):
queryset = Student.objects.all()
serializer_class = StudentSerializer
class UniversityViewSet(viewsets.ModelViewSet):
queryset = University.objects.all()
serializer_class = UniversitySerializer
class TestViewSet(viewsets.ModelViewSet):
def retrieve(self, request, *args, **kwargs):
return Response({'something': 'my custom JSON'})
The first two parts regarding Students and Universities were created after following a tutorial on Django setup. I don't need the functionality that it provides for creating, editing and removing objects. I tried playing around with the TestViewSet which I created.
I am currently stuck trying to receive JSON data that gets posted to the url ending with "other" and processing that JSON before responding with some custom JSON.
Edit
These two links were helpful in addition to the solution provided:
Django REST framework: non-model serializer
http://jsatt.com/blog/abusing-django-rest-framework-part-1-non-model-endpoints/
You can use their generic APIView class (which doesn't have any attachment to Models or Serializers) and then handle the request yourself based on the HTTP request type. For example:
class RetrieveMessages(APIView):
def post(self, request, *args, **kwargs):
posted_data = self.request.data
city = posted_data['city']
return_data = [
{"echo": city}
]
return Response(status=200, data=return_data)
def get....
I have a browsable API:
restaurant_router = DefaultRouter()
restaurant_router.register(r'rooms', RoomsViewSet)
restaurant_router.register(r'printers', PrintersViewSet)
restaurant_router.register(r'shifts', ShiftsViewSet)
urlpatterns = patterns('',
url(r'^$', api_root),
url(r'^restaurant/$',
RestaurantView.as_view(),
name='api_restaurants_restaurant'),
url(r'^restaurant/', include(restaurant_router.urls)),
)
In the api_root I can link to the named route:
#api_view(('GET',))
def api_root(request, format=None):
return Response({
'restaurant': reverse('api_restaurants_restaurant', request=request, format=format),
})
Or I can use the browsable API generated by the DefaultRouter, as explained in the documentation:
The DefaultRouter class we're using also automatically creates the API
root view for us, so we can now delete the api_root method from our
views module.
What do I do if I want to mix ViewSets and normal Views, and show everything in the same API root? The DefaultRouter is only listing the ViewSets it controls.
You can define your views as ViewSets with only one method.
So you can register it in router and it will be in one space with ViewSets.
http://www.django-rest-framework.org/api-guide/viewsets/
Doesn't look like there's a simple way to do that using the DefaultRouter, you'd have to build your own router. If it's any consolation the DefaultRouter's logic for generating the APIRoot view is fairly simple and you could probably easily roll your own, similar router based on the DefaultRouter class (e.g. Modify the ApiRoot class implementation to fetch additional URLs to include, you can do this any number of ways e.g. pass them into your Router's constructor):
https://github.com/tomchristie/django-rest-framework/blob/86470b7813d891890241149928d4679a3d2c92f6/rest_framework/routers.py#L263
From http://www.django-rest-framework.org/api-guide/viewsets/:
A ViewSet class is simply a type of class-based View, that does not provide any method handlers such as .get() or .post(), and instead provides actions such as .list() and .create()
Which means we can extend your ViewSets:
def other_rooms_view(request):
return Response(...)
class RoomsViewSet(ViewSet):
...
def list(self, request):
return other_rooms_view(request)
restaurant_router = DefaultRouter()
restaurant_router.register(r'rooms', RoomsViewSet)
I am trying to create a restful api using class based views in django.
class SomeAPI(MultiDetailView):
def get(self,request,format=None):
#some logic
def post(self,request,format=None):
#some logic
I want to process a get request like www.pathtowebsite.com/api?var1=<someval>&var2=<someval>&var3=<someval>
My post url would be www.pathtowebsite.com/api/unique_token=<token_id>
Basically a get request would generate a unique token based on some parameters and a post request would post using that token.
How would my URL file look like in such a scenario?
P.S I have hardly ever worked with class based views.
First of all: DRF will do a lot of your legwork for you, even generate consistent URLs across your API. If you want to learn how to do things like this in the Django URL dispatcher then you can embed regexes in your URLS:
project/urls.py:
from django.conf.urls import url
from project.app.views import SprocketView
urlpatterns = [
url(r'^api/obj_name/(P<id>[a-f0-9]{24})/$', SprocketView.as_view()),
url(r'^api/obj_name/unique_token=(P<id>[a-f0-9]{24})/$', SprocketView.as_view()),
]
project/app/views.py
from django.shortcuts import get_object_or_404
from django.views.generic import View
from .forms import SprocketForm
from .models import Sprocket
class SprocketView(View):
def get(request, id):
object = get_object_or_404(Sprocket, pk=id)
return render(request, "sprocket.html", {'object':object}
def post(request, id):
object = Sprocket.get_or_create(pk=id)
object = SprocketForm(request.POST, initial=object).save(commit=False)
object.user = request.user
object.save()
return render(request, "sprocket.html", {'object':object, 'saved':True})
That's a lof of functionality that frameworks are supposed to lift from you and I suggest reading about Django CBV. One resource I can wholeheartedly recommend is Two Scoops.