If you have some models:
class Teacher(models.Model):
name = models.CharField(max_length=50)
class Student(models.Model):
age = models.PositiveIntegerField()
teacher = models.ForeignKey(Teacher, related_name='students')
and you use it like this:
>>> student = Student.objects.get(pk=1)
>>> student.teacher.name # This hits the database
'Some Teacher'
>>> student.teacher.name # This doesn't (``teacher`` is cached on the object)
'Some Teacher'
That's awesome. Django caches the related object so that you can use it again without having to abuse your database.
But, if you use it like this:
>>> teacher = Teacher.objects.get(pk=1)
>>> for student in teacher.students.all(): # This hits the database
... print(student.age)
...
8
6
>>> for student in teacher.students.all(): # This does too (obviously)
... print(student.age)
...
8
6
There's no caching or efficient access to related objects this direction.
My question is thus: Is there a built-in (or non-problematic way) to backward access related objects in an efficient way (a cached way), like you can in the student.teacher example above?
The reason I want this is because I have a model with multiple methods that need access to the same related objects over and over, so a page that should have 12 queries ends up with about 30.
There isn't any built-in way. I've written about this issue on my blog, with a technique to optimise accessing reverse relationships.
orokusaki,
You just need to cache the queryset as a python variable
students = teacher.students.all()
And then just use students in your for loops.
Below is a link to Django's own documentation about this specific issue :-)
http://docs.djangoproject.com/en/1.1/topics/db/optimization/#understand-cached-attributes
Try this perhaps?
teacher = Teacher.objects.select_related().get(pk=1)
http://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models.QuerySet.select_related
I have never used select_related in concert with a .all() on its result, so I'm not sure if it will yield DB savings or not.
Related
I'm new to Django and I'm facing a question to which I didn't an answer to on Stackoverflow.
Basically, I have 2 models, Client and Order defined as below:
class Client(models.Model):
name = models.CharField(max_length=200)
registration_date = models.DateTimeField(default=timezone.now)
# ..
class Order(models.Model):
Client = models.ForeignKey(ModelA, on_delete=models.CASCADE, related_name='orders')
is_delivered = models.BooleanField(default=False)
order_date = models.DateTimeField(default=timezone.now)
# ..
I would like my QuerySet clients_results to fulfill the 2 following conditions:
Client objects fill some conditions (for example, their name start with "d" and they registered in 2019, but it could be more complex)
Order objects I can access by using the orders relationship defined in 'related_name' are only the ones that fills other conditions; for example, order is not delivered and was done in the last 6 weeks.
I could do this directly in the template but I feel this is not the correct way to do it.
Additionally, I read in the doc that Base Manager from Order shouldn't be used for this purpose.
Finally, I found a question relatively close to mine using Q and F, but in the end, I would get the order_id while, ideally, I would like to have the whole object.
Could you please advise me on the best way to address this need?
Thanks a lot for your help!
You probably should use a Prefetch(..) object [Django-doc] here to fetch the related non-delivered Orders for each Client, and stores these in the Clients, but then in a different attribute, since otherwise this can generate confusion.
You thus can create a queryset like:
from django.db.models import Prefetch
from django.utils.timezone import now
from datetime import timedelta
last_six_weeks = now() - timedelta(days=42)
clients_results = Client.objects.filter(
name__startswith='d'
).prefetch_related(
Prefetch(
'orders',
Order.objects.filter(is_delivered=False, order_date__gte=last_six_weeks),
to_attr='nondelivered_orders'
)
)
This will contain all Clients where the name starts with 'd', and each Client object that arises from this queryset will have an attribute nondelivered_orders that contains a list of Orders that are not delivered, and ordered in the last 42 days.
I'm getting rather tired of paging through lots of irrelevant little fiddly properties while looking for the actual database structure of my models. Would it be a bad thing to use proxy models universally just to keep my code better organized / more readable? I.e.
class Foo_Base( models.Model):
title = models.CharField( ...)
# other DB fields. As little as possible anything else.
class Bar_Base( models.Model):
foo = models.ForeignKey( Foo_Base, ... )
etc. not many more lines than there are columns in the DB tables. Then at the bottom or elsewhere,
class Foo( Foo_Base):
class Meta:
proxy=True
#property
def some_pseudo_field(self):
# compute something based on the DB fields in Foo_Base
return result
#property
# etc. pages of etc.
The fact that makemigrations and migrate tracks proxy models makes me slightly concerned, although this usage seems to be exactly what the Django documentation says they are for (wrapping extra functionality around the same database table).
Or is there another way to organize my code that accomplishes the same (keeping fundamental stuff and fiddly little support bits apart).
[Edit] am offering up something that seems to work as a self-answer below. I'd still very much like to hear from anybody who knows for a fact that this is OK, given the deep Django magic on its declarative field declarations.
(About the only thing I dislike about Python, is that it does not have include functionality for reading in a heap of code from another file! )
I think I may have found an answer: use a plugin class inheriting from object,
as is commonplace for class-based Views.
I'd still very much like to hear from anybody who knows for a fact that this is OK, given the deep Django magic on its declarative field declarations.
Minimal proof of concept:
class PenMethods1( object):
#property
def namey(self):
return format_html('<div class="namey">{name}</div>', name=self.name )
class PenTest1(PenMethods1, models.Model):
name = models.CharField( max_length=16, blank=True )
def __repr__(self):
return f'<Pentest1 object id={self.id} name={self.name}>'
Initial migration was OK. Then I added
pnum = models.ForeignKey( 'Pennum', models.SET_NULL, null=True)
(Pennum was something already lying around in my playpen) and ran makemigrations and migrate. Again OK and basic functioning checks out...
>>> from playpen.models import PenTest1, Pennum
>>> n = Pennum.objects.last()
>>> n
<Pennum object id=3 name="t3" num=14 >
>>> p = PenTest1.objects.get(name='bar')
>>> p
<Pentest1 object id=2 name=bar>
>>> p.namey
'<div class="namey">bar</div>'
>>> p.pnum=n
>>> p.save()
>>> n=Pennum.objects.last()
>>> list(n.pentest1_set.all())
[<Pentest1 object id=2 name=bar>]
>>>
I'm new to django and I think this is a simple question -
I have an intermediate class which is coded as follows -
class Link_Book_Course(models.Model):
book = models.ForeignKey(Book)
course = models.ForeignKey(Course)
image = models.CharField(max_length = 200, null=True)
rating = models.CharField(max_length = 200,null=True)
def __unicode__(self):
return self.title
def save(self):
self.date_created = datetime.now()
super(Link_Book_Course,self).save()
I'm making this call as I'd like to have to have all of the authors of the books (Book is another model with author as a CharField)
storeOfAuthorNames = Link_Book_Course.objects.filter(book__author)
However, it doesn't return a querySet of all of the authors, in fact, it throws an error.
I think it's because book__author has multiple values- how can I get all of them?
Thanks!
I don't think you're using the right queryset method. filter() filters by its arguments - so the expected usage is:
poe = Author.objects.get(name='Edgar Allen Poe')
course_books_by_poe = Link_Book_Course.objects.filter(book__author=poe)
It looks like you're trying to pull a list of the names all the authors of books used in a particular course (or maybe all courses?). Maybe you're looking for .values() or values_list()?
all_authors_in_courses = Link_Book_Course.objects.values_list(
'book__author', flat=True
).distinct()
(Edit: Updated per #ftartaggia's suggestion)
As others already explained, the use of filter method is to get a subset of the whole set of objects and does not return instances of other models (no matter if related objects or so)
If you want to have Author models instances back from django ORM and you can use aggregation APIs then you might want to do something like this:
from django.db.models import Count
Author.objects.annotate(num_books=Count('book')).filter(num_books__gt=1)
the filter method you are trying to use translates more or less into SQL like this:
SELECT * FROM Link_Book_Course INNER JOIN Book ON (...) WHERE Book.author = ;
So as you see your query has an incomplete where clause.
Anyway, it's not the query you are looking for.
What about something like (assuming author is a simple text field of Book and you want only authors of books referred from Link_Book_Course instances):
Book.objects.filter(pk__in=Link_Book_Course.objects.all().values_list("book", flat=True)).values_list("author", flat=True)
To start with, a filter statement filters on a field matching some pattern. So if Book has a simple ForeignKey to Author, you could have
storeOfAuthorNames = Link_Book_Course.objects.filter(book__author="Stephen King"), but not just
storeOfAuthorNames = Link_Book_Course.objects.filter(book__author).
Once you get past that, I am guessing Book has Author as a ManyToManyField, not a ForeignKey (because a book can have multiple authors, and an author can publish multiple books?) In that case, just filter(book__author="Stephen King") will still not be enough. Try Link_Book_Course.objects.filter(book_author__in=myBookObject.author.all())
I have a couple of related models, that look a bit like this:
class Book(models.Model):
title = models.TextField()
class Author(models.Model):
"""
>>> b = Book(title='some title')
>>> b.save()
>>> a = Author(name='some name')
>>> a.save()
>>> a.books.add(b)
>>> b in a.books.all()
True
"""
name = models.TextField()
books = models.ManyToManyField(Book)
This version is a simplification of my production app, but the same test in production fails - a.books.all() returns the empty list, even after I do an a.books.add(b).
I've looked in the database (sqlite), and a new entry has definitely been created in the joining table, book_author. I've also tried calling a transaction.commit(), and a connection.close() to try and refresh the view of the DB. No joy. Any pointers towards what sorts of things might be causing the strange behaviour in production would be gratefully received.
One thought I had was that it had something to do with a third related model, for which I've manually specified a through table, something like this:
class Genre(models.Model):
desc = models.TextField()
books = models.ManyToManyField(Book, through='BookGenres')
class BookGenres(models.Model):
book = models.ForeignKey(Book)
genre = models.ForeignKey(Genre)
However, adding this to the test app doesn't break things... What else should I look for?
[edit 11/5] more weird behaviour, following on from advice from Daniel the comments (thanks for trying! :-)
More strange behaviour:
>>>a.books.all()
[]
>>>a.books.filter(pk=b.id)
[]
>>>a.books.filter(pk=b.id).count()
1
>>>len(a.books.filter(pk=b.id))
0
As I say, my "real" models are more complex, and I haven't been able to replicate this behaviour in simplified tests, but any ideas for what to look at would be gratefully appreciated.
I'm not sure that the in operator necessarily works in querysets. Don't forget that Django model instances don't have identity, so two objects loaded from the db in two separate operations can have different internal IDs even if they have the same pk.
I would explicitly query the item you're expecting :
>>> a.books.add(b)
>>> a.books.filter(pk=b.pk).count()
1
I'd also add that I don't see the point of this test. Django's model operations are very well covered by its own test suite - you should reserve your unit/doctests for your own business logic.
I'm implementing a small e-shop application in django.
My question concerns modelling an Order with many OrderLines:
How to model the Order to OrderLines relationship with the OrderLines accessible directly from the Order, i.e.
Order
def addOrderLine
def allOrderLines
I want to access the OrderLines from the Order and not have to get them from the db directly. Django offers the possibility to define ForeignKeys, but this doesn't solve my problem, because I'd have to define the following:
class OrderLine(models.Model):
order = models.ForeignKey(Order)
With this definition I'd have to fetch the OrderLines directly from the db and not through the Order.
I'm might use this definition and provide methods on the Order level. This, however, doesn't work because if I define the Order above the OrderLine in the models.py file, the Order doesn't see the OrderLines
You want a ForeignKey to Order from OrderLine. Something like this:
from django.db import models
class Order(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
class OrderLine(models.Model):
order = models.ForeignKey(Order, related_name='orderlines')
name = models.CharField(max_length=30)
def __unicode__(self):
return self.name
# Create a new order in the DB:
>>> order = Order.objects.create()
# Add a few items to this order:
>>> order.orderlines.create(name='Candied Yams')
<Candied Yams>
>>> order.orderlines.create(name='Minty loin of Lamb')
<Minty loin of Lamb>
# Get all items for this order:
>>> order.orderitems.all()
[<Candied Yams>, <Minty loin of Lamb>]
This is pretty well documented behavior :)
If I understand this correctly you're looking for the inverse of the many to one i.e. an accessor that provides you with a set of all orderlines per order.
Luckily, the act of creating a many-to-one link does this for you. Try this:
o = Order.objects.get(id=X)
lines = o.orderline_set.all()
lines should now contain the entire set of linked order lines. It doesn't seem to be widely documented, but if you read the example code in the many-to-one documentation closely, you'll see this functionality used all the time.
Notes:
Lower case orderline is deliberate, it is always lower case.