在qs.excude()中通过span关系评估同一个对象

时间:2014-12-18 13:49:34

标签: django django-queryset

考虑一个带有房间对象的酒店和这些房间的预订对象。我想找一个特定时期可用的房间,或者(从下面的例子中可以看到)从哪个房间开始。

可以“删除”预订,这可以通过设置“实时”字段获得。所以他们不是实际删除但只是不活动,这需要保持这种方式。

>>> indate = "20141225"
>>> Room.objects.exclude(
(models.Q(reservation__live=True , reservation__date_out__gt=indate) 
| models.Q(reservation__date_out__isnull=True, reservation__live=True))
)

问题陈述:上面的代码有一个令人遗憾的副作用:当live=True的预订之外期间,当有另一个在同一房间的时间段内预订live=False ,然后该房间将被排除。情况并非如此:由于我要求的期间内的预订未设置为live=True,因此不应将其考虑在内。

在通过房间预订关系进行(live = True date_out__gt = indate)比较时,上面的查询似乎没有考虑相同的预订。< / p>

问题:在排除()中是否有办法确保在比较中考虑相同的预订?

我尝试使用负面模型.Q(~Models.Q)无济于事。 请注意,上面只是一个更大的查询中的代码提取(问题所在的位置)。因此,我不能简单地qs.filter(reservation__live=True)。移动查询以过滤而不是排除似乎不是一种选择。

修改:根据要求添加我的简化模型,如下所示。 Edit2 :添加一些解释问题的测试数据,并根据@ knbk的建议添加链式exclude()。

class AvailableRoomManager(models.Manager):

    def available_with_Q(self, indate, outdate):
        qs = super(AvailableRoomManager, self).get_queryset()

        if indate and outdate:
            qs = qs.exclude(models.Q(reservation__date_out__gt=indate, reservation__date_in__lt=outdate, reservation__live=True)
                            | models.Q(reservation__date_out__isnull=True, reservation__date_in__isnull=False, reservation__date_in__lt=outdate, reservation__live=True))

        elif indate and not outdate:
            qs = qs.exclude((models.Q(reservation__date_out__gt=indate, reservation__date_in__isnull=False, reservation__live=True)
                            | models.Q(reservation__date_out__isnull=True, reservation__date_in__isnull=False, reservation__live=True)))

        return qs

    def available_with_chained_excludes(self, indate, outdate):
        qs = super(AvailableRoomManager, self).get_queryset()

        if indate and outdate:
            qs = qs.exclude(reservation__date_out__gt=indate, reservation__date_in__lt=outdate, reservation__live=True) \
                   .exclude(reservation__date_out__isnull=True, reservation__date_in__isnull=False, reservation__date_in__lt=outdate, reservation__live=True)

        elif indate and not outdate:
            qs = qs.exclude(reservation__date_out__gt=indate, reservation__date_in__isnull=False, reservation__live=True) \
                   .exclude(reservation__date_out__isnull=True, reservation__date_in__isnull=False, reservation__live=True)

        return qs


class Room(models.Model):
    name = models.CharField(max_length=30, unique=True)

    objects = models.Manager()
    available_rooms = AvailableRoomManager()

    def __str__(self):
        return self.name


class Reservation(models.Model): 
    date_in = models.DateField()
    date_out = models.DateField(blank=True, null=True)
    room = models.ForeignKey(Room)
    live = LiveField()  # See django-livefield; to do deletion. Basically adds a field "live" to the model.

    objects = LiveManager()
    all_objects = LiveManager(include_soft_deleted=True)

如果在搜索周期之外存在活动(live = True),并且在该期间内存在非活动(live!= True)时,上述exclude()语句中会弹出问题寻找。

使用上述模型的一些简单测试数据,显示问题所在:

# Let's make two rooms, R001 and R002
>>> room1 = Room.objects.get_or_create(name="R001")[0]
>>> room2 = Room.objects.get_or_create(name="R002")[0]

# First reservation, with no date_out, is created but then "deleted" by setting field 'live' to False
>>> res1 = Reservation.objects.get_or_create(date_in="2014-12-01", date_out=None, room=room1)[0]
>>> res1.live = False
>>> res1.save()

# Second reservation in same room is created with date_out set to Dec 15th
>>> res2 = Reservation.objects.get_or_create(date_in="2014-12-01", date_out="2014-12-15", room=room1)[0]

# Here I'd expect to have R001 listed as well... this is not the case
>>> Room.available_rooms.available_with_Q("2014-12-16", "")
[<Room: R002>]
>>> Room.available_rooms.available_with_chained_excludes("2014-12-16", "")
[<Room: R002>]

# As a test, when changing "deleted" res1's Room to room2, the manager does return R001
>>> res1.room = room2
>>> res1.save()
>>> Room.available_rooms.available_with_Q("2014-12-16", "")
[<Room: R001>, <Room: R002>]
>>> Room.available_rooms.available_with_chained_excludes("2014-12-16", "")
[<Room: R001>, <Room: R002>]

2 个答案:

答案 0 :(得分:1)

我用你的github项目测试了它,我得到了和你一样的结果。看来,filter正确地将多个相关对象过滤器转换为INNER JOINexclude将其转换为某种子查询,其中每个过滤器(即使在同一个调用中)都会被检查它自己的。

我找到了解决方法,那就是明确创建预订子查询:

elif indate and not outdate:
    ress = Reservation.objects.filter(Q(live=True, date_in__isnull=False), Q(date_out__gt=indate) | Q(date_out__isnull=True))
    rooms = Room.objects.exclude(reservation__in=ress)
etc...

顺便说一下,如果您需要使用filter而不是exclude,以下查询总是相同的,事实上,这就是Django内部所做的事情:

Room.objects.exclude(<some_filter>)
Room.objects.filter(~Q(<some_filter>))

答案 1 :(得分:0)

如果在此时间内已经预订,或者用户希望在已经预订时结帐,则您不想办理入住手续:

第一种情况:R_IN User_IN R_out

第二种情况:R_IN User_IN(房间被认为很忙,所以不再检查)

第三种情况:User_IN R_IN User_OUT

Room.objects.exclude(
    models.Q(reservation__live=False, reservation__date_in__lte=indate, reservation__date_out__gte=indate)
  |
    models.Q(reservation__live=False, reservation__date_in__lte=indate, reservation__date_out__isnull=True)
  |
    models.Q(reservation__live=False, reservation__date_in__gte=indate, reservation__date_in__lte=outdate)
)

如果你需要考虑到没有过期的情况你可以简单地考虑最后一个案例,处理它对你最好的方式,也许因为可能的冲突或强迫用户离开一天而完全删除那个房间在预订之前,您已经有了支票