How to accept form data and return it along with the results of some processing in Django RESTFramework? - django

I am trying to understand Django RESTFramework. I am already familiar with Django. I want to create an endpoint that accepts some text data and processes it and returns it to the user along with the results of the processing (in text). I have completed a couple of tutorials on the topic but I still don't understand how it works. Here is an example from a working tutorial project. How can I edit it to achieve my goal? It all looks automagical.
# views.py
from rest_framework import generics
from .models import Snippet
from .serializers import SnippetSerializer
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
​
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
# Here I would like to accept form data and process it before returning it along with the
# results of the processing.
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer

Okay, I think you are a newbie in Django rest and try to understand its flow so I can explain it with an example of a subscription plan.
First, create a model in models.py file
from django.db import models
class SubscriptionPlan(models.Model):
plan_name = models.CharField(max_length=255)
monthly_price = models.IntegerField()
yearly_price = models.IntegerField()
Then create views in a view.py file like
from rest_framework.views import APIView
class SubscriptionCreateAPIView(APIView):
serializer_class = SubscriptionSerializer
def post(self, request):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(
{'message': 'Subscription plan created successfully.',
'data': serializer.data},
status=status.HTTP_201_CREATED
)
and then define a serializer for validation and fields in which we can verify which fields will be included in the request and response object.
serializers.py
from rest_framework import serializers
from .models import SubscriptionPlan
class SubscriptionSerializer(serializers.ModelSerializer):
plan_name = serializers.CharField(max_length=255)
monthly_price = serializers.IntegerField(required=True)
yearly_price = serializers.IntegerField(required=True)
class Meta:
model = SubscriptionPlan
fields = (
'plan_name', 'monthly_price', 'yearly_price',
)
def create(self, validated_data):
return SubscriptionPlan.objects.create(**validated_data)
Now add urls in src/subsciption_module/urls.py
from django.urls import path
from .views import SubscriptionCreateAPIView
app_name = 'subscription_plan'
urlpatterns = [
path('subscription_plan/', SubscriptionCreateAPIView.as_view()),
]
At the end include module url in root urls.py file where your main urls will be located. It will be the same directory which contains settings.py and wsgi.py files.
src/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/', include('src.subscription_plan.urls', namespace='subscription_plan')),
]
That's it. This is how flow works in django rest and you can process data and display data in this way. For more details you can refer django rest docs.

But this is not in any way different from what you do with plain Django. Your SnippetDetail view is just a class-based view, and like any class-based view if you want to do anything specific you override the relevant method. In your case, you probably want to override update() to do your custom logic when receiving a PUT request to update data.

Related

Combine two django models with shared ids into a single viewset

I have two django models in two independent apps, who use the same user ids from an external authentication service:
In app1/models.py:
class App1User(models.Model):
user_id = models.UUIDField(unique=True)
app1_field = models.BooleanField()
In app2/models.py:
class App2User(models.Model):
user_id = models.UUIDField(unique=True)
app2_field = models.BooleanField()
I would like to have a combined viewset that can make it seem like these two are a single model with a list response as follows:
[
{
'user_id': ...,
'app1_field': ...,
'app2_field': ...
},
...
]
If I create or update with this viewset, it should save the data to each of the two models.
To create a combined viewset for App1User and App2User models, you can
follow these steps:
Create a serializer for the combined model. In your main app, create a
serializers.py file and define the following serializer:
from rest_framework import serializers
from app1.models import App1User
from app2.models import App2User
class App1UserSerializer(serializers.ModelSerializer):
class Meta:
model = App1User
fields = ('user_id', 'app1_field')
class App2UserSerializer(serializers.ModelSerializer):
class Meta:
model = App2User
fields = ('user_id', 'app2_field')
class CombinedUserSerializer(serializers.Serializer):
user_id = serializers.UUIDField()
app1_field = serializers.BooleanField()
app2_field = serializers.BooleanField()
def create(self, validated_data):
app1_data = {'user_id': validated_data['user_id'], 'app1_field': validated_data['app1_field']}
app2_data = {'user_id': validated_data['user_id'], 'app2_field': validated_data['app2_field']}
app1_serializer = App1UserSerializer(data=app1_data)
app2_serializer = App2UserSerializer(data=app2_data)
if app1_serializer.is_valid() and app2_serializer.is_valid():
app1_serializer.save()
app2_serializer.save()
return validated_data
def update(self, instance, validated_data):
app1_instance = App1User.objects.get(user_id=validated_data['user_id'])
app2_instance = App2User.objects.get(user_id=validated_data['user_id'])
app1_serializer = App1UserSerializer(app1_instance, data={'app1_field': validated_data['app1_field']})
app2_serializer = App2UserSerializer(app2_instance, data={'app2_field': validated_data['app2_field']})
if app1_serializer.is_valid() and app2_serializer.is_valid():
app1_serializer.save()
app2_serializer.save()
return validated_data
This serializer defines the structure of the combined model and uses App1UserSerializer and App2UserSerializer to handle the fields of each model.
Create a viewset for the combined model. In your main app, create a viewsets.py file and define the following viewset:
from rest_framework import viewsets
from app1.models import App1User
from app2.models import App2User
from .serializers import CombinedUserSerializer
class CombinedUserViewSet(viewsets.ModelViewSet):
serializer_class = CombinedUserSerializer
def get_queryset(self):
app1_users = App1User.objects.all()
app2_users = App2User.objects.all()
combined_users = []
for app1_user in app1_users:
app2_user = app2_users.filter(user_id=app1_user.user_id).first()
if app2_user:
combined_user = {
'user_id': app1_user.user_id,
'app1_field': app1_user.app1_field,
'app2_field': app2_user.app2_field
}
combined_users.append(combined_user)
return combined_users
This viewset uses CombinedUserSerializer to handle the requests and retrieves the data from both App1User and App2User models. The get_queryset() method retrieves all the App1User and App2User instances, matches them based on their user_id, and creates a list of combined users.
Register the viewset in your main app's urls.py file:
# main_app/urls.py
from django.urls import include, path
from rest_framework import routers
from .views import CombinedUserViewSet
router = routers.DefaultRouter()
router.register(r'combined-users', CombinedUserViewSet)
urlpatterns = [
path('', include(router.urls)),
]
This code sets up a default router and registers the CombinedUserViewSet with the endpoint /combined-users/, which can be used to access the combined model data.
Above solution is the idea how it can be done, you can modify according to your needs. If this idea helps, plz mark this as accept solution.

