How do I join two tables in Django ORM? - python

I am a beginner in Django and I know this question has alredy been asked, but I've tried every possible solution from previous answers and it still doesn't work. I can't figure out what I'm doing wrong.
The thing is my view currently returns all the fields of the Grade table, but I need it to return all of those fields plus the "name" field which is in the Student table, by joining the two tables.
I read that Django should do it automatically as long as I use ForeignKey, which I did, but it actually doesn't work.
What am I doing wrong? I'm sorry if it's a noob question and if the solution is really obvious, I'm still trying to learn how Django works.
app/models.py
class Student(models.Model):
id = models.IntegerField(primary_key=True, default=0)
name = models.CharField(max_length=50)
class Grade(models.Model):
subject = models.CharField(max_length=50)
grade = models.IntegerField(default=0)
student = models.ForeignKey(Student, on_delete=models.CASCADE)
app/serializers.py
class StudentSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = ('id', 'name')
class GradeSerializer(serializers.ModelSerializer):
class Meta:
model = Grade
fields = ('subject', 'grade', 'student')
app/views.py
class StudentView(viewsets.ModelViewSet):
serializer_class = StudentSerializer
queryset = Student.objects.all()
class GradeView(viewsets.ModelViewSet):
serializer_class = GradeSerializer
queryset = Grade.objects.all().select_related("student")
filterset_fields = ('student')

