Python Django Rest Post API without storage - python

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....

Related

DRF Post to ViewSet without writing into Model

I've always written data into database when posting via Django Rest Framework endpoints. This time I would like to process received data and send it somewhere else without writing into DB. I switched from ModelViewSet to ViewSet, I can issue GET request OK but receiving Bad Request 400 when I curl or POST via DRF URL. Here's a working minimal code (removed need for authentication etc):
urls.py
from django.urls import path, include
from .views import ContactView
from rest_framework import routers
router = routers.DefaultRouter()
router.register('message', ContactView, basename='message')
urlpatterns = [
path('', include(router.urls)),
]
serializers.py
from rest_framework import serializers
class ContactSerializer(serializers.Serializer):
text = serializers.CharField(max_length=250)
views.py
from rest_framework.response import Response
from .serializers import ContactSerializer
from rest_framework import viewsets
class ContactView(viewsets.ViewSet):
def list(self, request):
return Response('Got it')
def create(self, request):
serializer = ContactSerializer(data=request.data)
if serializer.is_valid():
return Response(serializer.data)
else:
return Response('Invalid')
Would greatly appreciate your suggestions.
You can use GenericAPIView for get or post request and do some logic in validate method, for example do something with signals or edit something. Also u can use #detailt_route or #list_route for any ModelViewSet for write special url for instance, example for edit extra data.
how i did rarely:
in urls.py
urlpatterns = [
url('v1/message', ContactAPIView.as_view(), name='message'),
]
in view.py
class ContactAPIView(GenericAPIView):
serializer_class = ContactSerializer
permission_classes = ()
def post(self, request, *args, **kwargs):
serializer_class = self.get_serializer_class()
serializer = serializer_class(data=request.data, context={'request': request})
serializer.is_valid(raise_exception=True)
data = {"status": True}
return Response(data)
in serializers.py
class ContactSerializer(serializers.Serializer):
text = serializers.TextField()
def validate(self, attrs):
write some logic
you are getting this error because you are using Viewsets which uses DefaultRouter to register routers for you. What it does is that it creates 2 urls for your viewset
message
message/id
so in your current case; i.e. viewset you need to send some dummy number in your url to access this post function (which is not a good approach).
So, you should use any class which parent doesn't include ViewSetMixin (which gives functionality of Router Registration) like in your case inherit your view from these classes
ListModelMixin
CreateModelMixin
GenericAPIView

Django Rest Framework: URL for associated elements

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.

Wagtail: Serializing page model

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...

Django rest framework and python3.5 OrderedDict mutated during iteration

I use Django rest framework and python3.5. Earlier I had another version of python and everything was going right. When I want to get some information from server with URL for example like:
"http://127.0.0.1:8000/api/companies"
I'm getting error:
"OrderedDict mutated during iteration"
.
In views.py I have:
from django.shortcuts import render
from companies.models import Companies
from companies.serializers import CompaniesSerializer
from rest_framework import generics
from rest_framework.response import Response
from rest_framework.renderers import JSONRenderer
from rest_framework import status
class CompaniesList(generics.ListCreateAPIView):
queryset = Companies.objects.all()
serializer_class = CompaniesSerializer
class CompaniesDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Companies.objects.all()
serializer_class = CompaniesSerializer
What should I do to make it working? Where is something mutating the dict?
I don't know why using ListCreateApiView is mutating dict, but I changed class into function like :
#api_view(['GET'])
def CompaniesList(request):
if request.method == 'GET':
companies = Companies.objects.all()
serializer = CompaniesSerializer(companies, many=True)
return Response(serializer.data)
and now it's working...

How to create Django restful URL Patterns?

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.

Categories