Django: add new column by function ( datetimefield to datefield ) - python

Change datetimefield to datefield and put in in new column
python ways are useless because i want to be able to use order_by and distinct
i don't want to use sorted() and etc
my django model is something like this
class Product(models.Model):
price = models.PositiveIntegerField(null=False, default=0)
discounted_price = models.PositiveIntegerField(null=False, default=0)
date = models.DateTimeField(auto_now_add=True)
And i want to be able to call date() function
and put it in another column and order_by it
Product.objects.annotate(day=date.date()).order_by('-day').distinct('day')

You can do somthing like this
Product.objects.annotate(date__date=date.date()).order_by('-date').distinct('date')
check doc. for more information

After lots of search i found my answer
from django.db.models.functions import TruncDate
Product.objects.annotate(day=TruncDate('date')).order_by('-day').distinct('day')

Related

Django queryset: order_by ForeignKey boolean attribute

I'm trying to order a queryset so that:
the first elements of the queryset are those which have a ForeignKey boolean attribute (first) set to True, and amongst them, they are ordered by creation date
the following elements are those having ForeignKey first attribute set to False, and again amongst them, they are ordered by creation date
Here is an example of the models:
class A(models.Model):
first = models.BooleanField(blank=False, default=False)
class B(models.Model):
a = models.ForeignKey(A)
created = models.DateTimeField(auto_now_add=True)
The queryset looks like:
queryset = B.objects.all().order_by("-a__first", "-created")
This snipper, however, is not doing the work.
A solution that I'm thinking of is to use two different databases call (one filtering for a__first=True and the other filtering for a__first=False), and then sum up the querysets results. But I would like to understand if there is a better and cleaner way of solving this problem.
Try the following conditional query, it might possibly work
from django.db.models import Case, When
B.objects.annotate(
first_true=Case(
When(a__first=True, then=('created')),
default=None
),
first_false=Case(
When(a__first=False, then=('created')),
default=None
)
).order_by(
'-first_true',
'-first_false',
)

Django ORM: Filter on timedelta

I've got a field 'frequency' and i'd like use it in my filter.
My model:
class Foo(models.Model):
name = models.CharField(max_length=50)
frequency = models.IntegerField(default='60')
last_checked = models.DateTimeField()
I'd like get my data some like it, but instead of '10' minutes I can put there 'frequency' field.
Foo.objects.filter(last_checked__lt=timezone.now()-timedelta(minutes=10))
How can i do this? Or maybe I shoud go other way? Thanks for helpful tips.

Django - Aggregate equal values of fields in model

I have activity logs for user activities, basically structured like this:
class ActivityLog(TimeStampedModel):
user = models.ForeignKey(User, on_delete=models.CASCADE)
action_type = models.CharField(max_length=25)
object_raw = models.CharField(max_length=500)
I want to aggregate all the entries where object_raw matches, i.e. so if a user searched for 'foo' on 4 different occasions, I get back one entry for 'foo', with count=4. I'm having trouble doing this right now. I know how to do it in sql, but don't understand that django syntax. I've been reading through the docs but I still don't get it. If anyone could help, it would be much appreciated!
To get one object with 'foo' and how many objects have object_raw='foo' you can do:
activity_logs = ActivityLog.objects.filter(object_raw='foo')
if activity_logs.exists():
activity_logs.first() # get one object
activity_logs.count() # get number of objects
If you just want how many objects have object_raw='foo', you can use conditional expressions with aggregates:
from django.db.models import IntegerField, Sum
Client.objects.aggregate(
num_object_raw=Sum(
Case(
When(object_raw='foo', then=1),
output_field=IntegerField()
)
)
)

Django query annotation with boolean field

