Format url by user name - python

I have a small messaging API built with DRF which sends messages between users in the system.
My messages view contains several extra actions:
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]
filterset_fields = [MessageFields.MARK_READ]
def get_user(self):
user = self.request.user
return user
def get_queryset(self):
return Message.objects.filter(sent_to=self.get_user())
#action(detail=True)
def sent_messages(self, request, pk):
"""
Return all messages sent by the user.
"""
queryset = Message.objects.filter(sender=self.get_user())
serialized_data = MessageSerializer(queryset, many=True)
return Response(serialized_data.data, status=HTTP_200_OK)
#action(detail=True)
def last_50_messages(self, request, pk):
"""
Return the user's 50 last messages
"""
queryset = Message.objects.filter(sent_to=self.get_user())
serialized_data = MessageSerializer(queryset, many=True)
return Response(serialized_data.data, status=HTTP_200_OK)
Urls file:
from .views import MessagesViewSet
messages_router = DefaultRouter()
messages_router.register(r'messages', MessagesViewSet, basename='messages')
urlpatterns = [
url('', include(messages_router.urls))
]
Right now the only way to access the two custom methods is opening one of the message instances and then add it to the URL line and it'll work.
How can format the url for each method so it will be via the username?
right now:
http://127.0.0.1:8000/api/messages/1/sent_messages/
I looking for something like:
http://127.0.0.1:8000/api/messages/#request.user.username/sent_messages/

