Serialize model objects into a JSON dict using django-rest-framework - python

So if I have the following model class in Django:
class Person(models.Model):
name = models.CharField(max_length=250)
is_active = models.BooleanField(default=True)
address_line1 = models.CharField(max_length=100, blank=True, null=True)
address_line2 = models.CharField(max_length=100, blank=True, null=True)
town = models.CharField(max_length=100, blank=True, null=True)
county = models.CharField(max_length=100, blank=True, null=True)
post_code = models.CharField(max_length=100, blank=True, null=True)
and my goal is to serialize it into the following JSON:
{
"name": "Joe Bloggs",
"is_active": true,
"contact": {
"address1": "Bloggs House"
"address2": "1 Bloggs Lane",
"city": "Bloggs Town",
"county": "Bloggs Town",
"zip": "BL0 GG5",
"country": "UK"
}
}
I tried the following, but it didn't work and I'm pretty sure that's not how the serializers.ListField is meant to work (I think it's meant to be for a list of the same thing):
class MailChimpListSerializer(serializers.ModelSerializer):
class Meta:
model = Person
contact = serializers.DictField(
address1=serializers.CharField(source='address_line1'),
address2=serializers.CharField(source='address_line2'),
city=serializers.CharField(source='town'),
state=serializers.CharField(source='town', read_only=True),
zip=serializers.CharField(source='post_code'),
country=serializers.SerializerMethodField()
)
permission_reminder = serializers.SerializerMethodField()
campaign_defaults = serializers.DictField(
from_name=serializers.CharField(source='name'),
from_email=serializers.CharField(source='primary_contact_email'),
subject=serializers.CharField(),
language=serializers.CharField()
)
email_type_option = serializers.SerializerMethodField()
fields = ('name', 'contact', 'permission_reminder',
'campaign_defaults', 'email_type_option')
How do I create the contact JSON list with the address etc in it?

What you want is a DictField not a ListField, the key contact in your desired JSON output is an object (dict in Python), not a list:
contact = serializers.DictField(
address1=serializers.CharField(source='address_line1'),
address2=serializers.CharField(source='address_line2'),
...
)
Here's another way that is more manual:
class MySerializer(serializers.ModelSerializer):
contact = serializers.SerializerMethodField()
def get_contact(self, obj):
return dict(
address1=obj.address1, # As long as the fields are auto serializable to JSON
some_field=SomeSerializer(obj.some_field).data,
)

Related

Calculate sum of model objects in django serializers

In my app, each user has his wallets in which he can save his daily expenses.
How can I get sum of money for each instance of wallet with many expenses saved to it?
I've tried serializers.SerializerMethodField()
from django.db.models import Sum
from django.db.models import Q
class WalletInstanceSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.id')
entry = BudgetEntrySerializer(many=True, read_only=True)
expense_sum = serializers.SerializerMethodField()
class Meta:
model = WalletInstance
fields = '__all__'
def get_expense_sum(self, obj):
return WalletInstance.objects.filter(Q(id=obj.id)|Q(entry__entry_type='income')).aggregate(Sum('entry__amount'))['entry__amount__sum']
And this gives me data in the correct format but the sum is wrong, for example
[
{
"id": "d458196e-49f1-42db-8bc2-ee1dba438953",
"owner": 1,
"entry": [
{
"id": 3,
"owner": 1,
"title": "dsfdsf",
"amount": 7,
"description": "sdfdsf",
"entry_type": "income",
"date_added": "2022-08-13",
"entry_category": {
"id": 2,
"name": "Transport"
}
},
{
"id": 4,
"owner": 1,
"title": "fesfvsdfgvbtdg",
"amount": 12,
"description": "efesf",
"entry_type": "income",
"date_added": "2022-08-13",
"entry_category": {
"id": 2,
"name": "Transport"
}
}
],
"viewable": [
"admin#gmail.com"
],
"expense_sum": 83,
"name": "dsdsds",
"date_added": "2022-08-13"
}
]
Expense_sum is 83 but amount fields are 7 and 12 so that's equal to 19
How can i get the correct number?
Models
class BudgetEntry(models.Model):
STATE= [
('income','income'),
('expenses','expenses'),
]
owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='owner_of_entry', on_delete=models.CASCADE)
title = models.CharField(max_length=20)
amount = models.IntegerField()
description = models.CharField(max_length=60, null=True)
entry_type = models.CharField(max_length=15, choices=STATE, null=True)
entry_category = models.ForeignKey(Category, null=True, blank=True, related_name='category_of_entry', on_delete=models.SET_NULL)
date_added = models.DateField(auto_now_add=True)
class WalletInstance(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
name = models.CharField(max_length=30, null=True)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='owner', on_delete=models.CASCADE)
viewable = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='can_view', blank=True)
entry = models.ManyToManyField(BudgetEntry, related_name='BudgetEntry', blank=True)
date_added = models.DateField(auto_now_add=True)
I think ORM query for the BudgetEntry is wrong. It should be like the following:
def get_expense_sum(self, obj):
return obj.entry.filter(entry_type='income').aggregate(Sum('entry__amount'))['entry__amount__sum']
I've figured it out. Firstly I've created #property in my database to get sum of expenses, but this wasn't very useful If I ever wanted to count income so I passed the value arg to the function where the value can be expense/income
class WalletInstance(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
name = models.CharField(max_length=30, null=True)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='owner', on_delete=models.CASCADE)
viewable = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='can_view', blank=True)
entry = models.ManyToManyField(BudgetEntry, related_name='BudgetEntry', blank=True)
date_added = models.DateField(auto_now_add=True)
def total_amount(self, value):
queryset = self.entry.filter(entry_type=value).aggregate(
total_amount=models.Sum('amount'))
return queryset["total_amount"]
And now I'm calling this function in my serializer instead of counting there
class WalletInstanceSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.id')
entry = BudgetEntrySerializer(many=True, read_only=True)
viewable = serializers.SlugRelatedField(many=True, queryset=get_user_model().objects.all(), slug_field='email')
expense_sum = serializers.SerializerMethodField()
class Meta:
model = WalletInstance
fields = '__all__'
def get_expense_sum(self, obj):
wallet_obj = WalletInstance.objects.get(id=obj.id)
return wallet_obj.total_amount('expenses')