Let's say I have a Product model with products in a storefront, and a ProductImages table with images of the product, which can have zero or more images. Here's a simplified example:
class Product(models.Model):
product_name = models.CharField(max_length=255)
# ...
class ProductImage(models.Model):
product = models.ForeignKey(Product, related_name='images')
image_file = models.CharField(max_length=255)
# ...
When displaying search results for products, I want to prioritize products which have images associated with them. I can easily get the number of images:
from django.db.models import Count
Product.objects.annotate(image_count=Count('images'))
But that's not actually what I want. I'd like to annotate it with a boolean field, have_images, indicating whether the product has one or more images, so that I can sort by that:
Product.objects.annotate(have_images=(?????)).order_by('-have_images', 'product_name')
How can I do that? Thanks!
I eventually found a way to do this using django 1.8's new conditional expressions:
from django.db.models import Case, When, Value, IntegerField
q = (
Product.objects
.filter(...)
.annotate(image_count=Count('images'))
.annotate(
have_images=Case(
When(image_count__gt=0,
then=Value(1)),
default=Value(0),
output_field=IntegerField()))
.order_by('-have_images')
)
And that's how I finally found incentive to upgrade to 1.8 from 1.7.
As from Django 1.11 it is possible to use Exists. Example below comes from Exists documentation:
>>> from django.db.models import Exists, OuterRef
>>> from datetime import timedelta
>>> from django.utils import timezone
>>> one_day_ago = timezone.now() - timedelta(days=1)
>>> recent_comments = Comment.objects.filter(
... post=OuterRef('pk'),
... created_at__gte=one_day_ago,
... )
>>> Post.objects.annotate(recent_comment=Exists(recent_comments))
Use conditional expressions and cast outputfield to BooleanField
Product.objects.annotate(image_count=Count('images')).annotate(has_image=Case(When(image_count=0, then=Value(False)), default=Value(True), output_field=BooleanField())).order_by('-has_image')
Read the docs about extra
qs = Product.objects.extra(select={'has_images': 'CASE WHEN images IS NOT NULL THEN 1 ELSE 0 END' })
Tested it works
But order_by or where(filter) by this field doesn't for me (Django 1.8) 0o:
If you need to order the resulting queryset using some of the new
fields or tables you have included via extra() use the order_by
parameter to extra() and pass in a sequence of strings. These strings
should either be model fields (as in the normal order_by() method on
querysets), of the form table_name.column_name or an alias for a
column that you specified in the select parameter to extra().
qs = qs.extra(order_by = ['-has_images'])
qs = qs.extra(where = ['has_images=1'])
FieldError: Cannot resolve keyword 'has_images' into field.
I have found https://code.djangoproject.com/ticket/19434 still opened.
So if you have such troubles like me, you can use raw
If performance matters, my suggestion is to add the hasPictures boolean field (as editable=False)
Then keep right value through ProductImage model signals (or overwriting save and delete methods)
Advantages:
Index friendly.
Better performance. Avoid joins.
Database agnostic.
Coding it will raise your django skills to next level.
When you have to annotate existence with some filters, Sum annotation can be used. For example, following annotates if there are any GIFs in images:
Product.objects.filter(
).annotate(
animated_images=Sum(
Case(
When(images__image_file__endswith='gif', then=Value(1)),
default=Value(0),
output_field=IntegerField()
)
)
)
This will actually count them, but any pythonic if product.animated_images: will work same as it was boolean.

Django datetime issues (default=datetime.now())

I have the below db model:
from datetime import datetime
class TermPayment(models.Model):
# I have excluded fields that are irrelevant to the question
date = models.DateTimeField(default=datetime.now(), blank=True)
I add a new instance by using the below:
tp = TermPayment.objects.create(**kwargs)
My issue: all records in database have the same value in date field, which is the date of the first payment. After the server restarts, one record has the new date and the other records have the same as the first. It looks as if some data is cached, but I can't find where.
database: mysql 5.1.25
django v1.1.1
it looks like datetime.now() is being evaluated when the model is defined, and not each time you add a record.
Django has a feature to accomplish what you are trying to do already:
date = models.DateTimeField(auto_now_add=True, blank=True)
or
date = models.DateTimeField(default=datetime.now, blank=True)
The difference between the second example and what you currently have is the lack of parentheses. By passing datetime.now without the parentheses, you are passing the actual function, which will be called each time a record is added. If you pass it datetime.now(), then you are just evaluating the function and passing it the return value.
More information is available at Django's model field reference
Instead of using datetime.now you should be really using from django.utils.timezone import now
Reference:
Documentation for django.utils.timezone.now
so go for something like this:
from django.utils.timezone import now
created_date = models.DateTimeField(default=now, editable=False)
From the documentation on the django model default field:
The default value for the field. This can be a value or a callable object. If callable it will be called every time a new object is created.
Therefore following should work:
date = models.DateTimeField(default=datetime.now,blank=True)
David had the right answer. The parenthesis () makes it so that the callable timezone.now() is called every time the model is evaluated. If you remove the () from timezone.now() (or datetime.now(), if using the naive datetime object) to make it just this:
default=timezone.now
Then it will work as you expect:
New objects will receive the current date when they are created, but the date won't be overridden every time you do manage.py makemigrations/migrate.
I just encountered this. Much thanks to David.
The datetime.now() is evaluated when the class is created, not when new record is being added to the database.
To achieve what you want define this field as:
date = models.DateTimeField(auto_now_add=True)
This way the date field will be set to current date for each new record.
datetime.now() is being evaluated once, when your class is instantiated. Try removing the parenthesis so that the function datetime.now is returned and THEN evaluated. I had the same issue with setting default values for my DateTimeFields and wrote up my solution here.
From the Python language reference, under Function definitions:
Default parameter values are evaluated when the function definition is executed. This means that the expression is evaluated once, when the function is defined, and that that same “pre-computed” value is used for each call.
Fortunately, Django has a way to do what you want, if you use the auto_now argument for the DateTimeField:
date = models.DateTimeField(auto_now=True)
See the Django docs for DateTimeField.
The answer to this one is actually wrong.
Auto filling in the value (auto_now/auto_now_add isn't the same as default). The default value will actually be what the user sees if its a brand new object. What I typically do is:
date = models.DateTimeField(default=datetime.now, editable=False,)
Make sure, if your trying to represent this in an Admin page, that you list it as 'read_only' and reference the field name
read_only = 'date'
Again, I do this since my default value isn't typically editable, and Admin pages ignore non-editables unless specified otherwise. There is certainly a difference however between setting a default value and implementing the auto_add which is key here. Test it out!
In Django 3.0 auto_now_add seems to work with auto_now
reg_date=models.DateField(auto_now=True,blank=True)
if you need only DateField try this
date = models.DateField(auto_now=False, auto_now_add=False, null=True, blank=True)
if you need Both Date and Time try this
date = models.DateTimeField(auto_now_add=True, null=True, blank=True)

Categories