【问题标题】:Override query on Django DateField to change date searched for覆盖 Django DateField 上的查询以更改搜索的日期
【发布时间】:2021-10-28 15:25:56
【问题描述】:

我在 Django 中有一个代表一周的模型。我想让某人输入任何日期并让模型自动保存一周的开始。这很简单,只需覆盖保存即可。

class Week(models.Model):
  start_date = models.DateField()
  
  def save(self):
    self.start_date = self.start_date - datetime.timedelta(days=date.weekday())

但我也希望有人能够查询任何一天并获得星期。例如,我希望有人这样做:

this_week = Week.objects.filter(start_date=date.today())

今天是星期三,并获取设置日期为一周开始的星期对象。它需要适用于任何日期,而不仅仅是今天。

我知道我们可以在管理器中覆盖 get_queryset,但是有没有办法编辑实际搜索的内容?我能找到的每个管理器示例都以静态方式更改了查询集。还是我最好的办法是尝试继承 DateField?

(注意上面的代码是输入的、简化的,可能包含错误,但在我的实际代码中是有效的)

【问题讨论】:

    标签: python django django-queryset


    【解决方案1】:

    我们可以将DateField 子类化为每次清除日期时间到一周的开始。因此看起来像:

    from django.db.models.fields import DateField
    from datetime import timedelta
    
    class WeekField(DateField):
        def to_python(self, value):
            value = super().to_python(value)
            if value is not None:
                value -= timedelta(days=value.weekday())
            return value

    然后我们可以使用WeekField 创建、过滤等。例如,我们可以在 Week 模型中指定它:

    class Week(models.Model):
        week = WeekField()

    然后例如使用.get_or_create(…):

    >>> from datetime import date
    >>> Week.objects.get_or_create(week=date.today())
    (<Week: Week object (1)>, True)
    >>> Week.objects.get_or_create(week=date.today())
    (<Week: Week object (1)>, False)
    

    按周过滤:

    >>> Week.objects.filter(week=date(2021, 8, 31))
    <QuerySet [<Week: Week object (1)>]>
    >>> Week.objects.filter(week=date(2021, 8, 2))
    <QuerySet []>
    

    使用.values()查看本周的开始:

    >>> Week.objects.values()
    <QuerySet [{'id': 1, 'week': datetime.date(2021, 8, 30)}]>
    

    我没有对此进行广泛测试,但我想大部分功能都已涵盖。如果还有未涵盖的用例,请联系我。

    【讨论】:

    • to_python 方法是我所缺少的。
    • @lovefaithswing:我实际上发现这个问题是一个很好的挑战。我在我的网站上写了一篇关于它的文章 :) django-antipatterns.com/pattern/…
    【解决方案2】:

    在数据库级别,您可以根据 start_date 进行排序,而不是默认的primary_key

    class Week(models.Model):
      start_date = models.DateField()
    
      class Meta:
        ordering = ['start_date']
    

    从这里开始,您可以直接找到所需的一周。

    week = Week.objects.filter(start_date__lte=date.today()).first()
    

    使用Meta 的替代方法是在查询中简单地使用order_by

    week = Week.objects.filter(start_date__lte=date.today()).order_by('start_date').first()
    

    【讨论】:

    • 但如果我需要执行 get_or_create 查询,这将不起作用。
    猜你喜欢
    • 1970-01-01
    • 2011-08-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-14
    • 2017-09-03
    • 1970-01-01
    • 2014-08-21
    相关资源
    最近更新 更多