DRF Serializer How do I serialize my data and display - python

I have following Serializer I am facing problem with Json with serializing. I have user named daniel james and he have multiple subject like maths science I am providing nested serializer to fill all subject but based on subject users name also repeats below is more specific qsn
This is my model.
class Result(BaseModel):
semester_choices = (
('first', 'First'),
('second', 'Second'),
('third', 'Third'),
('fourth', 'Fourth'),
('fifth', 'Fifth'),
('sixth', 'Sixth'),
('seventh', 'Seventh'),
('eight', 'Eight'),
('all', 'All'),
)
user = models.ForeignKey(User, on_delete=models.CASCADE)
registration_number = models.CharField(null=True, blank=True, max_length=20)
semester = models.CharField(max_length=50, choices=semester_choices, null=True, blank=True)
gpa = models.CharField(max_length=20)
subject = models.CharField(max_length=50)
serializers.py
class ResultSerializer(serializers.ModelSerializer):
class Meta:
model = Result
fields = '__all__'
class ListResultSerializer(ResultSerializer):
user = serializers.CharField()
semester = serializers.CharField()
subject = serializers.SerializerMethodField()
class Meta(ResultSerializer.Meta):
fields = (
'user',
'semester',
'subject',
)
def get_subject(self, instance):
return SubjectSerializer(instance).data
This is my SubjectSerializer
class SubjectSerializer(ResultSerializer):
class Meta(ResultSerializer.Meta):
fields = (
'gpa',
'subject'
)
And In my views.py I have done like this.
class ListResultView(rest_generics.ListAPIView, UserMixin):
serializer_class = serializers.ListResultSerializer
permission_classes = (AllowAny,)
def get_object(self):
return self.get_user()
def get_queryset(self):
return usecases.ListResultUseCase(
user=self.get_user()
).execute()
I use usecases.py to filter the data here is further code
class ListResultUseCase:
def __init__(self, user: User):
self._user = user
def execute(self):
self._factory()
return self._result
def _factory(self):
self._result = Result.objects.filter(user=self._user)
Now this is the Json I am getting right now from above code.
[
{
"user": "daniel james",
"semester": "first",
"subject": {
"gpa": "a+",
"subject": "maths"
}
},
{
"user": "daniel james",
"semester": "first",
"subject": {
"gpa": "A",
"subject": "data structures"
}
}
]
I want my json to be in this format
[
{
"user": "daniel james",
"semester": "first",
"subject": [
{
"gpa": "a+",
"subject": "maths"
},
{
"gpa": "A",
"subject": "data structures"
}
]
}
]
Any help in serializer?

You could create a seperate serializer to use with the nested serializer.
class SubjectNestedSerializer(serializers.ModelSerializer):
class Meta:
model = Subject
exclude = ['user']
P.S
Try to change your model like this
class Semester(BaseModel):
semester_choices = (
('first', 'First'),
('second', 'Second'),
('third', 'Third'),
('fourth', 'Fourth'),
('fifth', 'Fifth'),
('sixth', 'Sixth'),
('seventh', 'Seventh'),
('eight', 'Eight'),
('all', 'All'),
)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='semesters')
registration_number = models.CharField(null=True, blank=True, max_length=20)
semester = models.CharField(max_length=50, choices=semester_choices, null=True, blank=True)
class Subject(BaseModel):
semester = models.ForeignKey(Semester, on_delete=models.CASCADE, related_name='semester_subjects')
gpa = models.CharField(max_length=20)
subject = models.CharField(max_length=50)

You could create a Serializer for your user, and adding a method field that retrieve all the subjects of the user like this.
def get_subjects(self,instace):
subjects = Subject.objects.filter(user=instance)
return SubjectSerializer(subjects, many=True)

