How to post array data through serializers? - django

I want to post some array data through serializer's create method. How do I get array data in serializer's create method?
this is my result which gives error because of array's data. I have to post belows particular,inch_first...rate arrays data.
{
"order_media": {
"client_employee": "4",
"client": "63",
"narration": "Print ad",
"vendor": "68",
"total_amount": "2590.00",
"discount_rate_client": "10.00",
"discount_amount_client": "259.00",
"service_percentage": "10.00",
"service_amount": "259.00",
"client_vat": "388.50",
"total_receivable": "2978.50",
},
"pub_date": "2019-04-03",
"particular": ["Banner", "Poster", "Plastic banner"],
"inch_first": ["4", "5", "3"],
"inch_second": ["4", "5", "3"],
"size": ["16.00", "25.00", "9.00"],
"quantity": ["5", "5", "6"],
"rate": ["10", "10", "10"],
}
This is my model
class RequestPrintProduction(models.Model):
particular = models.CharField(max_length=255,null=True,blank=True)
inch_first = models.CharField(max_length=45,null=True,blank=True)
inch_second = models.CharField(max_length=45,null=True,blank=True)
size = models.CharField(max_length=45,blank=True, null=True)
quantity = models.CharField(max_length=45,null=True, blank=True)
rate = models.DecimalField(max_digits=12, decimal_places=2, default=Decimal(0.00), null=True, blank=True)
pub_date = models.DateField(blank=True, null=True)
vendor = models.ForeignKey(Party, blank=True, null=True)
request_id = models.ForeignKey(Request, blank=True, null=True, related_name='requestprint')
def __str__(self):
return self.particular
this is my api views:
class PrintRequestAPIView(ListBulkCreateAPIView):
serializer_class = RequestPrintSerializer
this is my serializer:
class RequestPrintSerializer(serializers.ModelSerializer):
order_media = OrderMediaSerializer(required=False)
class Meta:
model = RequestPrintProduction
fields = '__all__'
def create(self, validated_data):
service_request = Request()
service_request.save()
validated_data['request_id'] = service_request
order_media = validated_data.pop('order_media')
print(order_media)
online_service_request = self.Meta.model.objects.create(**data)
order_media['request_id'] = service_request
order_media = OrderMedia.objects.create(**order_media)
return online_service_request
I expect to post array's data successfully.

Let's try to arrange the post data like below. And then whenever you are initializing RequestPrintSerializer for this request initialize the serializer with many=True. Hope it helps. Good lucks.
{
"order_media": {
"client_employee": "4",
"client": "63",
"narration": "Print ad",
"vendor": "68",
"total_amount": "2590.00",
"discount_rate_client": "10.00",
"discount_amount_client": "259.00",
"service_percentage": "10.00",
"service_amount": "259.00",
"client_vat": "388.50",
"total_receivable": "2978.50"
},
"request_print_production": [
{
"pub_date" : "2019-04-03",
"particular": "Banner",
"inch_first": "4",
"inch_second": "4",
"size": "16.00",
"quantity": "5",
"rate": "10"
},
{
"pub_date" : "2019-04-03",
"particular": "Poster",
"inch_first": "5",
"inch_second": "5",
"size": "25.00",
"quantity": "5",
"rate": "10"
},
{
"pub_date" : "2019-04-03",
"particular": "Plastic Banner",
"inch_first": "3",
"inch_second": "3",
"size": "9.00",
"quantity": "6",
"rate": "10"
}
]
}

