How to use path parameter in GenericViewSet, Mixin CBS? - python

I'm new in DRF, Django.
I want to make get API with path parameter which name is period.
(ex. localhost:8000/api/ranks/{period})
views.py
class RankingView(mixins.ListModelMixin,
GenericViewSet):
queryset = Record.objects.all()
serializer_class = RecordSerializer
#action(methods=['GET'], detail=False, url_path='<str:period>', url_name='<str: period>')
def get_rank(self, request, period):
#logic
return
urls.py
router.register(r'api/ranks', RankingView)
urlpatterns = [
path('', include(router.urls)),
]
But, its not working. It might be easy to solve but I couldn't find answer.

try to add specific path like this:
urlpatterns = [
path('', include(router.urls)),
path('api/ranks/<str:period>', views.RankingView.as_view({
'get': 'get_rank'
})),
]

I solved this problem with python regular expression.
class RankingView(mixins.ListModelMixin,
GenericViewSet):
queryset = Record.objects.all()
serializer_class = RecordSerializer
#action(methods=['GET'], detail=False, url_path='(?P<period>\w+)')
def get_rank(self, request, period):
#logic
return
It makes easy to manage urls, and also can easily use path parameter with mixins, viewSet.

Related

Implement REST API with Django

This is my Django model:
class M(models.Model):
a = models.IntegerField()
b = models.IntegerField()
This is the serializer:
class MSerializer(ModelSerializer):
class Meta:
model = M
fields = ['a', 'b']
I would like to be able to implement these REST APIs:
127.0.0.1:8000/m/ (GET list of all elements, POST new element)
127.0.0.1:8000/m/:id/ (GET details of element with id id)
127.0.0.1:8000/n/:a/m/ (GET all elements with a specific a field)
So far this is the view and urls that I implemented:
class MViewSet(ModelViewSet):
queryset = M.objects.all()
serializer_class = MSerializer
router = DefaultRouter()
router.register(r'm', MViewSet)
urlpatterns = [
path('', include(router.urls)),
]
However, in this way the third use case is not working. How can I modify my code to make the third case work? I'd prefer to use as few lines of code as possible (i.e., I would like to use some Django built-in functionalities).
Since it look like you want the 3rd endpoint on another root (possibly another app name n), I'll implement it is a standalone API view, and not as an action on a viewset (although both options are possible)
class FilteredMListView(ListAPIView):
serializer_class = MSerializer
def get_queryset(self):
return M.objects.filter(a=self.kwargs["a"])
Then you register it to the router using:
urlpatterns = [
path("n/<str:a>/m/", FilteredMListView.as_view())
]
For your 3rd case, I would use a ListAPIView, overriding the get_queryset method to filter by the passed value for a. The idea is that when get_queryset method is invoked, and with as many other filters you'd like to implement, the condition for a is always present. Since the value for a will be in the url, it is mandatory, and you always have in in the view's kwargs. Would look like this:
urls.py
router = DefaultRouter()
router.register(r'm', MViewSet)
urlpatterns = [
path('', include(router.urls)),
path('<a>/m', AValuesListApiView.as_view()
]
views.py
class AValuesListApiView(generics.ListAPIView):
queryset = M.objects.all()
serializer_class = MSerializer
def get_queryset(self):
return super().get_queryset().filter(score=self.kwargs["score"])

Django REST Framework: change the url to the .list() method only in a ModelViewSet

I'm making a registration api, and as I followed the docs, it recommended the use of ViewSets, so i used a ModelViewSet, now if i POST to the url '*/api/register/', I'll make a registration if provided valid fields (using .create() method), which is perfect for my case just what i needed.
but when I want to list all the users for the admin, the request should be to the same url with a GET method, which is a bit weird to access '*/api/register/' to see the users...
so I wanted to change the url for the .list() method to be something like '*/api/users/', but I can't find how to do that.
here is what i'm doing until now:
apis.py:
class RegisterApi(ModelViewSet):
serializer_class = UserSerializer
queryset = User.objects.filter(is_active=True)
def get_permissions(self):
if self.action == 'create':
return [permissions.AllowAny()]
else:
return [permissions.IsAdminUser()]
def create(self, request, *args, **kwargs):
response = super().create(request, *args, **kwargs)
if response:
response.data = dict(status=True, code=1)
return response
urls.py:
api_router = SimpleRouter()
api_router.register('register', apis.RegisterApi, basename='users')
extra_urls = [path('login/', apis.LoginApi.as_view()), ]
urlpatterns = [
# views urls
path('', views.Home.as_view(), name='home'),
path('dashboard/', views.Dashboard.as_view(), name='dashboard'),
path('login/', views.LoginView.as_view(), name='login'),
path('register/', views.RegistrationView.as_view(), name='register'),
# api urls
path('api/', include(extra_urls + api_router.urls)),
]
Any hints about this?
You should rename the api to /users. Then the http method POST is for creating (or registering) a new User, the GET method is for listing Users.
Read more about restful api naming convention, for example: https://restfulapi.net/resource-naming/

multiple parameter in get request in django

url.py
urlpatterns = [
path('api_doc/', schema_view),
path('admin/', admin.site.urls),
# regex for swagger creation
path(r'^request.GET.get(‘tag’)&request.GET.get(‘order_by’)', views.QuestionList.as_view()),
# path(r'^?tag={tag}&order_by={name}', views.QuestionList.as_view()),
]
This is mu url.py file and i a trying to input "tag" and "order_by" in url in swagger but it's not working? i have tried above url options
In the last one "?" is not reconized by url.
The query string [wiki] is not part of the path. So you can not capture or check this in the urlpatterns. Your path looks like:
path('', views.QuestionList.as_view())
In your view, you can then filter the queryset accordingly:
from django.views.generic.list import ListView
from app.models import Question
class QuestionList(ListView):
model = Question
def get_queryset(self, *args, **kwargs):
qs = super().get_queryset(*args, **kwargs)
if 'tag' in self.request.GET:
qs = qs.filter(tag__id=self.request.GET['tag'])
if 'order_by' in self.request.GET:
qs = qs.order_by(self.request.GET['order_by'])
return qs
That being said, you are introducing a security vulnerability by allowing arbitrary ordening.
It furthermore might be worth to take a look at django-filter [GitHub] to do filtering based on a QueryDict in a more declarative way.

One object only in GET method in Django REST Framework

I have a list of all my objects when I use get method by api/movies in my api, and this is ok. I want also to get only one, specyfic object when use get method by api/movies/1 but now I still have a list of all my objects... What to change in my MoviesView or in urls?
My views.py:
class MoviesView(APIView):
def get(self, request):
movies = Movie.objects.all()
serializer = MovieSerializer(movies, many=True)
return Response(serializer.data)
My appurls.py:
urlpatterns = [
url('movies', MoviesView.as_view(), name="MoviesView"),
]
And my project urls.py:
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include("api.urls")),
]
When I use routers everythig crushes... Could you help me?
You can simply use viewsets.ModelViewSet that natively implements list and retrieve.
You declare something like router.register('movies', my_views.MoviesViewSet) in you urls.py and
class MoviesViewSet(viewsets.ModelViewSet):
queryset = Movie.objects.all()
serializer_class = MovieSerializer
permission_classes = [IsAuthenticated, ]
def get_queryset(self):
return self.queryset
def get_object(self):
movie_id = self.kwargs['pk']
return self.get_queryset().filter(id=movie_id)
def retrieve(self, request, *args, **kwargs):
try:
instance = self.get_object()
except (Movie.DoesNotExist, KeyError):
return Response({"error": "Requested Movie does not exist"}, status=status.HTTP_404_NOT_FOUND)
serializer = self.get_serializer(instance)
return Response(serializer.data)
def list(self, request, *args, **kwargs):
queryset = self.get_queryset()
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
This approach implies that you declare a Serializer, just like:
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = '__all__'
Django simply maps HOST/movies/ to list (multiple objects) and HOST/movies/PK/ to retrieve method (one single object).
Docs:
https://www.django-rest-framework.org/api-guide/viewsets/#modelviewset
https://www.django-rest-framework.org/api-guide/serializers/#modelserializer
Hope it helps.
BR.
Eduardo
I would suggest you if you want to retrieve just 1 element to use a Generic View, i.e RetrieveAPIView
It would give you all you need for getting 1 element.
from rest_framework import generics
class MoviesView(generics.RetrieveAPIView):
queryset = Movie.objects.all()
serializer_class = MovieSerializer
but you need also to change urls.py
url(r'movies/(?P<pk>[0-9]+)/$', MoviesView.as_view(), name="MoviesView"),
When you make a GET request to "api/movies/1", the url is matched to the "api/movies" path (read more in the docs), and the MoviesView's get method is called. And your get() implementation just fetches all the movies (movies = Movie.objects.all()), serializes and returns them - that's why you get the entire list.
If you want to retrieve one specific object, you need to somehow specify which object you have in mind, using its primary key (in your case, id).
1. You have to define a separate path: movies/<int:pk>/ (btw, which Django version are you using? url has been deprecated, use path instead!)
2. You have to define a detail view to handle this new case, and pass it to the path function as the second argument.
This general problem can really be solved in many ways, and depending on your app you may want to use a ViewSet instead of views. Then you don't have to define paths (urls) separately - you can use a router. You can't use routers with your view, because router needs a viewset class as its argument.
If you provide more details, I could try to suggest something more specific.
My appurls.py:
use path method
urlpatterns = [
path('movies', MoviesView.as_view(), name="MoviesView"),]
Maybe it works
Start by adding a format keyword argument to both of the views, like so
def snippet_list(request, format=None):
and
def snippet_detail(request, pk, format=None):
Now update the snippets/urls.py file slightly, to append a set of format_suffix_patterns in addition to the existing URLs
from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
urlpatterns = [
path('snippets/', views.snippet_list),
path('snippets/<int:pk>', views.snippet_detail),
]
urlpatterns = format_suffix_patterns(urlpatterns)