What you want is an aggregation of existing data to have a more meaningful result for frontend. I'd rather generating my own response than using a serializer for this.
Here is a simple example for you.
Let's say you want results for multiple users. (It's even easier if you want for one user)
def get_item_with_attribute_from_list(items, attribute, attribute_value):
"""
This is just a utility function that I use occasionally
"""
if items is None or len(items) == 0:
return None
if isinstance(items[0], dict):
for item in items:
if item.get(attribute) == attribute_value:
return item
else:
for item in items:
if getattr(item, attribute, None) == attribute_value:
return item
results = Result.objects.all().select_related('user')
response = []
for result in results:
user_dict = get_item_with_attribute_from_list(response, "user", result.user.name)
subject = {"gpa": "A", "subject": "data structures"}
if user_dict:
semester_list = user_dict['semesters']
semester = get_item_with_attribute_from_list(semester_list, 'semester_name', result.semester)
if semester:
semester['subjects'].append(subject)
else:
semester_list.append({'subjects': [subject]})
else:
response.append({'user': result.user.name, 'semesters': [{'semester_name': result.semester, 'subjects': [subject]}]})
Resulting JSON will be something like this.
[
{
"user": "daniel james",
"semesters": [
{
"semester_name": "first",
"subjects": [
{
"gpa": "a+",
"subject": "maths"
},
{
"gpa": "A",
"subject": "data structures"
}
]
},
{
"semester_name": "second",
"subjects": [
{
"gpa": "b+",
"subject": "geometry"
},
{
"gpa": "C",
"subject": "chemistry"
}
]
}
]
},
... # Other user's data
]
This way, you'll have more structured data, you'll be able to calculate SPA's for each semester easily.

Related

django drf about many-to-many RelatedField custom field

I use the latest version of The Django REST Framework,
and The table in model is many-to-many related
My current model code looks like this:
model.py
class LvsDeploy(models.Model):
MASTER = 'MASTER'
BACKUP = 'BACKUP'
ROLE_CHOICES = (
(MASTER, 'MASTER'),
(BACKUP, 'BACKUP')
)
id = models.AutoField(primary_key=True)
cluster_name = models.CharField(max_length=30)
dip = models.CharField(max_length=15, verbose_name='DR IP')
role = models.CharField(max_length=10, choices=ROLE_CHOICES, verbose_name="role")
class LoadBalance(models.Model):
id = models.AutoField(primary_key=True)
lb_name = models.CharField(max_length=30, verbose_name='load balance')
cluster = models.ManyToManyField('LvsDeploy',related_name='cluster')
vip = models.CharField(max_length=50, verbose_name='VIP')
port = models.IntegerField(verbose_name='port')
def __str__(self):
return self.lb_name
My serializer code looks like this:
serializers.py
class LvsDeployReadOnly(serializers.ModelSerializer):
class Meta:
model = LvsDeploy
fields = '__all__'
class LoadBalanceSerializer(serializers.ModelSerializer):
cluster_name = serializers.RelatedField(source='cluster.name',read_only=True)
cluster = LvsDeployReadOnly(many=True)
##rs = RSInfoSerializer(many=True, read_only=True)
class Meta:
model = LoadBalance
fields = '__all__'
##extra_fields = ['rs','cluster','cluster_name']
extra_fields = ['rs','cluster','cluster_name']
My views code:
class BalanceList(views.APIView):
def get(self, request):
queryset = LoadBalance.objects.all()
serializer = LoadBalanceSerializer(queryset, many=True)
print(serializer.data)
return Response(serializer.data)
and request actual output:
[
{
"id": 2,
"cluster_name": null,
"cluster": [
{
"id": 1,
"cluster_name": "lvs_sz01",
"dip": "1.1.1.6",
"role": "BACKUP",
},
{
"id": 2,
"cluster_name": "lvs_sz01",
"dip": "1.1.1.5",
"role": "BACKUP",
}
],
"lb_name": "lb001",
"vip": "1.1.1.1",
"port": 80,
}
]
But the cluster_name filed value is same in the dictionary of lists .
I want the output to look like this:
[
{
"id": 2,
"cluster_name": "lvs_sz01",
"cluster": [
{
"dip": "1.1.1.6",
"role": "BACKUP",
},
{
"dip": "1.1.1.5",
"role": "BACKUP",
}
],
"lb_name": "lb001",
"vip": "1.1.1.1",
"port": 80,
}
]
How should I change it? Can you help me ?
I solved it myself with the following method:
class LoadBalanceSerializer(serializers.ModelSerializer):
cluster_name = serializers.SerializerMethodField()
cluster = LvsDeployReadOnly(many=True, read_only=True)
rs = RSInfoSerializer(many=True, read_only=True)
class Meta:
model = LoadBalance
fields = '__all__'
extra_fields = ['rs','cluster','cluster_name']
def get_cluster_name(self,obj):
print(type(obj.lb_name))
lvs_cluster = obj.cluster.all()
cluster_name = [ i.cluster_name for i in lvs_cluster ][0]
print(cluster_name)
return cluster_name
Thanks!

