Django rest filter data by field - django-rest-framework

Controller:
class TicketMessageSerializerView(generics.RetrieveAPIView):
queryset = TicketMessage.objects.all()
serializer_class = TicketMessageSerializer
How to filter the list of messages by the ticket_id field, the value of which is taken from the url:
path('api/tickets/<int:ticket_id>/messages/',
views.TicketMessageSerializerView.as_view()),

In that case you work with a ListAPIView (since you return a list of objects), and you can override the get_queryset method to specify what TicketMessages that should be returned, so:
class TicketMessageSerializerView(generics.ListAPIView):
queryset = TicketMessage.objects.all()
serializer_class = TicketMessageSerializer
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).filter(
ticket_id=self.kwargs['ticket_id']
)

Related

Retrieve Single Object in Modelviewset

I was using CBV, ModelViewSet but somehow my API Keep showing all the Objects inside, how i get my ModelViewSet retrieve a single object that base on my sku ?
api/views.py
class SamAPIViewSet(viewsets.ModelViewSet):
queryset = SamModels.objects.all()
serializer_class = SamSerializers
lookup_field = 'pk'
def get_queryset(self):
return self.queryset
def get_object(self):
sku_id = self.kwargs['pk']
return self.get_queryset().filter(id=sku_id)
def list(self, request):
products = SamModels.objects.all()
serializers = self.get_serializer(products, many=True)
return Response(serializers.data)
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializers = self.get_serializer(instance)
return Response(serializers.data)
api/urls.py
sam_api_list = SamAPIViewSet.as_view({
'get': 'list'
})
sam_api_detail = SamAPIViewSet.as_view({
'get': 'retrieve'
})
urlpatterns = [
url(r'sam', sam_api_list, name='api-sam'),
url(r'sam/<int:pk>', sam_api_detail, name='api-sam-detail'),
]
sam/1, it return all of my objects, i am not sure what wrong, my object has name, id, sku and how do i change my view to get retrieve single object base on my sku and not my id ?
Your routes are upside down. Try this:
urlpatterns = [
url(r'sam/<int:pk>', sam_api_detail, name='api-sam-detail'),
url(r'sam', sam_api_list, name='api-sam'),
]
Django will select the first expression that matches. If r'sam' is above r'sam/int:pk' then it will select r'sam' without going down to the next entry
url(r'sam/<int:pk>', sam_api_detail, name='api-sam-detail'),
url(r'sam', sam_api_list, name='api-sam'),
to
path(r'sam/<str:pk>', sam_api_detail, name='api-sam-detail'),
path(r'sam', sam_api_list, name='api-sam'),
remove
def get_queryset(self):
return self.queryset
def get_object(self):
sku_id = self.kwargs['pk']
return self.get_queryset().filter(id=sku_id)
and change lookup_field = 'pk' to lookup_field = 'sku'

Using django-filter overriding list method in drf

I'm using django-filter to filter my viewsets in drf.
When I have a ModelViewset, works fine like example bellow:
class MyExampleViewSet(viewsets.ModelViewSet):
queryset = myqueryset
model = ModelExample
filter_backends = (DjangoFilterBackend, OrderingFilter,)
filterset_fields = {
"field_example": ["exact", "icontains"],
"another_field_example": ["exact", "range"],
}
serializer_class = MyExampleViewSet
My problem is when I override the list method using a ViewSet, like this:
class MyExampleViewSet(viewsets.ViewSet):
def list(self, request, queryset=queryset, *args, **kwargs):
return something
In this case my filters does not working. Is there a way of using django-filter in this case (overriding list)?
I know what I can do with query_params, but I would like to use django-filter.
First, you should take a look at how the list method is implemented:
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not 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)
Django-filter is applied at this point queryset = self.filter_queryset(self.get_queryset()).
So if you want to override the list method but keep the the filtering feature, then make sure to call self.filter_queryset() with the queryset.

AttributeError: 'collections.OrderedDict' object has no attribute 'model_id' and 'model_id' is missing from visible fields

