如何使用 google-appengine 和 django-nonrel 模仿“select_related”?
Posted
技术标签:
【中文标题】如何使用 google-appengine 和 django-nonrel 模仿“select_related”?【英文标题】:How can I mimic 'select_related' using google-appengine and django-nonrel? 【发布时间】:2011-10-31 20:23:21 【问题描述】:django nonrel 的documentation 声明:“您必须手动编写代码来合并多个查询(JOIN、select_related() 等)的结果”。
有人可以指出任何手动添加相关数据的 sn-ps 吗? @nickjohnson 有一个 excellent post 展示了如何使用直接的 AppEngine 模型执行此操作,但我使用的是 django-nonrel。
对于我的特殊用途,我正在尝试获取 UserProfiles 及其相关的用户模型。这应该只是两个简单的查询,然后匹配数据。
但是,使用 django-nonrel,查询集中的每个结果都会触发一个新查询。如何以“select_related”的方式访问相关项目?
我已经尝试过了,但它似乎没有像我预期的那样工作。查看 rpc 统计信息,它似乎仍在为每个显示的项目触发查询。
all_profiles = UserProfile.objects.all()
user_pks = set()
for profile in all_profiles:
user_pks.add(profile.user_id) # a way to access the pk without triggering the query
users = User.objects.filter(pk__in=user_pks)
for profile in all_profiles:
profile.user = get_matching_model(profile.user_id, users)
def get_matching_model(key, queryset):
"""Generator expression to get the next match for a given key"""
try:
return (model for model in queryset if model.pk == key).next()
except StopIteration:
return None
更新: 咳……我知道我的问题是什么了。
我试图提高 django admin 中 changelist_view 的效率。当外键在我的“display_list”中时,上面的 select_related 逻辑似乎仍在为结果集中的每一行生成额外的查询。但是,我将其追溯到不同的东西。上面的逻辑不会产生多个查询(但如果你更接近地模仿 Nick Johnson 的方式,它看起来会更漂亮)。
问题是在 django.contrib.admin.views.main 的 ChangeList 方法内的第 117 行有以下代码:result_list = self.query_set._clone()
。所以,即使我在管理员中正确地覆盖了查询集并选择了相关的东西,这个方法会触发查询集的克隆,它不会保留我为“选择相关”添加的模型上的属性,导致页面加载效率比我开始时还要低。
还不确定该怎么做,但是选择相关内容的代码就可以了。
【问题讨论】:
【参考方案1】:我不喜欢回答自己的问题,但答案可能对其他人有所帮助。
这是我的解决方案,它将完全基于上面链接的 Nick Johnson 的解决方案在查询集中获取相关项目。
from collections import defaultdict
def get_with_related(queryset, *attrs):
"""
Adds related attributes to a queryset in a more efficient way
than simply triggering the new query on access at runtime.
attrs must be valid either foreign keys or one to one fields on the queryset model
"""
# Makes a list of the entity and related attribute to grab for all possibilities
fields = [(model, attr) for model in queryset for attr in attrs]
# we'll need to make one query for each related attribute because
# I don't know how to get everything at once. So, we make a list
# of the attribute to fetch and pks to fetch.
ref_keys = defaultdict(list)
for model, attr in fields:
ref_keys[attr].append(get_value_for_datastore(model, attr))
# now make the actual queries for each attribute and store the results
# in a dict of pk: model for easy matching later
ref_models =
for attr, pk_vals in ref_keys.items():
related_queryset = queryset.model._meta.get_field(attr).rel.to.objects.filter(pk__in=set(pk_vals))
ref_models[attr] = dict((x.pk, x) for x in related_queryset)
# Finally put related items on their models
for model, attr in fields:
setattr(model, attr, ref_models[attr].get(get_value_for_datastore(model, attr)))
return queryset
def get_value_for_datastore(model, attr):
"""
Django's foreign key fields all have attributes 'field_id' where
you can access the pk of the related field without grabbing the
actual value.
"""
return getattr(model, attr + '_id')
为了能够在管理员上修改查询集以使用相关的选择,我们必须跳过几个环节。这是我所做的。 'AppEngineRelatedChangeList' 的 'get_results' 方法唯一改变的是我删除了 self.query_set._clone() 并改用了 self.query_set。
class UserProfileAdmin(admin.ModelAdmin):
list_display = ('username', 'user', 'paid')
select_related_fields = ['user']
def get_changelist(self, request, **kwargs):
return AppEngineRelatedChangeList
class AppEngineRelatedChangeList(ChangeList):
def get_query_set(self):
qs = super(AppEngineRelatedChangeList, self).get_query_set()
related_fields = getattr(self.model_admin, 'select_related_fields', [])
return get_with_related(qs, *related_fields)
def get_results(self, request):
paginator = self.model_admin.get_paginator(request, self.query_set, self.list_per_page)
# Get the number of objects, with admin filters applied.
result_count = paginator.count
# Get the total number of objects, with no admin filters applied.
# Perform a slight optimization: Check to see whether any filters were
# given. If not, use paginator.hits to calculate the number of objects,
# because we've already done paginator.hits and the value is cached.
if not self.query_set.query.where:
full_result_count = result_count
else:
full_result_count = self.root_query_set.count()
can_show_all = result_count self.list_per_page
# Get the list of objects to display on this page.
if (self.show_all and can_show_all) or not multi_page:
result_list = self.query_set
else:
try:
result_list = paginator.page(self.page_num+1).object_list
except InvalidPage:
raise IncorrectLookupParameters
self.result_count = result_count
self.full_result_count = full_result_count
self.result_list = result_list
self.can_show_all = can_show_all
self.multi_page = multi_page
self.paginator = paginator
【讨论】:
以上是关于如何使用 google-appengine 和 django-nonrel 模仿“select_related”?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 webpack 生成 d.ts 和 d.ts.map 文件?
如何使用 Java 和 PostgreSQL 执行 \d tablename [重复]