我正在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)
检查django-filters
软件包:django-filters
它提供FilterSet
类,以声明的方式涵盖了多个字段过滤的所有逻辑。
老实说,假设没有适当的索引,而无需添加高速缓存表等。
一旦有了查询集,就可以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