I'm setting up an API Endpoint using Django Rest Framework viewsets and routers, and I'm trying to get the url to accept two values: first, to filter objects by a user_id, and then by the object's id. (In my case, the objects are from a model called Request.) For example, mysite.com/api/requests/1A/ would return all Request objects for user 1A, and mysite.com/api/requests/1A/23/ would return Request object with pk=23 for user 1A.
Right in my urls.py:
# urls.py
from django.conf.urls import url, include
from rest_framework import routers
from . import views
router = routers.DefaultRouter()
router.register(r'requests/(?P<user_id>.+?)(?=\/)', viewset=views.RequestsByUser, base_name='request')
urlpatterns = [
url(r'^', include(router.urls)),
]
# views.py
class RequestsByUser(viewsets.ModelViewSet):
serializer_class = RequestsSerializer
def get_queryset(self):
u_id = self.kwargs['user_id']
return Request.objects.filter(user_id=u_id)
This works well for listing all Request objects when the url is only passed the user_id. But when I try to also pass the object's id example: mysite.com/api/requests/1A/23/, rest framework returns an empty result.
So the url will properly filter by user_id, but won't properly serve the detailed view of an object when given its primary key (object_id). (It looks like the proper page for a detailed view, except it's missing the data for the object.)
Django debugging says that the following four url patterns are in my URLConf:
^api/ ^ ^test/(?P<user_id>.+?)(?=\/)/$ [name='request-list']
^api/ ^ ^test/(?P<user_id>.+?)(?=\/)\.(?P<format>[a-z0-9]+)/?$ [name='request-list']
^api/ ^ ^test/(?P<user_id>.+?)(?=\/)/(?P<pk>[^/.]+)/$ [name='request-detail']
^api/ ^ ^test/(?P<user_id>.+?)(?=\/)/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='request-detail']
I've read through the Django Rest Framework docs for url routing several times, and I feel like I must be missing something. My understanding is the router will automatically create url routing for detailed views based on primary keys, and it looks like it's doing that in the URL Conf. Is my regular expression configured wrong, or maybe something else?
Try something like this:
settings.py
INSTALLED_APPS = [
...
'rest_framework',
'django_filters',
...
]
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',),
}
serializers.py
import django_filters.rest_framework
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ('id', 'MyField', 'MyFavoriteField','OtherField')
class MyModelListView(generics.ListAPIView):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
filter_backends = (django_filters.rest_framework.DjangoFilterBackend,)
filter_fields = ('id', 'MyField','MyFavoriteField',)
urls.py:
path('service_name/', MyModelListView.as_view(), name="something_name"),
GET:
http://localhost:8070/services/service_name/?id=123&MyField=My%20Field%20Value
More Info:
https://www.django-rest-framework.org/api-guide/filtering/#filtering
Related
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
...
urls.py
from rest_framework import routers
router = routers.DefaultRouter()
router.register('fan/<str:name>', FanView)
urlpatterns = [
path(r'', include(router.urls)),
]
view.py
class FanView(viewsets.ModelViewSet):
queryset = Fan.objects.all()
serializer_class = FanSerializer
def get_queryset(self):
queryset = Fan.objects.all()
print(self.request.query_params.get('name', None))
return queryset
Hi i am trying to send name in djnago-rest-framework url.
And reading the same in my viewSet.
But, i am always getting None.
I don't wants to send data like fan/?name=foo
Please have a look
Is there any way to achive that ?
What you are trying to access is not in query_params. This is a url parameter and is stored in self.kwargs.lookup_field. You can find here how to access the url parameter.
I have the following urls.py:
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from location import views
router = DefaultRouter()
router.register('city', views.CityViewSet, 'city')
app_name = 'location'
urlpatterns = [
path('', include(router.urls)),
]
when hitting the url /api/location/city/25/ I get all the details for that object instance (with the id of 25) as expected.
My question how do I grab the id number in my viewset?
I know how to do it with just regular query parameters /?id=25, but can't seem to figure out how to do it when it's part of the URL.
URL captures are available as the kwargs attribute (which is a dict) of the viewset instance. So from inside any viewset method, you can access them via self.kwargs.
In this case, as you're retrieving the instance (GET on detail), you can get the pk (primary key) via:
class CityViewSet(ModelViewSet):
...
def retrieve(self, request, *args, **kwargs):
pk = self.kwargs['pk']
Note that, I've assumed your lookup_url_kwarg is pk (the default); if you have something different you need to access by that key name as you can imagine.
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+'
...
I try to create a view, that will accept POST requests and create new instances of my model(see bottom of post). I follow this tutorial. The problem is that when I access URL associated with view, which inherits from CreateAPIView I dont see a form in html representation of API for creation of new instances and I also see that it accepts GET requests, not POST as it mentioned in documentation.
Page looks like this
My views.py
from django.shortcuts import render
from rest_framework.generics import ListAPIView, CreateAPIView
from datingapp.models import Profile
from .serializers import ProfileSerializer, ProfileCreateSerializer
class ProfilesAPIView(ListAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
class ProfileCreateAPIView(CreateAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileCreateSerializer
My urls.py
from django.conf.urls import url
from django.contrib import admin
from datingapp.views import ProfilesAPIView, ProfileCreateAPIView
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'api/profiles/', ProfilesAPIView.as_view(), name='list'),
url(r'api/profiles/create/$', ProfileCreateAPIView.as_view(), name='create')
]
My serializers.py
from rest_framework.serializers import ModelSerializer
from datingapp.models import Profile
class ProfileSerializer(ModelSerializer):
class Meta:
model = Profile
fields = [
'name',
'age',
'heigth'
'location',
]
class ProfileCreateSerializer(ModelSerializer):
class Meta:
model = Profile
fields = [
'name',
'age',
'heigth'
'location',
]
In my settings.py I have crispy_forms installed.
What am I doing wrong ?
UPD: here is what I want to achieve
As you see there is a form and it accepts only POST and also says that GET is not allowed
The problem is in your router. The first pattern matches both api/profiles/ and api/profiles/create/ so the second one will never be evaluated. You are seeing the ProfilesAPIView instead of the create view.
url(r'api/profiles/', ProfilesAPIView.as_view(), name='list'),
url(r'api/profiles/create/$', ProfileCreateAPIView.as_view(), name='create')
To fix it, either swap the order of the urls, or add a $ to the end of the first pattern. r'api/profiles/$'
I was following a tutorial and had a similar problem. Probably I was not following the same version of Django Rest Framework and they had changes.
But I solved this problem doing this.
class AssetBundleList(generics.ListAPIView):
to
class AssetBundleList(generics.ListCreateAPIView):
Hope this helps someone.