I want to prefetch_related to two level of M2M values,
Here is my models.py
class A(models.Model):
name = models.CharField(max_length=40)
b = models.ManyToManyField('B')
class B(models.Model):
name = models.CharField(max_length=40)
c = models.ManyToManyField('C')
class C(models.Model):
name = models.CharField(max_length=40)
d = models.ManyToManyField('D')
And my ORM is
a_obj = A.objects.all().prefetch_related('a__b__c')
And I am trying to access the values like below,
Method A:
for each_obj in a_obj:
print(each_obj.a__b__c)
Method B:
for each_obj in a_obj:
print(each_obj.a.all())
Method A throws an error saying No such value a__b__b for A found
Method B doesn't throw any error, but the number of queries increases to the length of a_obj.
Is there a way to access a__b__c in a single query?
You load both the related B and C models with .prefetch_related(…) [Django-doc]:
a_objs = A.objects.prefetch_related('b__c')
But here .prefetch_related(…) does not change how the items look, it simply loads items. You thus can access these with:
for a in a_objs:
for b in a.b.all():
for c in b.c.all():
print(f'{a} {b} {c}')
You this still access the items in the same way, but here Django will already load the objects in advance to prevent extra queries.
Related
I have the same schema in my django application:
class SomeModel(models.Model):
value = models.CharField(max_length=30)
class AbstractModel(models.Model):
someModel = models.ForeignKey(SomeModel)
class Meta:
abstract = True
class A(AbstractModel):
anotherValue = models.CharField(max_length=5)
class B(AbstractModel):
anotherValue = models.CharField(max_length=5)
class C(AbstractModel):
anotherValue = models.CharField(max_length=5)
class D(AbstractModel):
anotherValue = models.CharField(max_length=5)
class E(AbstractModel):
anotherValue = models.CharField(max_length=5)
With this layout, I need the most efficient way to query all objects from models A, B, C, D and E with a given id of SomeModel. I know that I cannot execute a query in an abstract model, so right now, what I do is query each model separately like this:
A.objects.filter(someModel__id=id)
B.objects.filter(someModel__id=id)
C.objects.filter(someModel__id=id)
D.objects.filter(someModel__id=id)
E.objects.filter(someModel__id=id)
Obviously this approach is quite slow, because I need to make 5 different queries each time I want to know all those objects. So my question is, is there a way to optimize this kind of query?
UPDATE:
I have tried the union method like this:
qs1 = A.objects.filter(**filters) # hits DB
qs2 = B.objects.filter(**filters) # hits DB
qs3 = C.objects.filter(**filters) # hits DB
qs4 = D.objects.filter(**filters) # hits DB
qs5 = E.objects.filter(**filters) # hits DB
qs1.union(qs2, qs3, qs4, qs5) # hits DB
That's actually 6 hits to the database!! I woulk like only one!
I have checked this printing the number of queries made:
from django.conf import settings
settings.DEBUG = True
from django.db import connection
print(len(connection.queries))
You may use union method, but what you want to do? If you want to call five objects by one pk and you want to be sure that they have strict relation between each other you may use OneToOne relationship.
So in the first case you just need to make a query, in the second case you must make new migration and maybe you will need to rebuild your tables.
I'm new to Python/Django, so any help is much appreciated!
I am trying to find out a winner based on how many times score_1 > score_2 in all the child objects.
I have these two Models:
class Parent(models.Model):
winner = models.IntegerField(default=0)
def foo(self):
childs_of_me = self.child_set.all()
number_childs = childs_of_me.count()
one_better = childs_of_me.filter(score_1__gt=score_2)
one_wins_count = one_better.count()
if one_wins_count > number_childs/2:
self.winner = 1
class Child(models.Model):
parent = models.ForeignKey(Parent, on_delete=models.CASCADE)
score_1 = models.IntegerField(default=0)
score_2 = models.IntegerField(default=0)
I've followed the answer to this Question(Select Children of an Object With ForeignKey in Django?) to get child objects of a Parent object. However, I can't seem to figure out how to filter the returned set based on attributes in the Child Model.
one_better = childs_of_me.filter(score_1__gt=score_2)
returns an error: global variable score_2 unknown
How would I go about doing this?
What you here want to do is reference another field as value in the queryset. You can do this by constructing an F-expression:
one_better = childs_of_me.filter(score_1__gt=F('score_2'))
Without doing this, Python interprets this as an identifier. Here we use a string, and wrap it around an F-object, to indicate that we want to refer to a field.
I have two Django models and connected via Foreignkey element and in the second model I need to use the firs model's attribute - example (pseudocode):
Class Category(models.Model):
c_attribute = "Blue"
Class Object(models.Model):
o_category = models.ForeignKey(Category)
o_color = o_category.c_attribute
The key here is the last line - I got error saying that ForeignKey object has no attribute c_attribute.
Thanks
Because o_category is a Key to a Category, not a Category itself!
you can check by type(o_category) to check is not Category!
so you have access to related Cateogry of a Object in other parts of application when connected to database.for example in shell you can write:
c = Category()
c.save()
o = Object(o_category = c, ...) #create Object with desired params
... #some changes to o
o.save()
o.o_category.c_attribute #this will work! :)
You can use to_field='', but that might give you an error as well.
Class Object(models.Model):
o_category = models.ForeignKey(Category)
o_color = models.ForeignKey(Category, to_field="c_attribute")
The best thing is do create a function in your Object model, that would get you the categories c_attribute like so:
def get_c_attribute(self):
return self.o_category.c_attribute
class Toy(models.Model):
name = models.CharField(max_length=20)
desc = models.TextField()
class Box(models.Model):
name = models.CharField(max_length=20)
proprietor = models.ForeignKey(User, related_name='User_Box')
toys = models.ManyToManyField(Toy, blank=True)
How to create a view that add Toy to Box?
def add_this_toy_to_box(request, toy_id):
You can use Django's RelatedManager:
A “related manager” is a manager used in a one-to-many or many-to-many related context. This happens in two cases:
The “other side” of a ForeignKey relation. That is:
class Reporter(models.Model):
...
class Article(models.Model):
reporter = models.ForeignKey(Reporter)
In the above example, the methods below will be available on the manager reporter.article_set.
Both sides of a ManyToManyField relation:
class Topping(models.Model):
...
class Pizza(models.Model):
toppings = models.ManyToManyField(Topping)
In this example, the methods below will be available both on topping.pizza_set and on pizza.toppings.
These related managers have some extra methods:
To create a new object, saves it and puts it in the related object set. Returns the newly created object:
create(**kwargs)
>>> b = Toy.objects.get(id=1)
>>> e = b.box_set.create(
... name='Hi',
... )
# No need to call e.save() at this point -- it's already been saved.
# OR:
>>> b = Toy.objects.get(id=1)
>>> e = Box(
... toy=b,
... name='Hi',
... )
>>> e.save(force_insert=True)
To add model objects to the related object set:
add(obj1[, obj2, ...])
Example:
>>> t = Toy.objects.get(id=1)
>>> b = Box.objects.get(id=234)
>>> t.box_set.add(b) # Associates Box b with Toy t.
To removes the specified model objects from the related object set:
remove(obj1[, obj2, ...])
>>> b = Toy.objects.get(id=1)
>>> e = Box.objects.get(id=234)
>>> b.box_set.remove(e) # Disassociates Entry e from Blog b.
In order to prevent database inconsistency, this method only exists on ForeignKey objects where null=True. If the related field can't be set to None (NULL), then an object can't be removed from a relation without being added to another. In the above example, removing e from b.entry_set() is equivalent to doing e.blog = None, and because the blog ForeignKey doesn't have null=True, this is invalid.
Removes all objects from the related object set:
clear()
>>> b = Toy.objects.get(id=1)
>>> b.box_set.clear()
Note this doesn't delete the related objects -- it just disassociates them.
Just like remove(), clear() is only available on ForeignKeys where null=True.
Reference: Relevant Django doc on handling related objects
Django automatically creates reverse relations on ManyToManyFields, so you can do:
toy = Toy.objects.get(id=toy_id)
toy.box_set.add(box)
this is the representation of my models:
class B(models.Model):
"""I'm a dummy model, so doesn't pay atention of what I do"""
name = models.CharField(max_length=250)
class A(models.Model):
name = models.CharField(max_length=250)
many_b = models.ManyToManyField(B)
Now, suppose I have a list of B objects. And a single A object that will be related to that Bs. Something like this:
a = A.objects.get(id=1)
list_of_b = [B<name='B1'>,B<name='B2'>,B<name='B3'>,]
The way I relate them now is this:
for b_object in list_of_b:
a.many_b.add(b_object)
Is there any way to add all the B objects in a single transaction? Maybe in a single method, like:
a.many_b.addList(b) #This doesn't exist
From the docs:
>>> john = Author.objects.create(name="John")
>>> paul = Author.objects.create(name="Paul")
>>> george = Author.objects.create(name="George")
>>> ringo = Author.objects.create(name="Ringo")
>>> entry.authors.add(john, paul, george, ringo)
So if you have a list, use argument expansion:
a.many_b.add(*list_of_b)
I guess what you want is a kind of bulk insert right?
As far as I know this is just available in the Django TRUNK not in 1.3!
check it out some tutorial:
http://www.caktusgroup.com/blog/2011/09/20/bulk-inserts-django/