I am following this solution on how to get specific fields from a django model:
Select specific fields in Django get_object_or_404
from django.core import serializers as djangoserializer # module 'rest_framework.serializers' has no attribute 'serialize'
class ProjectDetailApiView(APIView):
authentication_classes = (authentication.SessionAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
def get(self, request, slug=None, format=None):
project_instance = get_object_or_404(Project.objects.only('project_title', 'project_post'), slug=slug)
data = djangoserializer.serialize('json', [ project_instance, ], fields=('project_title','project_post'))
user = self.request.user
updated = False
viewed = False
if not user in project_instance.project_views.all():
viewed = True
project_instance.project_views.add(user)
updated = True
data = {
"project": data,
"updated":updated,
"viewed":viewed
}
return Response(data)
Output:
{
"project": "[{\"model\": \"webdata.project\", \"pk\": 4, \"fields\": {\"project_title\": \"Project 4\", \"project_post\": \"Blabla\"}}]",
"updated": true,
"viewed": false
}
Desired Output:
{
"project_title": "Project 4",
"project_post": "Blabla",
"updated": true,
"viewed": false
}
Thank you
Use DRF's Serializer instead of Django's built-in serializer.
# serializers.py
from rest_framework import serializers
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = ('project_title', 'project_post', 'updated', 'viewed')
# views.py
class ProjectDetailApiView(APIView):
authentication_classes = (authentication.SessionAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
def get(self, request, slug=None, format=None):
project_instance = get_object_or_404(Project, slug=slug)
serializer = ProjectSerializer(project_instance)
return Response(serializer.data)
The serialization of regular dictionaries is very good in python.
So instead of configuring the serializer - why not just create a python dictionary with the desired data? (That's how I do it for simple things that I need only in one place.)
data = {
"project_title": project_instance.project_title,
"project_post": project_instance.project_post,
"updated":updated,
"viewed":viewed
}
return JSONResponse(data)
You haven't posted the Project model, I'm just supposing from the serializer config that the fields are named project_title and project_post.
This will return a response with status 200, mimetype application/json and the data dict as valid JSON.
Related
i am beginner in django and i was trying to learn work with djangorest viewset(not modelviewset)
and i cant find any resource to undrestand it.
i want to learn how to write a partial_update with viewset.
here is what i have tried:
models.py :
class UserIdentDocs(models.Model):
owner = models.ForeignKey(User,on_delete=models.CASCADE, related_name = "user_ident_Docs")
code = models.PositiveBigIntegerField(null=True)
img = models.ImageField(null=True)
video = models.FileField(null=True)
is_complete = models.BooleanField(default=False)
serializers.py:
class AdminUserIdentSerializer(serializers.ModelSerializer):
class Meta:
model = UserIdentDocs
fields = [
"owner",
"code",
"img",
"video",
"is_complete",
]
views.py:
from rest_framework.parsers import MultiPartParser, FormParser
class UserDocAdminViewSet(viewsets.ViewSet):
"""User docs admin view set"""
permission_classes = [AllowAny]
parser_classes = (MultiPartParser, FormParser)
serializer_class = AdminUserIdentSerializer
queryset = UserIdentDocs.objects.filter(is_complete=False)
def list(self, request):
serializer = self.serializer_class(self.queryset, many=True)
return Response(serializer.data)
def retrive(self, request, pk=None):
doc_object = get_object_or_404(self.queryset, pk=pk)
serializer = self.serializer_class(doc_object)
return Response(serializer.data)
def create(self, request ):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data,status=status.HTTP_201_CREATED)
def partial_update(self, request, pk=None):
doc_object = get_object_or_404(self.queryset, pk=pk)
serializer = self.serializer_class(doc_object,data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response({"detail":"item updated succesfuly"}, status=status.HTTP_205_RESET_CONTENT)
urls.py:
urlpatterns = [
...
# admin viewset for docs
# list of accounts with is_complete=False
path("admin/userdocs/", UserDocAdminViewSet.as_view({"get":"list", "post":"create"}), name="user-docs-list"),
path("admin/userdocs/<int:pk>", UserDocAdminViewSet.as_view({"get":"retrive","patch":"partial_update"}), name="user-docs-detail"),
]
i can create user in browsable api but when i want to use partial update i can't even see the fields in browsable api and i only see this :
media type: multipart/form-data
content:this is what i see
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.
I implemented a modelviewset with django-filter and django default pagination comnbined. Its working fine when I use either django-filter or django pagination. But when they are used simultaneously then I am getting duplicate results in response.
So whats the correct way to use pagination in django-filter with CBV?
class TableMetaView(ModelViewSet):
"""
This will be used to create new tables.
You require to add the table fields in json request and also the job request associated with that
table.
If you want to create new table then pass CREATE NEW TABLE
In response you will get the table list along with the job request for each tables
"""
serializer_class = TableMetaSerializer
queryset = TableMeta.objects.all()
renderer_classes = [JSONRenderer]
filterset_fields = [
"schema_name",
"type",
"status",
"grouping__name",
"dataset__name",
]
ordering_fields = ["created_on", "modified_on"]
ordering = ["-modified_on"]
pagination_class = StandardResultsSetPagination
permission_classes = [
UserHasDatasetChangeAccess & IsTableEditable,
]
def get_queryset(self):
if getattr(self, "swagger_fake_view", False):
# queryset just for schema generation metadata
return TableMeta.objects.none()
return TableMeta.objects.filter(
dataset=get_object_or_404(DataSet, id=self.request.META.get(DATASET_ID, ""))
)
After searching a lot, I managed to do some changes and code is now running fine with django-filters and pagination.
custom_pagination.py
from collections import OrderedDict
from constance import config
from rest_framework.pagination import ( # noqa
CursorPagination,
LimitOffsetPagination,
PageNumberPagination,
)
from rest_framework.response import Response
class MyPagination(PageNumberPagination):
page_size = 5
page_size_query_param = "page_size"
max_page_size = 50
def get_paginated_response(self, data):
return Response(
OrderedDict(
{
"next": self.get_next_link(),
"previous": self.get_previous_link(),
"results": data,
}
)
)
views.py
class TableMetaView(ModelViewSet):
"""
This will be used to create new tables.
You require to add the table fields in json request and also the job request associated with that
table.
If you want to create new table then pass CREATE NEW TABLE
In response you will get the table list along with the job request for each tables
"""
serializer_class = TableMetaSerializer
queryset = TableMeta.objects.all()
renderer_classes = [JSONRenderer]
filterset_class = TableFilter
ordering_fields = ["created_on", "modified_on"]
ordering = ["-modified_on"]
pagination_class = MyPagination
permission_classes = [
UserHasDatasetChangeAccess & IsTableEditable,
]
def get_queryset(self):
if getattr(self, "swagger_fake_view", False):
# queryset just for schema generation metadata
return TableMeta.objects.none()
return TableMeta.objects.filter(
dataset=get_object_or_404(DataSet, id=self.request.META.get(DATASET_ID, ""))
)
def list(self, request: Request, *args: Any, **kwargs: Any) -> Response:
filtered_qs = self.filterset_class(request.GET, queryset=self.get_queryset()).qs
page = self.paginate_queryset(queryset=filtered_qs)
serializer = self.serializer_class(page, many=True)
return self.get_paginated_response(serializer.data)
custom_filter.py
class TableFilter(django_filters.FilterSet):
class Meta:
model = TableMeta
fields = {
"name": ["exact", "icontains"],
"schema_name": ["exact"],
"type": ["exact"],
"status": ["exact"],
"grouping__name": ["exact"],
"dataset__name": ["exact"],
}
Also you need to set default ordering
models.py
class TableMeta(models.Model):
class Meta:
ordering = ['-modified_on'] # list of fields here
After this django-filters and pagination are working fine in ModelViewSet.
There seems to be a lot of documentation out there on this but none of it seems to work for me. I am trying to build an API View that creates one or many objects at the same time.
Something like passing in the following:
[
{
"response": "I have no favourites",
"user": 1,
"update": "64c5fe6f-cb65-493d-8ef4-126db0195c33",
"question": "297b46b4-714b-4434-b4e6-668ff926b38e"
},
{
"response": "Good",
"user": 1,
"update": "64c5fe6f-cb65-493d-8ef4-126db0195c33",
"question": "13916052-690e-4638-bb7c-908c38dcd75e"
}
]
My current Viewset
class FeedbackViewSet(viewsets.ModelViewSet):
permission_classes = [AllowAny]
queryset = Feedback.objects.all()
serializer_class = FeedbackSerializer
and Serializer:
class ContributionSerializer(serializers.ModelSerializer):
class Meta:
model = Contribution
fields = '__all__'
I have tried setting FeedbackSerializer(many=True) but this then tells me its not callable. Further, I have tried a ListCreateAPIView but this tells me it expects a dictionary but not a list.
you have the correct idea with many=True. You just need to put it in the correct location... so in the ViewSet:
class FeedbackViewSet(viewsets.ModelViewSet):
permission_classes = [AllowAny]
queryset = Feedback.objects.all()
serializer_class = FeedbackSerializer
def get_serializer(self, *args, **kwargs):
# add many=True if the data is of type list
if isinstance(kwargs.get("data", {}), list):
kwargs["many"] = True
return super(FeedbackViewSet, self).get_serializer(*args, **kwargs)
There are other ways to achieve the same behaviour, but I think this is pretty clean!
Override the create(...) method
from rest_framework.response import Response
from rest_framework import status
class FeedbackViewSet(viewsets.ModelViewSet):
permission_classes = [AllowAny]
queryset = Feedback.objects.all()
serializer_class = FeedbackSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data, many=True) # not that `many=True` id mandatory since you are dealing with a list of of inputs
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(
serializer.data,
status=status.HTTP_201_CREATED,
headers=headers
)
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
}