I have for the moment a GET request where I have to send a body as a parameter but as in front it is not possible to make a GET request with a body I would like to pass my parameters as query parameters in the URL . How can I do this with the code I currently have?
My serializer class:
#dataclass
class PlantLinkParams:
plant_id: int
link: str
class LnkPlantPlantByLinkSerializer(serializers.Serializer):
plant_id = serializers.IntegerField()
link = serializers.CharField()
def create(self, validated_data):
return PlantLinkParams(**validated_data)
My view class :
class PlantLinkAPIView(APIView):
permission_classes = (AllowAnonymous,)
queryset = LnkPlantPlant.objects.prefetch_related("plant", "plant_associated")
def get(self, request):
params_serializer = LnkPlantPlantByLinkSerializer(data=request.data)
params_serializer.is_valid(raise_exception=True)
params = params_serializer.save()
data = self.getAllPlantAssociatedByLink(params)
serializer = ReadLnkPlantPlantSerializer(instance=data, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def getAllPlantAssociatedByLink(self, params: PlantLinkParams):
data = []
queryset = (
LnkPlantPlant.objects.filter(
plant=params.plant_id,
link=params.link,
)
)
for entry in queryset:
data.append(entry)
return data
You could do something simpler using a ListAPIView:
class PlantLinkAPIView(ListAPIView):
permission_classes = (AllowAnonymous,)
serializer_class = ReadLnkPlantPlantSerializer
def get_queryset(self):
# Retrieve the query parameters (?plant_id=xxx&link=yyy)
try:
plant_id = int(self.request.GET.get('plant_id'))
except (ValueError, TypeError):
# Prevents plant_id to be set if not a valid integer
plant_id = None
link = self.request.GET.get('link')
params = {}
if plant_id:
params['plant__id'] = plant_id
if link:
params['link'] = link
# Only filtering the queryset if one of the params is set
if params:
return LnkPlantPlant.objects.filter(**params)
return LnkPlantPlant.objects.all()
You don't need more than that to get your view working.
Related
I have a problem with my PATCH request instance. Currently, data that is being PATCHED and sent from a request is overriding every item in my list of strings ArrayField inside my model object.
I need my patch request behavior to append to the rest of the items in ArrayField object, not delete/override.
How can I go about doing that?
I assume I need to override the patch method within RetrieveUpdateAPIView,
so I've started out here:
def patch(self, request, **kwargs):
item = self.kwargs.get('slug')
serializer = StockListSerializer(item, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
Response(serializer, status=status.HTTP_200_OK)
return Response(status=status.HTTP_400_BAD_REQUEST)
serializer.py:
class StringArrayField(serializers.ListField):
def to_representation(self, obj):
obj = super().to_representation(obj)
return ",".join([str(element) for element in obj])
def to_internal_value(self, data):
data = data.split(",")
return super().to_internal_value(data)
class StockListSerializer(serializers.ModelSerializer):
stock_list = StringArrayField()
class Meta:
model = Bucket
fields = ("stock_list",)
view.py
class EditBucketSymbols(generics.RetrieveUpdateAPIView):
permission_classes = [IsAuthenticated]
serializer_class = StockListSerializer
queryset = Bucket.objects.all()
def get_object(self, queryset=queryset, **kwargs):
item = self.kwargs.get('slug')
return get_object_or_404(Bucket, slug=item)
url.py:
path('bucket/symbols/<str:slug>/', EditBucketSymbols.as_view(), name='editsymbols')
model.py:
stock_list = ArrayField(models.CharField(max_length=6,null=True),size=30,null=True, blank=True)
You can make use of the field-lever validation technique here,
class StockListSerializer(serializers.ModelSerializer):
stock_list = StringArrayField()
class Meta:
model = Bucket
fields = ("stock_list",)
def validate_stock_list(self, stock_list):
existing_stock_list = []
if self.instance and self.instance.stock_list:
# Patch or Put request
existing_stock_list = self.instance.stock_list
return existing_stock_list + stock_list
I would do this within the .validate() method of the serializer. something like:
def validate(self, validated_data):
# Make sure that this only runs for Patch requests
if self.context["request"].method == "PATCH"
# Get list of existing stocks from instance and append incoming list
stock_list = self.instance.stock_list + validated_data["stock_list"]
# Replace data
validated_data["stock_list"] = stock_list
return validated_data
I have a very simple APIView, but I don't know how to setup pagination here. In this scenario I create a CustomPagination.
pagination_class = CustomPagination works OK when I define queryset at the beginning in generics.ListAPIView, for ex. queryset = Event.objects.all() but not with custom get:
views.py:
class ProductAPIView(APIView):
def get(self, request):
pagination_class = CustomPagination
data = Product.objects.filter(status=1)
product_serializer = ProductSerializers(data,many=True)
productData=[]
for record in product_serializer.data:
value = json.dumps(record)
temp = json.loads(value)
_id = temp['id']
title = temp['title']
sub_title = temp['sub_title']
productData.append({"id":_id, "title":title, "sub_title":sub_title})
return Response({"productData":productData})
pagination.py:
from rest_framework.pagination import PageNumberPagination
class CustomPagination(PageNumberPagination):
page_size = 1
page_size_query_param = 'page_size'
max_page_size = 1000
your views.py should look like this:
class ProductAPIView(APIView):
pagination_class = CustomPagination()
def get(self, request):
queryset = Product.objects.filter(status=1)
# for pagination
page = self.pagination_class.paginate_queryset(queryset=queryset, request=request)
if page is not None:
serializer = ProductSerializers(page, many=True)
return self.pagination_class.get_paginated_response(serializer.data)
serializer = ProductSerializers(queryset, many=True)
return Response(serializer.data)
Why are you using this:
if it is done automatically by serializer
for record in product_serializer.data:
value = json.dumps(record)
temp = json.loads(value)
_id = temp['id']
title = temp['title']
sub_title = temp['sub_title']
productData.append({"id":_id, "title":title, "sub_title":sub_title})
This is my models:
class Order(models.Model):
"""
订单
"""
order_num = models.CharField(max_length=128, unique=True) # 订单编号
order_status = models.CharField(max_length=12) # 订单状态 "未支付", "已支付,未完成", "已完成", "已经删除","其他"
product_describe = models.TextField() # 产品描述
billing_type = models.CharField(max_length=16) # 计费类型
buytime = models.CharField(max_length=16) # 比如:1月 永久
count = models.IntegerField() # 购买数量
paytype = models.CharField(max_length=16) # 支付方式(支付包,微信,xxx)
cost = models.DecimalField(max_digits=8, decimal_places=2, default=0.00) # 费用(需要花费多少钱)
account = models.ForeignKey(to=Account) # 所属账户
ctime = models.DateTimeField(auto_now_add=True) # 创建时间
uptime = models.DateTimeField(auto_now=True) # 更新时间
def __str__(self):
return self.product_describe
def __unicode__(self):
return self.product_describe
This is my serializer:
class OrderCreateSerializer(ModelSerializer):
data_params = serializers.DictField() # 根据产品数据模型不同而异
class Meta:
model = Order
fields = (
"product_describe", # 产品描述 (购买xx产品 + 参数)
"billing_type", # 计费类型 ("包年包月")
# "buytime", # "购买时间"
# "count", # 数量
# "paytype", # 支付方式
"data_params", # 数据
)
def create(self, validated_data):
request = self.context.get("request")
if request and hasattr(request, "user"):
user = request.user
validated_data["order_num"] = generateOrderNum(userid=user.id)
validated_data["order_status"] = "未支付"
data_dic = validated_data.pop("data_params") #
validated_data["buytime"] = data_dic["data"]["buytime"]
validated_data["count"] = data_dic["data"]["count"]
validated_data["paytype"] = "" # 支付类型
validated_data["cost"] = 0.00 # 所需钱
validated_data["account"] = user.account # 是哪个账户
return Order.objects.create(**validated_data)
You see, in my serializer I have pop the data_params:
data_dic = validated_data.pop("data_params")
But when I access this API, I get:
AttributeError at /api/financialmanage/order/add/
Got AttributeError when attempting to get a value for field data_params on serializer OrderCreateSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the Order instance.
Original exception text was: 'Order' object has no attribute 'data_params'.
If I don't pop data_params, I will get bellow error:
TypeError at /api/financialmanage/order/add/
'data_params' is an invalid keyword argument for this function
EDIT
My views.py:
class OrderSerializerCreateAPIView(CreateAPIView):
"""
create Order
"""
serializer_class = OrderCreateSerializer
permission_classes = []
queryset = Order.objects.all()
EDIT-2
In my case the data_params dictionary is necessary for me.
Because when I buy a product(such as CloudServer), which has count, vcpus, ram, disk, and bandwidth, I through the data_params to get that.
You may want to know why I must use data_params to receive the data, because, the product may be different, if the product is Wine, it can not have the vcpus property now.
I resolved it that same to yours:
data_params = serializers.DictField() # yours
data_params = serializers.DictField(write_only=True) # try it, pls.
the source code:
# rest_framework/serializers.py => L504
def to_representation():.
..
fields = self._readable_fields # this function rest_framework/serializers.py=>L371
...
class OrderCreateSerializer(ModelSerializer):
data_params = DictField(child=serializers.CharField())
.
.
.
def create(self, validated_data):
print(validated_data)
data_dic = validated_data.pop("data_params")
print(data_dic)
return super(OrderCreateSerializer, self).create(validated_data)
class OrderSerializer(ModelSerializer):
class Meta:
model = Order
fields = '__all__'
# by #Vasil Shtiliyanov if you want return data_parms after create
def to_representation(self, instance):
serialized_data = super(OrderSerializer, self).to_representation(instance)
serialized_data['data-params'] = #logic goes here
return serialized_data
class OrderSerializerCreateAPIView(CreateAPIView):
"""
create Order
"""
serializer_class = OrderCreateSerializer
permission_classes = []
queryset = Order.objects.all()
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
instance = self.perform_create(serializer)
data = OrderSerializer(instance).data
headers = self.get_success_headers(data )
return Response(data , status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
return serializer.save()
If you want to add the data-params argument to the serializer when it is not a field in the model you should use the def to_representation function of DRF. Should look something like this:
def to_representation(self, instance):
serialized_data = super(SerializerClass, self).to_representation(instance)
serialized_data['data-params'] = #logic goes here
return serialized_data
And remove data-params from fields parameter of the Meta class.
Due to the use of different serializers based on certain condition, i preferred to use APIView and override get function. I was content with APIView but now that i need pagination feature, I am having trouble to make it happen. That is why i want to switch to GenericAPIView but due to the use of multiple serializer I have no idea how can i do it.
class ItemsAPIView(APIView):
permission_classes = (permissions.IsAuthenticated,)
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
def get(self, request, format=None):
"""
Return a list of all devices of this user.
"""
reply = {}
try:
products = BaseItem.objects.owned_items().filter(owner=request.user)
reply['data'] = OwnedItemSerializer(products, many=True).data
items = BaseItem.objects.dev_items().filter(owner=request.user)
reply['data'].extend(ItemSerializer(items, many=True).data)
except:
reply['data'] = []
return Response(reply, status.HTTP_200_OK)
UPDATE
Another way i tried is
class ItemsAPIView(APIView):
permission_classes = (permissions.IsAuthenticated,)
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
def get(self, request, format=None):
"""
Return a list of all items with product of this user.
"""
reply = {}
print ('request', request.META.get('REMOTE_ADDR'))
try:
products = BaseItem.objects.owned_items().filter(owner=request.user)
reply['data'] = OwnedItemSerializer(products, many=True).data
items = BaseItem.objects.dev_items().filter(owner=request.user)
page = self.paginate_queryset(items)
print ('page', page) # i always get None even when pass url as api/items?page=1
if page is not None:
reply['data'].extend(ItemSerializer(page, many=True).data)
reply['data'].extend(ItemSerializer(items, many=True).data)
except:
reply['data'] = []
return Response(reply, status.HTTP_200_OK)
#property
def paginator(self):
"""
The paginator instance associated with the view, or `None`.
"""
if not hasattr(self, '_paginator'):
print (hasattr(self, '_paginator'))
if self.pagination_class is None:
self._paginator = None
else:
self._paginator = self.pagination_class()
return self._paginator
def paginate_queryset(self, queryset):
"""
Return a single page of results, or `None` if pagination is disabled.
"""
print ('queryset', queryset)
if self.paginator is None:
return None
return self.paginator.paginate_queryset(queryset, self.request, view=self)
def get_paginated_response(self, data):
"""
Return a paginated style `Response` object for the given output data.
"""
assert self.paginator is not None
return self.paginator.get_paginated_response(data)
No any way is working. Where have i done mistake?
Do you really need two serializers ?
I think it may be a better choice to use a single Serializer with a custom to_representation:
class ItemSerializer(ModelSerializer):
# Your fields
def to_representation(self, instance):
data = super(ItemSerializer, self).to_representation(instance)
request = self.context.get('request')
if request and instance.is_owned_by(request.user):
return self.owner_to_representation(data, instance) # TO IMPLEMENT
return data
Then, you can use a generic view. Your code is cleaner, simpler and you do not have to worry about the pagination:
class ItemList(generics.ListAPIView):
serializer_class = ItemSerializer
permission_classes = (permissions.IsAuthenticated,)
def get_queryset(self):
return BaseItem.objects.owned_items()| BaseItem.objects.dev_items()
This is as simple as importing your paginator, and calling it manually in the APIView.
class PollView(views.APIView):
authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication,)
paginator = CustomPagination()
def get(self, request):
queryset = Poll.objects.all()
context = self.paginator.paginate_queryset(queryset, request)
serializer = PollSerializer(context, many=True)
return self.paginator.get_paginated_response(serializer.data)
NOTE: Custom class is not necessary, you can simply import from rest_framework.pagination at the top of your script. I created a CustomPagination class, inheriting from PageNumberPagination, so that I could set the page_size query, as per docs - http://www.django-rest-framework.org/api-guide/pagination/
I have a slightly complicated APIView which makes that I can't use a generic ListAPIView to return a queryset. But I can't seem to simply serialize a simple Django queryset using a ModelSerializer, even when I set many=True.
Somehow this doesn't work:
serializers.py:
class SomeModelSerializer(serializers.ModelSerializer):
class Meta:
model = SomeModel
fields = ['some_field']
views.py:
from rest_framework.response import Response
class SomeAPIView(APIView):
serializer_class = SomeInputSerializer
def post(self, request, format=None):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
# first some business logic, then return results
results = SomeModel.objects.all()
output_serializer = SomeModelSerializer(results, many=True)
return Response(output_serializer.data)
All I keep getting is: 'ListSerializer' object is not iterable.
What am I doing wrong?
Error:
/projectfolder/venv/lib/python2.7/site-packages/django/template/defaulttags.py in render
try:
values = self.sequence.resolve(context, True)
except VariableDoesNotExist:
values = []
if values is None:
values = []
if not hasattr(values, '__len__'):
values = list(values) ...
len_values = len(values)
if len_values < 1:
return self.nodelist_empty.render(context)
nodelist = []
if self.is_reversed:
values = reversed(values)
values = list(values) seems to be responsible for the error
Was running into the same problem as you did. I found a quick and simple fix for the error: Copy the serializer data to a new array and return that.
results = SomeModel.objects.all()
output_serializer = SomeModelSerializer(results, many=True)
data = output_serializer.data[:]
return Response(data)
This works for me, hopefully for you as well.
Below works for me using an as_view() url:
class ListCreateMemberViewSet(generics.ListCreateAPIView):
"""
API endpoint that allows multiple members to be created.
"""
queryset = Member.objects.none()
serializer_class = MemberSerializer
def get_queryset(self):
queryset = Member.objects.all()
return queryset
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data, many=isinstance(request.data, list))
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
results = Member.objects.all()
output_serializer = MemberSerializer(results, many=True)
data = output_serializer.data[:]
return Response(data)
The error is a result of trying to run a list on a serializer directly.
For example, this would raise the above exception:
results = Member.objects.all()
output_serializer = MemberSerializer(results, many=True)
all_results = list(output_serializer) # this line here
all_results += output_serializer # or this line here
And this would not (difference is that we are listing output_serializer.data instead of output_serializer):
results = Member.objects.all()
output_serializer = MemberSerializer(results, many=True)
all_results = list(output_serializer.data)
all_results += output_serializer.data
I have the feeling that in the original question, the example code was not 100% matching the actual code.