TypeError: delete() missing 1 required positional argument: 'pk' django

i have a view in my django app which looks like following
class DeployedContractsList(APIView):
def get_Contract(self, address):
contracts = DeployedContracts.objects.all()
if address:
contracts = contracts.filter(
Q(deployed_contract_address__iexact=address)
)
return contracts
def get(self, request, email, format=None):
service_providers = self.get_queryset(email)
serializer = DeployedContractsSerializer(service_providers, many=True)
return Response(data=serializer.data)
def delete(self, request, pk, format=None):
item = self.get_Contract(address=pk)
item.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
and i have configured my app urls in following way
router = DefaultRouter()
urlpatterns = [
url(r'^(?P<email>[\w.%+-]+#[A-Za-z0-9.-]+\.[A-Za-z]{2,4})/$', views.DeployedContractsList.as_view()),
url(r'^(?P<pk>)/$', views.DeployedContractsList.as_view()),
url(r'^', views.DeployedContractsList.as_view())
]
and project's urls.py i have following configurations
urlpatterns = [
path('admin/', admin.site.urls),
url(r'^deployedcontracts', include('deployedcontracts.urls')),
]
but upon making a delete request by http://127.0.0.1:8000/deployedcontracts/0xe9114368611e9f20cf46b76aa33319fc0ce0b585/ i am getting following error
despite the fact i have attached pk in request url i.e 0xe9114368611e9f20cf46b76aa33319fc0ce0b585 as it can be seen in my url. Any help is appreciated.
You forgot to add regular expression to url patern:
url(r'^(?P<pk>[\w-]+)/$', views.DeployedContractsList.as_view()),

Categories