How do I use a DB procedure from the django ORM? - django

For example, in MySQL, I would write:
SELECT id, my_calc(id, '2022-09-26') as calculated
FROM items;
How do I tell the ORM to use this DB procedure and pass the parameters?
Would I have to define a Func?
Items.objects.annotate(calculated=...)

Define the new function:
from django.db.models import Func
class MyCalc(Func):
function = 'my_calc'
arity = 2 # requires 2 arguments
Use it:
from django.db.models import Value
Items.objects.annotate(calculated=MyCalc(F('id'), Value('2022-09-26'))

obviously through Func expression.
More here:
https://docs.djangoproject.com/en/4.1/ref/models/expressions/#func-expressions
in your case (postgres):
self.annotate(calculated=Func(F('id'), Value('2022-09-26), function='public.MY_CALC', output_field=CharField()))

Related

Use TO_CHAR in Django filter query method that makes a join

I need to cast a field via TO_CHAR using Django. I know I can:
Author.objects.extra(where=["TO_CHAR(name) = 'John'"])
and I also can:
Author.objects.filter(article__name='CoolArticle')
which will make the join on Article behind the scenes. Is there a way to combine both and achieve this:
author.articles.filter(website__name='AwesomeWeb')
# where website__name is casted via TO_CHAR
You need to write a custom transformer lookup. Something like:
from django.db.models import Transform
from django.db.models.fields import Field
class ToChar(Transform):
lookup_name = 'tochar'
function = 'TO_CHAR'
Field.register_lookup(ToChar)
Now you should be able to do:
author.articles.filter(website__name__tochar='AwesomeWeb')

Error on OR on filter Django

i'm try create or on filter on django
This is my little example :
products=Products.objects.values('name', 'price').all().filter(status=1|0)
The problem is that don't validate the two options (1|0)
don't get a error on the print(products.query) only validate one option don't the 2 options..!!
Please thanks !!
To filter using OR in django you need a special class called Q.
Documentation about Complex lookups with Q objects
from django.db.models import Q
products = Products.objects.values('name', 'price').filter(Q(status=1) | Q(status=0))
It's good to use Q object
manager.filter(Q(status=1) | Q(status=0))
You need to know that the method all() on a manager just delegates to get_queryset().
To use filter(), you would already have the QuerySet
Rather than all() whose calls the queryset, and then filter whose already call the queryset,
just do manager.filter()
all().filter() becomes just filter() because it's redundant
There it is:
from django.db.models import Q
products = Product.objects.values('name','price').filter(
Q(status=1) | Q(status=0),
)

Map (apply function) Django QuerySet

Is there a mechanism to map Django QuerySet items not triggering its evaluation?
I am wondering about something like Python map. A function that uses a function to apply it over the QuerySet, but keeping the lazy evaluation.
For example, using models from Django documentation example, is there something like? (not real code):
>>> Question.objects.all().map(lambda q: q.pub_date + timedelta(hours=1))
which keeps the lazy evaluation?
Just working through something like this myself. I think the best way to do it is to use a python list comprehension.
[q.pub_date + timedelta(hours=1) for q in Question.objects.all()]
Then Django takes care of optimizing this as it would any other query.
You can use annotate for this purpose, for example
from datetime import timedelta
from django.db.models import F, ExpressionWrapper, DateTimeField
Question.objects.annotate(
new_pub_date=ExpressionWrapper(
F('pub_date') + timedelta(hours=1),
output_field=DateTimeField()
)
)
For something a little bit more complex than this example, you can use Func, Case, When
You can use values_list to get any column you like:
Question.objects.values_list('pub_date')
This is simpler than anything you can cook up yourself.

"SELECT field as x..." with Django ORM

Is it possible to use AS sql statement with Django ORM:
SELECT my_field AS something_shiny WHERE my_condition = 1
If it is possible then how?
By now the Django documentation says that one should use extra as a last resort.
So here is a better way to do this query:
from django.db.models import F
Foo.objects.filter(cond=1).annotate(sth_shiny=F('my_field'))
use extra()
Foo.objects.filter(cond=1).extra(select={'sth_shiny':'my_field'})
Then you could access sth_shiny attr of resulted Foo instances

Making queries using F() and timedelta at django

I have the following model:
class Process(models.Model):
title = models.Charfield(max_length=255)
date_up = models.DateTimeField(auto_now_add=True)
days_activation = models.PositiveSmallIntegerField(default=0)
Now I need to query for all Process objects that have expired, according to their value of days_activation.
I tried
from datetime import datetime, timedelta
Process.objects.filter(date_up__lte=datetime.now()-timedelta(days=F('days_activation')))
and received the following error message:
TypeError: unsupported type for timedelta days component: F
I can of course do it in Python:
filter (lambda x: x.date_up<=datetime.now() - timedelta(days=x.days_activation),
Process.objects.all ()),
but I really need to produce a django.db.models.query.QuerySet.
7 days == 1 day * 7
F is deep-black Django magic and the objects that encounter it
must belong to the appropriate magical circles to handle it.
In your case, django.db.models.query.filter knows about F, but datetime.timedelta does not.
Therefore, you need to keep the F out of the timedelta argument list.
Fortunately, multiplication of timedelta * int is supported by F,
so the following can work:
Process.objects.filter(date_up__lte=datetime.now()-timedelta(days=1)*F('days_activation'))
As it turns out, this will work with PostgreSQL, but will not work with SQlite (for which Django 1.11 only supports + and - for timedelta,
perhaps because of a corresponding SQlite limitation).
You are mixing two layers: run-time layer and the database layer. F function is just a helper which allows you to build slightly more complex queries with django ORM. You are using timedelta and Ftogether and expecting that django ORM will be smart enough to convert these things to raw SQL, but it can't, as I see. Maybe I am wrong and do not know something about django ORM.
Anyway, you can rewrite you ORM call with extra extra and build the WHERE clause manually using native SQL functions which equals to datetime.now() and timedelta.
You have to extend Aggregate. Do like below:
from django.db import models as DM
class BaseSQL(object):
function = 'DATE_SUB'
template = '%(function)s(NOW(), interval %(expressions)s day)'
class DurationAgr(BaseSQL, DM.Aggregate):
def __init__(self, expression, **extra):
super(DurationAgr, self).__init__(
expression,
output_field=DM.DateTimeField(),
**extra
)
Process.objects.filter(date_up__lte=DurationAgr('days_activation'))
Hopefully, It will work for you. :)
I tried to use solution by Lutz Prechelt above, but got MySQL syntax error.
It's because we can't perform arithmetic operations with INTERVAL in MySQL.
So, for MySQL my solution is create a custom DB function:
class MysqlSubDate(Func):
function = 'SUBDATE'
output_field = DateField()
Example of usage:
.annotate(remainded_days=MysqlSubDate('end_datetime', F('days_activation')))
Also you can use timedelta, it will be converted into INTERVAL
.annotate(remainded_days=MysqlSubDate('end_datetime', datetime.timedelta(days=10)))