2

django nonrel 的文档指出:“您必须手动编写代码来合并多个查询的结果(JOIN、select_related() 等)”。

有人可以指出我手动添加相关数据的任何片段吗?@nickjohnson 有一篇很棒的文章展示了如何使用直接的 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

更新: Ick...我弄清楚我的问题是什么。

我试图提高 django admin 中 changelist_view 的效率。当外键在我的“display_list”中时,上面的 select_related 逻辑似乎仍在为结果集中的每一行生成额外的查询。但是,我将其追溯到不同的东西。上面的逻辑不会产生多个查询(但如果你更接近地模仿尼克约翰逊的方式,它看起来会更漂亮)。

问题是在 ChangeList 方法内第 117 行的 django.contrib.admin.views.main 中有以下代码:result_list = self.query_set._clone(). 所以,即使我在管理员中正确地覆盖了查询集并选择了相关的东西,这个方法会触发查询集的克隆,它不会保留我为“选择相关”添加的模型上的属性,导致比我开始时更低效的页面加载。

还不知道该怎么做,但是选择相关内容的代码就可以了。

4

1 回答 1

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
于 2011-08-27T01:56:51.923 回答