django REST framework remove nested json

I would like to get the data from multiple model, now I work as these:
class IGStatSerializer(serializers.ModelSerializer):
class Meta:
model = IGStat
fields = [
"account",
"rank",
"followers",
"created",
]
class IGSerializer(serializers.ModelSerializer):
stats = serializers.SerializerMethodField()
class Meta:
model = IGAccount
fields = [
"id",
"username",
"avatar",
"brand",
"stats",]
def get_stats(self, instance):
today = datetime.today() - timedelta(days=1)
stat = instance.stats.all().filter(created__year=today.year,
created__month=today.month,
created__day=today.day)
return IGStatSerializer(stat, allow_null=True, many=True).data
And the json result will be like this:
{
"id": 3613,
"username": "beautyfromnaturehk",
"avatar": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/s320x320/42763479_187833352109784_1648992215864705024_n.jpg?tp=1&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_ohc=Q4hJvaXL-vYAX--Ol1x&oh=e05aef733557c9951642c3c8b518d2f9&oe=607A54CC",
"brand": 4172,
"stats": [
{
"account": 3613,
"rank": 22822,
"followers": 21485,
"created": "2021-03-16T00:00:00Z"
}
]
},
And in actual case, there will combine more than one models together and having many nested json.
So I would like to remove the nested and rename the field name, like the above case should be like this:
{
"id": 3613,
"username": "beautyfromnaturehk",
"avatar": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/s320x320/42763479_187833352109784_1648992215864705024_n.jpg?tp=1&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_ohc=Q4hJvaXL-vYAX--Ol1x&oh=e05aef733557c9951642c3c8b518d2f9&oe=607A54CC",
"brand": 4172,
"stats_account": 3613,
"stats_rank": 22822,
"stats_followers": 21485,
"stats_created": "2021-03-16T00:00:00Z"
},
The stats nested was remove and rename the content of it.
Write custom to_representation function in your serializer like this
class IGStatSerializer(serializers.ModelSerializer):
class Meta:
model = IGStat
fields = [
"account",
"rank",
"followers",
"created",
]
class IGSerializer(serializers.ModelSerializer):
stats = serializers.SerializerMethodField()
class Meta:
model = IGAccount
fields = [
"id",
"username",
"avatar",
"brand",
"stats",]
def get_stats(self, instance):
today = datetime.today() - timedelta(days=1)
stat = instance.stats.all().filter(created__year=today.year,
created__month=today.month,
created__day=today.day)
return IGStatSerializer(stat, allow_null=True, many=True).data
def to_representation(self, instance):
data = super().to_representation(instance)
stats = data.pop('stats', None)
# If you are sure that there will be only one stats object
for key, value in stats[0].items():
data['stats_{key}'.format(key=key)] = value
return data

how to create multiple objects with one request DRF

