I try to operate the create method when generating the data for the first time and the update method if the data already exists after identifying the data of the currently logged-in user. So I wrote the code as follows but it doesn't work the way I want it to.
views.py
class arduinoViewSet (ModelViewSet) :
serializer_class = arduinoSerializers
permission_classes = [IsAuthenticated]
def get_queryset (self) :
queryset = arduino.objects.filter(name=self.request.user)
return queryset
def perform_create (self, serializer) :
serializer.save(name=self.request.user)
def perform_update(self, instance, validated_data):
instance.title = validated_data['name']
instance.save()
serializers.py
class arduinoSerializers (serializers.ModelSerializer) :
name = serializers.CharField(source='name.username', read_only=True)
class Meta :
model = arduino
fields = ['pk', 'name', 'temp', 'humi']
How do I write code to implement it the way I want?
I am trying to make sure the user has permission to view the object they are calling. Here is my permissions class:
from rest_framework import permissions
class IsOwner(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to do actions.
"""
message = 'You must be the owner of this object.'
def has_object_permission(self, request, view, obj):
print("CHECK THAT I GOT HERE")
return obj.user == request.user
And here is my ViewSet:
class TopLevelJobViewSet(ModelViewSet):
permission_classes = (IsOwner,)
serializer_class = TopLevelJobSerializer
queryset = TopLevelJob.objects.all()
filter_backends = [DjangoFilterBackend, RelatedOrderingFilter]
filter_class = TopLevelJobFilter
ordering_fields = '__all__'
Thehas_object_permissions is not being called, anyone visiting the endpoint is able to access all the objects.
Why is this? How do I get has_object_permissions to get called?
This post: Django rest framework ignores has_object_permission talks about it being an issue with not having GenericAPIView. But ModelViewSet has GenericViewSet which has generics.GenericAPIView. Is something overriding this somewhere?
EDIT: My issue was that I was calling list instead of get. How can I only returns objects in list that belong to a user?
This link: https://www.django-rest-framework.org/api-guide/filtering/#filtering-against-the-current-user shows I could implement something like this:
def get_queryset(self):
username = self.kwargs['username']
return Purchase.objects.filter(purchaser__username=username)
This seems to violate DRY if I have to add this to every viewset. Is there a way to turn this into a permissions class that I could always call?
You can implement custom generic filtering [drf-doc]. For example:
class IsOwnerFilter(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
return queryset.objects.filter(user=request.user)
Then you can add this to your ModelViewSet:
class TopLevelJobViewSet(ModelViewSet):
permission_classes = (IsOwner,)
serializer_class = TopLevelJobSerializer
queryset = TopLevelJob.objects.all()
filter_backends = [IsOwnerFilter, DjangoFilterBackend, RelatedOrderingFilter]
filter_class = TopLevelJobFilter
ordering_fields = '__all__'
I am following the tutorial of django Rest Framework. I want to add user-based permission so that only authenticated user can view each user's detail information.
Objective : Anyone can view the UserList, but only owner can view its UserDetail.
models.py
class Meeting(models.Model):
created = models.DateTimeField(auto_now_add=True)
sinceWhen = models.DateTimeField(null=True)
tilWhen = models.DateTimeField(null=True)
owner = models.ForeignKey('auth.User', related_name='meetings', on_delete=models.CASCADE)
#highlighted = models.TextField()
def save(self, *args, **kwargs):
super(Meeting, self).save(*args, **kwargs)
class Meta:
ordering = ('created',)
views.py
from django.contrib.auth.models import User
# User is not created inside models.py
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserListSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly,)
# I added IsOwnerOrReadOnly to make it work, but this is the part where it causes error!
serializers.py
class UserSerializer(serializers.ModelSerializer):
meetings = serializers.PrimaryKeyRelatedField(many=True, queryset=Meeting.objects.all())
#owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
model = User
fields = ('id', 'username', 'meetings',)
class UserListSerializer(serializers.ModelSerializer):
#meetings = serializers.PrimaryKeyRelatedField(many=True, queryset=Meeting.objects.all())
class Meta:
model = User
fields = ('username',)
permissions.py
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
# Any permissions are only allowed to the owner of the meeting
return obj.owner == request.user
I overrode IsOwnerOrReadOnly so that only user can view the details of his/her user detail.
And add this to permission_class in views.py.
Then I got this error :
File "/home/tony/env/lib/python3.6/site-packages/rest_framework/views.py" in check_object_permissions
345. if not permission.has_object_permission(request, self, obj):
File "/home/tony/swpp_hw1/meetings/permissions.py" in has_object_permission
15. return obj.owner == request.user
Exception Type: AttributeError at /users/1/
Exception Value: 'User' object has no attribute 'owner'
I tried to add User class in models.py, but again it causes error...
How can solve this issue?
Try to change it as:
return obj == request.user
as object is user you are trying to access and request.user is current authenticated user.
i have my serializer like this
class PublicacionSerializer(serializers.ModelSerializer):
usuario = UserSerializer2()
likeado = serializers.SerializerMethodField()
class Meta:
model = Publicacion
fields = ('id','usuario', 'likeado')
def get_likeado(self, obj):
user = self.context['request'].user
try:
like = Like.objects.get(publicacion=obj, usuario=user)
return like.id
except Like.DoesNotExist:
return False
so i use that seriaizer in another one:
class EstadoSerializer(serializers.ModelSerializer):
publicacion = PublicacionSerializer(read_only=True)
in views.py i have
class ModificarEstadoMixin(object):
queryset = Estado.objects.all()
serializer_class = EstadoSerializer
class ModificarEstadoDetail(ModificarEstadoMixin, RetrieveUpdateAPIView):
permission_classes = (permissions.IsAuthenticated,
CanModifEstado,)
pass
when i access to the url for know if an user has liked to a post i got a KeyError 'request' in code line
user = self.context['request'].user
anyone knows how to solve it?
When you call that serializer, you have to pass context from view like
MySerializer(context={'request': request})
Please consider these three models:
class Movie(models.Model):
name = models.CharField(max_length=254, unique=True)
language = models.CharField(max_length=14)
synopsis = models.TextField()
class TimeTable(models.Model):
date = models.DateField()
class Show(models.Model):
day = models.ForeignKey(TimeTable)
time = models.TimeField(choices=CHOICE_TIME)
movie = models.ForeignKey(Movie)
class Meta:
unique_together = ('day', 'time')
And each of them has their serializers:
class MovieSerializer(serializers.HyperlinkedModelSerializer):
movie_id = serializers.IntegerField(read_only=True, source="id")
class Meta:
model = Movie
fields = '__all__'
class TimeTableSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = TimeTable
fields = '__all__'
class ShowSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Show
fields = '__all__'
And their routers
router.register(r'movie-list', views.MovieViewSet)
router.register(r'time-table', views.TimeTableViewSet)
router.register(r'show-list', views.ShowViewSet)
Now I would like to get all the TimeTable objects (i.e. date list) by filtering all the Show objects by a specific movie object. This code seems to be the working and getting the list like I want it
m = Movie.objects.get(id=request_id)
TimeTable.objects.filter(show__movie=m).distinct()
But I have no clue how to use this in django rest framework? I tried doing this way (which I am pretty sure its wrong), and I am getting error:
views.py:
class DateListViewSet(viewsets.ModelViewSet, movie_id):
movie = Movie.objects.get(id=movie_id)
queryset = TimeTable.objects.filter(show__movie=movie).distinct()
serializer_class = TimeTableSerializer
urls.py:
router.register(r'date-list/(?P<movie_id>.+)/', views.DateListViewSet)
error:
class DateListViewSet(viewsets.ModelViewSet, movie_id):
NameError: name 'movie_id' is not defined
How can I filter using viewsets in django rest framework? Or if there is any other prefered way than please list it out. Thank you.
ModelViewSet by design assumes that you want to implement a CRUD(create, update, delete)
There is also a ReadOnlyModelViewSet which implements only the GET method to read only endpoints.
For Movie and Show models, a ModelViewSet or ReadOnlyModelViewSet is a good choice whether you want implement CRUD or not.
But a separate ViewSet for a related query of a TimeTable which describes a Movie model's schedule doesn't looks so good.
A better approach would be to put that endpoint to a MovieViewSet directly. DRF provided it by #detail_route and #list_route decorators.
from rest_framework.response import Response
from rest_framework.decorators import detail_route
class MovieViewSet(viewsets.ModelViewset):
queryset = Movie.objects.all()
serializer_class = MovieSerializer
#detail_route()
def date_list(self, request, pk=None):
movie = self.get_object() # retrieve an object by pk provided
schedule = TimeTable.objects.filter(show__movie=movie).distinct()
schedule_json = TimeTableSerializer(schedule, many=True)
return Response(schedule_json.data)
This endpoint will be available by a movie-list/:id/date_list url
Docs about extra routes
Register your route as
router.register(r'date-list', views.DateListViewSet)
now change your viewset as shown below,
class DateListViewSet(viewsets.ModelViewSet):
queryset = TimeTable.objects.all()
serializer_class = TimeTableSerializer
lookup_field = 'movie_id'
def retrieve(self, request, *args, **kwargs):
movie_id = kwargs.get('movie_id', None)
movie = Movie.objects.get(id=movie_id)
self.queryset = TimeTable.objects.filter(show__movie=movie).distinct()
return super(DateListViewSet, self).retrieve(request, *args, **kwargs)
Use a retrieve method, which will match any GET requests to endpoint /date-list/<id>/.
Advantage is that you don't have to explicitly handle the serialization and returning response you make ViewSet to do that hard part. We are only updating the queryset to be serialized and rest framework does the rest.
Since ModelViewSet is implemented as,
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
"""
A viewset that provides default `create()`, `retrieve()`, `update()`,
`partial_update()`, `destroy()` and `list()` actions.
"""
pass
Its implementation includes the following methods (HTTP verb and endpoint on bracket)
list() (GET /date-list/)
create()(POST /date-list/)
retrieve()(GET date-list/<id>/)
update() (PUT /date-list/<id>/)
partial_update() (PATCH, /date-list/<id>/
destroy() (DELETE /date-list/<id>/)
If you want only to implement the retrieve() (GET requests to endpoint date-list/<id>/), you can do this instead of a `ModelViewSet),
from rest_framework import mixins, views
class DateListViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
queryset = TimeTable.objects.all()
serializer_class = TimeTableSerializer
lookup_field = 'movie_id'
def retrieve(self, request, *args, **kwargs):
movie_id = kwargs.get('movie_id', None)
movie = Movie.objects.get(id=movie_id)
self.queryset = TimeTable.objects.filter(show__movie=movie).distinct()
return super(DateListViewSet, self).retrieve(request, *args, **kwargs)
The error
class DateListViewSet(viewsets.ModelViewSet, movie_id): NameError: name 'movie_id' is not defined
happens because movie_id is being passed as parent class of DataListViewSet and not as parameter as you imagined
This example in the documentation should be what you are looking for.
Adjust your URL:
url(r'date-list/(?P<movie_id>.+)/', views.DateListView.as_view())
Adjust your Model:
class Show(models.Model):
day = models.ForeignKey(TimeTable, related_name='show')
time = models.TimeField(choices=CHOICE_TIME)
movie = models.ForeignKey(Movie)
class Meta:
unique_together = ('day', 'time')
Your view would look like this:
class DateListView(generics.ListAPIView):
serializer_class = TimeTableSerializer
def get_queryset(self):
movie = Movie.objects.get(id=self.kwargs['movie_id'])
return TimeTable.objects.filter(show__movie=movie).distinct()
Another way to do it would be:
Adjust your URL:
router.register(r'date-list', views.DateListViewSet)
Adjust your Model:
class Show(models.Model):
day = models.ForeignKey(TimeTable, related_name='show')
time = models.TimeField(choices=CHOICE_TIME)
movie = models.ForeignKey(Movie)
class Meta:
unique_together = ('day', 'time')
Your view would look like this:
class DateListViewSet(viewsets.ModelViewSet):
serializer_class = TimeTableSerializer
queryset = TimeTable.objects.all()
filter_backends = (filters.DjangoFilterBackend,)
filter_fields = ('show__movie_id')
Which will allow you to make requests such as:
http://example.com/api/date-list?show__movie_id=1
See documentation
Ivan Semochkin has the correct answer but the detail decorator is deprecated. It was replaced by the action decorator.
from rest_framework.decorators import action
class MovieViewSet(viewsets.ModelViewset):
#action(detail=True)
def date_list(self, request, pk=None):
movie = self.get_object() # retrieve an object by pk provided
schedule = TimeTable.objects.filter(show__movie=movie).distinct()
schedule_json = TimeTableSerializer(schedule, many=True)
return Response(schedule_json.data)
To improve #all-is-vanity answer, you can explicitly use movie_id as a parameter in the retrieve function since you are overriding the lookup_field class property:
def retrieve(self, request, movie_id=None):
movie = Movie.objects.get(id=movie_id)
self.queryset = TimeTable.objects.filter(show__movie=movie).distinct()
return super(DateListViewSet, self).retrieve(request, *args, **kwargs)
You can also call self.get_object() to get the object:
def retrieve(self, request, movie_id=None):
movie = self.get_object()
self.queryset = TimeTable.objects.filter(show__movie=movie).distinct()
return super(DateListViewSet, self).retrieve(request, *args, **kwargs)