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...
Related
I have a class named Property with a field property_type, i wana autofill this field when i create one of the other models who have onetoone relationship with Property
I want the when i create a Apartment in django admin for exp the property_type in Property should autofill with the "Apartment", when i create Villa in django admin it should autofill with "villa"
class Property(models.Model):
created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name="agent_of_property")
district_id = models.ForeignKey(District, on_delete=models.CASCADE)
slug = models.SlugField(max_length=255, unique=True)
property_type = models.CharField(choices=PROPERTY_TYPE, max_length=20)
title = models.TextField(verbose_name="Titulli", help_text="Vendos titullin e njoftimit", max_length=500)
description = models.TextField(verbose_name="Pershkrimi", help_text="Vendos pershkrimin",max_length=1000)
address_line = models.CharField(verbose_name="Adresa", help_text="E nevojeshme",max_length=255)
price = models.IntegerField()
area = models.IntegerField()
views = models.IntegerField(default=0)
documents = models.CharField(verbose_name="Dokumentacioni", help_text="Dokumentat", max_length=255)
status = models.BooleanField(default=True)
activity = models.CharField(max_length=20, choices=ACTION_OPTION)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True, editable=False)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = "Prona"
verbose_name_plural = "Pronat"
def get_absolute_url(self):
return reverse("pronat:property_detail", args=[self.slug])
def __str__(self):
return self.title
class Apartment(Property):
property_id = models.OneToOneField(Property, on_delete=models.CASCADE, parent_link=True, primary_key=True)
floor = models.IntegerField(default=0)
room_num = models.IntegerField(default=0)
bath_num = models.IntegerField(default=0)
balcony_num = models.IntegerField(default=0)
class Meta:
verbose_name = "Apartament"
verbose_name_plural = "Apartamentet"
def save(self, *args, **kwargs):
self.property_type = "Apartment"
class Villa(Property):
property_id = models.OneToOneField(Property, on_delete=models.CASCADE, parent_link=True, primary_key=True)
living_room = models.IntegerField(default=1)
floors = models.IntegerField(default=1)
room_num = models.IntegerField(default=1)
bath_num = models.IntegerField(default=1)
balcony_num = models.IntegerField(default=0)
class Meta:
verbose_name = "Vila"
verbose_name_plural = "Vilat"```
I don't understand what your are doing. If you are inheriting from Property models in Apartment just remove OneToOneField and your save method would be
def save(self, *args, **kwargs):
self.property_type = 'Apartment'
super().save(*args, **kwargs)
But if you really want to use OneToOneField, replace Property inherit with models.Model and your save method should look like this.
def save(self, *args, **kwargs):
self.property_id.property_type = 'Apartment'
self.property_id.save()
super().save(*args, **kwargs)
For 2nd case you can also use django signal
from django.db.models.signal import post_save
# ...
def populate_property_type(sender, created, instance, **kwargs):
if sender.__name__ == 'Apartment':
instance.property_id.property_type = 'Apartment'
instance.property_id.save()
# condition for Villa
post_save.connect(populate_property_type, sender=Apartment)
post_save.connect(populate_property_type, sender=Villa)
I would suggest that you should add field in Appartment table with the name appartment_type and remove property_type from Property. But If you don't want to do that then Two ways are possible for your case.
1.When you are adding values from Django admin, you should also add the property_type from Admin.In that case you don't need to override save method.
Register your Property and Appartment Table in one view.
Make the admin Tables of Property and Appartment for admin view. Then register it like this way i-e
admin.site.register(PropertyAdmin, AppartmentAdmin)
and add values into both tables.
2: Simply override the save method and add this
def save(self, *args, **kwargs):
self.property_id.property_type = 'Apartment' # you should add other field in Appartment table just avoid hard coded 'Apartment'
self.property_id.save()
super().save(*args, **kwargs)
There are these models defined as base structures:
class Product(models.Model):
product_id = models.CharField(max_length=40, default=0, primary_key=True)
product_name = models.CharField(max_length=40, default='Generic Product')
def __str__(self):
return self.product_id
class ShoppingList(models.Model):
shop_list_id = models.CharField(max_length=20, default=0, primary_key=True)
shop_list_name = models.CharField(max_length=40, default=0)
product_id = models.ForeignKey(Product, on_delete=models.CASCADE, default=0)
session_id = models.CharField(max_length=40, default=0)
def __str__(self):
return self.shop_list_id
And there is another model that is used for checking if a Product and/or ShoppingList exist:
class ShopListCheck(models.Model):
product_id = models.ForeignKey(ShoppingList, on_delete=models.CASCADE, default=0)
shop_list_id = models.OneToOneField(ShoppingList, related_name='sid',
on_delete=models.CASCADE, default=0)
request_creation_date = models.DateTimeField(auto_now=True)
def session_id(self):
return self.product_id.session_id
def shop_list_id(self):
return self.product_id.shop_list_id
def __str__(self):
return self.product_id
Serializers are defined this way:
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = '__all__'
class ShoppingListSerializer(serializers.ModelSerializer):
class Meta:
model = ShoppingList
fields = '__all__'
class ShopListCheckSerializer(serializers.ModelSerializer):
session_id = serializers.SerializerMethodField()
link = serializers.SerializerMethodField()
class Meta:
model = ShopListCheck
fields = ('product_id', 'shop_list_id', 'session_id',
'request_creation_date', 'link')
def get_session_id(self, obj):
return obj.product_id.session_id
def get_link(self, obj):
return 'http://example.com/' + str(obj.product_id)
+ '/?session_id=' + str(obj.session_id())
Now I can send a POST request containing product_id to the ShopListCheck endpoint and I get all the product_id, shop_list_id, session_id, etc. (or error 400 if there's no given product_id in the dababase) in response.
What to do when I also want to check in the same request if given shop_list_id exists (or set "0" when I don't care about that)?
BTW, do you know any good resources I could practice this kind of relationships between models?
You can override is_valid method of drf serializers to do field-level validation on shop_list_id and also set that field afterwards. You can check the documentation from here.
In this project, I have two models Student and Parent related to each other through one to one field.
In Parent serializer, I want to add Students attributes like age. I am thinking of using SerializerMethodField for both cases is their any better way to do it?
I am not getting the queries on how to get the object attributes and little explanation would be great.
Here what I have done till now.
Models.py
class Student(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, default=None)
batch = models.ForeignKey(Batch, on_delete=models.CASCADE, null=True, related_name='students')
email = models.EmailField(null=True)
phone_number = models.CharField(max_length=10, null=True)
dob = models.DateField(blank=True, null=True, help_text="Enter in the following format : YYYY-MM-DD")
address = models.TextField(max_length=150, null=True)
age = models.IntegerField(blank=True)
image = models.ImageField(upload_to='profile_pictures', default='student_image.png', blank=True)
#property
def remarks(self):
return self.remark_set.all()
#property
def marks(self):
return self.marks_set.all()
def __str__(self):
return self.user.firstName + ' ' + self.user.lastName
class Parent(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, default=None)
child = models.ForeignKey(Student, on_delete=models.CASCADE)
email = models.EmailField(null=True)
phone_number = models.CharField(max_length=10, null=True)
address = models.TextField(max_length=150, null=True)
image = models.ImageField(upload_to='profile_pictures', default='student_image.png', blank=True)
def __str__(self):
return self.user.firstName + ' ' + self.user.lastName
Serilaizer.py
class ParentSerializer(serializers.HyperlinkedModelSerializer):
student_age = serializers.SerializerMethodField()
student_batch = serializers.SerializerMethodField()
parent_name = serializers.SerializerMethodField()
class Meta:
model = Parent
fields = "__all__"
def get_student_age(self, obj):
return Parent.objects.get(child__age = self.obj.user.child)
def get_student_batch(self, obj):
return Parent.objects.get(child__bacth = self.obj.user.child)
def get_parent_name(self, user):
return Parent.objects.get(user=self.request.user)
Views.py
class ParentView( mixins.ListModelMixin, mixins.RetrieveModelMixin,viewsets.GenericViewSet):
queryset = Parent.objects.all()
serializer_class = serializers.ParentSerializer
first way:
from apps.models import Student, parent
class BasicUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = "__all__"
class BasicStudentSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = "__all__"
class ParentSerializer(serializers.ModelSerializer):
user = BasicUserSerializer(read_only=True,many=False)
child = BasicStudentSerializer(read_only=True, many=True)
class Meta:
model = Parent
fields = '__all__'
you can do this way . its replace a serializer field that you want and if parent have several child then in child's field you have new all child's information as dictionary.
================================================================
second way is use HyperLinkModel .
class ParentSerializer(serializers.ModelSerializer):
user = serializers.HyperlinkedRelatedField(read_only=True,many=False)
child = serializers.HyperlinkedRelatedField(read_only=True, many=True)
class Meta:
model = Parent
fields = '__all__'
but notice that in first way you will have a independent serializer class that every time you need to serialize model class that related to User or Child you can use them simply.
I have the following models:
class School(models.Model):
id = patch.BigAutoField(primary_key=True)
name = models.CharField('Name', max_length=100)
address = models.CharField('Address', max_length=500, blank=True, null=True)
class Child(BaseModel):
id = patch.BigAutoField(primary_key=True)
name = models.CharField('Name', max_length=100, blank=True, null=True)
school = models.ForeignKey('User', blank=True, null=True, db_constraint=False, db_index=True, on_delete=models.CASCADE, default=None)
I have the following serializers :
class SchoolSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
name = serializers.CharField(required=True, max_length=100)
address = serializers.CharField(required=False, max_length=400)
def create(self, validated_data):
return School.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.name = validated_data.get('name', instance.name)
instance.address = validated_data.get('address', instance.address)
instance.save()
return instance
class Meta:
model = School
fields = ('id', 'name', 'address')
class ChildSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
name = serializers.CharField(required=False, max_length=100, allow_blank=False)
school = SchoolSerializer()
def create(self, validated_data):
return Child.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.name = validated_data.get('name', instance.name)
instance.school = validated_data.get('school', instance.school)
instance.save()
return instance
Now the problem that I am facing is that when I saving any value in my child table using serializer then the value of school is showing null in my database but in my request object I am getting value for school_id.
Since this school = SchoolSerializer() school would be a fully serialized object, not scalar value.
Take a look at this example it should help: Writable nested serializer with existing objects using Django Rest Framework 3.2.2
You got to convert serialized object school manually into scalar valued school PK to fill school_id FK field. Or just remove school = SchoolSerializer(), then ChildSerializer will start serializing this field as scalar-valued (but still FK) and thus will make it simply writable directly to the school_id field. The rest of the code should work well.
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