For the student you can use the `StudentsSerializer, like:
class GradeSerializer(serializers.ModelSerializer):
student = StudentSerializer()
class Meta:
model = Grade
fields = ('subject', 'grade', 'student')

Related

Unable to get related data from ManyToManyField

I'm trying to fetch related objects from below two models.
Following django models with ManyToManyField relationship.
Book
class Book(models.Model):
authors = models.ManyToManyField(
to=Author, verbose_name="Authors", related_name="books_author"
)
bookshelves = models.ManyToManyField(
to=Bookshelf, verbose_name="Bookshelf", related_name="books_shelves"
)
copyright = models.NullBooleanField()
download_count = models.PositiveIntegerField(blank=True, null=True)
book_id = models.PositiveIntegerField(unique=True, null=True)
languages = models.ManyToManyField(
to=Language, verbose_name=_("Languages"), related_name="books_languages"
)
Author
class Author(models.Model):
birth_year = models.SmallIntegerField(blank=True, null=True)
death_year = models.SmallIntegerField(blank=True, null=True)
name = models.CharField(max_length=128)
def __str__(self):
return self.name
class Meta:
verbose_name = _("Author")
verbose_name_plural = _("Author")
I have to fetch all the Auhtors with their related books. I have tried a lot of different ways none is working for me.
First way : using prefetch_related
class AuthorListAPIView(APIErrorsMixin, generics.ListAPIView):
serializer_class = AuthorSerializer
queryset = Author.objects.exclude(name__isnull=True)
def get_queryset(self):
auths = queryset.prefetch_related(Prefetch("books_author"))
Second way using related_name 'books_auhtor'
class AuthorListAPIView(APIErrorsMixin, generics.ListAPIView):
serializer_class = AuthorSerializer
queryset = Author.objects.exclude(name__isnull=True)
def get_queryset(self):
auths = queryset.books_author.all()
None of the above ways worked for me. I want to prepare a list of Authors and their associated books.
For ex:-
[{'Author1':['Book1','Book2'],... }]
Prefetching is not necessary, but can be used to boost efficiency, you can work with:
class AuthorListAPIView(APIErrorsMixin, generics.ListAPIView):
serializer_class = AuthorWithBooksSerializer
queryset = Author.objects.exclude(name=None).prefetch_related('books_author')
In the AuthorWithBooksSerializer, you can then add the data of the books, for example:
from rest_framework import serializers
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ('book_id', 'copyright')
class AuthorWithBooksSerializer(serializers.ModelSerializer):
books = BookSerializer(source='books_author', many=True)
class Meta:
model = Author
fields = ('name', 'books')
Here the books will use the BookSerializer and thus encode a list of dictionaries.
While you can use the name of the author as object key, I strongly advise against this: it makes the object less accessible since the keys are no longer fixed and if these contain spaces, it can also result in more trouble obtaining the value(s) associated with a given attribute name.

Django Rest Framework: Handle Many-to-Many relationship

The requirement is "I want to insert person with the person groups selection and also at the time of Creating person group I can choose persons for that particular group".
I've added two models in my models.py and manage many to many relationship between.
models.py
from django.db import models
class PersonGroup(models.Model):
id = models.AutoField(primary_key=True)
groupName = models.CharField(max_length=30)
detail = models.CharField(max_length=200)
class Person(models.Model):
id = models.AutoField(primary_key=True)
personId = models.CharField(max_length=20)
personName = models.CharField(max_length=20)
state = models.IntegerField()
personGroup = models.ManyToManyField(PersonGroup, related_name="person_list", blank=True)
serializers.py
class PersonSerializer(serializers.ModelSerializer):
personGroup = serializers.PrimaryKeyRelatedField(queryset=PersonGroup.objects.all(), many=True)
class Meta:
model = Person
fields = '__all__'
class PersonGroupSerializer(serializers.ModelSerializer):
person_list = PersonSerializer(many=True, read_only=True)
class Meta:
model = PersonGroup
fields = '__all__'
The above code help me to create person with personGroup selection
But, I also want to add persons selection at the time of create personGroup. Currently at the time of creating personGroup I'm not allowed to enter persons.
Please let me know if there any solution by which I can also select available persons at the time of person group creation.
Your person_list field in the PersonGroupSerializer is on read only, so you can't modify it using the API.
person_list = serializers.PrimaryKeyRelatedField(queryset=Person.objects.all(), many=True)
Try removing this arg.
You might also want to switch to a ForeignKey field instead of slugged.

Django rest framework serializer with reverse relation

I have two models where employee have relation with person model but person have no relation with employee model.
Like:
class Person(models.Model):
name = models.CharField(max_length=100)
address = models.CharField(max_length=100)
class Employee(models.Model):
person = models.ForeignKey(Person, related_name='person_info')
code = models.CharField()
In such cases I want code field data in person serializer.
I can solved this with writing method in person model or using SerializerMethodField in person serializer
like this:
def get_employee_code(self):
return Employee.objects.get(person=self).id
and add this as source in person serializer
employee_code = serializers.CharField(source='get_employee_code')
Or adding employee serializer into person serialiszer
class PersonSerializer(serializers.ModelSerializer):
employee = EmployeeSerializer()
class Meta:
model = Person
fields = ('name', 'address', 'employee')
But i was trying to do this with reverse relation but i can't. I have tried like this, it gives an error
Serializer:
class PersonSerializer(serializers.ModelSerializer):
employee_code = serializers.CharField(source='person_info.code')
class Meta:
model = Person
fields = ('name', 'address', 'employee_code')
How can i solve this with reverse relation?
At the moment because you are using a ForeignKey field on the person attribute, it means that its returning a list when you access the reverse relation.
One solution would be to use a slug related field, though this must have many and read_only set to True, and will return a list because of the ForeignKey field.
class PersonSerializer(serializers.ModelSerializer):
employee_code = serializers.SlugRelatedField(
source='person_info',
slug_field='code',
many=True,
read_only=True,
)
class Meta:
model = Person
fields = ('name', 'address', 'employee_code')
The other option is to change your ForeignKey into a OneToOneField, which would still need read_only set to True but it will not return a list.
class Person(models.Model):
name = models.CharField(max_length=100)
address = models.CharField(max_length=100)
class Employee(models.Model):
person = models.OneToOneField(Person, related_name='person_info')
code = models.CharField()
class PersonSerializer(serializers.ModelSerializer):
employee_code = serializers.SlugRelatedField(
source='person_info',
slug_field='code',
read_only=True,
)
class Meta:
model = Person
fields = ('name', 'address', 'employee_code')
Or, if you don't want to change the ForeignKey, you could add a employee_code property method to the model instead to return the first employee code in the person_info relation.
class Person(models.Model):
name = models.CharField(max_length=100)
address = models.CharField(max_length=100)
#property
def employee_code(self):
employees = self.person_info.filter()
if employees.exists():
return employees.first().code
return ''
class Employee(models.Model):
person = models.OneToOneField(Person, related_name='person_info')
code = models.CharField()
class PersonSerializer(serializers.ModelSerializer):
employee_code = serializers.CharField(
read_only=True,
)
class Meta:
model = Person
fields = ('name', 'address', 'employee_code')
you can access the reverse relation with custom SerializerMethodField()
class PersonSerializer(serializers.ModelSerializer):
employee_code = serializers.SerializerMethodField()
def get_employee_code(self, obj):
return obj.person_info.code
class Meta:
model = Person
fields = ('name', 'address', 'employee_code')

Django REST Framework Serializer returning object instead of data

I am writing a simple database for the condo I live in which has a list of people, units, unit type (home vs parking space), and unitholder (join table for many-to-many relationship between a person and a unit) - one person can be the owner of a unit type of "home" while renting a parking space.
This is my model:
class Person(models.Model):
first_name = models.CharField(max_length=30, null=False)
last_name = models.CharField(max_length=30, null=False)
phone = models.CharField(max_length=20)
email = models.EmailField(max_length=20)
class UnitType(models.Model):
description = models.CharField(max_length=30)
class Unit(models.Model):
unit_number = models.IntegerField(null=False, unique=True)
unit_type = models.ForeignKey(UnitType, null=False)
unitholders = models.ManyToManyField(Person, through='UnitHolder')
class UnitHolderType(models.Model):
description = models.CharField(max_length=30)
class UnitHolder(models.Model):
person = models.ForeignKey(Person)
unit = models.ForeignKey(Unit)
unitholder_type = models.ForeignKey(UnitHolderType)
This is my view:
class PersonViewSet(viewsets.ModelViewSet):
queryset = Person.objects.all()
serializer_class = PersonSerializer
class UnitHolderTypeViewSet(viewsets.ModelViewSet):
queryset = UnitHolderType.objects.all()
serializer_class = UnitHolderTypeSerializer
class UnitViewSet(viewsets.ModelViewSet):
queryset = Unit.objects.all()
serializer_class = UnitSerializer
class UnitHolderViewSet(viewsets.ModelViewSet):
queryset = UnitHolder.objects.all()
serializer_class = UnitHolderSerializer
class UnitTypeViewSet(viewsets.ModelViewSet):
queryset = UnitType.objects.all()
serializer_class = UnitTypeSerializer
This is my serializer:
class UnitSerializer(serializers.ModelSerializer):
unit_type = serializers.SlugRelatedField(
queryset=UnitType.objects.all(), slug_field='description'
)
class Meta:
model = Unit
fields = ('unit_number', 'unit_type', 'unitholders')
class UnitTypeSerializer(serializers.ModelSerializer):
class Meta:
model = UnitType
class PersonSerializer(serializers.ModelSerializer):
class Meta:
model = Person
class UnitHolderSerializer(serializers.ModelSerializer):
person = serializers.PrimaryKeyRelatedField(many=False, read_only=True)
unit = serializers.PrimaryKeyRelatedField(many=False, read_only=True)
class Meta:
model = UnitHolder
fields = ('person', 'unit', 'unitholder_type')
class UnitHolderTypeSerializer(serializers.ModelSerializer):
class Meta:
model = UnitHolderType
The problem:
When I query the /units endpoint like the following:
u = requests.get('http://localhost:8000/units').json()
My response looks like this:
[{'unit_type': 'Home', 'unit_number': 614, 'unitholders': [1]}]
What I want back is something like this:
[
{
'unit_type': 'Home',
'unit_number': 614,
'unitholders': [
{
'id: 1,
'first_name': 'myfirstname',
'last_name': 'mylastname',
'unitholder_type': 'renter'
}
]
}
]
I'm pretty sure my problem is in my UnitSerializer but I am brand new to DRF and read the through the documentation but still can't seem to figure it out.
An easy solution would be using depth option:
class UnitSerializer(serializers.ModelSerializer):
unit_type = serializers.SlugRelatedField(
queryset=UnitType.objects.all(), slug_field='description'
)
class Meta:
model = Unit
fields = ('unit_number', 'unit_type', 'unitholders')
depth = 1
This will serialize all nested relations 1 level deep. If you want to have fine control over how each nested field gets serialized, you can list their serializers explicitly:
class UnitSerializer(serializers.ModelSerializer):
unit_type = serializers.SlugRelatedField(
queryset=UnitType.objects.all(), slug_field='description'
)
unitholders = UnitHolderSerializer(many=True)
class Meta:
model = Unit
fields = ('unit_number', 'unit_type', 'unitholders')
Also as a side note, you need to look into modifying your querysets inside views to prefetch related objects, otherwise you will destroy the app performance very quickly (using something like django-debug-toolbar for monitoring generated queries is very convenient):
class UnitViewSet(viewsets.ModelViewSet):
queryset = Unit.objects.all().select_related('unit_type').prefetch_related('unitholders')
serializer_class = UnitSerializer
Perhaps you must doing somethings so:
class UnitHolderViewSet(viewsets.ModelViewSet):
queryset = UnitHolder.objects.all()
unitholders = UnitHolderSerializer(read_only=True, many=True)
Django rest framework serializing many to many field

Django REST Framework relationship serialization

I've been banging my head against this issue and know I have to be missing something simple.
I'm trying to learn Django REST Framework and having issues setting the foreign keys of a new object to existing other objects when POSTing JSON to the server.
models.py
class Genre(models.Model):
name = models.CharField(max_length=200)
class Author(models.Model):
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
def full_name(self):
return self.first_name + ' ' + self.last_name
class Book(models.Model):
title = models.CharField(max_length=200)
genre = models.ForeignKey(Genre)
isbn = models.CharField(max_length=15, default='')
summary = models.CharField(max_length=500, null=True)
author = models.ForeignKey(Author)
serializers.py
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = ('id', 'first_name', 'last_name',)
class GenreSerializer(serializers.ModelSerializer):
class Meta:
model = Genre
fields = ('id', 'name',)
class BookSerializer(serializers.ModelSerializer):
author = AuthorSerializer(read_only=True)
genre = GenreSerializer(read_only=True)
class Meta:
model = Book
fields = ('id','url', 'author', 'genre', 'title', 'isbn', 'summary',)
What I'm trying to is create a new book related to an existing Author and Genre. So, given some JSON like
{"title": "Title",
"author": {"id":1}
"genre" : {"id":2}
...
}
I want to create a new book and have its Genre and Author set to the appropriate entities that are already in the database.
I've tried to change the author and genre fields on BookSerializer to serializers.PrimaryKeyRelatedField() and scoured the docs and SO for answers, including this one. I've tried to change the fields in the JSON to "author": 1 or "genre_id": 2 but I can't seem to get it working. I keep getting django.db.utils.IntegrityError: books_book.genre_id may not be NULL.
I am using a DRF ModelViewSet for the views if that makes a difference.
What am I missing here?
You are getting Integrity error because it's expecting the author instance but you are sending the pk related to author. Try this
serializers.py
class BookSerializer(serializers.ModelSerializer):
author = AuthorSerializer(read_only=True)
genre = GenreSerializer(read_only=True)
class Meta:
model = Book
fields = ('id','url', 'author', 'genre', 'title', 'isbn', 'summary',)
def create(self, validated_data):
author_id = self.initial_data.pop("author")
genre_id = self.initial_data.pop("genre")
author = Author.objects.get(id=author_id)
genre = Genre.objects.get(id=genre_id)
book = Book.objects.create(author=author, genre=genre, **validated_data)
return book

Categories

Resources