Django ForeignKey returning id instead of name

Api return:
{
"id": "2c1f7627-ae73-4ca3-8243-82bc61e84dc7",
"title": "fdgdfg",
"author": "a8e13c21-f1ad-4292-9f73-dcb2e8757350",
"price": "234234.00",
"published": "2021-08-26",
"edition": "dfgdfg",
"isbn_code": "dfgdfg",
"created_at": "2021-08-26T05:16:33.022617+02:00",
"updated_at": "2021-08-26T05:16:33.023618+02:00",
"pages": 342,
"description": "sdfsdfsdf",
"cover": "HARDCOVER",
"genre": "ACTION",
"language": "SERBIAN",
"format": "A3",
"publisher": "BIG_5"
}
Models:
class Book(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.CharField(max_length=255, null=True, blank=True)
author = models.ForeignKey(Author, on_delete=models.SET_NULL, null=True, blank=True)
price = models.DecimalField(decimal_places=2, max_digits=255)
published = models.DateField()
edition = models.CharField(max_length=255)
isbn_code = models.CharField(max_length=255)
pages = models.IntegerField(blank=True, null=True, default=0)
description = models.TextField(null=True, blank=True)
cover = models.CharField(max_length=30, choices=Cover.choices(), default=None, null=True, blank=True)
genre = models.CharField(max_length=30, choices=Genre.choices(), default=None, null=True, blank=True)
language = models.CharField(max_length=30, choices=Language.choices(), default=None, null=True, blank=True)
format = models.CharField(max_length=30, choices=Format.choices(), default=None, null=True, blank=True)
publisher = models.CharField(max_length=30, choices=Publisher.choices(), default=None, null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
class Meta:
verbose_name = 'Book'
verbose_name_plural = 'Books'
Serizalizers:
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ('id', 'title', 'author',
'price', 'published', 'edition',
"isbn_code", 'created_at', "updated_at",
"pages", "description", "cover",
"genre", "language", "format",
"publisher"
)
And so i want to get the author name instead of id in the api.
Ask any questions you have, i really need help with this since ive been struggling with this for some time.
Im told to add more details in here but theres nothing else to say about this
You can work with a SlugRelatedField [DRF-doc] as author, and thus implement this as:
class BookSerializer(serializers.ModelSerializer):
author = serializers.SlugRelatedField(
slug_field='full_name',
queryset=Author.objects.all()
)
class Meta:
model = Book
# …
The advantage of using a SlugRelatedField, si that it can be used both in the read and write direction: if one specifies the full_name of an Author, then one can use the BookSerializer to create, update and retrieve the details of a Book.
can do it like this:
class BookSerializer(serializers.ModelSerializer):
author = serializer.ReadOnlyField(source='author.full_name')
class Meta:
model = Book
fields = ('id', 'title', 'author',
'price', 'published', 'edition',
"isbn_code", 'created_at', "updated_at",
"pages", "description", "cover",
"genre", "language", "format",
"publisher"
)

How to store images like Images API

Hi this my model for create simple member already searched for several places and until now I haven't figured out how I can store the directory of my image next to my image field.
class Member(models.Model):
with open(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))+'/api/data/states.json') as s:
states_json = json.load(s)
STATES = [(str(state["nome"]), str(state["sigla"])) for state in states_json]
name = models.CharField(max_length=100, null=True, blank=True, verbose_name='Nome')
image = models.ForeignKey(
"wagtailimages.Image",
blank=False,
null=True,
related_name="+",
on_delete=models.SET_NULL,
verbose_name='Imagem do membro',
)
role = models.CharField(max_length=35, null=True, blank=True, verbose_name='Cargo')
city = models.CharField(max_length=30, null=True, blank=True, verbose_name='Cidade')
state = models.CharField(
verbose_name='Estado',
max_length=19,
choices=STATES,
default=('SP'),
)
created = models.DateField(default=timezone.now, verbose_name='Atualizado')
modified = models.DateField(default=timezone.now, verbose_name='Modificado')
panels = [
FieldPanel("name"),
ImageChooserPanel("image"),
FieldPanel("role"),
FieldPanel("city"),
FieldPanel("state"),
FieldPanel("created"),
FieldPanel("modified"),
]
def __str__(self):
return self.name
class Meta:
verbose_name = 'Membro'
verbose_name_plural = 'Membros'
This work normally but how to store image informations like Wagtail Images API?
Example:
"detail_url": "http://localhost/api/v2/images/2/",
"download_url": "/media/original_images/l8GlI3V.jpg"
This my JSON from API
[
{
"id": 6,
"name": "Gabriel",
"role": "Desenvolvedor",
"image": 4,
"city": "Itapetininga",
"state": "São Paulo",
"created": "2020-04-26",
"modified": "2020-04-26"
}
]
Thanks for help!!!
This is the link from the docs, Wagtail has a dependency of DRF, Create the serializer that you want and represent the image as a link instead of the pk, This should be straightforward (see DRF docs).
This video on custom serializers should help too.

