Django Rest Serializer get data related through Foreign Keys - python

I have the following model skeletons:
class A(models.Model):
post_id = models.ForeignKey('D')
user_id = models.ForeignKey('B')
class B(AbstractBaseUser):
email = models.EmailField(unique=True)
username = models.CharField(max_length=20, unique=True)
first_name = models.CharField(max_length=40)
last_name = models.CharField(max_length=40)
# `resource_id`
profile_photo = models.ForeignKey('C', null=True)
class C(models.Model):
user_id = models.ForeignKey('B')
name = models.CharField(max_length=60)
I want to write a serializer for A which should return name from model C, which is related to B.
The relation here is like
A->B->C
Now using A's serializer I want data to be fetched from C
I want to access C via A i.e get profile_photo from B and the get the name of profile_photo from C
I scrolled through RelatedFields as given here Django Rest relations but am not able to achieve what I want.
Is their any way I can achieve it.
Also there are a lot of fields other than mentioned in the model skeleton and I do not want to fetch those.
EDIT:
The final result I need is the all the user_id for a particular post_id from A with the name from model C

You can do this with a ModelSerializer:
Note: I will asumme that 'posts.Post' is model C
class CsSerializer(serializers.ModelSerializer):
class Meta:
model = C
fields = ('name')
class AsSerializer(serializers.ModelSerializer):
post_id = CsSerializer()
class Meta:
model = A
# exclude = ('field_you_dont_want_1', 'field_you_dont_want_2')
# fields = ('field_you_want_1', 'field_you_want_2')
Note that in CsSerializer you can return any field you want from model C in this case I'm just returning a name.
If you just need a string, you could use StringRelatedField
class AsSerializer(serializers.ModelSerializer):
post_id = serializers.StringRelatedField(many=True)
class Meta:
model = A
This StringRelatedField will return what C models return in __unicode__ method (__str__ method if python 3.x).
EDIT
Now, from your comment, I know that 'posts.Post' is not C model. So you could use SerializerMethodField:
class AsSerializer(serializers.ModelSerializer):
c_name = serializers.SerializerMethodField()
def get_c_name(self, obj):
return obj.user_id.c_set.values('name') # this will return a list of dicts with B's related C objects.
class Meta:
model = A
EDIT
You can always define class methods in your models:
class B(AbstractBaseUser):
email = models.EmailField(unique=True)
username = models.CharField(max_length=20, unique=True)
first_name = models.CharField(max_length=40)
last_name = models.CharField(max_length=40)
# `resource_id`
profile_photo = models.ForeignKey('C', null=True)
def get_profile_photo(self):
return self.profile_photo.name # This will return 'C' related object's name
So when you have an A's objects:
a_object.user_id.get_profile_photo()
Or if you have an B's object:
b_object.get_profile_photo()
EDIT
You can define a new Serializer for C class:
class CsSerializer(serializers.Serializer):
class Meta:
model = C
Then in your AsSerializer
class AsSerializer(serializers.ModelSerializer):
c_name = serializers.SerializerMethodField()
def get_c_name(self, obj):
qs = obj.user_id.c_set.all()
qs_serialized = CsSerializer(qs, many=True)
return qs_serialized.data
class Meta:
model = A

Related

Django - UniqueConstraint not created

I am trying to enforce a constraint for mysql where user is prohibited from inserting twice the same name and model. E.g This should not be allowed to be inserted twice: name:Name1 model:Model1
#Model
class Car(models.Model):
name = models.CharField(max_length=100)
model = models.CharField(max_length=100)
#View
class CarListCreateAPIView(generics.ListCreateAPIView):
serializer_class = CarSerializer
def get_queryset(self):
trip_code = self.kwargs.get("pk")
return Car.objects.filter(trip = trip_code) #Return cars for given trip
#Seializer
class CarSerializer(serializers.ModelSerializer):
class Meta:
model = Car
fields = ('__all__')
constraints = [
models.UniqueConstraint(fields=['name', 'model'], name='car_name_model_constraint')
]
The problem is that the constraint is never created and thus not enforced. What might be the issue with the code?
Use unique_together in class Meta in Model like that:
class Car(models.Model):
name = models.CharField(max_length=100)
model = models.CharField(max_length=100)
class Meta:
unique_together = ['name','model']
More on that here: https://docs.djangoproject.com/en/4.0/ref/models/options/#unique-together

Getting AttribueError by SlugRelatedField despite the object being saved

