I'm unable to join ManyToMany relationship records using a serializer to a unioned queryset from different DB modals.
I have two similar DB models, A and B both with a ManyToMany relationship with another model, Tag.
from django.db import models
class Tag(models.Model):
name = models.CharField(max_length=100)
class A(models.Model):
name = models.CharField(max_length=100)
tags = models.ManyToManyField(Tag)
class B(models.Model):
name = models.CharField(max_length=100)
tags = models.ManyToManyField(Tag)
I want to union A and B, and join the data from Tag via a Serializer. Here's my serializer:
from rest_framework import serializers
from union.models import A, B, Tag
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = '__all__'
class ABUnionSerializer(serializers.Serializer):
name = serializers.CharField()
tags = TagSerializer(many=True)
When I create a queryset that unions A and B, then pass that to ABUnionSerializer, the serializer does not join the Tag data from B correctly. Example:
from django.test import TestCase
from union.models import A, B, Tag
from union.serializers import ABUnionSerializer
class UnionTestCase(TestCase):
def test_union(self):
ta = Tag.objects.create(name='t-a')
tb = Tag.objects.create(name='t-b')
a = A.objects.create(name='aaa')
a.tags.add(ta)
b = B.objects.create(name='bbb')
b.tags.add(tb)
u = A.objects.all().union(B.objects.all())
s = ABUnionSerializer(u, many=True)
print(s.data)
The Serializer attempts to use relationship table from A instead of B. In this example, this causes the serialized record "bbb" to have tag "t-a" instead of "t-b".
This is likely due to this documented behavior of unioned querysets:
union(), intersection(), and difference() return model instances of the type of the first QuerySet even if the arguments are {{QuerySet}}s of other models.
What's the best way to join Tags to this unioned queryset?
Related
Is there a way of filling some particular fields in a model using a field value of another model object?
For example, I thought of the following scenario:
1 - there are some models
from django.db import models
class Supplier(models.Model):
supplier = models.Charfield(max_length=50, unique=True)
class Object(models.Model):
object = models.Charfield(max_length=50)
supplier = models.ForeignKey(Supplier, on_delete=models.CASCADE, to_field="supplier")
class Transaction(models.Model):
object_id = models.ForeignKey(Object, on_delete=models.CASCADE)
supplier = models.Charfield(max_length=50)
2 - Those models are serialized
from . import models
from rest_framework.serializers import ModelSerializer
class SupplierSerializer(ModelSerializer):
class Meta:
model = models.Supplier
fields = '__all__'
class ObjectSerializer(ModelSerializer):
class Meta:
model = models.Object
fields = '__all__'
class TransactionSerializer(ModelSerializer):
class Meta:
model = models.Transaction
exclude = ('supplier',)
3 - And there is a view
from . import models, serializers
from rest_framework.viewsets import ModelViewSet
class TransactionApiViewset(ModelViewSet):
queryset = models.Transaction.objects.all()
serializer_class = serializers.TransactionSerializer
When submiting a post with 'object_id' field to Transaction Api, I'd like that the 'supplier' field in Transaction Model autofills with the 'supplier' field value of Object object related to 'object_id'.
I'd appreciate any help.
I think you could use property(or cached_property) method for supplier instead of saving as a field:
class Transaction(models.Model):
object = models.ForeignKey(Object, on_delete=models.CASCADE)
#property
def supplier(self):
return self.object.supplier.supplier
If you need to keep supplier saved in Transaction table, then just keep the model definition as you posted, and add a validate method in serializer:
class TransactionSerializer(ModelSerializer):
def validate(self, data):
object = Object.objects.get(id=data["object_id"])
data["supplier"] = object.supplier.supplier
return data
That would automatically update Transaction with new object related supplier's supplier field.
I have the following model:
from django.db import model
class Fruit(models.Model):
fruit_name = models.CharField(max_length=100)
And A form like this:
from django import forms
from .models import SomeModel
class FruitForm(forms.ModelForm):
some_input = forms.CharField(max_length=100)
class Meta:
model = Fruit
fields = ['fruit_name']
This guide shows me how to create a dropdown like this:
fruit = [
('orange', 'Oranges'),
('cantaloupe', 'Cantaloupes'),
('mango', 'Mangoes'),
('honeydew', 'Honeydews'),
]
class TestForm(forms.Form):
some_input = forms.CharField(max_length=100)
favorite_fruit = forms.CharField(widget=forms.Select(choices=FRUIT_CHOICES))
What I want to happen is for the key/value tuple list to generate based on the fruit_id and fruit_name columns of the Fruit model, without the need to manually insert the data into the FruitForm.
What am I missing here?
Usually you do that with a ModelChoiceField [Django-doc]:
from django import forms
class TestForm(forms.Form):
some_input = forms.CharField(max_length=100)
favorite_fruit = forms.ModelChoiceField(queryset=Fruit.objects.all())
In your Fruit model, you then implement the __str__ method to decide how to render your fruit. Here that would be:
from django.db import model
class Fruit(models.Model):
fruit_name = models.CharField(max_length=100)
def __str__(self):
return self.fruit_name
You can alter the queryset= parameter, for example to filter the queryset in advance. For example if you only want to show Fruit that starts with an A, you can filter like:
from django import forms
# example: only show fruit that starts with an A
class TestForm(forms.Form):
some_input = forms.CharField(max_length=100)
favorite_fruit = forms.ModelChoiceField(
queryset=Fruit.objects.filter(fruit_name__startswith='A')
)
I have two models and I want to call the field values associated with the foreign key present in both the models.
For example:
Say we have two models:
from django.db import models
from django.contrib.auth.models import User
class Bike(models.Model):
bike_model = models.CharField(max_length=200)
owner = models.ForeignKey(User,on_delete=models.CASCADE)
class Car(models.Model):
car_model = models.CharField(max_length=200)
owner = models.ForeignKey(User,on_delete=models.CASCADE)
And the relating serializer class is:
from rest_framework import serializers
from .models import Bike,Car
class BikeSerializer(serializers.ModelSerializer):
class Meta:
model = Bike
fields = ('bike_model','owner')
class CarSerializer(serializers.ModelSerializer):
class Meta:
model = Car
fields = ('car_model','owner')
Now, I want to add a field in BikeSerializer to get all the cars associated with the given owner. That is I want to make the following change:
class BikeSerializer(serializers.ModelSerializer):
cars_owned = ???
class Meta:
model = Bike
fields = ('bike_model','owner','cars_owned')
I am unable to get how the cars owned by the owner can be returned. Thus, for the data from serializer, I want the bike model, the owner id and the list of all the car ids that the owner has.
You should look this SerializerMethodField.
Basically, you need to create a method inside BikeSerializer that return cars owned.
class BikeSerializer(serializers.ModelSerializer):
cars_owned = serializers.SerializerMethodField()
class Meta:
model = Bike
fields = ('bike_model','owner','cars_owned')
def get_cars_owned(self, object):
# This is your query to get all cars associated
return object.cars_owned.all()
You may add seializer as field:
class BikeSerializer(serializers.ModelSerializer):
cars_owned = CarSerializer(source='owner.car_set', many=True, read_only=True)
class Meta:
model = Bike
fields = ('bike_model','owner','cars_owned')
I have models without relations:
class A(models.Model):
pass
class B(models.Model):
a_id = models.IntegerField()
How to filter queryset of A objects if there is B object and A().id == B().a_id?
It's easy to do with ForeignKey (A.objects.filter(b__isnull=False)), but how to do it without relation?
Update: will be great to do it with single request.
ids = B.objects.values_list('a_id', flat=True)
A.objects.filter(id__in=ids)
In my Form I want to populate a ModelChoiceField with Data from two Models.
How can I change the queryset that it contains the Objects of two different models?
My approach was to pack the querysets in a list. But that don't seems to be the right container for the data.
I don't think you can use a ModelChoiceField with two different models, because you cannot have a queryset composed by two different models.
You'd better try to use ChoiceField, which accepts a choices parameter with a list of tuples.
Say you have two models in models.py like this:
from django.db import models
class Model1(models.Model):
name = models.CharField(max_length=20, primary_key=True)
description = models.CharField(max_length=200)
class Model2(models.Model):
name = models.CharField(max_length=20, primary_key=True)
description = models.CharField(max_length=200)
You can create a Form like this in forms.py:
from django import forms
from .models import Model1, Model2
class MultipleModelChoiceForm(forms.Form):
select = forms.ChoiceField(choices=[])
def __init__(self, *args, **kwargs):
super(MultipleModelChoiceForm, self).__init__(*args, **kwargs)
choices = []
for obj1 in Model1.objects.all():
choices.append((obj1.name, obj1.description))
for obj2 in Model2.objects.all():
choices.append((obj2.name, obj2.description))
self.fields['select'].choices = choices
Note that choices are defined in the __init__ of the form, to have the select field updated with all records in Model1 and Model2 when you create an instance of the form.