如何基于外部查询在子查询中创建条件?

时间:2017-09-15 12:40:48

标签: python django django-models django-queryset

我在Airbnb上工作,就像app一样。我有Flat模型和Price模型,价格具有不同类型的优先级,允许客户创建灵活的价格范围。

我坚持使用一个复杂的查询。此查询应按日期范围返回Flats并计算此范围的价格。

这是模特的一部分:

class Flat(models.Model):
    prices = models.ManyToManyField(
        'Price'
        related_name='flats'
    )
    price = models.PositiveIntegerField()
    ...

class Price(models.Model):
    PRIORITY_CHOICES = ((i, i) for i in range(1, 6))

    priority = PositiveIntegerField(choices=PRIORITY_CHOICES)
    start_date = models.DateField()
    end_date = models.DateField()
    price = models.PositiveIntegerField()

到目前为止,我已经弄明白了如何在每天注释具有更高优先级的价格。我为Flat写了一个自定义管理器:

class FlatManager(models.Manager):

    def with_prices(self, start, end):
        days = get_days(start, end) # utils function return list of days
        prices = {}
        for day in days:
            prices[str(day)] = models.Subquery(
            Price.objects.filter(
                flats=models.OuterRef('pk')).
                filter(start_date__lte=day, end_date__gte=day).
                order_by('-priority').
                values('price')[:1]
        )
        return self.annotate(**price_dict)

这是我的问题

Flat中的某些日期可以没有价格区块,因此Flat有自己的price字段,用于客户不会使用灵活价格的情况。我不知道在查询中需要添加条件运算符的位置。如果我将其添加到Price子查询中,那么由于嵌套,我无法使用Outref('price')。 当我解决它时,我认为计算聚合值的总和对我来说不会那么复杂。

请至少给出一些提示,我真的坚持了下来。

1 个答案:

答案 0 :(得分:0)

我将条件移动到主查询,检查子查询是否返回None,然后使用Flat模型字段。
对于一个date值,它看起来像这样:

day = # some date
price = Price.objects.filter(
            flats=models.OuterRef('pk')).
            filter(start_date__lte=day, end_date__gte=day).
            order_by('-priority').
            values('price')[:1])
Flat.objects.annotate(price_block=Subquery(price).annotate(
    derived_price = Case(
                      When(price_block__isnull=True, then='price'),
                      default=F('price_block')
    ))

如果子查询返回derived_price,那么price值将包含来自Price模型的price值或来自Flat模型的None值/> 但在我的情况下,我有一系列日期,所以我需要每个日期的子查询和条件。此外,我需要所有注释价格的总和 我是这样做的:

class FlatManager(models.Manager):

    def _construct_conditions(self, keys):
        # Function construct conditions 
        # like in previous example for each date in range
        annotations = {}
        for key in keys:
            condition = {'{}__isnull'.format(key): True, 'then': 'price'}
            annotations['derived_{}'.format(key)] = Case(When(**condition), default=F(key))
        return annotations

    def _add_prices(self, keys):
        values = [F('derived_{}'.format(key)) for key in keys]
        return sum(values)

    def with_prices(self, start, end):
        days = get_days(start, end) # utils function return list of days
        prices = {}
        for num, day in enumerate(days):
            prices['price_{}'.format(num)] = Subquery(
                Price.objects.filter(
                flats=OuterRef('pk')).
                filter(start_date__lte=day, end_date__gte=day).
                order_by('-priority').
                values('weekend_price' if day.weekday() in [4, 5] else 'price')[:1] 
                # also add a condition for weekend price
        )
        return (self.annotate(**prices).
                annotate(**self._construct_conditions(prices.keys())).
                annotate(sum=self._add_prices(prices.keys()))
                )