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
Related
I have a CustomUser model and two separate Models for profile two types of User. I'm trying to combine the attribute of CustomUser and one of the Profile into single endpoint from which the user can see/update/delete the user/profile. For instance there are 2 types of users, doctor & patient. so if the user is doc then the endpoint will return the attributes of CustomUser+DoctorProfile and same for the Patient CustomUser+PatientProfile. Below is the code. I will explain the issue in the code base with comments. I will enormously appreciate any suggestion. One thing to mention is that I split the models.py file into 3 different folder and imported all of them into __init__.py of models folder.
CustomUser Model:
class CustomUser(AbstractBaseUser, PermissionsMixin):
class Types(models.TextChoices):
DOCTOR = "DOCTOR", "Doctor"
PATIENT = "PATIENT", "Patient"
# what type of user
type = models.CharField(_("Type"), max_length=50, choices=Types.choices, null=True, blank=False)
avatar = models.ImageField(upload_to="avatars/", null=True, blank=True)
email = models.EmailField(max_length=255, unique=True)
name = models.CharField(max_length=255)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
objects = CustomBaseUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['name', 'type'] #email is required by default
def get_full_name(self):
return self.name
def __str__(self):
return self.email
DoctorProfile Model:
class DoctorProfile(models.Model):
class DoctorType(models.TextChoices):
"""Doctor will choose profession category from enum"""
PSYCHIATRIST = "PSYCHIATRIST", "Psychiatrist"
PSYCHOLOGIST = "PSYCHOLOGIST", "Psychologist"
DERMATOLOGIST = "DERMATOLOGIST", "Dermatologist"
SEXUAL_HEALTH = "SEXUAL HEALTH", "Sexual health"
GYNECOLOGIST = "GYNECOLOGIST", "Gynecologist"
INTERNAL_MEDICINE = "INTERNAL MEDICINE", "Internal medicine"
DEVELOPMENTAL_THERAPIST = "DEVELOPMENTAL THERAPIST", "Developmental therapist"
owner = models.OneToOneField(CustomUser, on_delete=models.CASCADE, related_name='doctor_profile')
doctor_type = models.CharField(
_("Profession Type"),
max_length=70,
choices=DoctorType.choices,
null=True,
blank=False)
title = models.IntegerField(_('Title'), default=1, choices=TITLES)
date_of_birth = models.DateField(null=True, blank=False)
gender = models.IntegerField(_('Gender'), default=1, choices=GENDERS)
registration_number = models.IntegerField(_('Registration Number'), null=True, blank=False)
city = models.CharField(_('City'), max_length=255, null=True, blank=True)
country = models.CharField(_('Country'), max_length=255, null=True, blank=True)
def __str__(self):
return f'profile-{self.id}-{self.title} {self.owner.get_full_name()}'
Serializer:
class DoctorProfileFields(serializers.ModelSerializer):
"""To get required attributes from DoctorProfile model"""
class Meta:
model = DoctorProfile
fields = ('doctor_type', 'title', 'date_of_birth', 'registration_number', 'gender', 'city', 'country', )
class DoctorProfileSerializer(serializers.ModelSerializer):
"""Above Serializer is used in a new attribute profile. So that I can combine the CustomUser and DoctorProfile."""
profile = DoctorProfileFields(source='*')
"""
if I use source in the above line the serializer returns the json in the
expected format while I use get method, otherwise it return error saying profile
is not an attribute of CustomUser. but for put method the json payload is getting
received in a wrong formation. attributes of nested Profile object is getting
combined in the same level of Custom user,
{"name": "jon", "avatar": null, "doctor_type": "anything"}
but it has to receive like this
{"name": "jon", "avatar": null, "profile": {"doctor_type": "anything}}
"""
class Meta:
model = User
fields = ('name', 'avatar', 'profile', )
#transaction.atomic
def update(self, instance, validated_data):
ModelClass = self.Meta.model
"""print("=======validated_data=========: ", validated_data). I found in this
line that the payload is wrong"""
profile = validated_data.pop('profile', {})
"""print("=======profile=========: ", profile) profile is not in validated data
that's why profile = {}"""
ModelClass.objects.filter(id=instance.id).update(**validated_data)
if profile:
DoctorProfile.objects.filter(owner=instance).update(**profile)
new_instance = ModelClass.objects.get(id = instance.id)
return new_instance
On the other hand if I don't use source the error is same for both get and put method.
View:
class DoctorProfileAPIView(generics.RetrieveUpdateDestroyAPIView):
"""To get the doctor profile fields and update and delete"""
serializer_class = DoctorProfileSerializer
queryset = User.objects.all()
def get_object(self):
return get_object_or_404(User, id=self.request.user.id, is_active=True)
After using source the get method returns in the expected format:
{
"name": "Maruf updated again",
"avatar": null,
"profile": {
"doctor_type": null,
"date_of_birth": null,
"registration_number": null,
"city": null,
"country": null
}
}
But the problem is with the Put method.
Another note: with using source and not overriding the update method in the serializer, only the CustomUser attributes is getting updated.
my objective is to get both CustomUser+Profile in the same endpoint.
And when updating, the CustomUser and the Profile will be updated in their own table but through the same endpoint.
I'm working on a small project using Django Rest Framework, I have two models ( contacts and category)
So a contact can be in a category, I have a foreign key between the models, I would like to know how can I get data category name instead of getting the id number.
This is my code :
class Category(models.Model):
cat_name = models.CharField(blank=False, max_length=255)
comment = models.CharField(blank=False, max_length=255)
private = models.BooleanField(default=False)
allowed = models.BooleanField(default=False)
def __str__(self):
return self.name
class Contact(models.Model):
category = models.ForeignKey(Category, on_delete=models.DO_NOTHING)
first_name = models.CharField(max_length=60)
last_name = models.CharField(max_length=60)
My serializer
class ContactSerializer(serializers.ModelSerializer):
class Meta:
model = Contact
fields = "__all__"
Result I get :
"first_name": "John",
"last_name": "Doe",
"category": 1 ( i want to get the name of the category instead of the id )
This is one possible solution
class ContactSerializer(serializers.ModelSerializer):
class Meta:
model = Contact
fields = "__all__"
def to_representation(self, obj):
return {
"first_name": obj.first_name,
"last_name": obj.last_name,
"category": obj.category.cat_name
}
Try this:
class ContactSerializer(serializers.ModelSerializer):
category_name = serializers.SerializerMethodField('get_category_name')
def get_category_name(self, obj):
if obj.category_id:
return obj.category.cat_name
return ""
class Meta:
model = Contact
fields = "__all__"
I got into same situation.I think there is no need to write another function if you can achieve this by one line of code and adding it to fields using source.You can also try this:
class ContactSerializer(serializers.ModelSerializer):
category = serializers.CharField(source="category.cat_name", read_only=True)
class Meta:
model = Contact
fields = ['first_name','last_name', 'category']
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 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)
I have two model below with foreign key relation.
class City(TimeStampedModel):
uuid = models.UUIDField(default=uuid.uuid4, editable=False)
long_name = models.CharField(max_length=200)
short_name = models.CharField(max_length=200)
class Address(TimeStampedModel):
address_object = GenericForeignKey('address_content_type', 'object_id')
address1 = models.CharField(max_length=200)
address2 = models.CharField(max_length=200, null=True)
landmark = models.CharField(max_length=200, null=True)
city = models.ForeignKey(City, related_name='address_city')
And I have defined Below serializer for Address
class CityRelation(serializers.RelatedField):
def to_representation(self, value):
if isinstance(value, City):
return CitySerializer(value).data
class AddressBookSerializer(serializers.ModelSerializer):
city = CityRelation(read_only=True)
class Meta:
model = Address
fields = ('id', 'uuid', 'address1', 'address2', 'landmark', 'city')
#atomic
def create(self, validated_data):
address_book = Address(**validated_data)
address_book.save()
return address_book
def update(self, instance, validated_data):
instance.address1 = validated_data['address1']
instance.address2 = validated_data['address2']
instance.landmark = validated_data['landmark']
instance.city = validated_data['city']
instance.save()
return instance
Here While Deserializing i want to pass only city_id in JSON but while serializing i want complete city object....so i override relatedfield...but i am getting error in deserialization. I also tried to override to_internal_value() method but it did not called during deserialization. How can i do that??
I solved it...Actually i set readonly=True for city relation but with readonly only to_representation() get called not to_internal_value()...so i passed queryset...