我怎样才能模仿“select_lated”使用 google-appengine 和 django-nonrel?

发布于 2024-12-01 02:57:24 字数 1617 浏览 1 评论 0原文

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

有人可以向我指出手动添加相关数据的任何片段吗? @nickjohnson 有一篇 优秀帖子 展示了如何执行此操作使用直接的 AppEngine 模型,但我使用的是 django-nonrel。

对于我的特殊用途,我试图获取 UserProfiles 及其相关的用户模型。这应该只是两个简单的查询,然后匹配数据。

但是,使用 django-nonrel,将为查询集中的每个结果触发一个新查询。如何以“select_相关”方式访问相关项目?

我已经尝试过这个,但它似乎没有像我预期的那样工作。查看 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 管理中的changelist_view 的效率。当外键位于我的“display_list”中时,上面的 select_lated 逻辑似乎仍在为结果集中的每一行生成额外的查询。然而,我追踪到了不同的东西。上面的逻辑不会产生多个查询(但是如果你更仔细地模仿 Nick Johnson 的方式,它看起来会更漂亮)。

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

还不知道该怎么办,但是选择相关内容的代码就很好。

django nonrel's documentation states: "you have to manually write code for merging the results of multiple queries (JOINs, select_related(), etc.)".

Can someone point me to any snippets that manually add the related data? @nickjohnson has an excellent post showing how to do this with the straight AppEngine models, but I'm using django-nonrel.

For my particular use I'm trying to get the UserProfiles with their related User models. This should be just two simple queries, then match the data.

However, using django-nonrel, a new query gets fired off for each result in the queryset. How can I get access to the related items in a 'select_related' sort of way?

I've tried this, but it doesn't seem to work as I'd expect. Looking at the rpc stats, it still seems to be firing a query for each item displayed.

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

UPDATE:
Ick... I figured out what my issue was.

I was trying to improve the efficiency of the changelist_view in the django admin. It seemed that the select_related logic above was still producing additional queries for each row in the results set when a foreign key was in my 'display_list'. However, I traced it down to something different. The above logic does not produce multiple queries (but if you more closely mimic Nick Johnson's way it will look a lot prettier).

The issue is that in django.contrib.admin.views.main on line 117 inside the ChangeList method there is the following code: result_list = self.query_set._clone(). So, even though I was properly overriding the queryset in the admin and selecting the related stuff, this method was triggering a clone of the queryset which does NOT keep the attributes on the model that I had added for my 'select related', resulting in an even more inefficient page load than when I started.

Not sure what to do about it yet, but the code that selects related stuff is just fine.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

蛮可爱 2024-12-08 02:57:24

我不喜欢回答自己的问题,但答案可能对其他人有帮助。

这是我的解决方案,它将完全基于上面链接的 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

I don't like answering my own question, but the answer might help others.

Here is my solution that will get related items on a queryset based entirely on Nick Johnson's solution linked above.


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')

To be able to modify the queryset on the admin to make use of the select related we have to jump through a couple hoops. Here is what I've done. The only thing changed on the 'get_results' method of the 'AppEngineRelatedChangeList' is that I removed the self.query_set._clone() and just used self.query_set instead.


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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文