I have combined these two answers: one and two In the attempt to select only certain fields from nested objects without any success at all, the result is returning ALL fields from all tables.
serializers:
class NameTestTypeSerializer(serializers.ModelSerializer):
class Meta:
model = TestTypeModel
fields = 'name'
class ExecutedTestSerializer(serializers.ModelSerializer):
test_type = NameTestTypeSerializer
class Meta:
model = ExecutedTestModel
fields = ('id', 'result', 'test_type')
depth = 1
models:
class TestTypeModel(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(null=False, max_length=255, unique=True)
........
class Meta:
db_table = 'TestType'
class ExecutedTestModel(models.Model):
id = models.AutoField(primary_key=True)
test_type = models.ForeignKey(TestTypeModel, to_field='id')
result = models.IntegerField(null=False)
class Meta:
db_table = 'ExecutedTest'
viewset:
class ExecutedTestViewSet(viewsets.ModelViewSet):
permission_classes = (IsAuthenticatedOrReadOnly,)
serializer_class = ExecutedTestSerializer
def get_queryset(self):
queryset = ExecutedTestModel.objects.all().select_related('test_type').defer('test_type__executable' )
return queryset
How did you check that executable is fetched? In django you can access deferred fields, they are loaded from db on demand.
I believe the problem isn't in underscore notation, instead it is in the definition of the serializers.
Related
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.
How can I filter the reverse relation queryset generated by a ModelViewSet without causing an additional n queries?
MRE:
models.py:
class Product(models.Model):
name = models.CharField(max_length=45)
image_url = models.URLField()
class Item(models.Model):
product = models.ForeignKey(Product, related_name='items', on_delete=models.CASCADE)
price = models.DecimalField(max_digits=10, decimal_places=2)
quantity = models.IntegerField()
views.py:
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.prefetch_related('items').all()
serializer_class = ProductSerializer
filter_backends = [filters.DjangoFilterBackend]
filter_class = ProductFilter
serializers.py:
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = '__all__'
class ProductSerializer(serializers.ModelSerializer):
items = ItemSerializer(many=True, read_only=True)
class Meta:
model = Product
fields = '__all__'
filters.py:
import django_filters as filters
class ProductFilter(filters.FilterSet):
price = filters.RangeFilter(field_name='items__price')
quantity = filters.RangeFilter(field_name='items__quantity')
class Meta:
model = Product
fields = {
'name': ['icontains'],
}
Filtering on the Product model's fields works fine, but the items reverse relation ignores the filtering completely.
I found a "solution" from this answer; ie. do the filtering via a SerializerMethodField:
item_fieldset = {'price', 'quantity'}
class ProductSerializer(serializers.ModelSerializer):
items = serializers.SerializerMethodField('get_items')
class Meta:
model = Product
fields = '__all__'
def get_items(self, product):
params = self.context['request'].query_params
filter_fields = item_fieldset.intersection(set(params))
filters = {field: params.get(field) for field in filter_fields}
serializer = ItemSerializer(instance=product.items.filter(**filters), many=True)
return serializer.data
But this cripples the performance of my app.
Is there a better way?
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')
I'm building a read API for an existing (legacy) SQL database using Django Rest Framework. The problem is that way too many queries are made.
I want to select a Widget and its (about 40) related widgets, including the widget_technology for each of the related widgets. The technology of a widget depends on the presence or absence of a certain tag (I realize it's suboptimal database design but it's legacy...). The problem is that this results in 40+ queries (one per related widget).
Is there any way to fix it?
I've been looking at prefetch_related('widget_tags') in the DRF part of the code, but that doesn't seem to help.
Here is the main table:
class Widgets(models.Model):
widget_id = models.AutoField(primary_key=True)
widget_slug = models.CharField(unique=True, max_length=70)
widget_name = models.CharField(max_length=70)
widget_tags = models.ManyToManyField('Tags', through='IsTag')
related_widgets = models.ManyToManyField('Widgets', through='Similar', related_name='similar')
#property
def widget_technology(self):
if self.widget_tags.filter(tag_slug='tech_1').exists():
return 'TECH_1'
elif self.widget_tags.filter(tag_slug='tech_2').exists():
return 'TECH_2'
class Meta:
managed = False
db_table = 'widgets'
For completeness, here are the other tables:
class Similar(models.Model):
first_widget = models.ForeignKey(
Widgets, models.DO_NOTHING, primary_key=True, related_name='similar_first_widget'
)
second_widget = models.ForeignKey(
Widgets, models.DO_NOTHING, related_name='similar_second_widget'
)
score = models.IntegerField()
class Meta:
managed = False
db_table = 'similar'
unique_together = (('first_widget', 'second_widget'),)
class IsTag(models.Model):
is_tag_widget = models.ForeignKey(Widgets, models.DO_NOTHING, primary_key=True)
is_tag_tag = models.ForeignKey('Tags', models.DO_NOTHING)
class Meta:
managed = False
db_table = 'is_tag'
unique_together = (('is_tag_widget', 'is_tag_tag'),)
class Tags(models.Model):
tag_id = models.AutoField(primary_key=True)
tag_slug = models.CharField(max_length=100)
class Meta:
managed = False
db_table = 'tags'
EDIT: Adding the View
class WidgetDetail(APIView):
def get_object(self, slug):
try:
return Widgets.objects.get(widget_slug=slug)
# tried this but didn't solve the issue:
# return Widgets.objects.prefetch_related('related_widgets', 'related_widgets__widget_tags').get(widget_slug=slug)
except Widgets.DoesNotExist:
raise Http404
def get(self, request, slug, format=None):
widget = self.get_object(slug)
serializer = FullWidgetSerializer(widget)
return Response(serializer.data)
and the serializers:
class FullWidgetSerializer(serializers.ModelSerializer):
# special cases
widget_tags = SimpleTagSerializer(many=True, read_only=True)
related_widgets = SimpleWidgetSerializer(many=True, read_only=True)
class Meta:
model = Widgets
fields = (
'widget_slug', 'related_widgets', 'widget_tags', 'widget_technology')
class SimpleWidgetSerializer(serializers.ModelSerializer):
class Meta:
model = Widgets
fields = ('widget_slug', 'widget_technology')
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