Django getting foreign-key object list - django

I have model like this:
class A:
....
class B:
....
a = model.ForeignKey(A, related_name='a')
....
Let's assume there is an B object.
I can get A object like this:
b = B()
a = b.a
Then what is the simplest way to get all B object related with A?
Additionally,
I can get a list of A.
list_a = A.objects.filter()
Then what is the simplest way of getting a list of B which relates with A object in the list_a?
One more reverse case: I have a list of B:
list_b = B.objects.filter()
Then what is the simplest and optimized way to get the list of A object related to the B object in the list_b?

B.objects.filter(a__in=a_list)
note that you can filter on related objects like this (instead if executing two queries do it in one)
for example if your a_list is a query like this:
a_list = A.objects.filter(field=2)
you can filter B like this:
B.objects.filter(a__field=2)
which is more readable and also django can optimize it too)
Update: you can query reversed relations the same way
A.objects.filter(b__in=b_list)
A.objects.filter(b__field=2)
note that it's better to change your code to
a = model.ForeignKey(A, related_name='b')
b is the name of the field in reveres relations so an_a_instance.b.all() returns all instances of b which are pointing at given a_instance

Related

Django add objects to Related Manager during creation

Consider a simple ForeignKey relationship:
class A(Model):
pass
class B(Model):
a = ForeignKey(A)
I have an API view that creates an A and a set of B's based on outside data (data NOT passed from the user), then serializes the created objects and returns the serialized data. My object creation code looks something like:
a = A()
a.b_set.bulk_create(B(a=a) for b in [...])
My issue is that this does not add the B objects to a's b_set, so that if I were to run
print(a.b_set.all())
afterwards, it would re-query the DB to get b_set. This is unnecessary though, because I already have a's entire b_set as I just created it. I'm doing this with a series of nested objects so it results in a LOT of unnecessary queries. My current workaround is to, after creation, run a query like
A.objects.prefetch_related('b_set').get(a=a.id)
then serializer that fetched object. This limits serializtion to just one unnecessary query, but I'd like to eliminate that one as well. It seems to me like there should be a way to cache the created B objects on a, and eliminate
any need to hit the DB again during serialization.
I believe you need to first execute a.save() before you can bulk_create. Here are my results using the two models you described:
a = A()
a.save()
a.b_set.bulk_create([B(a=a), B(a=a), B(a=a)])
a.b_set.count()
>>> 3
After some investigation into the QuerySet and Model source code, I decided my best/only option was to directly modify _prefetched_objects_cache on each object. Definitely not pretty but it works. Here's the gist of what I did:
a = A()
b_set = a.b_set.bulk_create(B(a=a) for b in [...])
a._prefetch_related_cache = {}
a._prefetch_related_cache['b_set'] = b_set
This ensures that all the created B's are cached on a. Note that if B has an auto-created primary key field, those fields won't be populated in the objects returned by bulk_create with most backends. Fortunately I'm using PostgreSQL which returns auto-PKs from bulk_create so that's not a problem for me.

Django - limiting foreignkey queryset in single query

Let me first present model it will be easier to explain my question
Class A:
points = Int
Class B:
fk = ForignKey(A)
Let us assume that we have many A and B objects
top_a = A.objects.all().order_by("points")[:3]
result = B.objects.filter(fk__in=list(top_a))
Is there possibility of getting same results as above one but doing it in 1 single query? Result will be all of instances of B which have fk to one of A from top 3
Yes you can do like:
result = B.objects.all().order_by('fk__points')[:3]

Django queryset exclude empty foreign key set

I have the following models where B has a many-to-one relationship with A:
class A(model.Model):
name = models.IntegerField()
class B(models.Model
a = models.ForeignKey(A, db_column='a_id')
When I use a queryset on A, is there a way to exclude the rows in A that have no rows in B?
Use isnull :
A.objects.filter(b__isnull=False).distinct()
Using distinct() prevents duplicate entries, otherwise each a appears once for every b which is linked to it.
no_rows_in_b = B.objects.all().select_related('a')
will get you all the B's with A's
Then you can cycle through them and output the A's
If you want non-repeats:
no_rows_in_b = B.objects.all().distinct('a').select_related('a')
Then:
for rec in no_rows_in_b:
print(rec.a)
Notice that if you want to be more explicit, you could do something like this:
A.objects.exclude(b__isnull=True).distinct()
using exclude instead of filter and using the True boolean arg.

How to use Q objects to check if any members of arbitrary-length list are in Many-To-Many Relationship

Suppose I have the following Django models:
class myObj1(models.Model):
myField1 = models.IntegerField()
class myObj2(models.Model):
myLocalObj1 = models.ManyToManyField(myObj1)
Furthermore, suppose I have a list of unique myObj1s:
a = myObj1(myField=1)
b = myObj1(myField=2)
c = myObj1(myField=3)
myTargetList = [a, b, c]
Now, I would like to write a Django query using Q objects such that it returns all the myObj2s that have any member of myTargetList as myLocalObj1. Furthermore, I don't know the exact size of myTargetList in advance.
How should I do it? This obviously won't work:
myObj2.objects.filter(Q(myLocalObj1__in=myTargetList))
EDIT: To make this a little more in line with what you are looking for (although I would not necessarily recommend doing things this way), you could:
vallist=[]
for b in myTargetList:
vallist.append(b.myField)
myObj2.objects.filter(myLocalObj1__in=myObj1.objects.filter(myField__in=vallist))
This is available in the Django docs here:
https://docs.djangoproject.com/en/dev/topics/db/queries/#lookups-that-span-relationships

django one-to-many

Long story short: I have two models, and a database imported from XML. The model layout is as follows:
class A:
ForgeinKey(B)
class B:
list = {A1, A2 ... An}
Is there a replacement for {A1, A2 ... An} that would make B.list return a list of A's.
Edit: The idea is to have a field in B that lists all the A's that are pointing to it. I can't seem to figure out how to call A.objects.* from inside B's definition. I don't even know if that's possible.
Edit2: Solved, thanks everyone for help :)
If I understood correctly, you want B().list to return the result of SELECT * FROM A WHERE B_id = <B.id>?
You have to use related_name, then:
class A(Model):
b = ForeignKey(B, related_name='list')
Or, you can use the default name, "B().A_set"