I have the following models
class Product(models.Model):
name = models.CharField(null=True, blank=True, max_length=500)
category = models.CharField(null=True, blank=True, max_length=120)
class SpecificationName(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE, null=True, related_name='specifications')
name = models.CharField(max_length=125)
class Attribute(models.Model):
spec_name = models.ForeignKey(SpecificationName, on_delete=models.CASCADE, null=True, related_name='attributes')
index = models.CharField(max_length=200, blank=True, null=True)
value = models.CharField(max_length=250, blank=True, null=True)
after saving objects in Django admin I have an example
{
"name": "Apple Smart Watch",
"category": "IT",
"specifications": [
{
"name": "Test Data",
"attributes": [
{
"index": "test",
"value": "test2"
},
{
"index": "test7",
"value": "test8"
},
{
"index": "test9",
"value": "test10"
}
]
},
{
"name": "Test Data Continued",
"attributes": [
{
"index": "bla",
"value": "bla1"
},
{
"index": "bla 2",
"value": "bla 4"
},
{
"index": "test9",
"value": "test10"
}
]
},
{
"name": "Test Spec",
"attributes": []
}
]
}
I need to save this kind of object with one request but I am failing to do this
my serializer looks like this
class ProductSerializer(serializers.ModelSerializer):
specifications = SpecNameSerializer(many=True)
# attributes = AttributeSerializer(many=True, required=False)
class Meta:
model = Product
fields = ['name', 'category', 'brand', 'price', 'specifications']
def create(self, validated_data):
specs = validated_data.pop('specifications')
instance = Product.objects.create(**validated_data)
for spec in specs:
SpecificationName.objects.create(product=instance, **spec)
print(spec)
return instance
with this code, I am getting the following result but not as expected
{
"name": "Appel watch series",
"specifications": [
{
"name": "Test Data",
"attributes": []
},
{
"name": "Test Data comn",
"attributes": []
},
{
"name": "Test Spec",
"attributes": []
}
]
}
it cannot write into attributes
I searched for many answers but I did not find or applied some of them, again it did not help me. I am using just ListCreateView in the views. Please is there anybody who can help solve this problem. Thanks in advance!
SOLVED
I am using here ModelSerializer instead I used Serializer and added some changes here my answer and it worked
class AttributeSerializer(serializers.Serializer):
index = serializers.CharField(max_length=200)
value = serializers.CharField(max_length=200)
class SpecNameSerializer(serializers.ModelSerializer):
attributes = AttributeSerializer(many=True)
class Meta:
model = SpecificationName
fields = '__all__'
class ProductSerializer(serializers.ModelSerializer):
specifications = SpecNameSerializer(many=True)
class Meta:
model = Product
fields = ['name', 'category', 'brand', 'price', 'specifications']
def create(self, validated_data):
specs = validated_data.pop('specifications')
instance = Product.objects.create(**validated_data)
for spec in specs:
SpecificationName.objects.create(product=instance, **spec)
attrs = spec.pop('attributes')
for attr in attrs:
Attribute.objects.create(spec_name=spec, **attr)
return instance

How to create multiple model instances without duplicated writable nested serializer in Django REST Framework?