Save external kraken API json in my Django database?

The context
I want to save json data from an external public kraken API in my Django database
Do you have any suggestion of (i) how can i get the json response working accordingly, and (ii) how can i afterwards save it in my django database?
If you have any ideas or tips I would be very grateful.
Thank you!
My view.py
from rest_framework import generics
from .serializers import KrakenSerializer
from kraken.models import Kraken
import requests
class KrakenList(generics.RetrieveAPIView):
serializer_class = KrakenSerializer
queryset = Kraken.objects.all()
def get_object(self):
url = 'https://api.kraken.com/0/public/Time'
r = requests.get(url, headers={'Content-Type':
'application/json'})
kraken = r.json()
return kraken
def seed_kraken():
for i in kraken:
krakenss = Kraken(
unixtime=i["unixtime"],
)
krakenss.save()
My urls.py
from .views import KrakenList
from django.urls import path
app_name = 'kraken'
urlpatterns = [
path('', KrakenList.as_view(), name='listkraken'),
]
My serializers.py
from rest_framework import serializers
from kraken.models import Kraken
class KrakenSerializer(serializers.ModelSerializer):
class Meta:
model = Kraken
fields = ('unixtime',)
My models.py
from django.db import models
class Kraken(models.Model):
unixtime = models.IntegerField(default=0)
This the Django REST framework with wrong empty json answer:
This is how the json unixtime answer should look like in my Django REST framework
The issue
Do you have any suggestion of (i) how can i get the json info working accordingly, and (ii) how can i afterwards save it in my django database?

DjangoREST APIView - different url params for different methods

I got a DjangoREST APIView that supports Read and Create operations. Something like this:
class FirebaseUser(APIView):
...
get(request):
...
post(request):
...
urls.py:
...
path('user/', views.FirebaseUser.as_view()),
...
I need an API that would accept a read request with user id as url param
GET .../api/user/<userId>
But for create operation there's no user ID yet and I need something like this
POST .../api/user/
What is the best way to make my APIView treat url params differently depending on method?
You can define a ModelViewSet like this in your views.py:
from rest_framework import viewsets
class FirebaseUserViewSet(viewsets.ModelViewSet):
queryset = FirebaseUser.objects.all() # or whatever should your queryset be
serializer_class = FirebaseUserSerializer
Then, in your urls.py you register the viewset:
from django.urls import path
from rest_framework import routers
router = routers.DefaultRouter()
router.register(r'user', FirebaseUserViewSet)
urlpatterns = [
path('', include(router.urls)),
]
This will create a few new API endpoints and you'll be able to do all the CRUD operations.
I suggest reading a bit more about ModelViewSets in the official docs.
Also, if you require only certain operations, for example only read and create you may consider extending only certain mixins from rest_framework.mixins (read more here).
So, I came up with using ViewSet instead of APIView.
This is how it looks now:
urls.py
path('user/', views.FirebaseUser.as_view({'post': 'create'})),
path('user/<str:pk>', views.FirebaseUser.as_view({'patch': 'update', 'delete': 'destroy'})),
views.py
class FirebaseUser(ViewSet):
authentication_classes = [...]
permission_classes = [...]
#staticmethod
def create(request):
...
#staticmethod
def update(request: Request, pk=None):
uid = pk
...
#staticmethod
def destroy(request: Request, pk=None):
uid = pk
...

