如何使用 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 [重复]

如何在 React 和 TypeScript 项目中使用 types.d.ts 文件

如何使用 D-Bus 和 Python 3 读取通知

如何使用 automake 安装 D 库的 .d 文件?

max()如何使用d.get?