I have two related models: Product and ProductDescription. In 1 submit action user able to insert a new Product with multiple descriptions depend on the available languages. I use writable nested serializer to insert into Product and ProductDescription simultaneously. I do it by overriding create function in ProductDescriptionSerializer class, it works. However, I can only insert 1 ProductDescription at a time.
Then I tried to use this answer to create multiple model instances at once. The problem is it also creates the same Product twice instead of using the newly created Product Id to insert the next ProductDescription.
My models.py:
class Product(models.Model, ProductStatus):
product_code = models.CharField(max_length=6)
color = models.ForeignKey(ColorParent, on_delete=models.SET_NULL, null=True)
collection = models.ForeignKey(ProductCollection, on_delete=models.SET_NULL, null=True)
video = models.URLField(verbose_name='Video URL', max_length=250, null=True, blank=True)
status = models.CharField(max_length=20, choices=ProductStatus.status, default=ProductStatus.active)
class ProductDescription(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
language = models.ForeignKey(Language, on_delete=models.CASCADE)
description = models.TextField(max_length=500, null=True, blank=True)
def __str__(self):
return '%s - %s' % (self.product, self.language)
My serializers.py:
class CustomRelatedField(serializers.RelatedField):
def display_value(self, instance):
return instance
def to_representation(self, value):
return str(value)
def to_internal_value(self, data):
model = self.queryset.model
return model.objects.get(id=data)
class ProductSerializer(serializers.ModelSerializer):
collection = CustomRelatedField(queryset=ProductCollection.objects.all(), many=False)
color = CustomRelatedField(queryset=ColorParent.objects.all(), many=False)
class Meta:
model = Product
fields = ['id', 'product_code', 'collection', 'color', 'video', 'status']
class ProductDescriptionSerializer(serializers.ModelSerializer):
product = ProductSerializer()
language = CustomRelatedField(many=False, queryset=Language.objects.all())
class Meta:
model = ProductDescription
fields = ['id', 'product', 'language', 'description']
def to_representation(self, instance):
data = super().to_representation(instance)
if self.context['request'].method == 'GET':
data['product'] = instance.product.product_code
return data
return Serializer.to_representation(self, instance)
# The `.create()` method does not support writable nested fields by default.
def create(self, validated_data):
# create product data for Product model.
product_data = validated_data.pop('product')
product = Product.objects.create(**product_data)
# create ProductDescription and set product FK.
product_description = ProductDescription.objects.create(product=product, **validated_data)
# return ProductDescription instance.
return product_description
My views.py:
class CreateListModelMixin(object):
def get_serializer(self, *args, **kwargs):
if isinstance(kwargs.get('data', {}), list):
kwargs['many'] = True
return super(CreateListModelMixin, self).get_serializer(*args, **kwargs)
class ProductDescriptionView(CreateListModelMixin, viewsets.ModelViewSet):
permission_classes = [permissions.DjangoModelPermissions]
queryset = ProductDescription.objects.all()
serializer_class = ProductDescriptionSerializer
http_method_names = ['get', 'head', 'post', 'put', 'patch', 'delete']
The JSON format I use to POST data:
[
{
"product": {
"product_code": "BQ1080",
"collection": 5,
"color": 7,
"video": "https://www.youtube.com/watch?v=",
"status": "Continue"
},
"language": 1,
"description": "English description."
},
{
"product": {
"product_code": "BQ1080",
"collection": 5,
"color": 7,
"video": "https://www.youtube.com/watch?v=",
"status": "Continue"
},
"language": 2,
"description": "Vietnamese description."
}
]
It creates a duplicate Product in Product List:
[
{
"id": 26,
"product_code": "BQ1080",
"collection": 5,
"color": 7,
"video": "https://www.youtube.com/watch?v=",
"status": "Continue"
},
{
"id": 27,
"product_code": "BQ1080",
"collection": 5,
"color": 7,
"video": "https://www.youtube.com/watch?v=",
"status": "Continue"
}
]
The ProductDescription datas are correct though:
[
{
"id": 5,
"product": "BQ1080",
"language": "English",
"description": "English description."
},
{
"id": 6,
"product": "BQ1080",
"language": "Vietnam",
"description": "Vietnamese description."
}
]
To avoid duplicate product you can use get_or_create() method:
class ProductDescriptionSerializer(serializers.ModelSerializer):
...
def create(self, validated_data):
# create product data for Product model.
product_data = validated_data.pop('product')
product_code = product_data.pop("product_code")
product, _ = Product.objects.get_or_create(product_code=product_code, defaults=product_data)
# create ProductDescription and set product FK.
product_description = ProductDescription.objects.create(product=product, **validated_data)
# return ProductDescription instance.
return product_description
Note that get_or_create is prone to race condition. So if two same requests came to you service at the same time you may still have duplicate products.
I think you need to override your ProductSerializer's create method. Maybe you can try like this:
class ProductSerializer(serializers.ModelSerializer):
collection = CustomRelatedField(queryset=ProductCollection.objects.all(), many=False)
color = CustomRelatedField(queryset=ColorParent.objects.all(), many=False)
def create(self, validated_data):
instance, _ = Product.objects.get_or_create(**validated_data)
return instance
class Meta:
model = Product
fields = ['id', 'product_code', 'collection', 'color', 'video', 'status']
So that, first it will try to get if the Product exists, else create the instance(hence reducing duplicate entry).
ForeignKey not for this job you should use ManyToManyField

Django Python arrange json into a single node

I'm working with a legacy database which has 2 records, the data in each record contain the following values: Name, Email, Company, Phone
These 2 records contain the same data value except the value for Phone which is different.
When I retrieve them in JSON I get the following output:
"results": [
{
"Name": "Mack",
"Email": "Mack#email.com",
"Company": "Company Name",
"Phone": "123456789"
},
{
"Name": "Mack",
"Email": "Mack#email.com",
"Company": "Company Name",
"Phone": "1111111"
}
]
My question: As you can see there is some duplicate data in the output above, so is there a way to arrange the JSON output to the following:
"results": [
{
"Name": "Mack",
"Email": "Mack#email.com",
"Company": "Company Name",
"Phone": "123456789"
"Phone 2": "1111111"
}
]
Here is the code for Views.py file:
class DataView(viewsets.ModelViewSet):
queryset = DataList.objects.all()
serializer_class = DataSerializer
Here is the code for Serializers.py file:
class DataSerializer(serializers.ModelSerializer):
data_details = serializers.SerializerMethodField()
class Meta:
model = DataList
fields = 'data_details'
def get_data_details(self, obj):
return [{
'name': obj.name,
'email': obj.email,
'company': obj.company,
'phone': obj.phone,
}]
Here is the code for models.py file:
class Data(models.Model):
name = models.CharField(db_column='name', primary_key=True, max_length=50)
email = models.CharField(db_column='email', max_length=50)
company = models.CharField(db_column='company', max_length=100)
phone= models.TextField(db_column='phone')
class Meta:
managed = False
db_table = 'data_table'
Thank you.

Categories