在查询字符串评估中使用 Q() 链接重构多个 if 条件 Python/Django

Refactoring multiple if conditions with Q() chaining in querystring evaluation Python/Django

我正在使用 Django 克隆 airbnb 房间注册系统。我有一个基于 class 的视图(HTTP get 方法),它根据各种查询字符串键值选项和 returns 过滤存储在数据库中的房间。通过查询字符串提供的过滤器选项是:

    location        = request.GET.get('location')
    adults          = int(request.GET.get('adults', 0))
    children        = int(request.GET.get('children', 0))
    infants         = request.GET.get('infants', 0)
    min_cost        = float(request.GET.get('min_cost', 0))
    max_cost        = float(request.GET.get('max_cost', sys.maxsize))
    property_type   = request.GET.get('property_type', None)
    place_type      = request.GET.get('place_type', None)
    check_in        = request.GET.get('checkin', None)
    check_in_date   = datetime.datetime.strptime(check_in, '%Y-%m-%d') if check_in else None
    check_out       = request.GET.get('checkout', None)
    check_out_date  = datetime.datetime.strptime(check_out, '%Y-%m-%d') if check_out else None
    min_beds        = request.GET.get('min_beds', None)
    min_bedrooms    = request.GET.get('min_bedrooms', None)
    min_baths       = request.GET.get('min_baths', None)
    amenities       = request.GET.getlist('amenities', None)
    languages       = request.GET.getlist('languages', None)

我决定使用 &= 操作将所有过滤器表达式存储为 Q() 对象。已预订日期和房东选择的不可用日期 ('blockeddate') 与提供的 check_in_date 和 check_out_date 冲突的房间将被过滤掉。在将所有 Q() 表达式存储在名为 'queries' 的变量中后,我将 'queries' 作为参数传递给 Room.objects.filter() 函数。 'min_beds'、'min_bedrooms' 和 'min_baths' 选项在初始过滤后进行评估,以便我可以在过滤后的查询集上执行 annotate() 函数。

以下代码有效,但我想知道在数据库调用和时间复杂度方面是否有更简洁有效的过滤方法。也许使用 prefetch_related()?到目前为止,似乎有太多重复的 if 语句,但我想不出更好的方法来评估 None 查询字符串选项的情况。

queries = (
        Q(address__icontains = location) & 
        Q(max_capacity__gte = adults + children) &
        Q(price__range = (min_cost, max_cost))
        )
    if check_in_date and check_out_date:
        queries &= (
            ~Q(blockeddate__start_date__range = (check_in_date, check_out_date)) &
            ~Q(blockeddate__end_date__range = (check_in_date, check_out_date)) &
            ~Q(booking__start_date__range = (check_in_date, check_out_date)) &
            ~Q(booking__end_date__range = (check_in_date, check_out_date))
        )
    if property_type:
        queries &= Q(property_type__name = property_type)
    if place_type:
        queries &= Q(place_type__name = place_type)
    if amenities:
        q_expressions = [Q(amenities__name = amenity) for amenity in amenities]
        for expression in q_expressions:
            queries &= expression
    if languages:
        q_expressions = [Q(host__userlanguage__language__name = language) for language in languages]
        for expression in q_expressions:
            queries &= expression 

    room_qs = Room.objects.filter(queries)
    if min_beds:
        room_qs = room_qs.annotate(num_beds=Sum('bedroom__bed__quantity')).filter(num_beds__gte = min_beds)
    if min_bedrooms:
        room_qs = room_qs.annotate(num_bedrooms=Count('bedroom')).filter(num_bedrooms__gte = min_bedrooms)
    if min_baths:
        room_qs = room_qs.annotate(num_baths=Count('bath')).filter(num_baths__gte = min_baths)

查看 django-filters 包: django-filters

它提供 FilterSet 类 以声明的方式包含按多个字段过滤的所有逻辑。

老实说,如果不添加缓存表等,我就无法看到索引已就位。

获得查询集后,您可以print(queryset.query) 获取SQL 并将结果放入EXPLAIN ...。 (Django 调试工具栏插件也可以做到这一点。)

你可以稍微干掉代码:

from django.db.models import Q


def list_q(queries, field, values):
    for value in values:
        queries &= Q(**{field: value})
    return queries


def annotation_filter(qs, name, aggregation, op, value):
    if value:
        return qs.annotate(**{name: aggregation}).filter(
            **{f"{name}__{op}": value}
        )
    return qs


def q(...):
    queries = (
        Q(address__icontains=location)
        & Q(max_capacity__gte=adults + children)
        & Q(price__range=(min_cost, max_cost))
    )
    if check_in_date and check_out_date:
        date_range = (check_in_date, check_out_date)
        queries &= (
            ~Q(blockeddate__start_date__range=date_range)
            & ~Q(blockeddate__end_date__range=date_range)
            & ~Q(booking__start_date__range=date_range)
            & ~Q(booking__end_date__range=date_range)
        )
    if property_type:
        queries &= Q(property_type__name=property_type)
    if place_type:
        queries &= Q(place_type__name=place_type)
    queries = list_q(queries, "amenities__name", amenities)
    queries = list_q(
        queries, "host__userlanguage__language__name", languages
    )
    room_qs = Room.objects.filter(queries)
    room_qs = annotation_filter(
        room_qs, "num_beds", Sum("bedroom__bed__quantity"), "gte", min_beds,
    )
    room_qs = annotation_filter(
        room_qs, "num_bedrooms", Count("bedroom"), "gte", min_bedrooms,
    )
    room_qs = annotation_filter(
        room_qs, "num_baths", Count("bath"), "gte", min_baths
    )
    return room_qs