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)
Related
In my django app, i have four model tables linked using foreign keys, the problem is, when i make a query for any model table, the fields that are linked through foreign keys are returned as id's instead of the name.
My way does not work.
My customers models.py file
1st model
class Customer(models.Model):
name = models.CharField(max_length=50)
phone = models.CharField(max_length=20, unique=True)
email = models.EmailField(max_length=255, unique=True, blank=True)
image = models.ImageField(default='default.png', upload_to='customer_photos/%Y/%m/%d/')
data_added = models.DateField(default=datetime.now, blank=True)
def __str__(self):
return self.name
2nd model
class ShippingAddress(models.Model):
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name="customer_ship_address")
description = models.TextField(blank=True)
frequent_customer = models.BooleanField(default=False)
address = models.CharField(max_length=50, blank=True)
zip_code = models.CharField(max_length=12, blank=True)
location = models.CharField(max_length=255)
def __str__(self):
return self.customer.name
3rd model - PaymentInvoice
class paymentInvoice(models.Model):
shipping_address_owner = models.ForeignKey(
ShippingAddress, on_delete=models.CASCADE, related_name="customer_invoice")
product = models.ManyToManyField(
Product, related_name='product_invoice')
mode = models.CharField(max_length=20, choices=paymentMode.choices, default=paymentMode.MPESA)
date = models.DateField(default=datetime.now)
invoice_id = models.CharField(
max_length=50, unique=True, default=increment_invoice_number)
quantity = models.PositiveSmallIntegerField()
status = models.CharField(
max_length=20, choices=paymentStatus.choices, default=paymentStatus.PENDING)
payment_made = models.DecimalField(max_digits=20, decimal_places=2)
def __str__(self):
return self.shipping_address_owner.customer.name
My Products models.py file
class Product(models.Model):
slug = models.CharField(max_length=200, unique=True)
name = models.CharField(max_length=200)
available = models.BooleanField(default=True)
description = models.TextField(blank=True)
image = models.ImageField(default='default_product.jpg', upload_to='product_photos')
category = models.CharField(max_length=200)
qty_amount = models.CharField(
max_length=20, choices=Qty_Choices, default='250ml')
price = models.DecimalField(max_digits=10, decimal_places=2)
def __str__(self):
return self.name
My PaymentInvoice views.py file
class paymentInvoiceListCreateView(ListCreateAPIView):
"""
ListCreateAPIView executes both 'GET' and 'POST' requests. i.e listing a queryset or creating a model instance.
"""
serializer_class = paymentInvoiceSerializer
queryset = paymentInvoice.objects.all().order_by(
'-date').values(shipping_address_owner__customer)
When i make the above query the api returns the following, where the product field and shipping_address_owner field are just id's. I need there respective names instead.
{
"count": 6,
"next": null,
"previous": null,
"results": [
{
"id": 9,
"mode": "Mpesa",
"date": "2020-07-27",
"invoice_id": "INV-0006",
"quantity": 1,
"status": "Pending",
"payment_made": "500.00",
"shipping_address_owner": 9,
"product": [
1
]
},
EDIT: paymentInvoiceSerializer
class paymentInvoiceSerializer(serializers.ModelSerializer):
class Meta:
model = paymentInvoice
fields = '__all__'
You need to make some changes to your serializer.
If you just need the name and nothing more:
class paymentInvoiceSerializer(serializers.ModelSerializer):
product = serializers.SerializerMethodField()
shipping_address_owner = serializers.SerializerMethodField()
class Meta:
model = paymentInvoice
fields = '__all__'
def get_product(self, instance):
names = []
for product in instance.product.all():
names.append(product.name)
return names
def get_shipping_address_owner(self, instance):
return instance.shipping_address_owner.customer.name
You can also create different serializer for each model and pass them to their fields to get the full serialized data for them. It would be something like this:
ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = '__all__'
And then in your payment serializer:
class paymentInvoiceSerializer(serializers.ModelSerializer):
product = ProductSerializer()
class Meta:
model = paymentInvoice
fields = '__all__'
You can do the same for shipping_address_owner.
You can try like this in your serializer.
class paymentInvoiceSerializer(serializers.ModelSerializer):
shipping_address_owner = serializers.CharField(source='shipping_address_owner.customer')
class Meta:
model = paymentInvoice
fields = ['shipping_address_owner',...]
I'm trying to access some fields from another model in my serializer but I can't get it working by any means. Here is how I'm going about this.
Here are my models:
class Category(models.Model):
name = models.CharField(max_length=256)
class Meta:
ordering = ('name',)
def __str__(self):
return self.name
class Contact(models.Model):
first_name = models.CharField(max_length=256)
last_name = models.CharField(max_length=256)
address = models.CharField(max_length=1024)
city = models.CharField(max_length=256)
country = models.ForeignKey(Country, on_delete=models.CASCADE)
email = models.EmailField(max_length=256, unique=True)
phone = models.CharField(max_length=256)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
integration = models.ForeignKey(Integration, null=True, on_delete=models.SET_NULL)
def __str__(self):
return self.first_name
class Company(models.Model):
name = models.CharField(max_length=256)
address = models.CharField(max_length=256)
city = models.CharField(max_length=256)
country = models.ForeignKey(Country, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.name
class ContactCompany(models.Model):
contact = models.ForeignKey(Contact, on_delete=models.CASCADE, related_name='job')
company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name='company')
job = models.TextField(blank=True, help_text='Job', max_length=5000, null=True)
started_at = models.DateTimeField(auto_now_add=True)
finished_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.job
And here is my serializer
class ContactSerializer(serializers.ModelSerializer):
country_name = serializers.CharField(source='country.name')
category_name = serializers.CharField(source='category.name')
user_name = serializers.CharField(source='user.email')
integration_name = serializers.CharField(source='integration.name')
class Meta:
model = Contact
fields = ('first_name', 'last_name', 'address', 'city', 'country_name', 'email', 'phone',
'category_name', 'user_name', 'integration_name', 'job', )
How can I access the company field from the ContactCompany model in this serializer?
Here is how the API response looks like
{
"first_name": "First name",
"last_name": "Last name",
"address": "Address",
"city": "City",
"country_name": "Gabon",
"email": "saidsadiasida#gmam.com",
"phone": "0712345678",
"category_name": "TestCategory",
"user_name": "ekartdragos#cyberaxo.com",
"integration_name": "testmonth",
"job": [
4
]
}
How can I get the job text be desplayed instead of the id?
One possible solution is to used serializerMethodField and get your desire information.
class ContactSerializer(serializers.ModelSerializer):
country_name = serializers.CharField(source='country.name')
category_name = serializers.CharField(source='category.name')
user_name = serializers.CharField(source='user.email')
integration_name = serializers.CharField(source='integration.name')
company = serializers.SerializerMethodField()
class Meta:
model = Contact
fields = ('first_name', 'last_name', 'address', 'city', 'country_name', 'email', 'phone',
'category_name', 'user_name', 'integration_name', 'job', 'company' )
def get_company(self, obj):
"""
at first we get the instance of ContactCompany from the id of object. then get the company information
"""
try:
contact_company = ContactCompany.objects.get(contact=obj.id)
expect ContactCompany.DoesNotExist:
# return or raise your desire this
return contact_company.company # here we have company id of this contact, also we can generate all company information from this.
As you did with the others one: its Foreign key on ContactCompany and related_name = 'company'
class ContactSerializer(serializers.ModelSerializer):
company_name = serializers.CharField(source='company.name')
company_ address = serializers.CharField(source='company.name')
I am trying to create an api for device group and device. A device group can have multiple devices and i want to have the post api for device group with multiple devices because group creation is shown only when device is selected and user might select multiple devices and then create a new group. That way when group is created, those selected devices should also be shown as device_list
Here is my code, i am not sure on how to do post request
class BaseDevice(PolymorphicModel):
name = models.CharField(max_length=250, blank=False, null=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)
class DeviceIdSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source='token', format='hex', read_only=True)
class Meta:
model = Device
# id is the token of the device and name is the name of the device
fields = ('id', 'name')
class DeviceGroupSerializer(serializers.ModelSerializer):
name = serializers.StringRelatedField()
device_list = DeviceIdSerializer(read_only=False, many=True, required=False, source="groups")
class Meta:
model = DeviceGroup
# name is the name of group created and device_list is the list of devices with id(token) and device name
fields = ('id', 'name', 'device_list')
def create(self, validated_data):
print ('validated_data', validated_data)
device_list_data = validated_data.pop('device_list')
group = DeviceGroup.objects.create(**validated_data)
for device_list in device_list_data:
BaseDevice.objects.create(group=group, **device_list)
return group
class DeviceGroupAPIView(APIView):
permission_classes = (permissions.IsAuthenticated,)
def get_object(self, user, token):
try:
return BaseDevice.objects.filter(owner=user).get(token=token)
except ObjectDoesNotExist:
return error.RequestedResourceNotFound().as_response()
def post(self, request, token=None, format=None):
device_group_instance = DeviceGroup.objects.get(token=token)
for device_token in request.data['devices']:
device = Device.objects.get(token=device_token, owner=request.user)
device.group = device_group_instance
Here is my api design
{
"data":[
{
"id":1,
"name":"Home",
"device_list":[
{
"id":"481cfef5a4884e52a63d135967fbc367",
"name":"Oxygen Provider"
},
{
"id":"7eb006d6db50479aa47f887da0d4f10e",
"name":"Fan Speed"
}
]
},
{
"id":2,
"name":"Business",
"device_list":[
]
}
]
}
UPDATE
url(r'^device_group/(?P<token>[0-9a-f]+)/add$', DeviceGroupAPIView.as_view(), name='device_group'),
I've changed your code a bit
class BaseDevice(PolymorphicModel):
name = models.CharField(max_length=250, blank=False, null=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)
class DeviceIdSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source='token', format='hex', read_only=True)
# token does not exist in your model so this will not be included
class Meta:
model = Device
# id is the token of the device and name is the name of the device
fields = ('id', 'name')
class DeviceGroupSerializer(serializers.ModelSerializer):
device_list = DeviceIdSerializer(read_only=False, many=True, required=False, source="groups")
class Meta:
model = DeviceGroup
# name is the name of group created and device_list is the list of devices with id(token) and device name
fields = ('id', 'name', 'device_list')
def create(self, validated_data):
print ('validated_data', validated_data)
device_list_data = validated_data.pop('groups', [])
# notice that I pop 'groups' because validation changes the input data
# to match the field names
# Also since it is not required I've added a default value
group = DeviceGroup.objects.create(**validated_data)
devices = [BaseDevice(group=group, **device_list) for device_list in device_list_data]
BaseDevice.objects.bulk_create(devices)
# Use bulk create when you have to create multiple objects
# It hits the db only once instead of multiple times
return group
class DeviceGroupAPIView(ModelViewSet):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = DeviceGroupSerializer
queryset = DeviceGroup.objects.all()
# Although I have used ModelViewSet, you could use any other one
# I used this so that I don't need to write the code for create,
# update, delete or list
# urls.py
router = routers.DefaultRouter()
router.register(r'device_group', DeviceGroupAPIView, base_name='device_group')
# this gives the following urls
# /device_group/ POST to create, GET to list
# /device_group/(?<pk>\d+)/ GET to retrieve single DeviceGroup, PATCH/PUT to update it, and DELETE to delete it
This is the structure of the JSON to POST to create a new DeviceGroup with a bunch of Devices
{
"name":"Group Name",
"device_list":[
{
"name":"Device 1"
},
{
"name":"Device 2"
},
{
"name":"Device 3"
}
]
}
Hope this helps
Also you should read up more about Django-Rest-Framework
I am using Django 1.10.5 and djangorestframework 3.5.3.
I have 2 models that are related with one to many relation:
class Minisymposium(models.Model):
STATUS_CHOICES = (
('pending', 'Pending'),
('approved', 'Approved'),
('denied', 'Denied'))
id = models.AutoField(primary_key=True)
number = models.IntegerField(default=0, null=False)
title = models.CharField(max_length=100, null=False)
description = models.TextField(max_length=9000, null=False)
status = models.CharField(max_length=100, choices=STATUS_CHOICES, null=False, default='pending')
user = models.ForeignKey(User, null=False, related_name='user')
corresponding_organizer = models.ForeignKey(User, null=False, related_name='corresponding_organizer')
anticipated_abstracts = models.IntegerField(null=False)
anticipated_attendees = models.IntegerField(null=False)
date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return '{0}-{1}'.format(self.number, self.title)
class UnregisteredOrganizer(models.Model):
id = models.AutoField(primary_key=True)
first_name = models.CharField(max_length=1000, blank=False)
last_name = models.CharField(max_length=1000, blank=False)
email = models.EmailField(max_length=254, blank=True)
affiliation = models.CharField(max_length=254, help_text='(institution only)', blank=True)
phone_number = PhoneNumberField(blank=True)
minisymposium = models.ForeignKey(Minisymposium, related_name='other_organizers')
Each model have a serializer. But the problem is with Minisymposium`s serializer. Because I want to send an UnregisteredOrganizer`s ID on creating one, and get the whole object as serialized on getting a Minisymposium.
And as I see in ModelSerializer it is not possible:
class MinisymposiumSerializer(serializers.ModelSerializer):
other_organizers = UnregisteredOrganizerSerializer(many=True)
class Meta:
model = Minisymposium
fields = ('url', 'id', 'number', 'title', 'description', 'status', 'user', 'corresponding_organizer',
'anticipated_abstracts', 'anticipated_attendees', 'other_organizers', 'date')
def create(self, validated_data):
other_organizers = []
if 'other_organizers' in validated_data:
other_organizers = validated_data.pop('other_organizers')
minisymposium = Minisymposium.objects.create(**validated_data)
minisymposium.save()
for organizer in other_organizers:
UnregisteredOrganizer.objects.create(minisymposium=minisymposium, **organizer).save()
return minisymposium
How can I do that?
Thank you !
Because I want to send an UnregisteredOrganizer`s ID on creating one, and get the whole object as serialized on getting a Minisymposium.
Why have such an inconsistent API ?
The recommended option here is to set the fields on Minisymposium as read_only except for the id which should be read_only=False.
Therefore, you can get the full object when getting the data and just expect the id when post/put/patching the data. Posted JSON would look like:
{
...
"url": "whatever",
"title": "Some nice title",
"other_organizers": [{"id": 5}, {"id": 5123}]
}
Creation code would be like:
def create(self, validated_data):
other_organizers = validated_data.pop('other_organizers', [])
minisymposium = Minisymposium.objects.create(**validated_data)
minisymposium.save()
organizers = []
for organizer_id in other_organizers:
organizers .append(UnregisteredOrganizer.objects.get(id=organizer_id)
minisymposium. other_organizers = organizers
return minisymposium
My API call to api/business-review/3abe3a1e-199c-4a4b-9d3b-e7cb522d6bed/ currently returns the following:
[
{
"id": "3abe3a1e-199c-4a4b-9d3b-e7cb522d6bed",
"date_time": "2016-05-31T19:18:24Z",
"review": "Another quality job, Anna has a no fuss approach to his job and clearly takes pride in what he does. Will continue to use again and again.",
"rating": "4.0",
"person": "c1cc5684-1be1-4120-9d81-05aec29f352a",
"employee": "ecdc1f99-138c-4f9f-9e1f-b959d59209aa",
"service": "1dfa408f-d5bc-4eb2-96ae-e07e7999a01a",
}
]
Now I want to create three new fields:
person_name - which grabs the first_name and last_name of the reviewer
employee_name - which grabs the first_name and last_name of the employee
service_name - which grabs the title of the service
I've tried the following so far but it doesn't create any new variables:
serializers.py
class ReviewSerializer(serializers.ModelSerializer):
"""
Class to serialize Review objects
"""
person_name = serializers.CharField(source='person.reviewer.first_name', read_only=True)
employee_name = serializers.CharField(source='person.employer.first_name', read_only=True)
service_name = serializers.CharField(source='service.title', read_only=True)
class Meta:
model = Review
fields = '__all__'
read_only_fields = 'id'
models.py
class Review(models.Model):
"""
Review model
"""
id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
date_time = models.DateTimeField(blank=True, null=True, default=None)
person = models.ForeignKey(Person, null=True, default=None, related_name='reviewer')
employee = models.ForeignKey(Person, null=True, default=None, related_name='employee')
review = models.TextField(null=True, default=None)
service = models.ForeignKey(Service, null=True, default=None)
rating = models.DecimalField(max_digits=4, decimal_places=1)
class Person(models.Model):
"""
Person entity
"""
id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
class Service(models.Model):
"""
Service model
"""
id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
title = models.CharField(max_length=255)
Please try adding the field name explicitly instead of using __all__ which only picks up fields which are present in model and not those defined in serializer like this
fields = ['person', 'service', 'review', 'employee', 'person_name', 'service_name', 'employee_name', 'date_time']
Since you want add data to the serialized representation of your object, its better to use SerializerMethodField() for person_name, service_name and employee_name fields.
This is a read-only field. It gets its value by calling a method on
the serializer class it is attached to. It can be used to add any sort
of data to the serialized representation of your object.
Also, since the ForeignKey fields person, employee and service allow null values, you will have to handle the case when they are actually null. Otherwise, AttributeError exception will be raised on the serializer.
class ReviewSerializer(serializers.ModelSerializer):
"""
Class to serialize Review objects
"""
person_name = serializers.SerializerMethodField()
employee_name = serializers.SerializerMethodField()
service_name = serializers.SerializerMethodField()
class Meta:
model = Review
fields = ['person', 'service', 'review', 'employee', 'person_name', 'service_name', 'employee_name', 'date_time']
read_only_fields = 'id'
def get_person_name(self, obj):
try:
person_name = obj.person.first_name + obj.person.last_name
except AttributeError: # handle case if person is null
return None
else:
return person_name
def get_employee_name(self, obj):
try:
employee_name = obj.employee.first_name + obj.employee.last_name
except AttributeError: # handle case if employee is null
return None
else:
return employee_name
def get_service_name(self, obj):
try:
service_name = obj.service.title
except AttributeError: # handle case if service is null
return None
else:
return service_name