2

django nonrelのドキュメントには、「複数のクエリ(JOIN、select_related()など)の結果をマージするためのコードを手動で作成する必要があります」と記載されています。

関連データを手動で追加するスニペットを誰かに教えてもらえますか?@nickjohnsonには、ストレートAppEngineモデルでこれを行う方法を示す優れた投稿がありますが、私はdjango-nonrelを使用しています。

私の特定の用途では、関連するユーザーモデルでUserProfilesを取得しようとしています。これは2つの単純なクエリであり、データを照合する必要があります。

ただし、django-nonrelを使用すると、クエリセット内の結果ごとに新しいクエリが実行されます。「select_related」のような方法で関連アイテムにアクセスするにはどうすればよいですか?

これを試しましたが、期待どおりに機能しないようです。rpc statsを見ると、表示されている各アイテムに対してクエリが実行されているようです。

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管理者のchangelist_viewの効率を改善しようとしていました。上記のselect_relatedロジックは、外部キーが「display_list」にある場合でも、結果セットの各行に対して追加のクエリを生成しているように見えました。しかし、私はそれを別の何かにたどりました。上記のロジックは複数のクエリを生成しません(ただし、ニックジョンソンの方法をより厳密に模倣すると、よりきれいに見えます)。

問題は、ChangeListメソッド内の117行目のdjango.contrib.admin.views.mainに次のコードがあることですresult_list = self.query_set._clone()。したがって、管理者でクエリセットを適切にオーバーライドして関連するものを選択していても、このメソッドは、「関連する選択」用に追加したモデルの属性を保持しないクエリセットのクローンをトリガーしていました。私が始めたときよりもさらに非効率的なページの読み込み。

まだ何をすべきかわからないが、関連するものを選択するコードは問題ない。

4

1 に答える 1

1

私は自分の質問に答えるのは好きではありませんが、答えは他の人を助けるかもしれません。

上記にリンクされているNickJohnsonのソリューションに完全に基づいて、クエリセットの関連アイテムを取得する私のソリューションを次に示します。


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 に答える