It seems like you have a char field in your database, but want to post an array to DRF. In fact, as the comments mentioned, this is an issue with nearly all your fields. You seem to get around this by just passing strings in everywhere.
Without database support this won't work directly. However, if you do really want to do this then you are going to have to do some of the work in your own code. There are custom fields for this out there somewhere, probably, but I've never seen 'em.
JSON Post Only
Here's an easy way to go about doing that:
Declare the field on your serializer directly as a ListField
In the 'validate' function, convert to a string (csv, json.dumps, etc)
Note that I'm not checking for empty values, settings defaults, etc. Please read the documentation (and source) for ListField on your own.
class Serializer(ModelSerializer):
particular = ListField(child=CharField(max_length=10))
def validate_particular(self, value):
"""Convert the list to a json string, and do extra validation if needed"""
return json.dumps(value)
Input & Output (Post & Get)
If you are using this both for input and output, and want to return a list from your model, you are going to need to make a custom field:
class ListAsJsonField(fields.ListField):
"""
Converts an incoming list of string values to json.
This field is _very_ primitive, lots of edge cases may break it.
"""
child = fields.CharField(min_length=1) # always use a char field
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def to_representation(self, data):
"""Convert to our output representation. Assumes input is always valid json now"""
return json.loads(data)
def to_internal_value(self, data):
"""Convert to the internal value -> database (and validated_data)"""
return json.dumps(data)
class MySerializer(serializers.ModelSerializer):
particular = ListAsJsonField(required=True, min_length=1)
...
Notes:
Some databases like postgres have a custom JSON field & Array field. Django ships with fields for those in django.contrib.postgres. Using these would imply you also want to fix your other model issues.
What the heck is ListBulkCreateAPIView?

Related

When including resources DRF returns my soft deleted records

I have DRF API that supports including the books of an author in an authors get request.
Our API has a soft delete system where Book(2) marked as deleted.
But when I do the request below Book(2) is still included in the response.
I would like to have only Book(1) in the response of this request.
GET http://localhost/authors/2?include=books
API returns to me:
{
"data": {
"type": "Author",
"id": "2",
"attributes": {...},
"relationships": {
"books": {
"meta": {
"count": 2
},
"data": [
{
"type": "Book",
"id": "1"
},
{
"type": "Book",
"id": "2"
}
]
}
}
},
"included": [
{
"type": "Book",
"id": "1",
"attributes": {...}
},
{
"type": "Book",
"id": "2",
"attributes": {...}
}
]
}
I have a BaseModel that handles the soft deletion overriding the delete method:
class BaseModel(Model):
archive = BoolYesNoField(db_column="archive", default=False, null=False)
created = DateTimeField(db_column="created", auto_now_add=True, null=False)
updated = DateTimeField(db_column="updated", auto_now=True, null=False)
objects = BaseManager()
all_objects = BaseManager(alive_only=False)
def delete(self):
self.archive = True
self.save()
relate_obj_delete(self)
def hard_delete(self):
super(Model, self).delete()
def undelete(self):
self.archive = False
self.save()
Manager
class BaseManager(Manager):
def __init__(self, *args, **kwargs):
self.alive_only = kwargs.pop("alive_only", True)
super(BaseManager, self).__init__(*args, **kwargs)
def get_queryset(self):
if self.alive_only:
return BaseQuerySet(self.model).filter(archive=False)
return BaseQuerySet(self.model)
def hard_delete(self):
return self.get_queryset().hard_delete()
AuthorViewSet
class AuthorViewSet(BaseViewSet):
queryset = Author.objects.all()
serializer_class = AuthorSerializer
filterset_class = AuthorFilterSet
Serializer
class AuthorSerializer(BaseSerializer):
books = ResourceRelatedField(many=True, read_only=True)
included_serializers = {
"books": BookSerializer,
}
class Meta(BaseSerializer.Meta):
model = Author
If someone could just point me out in the right direction I would be really helpful.
There is probably a function that I need to override somewhere but I can't seem to find it.
Your issue is in your serializer. By default Django will use Model._base_manager to fetch related objects and not your custom manager. You need to specify in your nested ResourceRelatedField field which manager you would like to use
class AuthorSerializer(BaseSerializer):
books = ResourceRelatedField(
queryset=Book.objects,
many=True,
read_only=True
)
...

How do I write to nested parent in Django Serializer

