Django wrong behaviour on query relations - django

lately I found a weird behavior in Django. Then, I start thinking that what's wrong is the way I'm doing the work.
Lets assume that we have 2 models
class A(models.Model):
attr1 = models.CharField()
...
class B(models.Model):
a = models.ForeignKey("A",
related_name = "bs"
blank = True,
null = True)
So, if I have some items of B, without associations to A, say b1, b2, b3, when I set a new instance of A, say a1, if I query
a.bs.all()
b1, b2 and b3 are returned.
What's wrong with it? When I create a new instance, it should not rise any relation. I know that b1,b2 and b3 have no relations, but they can't be associated by default to any new instance.
Anyone know how to proceed in a Django way?
I know I can do the trick
if a1.id:
return []
else:
a1.bs.all()
But I think that's not the right way to do this.
Can anyone help me?
Thank you in advance

This is a bug in Django and is currently marked as "design decision needed":
https://code.djangoproject.com/ticket/14615

Related

How do I determine what "key path" arguments to provide to `prefetch_related` in Django?

Note: below, I am going to refer to the arguments supplied to prefetch_related as "key paths". I don't know if that's the best/correct term - so let me know if there's a better term to use and I will update the question.
I created an advanced search page in django that searches any of a number of fields from 6 different tables (not all of which are single a direct foreign key path) and displays selected fields from all those tables in a results table. The "key paths" included are:
msrun__sample
msrun__sample__tissue
msrun__sample__animal
msrun__sample__animal__tracer_compound
msrun__sample__animal__studies
(Note: no msrun fields are included in the search or display. That specific model class in this particular view only serves as a connection between the model classes involved in the view.)
It makes a huge difference in the run time when I include a prefetch like: .prefetch_related("msrun__sample__animal__studies"), but I see no discernible difference when I include any additional prefetch "key paths".
My question is: How do I determine which "key path" or "key paths" to include in the arguments to prefetch_related? I don't seem to understand the criteria that would go into that decision. I.e. Why would I or would I not, say, include all the related "key paths" among the prefetch_related arguments?
Tried this out with below models:
class A(models.Model):
name = models.CharField(max_length=100)
my_b_set = models.ManyToManyField('B', related_name='my_a')
class B(models.Model):
name = models.CharField(max_length=100)
my_c_set = models.ManyToManyField('C', related_name='my_b')
my_d_set = models.ManyToManyField('D', related_name='my_c')
class C(models.Model):
name = models.CharField(max_length=100)
class D(models.Model):
name = models.CharField(max_length=100)
And filled the relationships like so:
a = A.objects.create(name='a1')
b1 = B.objects.create(name='b1')
b2 = B.objects.create(name='b2')
c1 = C.objects.create(name='c1')
c2 = C.objects.create(name='c2')
d1 = D.objects.create(name='d1')
d2 = D.objects.create(name='d2')
a.my_b_set.add(b1)
a.my_b_set.add(b2)
b1.my_c_set.add(c1, c2)
b1.my_d_set.add(d1, d2)
b2.my_c_set.add(c1, c2)
b2.my_d_set.add(d1, d2)
Then run this query:
A.objects.prefetch_related('my_b_set', 'my_b_set__my_c_set', 'my_b_set__my_d_set')
As expected, it made 4 queries:
-- Get all A's
SELECT "changelog_a"."id", "changelog_a"."name" FROM "changelog_a"
-- Get all the related B's of A mapped with A's id
SELECT ("changelog_a_my_b_set"."a_id") AS "_prefetch_related_val_a_id", "changelog_b"."id", "changelog_b"."name"
FROM "changelog_b" INNER JOIN "changelog_a_my_b_set" ON ("changelog_b"."id" = "changelog_a_my_b_set"."b_id")
WHERE "changelog_a_my_b_set"."a_id" IN (2)
-- Get all the related C's of B mapped with B's id
SELECT ("changelog_b_my_c_set"."b_id") AS "_prefetch_related_val_b_id", "changelog_c"."id", "changelog_c"."name"
FROM "changelog_c" INNER JOIN "changelog_b_my_c_set" ON ("changelog_c"."id" = "changelog_b_my_c_set"."c_id")
WHERE "changelog_b_my_c_set"."b_id" IN (3, 4)
-- Get all the related D's of B mapped with B's id
SELECT ("changelog_b_my_d_set"."b_id") AS "_prefetch_related_val_b_id", "changelog_d"."id", "changelog_d"."name"
FROM "changelog_d" INNER JOIN "changelog_b_my_d_set" ON ("changelog_d"."id" = "changelog_b_my_d_set"."d_id")
WHERE "changelog_b_my_d_set"."b_id" IN (3, 4)
So in this example, overlapping key paths with new relations would not repeat the queries for B, and will only create new queries for those new relations.

Django queryset - Group on basis of foreign key and get count

I have 3 tables as follows:
class Bike:
name = CharField(...)
cc_range = IntField(...)
class Item:
bike_number = CharField(...)
bike = ForeignKey(Bike)
class Booking:
start_time = DateTimeField(...)
end_time = DateTimeField(...)
item = ForeignKey(Item, related_name='bookings')
I want to get a list of all the bikes which are not booked during a period of time (say, ["2016-01-09", "2016-01-11"]) with an item count with them.
For example, say there are two bikes b1, b2 with items i11, i12 and i21, i22. If i21 is involved in a booking (say ["2016-01-10", "2016-01-12"]) then I want something like
{"b1": 2, "b2": 1}
I have got the relevant items by
Item.objects
.exclude(bookings__booking_time__range=booking_period)
.exclude(bookings__completion_time__range=booking_period)
but am not able to group them.
I also tried:
Bike.objects
.exclude(item__bookings__booking_time__range=booking_period)
.exclude(item__bookings__completion_time__range=booking_period)
.annotate(items_count=Count('item')
But it removes the whole bike if any of it's item is booked.
I seem to be totally stuck. I would prefer doing this without using a for loop. The django documentation also don't seem to help me out (which is something rare). Is there a problem with my model architecture for the type of problem I want to solve. Or am I missing something out. Any help would be appreciated.
Thanks in advance !!
from django.db.models import Q, Count, Case, When, Value, BooleanField
bikes = models.Bike.objects.annotate(
booked=Case(
When(Q(item__bookings__start_time__lte=booking_period[1]) & Q(item__bookings__end_time__gte=booking_period[0]),
then=Value(True)),
default=Value(False),
output_field=BooleanField(),
)).filter(booked=False).annotate(item_count=Count('item'))
Please read the documentation about conditional expressions.

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"