Annotations Force Cached Queryset Evaluation - django

I am having issues using cache with annotate. When I run annotate, the queryset is evaluated, ignoring the cached results. This makes it much slower. I’d like to be able to annotate my model without having to fully run the query. Is this possible? If not, why not?
from django.core.cache import cache
from django.db.models import Exists, OuterRef
favorites = Favorites.objects.filter(item = OuterRef(‘pk’), user=user)
best_items = cache.get(‘best_items’)
best_items = best_items.annotate(is_favorite= Exists(favorites))

Related

How to delete data after fetch in a same action in Django?

I have a table in DB named Plan.
see code in models.py:
class Plan(models.Model):
id = models.AutoField(primary_key=True)
Comments = models.CharField(max_length=255)
def __str__(self):
return self.Comments
I want to fetch data(comments) from DB and after that data will be deleted. That means one data will be fetched once. And this data will be shown in the Django template.
I tried, see views.py
def Data(request):
data = Plan.objects.filter(id=6)
# latest_id = Model.objects.all().values_list('id', flat=True).order_by('-id').first()
# Plan.objects.all()[:1].delete()
context = {'data':data}
dataD = Plan.objects.filter(id=6)
dataD.delete()
return render(request,'data.html',context)
this code is deleting data from DB but not showing in the template.
How can i do this?
Your template must be updated because it fetch the data from the db one time only so if db is updated your template wouldn't change
From django docs:
Pickling QuerySets¶
If you pickle a QuerySet, this will force all the results to be loaded into memory prior to pickling. Pickling is usually used as a precursor to caching and when the cached queryset is reloaded, you want the results to already be present and ready for use (reading from the database can take some time, defeating the purpose of caching). This means that when you unpickle a QuerySet, it contains the results at the moment it was pickled, rather than the results that are currently in the database.
If you only want to pickle the necessary information to recreate the QuerySet from the database at a later time, pickle the query attribute of the QuerySet. You can then recreate the original QuerySet (without any results loaded) using some code like this:
>>> import pickle
>>> query = pickle.loads(s) # Assuming 's' is the pickled string.
>>> qs = MyModel.objects.all()
>>> qs.query = query # Restore the original 'query'.

Django queryset how to query SQL with positive values first, ZERO values second

I have an SQL query like the following:
select * from results_table order by case
when place = 0 then 1 else 0 end, place
This query sorts positive numbers first, ZEROs next. How can I write this in Django? Better yet, how can I write it in the following way:
Result.objects.filter(...).order_by('positive_place', 'place')
where 'positive_place' exists for certain models. I am reading about annotate but I am not quiet sure how it works yet. I need to write the annotation for every query. Is there a way to write annotation per query set?
An annotation is adding an attribute to each object in a queryset. Attributes can be further filtered and ordered. You can annotate a queryset using conditional expressions and you can make it reusable by calling custom queryset methods from the model manager.
I'm having a hard time understanding your desired ordering but here's an example of how it could be put together.
from django.db import models
from django.db.models import Case, Value as V, When
class ResultQuerySet(models.QuerySet):
def annotate_positive_place(self):
return self.annotate(
positive_place=Case(When(place=0, then=V(1)), default=V(0))
)
class Result(models.Model):
place = models.IntegerField()
objects = ResultQuerySet.as_manager()
Result.objects.annotate_positive_place().order_by('positive_place')

Trying to use the total of related objects' fields in a Django ORM query

Suppose I have two models:
class Task(Model):
duration = models.IntegerField(default=100)
class Record(Model):
minutes_planned = models.IntegerField(default=0)
task = models.ForeignKey(Task, related_name=records)
I would like to get ahold of all the objects whose total minutes planned across all related Records is lower than the object's duration. I've been having trouble finding a solution in the docs. Could someone point me to it?
Task.objects.filter(duration__gt=F('records__minutes_planned')))
Task.objects.filter(duration__gt=Sum('records__minutes_planned'))
Task.objects.filter(duration__gt=Sum(F('records__minutes_planned')))
but so far nothing has worked. The first one ran successfully, but from what I can tell, it compared them one-by-one instead of to a total of all records.
It seems Sum is restricted to usage only in .aggregate(). However, I would like to retrieve the objects themselves, rather than a set of values, which is what .aggregate() would give me.
UPDATE:
Found this portion the docs that looks promising.
Try using annotate(). You can annotate a field which holds the sum of the minutes_planned of all the Records and then use this value for filtering out the needed Tasks. The query will look something like:
Task.objects.annotate(total_minutes_planned=Sum('records__minutes_planned')).
filter(duration__gt=total_minutes_planned)
Hope this helps.
Here is the final solution written as a model Manager:
from django.db.models import Manager, OuterRef, Subquery, Sum
from django.db.models.functions import Coalesce
class TaskManager(Manager):
def eligible_for_planning(self, user):
from .models import Record
records = Record.objects.filter(task=OuterRef('pk')).order_by().values('task')
minutes_planned = records.annotate(total=Sum('minutes_planned')).values('total')
qs = self.model.objects.filter(user=user, status=ACTIONABLE, duration__gt=Coalesce(Subquery(minutes_planned), 0))
return qs
We're basically constructing a second query to grab the value needed for the first query.
In this case, records is the second query (or SubQuery), and it filters the Records by the pk of Task being queried in this manager.
Then, minutes_planned returns the actual total that will be compared to the Task's duration.
Finally, the whole thing is plugged into the queryset as a Subquery object. Wrap it in a Coalesce and add the default value, should there be no Record objects found. In my case, this is zero.
Reference

How to get revision dates for first and last update for multiple objects?

I need to make a bulk query for all instances of SomeModel, annotating them with the date of their creation and last update. Here's what I tried and is terribly slow:
query = SomeModel.objects.all()
for entry in query:
last_updated_date = entry.details.history.last().history_date
created_date = entry.details.history.first().history_date
csv_writer.writerow([entry.name, last_updated_date, created_date])
How could I optimize the code? I imagine that the problem is that I'm making a lot of SELECT queries, when probably a single bit more complex one would do.
You can try like this(using subquery):
from django.db.models import OuterRef, Subquery
from simple_history.models import HistoricalRecords
histories = HistoricalRecords.objects.filter(pk=OuterRef('details__history')).order_by('history_date')
SomeModel.objects.annotate(created_date=Subquery(histories.values('history_date')[:1])).annotate(last_updated==Subquery(histories.order_by('-history_date').values('history_date')[:1]))
FYI: this is an untested code.

View the SQL queries for Django queryset delete

How do you view the SQL generated by Django for a DELETE?
When doing a SELECT operation on a query set, you can do this:
>>> qs = Entry.objects.filter(date__gt='2010-06-01')
>>> qs.query.as_sql()
('SELECT ...)
But I don't know how to get the SQL for what happens when I do qs.delete().
It looks a bit more involved because Django "emulates the behavior of the SQL constraint ON DELETE CASCADE" when deleting objects.
(Background: trying to debug an IntegrityError generated by a foreign key constraint when deleting a subclassed model object.)
This works well enough:
>>> from django.db import connection
>>> connection.queries[:-10]
Thought the exceptions occurred before the queries were added to connection.queries, but they are indeed present.
Here's another method which relies on Django internals and doesn't include queries to do cascading deletes, but doesn't require executing the query:
from django.db.models import sql
qs = Entry.objects.filter(date__gt='2010-06-01')
query = qs.query.clone()
query.__class__ = sql.DeleteQuery
print(query)
You could try running django-debug-toolbar and see the queries that way.