I'm using Django serializers to create an API which I both read from and write to.
Models:
class Biomarker(models.Model):
low_value_description = models.TextField(blank=True, null=True)
high_value_description = models.TextField(blank=True, null=True)
class BiomarkerReading(models.Model):
biomarker = models.ForeignKey(Biomarker, on_delete=models.CASCADE)
test = models.ForeignKey(Test, on_delete=models.CASCADE)
value = models.DecimalField(max_digits=30, decimal_places=8, default=0)
Serializer:
class BiomarkerReadingSerializer(serializers.ModelSerializer):
class Meta:
model = BiomarkerReading
fields = (
'id', 'test', 'biomarker', 'value'
)
JSON format:
{
"id": 617188,
"test" 71829,
"biomarker": 32,
"value": 0.001
}
The above all works, and I can read and write to it with that JSON format. However I now need to add some fields from the parent model so the response looks like this:
{
"id": 617188,
"test" 71829,
"biomarker": {
"id": 32,
"low_value_description": "All good",
"high_value_description": "You will die",
},
"value": 0.001
}
I have got the read part working using these Serializers:
class BiomarkerDescriptionSerializer(serializers.ModelSerializer):
class Meta:
model = Biomarker
fields = ('id', 'low_value_description', 'high_value_description')
class BiomarkerReadingSerializer(serializers.ModelSerializer):
biomarker = BiomarkerDescriptionSerializer()
class Meta:
model = BiomarkerReading
fields = (
'id', 'test', 'biomarker', 'value'
)
However I can't find a way to write to it using the old json format (with "biomarker": 32 in the JSON).
I thought I would need to do something in validate or create, but I get a 400 response before it even hits those methods:
class BiomarkerReadingSerializer(serializers.ModelSerializer):
... # as above
def validate(self, data):
print('validate') # Doesn't print
data = super().validate(data)
return data
def create(self, validated_data):
print('create') # Doesn't print
return super().create(validated_data)
The example in the docs for writable-nested-serializers and the other examples I've found on SO only discuss the case of creating child records while writing to the parent record serializer, not the other way around.
I do not want to create a parent Biomarker via the API, I just need to be able to reference it by pk/id in the incoming JSON like before.
I don't mind if I have to change the names of keys to something like this for incoming:
{
"id": 617188,
"test" 71829,
"biomarker_id": 32,
"value": 0.001
}
Or something like this for the response:
{
"id": 617188,
"test" 71829,
"biomarker": 32,
"descriptions": {
"low_value_description": "All good",
"high_value_description": "You will die",
},
"value": 0.001
}
If that makes it easier.
I hate answering my own question, but the solution was to subclass serializers.RelatedField, which is explained in advanced-serializer-usage).
This lets you separately control how the value(instance) is represented as serialised, and how the serialised data is used to retrieve or create a value (instance).
class BiomarkerDescriptionSerializer(serializers.RelatedField):
def to_representation(self, value):
data = {}
for field in ('id', 'low_value_description', 'high_value_description'):
data[field] = getattr(value, field)
return data
def to_internal_value(self, data):
return Biomarker.objects.get(id=data)
def get_queryset(self, *args):
pass
class BiomarkerReadingSerializer(serializers.ModelSerializer):
biomarker = BiomarkerDescriptionSerializer()
...
The overridden get_queryset is required, even though it does nothing.
This means data going in looks like this:
{
"id": 617188,
"test" 71829,
"biomarker": 32,
"value": 0.001
}
Yet the data going out looks like this:
{
"id": 617188,
"test" 71829,
"biomarker": {
"id": 32,
"low_value_description": "All good",
"high_value_description": "You will die",
},
"value": 0.001
}
Thank you to those who offered answers, much appreciated
Using depth = 1 option will solve your problem.
class BiomarkerReadingSerializer(serializers.ModelSerializer):
class Meta:
model = BiomarkerReading
fields = (
'id', 'test', 'biomarker', 'value'
)
depth = 1
Take a look: https://www.django-rest-framework.org/api-guide/serializers/#specifying-nested-serialization

How can i display detailed data from many to many field instead of objects in django rest API , currently im getting only objects of it

