DRF: Simple Switch for a choice field in model via router - django

I want to build an APIView that can turn on power in a store so to say ...
Can I do it using a router?
model:
class Store(models.Model):
C = [(0,0), (1,1), (2,2), (3,3)]
name = models.IntegerField("name", max_length=60)
power_state = models.PositiveIntegerField("current state", default=0, choices=C)
user = models.ForeignKey(User, on_delete=models.CASCADE)
view:
class OnOff(APIView):
def patch(self, request):
store = Store.objects.get(pk = request.user.id)
return Response("switched")
I am new to DRF and I do not know if I need a serializer here. The interface I see looks like this:
while I was hoping for a simple dropdown between 0 and ... 3 in this case. Also how would the router have to be registered? Right now I put a path in the urls.py:
path('test/', views.OnOff.as_view(), name = "on-off"),
which means it will not be listed under 127.0.0.1:8000/api/ which would be nice.
I tried using (but 404):
router = routers.DefaultRouter()
...
router.register(r'onoff', views.OnOff, basename = "onoff")
urlpatterns = [
path("", views.StoreView.as_view(), name = 'index'),
url('^api/', include(router.urls)),
... ]

The DRF router generates REST style urls and this API does not appear to be REST-ful in the standard sense. ie. list all the objects and detail single objects. docs.
To add the /api/ using the path method:
path('api/test/', views.OnOff.as_view(), name = "on-off"),
if you want to just quickly get you API working via the built in interface, add a post method.
class OnOff(APIView):
def post(self, request):
store = Store.objects.get(pk = request.user.id)
... sudo code to save the on-off value sent from the interface ...
store.power_state = request.data.get('on-off', 0)
store.save()
return Response("switched")
To use the DRF router, a Viewset is required.
The router.url propertyu is list of urls that can be included in the main urls.py
urls.py
from rest_framework.routers import DefaultRouter
import .views
app_name = 'on-off'
router = DefaultRouter()
router.register(r'onoff', views.OnOffViewset)
urlpatterns = router.urls
views.py
class OnOffViewset(viewsets.ViewSet):
def update(self, request, pk=None):
# in this case the pk is a user.id
store = Store.objects.get(pk = request.user.id)
... sudo code to save the on-off value sent from the interface ...
store.power_state = request.data.get('on-off', 0)
store.save()
return Response({'status': store.power_state})
Projects main urls.py
path('api/', include('onoff.urls', namespace='on-off')),

Related

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

Django Rest Framework: Get Data by Field

i want to learn django.
My first learning project is a django + rest framework api.
i want to get a destination by its airport code. not by pk / id
currently when i call /api/destination/1 i get the destination with id 1
i want something like /api/destination/PMI or /api/destination/mallorca and as response i only want to get the destination with code PMI or with name mallorca.
is this possible?
my files:
modely.py
class Destination(models.Model):
name = models.CharField(max_length=50)
code = models.CharField(max_length=3)
country = models.CharField(max_length=50)
image = models.FileField()
serializers.py
class DestinationSerializer(serializers.ModelSerializer):
class Meta:
model = Destination
fields = ("id", "name", "code", "country", "image")
urls.py
router = DefaultRouter()
router.register(r'destination', DestinationViewSet)
views.py
class DestinationViewSet(viewsets.ModelViewSet):
serializer_class = DestinationSerializer
queryset = Destination.objects.all()
I would recommend picking one or the other to have as the identifier. For this example, I'm going to use the airport code.
In urls.py, you'll want to switch from a router to a urlpattern - remember to register this in your project.urls file!
from django.urls import path
urlpatterns = [path('destination/<code>/', DestinationViewSet.as_view())]
In your view, you'll want to switch to just a normal view and call the get() method.
from destinations.api.serializers import DestinationSerializer
from destinations.models import Destination
from rest_framework import views
from rest_framework.response import Response
class DestinationView(views.APIView):
def get(self, request, code):
destination = Destination.objects.filter(code=code)
if destination:
serializer = DestinationSerializer(destination, many=True)
return Response(status=200, data=serializer.data)
return Response(status=400, data={'Destination Not Found'})
Everything else should work the way it is!
Use an action decorator to create a custom get method
#action(detail=False, methods=['GET'], url_path='destination/(?P<pmi>\w{0,500})')
def custom_ge(self, request, pmi):
#Function implementation in here

Validate pk as int in drf viewset retrieve url

Code looks as follows:
class UserViewSet(ViewSet):
# ... Many other actions
def list(self):
# list implementation
def retrieve(self, request, pk):
# manual pk int validation
router = DefaultRouter()
router.register(r"users", UserViewSet, basename="users")
urlpatterns = router.urls
Right now pk is not validated as int therefore a request to db is made, which I want to avoid. Is there any way I can add that type of validation in urls?
I can achieve that without using router like this:
urlpatterns = [
path('users/<int:pk>/', UserViewSet.as_view({'get': 'retrieve'}),
# many other actions have to be added seperately
]
But I have many actions in my viewset and all of them have to be added separately. Is there a cleaner way to do so or a package?
Use lookup_value_regex attribute as,
class UserViewSet(ViewSet):
lookup_value_regex = '\d+'
...

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

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.

Django: routing same model but different category field to separate URLs

I have the following model and url routes. There is one Post model that I want to route to different URLs based on the category. Is there a way to do this by passing in extra information in app/urls.py?
In app/posts/models.py
class Post(models.Model):
author = ...
title = ...
body = ...
category = models.CharField()
In app/urls.py
urlpatterns = patterns(
'',
(r'^blog/', include('posts.urls'), {'category': 'blog'}),
(r'^school/', include('posts.urls'), {'category': 'school'}),
)
My understanding is that the extra info from app/urls.py is included in each url route in app/posts/urls.py. Is there a way to use that information? What can I put in place of the exclamation points below?
In app/posts/urls.py
from models import Post
queryset = Post.objects.order_by('-pub_date')
urlpatterns = patterns(
'django.views.generic.list_detail',
url(r'^$', 'object_list',
{'queryset': queryset.filter(category=!!!!!!)}
name="postRoot"),
url(r'^(?P<slug>[-\w]+)/$', 'object_detail',
{'queryset': queryset.filter(category=!!!!!!)},
name="postDetail")
)
Thanks, joe
I am not aware of a way to use the URL parameters the way you have indicated. If anyone knows better, do correct me.
I faced a similar situation some time ago and made do with a thin wrapper over the list_detail view.
# views.py
from django.views.generic.list_detail import object_list
def object_list_wrapper(*args, **kwargs):
category = kwargs.pop('category')
queryset = Post.objects.filter(category = category)
kwargs['queryset'] = queryset
return object_list(*args, **kwargs)
#urls.py
urlpatterns = patterns('myapp.views',
url(r'^$', 'object_list_wrapper', {}, name="postRoot"),
...