You have to change lookup_field value in ModelViewSet like this:
class MessagesViewSet(ModelViewSet):
...
lookup_field = "username"
...
But be careful, API like retrieve will be work with username lookup too, not pk.
To use both (username, lookup) check hook here:
class MultipleFieldLookupORMixin(object):
"""
Actual code http://www.django-rest-framework.org/api-guide/generic-views/#creating-custom-mixins
Apply this mixin to any view or viewset to get multiple field filtering
based on a `lookup_fields` attribute, instead of the default single field filtering.
"""
def get_object(self):
queryset = self.get_queryset() # Get the base queryset
queryset = self.filter_queryset(queryset) # Apply any filter backends
filter = {}
for field in self.lookup_fields:
try: # Get the result with one or more fields.
filter[field] = self.kwargs[field]
except Exception:
pass
return get_object_or_404(queryset, **filter) # Lookup the object
class RetrieveUserView(MultipleFieldLookupORMixin, generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
lookup_fields = ('account', 'username')

Related

How to make customize detail view set using ModelViewSet?

I am new to django.
I have a model:
class Class(models.Model):
name = models.CharField(max_length=128)
students = models.ManyToManyField("Student")
def __str__(self) -> str:
return self.name
Now I want to create API to display the students in a particular class,the detail view, by giving the name of the class as a parameter using ModelViewClass.
Currently, I have following viewset written:
class ClassViewSet(ModelViewSet):
serializer_class = serializers.ClassSerializer
queryset = models.Class.objects.all()
How to do that?
You can use the #action(...) decorator to create a custom view.
from rest_framework.decorators import action
class ClassViewSet(ModelViewSet):
serializer_class = serializers.ClassSerializer
queryset = models.Class.objects.all()
#action(detail=True, methods=['get'], url_path='students', url_name='students')
def students(self, request, *args, **kwargs):
class_ = self.get_object()
students = class_.students.all()
serializer = serializers.StudentSerializer(students, many=True)
return Response(serializer.data)
If you are not using the DRF routers, you can specify the custom route as below,
urlpatterns = [
path('classes/', views.ClassViewSet.as_view({'get': 'list'})),
path('classes/<int:pk>/', views.ClassViewSet.as_view({'get': 'retrieve'})),
path('classes/<int:pk>/students/', views.ClassViewSet.as_view({'get': 'students'})),
]

Django Viewset retrieve not working with Default Router

I am trying to access a specific comment using the retrieve method in Django View sets. I am using a default router in order to route my urls. I am able to list all comments at api/posts/, but am unable to get a single comment at api/posts/1. I am getting a type error: Field.init() got an unexpected keyword argument 'pk' when trying to access the URL. Any ideas as to why?
urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from posts.views import PostsViewSet, CommentsViewSet
router = DefaultRouter()
router.register(r"comments", CommentsViewSet, basename='comments')
urlpatterns = [
path("", include(router.urls)),
]
views.py
class CommentSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(label='commentID')
comment = serializer.CharField()
created_at = serializers.DateTimeField(required=False)
updated_at = serializers.DateTimeField(required=False)
posts = serializers.SerializerMethodField()
class Meta:
model = Comments
# fields = "__all__"
fields = ['id', 'comment','created_at', 'updated_at', 'posts']
def to_representation(self, instance: Comment) -> dict:
'''Pass for now'''
ret = super().to_representation(instance)
return ret
def get_queryset(self) -> QuerySet:
qs = Comment.objects.all()
return qs
def create(self, validated_data: dict) -> Comment:
return Comment.objects.create(**validated_data)
def update(self, instance: Comment, validated_data: dict) -> Comment:
'''Pass post-validation errors silently'''
for field in validated_data:
setattr(instance, field, validated_data.get(
field, getattr(instance, field)))
instance.save()
return instance
class CommentViewSet(viewsets.ViewSet):
def list(self, request):
queryset = Comment.objects.all()
result = CommentSerializer(queryset, many=True)
if result:
return Response(result.data)
else:
return Response(data=result.data, status=200)
def retrieve(self, request, pk=None):
queryset = Comment.objects.all()
condition = get_object_or_404(queryset, pk=pk)
result = CommentSerializer(queryset, pk=pk)
print(result)
return Response(result.data)
First you are not using the condition object in serializer.
Second drf default pk field name is id not pk and no need to pass pk during serialization process.Below is updated retrieve method kindly check the same.
def retrieve(self, request, pk=None):
# queryset = Comment.objects.all() #no need as you are using get_object_or_404 to fetch single object
condition = get_object_or_404(Comment, id=pk) #replaced pk by id here
result = StudentSerializer(condition) #used condition in serializer
print(result)
return Response(result.data)

Filter the queryset in Python 3 and jQuery DataTables

I am trying to query on jQuery Datatables list.
Right now I can fetch all records from the table, but I want to fetch records to whom their parent id matches.
For example, users have posts, so in my case it would be fetch posts where user id is lets say 1.
So I am trying to implement same thing for jQuery Datatables.
I can see data is being posted but I can't figure out how to query along with datatables, so that datatables filters are not affected by this change.
My current code:
class PartRequestViewSet(CommonViewset, generics.RetrieveAPIView):
queryset = PartRequest.objects.filter(deleted_at=None)
serializer_class = PartRequestSerializer
def list(self, request, *args, **kwargs):
records = request.GET.get('records', None)
# ID of the part
part_number_id = request.GET.get('part_number_id', None)
queryset = self.get_queryset()
queryset = self.filter_queryset(queryset)
page = self.paginate_queryset(queryset)
if page is not None and records is None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
Now in above code I can get the part_number_id from request, but how can I filter records using filter_queryset(), so that only parts_requests are given back in datatables where part_number_id is 1
Update
Current helper function filter_queryset that is used in above code.
def filter_queryset(self, queryset):
format = self.request.GET.get('format', None)
if format == 'datatables':
self.filter_backends += (DatatablesFilterBackend,)
else:
self.filter_backends += (DjangoFilterBackend,)
for backend in list(self.filter_backends):
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
There is django-filter extension. In your case it may be something like the following:
from rest_framework import viewsets
from django_filters.rest_framework import DjangoFilterBackend
from .models import Post
from .serializers import PostSerializer
class PostList(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = ['owner']
Then you can use get request like this: /posts?owner=3,
where 3 is user id and owner is ForeignKey in Post model to User.
The way I did is I added the following lines:
if part_number_id:
queryset = queryset.filter(part_number_id=part_number_id)
so the code looks like this now and it is working great. I'm open to better and more efficient options.
class PartRequestViewSet(CommonViewset, generics.RetrieveAPIView):
queryset = PartRequest.objects.filter(deleted_at=None)
serializer_class = PartRequestSerializer
def list(self, request, *args, **kwargs):
records = request.GET.get('records', None)
# ID of the part
part_number_id = request.GET.get('part_number_id', None)
queryset = self.get_queryset()
queryset = self.filter_queryset(queryset)
if part_number_id:
queryset = queryset.filter(part_number_id=part_number_id)
page = self.paginate_queryset(queryset)
if page is not None and records is None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)

How to Query model filed elements in the same URL

I am writing a single model application in DRF. My model looks like this:
class Superhero(models.Model):
squad_name = models.CharField(max_length=100)
hometown = models.CharField(max_length=30)
formed = models.DateField()
active = models.BooleanField()
members = JSONField()
My viewset looks like this:
class SuperheroViewSet(viewsets.ViewSet):
"""
A simple ViewSet for listing or retrieving superheros.
"""
serializer_class = SuperheroSerializer
def list(self, request):
"""list superhero object"""
queryset = Superhero.objects.filter()
serializer = SuperheroSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
queryset = Superhero.objects.filter()
superhero = get_object_or_404(queryset, pk=pk)
serializer = SuperheroSerializer(superhero)
return Response(serializer.data)
and finally, my router is:
router = DefaultRouter()
router.register(r'superhero', SuperheroViewSet, basename='superhero')
urlpatterns = router.urls
Now how do I set a URL,so I would query the members field like:
//superhero/{id}/members to get specific id members. I tried drf nested URL but didn't work. The url I have works for superhero/ and superhero/{id}.
You should use detailed viewset action.
Your code would looks smth like this:
from rest_framework.decorators import action
from rest_framework.shortcuts import get_object_or_404
from rest_framework.response import Response
class SuperheroViewSet():
...
#action(detail=True, methods=['get'], url_path='members')
def get_superhero_members(self, request, pk=None):
superhero = get_object_or_404(self.get_queryset(), pk=pk)
members = <get members of your hero>
return Response(members)
You should also probably use custom serializer for members and in response return: return Response(CustomSerializer(members).data)

Creating a new viewset with multiple filters for the viewset get method

Hi i have a viewset that I am creating. I want to over ride the the get functino and get all the records that have the filtered parameter which is passed into the get view. I also want to be able to do the rest of the crud functionality - GET POST PUT DELETE - and use the paramater that is passed through the url as a parameter for the POST and UPDATE.
Right now, when i pass in the parameter, rather than filter the data that is returned, it is giving me no details found which is not what i want. i want it to be used as a secondary filter for all the records that i get back from the database.
Here is the code:
viewset
class PreferenceUserViewSet(viewsets.ModelViewSet):
queryset = Preference.objects.all().filter(user_id=1)
serializer_class = PreferenceSerializer
class PreferenceNamespaceViewSet(viewsets.ModelViewSet):
queryset = Preference.objects.all().filter(user_id=1)
serializer_class = PreferenceSerializer
def get_permissions(self):
if self.action == 'create' or self.action == 'destroy':
permission_classes = [IsAuthenticated]
else:
permission_classes = [IsAdminUser]
return [permission() for permission in permission_classes]
#permission_classes((IsAuthenticated))
def list(self, request, namespace=None):
# switch user_id value with logged in users id
queryset = Preference.objects.all().filter(user_id=1, namespace=namespace)
serializer = PreferenceSerializer(queryset, many=True)
return Response(serializer.data)
urls:
path('preferences/<str:namespace>/', PreferenceNamespaceViewSet.as_view({
'get':'list'
})),
path('users/<int:pk>/stacks/', person_stack, name='user-stacks'),
I want to use the namepsace as a secondary filter to all the data that is returned in the GET. I also want to use it as a peice of data that I can enter when creating a new preference.
** I also want to do the samething with a third potential parameter like so... **
potential third parameters:
urlpatterns = [
path('preferences/<str:namespace>/<str:path>', PreferencePathViewSet.as_view({
'get':'list'
})),
path('preferences/<str:namespace>/', PreferenceNamespaceViewSet.as_view({
'get':'list'
})),
path('users/<int:pk>/stacks/', person_stack, name='user-stacks'),
]
I think you shouldn't add namespace as url parameter, instead you can use URL querystring to fetch namespace information(as well as other parameters). for example:
# URL
path('preferences/', PreferencePathViewSet.as_view({
'get':'list'
})
),
# view
class PreferenceNamespaceViewSet(viewsets.ModelViewSet):
queryset = Preference.objects.all().filter(user_id=1)
serializer_class = PreferenceSerializer
def get_permissions(self):
if self.action == 'create' or self.action == 'destroy':
permission_classes = [IsAuthenticated]
else:
permission_classes = [IsAdminUser]
return [permission() for permission in permission_classes]
#permission_classes((IsAuthenticated))
def list(self, request):
queryset = Preference.objects.all()
namespace = request.GET.get('namespace', None)
if namespace:
queryset = queryset.filter(user_id=1, namespace=namespace)
serializer = PreferenceSerializer(queryset, many=True)
return Response(serializer.data)

Categories