Is there a way I can get comments for only one specific article using Django rest framework?

Now I have tried to use nested routers to solve the issue but still Iwant to be able to create new comments without sending the blog post ID from the request body. I want to be able to get it from request parameters.
So, how can I get this id one and use it to create new comment
http://127.0.0.1:8000/blog-posts/1/comments/
My routes looks like this.
from rest_framework_extensions.routers import NestedRouterMixin
class NestedDefaultRouter(NestedRouterMixin, DefaultRouter):
pass
router = NestedDefaultRouter()
blog_posts_router = router.register('blog-posts', BlogPostViewSet)
blog_posts_router.register(
'comments',
CommentViewSet,
base_name='comments',
parents_query_lookups=['blog_post']
)
urlpatterns = [
path('', include(router.urls))
]
and this is how my Views are looking like
from django.shortcuts import render
from rest_framework.viewsets import ModelViewSet
from .models import BlogPost, Comment
from .serializers import BlogPostSerializer, CommentSerializer
from rest_framework.decorators import action
from django.http import JsonResponse
import json
from rest_framework_extensions.mixins import NestedViewSetMixin
class BlogPostViewSet(NestedViewSetMixin, ModelViewSet):
""" Handles creating, updating, listing and deleting blog posts. """
serializer_class = BlogPostSerializer
queryset = BlogPost.objects.all()
class CommentViewSet(NestedViewSetMixin, ModelViewSet):
""" Handles creating, updating, listing and deleting comments on blog posts. """
serializer_class = CommentSerializer
queryset = Comment.objects.all()
Thank you I'm waiting for your help. I couldn't find it from the documentations
What you can do to achieve that is to inherit the create function of the viewset and use the kwarg parameter to access the id of the post. Then add the post id to the request data before passing it to a serializer and using it to create a new object.
def create(self, request, *args, **kwargs):
post_id = kwargs['parent_lookup_post']
new_data = request.data.copy()
new_data['post'] = post_id
serializer = self.get_serializer(data=new_data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

Set some views which use Django Rest Framework API

I have a very basic Django Rest API.
I don't know how to have some HTML views, in the same django project, which uses API (finally keeping API returning JSON only).
I followed this, but it seems to change the API View (in this case, curl will retrieve HTML and not JSON) :
https://www.django-rest-framework.org/api-guide/renderers/#templatehtmlrenderer
Do I need another Django App ? Another Django project ? Some JS ?
EDIT :
Ok, I've seen it's possible, thanks to rrebase.
But I can't retrieve the JSON with Curl, here my views.py
from rest_framework import generics
from rest_framework.renderers import TemplateHTMLRenderer, JSONRenderer
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.permissions import IsAdminUser
from . import models
from . import serializers
class UserListView(generics.ListAPIView):
renderer_classes = [JSONRenderer, TemplateHTMLRenderer]
template_name = 'profile_list.html'
def get(self, request):
queryset = models.CustomUser.objects.all()
serializer_class = serializers.UserSerializer
return Response({'profiles': queryset})
My models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
def __str__(self):
return self.email
I get an error "Object of type 'CustomUser' is not JSON serializable" when I request the API (http://127.0.0.1:8000/api/v1/users/)
Sorry, it's some different that initial question...
Yes, you can have both. The link you provided to docs has the following:
You can use TemplateHTMLRenderer either to return regular HTML pages using REST framework, or to return both HTML and API responses from a single endpoint.
When making an API request, set the ACCEPT request-header accordingly to html or json.
Finally I made some conditions in my view, and it's working
class UserListView(generics.ListAPIView):
renderer_classes = [JSONRenderer, TemplateHTMLRenderer]
permission_classes = (IsAdminUser,)
def get(self, request):
queryset = CustomUser.objects.all()
if request.accepted_renderer.format == 'html':
data = {'profiles': queryset}
return Response(data, template_name='profile_list.html')
else:
serializer = UserSerializer(queryset, many=True)
data = serializer.data
return Response(data)