Filter on prefetch_related in Django - django

Is there a way of filtering prefetched objects? I need to get the latest() of the prefetched objects but prefetch_related doesn't work if you use latest because the query is changed?
The example here does what I need but I was hoping there's a simpler workaround...
https://github.com/ionelmc/django-prefetch#example

As of Django 1.7, filtering prefetched objects is possible. See this SO answer, and the Django documentation.

It is very simple method which is hardly comparable with those app, but hope you will find it useful:
class Author(models.Model):
name = models.CharField(max_length=100)
def latest_book(self):
return max(self.book_set.all(), key=lambda book: book.created)
authors = Author.objects.prefetch_related('book_set')
authors[0].latest_book() # what you wanted

Yes, it can be done in this way :
authors=Author.objects.prefetch_related('book_set')
If you want to filter by an attribute(name) present in Author model you can simply filter it by writing:
authors.filter(name='your_value')
But if you want to apply filter on the Books model you have to write the following way:
authors.filter(book__created__gt='your_date')
This will filter all the books that have create date(created attribute in the Book module) greater than your date.

Related

Django QuerySet from one-to-many and from many-to-one

working on a project, where i came to a small problem with QuerySets. (Look at the bottom to see a short diagram of my model structure.) I tried to query some information of a BLOG-Model starting from the Collection-Model. Now is it better to query first the Entrie-Model and find somehow the Collection to afterwards find the Blog-Model and add the information in the end to the Collection? Or is there a better / faster way to get information directly from the parent Model? Normaly it is easy if you just have 1:n->1:n Relations because you can easily follow the foreignkey, but this confuses me.
Here a short overview of my model structure:
<--------------- QUERY DIRECTION ----------------
(BLOG) --(1:N)--> (ENTRIE) <--(N:1)-- (COLLECTION)
BR, Felix
django's relations works both ways so if you need blog model from collection model you can directly query your Blog model so from your diagram:
class Blog:
entrie = models.ForeignKey(Entrie, on_delete=CASCADE)
class Entrie:
pass
class Collection:
entrie = models.ForeignKey(Entrie, on_delete=CASCADE)
so your query can be something like if you have not specified any related_name attributes on your models relation fields
Blog.objects.filter(entrie__collection={your_data})

Store user-defined filters in database

