可用项目的 Django ListView 查询集

Posted

技术标签:

【中文标题】可用项目的 Django ListView 查询集【英文标题】:Django ListView queryset for available items 【发布时间】:2019-06-01 09:39:14 【问题描述】:

所以我想显示任何给定日期的所有可用项目,应该不难,但不知何故我遇到了有关相关项目的问题。

假设我们有以下模型,一个用于存储所有预订的模型和一个包含项目的模型。

然后我会创建 ListView 来检索任何给定日期之间可用的所有项目。我重写了查询集来检索用户填写的数据。

这似乎有效,但有一个问题,即使我检查“form_start_date”或“form_end_data”是否与现有预订冲突,但当单个项目有多个预订时它不起作用。

示例

项目 #01 的预订量 [X]: 2019 年 1 月 1 日至 2019 年 1 月 3 日 2019 年 1 月 11 日至 2019 年 1 月 18 日

  Jan 2019   1   2   3   4   5   6   7   8   9   10   11   12   13   14   15   16   17   18  
 ---------- --- --- --- --- --- --- --- --- --- ---- ---- ---- ---- ---- ---- ---- ---- ---- 
  Item #01   X   X   X           O   O   O            X    X    X    X    X    X    X    X   
  Item #02       X   X   X   X   X                                                 

当我检查 01-06-2019 到 01-08-2019 的可用性 [O] 时,项目 #01 不可用,我在这里缺少什么?

models.py

class Booking(models.Model):
    item = models.ForeignKey('Item', on_delete=models.SET_NULL, null=True)
    start_date = models.DateField()
    end_date = models.DateField()

class Item(models.Model):
    name = models.CharField(max_length=20, unique=True)

views.py

class AvailableItems(generic.ListView):
    model = Item

    def get_queryset(self):
        start_date = datetime.strptime(self.request.GET.get('start_date'), '%Y-%m-%d')
        end_date = datetime.strptime(self.request.GET.get('end_date'), '%Y-%m-%d')
        # As Willem suggested in the comments, it is easier to check for available items
        available_items = (
            Item.objects.filter(booking__start_date__gte = start_date, booking__end_date__lte = end_date)
        )

        if start_date and end_date:
            return available_items
        else:
            return Item.objects.all()

【问题讨论】:

我认为这里的条件是不充分的,因为start_date 可能不在form_start_dateform_end_date 之间,end_date 也不可能在这两点之间,但是仍然预订重叠。例如因为预订跨度大于表单的跨度(开始较早,结束较晚)。 感谢您的关注,我添加了另一个规则来捕捉这一点。 【参考方案1】:

我们先分析一下两个区间(f1, t1)(f2 sub>, t2) 重叠。要解决的一个更简单的问题是找出两个间隔何时重叠。这适用于两种情况:

    鉴于 t12,因此第一个事件在第二个之前结束;或 鉴于 t21,因此第一个事件在第二个事件开始之前结束。

这意味着两个事件重叠给定 t1 ≥ f2t2 ≥f1.

有了这些知识,我们可以设计如下过滤器:

bookings = Booking.objects.filter(
    end_date__gte=form_start_date,
    start_date__lte=form_end_date
)

return Item.objects.exclude(
    booking__in=bookings
)

这会导致如下查询:

SELECT item.*
FROM item
WHERE NOT (
    item.id IN (
        SELECT V1.item_id
        FROM booking V1
        WHERE (V1.id IN (
            SELECT U0.id FROM booking U0
            WHERE (U0.end_date >= 2019-01-01 AND U0.start_date <= 2019-02-02)
            AND V1.item_id IS NOT NULL
        )
    )
)

(这里2019-01-012019-02-02 是假设的开始和结束日期)。

我认为通过Form 处理这两个日期可能会更好,但是要进行适当的验证和清理。

例如,如果我们使用问题中提供的数据填充一个空数据库,我们会得到:

>>> i1 = Item.objects.create(name='Item #01')
>>> i2 = Item.objects.create(name='Item #02')
>>> b1 = Booking.objects.create(item=i1, start_date=)
KeyboardInterrupt
>>> from datetime import date
>>> b1 = Booking.objects.create(item=i1, start_date=date(2019,1,1), end_date=date(2019, 1, 3))
>>> b2 = Booking.objects.create(item=i1, start_date=date(2019,1,11), end_date=date(2019, 1, 18))
>>> bookings = Booking.objects.filter(
...     end_date__gte=date(2019, 1, 6),
...     start_date__lte=date(2019, 1, 8)
... )
>>> Item.objects.exclude(
...     booking__in=bookings
... )
<QuerySet [<Item: Item object (2)>, <Item: Item object (3)>]>
>>> b3 = Booking.objects.create(item=i2, start_date=date(2019,1,2), end_date=date(2019, 1, 6))
>>> bookings = Booking.objects.filter(
...     end_date__gte=date(2019, 1, 6),
...     start_date__lte=date(2019, 1, 8)
... )
>>> Item.objects.exclude(
...     booking__in=bookings
... )
<QuerySet [<Item: Item object (2)>]>

所以首先我构建了两个项目,并在第一个项目上进行了两次预订。如果我们然后进行查询,我们会看到两个项目都弹出。如果我们随后为Item #02 添加一个额外的预订,然后如果我们再次执行查询,我们会看到只有第一个项目(我首先为测试目的制作了一个项目,然后被删除)显示,因为对于给定的范围,第二项不再可用:已通过预订b3预订。

【讨论】:

感谢您的回复,其他逻辑,以检查 form_end_date > form_start_date 是否将由 clean_method 处理。这是我遇到问题的可用性,所以我想简化这个例子。奇怪的是,当我为“预订”而不是“项目”创建 ListView 时,逻辑似乎有效,但在一段时间内显示包含某些预订的列表是愚蠢的。这是我们追求的可用项目 那么上面的还是不行?因为,好吧,根据它生成的 SQL 输出,查询的逻辑似乎是有效的。 我在创建预订时测试了 save 方法的逻辑,它确实有效,但是当我尝试在给定日期显示包含所有可用项目的列表时,它不会显示两者之间的可用天数同一项目的两次预订。 @KevinD:这是一个更难的问题,因为表中没有“顺序”。您可以按预订日期排序(例如先在start_date,然后在end_date)。但是在 Python 级别计算两个这样的行之间的天数差异可能更好。在 SQL 中,很难获得“上一个”和“下一个”行并在它们之间进行处理。以上只是检索在给定范围内没有“活动”预订的所有项目。 亲爱的 Willem,我想我的解释不是很清楚,因为我正在查询 Booking 模型,并检查用户输入和现有记录之间的任何冲突,它不应该冲突。我已经编辑了我的示例,当我选择日期 [O] 时,项目 #01 显示为不可用,它显示了不应该发生的冲突......

以上是关于可用项目的 Django ListView 查询集的主要内容,如果未能解决你的问题,请参考以下文章

Django将自定义查询集添加到ListView中

Django_单表查询

Django 模板过滤器查询集

将视图中的 Django 查询集传递给模板

Django 可重用查询集

在不使用 API 的情况下,是不是有任何可接受的方式来切割/重组 Django 查询集?