I'm new to Django,i tryed to list all invoices from my model Invoices and im getting items as only its object, to make the code efficient i need to get the whole data of items in a single query how can i get the details of items along with the data instead of item objects
Here is what i have tryed
models.py
class ItemsInvoice(models.Model):
invoiceId = models.CharField(max_length=20)
product_Id = models.CharField(max_length=20)
item_price = models.CharField(max_length=20)
class Invoices(models.Model):
customer = models.CharField(max_length=10,null=True)
total_amount = models.CharField(max_length=12,null=True)
items = models.ManyToManyField(ItemsInvoice,related_name="item_invoice")
views.py
class InvoiceView(ListAPIView):
serializer_class = InvoiceSerializers
def get_queryset(self):
# queryset = Invoices.objects.prefetch_related('items').all().order_by('-id')
queryset = Invoices.objects.all().order_by('-id')
return queryset
serializers.py
class InvoiceSerializers(serializers.ModelSerializer):
class Meta:
model = Invoices
fields = '__all__'
if i run it on my postman, its response will be like this
API response what i have
[
{
"id": 69,
"customer": "4",
"total_amount": "25000",
"items": [
66,
67,
68
]
}
]
but actually i want my output like this , ie the item field should list all data inside in it
API response what i want
[
{
"id": 69,
"customer": "4",
"total_amount": "25000",
"items": [
{
"id": 66,
"invoiceId": "69",
"product_Id": "3",
"item_price": "300",
},
{
"id": 67,
"invoiceId": "69",
"product_Id": "4",
"item_price": "200",
},
{
"id": 68,
"invoiceId": "69",
"product_Id": "4",
"item_price": "200",
}
]
}
]
how can i achieve it by using django-orm or raw query
You must define ItemInvoiceSerializer and set many=true.
class ItemInvoiceSerializer(serializers.ModelSerializer):
class Meta:
model = ItemsInvoice
fields = '__all__'
class InvoiceSerializers(serializers.ModelSerializer):
items = ItemInvoiceSerializer(many=True) # i changed true to True
class Meta:
model = Invoices
fields = '__all__'

How to change default behavior of serializers

I am doing some stuff with the django restframework. Very basic now, but I like to have my data coming back to me bit differently.
This is how I get the response now:
[
{
"line": "line1",
"user_text": "Some text",
"topic": "value/xrp"
},
{
"line": "line2",
"user_text": "Some text 2",
"topic": "beer/heineken/sale"
}
]
This is what I like to get:
{
"line1": {
"user_text": "Some text",
"topic": "value/xrp"
},
"line2": {
"user_text": "Some text 2",
"topic": "beer/heineken/sale"
}
}
This is my serializer:
class LineSerializer(serializers.Serializer):
line = serializers.CharField(max_length=16)
user_text = serializers.CharField(max_length=16)
topic = serializers.CharField()
This is the view
class DisplayDetailAPIView(ListAPIView):
serializer_class = LineSerializer
def get_queryset(self):
return Line.objects.filter(display__serial_number=self.kwargs['serial_number'])
And the model (as reference)
class Line(models.Model):
display = models.ForeignKey(Display, on_delete=models.CASCADE, related_name='lines')
line = models.CharField(max_length=16)
user_text = models.CharField(max_length=16, null=True, blank=True)
topic = models.ForeignKey(Topic, on_delete=models.CASCADE, blank=True, null=True)
def __str__(self):
return self.line
I looked in the rest framework documentation and a bit here on stackoverflow but I could not find an answer yet.
If someone has hints for me that would be very much appreciated :)
You can override to_representation in your serializer class:
class LineSerializer(serializers.ModelSerializer):
class Meta:
model = Line
fields = ('user_text', 'topic')
def to_representation(self, instance):
data = super().to_representation(instance)
response_data = {}
response_data[instance.line] = data
return response_data
You just create a temporary data dictionary and assign the current instance line attribute to be the key of your JSON response - what you actually want to have in the end:
[
{
"line1": {
"user_text": "lorem ipsum",
"topic": ...
}
},
{
"line2": {
"user_text": "dolor sin amet",
"topic": ...
}
}
]
Note 1: You may want to use a ModelSerializer instead of Serializer in this particular case, I think it is more straightforward to use and you can access instance.line directly, without specifying it actually to be serialized.
Note 2: Here your topic field will be serialized as an id since it is a ForeignKey relation on a database level. If you want to serialize topic as a CharField from one of the actual topic's fields, have a look here.

