I have an array of choice fields. I want the human-readable text as a response instead of just numbers.
How to deserialize an array choice field in Django Rest Frameworks?
Response
"categories": [
1,
2
],
Expected Response
"categories": [
"Carpentry",
"Interior"
],
class ChoiceArrayField(ArrayField):
def formfield(self,**kwargs):
defaults = {
'form_class': forms.TypedMultipleChoiceField,
'choices': self.base_field.choices,
}
defaults.update(kwargs)
return super(ArrayField, self).formfield(**defaults)
Model.py
categories_choices = (
(1,"Carpentry"),
(2,"Interior"),
(3,"Furniture"),
(4,"Plumber"),
(5,"Electrican"),
(6,"Construction"),
(7,"Painting"),
(8,"Flooring"),
(9,"Yard Settings")
)
categories = ChoiceArrayField(
base_field = models.CharField(max_length=32,choices=categories_choices, null=True,blank=True),
default=list,
null=True, blank=True
)
Serializer.py
class ProfileSerializer(serializers.ModelSerializer):
phone_number = serializers.IntegerField(source='phone.phonenum')
id = serializers.IntegerField(source='phone.id')
class Meta:
exclude = ('phone',)
model = ProfileModel
You can do like
class ProfileSerializer(serializers.ModelSerializer):
phone_number = serializers.IntegerField(source='phone.phonenum')
id = serializers.IntegerField(source='phone.id')
class Meta:
exclude = ('phone',)
model = ProfileModel
def to_representation(self, instance):
response = super().to_representation(instance)
categories = instance.get_categories_display()
response["categories"] = categories
return response
Related
I am trying to work with a post request were I am first saving a Tag object, which then becomes the tag field of a Tagging object. However, no matter what combinations I have tried, despite the Tag Post method working, when I pass a json object like this:
{
"user_id": 1,
"gameround_id": 2015594866,
"resource_id": 2975,
"tag": {
"name": "TESTTAGGGG2222",
"language": "en"
},
"score": 0,
"origin": ""
}
I keep getting this message:
{
"name": [
"This field is required."
],
"language": [
"This field is required."
]
}
However, when I pass a Tag, it works.
This is the post method:
def post(self, request, *args, **kwargs):
if not isinstance(request.user, CustomUser):
current_user_id = 1
else:
current_user_id = request.user.pk
gameround = request.data.get('gameround', '')
random_resource = request.data.get('resource', '')
created = datetime.now()
score = 0
origin = ''
name = request.data.get('name', '')
language = request.data.get('language', '')
tag_serializer = TagSerializer(data=request.data)
tagging_serializer = TaggingSerializer(data=request.data)
if tag_serializer.is_valid(raise_exception=True):
tag_serializer.save(tag=request.data)
if tagging_serializer.is_valid(raise_exception=True):
tagging_serializer.save(tagging=request.data, tag=tag_serializer.data)
return Response({"status": "success", "data": tagging_serializer.data},
status=status.HTTP_201_CREATED)
# else:
# return Response({"status": "success", "data": tag_serializer.data},status=status.HTTP_201_CREATED)
else:
return Response({"status": "error", "data": tag_serializer.errors},
status=status.HTTP_400_BAD_REQUEST)
How do I correctly pass the nested object in the post method so that I don't get this error anymore?
models.py
class Tag(models.Model):
name = models.CharField(max_length=256)
language = models.CharField(max_length=256)
objects = models.Manager()
def __str__(self):
return self.name or ''
#property
def tags(self):
tags = self.tagging.values('tag')
return tags.values('tag_id', 'tag__name', 'tag__language')
class Tagging(models.Model):
user = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, null=True)
gameround = models.ForeignKey(Gameround, on_delete=models.CASCADE, related_name='taggings')
resource = models.ForeignKey(Resource, on_delete=models.CASCADE, related_name='taggings')
tag = models.ForeignKey(Tag, on_delete=models.CASCADE, related_name='tagging')
created = models.DateTimeField(editable=False, null=True)
score = models.PositiveIntegerField(default=0)
# media_type = models.ForeignKey(Gamemode, on_delete=models.CASCADE)
origin = models.URLField(max_length=256, blank=True, default='')
objects = models.Manager()
def __str__(self):
return str(self.tag) or ''
serializers.py
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ('name', 'language')
def create(self, validated_data):
tag_data = validated_data.pop('tag')
Tag.objects.create(**tag_data)
return tag_data
def to_representation(self, data):
data = super().to_representation(data)
return data
class TaggingSerializer(serializers.ModelSerializer):
tag = TagSerializer(required=False, write_only=False)
resource_id = serializers.PrimaryKeyRelatedField(queryset=Resource.objects.all(),
required=True,
source='resource',
write_only=False)
gameround_id = serializers.PrimaryKeyRelatedField(queryset=Gameround.objects.all(),
required=False,
source='gameround',
write_only=False)
user_id = serializers.PrimaryKeyRelatedField(queryset=CustomUser.objects.all(),
required=False,
source='user',
write_only=False)
class Meta:
model = Tagging
fields = ('id', 'user_id', 'gameround_id', 'resource_id', 'tag', 'created', 'score', 'origin')
depth = 1
def create(self, validated_data):
"""Create and return a new tagging"""
tag_data = validated_data.pop('tag', None)
if tag_data:
tag = Tag.objects.get_or_create(**tag_data)[0]
validated_data['tag'] = tag
tagging = Tagging(
user=validated_data.get("user"),
gameround=validated_data.get("gameround"),
resource=validated_data.get("resource"),
tag=validated_data.get("tag"),
created=datetime.now(),
score=validated_data.get("score"),
origin=validated_data.get("origin")
)
tagging.save()
return tagging
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['tag'] = TagSerializer(instance.tag).data
return rep
In your post method, you made
tag_serializer = TagSerializer(data=request.data)
if tag_serializer.is_valid(raise_exception=True):
# the rest of the code
this means that your data will pass to TagSerializer so DRF will check if the fields for this serializer are in the request body or not, and it's not provided, cause this data does not belong to this serializer, it belongs to TaggingSerializer so this will give you an error This field is required
So, you need to send your request data only to TaggingSerializer, try that and let's see what will happen, and I will suggest using serializers.Serializer instead of using serializers.ModelSerializer for better performance
I've a model:
class ListingPrice(Timestamps):
price = models.ForeignKey("Price", on_delete=models.CASCADE)
location = models.ForeignKey("location", on_delete=models.CASCADE)
class Meta:
unique_together = ["price", "location"]
class Price(Timestamps):
package = models.ForeignKey("products.Package", on_delete=models.CASCADE)
locations = models.ManyToManyField("location", through="ListingPrice")
price = models.DecimalField(max_digits=11, decimal_places=3)
with a serializer:
class LocationSerializer(serializers.ModelSerializer):
name = LocalizedField()
class Meta:
model = location
fields = ['id', 'name']
class PriceSerializer(serializers.ModelSerializer):
locations = LocationSerializer(many=True, read_only=True)
class Meta:
model = Price
fields = ['package', 'locations', 'price']
def create(self, validated_data):
print("validated_data, validated_data)
and viewset:
class PriceViewSet(ModelViewSet):
queryset = Price.objects.all()
serializer_class = PriceSerializer
ordering = ['id']
permissions = {
"GET": ["view_minimum_listing_price", ],
"POST": ["add_minimum_listing_price", ],
'PUT': ['update_minimum_listing_price', ],
'DELETE': ['delete_minimum_listing_price', ],
}
In testing I'mm using the following:
data = {
"price": 15,
}
response = self.client.put(path=self.url, data=data, format='json', args=[1])
I'm trying to update the price in the instance with id 1, but neither put or update is not allowed? How to overcome this and update it?
edit: urls.py
router = SimpleRouter()
router.register('listing_price', PriceViewSet, basename='listing_price')
Please help me improve my code.. im newly placed and learning things
i want to display separately product field in my code just like category and sub-category in my code but i don't know how to. Also please point out if there anything that i can improve in my code
Here is my output:
[
{
"Category": {
"c_id": 1,
"parent_id": 15
},
"Sub_category": {
"sc_id": 1,
"is_active2": 1
},
"p_id": 2,
"group_id": "",
"product_name": "Fruit",
"is_prescription_needed": 1,
}]
Whereas my expected format is:
[
{
"Category": {
"c_id": 1,
"parent_id": 15
},
"Sub_category": {
"sc_id": 1,
"is_active2": 1
},
"Product": { # How do i separate this "Product" key and value
"p_id": 2,
"group_id": "",
"product_name": "Fruit",
"is_prescription_needed": 1
}]
here is my code:
Serialziers.py
class ProductCategorySerializer(serializers.ModelSerializer):
c_id = serializers.SerializerMethodField()
category_image = serializers.SerializerMethodField()
class Meta:
fields = ["c_id","parent_id","category","category_image","is_active"]
model = Category
def get_c_id(self,obj):
return obj.id
def get_category_image(self,obj):
return obj.image
class ProductSubCategorySerializer(serializers.ModelSerializer):
sc_id = serializers.SerializerMethodField()
is_active2 = serializers.SerializerMethodField()
class Meta:
fields = ["sc_id","sub_category","is_active2"]
model = ProductSubCategory
def get_sc_id(self,obj):
return obj.id
def get_is_active2(self,obj):
return obj.is_active
class ProductSerializer(serializers.ModelSerializer):
Category = ProductCategorySerializer(source='category', read_only=True)
Sub_category = ProductSubCategorySerializer(source='sub_category', read_only=True)
product_image = serializers.SerializerMethodField()
p_id = serializers.SerializerMethodField()
class Meta:
fields = ["Category","Sub_category", "p_id", "group_id", "product_name", "is_prescription_needed",
"product_description", "product_keypoint", "product_type", "product_image", "pack", "pack_unit", "hsn",
"company", "brand_id", "composition", "drug_details", "uses",
"side_effects", "cope_side_effects", "how_it_works", "safety_advice", "what_if_forget",
"medical_constraints", "created_at", "created_by", "modified_at", "modified_by"]
model = Product
def get_product_image(self, obj: Product):
image = obj.image
if len(image) == 0:
return ""
return image
def get_p_id(self,obj):
return obj.id
View.py
class categoryProduct(generics.GenericAPIView):
def post(self, request):
params = request.query_params
category = params.get("category")
sub_category = params.get("sub_category")
kwargs = {}
if category:
kwargs['category'] = category
else:
kwargs['category'] = 1
if sub_category:
kwargs['sub_category'] = sub_category
else:
kwargs['sub_category'] = 1
queryset = Product.objects.filter(**kwargs)
all_response = ProductSerializer(queryset, many=True)
#AFTER PAGINATION
return Response(all_response)
models.py
class Product(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE, db_column='category')
sub_category = models.ForeignKey(ProductSubCategory, on_delete=models.CASCADE, db_column='sub_category')
group_id = models.CharField(max_length=200)
product_name = models.CharField(max_length=500)
is_prescription_needed = models.IntegerField()
...
class Meta:
managed = False
db_table = 'product'
class Category(models.Model):
category = models.CharField(max_length=50)
image = models.CharField(max_length=500)
store_image = models.CharField(max_length=1000)
is_active = models.IntegerField()
parent_id = models.IntegerField()
class Meta:
managed = False
db_table = 'category'
class ProductSubCategory(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE, db_column="category")
sub_category = models.CharField(max_length=250)
is_active = models.IntegerField()
class Meta:
managed = False
db_table = 'sub_category'
You can make a separate field that is a a SerializerField of a serializer you create with the proper fields you want displayed under the Product key. This is how your other field, like Category get appropriately created. That is to say: remove the fields from the ProductSerializer into a new serializer, then add a Product SerializerField with the new serializer containing those keys.
One other way you can do this is to create a .to_representation method to change the output to precisely the format you want.
class ProductSerializer(serializers.ModelSerializer):
...
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['Product'] = {
"p_id": representation.pop('p_id')
"group_id": representation.pop('group_id'),
"product_name": representation.pop('product_name'),
"is_prescription_needed": representation.pop('is_prescription_needed')
}
return representation
I have two serializers like below. The output for the below snippet is Workers and with associated Ticket Counter details with all fields (ticket_counter,ticket_counter_name,worker). But I just need only one field that is ticket_counter_name.
class WorkerSerializer(serializers.ModelSerializer):
ticket_counter = WorkerToCounterSerializer(many=True, read_only=True)
class Meta:
model = User
fields = (
'username',
'ticket_counter',
)
class WorkerToCounterSerializer(serializers.ModelSerializer):
ticket_counter = SerializerMethodField()
ticket_counter_name = serializers.CharField(source='ticket_counter.ticket_counter_name')
class Meta:
model = WorkerToTicketCounter
list_serializer_class = FilteredListSerializer
fields = (
'ticket_counter',
'ticket_counter_name',
'worker',
)
def get_ticket_counter(self, obj):
return obj.ticket_counter.pk
class FilteredListSerializer(ListSerializer):
def to_representation(self, data):
data = data.filter(worker_to_ticket_counter_is_deleted=False)[:1]
return super(FilteredListSerializer, self).to_representation(data)
What above snippet outputs
{
"username": "xxxxxxxxxxx",
"ticket_counter": [
{
"ticket_counter": 7,
"ticket_counter_name": "Entrance Counter",
"worker": 4,
}
]
}
But What I want is
{
"username": "xxxxxxxxxxx",
"ticket_counter": "Entrance Counter"
}
I just need the name of the ticket_counter_name. In my case, there can't be two ticket_counters for a worker. Obviously, it gives only one ticket_counter. Is it possible?
EDIT: using string StringRelatedField
{
"username": "xxxxxxxxxxx",
"ticket_counter": [
"Entrance Counter",
"xxxxxxxxxxxxxxxx",
"xxxxxxxxxxxxxxxx",
"xxxxxxxxxxxxxxxx"
]
}
EDIT: WorkerToTicketCounter Model
class WorkerToTicketCounter(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
ticket_counter = models.ForeignKey(TicketCounter, related_name="workers")
worker = models.ForeignKey(User, related_name='ticket_counter')
worker_to_ticket_counter_is_deleted = models.BooleanField(default=False)
If I'm understood correctly, you only need a SerializerMethodField to perform both filtering and string represantion.
class WorkerSerializer(serializers.ModelSerializer):
ticket_counter = serializers.SerializerMethodField(read_only=True)
def get_ticket_counter(self, user):
qs = user.ticket_counter.filter(worker_to_ticket_counter_is_deleted=False)
if qs.exists() and hasattr(qs.first().ticket_counter, 'ticket_counter_name'):
return qs.first().ticket_counter.ticket_counter_name
return None
class Meta:
model = User
fields = ('username', 'ticket_counter',)
You can use StringRelatedField:
class WorkerSerializer(serializers.ModelSerializer):
ticket_counter = StringRelatedField(many=True, read_only=True)
class Meta:
model = User
fields = (
'username',
'ticket_counter',
)
Note to use StringRelatedField you should add __str__ method to your WorkerToTicketCounter model:
class WorkerToTicketCounter:
...
def __str__(self):
return self.ticket_counter.ticket_counter_name
How to serialize a fields in related models.
I got a models:
class Order(models.Model):
order_id = models.BigIntegerField(verbose_name='Order ID', unique=True)
order_name = models.CharField(verbose_name='Order name', max_length=255)
order_type = models.IntegerField(verbose_name='Campaign type')
class Types(models.Model):
delimiter = models.CharField(verbose_name='Delimiter', max_length=255)
status = models.BooleanField(verbose_name='Status', default=True)
title = models.CharField(verbose_name='Title', max_length=255)
class User(models.Model):
name = models.CharField(verbose_name='User name', max_length=200, unique=True)
class Report(models.Model):
order = models.ForeignKey(Order, to_field='order_id', verbose_name='Order ID')
user = models.ForeignKey(User, verbose_name='User ID')
ad_type = models.ForeignKey(Types, verbose_name='Type')
imp = models.IntegerField(verbose_name='Total imp')
month = models.DateField(verbose_name='Month', default=datetime.datetime.today)
View:
class ReportLisAPIView(ListAPIView):
serializer_class = ReportSerializer
def get_queryset(self):
month = parse_date(self.kwargs['month']) - relativedelta(day=1)
queryset = (
Report.objects.filter(month=month)
.values_list(
'user', 'user__name', 'order__order_id',
'order__order_name', 'order__order_type'
).all().annotate(Sum('imp'))
)
return queryset
Serializer:
class ReportSerializer(ModelSerializer):
class Meta:
model = Report
depth = 1
I need to get all field like in 'queryset' in get_queryset()
but I got an error:
Got AttributeError when attempting to get a value for field imp on
serializer ReportSerializer. The serializer field might be named
incorrectly and not match any attribute or key on the tuple
instance. Original exception text was: 'tuple' object has no attribute
'imp'.
But if I return in get_queryset() just Report.objects.filter(month=month).all() I'll get all objects and related object with all field, without aggregate of imp and not grouping.
So the question is how to make serializer return structure that set in queryset?
The get_queryset method requires to return a queryset but you are returning a tuple beacause of values_list. Either drop it to return a queryset or go with a more generic view like APIView.
I found a way how to do it.
As I use .values_list() it return list object instead of queryset object. So for serializer do understand what is inside the list I defined all fields in serializer. And in to_representation() I return dictionary like it should be.
Serializer:
class ReportSerializer(serializers.ModelSerializer):
user = serializers.IntegerField()
user_name = serializers.CharField()
order_id = serializers.IntegerField()
order_name = serializers.CharField()
order_type = serializers.IntegerField()
imp = serializers.IntegerField()
class Meta:
model = Report
fields = [
'user', 'user_name', 'order_id', 'order_name',
'order_type', 'imp'
]
depth = 1
def to_representation(self, instance):
Reports = namedtuple('Reports', [
'user',
'user_name',
'order_id',
'order_name',
'order_type',
'imp',
])
return super(ReportSerializer, self).to_representation(
Reports(*instance)._asdict()
)
View:
class ReportLisAPIView(ListAPIView):
serializer_class = ReportSerializer
def get_queryset(self):
month = parse_date(self.kwargs['month']) - relativedelta(day=1)
queryset = (
Report.objects.filter(month=month)
.values_list(
'user', 'user__name', 'order__order_id',
'order__order_name', 'order__order_type'
).all().annotate(Sum('imp'))
)
return queryset
def list(self, *args, **kwargs):
queryset = self.get_queryset()
serializer = self.serializer_class(queryset, many=True)
# actualy that's it! part of which is below can be pass and just
# return Response(serializer.data)
result = {
'month': parse_date(self.kwargs['month']).strftime('%Y-%m'),
'reports': []
}
inflcr = {}
for item in serializer.data:
inflcr.setdefault(item['user'], {
'id': item['user'],
'name': item['user_name'],
'campaigns': []
})
orders = {
'id': item['order_id'],
'name': item['order_name'],
'type': item['order_type'],
'impressions': item['imp'],
}
inflcr[item['user']]['campaigns'].append(orders)
result['reports'] = inflcr.values()
return Response(result)