I'm writing a custom create method for my model:
class TripReportViewSet(viewsets.ModelViewSet):
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
serializer_class = TripReportSerializer
pagination_class = TripReportSetPagination
# To order by favorite count or 'top':
queryset = TripReport.objects.all().annotate(count=Count('favoriters')).order_by('-count')
#queryset = TripReport.objects.all().order_by('-pk')
filter_backends = (filters.SearchFilter, filters.OrderingFilter)
search_fields = ('=author__username', '=slug', 'countries__name', )
ordering_fields = ('pk', )
def create(self, request, **kwargs):
countries = request.POST['countries'].split(',')
countries = list(map(int, countries))
countries = Country.objects.filter(pk__in=countries)
instance = TripReport.objects.create(
author=User.objects.get(pk=request.POST['author']),
title=request.POST['title'],
content=request.POST['content'],
)
instance.countries.set(countries)
instance.save()
return HttpResponse(TripReportSerializer(instance))
I can't seem to get the right response. I want to return my serialized object, but
HttpResponse(instance)
and
HttpResponse(TripReportSerializer(instance))
is giving me the wrong result. TripReportSerializer is the one I'm using for the view.
I see two things wrong with the code:
To return the serializer data I think you should use TripReportSerializer(instance).data
Rest Framework Views generally return a Response object, which is imported from rest_framework.response.Response
Another amendment you should make is to use the views get_serializer() method so the serializer will be populated with the context (view, format, and request), which in your case would mean using this code at the end of your create method.
serializer = self.get_serializer(instance)
return Response(serializer.data)
What you have to do is, serialize the newly created Trip instance and return by using DRF's Response class
from rest_framework.response import Response
class TripReportViewSet(viewsets.ModelViewSet):
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
serializer_class = TripReportSerializer
pagination_class = TripReportSetPagination
# To order by favorite count or 'top':
queryset = TripReport.objects.all().annotate(count=Count('favoriters')).order_by('-count')
# queryset = TripReport.objects.all().order_by('-pk')
filter_backends = (filters.SearchFilter, filters.OrderingFilter)
search_fields = ('=author__username', '=slug', 'countries__name',)
ordering_fields = ('pk',)
def create(self, request, **kwargs):
countries = request.POST['countries'].split(',')
countries = list(map(int, countries))
countries = Country.objects.filter(pk__in=countries)
instance = TripReport.objects.create(
author=User.objects.get(pk=request.POST['author']),
title=request.POST['title'],
content=request.POST['content'],
)
instance.countries.set(countries)
instance.save()
# changes
serializer = TripReportSerializer(instance)
return Response(serializer.data)
Related
I want to inherit Generic Filtering include (filterset_fields, search_fields, ordering_fields in extra action sold. So how to do it, and any way better for this case?
class ApartmentViewset(viewsets.ModelViewSet):
queryset = Apartment.objects.all().order_by('-timestamp')
serializer_class = ApartmentSerializer
# Set permission for only user owner apartment can edit it.
permission_classes = [
permissions.IsAuthenticatedOrReadOnly, IsOwnerApartmentOrReadOnly]
# Add search by address, filter by district and ordering by price
filter_backends = [filters.SearchFilter,
DjangoFilterBackend, filters.OrderingFilter]
filterset_fields = ['district']
search_fields = ['address', 'seller']
ordering_fields = (
'price',
)
# fill current username when create new apartment
def perform_create(self, serializer):
serializer.save(seller=self.request.user)
#action(detail=False)
def sold(self, request):
queryset = self.queryset.filter(issold=True)
serialize = self.serializer_class(queryset, many=True)
return Response(serialize.data)
Generic View (and hence all classes that inherit from it) in DRF has a filter_queryset method which is called by the various mixins to perform filtering, so you can simply call that in your method. If you also want pagination there are the methods paginate_queryset and get_paginated_response:
class ApartmentViewset(viewsets.ModelViewSet):
...
#action(detail=False)
def sold(self, request):
queryset = self.filter_queryset(self.queryset.filter(issold=True))
serialize = self.serializer_class(queryset, many=True)
return Response(serialize.data)
I'm working on a small project Django Rest Framework, I already create add contact function as you can see in my create function. now I'm working on bulk import, but when I submit my data as a list not as a dict I get an error message :
{"non_field_errors":["Invalid data. Expected a dictionary, but got list."]}
this is my code to add a contact,
class ContactView(ListModelMixin, viewsets.GenericViewSet):
queryset = Contact.objects.all()
serializer_class = ContactSerializer
def create(self, request):
serializeObject = ContactSerializer(data = request.data)
if serializeObject.is_valid():
serializeObject.save()
contactObject = Contact.objects.all()
contactSerializer = ContactSerializer(contactObject, many=True)
return Response(contactSerializer.data, status = status.HTTP_201_CREATED)
return Response(serializeObject.errors, status.HTTP_400_BAD_REQUEST)
Now i would like to create another function, for bulk create, since i have a list
This is my header data structure :
[{"Greeting":"amine","first_name":"alain","last_name":"amine","title":"ricardo","language":"ab#xyz.com","email":43822510594,"phone_1":43822510594,"phone_2":"not yet","mobile":43822510594,"fax":"not yet","internal_id":"xname"},{"Greeting":"bill","first_name":"microsoft","last_name":"bill","title":"microsoft","language":"bill#microsoft.com","email":652565455,"phone_1":652565455,"phone_2":"new york","mobile":652565455,"fax":"new york","internal_id":"microsoft"},{"Greeting":"john","first_name":"Yoyo","last_name":"Ruth","title":"xnameagain","language":"rh#xyz.com","email":5465559852,"phone_1":5465559852,"phone_2":"Vancouver","mobile":5465559852,"fax":"Vancouver","internal_id":"yname"}]
This is my serializer:
class ContactSerializer(serializers.ModelSerializer):
class Meta:
model = Contact
fields = "__all__"
I found the Solution on https://www.django-rest-framework.org/api-guide/serializers/#dealing-with-multiple-objects
all what i have to do is to add many=True to create multiple object
serializeObject = ContactSerializer(data = request.data, many=True)
Create method should look like this:
class ContactView(ListModelMixin, viewsets.GenericViewSet):
queryset = Contact.objects.all()
serializer_class = ContactSerializer
def create(self, request):
valid_objects = []
for data in request.data:
serializeObject = ContactSerializer(data=data)
if serializeObject.is_valid():
valid_objects.append(serializeObject)
else:
return Response(serializeObject.errors, status.HTTP_400_BAD_REQUEST)
for obj in valid_objects:
obj.save()
contactObject = Contact.objects.all()
contactSerializer = ContactSerializer(contactObject, many=True)
return Response(contactSerializer.data, status = status.HTTP_201_CREATED)
Advise
They may not be the best practices but it works.
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')
I have this class view, but I am unable to modify the serializer data to insert more data (which is needed and needs to be populated automatically).
Because I am creating many instances at once, the serializer is based on kwargs['many'] = True.
Any idea on how I can add another field to each serializer data?
Thanks,
:
class ReservedServiceView(CreateListModelMixin, ModelViewSet):
queryset = ReservedService.objects.all()
serializer_class = ReservedServiceSerializer
authentication_classes = (authentication.TokenAuthentication,)
def perform_create(self, serializer):
# Create an event that is a Reflection of the Reserved Service
serializer_data = self.request.data
for reserved_service in serializer_data:
print("--------",reserved_service, flush=True)
service_id = reserved_service['original_service']
original_service = Service.objects.get(pk=service_id)
calendar_event = CalendarEvent()
calendar_event.name = original_service.name
calendar_event.description = original_service.description
calendar_event.date = reserved_service['date']
calendar_event.type_id = 1
calendar_event.start_time = reserved_service['start_time']
calendar_event.end_time = reserved_service['end_time']
calendar_event.location_id = original_service.location.id
calendar_event.save()
reserved_service['associated_event'] = calendar_event.id
print("**********1", serializer_data)
print("**********2", self.request.data)
serializer.save()
Based in:
class CreateListModelMixin(object):
def get_serializer(self, *args, **kwargs):
""" if an array is passed, set serializer to many """
if isinstance(kwargs.get('data', {}), list):
kwargs['many'] = True
return super(CreateListModelMixin, self).get_serializer(*args, **kwargs)
I am not able to properly get your question, but if your question is that you are not getting the extra fields which you added to the serializer in the response of the view, then here is the answer for it.
The response of this view is returned by create method of CreateModelMixin which passes serializer.data to the data param of Response. You cannot update serializer.data because it is an immutable object. So, to solve this you will have to override the create method as follows:
class ReservedServiceView(CreateListModelMixin, ModelViewSet):
queryset = ReservedService.objects.all()
serializer_class = ReservedServiceSerializer
authentication_classes = (authentication.TokenAuthentication,)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
my_data = {}.update(serializer.validated_data)
# Now you can work over the my_data and add extra fields to it and save it
# and instead of passing serializer.data we pass my_data to Response class
headers = self.get_success_headers(serializer.data)
return Response(my_data, status=status.HTTP_201_CREATED, headers=headers)
I have a viewset and I override list() method, but filtering by field stop working. How can I call filtering options from my code:
This is my viewset:
class SupplementViewSet(viewsets.ModelViewSet):
permission_classes = (permissions.IsAuthenticated,)
queryset = models.Product.objects.filter()
serializer_class = serializers.SuplementSerializer
filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter,)
search_fields = ('hotel', 'name')
filter_fields = ('id', 'hotel', 'name')
def perform_create(self, instance):
instance.save(product_type=models.Product.SUPPLEMENT)
def list(self, request, pk=None):
if pk == None:
supplements = models.Product.objects.filter(product_type=models.Product.SUPPLEMENT)
else:
supplements = models.Product.objects.get(product_type=models.Product.SUPPLEMENT, id=pk)
page = self.paginate_queryset(supplements)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(page, many=True)
result_set = serializer.data
return Response(result_set)
def get_result_set(self, supplements):
result_set = serializers.ProductSerializer(supplements, many=True).data
return result_set
Thanks for the help.
def list(self, request, pk=None):
if pk == None:
supplements = models.Product.objects.filter(product_type=models.Product.SUPPLEMENT)
else:
supplements = models.Product.objects.filter(product_type=models.Product.SUPPLEMENT, id=pk)
supplements= self.filter_queryset(supplements)
page = self.paginate_queryset(supplements)
self.filter_queryset(queryset) is what you need, but it must accept queryset.
It would be helpful to see the actual implementation of the list method:
http://www.cdrf.co/3.9/rest_framework.viewsets/ReadOnlyModelViewSet.html#list
You can directly use the self.filter_queryset method (which is inherited from GenericAPIView in viewsets.ModelViewSet). In very rare scenarios you will need to override the self.filter_queryset method.
Both Ykh and Akshay Anurag answers are correct, for sake of simplicity you could simply call:
filter_queryset(queryset)
before calling
paginate_queryset(filtered_queryset)