Django - Retrieve nested fields multiple levels deep using foreign keys

I'm struggling to write a Django GET that returns the following looking response:
{
"lists": [
{
"id": "123",
"list_order": [
{
"id": "123_1",
"order": 1,
"list_id": "123",
"item_id": 9876,
"item": {
"id": 9876,
"name": "item1",
"location": "California"
}
},
{
"id": "123_2",
"order": 2,
"list_id": "123",
"item_id": 2484,
"item": {
"id": 2484,
"name": "item2",
"location": "California"
}
}
],
"updated_date": "2018-03-15T00:00:00Z"
}
]
}
Given a list_id, the response returns the basic information on the list ("id", "updated_date"), as well as the order of items in the list. Inside each item in the list order, it also grabs the related item details (nested in "item"). I'm able to get this response without the "item" details ("id", "name", "location" fields) and with no error:
{
"lists": [
{
"id": "123",
"list_order": [
{
"id": "123_1",
"order": 1,
"list_id": "123",
"item_id": 9876
},
{
"id": "123_2",
"order": 2,
"list_id": "123",
"item_id": 2484
}
],
"updated_date": "2018-03-15T00:00:00Z"
}
]
}
Again there is no error, and I can retrieve the first nested level without any issue. The problem is getting the "item" information to show within each "list_order". Below are my models, serializers, and views.
models.py
class Lists(models.Model):
id = models.CharField(null=False, primary_key=True, max_length=900)
updated_date = models.DateTimeField(blank=True, null=True)
class Meta:
managed = False
db_table = 'tbl_lists'
class Items(models.Model):
id = models.BigIntegerField(primary_key=True)
name = models.TextField(blank=True, null=True)
location = models.TextField(blank=True, null=True)
class Meta:
managed = False
db_table = 'tbl_items'
class ListOrder(models.Model):
id = models.CharField(null=False, primary_key=True, max_length=900)
list_id = models.ForeignKey(Lists, db_column='list_id', related_name='list_order')
item_id = models.ForeignKey(Items, db_column='item_id', related_name='items')
order = models.BigIntegerField(blank=True, null=True)
class Meta:
managed = False
db_table = 'tbl_list_order'
serializers.py
class ItemsSerializer(serializers.ModelSerializer):
class Meta:
model = Items
fields = '__all__'
class ListOrderSerializer(serializers.ModelSerializer):
item = ItemsSerializer(many=False, read_only=True)
class Meta:
model = ListOrder
fields = '__all__'
class ListsSerializer(serializers.ModelSerializer):
list_order = ListOrderSerializer(many=True, read_only=True)
class Meta:
model = Lists
fields = '__all__'
views.py
class ListsViewSet(generics.ListCreateAPIView):
"""
API endpoint that returns a list with its meta-information
"""
queryset = Lists.objects.all()
serializer_class = ListsSerializer
def get_queryset(self):
list_id = self.kwargs['list_id']
filters = [Q(id=list_id)]
return Lists.objects.filter(*filters)
def list(self, request, list_id):
queryset = self.get_queryset()
list_serializer = ListsSerializer(queryset, many=True)
return Response({ 'lists': list_serializer.data })
I'm pretty new to Django and like what it offers so far, though maybe I'm thinking of doing this in too much of a "SQL" way. I've read about select_related() and prefetch_related(), but not sure how I would apply it to this case. Any assistance is greatly appreciated and let me know if there's any other information I can provide.
In your ListOrderSerializer you are trying to serialize item. while in ListOrder model you used the field name item_id
Solution:
In ListOrderSerializer:
class ListOrderSerializer(serializers.ModelSerializer):
item_id = ItemsSerializer(many=False, read_only=True)
...