Something strange happened: I was defining an endpoint and initially two fields were visible in the API form: model_id and payload, as given in the model definition:
### models.py:
class CarModel(models.Model):
model_id = models.CharField(max_length=10, primary_key=True)
name = models.CharField(max_length=40)
active = models.BooleanField(default=True)
def __str__(self):
return self.model_id
class Calculator(models.Model):
model = models.ForeignKey(CarModel, on_delete=models.CASCADE)
payload = models.TextField()
def model_id(self):
return self.model.model_id
def __str__(self):
return f"Calculations for {self.model.name}"
### serializers.py:
class CalculatorSerializer(serializers.ModelSerializer):
model_id = serializers.SerializerMethodField()
class Meta:
model = Calculator
fields = ['model_id', 'payload']
def get_model_id(self, obj):
return obj.model_id()
### views.py:
class CalculatorViewSet(viewsets.ModelViewSet):
serializer_class = CalculatorSerializer
queryset = Calculator.objects.all()
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
return Response(f"{serializer.data.upper()}", status=status.HTTP_200_OK)
So, both fields were visible, but POST requests ended in the AttributeError: 'collections.OrderedDict' object has no attribute 'model_id'. Trying to fix that, I eventually and accidentally removed model_id from view - it doesn't display in DRF's forms. And the AttributeError still persists.
What is wrong with this piece of code?
OK, it turns out that defining fields in this manner:
fields = '__all__'
makes also the model_id visible. Still, no idea why explicit insert doesn't work.
In case of the other issue, the AttributeError, I had to pull the value out of an OrderedDict. Modified method looks like this:
def get_model_id(self, obj):
return obj["model"].model_id
Beside that, I found one more error inside views.py's create method: serializer.data won't implement upper() method; some key, in my case serializer.data['payload'], has to be referenced, so for example:
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
res = {
"payload": f"{serializer.data['payload'].upper()}"
}
return Response(res, status=status.HTTP_200_OK)

Django REST Framework - Extending ListAPIView with custom PUT method

I developed APIs using Django REST Framework for an inventory management application.
The endpoint to GET the list of products includes query parameters to filter the list. See below.
Product List View:
class ProductListAPIView(ListAPIView):
serializer_class = ProductListSerializer
queryset = Product.objects.all()
permission_classes = [DjangoModelPermissionsWithView]
filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
search_fields = [
'sku',
'product_name',
...
]
filter_class = ProductFilter
pagination_class = ProductPageNumberPagination
ordering = ['-id']
ordering_fields = [
'id',
'sku',
'product_name',
...
]
def get_serializer_context(self, *args, **kwargs):
return {"request": self.request}
I have created another view to handle requests in order export the products to PDF, CSV, etc:
class ProductExportAPIView(APIView):
def put(self, request, *args, **kwargs):
# We use the seriaziler only to validate request.data
serializer = ProductExportSerializer(data=request.data)
if serializer.is_valid():
user_id = request.user.pk
file_key = request.data.get('file_key')
file_name = request.data.get('file_name', '')
extra_args = request.data.get('extra_args', {})
product_ids = request.data.get('product_ids')
# NOTE THAT export_file IS A CELERY TASK
export_file.delay(user_id, file_key, file_name, product_ids, extra_args)
return Response(status=status.HTTP_204_NO_CONTENT)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
The API it's working fine, but it works only if the user selects the products - the product_ids field is used to provide the list of products to be exported.
I would like to let the users export ALL the products via ProductExportAPIView by providing the query params that I'm using with ProductListAPIView rather than providing product_ids.
product_ids should be an optional field to be used only to export a few products.
How I can enable query parameters filtering on my ProductExportAPIView, there is a way to do this without hardcoding it? Can I extend ProductListAPIView with the PUT method to export products?
In order to use the same query parameters defined in ProductListAPIView, now ProductExportAPIView extends ProductListAPIView, so it inherits everything I needed:
class ProductExportAPIView(ProductListAPIView):
permission_classes = [AllowAny]
http_method_names = ['put'] # disable GET method inherited from ProductListAPIView
def put(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
# We use the serializer only to validate request.data
serializer = ProductExportSerializer(data=request.data)
if serializer.is_valid():
user_id = request.user.pk
file_key = request.data.get('file_key')
file_name = request.data.get('file_name', '')
extra_args = request.data.get('extra_args', {})
product_ids = request.data.get('product_ids', [])
if len(product_ids)==0:
product_ids = [p.pk for p in queryset]
export_file.delay(user_id, file_key, file_name, product_ids, extra_args)
return Response(status=status.HTTP_204_NO_CONTENT)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

how to update serializer.validated_data in DRF

good day
me need update validated_data dict
example
class ProductsApiView(APIView):
permission_classes = [AllowAny]
def post(self, request):
serializer = ProductSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
product = serializer.save()
print(serializer.validated_data)
return Response({'message': 'created'}, status=status.HTTP_201_CREATED)
class ProductSerializer(serializers.ModelSerializer):
def create(self, validated_data):
validated_data['my_data'] = 'my_new_data'
return super(ProductSerializer, self).create(validated_data)
class Meta:
model = Product
fields = ['id', 'title']
when i make print(serializer.validated_data)
OrderedDict([('title', 'testttt')])
why in dict no my data "my_data" ?
You can provide arbitrary additional context by passing a context argument when instantiating the serializer. For example:
serializer = AccountSerializer(account, context={'request': request})
serializer.data
The context dictionary can be used within any serializer field logic, such as a custom .to_representation() method, by accessing the self.context attribute.
Reference

Resources