I am using django rest framework. I want to include the url of an action defined in a view in its serializer.
My serializers.py:
from rest_framework import serializers
class CommentSerializer(serializers.ModelSerializer):
"""Serializer for comments."""
class Meta:
model = Comment
fields = ["id", "item", "author", "content", "date_commented", "parent"]
class ItemDetailSerializer(serializers.ModelSerializer):
"""Serializer for items (for retrieving/detail purpose)."""
category = CategorySerializer(many=True, read_only=True)
media = MediaSerializer(many=True, read_only=True)
brand = BrandSerializer(many=False, read_only=True)
specifications = serializers.SerializerMethodField(source="get_specifications")
comments = ??????????????????????????????????????????????????
class Meta:
model = Item
fields = [
"id",
"slug",
"name",
"description",
"brand",
"show_price",
"location",
"specifications",
"is_visible",
"is_blocked",
"created_at",
"updated_at",
"seller",
"category",
"media",
"comments",
"users_wishlist",
"reported_by",
]
read_only = True
editable = False
lookup_field = "slug"
def get_specifications(self, obj):
return ItemSpecificationSerializer(obj.item_specification.all(), many=True).data
My views.py:
from rest_framework import viewsets, mixins, status
from ramrobazar.inventory.models import Item, Comment
from ramrobazar.drf.serializers ItemSerializer, ItemDetailSerializer, CommentSerializer
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from rest_framework.filters import SearchFilter
from django_filters.rest_framework import DjangoFilterBackend
class ItemList(viewsets.GenericViewSet, mixins.ListModelMixin):
"""View for listing and retrieving all items for sale."""
queryset = Item.objects.all()
serializer_class = ItemSerializer
serializer_action_classes = {
"retrieve": ItemDetailSerializer,
}
permission_classes = [IsAuthenticatedOrReadOnly]
lookup_field = "slug"
filter_backends = [DjangoFilterBackend, SearchFilter]
filterset_fields = [
"category__slug",
"brand__name",
]
search_fields = ["name", "description", "category__name", "brand__name", "location"]
def get_serializer_class(self):
try:
return self.serializer_action_classes[self.action]
except:
return self.serializer_class
def retrieve(self, request, slug=None):
item = self.get_object()
serializer = self.get_serializer(item)
return Response(serializer.data)
#action(detail=True, methods=['GET'])
def comments(self, request, slug=None):
item = Item.objects.get(slug=slug)
queryset = Comment.objects.filter(item=item)
serializer = CommentSerializer(queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
I have an action named comments in the ItemList view which gives all the comments of a specific item. I can get the details of the item from the url /api/items/<slug>. I can get all the comments of the item from the url api/items/<slug>/comments. I want to include a comments field in the ItemDetailSerializer serializer which is a link to api/items/<slug>/commments. How can I accomplish this?
You can do this via SerializerMethodField and reverse:
class ItemDetailSerializer(serializers.ModelSerializer):
...
comments = serializers.SerializerMethodField(source="get_comments")
...
def get_comments(self, obj):
return reverse(YOUR_URL_NAME, kwargs={'slug': obj.slug})
try this:
class ItemDetailSerializer(serializers.ModelSerializer):
...
comments = serializers.CharField(max_length=100, required=False)
def create(self, validated_data):
validated_data['comments'] = self.context['request'].build_absolute_uri() + 'comments'
return super(ContentGalleryListSerializer, self).create(validated_data)
You can use the HyperlinkedIdentityField for this:
comments = serializers.HyperlinkedIdentityField(
view_name='item-comments',
lookup_field='slug'
)
This will then render a comments field that contains a URL to the item-comments view, with the slug as lookup parameter.
You will need to register the view with the name item-comments, for example with:
urlpatterns = [
path('items/<slug>/comments/', ItemList.as_view({'get': 'comments'}), name='item-comments')
]
Note that normally nested routes are discouraged, and you'd put the comments in a separate view. But the above should work.
Related
I have two models with many-to-many relationship and I also have nested routers between them.
When I'm trying to create a tag at the endpoint api/page/4/tags/, tag is created in the database for tags, but nothing happend in my table for many to many relationship. How can I fix it?
I want to update my M2M everytime a new tag is created. Thank you
models.py
class Tag(models.Model):
title = models.CharField(max_length=30, unique=True)
class Page(models.Model):
...
tags = models.ManyToManyField(Tag, related_name='pages')
My serializers.py
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = '__all__'
class PageSerializer(serializers.ModelSerializer):
tags = TagSerializer(many=True)
owner = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = Page
fields = ['id', 'title', 'uuid', 'description', 'owner', 'is_private', 'tags']
views.py
class PageViewSet(viewsets.ModelViewSet):
queryset = Page.objects.all()
serializer_class = PageSerializer
def perform_update(self, serializer):
serializer.save(owner=self.request.user)
class TagViewSet(viewsets.ModelViewSet):
serializer_class = TagSerializer
queryset = Tag.objects.all()
class NestedTagViewSet(CreateModelMixin, ListModelMixin, GenericViewSet):
queryset = Tag.objects.all()
serializer_class = TagSerializer
permission_classes = (IsAuthenticatedOrReadOnly,)
def get_page(self, request, page_pk=None):
page = get_object_or_404(Page.objects.all(), pk=page_pk)
self.check_object_permissions(self.request, page)
return page
def create(self, request, *args, **kwargs):
self.get_page(request, page_pk=kwargs['page_pk'])
return super().create(request, *args, **kwargs)
def get_queryset(self):
return Tag.objects.filter(pages=self.kwargs['page_pk'])
def list(self, request, *args, **kwargs):
self.get_page(request, page_pk=kwargs['page_pk'])
return super().list(request, *args, **kwargs)
my urls.py
from django.urls import path, include
from rest_framework_nested import routers
from .views import PageViewSet, TagViewSet, NestedTagViewSet
router = routers.SimpleRouter()
router.register(r'pages', PageViewSet)
pages_router = routers.NestedSimpleRouter(router, r'pages', lookup='page')
pages_router.register(r'tags', NestedTagViewSet, basename='page-tags')
app_name = 'page'
urlpatterns = [
path(r'', include(router.urls)),
path(r'', include(pages_router.urls)),
]
I'm trying to create new tags at the api/ ^pages/(?P<page_pk>[^/.]+)/tags/$ [name='page-tags-list'] host
I will assume after you create a tag you want to add it to the current page in the route.
With this in mind, you have a tag viewset, so you need to override the create method when you are creating a tag.
class TagViewSet(viewsets.ModelViewSet):
serializer_class = TagSerializer
queryset = Tag.objects.all()
def create(self, request, *args, **kwargs):
response = super().create(request, *args, **kwargs)
new_tag = Tag.objects.get(id=response["id"])
page = Page.objects.get(id=kwards["page_pk"])
page.tags.add(new_tag)
return response
The idea is to create a tag and after you create it, append it to the page M2M relationship. Hope this helps.
Views.py -
#api_view(['GET'])
def view_items(request):
if request.query_params:
items = Item.objects.filter(**request.query_param.dict()) #error line
else:
items=Item.objects.all()
serializer=ItemSerializer(items,many=True)
if items:
return Response(serializer.data)
else:
return Response(status=status.HTTP_404_NOT_FOUND)
The **request.query_param should be changed into **request.query_params.
Let's say you want to filter by name.
#api_view(['GET'])
def view_items(request):
name = request.query_params.get("name", None)
if name:
items = Item.objects.filter(name=name)
I would suggest you to use django-filters, that's a better way to structure your code.
Create Item app
urls.py
app_name = "app-name"
router = SimpleRouter()
router.register("items", ItemViewSet, basename="item")
views.py
class ItemViewSet(views.ModelViewSet):
queryset = Item.objects.all()
serializer_class = ItemSerializer
filterset_class = ItemFilterSet
serializers.py
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ("id", "name", "label")
filters.py
from django_filters import rest_framework as filters
class ItemFilterSet(filters.FilterSet):
class Meta:
model = Item
fields = ("name", "label")
I need to get this endpoint /comments/int:post_id/
I can GET and POST comments and posts but I need to show all comments to specific post. I donĀ“t know how to connect it. My code look like
comment urls
urlpatterns = [
path('', views.CommentsView.as_view()),
path('<int:post_id>/', views.CreateCommentsView.as_view()),
]
comment view.py
# I get all comments/
class CommentsView(ListCreateAPIView):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
# Comments to specific post
class CreateCommentsView(ListCreateAPIView):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
lookup_url_kwarg = 'post_id'
def perform_create(self,serializer):
post = self.kwargs.get('post_id')
post =set.get_queryset().filter(id = 'post_id')
post.comments.add(comment)
post = Post.objects.filter(id=self.kwargs.get('post_id'))
serializer.save(user=self.request.user, post=post)
comment serializer
from rest_framework import serializers
from .models import Comment
from django.contrib.auth import get_user_model
User = get_user_model()
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ['id', 'user', 'post', 'content', 'created']
class UserSimpleSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['username']
class CommentSimpleSerializer(serializers.ModelSerializer):
user = UserSimpleSerializer()
class Meta:
model = Comment
fields = ['user', 'content', 'created']
post.view.py
class PostList(generics.ListCreateAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
class PostDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
class LikePost(generics.UpdateAPIView):
permission_classes = [IsNotPostUser]
queryset = Post.objects.all()
serializer_class = PostSerializer
you can pass the post_id in url parameters for /comments/ endpoint and call HTTP GET method. Then in CommentsView you need to override the get_queryset method. Your implementation will be like
class CommentsView(ListCreateAPIView):
serializer_class = CommentSerializer
def get_queryset(self):
query = self.request.query_params.get('post_id', None)
if query is not None:
queryset = Comment.objects.filter(post_id=post_id)
else:
queryset = Comment.objects.all()
return queryset
This /comments/ endpoint will return all the comments and this one /comments/?post_id=1 will return comments only related to specified post.
I have the following view:
class UserProfileView(APIView):
permissions_classes = [permissions.IsAuthenticated]
def get(self, request):
user = User.objects.get(id=request.user.id)
serializer = UserPrivateSerializer(user)
return Response(serializer.data)
The following Model:
class User(AbstractUser):
pp = models.ImageField(blank=True)
and the following serializer:
class UserPrivateSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
The following urls:
urlpatterns = [
path('profile/', UserProfileView.as_view())
]
I response I get is:
{
"pp": "/media/WIN_20190423_18_50_32_Pro.jpg"
}
when I expect:
{
"pp": "localhost:8000/media/WIN_20190423_18_50_32_Pro.jpg"
}
I know it's not a model or serializer issue because I have other views that use the same model and serializer where it returns the full path.
try this:
class UserProfileView(APIView):
permissions_classes = [permissions.IsAuthenticated]
def get(self, request):
user = User.objects.get(id=request.user.id)
serializer = UserPrivateSerializer(user, context=self.get_serializer_context())
return Response(serializer.data)
the key is add context=self.get_serializer_context() to you serializer.
It turns out all I do was to add context={"request": request} to the serializer.
I'm using ModelViewSet to generate a list of items by get_queryset. I want to return a json with the list of objects and an extra field (the sum total time of the DurationField).
Example:
{
"total_time":"00:10:00",
"objects":[
{
"pk":1,
"title":"Title",
"duration": "00:05:00"
},
{
"pk":1,
"title":"Title",
"duration": "00:05:00"
}
]
}
How can I do this? Here's my code below.
ModelViewSet:
class ModelViewSet(viewsets.ModelViewSet):
queryset = Model.objects.all()
serializer_class = ModelSerializer
permission_classes = (IsAuthenticated,)
def get_queryset(self):
user = self.request.user
list = Model.objects.filter(user=user)
total_time = .. ## sum the duration of list of objects
## I want to return the list and total_time
return list
serializers.py:
class ModelSerializer(serializers.ModelSerializer):
pk = serializers.IntegerField(read_only=True)
user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), required=False, allow_null=True)
project = serializers.PrimaryKeyRelatedField(queryset=Project.objects.all(), required=False, allow_null=True)
class Meta:
model = Model
fields = ('pk', 'title', 'user', 'project', 'duration')
urls:
router = DefaultRouter()
router.register(r'^', views.ModelViewSet)
urlpatterns = [
url(r'^api', include(router.urls)),
]
One way would be to subclass the paginator (the default pagination class is PageNumberPagination) and tweaking the get_paginated_response. This is wholly untested, but maybe something like this:
from rest_framework.pagination import PageNumberPagination
class CustomPageNumberPagination(PageNumberPagination):
def get_paginated_response(self, data, total_time):
return Response(OrderedDict([
('count', self.page.paginator.count),
('next', self.get_next_link()),
('previous', self.get_previous_link()),
('total_time', total_time),
('results', data)
]))
class ModelViewSet(viewsets.ModelViewSet):
queryset = Model.objects.all()
serializer_class = ModelSerializer
permission_classes = (IsAuthenticated,)
pagination_class = CustomPageNumberPagination
def get_queryset(self):
user = self.request.user
list = Model.objects.filter(user=user)
self.total_time = .. ## sum the duration of list of objects
return list
def get_paginated_response(self, data):
return self.paginator.get_paginated_response(data, self.total_time)
Anyhow, hopefully this helps a bit.
Good luck!
Finally I did it! But I'm not sure if it's the best practice. If anyone has a better idea, please share!
class ModelJSONRenderer(JSONRenderer):
"""
Add "objects" and "total_time" in Json
"""
def render(self, data, accepted_media_type=None, renderer_context=None):
data = {'objects': data, 'total_time': renderer_context['total_time']}
return super(ModelJSONRenderer, self).render(data, accepted_media_type, renderer_context)
ModelViewSet:
class ModelViewSet(viewsets.ModelViewSet):
queryset = Model.objects.all()
serializer_class = ModelSerializer
permission_classes = (IsAuthenticated,)
renderer_classes = (ModelJSONRenderer, )
def get_queryset(self):
user = self.request.user
list = Model.objects.filter(user=user)
self.total_time = .. ## sum the duration of list of objects
return list
def get_renderer_context(self):
"""
Returns a dict that is passed through to Renderer.render(),
as the `renderer_context` keyword argument.
"""
return {
'view': self,
'args': getattr(self, 'args', ()),
'kwargs': getattr(self, 'kwargs', {}),
'request': getattr(self, 'request', None),
'total_time': self.total_time
}