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

问题描述 投票:1回答:1

我正在Django中克隆airbnb房间注册系统。我有一个基于类的视图(HTTP get方法),该视图根据各种查询字符串键值选项过滤存储在数据库中的房间,并返回这些房间。通过查询字符串提供的过滤器选项为:

    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()对象。与已经提供的check_in_date和check_out_date冲突且已预订日期和主机选择的不可用日期(“ blockeddate”)的房间将被过滤掉。将所有Q()表达式存储在称为“查询”的变量中之后,我将“查询”作为参数传递给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)
python mysql django aggregate annotate
1个回答
0
投票

检查django-filters软件包:django-filters

它提供FilterSet类,以声明的方式涵盖了多个字段过滤的所有逻辑。


0
投票

老实说,假设没有适当的索引,而无需添加高速缓存表等。

一旦有了查询集,就可以print(queryset.query)获取SQL并在EXPLAIN ...中进行排序。 (Django Debug Toolbar插件也可以执行此操作。)

您可以将代码干燥一下:

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
© www.soinside.com 2019 - 2024. All rights reserved.