Suppose ModelA and (ModelB1, ModelB2 but they don't have common ancestor) has manytomany relationships.
Now I have a through model ModelAtoB.
class ModelAToB(Model):
model_a = models.ForeignKey(ModelA)
content_type=models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
model_b = generic.GenericForeignKey('content_type', 'object_id')
I see ManyToManyField being used in the django doc, how can I use it in my case as well?
class ModelA(Model):
pass
class ModelB(Model): # hum.. there's no ModelB which is a common ancestor of ModelB1, ModelB2
model_a_list = models.ManyToManyField(ModelA, through='ModelAtoB')
You cannot use the ManyToManyField in your case however your through model (ModelAToB) is enough to store the relation.
If you want to query all related ModelBs models to a given ModelAs you would do it somehow like this:
related_b = ModelAToB.objects.filter(model_a=a).prefetch_related('model_b')
b_instances = [rel.model_b for rel in related_b]
If you want all related ModelAs to a given ModelB this would be done this way:
from django.contrib.contenttypes.models import ContentType
model_b_ct = ContentType.objects.get_for_model(b)
related_a = ModelAToB.objects.filter(object_id=b.id, content_type=model_b_ct)
a_instance = [rel.model_a for rel in related_a]
Making a model method might be a handy tweak.
class ModelB(Model)::
def model_a_list(self):
return ModelA.objects.filter(
modelatob__content_type__app_label=self._meta.app_label,
modelatob__content_type__model=self._meta.module_name,
modelatob__object_id=self.id
)
This returns a queryset so you still can do things like .count(), .filter() on it.
Related
I have three models in a django DRF project:
class ModelA(models.Model):
name = ....
other fields...
class ModelB(models.Model):
name = ....
other fields...
class ModelC(models.Model):
name = ....
model_a = FKField(ModelA)
model_b = FKField(ModelB)
I was using the default ModelViewSet serializers for each model.
On my react frontend, I'm displaying a table containing 100 objects of ModelC. The request took 300ms. The problem is that instead of displaying just the pk id of modelA and ModelB in my table, I want to display their names. I've tried the following ways to get that data when I use the list() method of the viewset (retreive all modelc objects), but it significantly increases call times:
Serializing the fields in ModelCSerializer
class ModelCSerializer(serializers.ModelSerializer):
model_a = ModelASerializer(read_only=True)
model_b = ModelBSerializer(read_only=True)
class Meta:
model = ModelC
fields = '__all__'
Creating a new serializer to only return the name of the FK object
class ModelCSerializer(serializers.ModelSerializer):
model_a = ModelANameSerializer(read_only=True) (serializer only returns id and name)
model_b = ModelBNameSerializer(read_only=True) (serializer only returns id and name)
class Meta:
model = ModelC
fields = '__all__'
StringRelatedField
class ModelCSerializer(serializers.ModelSerializer):
model_a = serializer.StringRelatedField()
model_b = serializer.StringRelatedField()
class Meta:
model = ModelC
fields = '__all__'
Every way returns the data I need (except number 3 takes more work to get the FKobject's id) but now my table request takes 5.5 seconds. Is there a way to do this without significantly increasing call times? I guess this is due to the DB looking up 3 objects for every object I retrieve.
Also I wouldn't be able to make the primary_key of ModelA & ModelB the name field because they aren't unique.
Thanks
EDIT Answer for my example thanks to bdbd below:
class ModelCViewSet(viewsets.ModelViewSet):
queryset = ModelC.objects.select_related('model_a', 'model_b').all()
# ...
You can use select_related for this to optimise your queries and make sure that every object in your ModelC does not do extra DB hits
I have some models that look like the following, where I would like to serialize Parent with a boolean field representing whether an associated Child reverse lookup exists.
class Parent(models.Model):
... some fields
class Child(models.Model):
parent_fk = models.OneToOneField(
"Parent",
on_delete=models.CASCADE,
related_name="child",
)
... some other fields
class ParentSerializer(serializers.ModelSerializer):
has_child = serializers.BooleanField()
class Meta:
model = Parent
fields = ["has_child", ... some fields]
These are the solutions I've tried and what I understand to be the problems with them
just add "child" to fields - this inserts the value of the foreign key?
use SerializerMethodField - triggers a DB lookup for each row?
Is there a clean way to implement this feature?
You can annotate the Parent with an extra attribute:
from django.db.models import Exists, OuterRef
Parent.objects.annotate(
has_child=Exists(Child.objects.filter(parent_fk_id=OuterRef('pk')))
)
You can add this as queryset to the ViewSet, ListAPIView, etc.
The Parent objects that arise from this queryset will have an extra attribute .has_child.
I'm using Django 2.x
I have two models
class DynamicUrlObject(models.Model):
content_type = models.ForeignKey(ContentType, null=True, on_delete=models.SET_NULL)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
domain = models.CharField(max_length=255)
and
class MyModel(models.Model):
name = models.Char(max_length=50)
my_obj = fields.GenericRelation(DynamicUrlObject, related_query_name='my_obj')
I have an object of MyModel and want to create a record for DynamicUrlObject and link same to the MyModel model.
I'm doing it something like
dy_obj = DynamicUrlObject.objects.get_or_create(
content_object=my_existing_obj,
domain='http://example.com'
)
my_existing_obj.my_obj = dy_obj
my_existing_obj.save()
But this is not creating a record for DynamicUrlObject and gives an error as
django.core.exceptions.FieldError: Field 'content_object' does not generate an
automatic reverse relation and therefore cannot be used for reverse querying.
If it is a GenericForeignKey, consider adding a GenericRelation.
You cannot filter or get directly on a generic foreign key [1], so get_or_create() won't work. If you know the type of my_existing_obj is MyModel, you can use the GenericRelation you set on MyModel:
try:
dy_obj = DynamicUrlObject.objects.get(my_obj=my_existing_object, domain=...) # use the `related_query_name` here
except DynamicUrlObject.DoesNotExist:
dy_obj = DynamicUrlObject(content_object=my_existing_object, domain=...)
dy_obj.save()
Also once you've created dy_obj, you don't need to assign the reverse relationship to my_existing_object. The GenericRelation isn't a concrete field in the db, it's just a way for django ORM to know how to name the relationships.
[1] https://docs.djangoproject.com/en/2.2/ref/contrib/contenttypes/#django.contrib.contenttypes.fields.GenericForeignKey
Say I have a serializer A
class SerializerA(ModelSerializer):
some_field = CharField()
some_other_field = CharField()
field_require_other_model = SerializerMethodField()
class Meta:
model = ModelA
fields = ('some_field', 'some_other_field', 'field_require_other_model')
def get_field_require_other_model(self, instance):
other_model_qs = ModelB.objects.filter(email=instance.email)
# say I want to get whatever that comes first
return other_model_qs.first().useful_info
As seen above, SerializerA uses ModelA for getting all the fields except that one in ModelB. I can get the info from ModelB doing what I did, but I don't know if this is the best way getting the data. I'm not sure if I need to hit database so many times or if there's a way to lazily evaluate it.
Also, what if I have another SerializerMethodField() that utilizes ModelB but for different info. Is this way still the best way to get the data?
How about using .annotate, annotating the other field onto modelA from modelB and then defining it as a charfield(or whatever the type is) on the serializer?
Something like
queryset = ModelA.objects.all().annotate(other_field_on_model_b=F('ModelB__other_field_on_model_b'))
then in the seralizer
class SerializerA(ModelSerializer):
some_field = CharField()
some_other_field = CharField()
other_field_on_model_b = CharField(required=False) #or whatever the field type is.
class Meta:
model = ModelA
fields = ('some_field', 'some_other_field', 'other_field_on_model_b')
Could do the annotation in get_queryset() or in the end point itself.
I have a ManyToMany relationship setup with intermediary objects in Django. Any ideas how I can order the < select >s in the Inlines that show up for the intermediary objects?
You can use fields inside an InlineModelAdmin:
class FooInline(admin.StackedInline):
model = Foo
fields = ('field1', 'field2', 'field3')
I think this could be what you're looking for:
Orderable inlines using drag and drop with jQuery UI
http://djangosnippets.org/snippets/1053/
Have you tried specifying a model for the many-to-many relationship using the through argument? You should be able to customize the admin with a ModelAdmin class then.
class A(models.Model):
pass
class B(models.Model):
m2m = models.ManyToManyField(A, through='C')
class C(models.Model):
a = models.ForeignKey(A)
b = models.ForeignKey(B)