I am working on DRF (Django) API
I have a simple ModelViewSet. Delete request is working good for this:
class BuildingObjectViewSet(viewsets.ModelViewSet):
permission_classes = [AllowAny]
serializer_class = BuildingObjectSerializer
queryset = BuildingObject.objects.all()
I want to customize delete function. But this delete code works too long time > 1 minute
What is the reason of too long time of request?
class BuildingObjectViewSet(viewsets.ModelViewSet):
permission_classes = [AllowAny]
serializer_class = BuildingObjectSerializer
queryset = BuildingObject.objects.all()
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
deleted_id = instance.pk
self.perform_destroy(instance)
response = {
"message": "BuildingObject has been deleted.",
"building_object_id": deleted_id
}
return Response(response, status=status.HTTP_204_NO_CONTENT)
models.py
class BuildingObject(models.Model):
name = models.CharField(max_length=32)
address = models.CharField(max_length=200)
urls.py
path(
"building_objects/<int:pk>/",
BuildingObjectViewSet.as_view({
"delete": "destroy",
"get": "retrieve",
"patch": "partial_update",
}),
name="building_objects_detail"
),
Related
now i'm studying DRF and have to do project with photo albums. One of my tasks is to create custom #action "patch" method, using model field "title", but i can't understand how to add fields for search in custom methods. We can see them in base methods, like "get", "patch" and "put", but i can't find any info about how to add them to custom actions.
If anyone knows how to do it, please, tell me.
My model:
class PhotoAlbum(models.Model):
title = models.CharField(verbose_name='Название альбома', max_length=50, null=True)
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, verbose_name='Автор')
created_at = models.DateTimeField(verbose_name='Дата создания', editable=False,
default=datetime.datetime.today())
class Meta:
verbose_name = 'Фотоальбом'
verbose_name_plural = 'Фотоальбомы'
def __str__(self):
return f'{self.title} Автор: {self.created_by} Дата: {self.created_at}'
My view:
def photo_album_view(request):
photo_albums = PhotoAlbum.objects.all()
context = {
'photo_albums': photo_albums,
}
return render(request, 'photo_album.html', context=context)
My viewset:
class AlbumFilter(django_filters.FilterSet):
title = django_filters.Filter(field_name='title')
class PhotoAlbumViewSet(viewsets.ModelViewSet):
queryset = PhotoAlbum.objects.all()
filterset_class = AlbumFilter
serializer_class = PhotoAlbumSerializer
pagination_class = ResultPagination
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
search_fields = ['title', ]
ordering_fields = ['created_at', ]
To answer the above
say you have a viewset class
// all imports
class AbcApi(viewsets.ModelViewset):
serializer_class = random
permission_classses = [IsAuthenticated]
search_fields = ["a_field", "b_field"]
filter_backends = [....]
#custom action
#action(detail=False)
def custom_action(self, *args, **kwargs):
""" to answer your question """
qset_ = **self.filter_queryset**(self.queryset())
#the filter bold automatically involves the class filters in the custom method
the answer is to use self.filter_queryset(..pass the get_queryset()) here as seen
If you are using ModelViewSet, I believe you are looking for one of these custom methods:
def update(self, request, pk=None):
pass
def partial_update(self, request, pk=None):
pass
These functions allow you to add custom functionalities to your update method. Reference
Brief example:
def partial_update(self, request, pk=None):
serializer = UserPostSerializer(user, data=request.data, partial=True)
if serializer.is_valid():
try:
serializer.save()
except ValueError:
return Response({"detail": "Serializer not valid."}, status=400)
return Response({"detail": "User updated."})
else:
return Response(serializer.errors)
I have the following views:
class NotificationAPI(viewsets.ModelViewSet):
authentication_classes = (CustomJWTAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
queryset = Notification.objects.all()
def get_queryset(self):
qs = self.queryset.all()
queryset = qs.filter(recipient=self.request.user.id)
return queryset
class MarkAsReadView(APIView):
permission_classes = (IsAuthenticated,)
authentication_classes = [CustomJWTAuthentication, SessionAuthentication]
#swagger_auto_schema(manual_parameters=[authorization])
def post(self, request, notification_id):
# mark single notification as read
class MarkAllAsReadView(APIView):
permission_classes = (IsAuthenticated,)
authentication_classes = [CustomJWTAuthentication, SessionAuthentication]
#swagger_auto_schema(manual_parameters=[authorization])
def post(self, request):
#mark all notifications as read
and here is my urls.py routing:
router = routers.DefaultRouter(trailing_slash=False)
router.register('notifications', NotificationAPI, basename='notifications')
urlpatterns = [
path('', include(router.urls)),
# notifications
path('notifications/mark-as-read/<int:notification_id>',
MarkAsReadView.as_view(), name='mark-as-read'), # this work
path('notifications/mark-all-as-read',
MarkAllAsReadView.as_view(), name='mark-all-as-read'), #this doesn't work
]
Currently whenever i call mark-as-read it work fine, but when i try to call mark-all-as-read api it always return the following 405 error:
{
"detail": "Method \"POST\" not allowed."
}
I checked and it seem like the notifications ModelViewSet routing messing up the other mark-all-as-read ApiView route.
I checked by commenting the ModelViewSet out and the mark-all-as-read now work.
Why is this happening? shouldn't routing be separate by name and basename ? Why it's when i call mark-all-as-read it go into the ModelViewSet route, but the mark-as-read work normally?
UPDATE:
i had update my NotificationAPI modelviewset api with custom route as suggestion from Risadinha but it's displaying serializer in body, how do i remove it? because there no need for body data in my mark-as-read and mark-all-as-read requests
My updated NotificationAPI
class NotificationAPI(mixins.ListModelMixin,
viewsets.GenericViewSet):
authentication_classes = (CustomJWTAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
queryset = Notification.objects.all().order_by('-id')
serializer_class = NotificationSerializer
search_fields = ('verb__unaccent', 'description__unaccent')
pagination_class = ListPagination
filter_backends = [DjangoFilterBackend, SearchFilter]
filterset_fields = ['unread']
def get_queryset(self):
qs = self.queryset.all()
queryset = qs.filter(recipient=self.request.user.id)
return queryset
#action(
basename='mark-all-as-read',
detail=False,
methods=['post'],
name='mark-all-as-read',
url_path=r'mark-all-as-read',
url_name='Mark All As Read'
)
def mark_all_as_read(self, request):
#do some stuffs
#action(
basename='mark-as-read',
detail=False,
methods=['put'],
name='mark-as-read',
url_path=r'mark-as-read/(?P<pk>\d+)'
)
def mark_as_read(self, request, pk=None):
#do some stuffs
I have a small messaging API where the message contains a mark read boolean field.
I'm trying to automatically update the message instance so if the user logged in after the message was created, it'll be marked as read.
class MessagesViewSet(ModelViewSet):
"""
A simple ViewSet for viewing and editing the messages
associated with the user.
"""
authentication_classes = [TokenAuthentication, ]
permission_classes = [IsAuthenticated]
serializer_class = MessageSerializer
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
filterset_fields = FILTERS.FILTER_SET
search_fields = FILTERS.SEARCH_FIELDS
ordering_fields = FILTERS.ORDERING_FIELDS
ordering = [MessageFields.DATE, ]
def get_user(self):
user = self.request.user
return user
def get_queryset(self):
return Message.objects.filter(sent_to=self.get_user())
def perform_create(self, serializer):
"""
Set the sender to the logged in user.
"""
serializer.save(sender=self.get_user())
def perform_update(self, serializer):
"""
Update the message read field to true if necessary.
"""
date = self.kwargs[MessageFields.DATE]
mark_read = self.kwargs[MessageFields.MARK_READ]
last_login = self.get_user().last_login
# If the message hasn't been read yet.
if not mark_read:
if last_login > date:
serializer.save(mark_read=True)
pass
pass
But this is not updating the object when I access it.
The perform_update method will be ran if you send a PUT or PATCH request. What you want to do is to mark messages as True whenever user gets the messages. So you can either override get_queryset or list and retrieve functions.
For example you can try this:
class MessagesViewSet(ModelViewSet):
"""
A simple ViewSet for viewing and editing the messages
associated with the user.
"""
authentication_classes = [TokenAuthentication, ]
permission_classes = [IsAuthenticated]
serializer_class = MessageSerializer
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
filterset_fields = FILTERS.FILTER_SET
search_fields = FILTERS.SEARCH_FIELDS
ordering_fields = FILTERS.ORDERING_FIELDS
ordering = [MessageFields.DATE, ]
def get_user(self):
user = self.request.user
return user
def get_queryset(self):
return Message.objects.filter(sent_to=self.get_user())
def list(self, request):
serializer = MessageSerializer(self.get_queryset(), many=True)
for instance in serializer.data:
instance['mark_read'] = True
serializer.save()
return Response(serializer.data)
And for routing:
urlpatterns += [path('messages/',
MessagesViewSet.as_view({'get': 'list', 'post': 'create'}))]
Also you don't need to override perform_create method, it'll work fine.
I've also come around this issue and what helped me was overriding the update method itself and getting the instance object from there...
For example in your case, add:
def update(self,request,*args,**kwargs):
instance = self.get_object()
instance.sender = self.get_user()
serializer = self.get_serializer(instance,data = request.data)
self.perform_update(serializer)
return Response(serializer.data)
So my model is simple as
class Face(models.Model):
uid = models.CharField(max_length=510, primary_key=True)
photo = models.ImageField(upload_to='face_photos')
serializer
class FaceSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Face
fields = ['uid', 'photo']
extra_kwargs = {'uid': {'required': True}, 'photo': {'required': True}}
and view should be something like
class FaceViewSet(viewsets.ModelViewSet):
queryset = Face.objects.all()
serializer_class = FaceSerializer
permission_classes = [permissions.AllowAny]
And it works. However:
I don't want list, update, delete options. Only POST and GET.
I want to add my logic on post, so if uid exists then update, else create... as well other processing.
I want custom response after the POST.
How do I achieve this all not loosing all the goodies that viewsets.ModelViewSet provides, like validations, auto generated HTML fields in Rest API web view, etc?
This worked for me:
class FaceViewSet(mixins.CreateModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
"""
API endpoint for adding and processing new client (by uid) face
"""
queryset = Face.objects.all()
serializer_class = FaceSerializer
permission_classes = [permissions.AllowAny]
def create(self, request):
if "uid" in request.POST:
try:
instance = Face.objects.get(pk=request.POST['uid'])
serializer = FaceSerializer(
instance=instance,
data=request.data
)
except Face.DoesNotExist:
serializer = FaceSerializer(data=request.data)
else:
serializer = FaceSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = FaceSerializer(instance=instance)
return Response(serializer.data)
for number one you can use this:
http_method_names = ['get','post']
buth get method also cintains list method
I have a problem with PATCH Null Value in Django Rest Framework with Extend User Model. Please take a look my issues!
Serializers:, Profile is extend User Model
class UserEditSerializer(ModelSerializer):
job_title = serializers.CharField(source='profile.job_title')
class Meta:
model = User
fields = [
'username',
'job_title',
]
def update(self, instance, validated_data):
instance.username = validated_data.get('username', instance.username)
instance.save()
if (validated_data.get('profile') is not None):
profile_data = validated_data.pop('profile')
profile = instance.profile
profile.job_title = profile_data.get('job_title', profile.job_title)
My viewsets:
class UserUpdateAPIView(ReadOnlyModelViewSet):
queryset = User.objects.all()
serializer_class = UserEditSerializer
permission_classes = (IsAuthenticated,)
#detail_route(methods=['PATCH'])
def edit(self, request):
user_obj = User.objects.get(id=request.user.id)
serializer = UserEditSerializer(user_obj, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return JsonResponse({'message': 'Error'}, status=500)
My api patch request to server:
{
"job_title": ""
}
Error:
{
"detail": "Method \"GET\" not allowed."
}
Error Photo