I'm using using Django Rest Framework's HyperlinkedModelSerializer, ModelViewset, and DefaultRouter, and end up with good URLs like this: http://localhost:8000/api/users/1.
I would like to, given a user id, find the fully-qualified url for that user without hard-coding anything. Given 53, I want http://localhost:8000/api/users/1, and the host should change when I move to production.
# from urls.py
router = routers.DefaultRouter()
router.register(r'users', shared_views.UserViewSet)
# from models.py
class UserViewSet(viewsets.ModelViewSet):
'''
endpoint for viewing/editing users
'''
queryset = User.objects.all().order_by('-date_joined')
serializer_class = UserSerializer
# from serializers.py
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'email', 'groups')
How can I do this? I tried: reverse(UserViewSet.as_view({'get': 'retrieve'}), args=[request.user.id]) but got this error:
Reverse for 'shared.views.UserViewSet' with arguments '(1,)' and keyword arguments '{}' not found. 0 pattern(s) tried: []
As #Anush said, I can use the serializer to get the URL. The request needs to be passed in a particular way as a keyword argument (see below):
class OwnedViewSet(viewsets.ModelViewSet):
''' ModelViewSets that use hyperlinked model serializers
can inherit this to automatically
set `owner` = current user.
'''
def create(self, request, *args, **kwargs):
serialized_owner = UserSerializer(request.user, context={'request': request})
request.data['owner'] = serialized_owner.data['url']
return super(OwnedViewSet, self).create(request, *args, **kwargs)
Related
I have a Django project as following code in model
class Report(models.Model):
created_by_user=models.ForeignKey(User,on_delete=models.CASCADE)
following code in serializer
class ReportSerializer(serializers.ModelSerializer):
class Meta:
model=Report
fields='__all__'
and following code in view
class ReportCreateView(APIView):
def post(self,request, *args, **kwargs):
received_data=ReportSerializer(data=request.data)
if received_data.is_valid():
received_data.save()
return Response(received_data.data, status=status.HTTP_201_CREATED)
return Response(received_data.errors,status.HTTP_400_BAD_REQUEST)
when I send a post request by postman and send username and password in Authorization tab
it error:
{
"created_by_user": [
"This field is required."
]
}
but if I type username or password incorrect it will be
{
"detail": "Invalid username/password."
}
can everybody help me?
Your serializer has no idea about the currently logged-in user.You to pass it as context from request. user or request.
I personally prefer CurrentUserDefault to be used in the serializer. To make it work we need to pass the request as context because CurrentUserDefault picks user from context request. We need to update our views and serializer code as follows
Views file: Add request as context context
class ReportCreateView(APIView):
def post(self,request, *args, **kwargs):
received_data=ReportSerializer(data=request.data, context = {"request": request})
if received_data.is_valid():
received_data.save()
return Response(received_data.data, status=status.HTTP_201_CREATED)
return Response(received_data.errors,status.HTTP_400_BAD_REQUEST)
serializer.py: Update your serializer to auto-populate created_by_user
class ReportSerializer(serializers.ModelSerializer):
created_by_user = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model=Report
fields='__all__'
It will solve your user field required issue.
"created_by_user": ["This field is required."]
Now coming to your next part of the issue that is related to incorrect passwords.
By default, APIView picks the default authentication class from settings. Inside projects settings.py, we mostly write these lines while using DRF and they serve as default authentication for APIView:
From settings.py
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticated",
],
# Authentication settings
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.SessionAuthentication",
],
...
}
Inside APIView you can have a look at default permission_classes, and authentication_classes
From inside APIView:
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
that is when you type an invalid password:
"detail": "Invalid username/password."
Provide the correct username and password to your APIView from postman so that it gets the requested logged-in user for auto-populates at DB level.
you don't perform any process on your user data and just need to save the request user, due to that I think you don't need a serializer field for it and it's better to get your current user in view. also if you need more fields to serialize, you can make created_by_user read_only true and set its value on your view.
for example, if you have report name and report desc field in your model:
class Report(models.Model):
created_by_user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=256)
desc = models.TextField()
perform your serializer like this :
class ReportSerializer(serializers.ModelSerializer):
class Meta:
model = Report
fields = '__all__'
extra_kwargs = {
'created_by_user': {'read_only': True},
}
then set created_by_user value in your view:
class ReportCreateView(APIView):
def post(self, request, *args, **kwargs):
request.data['created_by_user'] = request.user # just need add this line
received_data = ReportSerializer(data=request.data)
if received_data.is_valid():
received_data.save()
return Response(received_data.data, status=status.HTTP_201_CREATED)
return Response(received_data.errors, status.HTTP_400_BAD_REQUEST)
So I’m trying to query my ‘Profile’ table by the relation attribute ‘owner’, which links to another table ‘User’. However, when I attempt to query by this attribute, I get the following error:
'AssertionError: Expected view UserProfile to be called with a URL keyword argument named "pk". Fix your URL conf, or set the .lookup_field attribute on the view correctly.'
To query the table I used:
Profile.objects.filter(owner__username = username)
Models.py:
class User(models.Model):
username = CharField(max_length = 80)
class Profile(models.Model):
owner = models.OneToOneField('User',
related_name = 'profile', on_delete = models.CASCADE)
Views.py:
class UserProfile(generics.GenericAPIView,
mixins.RetrieveModelMixin):
def get_queryset(self):
username = self.kwargs['username']
return Profile.objects.filter(owner__username=username)
serializer_class = UserProfileSerializer
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
Urls.py:
urlpatterns = [
path('user/<str:username>/profile/', views.UserProfile.as_view()),
]
Why am I getting this error, how would I fix it? Any help would be much appreciated.
Thanks, Grae.
set lookup_field and lookup_url_kwarg attributes in your view as,
from rest_framework import generics
class UserProfile(generics.RetrieveAPIView):
lookup_field = 'owner__username'
lookup_url_kwarg = 'username'
serializer_class = UserProfileSerializer
queryset = Profile.objects.all()
I'm in the process of debugging my custom permissions class and returning a value of False for my has_object_permission() function, but my I'm still able to access my API (GET request), via Restframework's API browser without authenticating and I can't understand why. Any help would be greatly appreciated. Please see code below. for whatever reasons, it appears that my has_object_permission function is not executing. Please Help
urls.py
router = BulkRouter()
router.register(r'api1', SimpleViewSet1)
urlpatterns = [
url(r'^test/', include(router.urls, namespace='api1')),
]
views.py
class SimpleViewSet1(generics.BulkModelViewSet):
queryset = Barcode.objects.all()
permission_classes = (MyUserPermission,)
serializer_class = SimpleSerializer1
def get_queryset(self):
user = User.objects.get(pk=2)
return Barcode.objects.filter(owner = user)
def get_object(self):
obj = get_object_or_404(self.get_queryset())
self.check_object_permissions(self.request, obj)
return obj
permissions.py
class MyUserPermission(BasePermission):
def has_permission(self, request, view):
return True
def has_object_permission(self, request, view, obj):
return False
serializer.py
class SimpleSerializer1(BulkSerializerMixin, # only required in DRF3
ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
class Meta(object):
model = Barcode
# only required in DRF3
list_serializer_class = BulkListSerializer
fields = ('barcode_number', 'barcode_type', 'owner')
models.py
#python_2_unicode_compatible
class Barcode(models.Model):
owner = models.ForeignKey('auth.User', related_name = 'barcodes')
barcode_number = models.CharField(max_length=200)
barcode_type = models.CharField(max_length=200)
def __str__(self):
return self.barcode_number
Django Rest API Guide says:
Also note that the generic views will only check the object-level permissions for views that retrieve a single model instance. If you require object-level filtering of list views, you'll need to filter the queryset separately. See the filtering documentation for more details.
rest_framework.generics.BulkModelViewSet, as it's name suggests,does bulk operations. It means that you have to use object-level filtering as proposed in the docs.
You should be looking especially under this section. Pay close attention to the example and make use of the code. You should also read about the DjangoModelPermissions to understand how does the example in the link above works.
I can't get #detail_route to work as I think it is supposed to.
I have two API calls I want to handle:
/movie/
/movie/highlight
I am trying to use #detail_route to pickup the /movie/highlight url in the viewset.
My urls.py looks like this:
from django.conf.urls import url, include
from rest_framework import routers
from api import views
router = routers.DefaultRouter()
router.register(r'users', views.UsersViewSet)
router.register(r'groups', views.GroupViewSet)
router.register(r'movie', views.MovieViewSet)
My views.py looks like this:
rom django.contrib.auth.models import User, Group
from movies.models import Movie
from api.serializers import UserSerializer, GroupSerializer, MovieSerializer
from rest_framework.response import Response
from rest_framework import permissions
from rest_framework import renderers
from rest_framework import viewsets
from rest_framework.decorators import detail_route
# Code from DRF quickstart tutorial
class UsersViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all().order_by('-date_joined')
serializer_class = UserSerializer
# Code from DRF quickstart tutorial
class GroupViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Group.objects.all()
serializer_class = GroupSerializer
# MY CODE
class MovieViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Movie.objects.all().order_by('-title')
serializer_class = MovieSerializer
#detail_route(renderer_classes=(renderers.StaticHTMLRenderer,))
def highlight(self, request, *args, **kwargs):
snippet = "Highlight"
return Response(snippet)
The serializers.py looks like this:
from django.contrib.auth.models import User, Group
from movies.models import *
from rest_framework import serializers
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'email', 'groups')
class GroupSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Group
fields = ('url', 'name')
# My code...
class MovieSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Movie
fields = ('title', 'details')
If I try the /movie/ url API call it works as expected. If I try the /movie/highlight/ call I get a 404 error.
I am a newbie to DRF so suspect I am doing something very silly here but can't find out what from the various docs and tutorials I have scanned.
You need to use #list_route decorator instead of #detail_route decorator.
This will generate the url movie/highlight/.
Using #list_route decorated method generates the url of type {prefix}/{methodname}/ whereas detail_route decorated method generateds url of type {prefix}/{lookup}/{methodname}/. Here methodname is name of your method and lookup is the lookup value on which lookup is performed to get the object for detail view.
class MovieViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Movie.objects.all().order_by('-title')
serializer_class = MovieSerializer
# use list_route decorator
#list_route(renderer_classes=(renderers.StaticHTMLRenderer,))
def highlight(self, request, *args, **kwargs):
snippet = "Highlight"
return Response(snippet)
If you register your URLs using routers.DefaultRouter(), it will generate the URL /movie/pk/highlight instead of /movie/highlight. If you want a custom URL other than the pre-generated one, use URL mapping.
I've extensively researched this fairly common issue, but none of the fixes worked for me. I'm building a Django project in REST framework and want to use hyperlinked relations. The User can have many Cars and Routes, which are independent. A Route is a collection of Positions.
These are my serializers:
class CarSerializer(serializers.HyperlinkedModelSerializer):
user = serializers.Field(source='user.username')
class Meta:
model = Car
fields = ('url', 'make', 'year', 'car_model', 'user')
class PositionSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Position
fields = ('url', 'drive_route', 'timestamp', 'latitude', 'longitude', 'altitude','speed','heading', 'accuracy', 'altitude_accuracy')
class DrivingRouteSerializer(serializers.HyperlinkedModelSerializer):
position = serializers.HyperlinkedRelatedField(view_name='position', many=True)
user = serializers.Field(source='user.username')
class Meta:
model = DrivingRoute
fields = ('url', 'id', 'route', 'position', 'user')
class UserSerializer(serializers.HyperlinkedModelSerializer):
routes = serializers.HyperlinkedRelatedField(view_name='routes-detail', many=True)
car = serializers.HyperlinkedRelatedField(view_name='car-detail', many=True)
class Meta:
model = User
fields = ('url', 'username', 'routes', 'car')
And here are the views:
class CarViewSet(viewsets.ModelViewSet):
queryset = Car.objects.all()
serializer_class = CarSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def pre_save(self, obj):
obj.user = self.request.user
class DrivingRouteViewSet(viewsets.ModelViewSet):
queryset = DrivingRoute.objects.all()
serializer_class = DrivingRouteSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def pre_save(self, obj):
obj.user = self.request.user
class PositionViewSet(viewsets.ModelViewSet):
queryset = Position.objects.all()
serializer_class = PositionSerializer
class UserViewSet(viewsets.ReadOnlyModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
And, for what it's worth, the URLs. I am using the Default Router, just as in the Django REST Framwork tutorial.
router = DefaultRouter()
router.register(r'car', views.CarViewSet)
router.register(r'routes', views.DrivingRouteViewSet)
router.register(r'position', views.PositionViewSet)
router.register(r'users', views.UserViewSet)
Overall, this is almost exactly the same as in the tutorial. Loading the 'routes', 'car', and 'position' URLS works fine, but the 'users' URL throws the error "Could not resolve URL for hyperlinked relationship using view name 'routes-detail'."
The view_name should typically be [route]-detail for routers, where [route] is the name of the model you registered the ViewSet under.
In your case, the view_name should be position-detail, not just position. You are also using routes-detail instead of drivingroutes-detail, which is using the long name because your model is DrivingRoute and not Route. You can override this by setting a base_name (third parameter) when using register on the router.
router = DefaultRouter()
router.register(r'car', views.CarViewSet)
router.register(r'routes', views.DrivingRouteViewSet, "routes")
router.register(r'position', views.PositionViewSet)
router.register(r'users', views.UserViewSet)
You have the following views:
car-list
car-detail
drivingroute-list
drivingroute-detail
position-list
position-detail
user-list
user-detail
If we take for example the following route:
router.register(r'car', views.CarViewSet)
It means that it is accessible under http://yourapi.com/car/ for car-list and under http://yourapi.com/car/1/ for car-detail (where 1 is the id).
The view names get built out of the class name minus suffix ViewSet plus list or detail.
Change your code according to these rules and please try again.
Cheers!