I need to allow users to create and store filters for one of my models. The only decent idea I came up with is something like this:
class MyModel(models.Model):
field1 = models.CharField()
field2 = models.CharField()
class MyModelFilter(models.Model):
owner = models.ForeignKey('User', on_delete=models.CASCADE, verbose_name=_('Filter owner'))
filter = models.TextField(_('JSON-defined filter'), blank=False)
So the filter field store a string like:
{"field1": "value1", "field2": "value2"}.
Then, somewhere in code:
filters = MyModelFilter.objects.filter(owner_id=owner_id)
querysets = [MyModel.objects.filter(**json.loads(filter)) for filter in filters]
result_queryset = reduce(lambda x, y: x|y, querysets)
This is not safe and I need to control available filter keys somehow. On the other hand, it presents full power of django queryset filters. For example, with this code I can filter related models.
So I wonder, is there any better approach to this problem, or maybe a 3rd-party library, that implements same functionality?
UPD:
reduce in code is for filtering with OR condition.
UPD2:
User-defined filters will be used by another part of system to filter newly added model instances, so I really need to store them on server-side somehow (not in cookies or something like that).
SOLUTION:
In the end, I used django-filter to generate filter form, then grabbing it's query data, converting in to json and saving it to the database.
After that, I could deserialize that field and use it in my FilterSet again. One problem that I couldn't solve in a normal way is testing single model in my FilterSet (when model in already fetched and I need to test, it it matches filter) so I ended up doing it manually (by checking each filter condition on model).
Are you sure this is actually what you want to do? Are your end users going to know what a filter is, or how to format the filter?
I suggest that you look into the Django-filter library (https://django-filter.readthedocs.io/).
It will enable you to create filters for your Django models, and then assist you with rendering the filters as forms in the UI.

Django: Using prefetch_selected with get() and filter()

I am trying to make queries using prefetch_selected to prefetch many to many objects.
Using prefetch_selected when doing an all() query works okay but I do not know how i’m supposed to use it with get() or filter(). Here is my model:
class Project(models.Model):
…
funders = models.ManyToManyField(Organization, related_name="funders")
class Organization(models.Model):
…
name = models.CharField(max_length=200, unique=True)
Trying the lines below doesn’t seem to work:
Project.objects.get(id=project_id).select_related('funders')
and
Project.objects.filter(id__in=[‘list-of-ids]).select_related('funders')
How i’m I supposed to go about it?
Thanks in advance.
You chained them in the wrong order, do it this way:
Project.objects.select_related('funders').get(id=project_id)
and
Project.objects.select_related('funders').filter(id__in=[‘list-of-ids])
You have to call select_related and prefetch_related on the Manager object (Project.objects).
select_related() is a queryset method. The documentation on querysets has two sections of methods: methods that return a new queryset, and methods that do not return a queryset. get() is in the second section, so you can't chain any other queryset methods after it.
One other thing: prefetch_related() runs an extra query for each model. Since you're only fetching a single project, project.funders.all() will run exactly 1 query to fetch all related organizations regardless of your use of prefetch_related(). prefetch_related becomes useful when you need the related organizations for multiple projects.
Since funder is m2m you cannot use select_related, you have to us prefetch instead. select_related only work on foreign key and one to one relation
Project.objects.prefetch_related('funders').filter(id__in=[id1, id2])

Getting objects.all() reverse() or descending order

In Django, calling object.manytomany.all().reverse(), and its template equivalent, object.manytomany.all.reverse, don't seem to work for many-to-many relationships.
How do I reverse a list of results of a many-to-many relationship?
You may be going about this wrong or maybe I misunderstand your code snippet. Say you have a Book Model where an instance of a Book can have multiple authors.
class Author(models.Model):
...
class Book(models.Model):
authors = models.ManyToManyField(Author)
Now this will return a QuerySet of all Book instances - Book.objects.all()
objects is the default manager for a Model and it's plural. object is its own thing in Python and it's best not to use it as your own variable. It's also worth stating that "manytomany" isn't a field or function in Django. Let us know if that's something you defined. So to get the ManyToMany multiple authors a book instance might have:
book = Book.objects.all()[0]
type(book.authors) # tells you this is a ManyRelatedManager
type(book.authors.all()) # tells you this is a QuerySet
book.authors.all() # will print out the authors for this book
Now that you have the authors and those authors are in the form of a QuerySet, you can do any normal QuerySet manipulation including reversing.
book.authors.all().reverse()
Remember that reversing something that isn't ordered doesn't mean much so you may want to consider using objects.order_by("<field name>").reverse(). Hope that helps.
So you can do it as
object.manytomany.order_by("-id")
This can be used to order objects in the order we want according to their properties.
Doubt.objects.all().order_by('-date_posted')
book = reversed(Book.objects.all())

How can I use annotate() to count a subset of related models in Django?

I'm trying to use Django's annotate feature to add the count of a related model to a queryset. However, I don't want a full count of related objects, I only want to count the active ones (i.e., "is_active=True"). I can't figure out how to filter down the count.
The (simplified) relevant models:
class Post(models.Model):
user = models.ForeignKey(User)
title = models.CharField(max_length=80)
body = models.TextField()
class Comment(models.Model):
user = models.ForeignKey(User)
post = models.ForeignKey(Post)
comment_body = models.CharField(max_length=80)
is_active = models.BooleanField(default=True)
In a view, I'm trying to annotate a queryset:
queryset=Post.objects.all().annotate(num_comments=Count('comment', distinct=True))
The above counts all the comments related to a post, whereas I only want to count the "is_active" ones. Google and the Django docs aren't helping me here. Has anyone had and solved this problem?
You just need to filter on is_active before doing the annotation:
Post.objects.filter(comment__is_active=True).annotate(num_comments=Count('comment'))
See the explanation here.
This is how I had to "annotate" the number of active comments on my Post queryset:
Post.objects.extra(select={"num_comments":
"""
SELECT COUNT(myapp_comment.id) FROM myapp_reply
WHERE myapp_comment.is_active='1' AND
myapp_comment.post_id = myapp_post.id
"""
},)
Not pretty, but it works. As I mentioned in a comment above, it wasn't possible to use the built-in aggregation function annotate() for this, since that counted all related comments and I only wanted to count the active related comments.
Daniel's solution didn't work, because it filtered out Posts which had no comments. I don't want to filter out any Posts, just inactive comments.
If anyone has a better solution, I will gladly up-vote and best-answer you!
There is two variants based on what database you are using. If you use MySQL the solution is simple and elegant:
Post.objects.annotate(num_comments=Sum('comment.is_active'))
This works because in database boolean fields are integers and True is 1, False is 0.
But this works ONLY in MySQL and works ONLY for boolean fields. The more general way to do the job which works on all databases and can do more complex checks is using a little SQL inserted a little 'hacky':
Post.objects.annotate(num_comments=Count('comment',
field='CASE WHEN myapp_comment.is_active THEN 1 END'))
I have the same problem in my personal blog, and that was the solution. I write a blog post for that. http://venelin.sytes.net/blog/django/filtrirane-na-agregirash-count-v-django/. It's on bulgarian but my site uses google translation. The translation is not very good but may help to understand why this works.