如何用创建后的天数注释查询集

Posted

技术标签:

【中文标题】如何用创建后的天数注释查询集【英文标题】:How to annotate a queryset with number of days since creation 【发布时间】:2019-09-23 16:56:48 【问题描述】:

我正在尝试根据促销进行一些复杂的排序:自创建以来每 7 天促销一篇文章(文章在 30 天后过期)。

我的方法是使用自创建以来的天数来注释查询集,但在我的代码中,注释字段 (days_since_creation) 的值始终为 0。

from datetime import timedelta
from django.test import TestCase
from django.db.models import ExpressionWrapper, F
from django.db.models.fields import IntegerField
from django.utils import timezone
from .models import Article
    # class Article(models.Model):
    #     title = models.CharField(max_length=150)
    #     creation_date = models.DateTimeField()
    # 
    #     def __str__(self):
    #         return self.title


class ArticleTestCase(TestCase):

    def test_days_since_creation(self):
        now = timezone.now()
        objects_data = [
            Article(
                title='Alrp',
                creation_date=(now - timedelta(days=5)) # 5 days ago
            ),
            Article(
                title='Bopp',
                creation_date=(now - timedelta(days=7)) # 7 days ago
            ),
            Article(
                title='Crkp',
                creation_date=(now - timedelta(days=14)) # 14 days ago
            ),
        ]
        Article.objects.bulk_create(objects_data)
        article_set = Article.objects\
            .annotate(
                days_since_creation=ExpressionWrapper(
                    now - F('creation_date'),
                    output_field=IntegerField()
                )
            )
        for article in article_set:
            print(article.days_since_creation)
        # all 3 objects print "0"

我希望每个对象的值分别为 5、7、14。我什至尝试过DurationField,但它只打印了 0:00:00。

之后,如果days_since_past 的值在[0, 7, 14, 21, 28,] 或1 中,我将再次使用order 字段注释查询集,否则为1,然后order_by('order')

【问题讨论】:

我找到了与您的问题相关的答案:Django How to mock datetime.now for unit testing @Mikey 它到底有什么关系?我不认为问题出在now 函数上。我打印了日期时间来检查它们的值,一切都是正确的。 【参考方案1】:

这有点 hacky,但有效。您需要将now 转换为DateTimeField。结果将在DurationField - timedelta 的标准。对于order,我们将使用Case 来检查我们是否在选定的持续时间内,然后我们对其进行排序。

我专门将Articles 的初始顺序更改为测试顺序。

from datetime import timedelta
from django.test import TestCase
from django.db.models import ExpressionWrapper, F, Value, Case, When
from django.db.models.fields import DateTimeField, DurationField, BooleanField
from django.utils import timezone
from .models import Article


class ArticleTestCase(TestCase):
    def test_days_since_creation(self):
        now = timezone.now()

        order_in_variants = [
            timedelta(days=0),
            timedelta(days=7),
            timedelta(days=14),
            timedelta(days=21),
            timedelta(days=28),
        ]

        objects_data = [
            Article(
                title='Crkp',
                creation_date=(now - timedelta(days=14))  # 14 days ago
            ),
            Article(
                title='Alrp',
                creation_date=(now - timedelta(days=5))  # 5 days ago
            ),
            Article(
                title='Bopp',
                creation_date=(now - timedelta(days=7))  # 7 days ago
            ),
        ]
        Article.objects.bulk_create(objects_data)
        article_set = Article.objects.all().annotate(
            days_since_creation=ExpressionWrapper(
                Value(now, DateTimeField()) - F('creation_date'),
                output_field=DurationField()
            )
        ).annotate(
            order=Case(
                When(days_since_creation__in=order_in_variants, then=Value(False)),
                default=Value(True),
                output_field=BooleanField(),
            )
        ).order_by('order')
        for article in article_set:
            print(article.days_since_creation.days, article.order)
        # 14 False
        # 7 False
        # 5 True

您可能还想检查days_since_creation 是否在范围内,而不是正好相差 7 天。让它更丑陋,但仍然:

order_in_variants = [
            (timedelta(days=0), timedelta(days=0, hours=23, minutes=59, seconds=59, microseconds=999999)),
            (timedelta(days=7), timedelta(days=7, hours=23, minutes=59, seconds=59, microseconds=999999)),
            (timedelta(days=14), timedelta(days=14, hours=23, minutes=59, seconds=59, microseconds=999999)),
            (timedelta(days=21), timedelta(days=21, hours=23, minutes=59, seconds=59, microseconds=999999)),
            (timedelta(days=28), timedelta(days=28, hours=23, minutes=59, seconds=59, microseconds=999999)),
        ]
# ...
order=Case(
                When(days_since_creation__range=order_in_variants[0], then=Value(False)),
                When(days_since_creation__range=order_in_variants[1], then=Value(False)),
                When(days_since_creation__range=order_in_variants[2], then=Value(False)),
                When(days_since_creation__range=order_in_variants[3], then=Value(False)),
                When(days_since_creation__range=order_in_variants[4], then=Value(False)),
                default=Value(True),
                output_field=BooleanField(),
            )

【讨论】:

效果很好!为什么你说使用Value 是hacky? Value 不是 hacky,感觉过于复杂,在数据库中这样做可能会更有效。 您的意思是来自article_set 的查询字符串效率低下?或者只是代码很复杂?既然您提到了范围检查,那么忽略时间部分不是更干净吗?将字段转换为 DateField 而不是 DateTimeField?改成Value(now.date(), DateField()) - F('creation_date__date'),不行,days_since_creation的值又是0了。 放弃它,这在 sql 方面非常好。如果它看起来更干净并且有效 - 可能会更好。但我无法让它工作。 你的意思是废弃cast to date 逻辑?此外,多个When 子句非常适合微调查询集,使用IntegerField 而不是BooleanField 作为order 字段的值。

以上是关于如何用创建后的天数注释查询集的主要内容,如果未能解决你的问题,请参考以下文章

如何用c语言来计算日期间隔天数?

若何用一条sql语句查看所有表的注释?

如何用js进行日期的加减

如何用Rational rose创建类图

如何用angular中的另一个数组的子对象创建一个数组

如何用相同的 XML 注释记录重载方法?