django rest framework get all related fields and filter in a manytomany field nested

I'm new in Django I want to get all informations of my contacts for a store given in parameter and all extra from another model.
Here is my code :
Models.py
class Store(models.Model):
name = models.CharField(max_length=100)
​
​
class Contact(models.Model):
id = models.CharField(primary_key=True, max_length=200)
first_name = models.CharField(max_length=100, null=True, blank=True)
last_name = models.CharField(max_length=100, null=True, blank=True)
email = models.CharField(max_length=100, null=True, blank=True)
stores = models.ManyToManyField(Store, related_name='contacts')
​
​
class DataType(models.Model):
datatype = models.CharField(max_length=10)
class DataField(models.Model):
name = models.CharField(max_length=100)
label = models.CharField(max_length=100)
datatype = models.ForeignKey(
'DataType',
on_delete=models.SET_NULL,
null=True
)
class DataFieldValue(models.Model):
contact = models.ForeignKey(
Contact,
on_delete=models.SET_NULL,
related_name='datafieldvalues',
null=True
)
datafield = models.ForeignKey(
'DataField',
on_delete=models.SET_NULL,
null=True
)
value = models.CharField(
max_length=250,
null=True,
blank=True
)
Views.py
# Contact API views.
class ContactListAPIView(generics.ListAPIView):
queryset = Contact.objects.all()
serializer_class = ContactSerializer
filter_class = ContactFilter
Filters.py
class ContactFilter(django_filters.rest_framework.FilterSet):
store = django_filters.CharFilter()
class Meta:
model = Contact
fields = ['store']
Serializers.py
class ContactSerializer(serializers.ModelSerializer):
class Meta:
model = Contact
fields = '__all__'
(Additionnal) Here is the schema :
schema
this is what i want:
GET /contacts/?store=54
{
"count": 25,
"next": "<url>/contacts/?page=3",
"previous": "<url>/contacts/?page=2",
"data": [
{
"id": "a519d2cd-13c6-48f3-b391-9a7e0cee58cc",
"src_id": 356,
"first_name": "Benjamin",
"last_name": "Pavard",
"email": "benjamin.pavard#email.fr",
"datafields" : {
"age" : "eighteen"
},
"store": 54
},
....
]
}
but this is what i got :
{
"next": null,
"previous": null,
"count": 10,
"data": [
{
"id": "1234567",
"first_name": null,
"last_name": null,
"email": "cartouche#email.fr",
},
.....
}
Questions :
how can i get the "datafields" ?
"age" is a "datafield" object and "eighteen" is a datafieldvalue object.
How can i get only the store that i've previously gave in parameter?
Thanks a lot for your help whatever it was.
You need to add more fields in the ContactSerializer
class ContactSerializer(serializers.ModelSerializer):
store = serializers.SerializerMethodField()
datafields = serializers.SerializerMethodField()
class Meta:
model = Contact
fields = '__all__'
def get_datafields(self, obj):
result = {}
for data_field_value in obj.datafieldvalues.all().select_related('datafield'):
result[data_field_value.datafield.label] = data_field_value.value
return result
def get_store(self, obj):
request = self.context['request']
store = request.query_params.get('store', '')
return store

device_list is not shown

I am creating an api where the list of groups are shown along with the devices id that falls under that groups. For example if there is a device named Speedometer, Humidifier and they fall under Home group then my api should include
{
"id": 1,
"name": "Home"
"device_list": [
{
"id": "b45c56ioxa1"
},
{
"id": "h4oc2d5ofa9"
}
]
},
but my code does not produce device_list in the api. It only shows name and id
device_list is the list of all the devices id that are in a certain group.
Here is my code
class DeviceIdSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source='token', format='hex', read_only=True)
class Meta:
model = Device
fields = ('id')
class DeviceGroupSerializer(serializers.ModelSerializer):
name = serializers.StringRelatedField()
device_list = DeviceIdSerializer(read_only=False, many=True, required=False, source="groups")
class Meta:
model = DeviceGroup
fields = ('id', 'name', 'device_list')
class DevicesGroupsAPIView(APIView):
permission_classes = (permissions.IsAuthenticated,)
def get(self, request, format=None):
"""
Returns a list of groups
"""
reply = {}
try:
groups = DeviceGroup.objects.all()
print ('reply', groups)
reply['data'] = DeviceGroupSerializer(groups, many=True).data
except:
reply['data'] = []
return Response(reply, status.HTTP_200_OK)
class BaseDevice(PolymorphicModel):
# User's own identifier of the product
name = models.CharField(max_length=250, blank=False, null=False)
# Any device should have a owner, right from the creation
owner = models.ForeignKey(User, blank=False, null=False)
token = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)
group = models.ForeignKey('DeviceGroup', related_name="groups", null=True, blank=True)
class Device(BaseDevice):
description = models.TextField(blank=True, null=True)
class DeviceGroup(models.Model):
name = models.CharField(max_length=250, blank=False, null=False)
I tried out the very same code you have except I used models.Model as the base model.
The first time I got an error
The fields option must be a list or tuple or "all". Got str.
which clearly states where your problem is.
So I changed class the fields option in DeviceIdSerializer
DeviceIdSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source='token', format='hex', read_only=True)
class Meta:
model = Device
fields = ('id',)
Note that I've added a comma (",") which makes fields a tuple instead of a string as it was before.
Now the data I get is
{
"id":1,
"name":"test",
"device_list":[
{
"id":"38ec7e152f9d49a38008c859a1022525"
},
{
"id":"b0d799509260474cb092899ef84ce49c"
},
{
"id":"e5c7cf8f9f5043c68c34c7b962569b08"
}
]
}
which is the same as what you are looking for...
I think your serializers need to look like this:
class DeviceIdSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source='token', format='hex', read_only=True)
class Meta:
model = Device
fields = ('id')
class DeviceGroupSerializer(serializers.ModelSerializer):
name = serializers.StringRelatedField()
groups = DeviceIdSerializer(read_only=False, many=True, required=False)
class Meta:
model = DeviceGroup
fields = ('id', 'name', 'groups')
Or change this:
class BaseDevice(PolymorphicModel):
# User's own identifier of the product
name = models.CharField(max_length=250, blank=False, null=False)
# Any device should have a owner, right from the creation
owner = models.ForeignKey(User, blank=False, null=False)
token = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)
group = models.ForeignKey('DeviceGroup', related_name="device_list", null=True, blank=True)

Categories

Resources