I am creating an API to save class teachers. Now all the fields in the ClassTeacher model are foreign fields so I am using a SlugRelatedField in the serializer. It looks like SlugRelatedField does not support attribute lookup like this "user__username" and raises attribute error HOWEVER the object is still being saved.
models.py
class ClassTeacher(models.Model):
teacher = models.ForeignKey(Teacher, on_delete=models.CASCADE)
class_name = models.ForeignKey(Classes, on_delete=models.CASCADE)
school_id = models.ForeignKey(School, on_delete=models.CASCADE)
serializers.py
class ClassTeacherSerializer(ModelSerializer):
teacher = SlugRelatedField(slug_field='user__username', queryset=Teacher.objects.all()) <---- this is causing the error
class_name = SlugRelatedField(slug_field='class_name', queryset=Classes.objects.all())
school_id = SlugRelatedField(slug_field='school_id__username', queryset=School.objects.all()) <---- and I am assuming that this will too
class Meta:
model = ClassTeacher
fields = '__all__'
I tried adding a #property in the Teacher model to retrieve the username and use the property in the slug_field but that did not work too.
How can I save the object without getting the error?
EDIT 1:
teachers/models.py
class Teacher(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
name = models.CharField(max_length=30)
photo = models.URLField()
teacher/serializers.py
class TeacherSerializer(ModelSerializer):
class Meta:
model = Teacher
fields = '__all__'
school/models.py
class School(models.Model):
school_id = models.OneToOneField(User, on_delete=models.CASCADE)
principal = models.CharField(max_length=50)
name = models.CharField(max_length=50)
photo = models.URLField()
school/serializers.py
class SchoolSerializer(ModelSerializer):
class Meta:
model = School
fields = '__all__'
EDIT 2:
Here's how I used the #property by referring from here:
teacher/models.py
class Teacher(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
name = models.CharField(max_length=30)
photo = models.URLField()
#Here's the extra property part
#property
def username(self):
return self.user.username
classteacher/serializers.py
class ClassTeacherSerializer(ModelSerializer):
#Here I changed user__username to just username as mentioned in the above link
teacher = SlugRelatedField(slug_field='username', queryset=Teacher.objects.all())
class_name = SlugRelatedField(slug_field='class_name', queryset=Classes.objects.all())
school_id = SlugRelatedField(slug_field='school_id__username', queryset=School.objects.all())
class Meta:
model = ClassTeacher
fields = '__all__'
try renaming serializer field from teacher to user and using slug_field='username'
You can use #property for example
class User(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
name = models.CharField(max_length=30)
photo = models.URLField()
#property
def get_username(self):
return Teacher.objects.filter(user_id=self.id)
and than inside your ClassTeacherSerializer use slug_field='username'
let me know if it works.

Showing extra field on ManyToMany relationship in Django serializer

I have a ManyToMany field in Django, like this:
class Dictionary(models.Model):
traditional = models.CharField(max_length=50)
simplified = models.CharField(max_length=50)
pinyin_numbers = models.CharField(max_length=50)
pinyin_marks = models.CharField(max_length=50)
translation = models.TextField()
level = models.IntegerField()
frequency = models.IntegerField()
idiom = models.BooleanField()
child_char = models.ManyToManyField('Dictionary', through='DictionaryChildChar', null=True)
class Meta:
db_table = 'dictionary'
indexes = [
models.Index(fields=['simplified', ]),
models.Index(fields=['traditional', ]),
]
class DictionaryChildChar(models.Model):
class Meta:
db_table = 'dictionary_child_char'
from_dictionary = models.ForeignKey(Dictionary, on_delete=models.CASCADE, related_name="from_dictionary")
to_dictionary = models.ForeignKey(Dictionary, on_delete=models.CASCADE, related_name="to_dictionary")
word_order = models.IntegerField()
Currently, I have a serializer like this:
class FuzzySerializer(serializers.ModelSerializer):
pinyin = serializers.CharField(
required=False, source="pinyin_marks")
definition = serializers.CharField(
required=False, source="translation")
hsk = serializers.CharField(required=False, source="level")
class Meta:
model = Dictionary
fields = ["id", "simplified", "pinyin", "pinyin_numbers","definition", "hsk", "traditional", "child_char"]
depth = 1
This gives me a dictionary entry, as well as the child dictionary entries associated with it (as a Chinese word is made up of several Chinese characters)
However, I need to know what order these child characters are in, and hence why I have word_order.
I would like this word_order field to appear on the individual child_char - how do I write my serializer in such a way that this additional field is present? Would I need to make a separate serializer for child_char?
EDIT: I have tried this serializer, it doesn't work:
class FuzzyChildCharSerializer(serializers.ModelSerializer):
class Meta:
model = DictionaryChildChar
fields = ["word_order"]
Easiest way is to create a dedicated FuzzyChildCharSerializer and then connect it to your original serializer as a nested relationship:
class FuzzyChildCharSerializer():
class Meta:
model = DictionaryChildChar
fields = ["word_order"] # And whatever other fields you want
class FuzzySerializer():
child_char = FuzzyChildCharSerializer(many=True)
...
You could also write a SerializerMethodField.
It appears I had to bridge the connection via the glue table, which makes sense.
class FuzzyChildCharSerializer(serializers.ModelSerializer):
pinyin = serializers.CharField(
required=False, source="pinyin_marks")
definition = serializers.CharField(
required=False, source="translation")
hsk = serializers.CharField(required=False, source="level")
class Meta:
model = Dictionary
fields = ["id", "simplified", "pinyin", "pinyin_numbers","definition", "hsk", "traditional",]
class FuzzyChildCharSerializerGlue(serializers.ModelSerializer):
to_dictionary = FuzzyChildCharSerializer()
class Meta:
model = DictionaryChildChar
fields = '__all__'
class FuzzySerializer(serializers.ModelSerializer):
pinyin = serializers.CharField(
required=False, source="pinyin_marks")
definition = serializers.CharField(
required=False, source="translation")
hsk = serializers.CharField(required=False, source="level")
from_dictionary = FuzzyChildCharSerializerGlue(many=True)
class Meta:
model = Dictionary
fields = ["id", "simplified", "pinyin", "pinyin_numbers","definition", "hsk", "traditional", "from_dictionary"]
depth = 1
This provides each character with its given word order

Reverse Lookup in django joined tables does not work

I just created a model as follows:
class A(models.Model):
name = models.CharField(max_length=150)
slug = models.SlugField(max_length=200)
class Meta:
db_table = "ax"
class B(models.Model):
name = models.CharField(max_length=150)
slug = models.SlugField(max_length=200)
class Meta:
db_table = "bx"
class Z(models.Model):
a = models.ForeignKey(A, on_delete=models.CASCADE)
b = models.ForeignKey(B, on_delete=models.CASCADE)
class Meta:
db_table = "zx"
I want to get list of A.names when B.slug == input_str.
What I've tried in view.py so far:
def myView(request, input_str):
context = {
'inputs_list': A.objects.filter(bx__slug__startswith=input_str).select_related('ax').select_related('bx').values('name')
}
return render(request, 'app/my_template.html', context)
This returns an error:
Cannot resolve keyword 'bx' into field. Choices are:zx, id, name,
slug...
My Question:
What is the correct lookup for returning A.name if B.slug= input_str. Or better say, how filter A based on a column in B?😊
A.objects.filter(bx__slug__startswith=input_str).select_related('ax').select_related('bx').values('name')

Lack of data with serialization model with ManyToManyField

Here's are examples I have:
models.py:
class Example(models.Model):
title = models.CharField(...)
description = models.CharField(...)
class Foo(models.Model):
example = models.ManyToManyField(Example)
serializers.py:
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = Foo
fields = '__all__'
depth = 1
views.py:
...
serialized_data = [FooSerializer(foo).data for foo in Foo.objects.all().get]
In output, I receive only Example's IDs, but is there any way I could get title and description fields also (details of m2mfield)? As I understand, Foo.objects.all().get simply doesn't contain this data, but maybe I could somehow get it and use it?
I could also rebuild models if needed, but currently I use m2mf because of needs to contain multiple objects as related to this model data.
update
models.py:
class Event(models.Model):
ts = models.BigIntegerField(editable=False)
class Foo(Event):
user = models.ForeignKey(User, ...)
example = *...(remains to be the same)*
foos = models.ForeignKey('self', **somemore** null=True)
serializers.py:
class EventSerializer(serializers.ModelSerializer):
class Meta:
model = Event
fields = '__all__'
def to_representation(self, instance):
result = {'ts': instance.ts}
if isinstance(instance, Foo):
result['foo'] = FooSerializer(instance).data
return result
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username')
class FooSerializer(serializers.ModelSerializer):
# user = UserSerializer(read_only=True) # with this I have an error: Got AttributeError when attempting to get a value for field 'username' on #serializer 'UserSerializer'
class Meta:
model = Foo
fields = '__all__'
depth = 1
You could use depth attribute to achieve desired output.
The default ModelSerializer uses primary keys for relationships, but
you can also easily generate nested representations using the depth
option.The depth option should be set to an integer value that
indicates the depth of relationships that should be traversed before
reverting to a flat representation.
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = Foo
fields = '__all__'
depth = 1
Apart from the answer, I would like to change your views.py code, cause it seems like very bad :(. Do it on DRF Way as
serialized_data = FooSerializer(Foo.objects.all(), many=True).data<br>
Example View
from rest_framework.viewsets import ModelViewSet
class FooViewset(ModelViewSet):
serializer_class = FooSerializer
queryset = Foo.objects.all()
UPDATE-1
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
exclude = ('password',) # add fields that are need to be excluded
class FooSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = Foo
fields = '__all__'
depth = 1
depth = 1 will serializer all fields in the model, (It's same as setting the fields=='__all__' in Meta class of serializer)
UPDATE-2
class FooSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = Foo
fields = '__all__'
depth = 1
def to_representation(self, instance):
real_data = super().to_representation(instance).copy()
# DO YOUR EXTRA CHECKS
child = UserSerializer(instance.child_foo).data
if child:
real_data.update({"child_data": child})
# After your checks, add it to "real_data"
return real_data
and I assumed I have a Foo model as
class Foo(models.Model):
example = models.ManyToManyField(Example)
user = models.ForeignKey(User)
child_foo = models.ForeignKey('self', null=True, blank=True)
In your serializer add depth = 1. Example where 'users' is the related field:
FooSerializer(serializers.ModelSerializer):
class Meta:
model = Foo
fields = ('id', 'account_name', 'users', 'created')
depth = 1

Categories