كيفية الجمع بين 2 أو أكثر من querysets في جانغو الرأي ؟

StackOverflow https://stackoverflow.com/questions/431628

سؤال

أنا أحاول أن أبني البحث عن جانغو موقع أنا وبناء في البحث وأنا أبحث في 3 نماذج مختلفة.والحصول على ترقيم الصفحات في نتيجة البحث قائمة أود أن استخدام عام object_list لعرض النتائج.ولكن للقيام بذلك يجب أن دمج 3 querysets في واحد.

كيف يمكن أن أفعل ذلك ؟ لقد حاولت هذه:

result_list = []            
page_list = Page.objects.filter(
    Q(title__icontains=cleaned_search_term) | 
    Q(body__icontains=cleaned_search_term))
article_list = Article.objects.filter(
    Q(title__icontains=cleaned_search_term) | 
    Q(body__icontains=cleaned_search_term) | 
    Q(tags__icontains=cleaned_search_term))
post_list = Post.objects.filter(
    Q(title__icontains=cleaned_search_term) | 
    Q(body__icontains=cleaned_search_term) | 
    Q(tags__icontains=cleaned_search_term))

for x in page_list:
    result_list.append(x)
for x in article_list:
    result_list.append(x)
for x in post_list:
    result_list.append(x)

return object_list(
    request, 
    queryset=result_list, 
    template_object_name='result',
    paginate_by=10, 
    extra_context={
        'search_term': search_term},
    template_name="search/result_list.html")

ولكن هذا لا يعمل أحصل على خطأ عند محاولة استخدام هذه القائمة في عام عرض.قائمة مفقود استنساخ السمة.

أحد يعرف كيف يمكن دمج القوائم الثلاث ، page_list, article_list و post_list?

هل كانت مفيدة؟

المحلول

ووصل إلى مجموعات طلبات البحث في قائمة هو النهج أبسط. إذا كان سيتم ضرب قاعدة بيانات لجميع مجموعات طلبات البحث على أي حال (على سبيل المثال، لأن النتيجة يجب أن تكون مرتبة)، وهذا لن تضيف تكلفة إضافية.

from itertools import chain
result_list = list(chain(page_list, article_list, post_list))

وعن طريق itertools.chain أسرع من حلقات كل قائمة وإلحاق العناصر واحدا تلو الآخر، حيث يتم تنفيذ itertools في C. كما أنها تستهلك ذاكرة أقل من تحويل كل مجموعة طلبات البحث في قائمة قبل وصل.

والآن فمن الممكن لفرز القائمة الناتجة على سبيل المثال حسب التاريخ (على النحو المطلوب في تعليق HASEN ي لإجابة أخرى). وظيفة sorted() يقبل مريح مولد وإرجاع القائمة:

result_list = sorted(
    chain(page_list, article_list, post_list),
    key=lambda instance: instance.date_created)

إذا كنت تستخدم بايثون 2.4 أو في وقت لاحق، يمكنك استخدام attrgetter بدلا من امدا. أذكر أني قرأت عن كونها أسرع، ولكن لم أكن أرى فرقا ملحوظا سرعة للحصول على قائمة مليون البند.

from operator import attrgetter
result_list = sorted(
    chain(page_list, article_list, post_list),
    key=attrgetter('date_created'))

نصائح أخرى

جرب هذا:

matches = pages | articles | posts

فإنه يحتفظ كل وظائف querysets التي هي لطيفة إذا كنت ترغب في order_by أو ما شابه ذلك.

يرجى ملاحظة ما يلي: هذا لا يعمل على querysets من اثنين من نماذج مختلفة.

ذات الصلة ، من أجل خلط querysets من الطراز نفسه ، أو مجالات مشابهة من عدد قليل من النماذج ، بدءا من جانغو 1.11 a qs.union() طريقة كما تتوفر أيضا:

union()

union(*other_qs, all=False)

الجديد في جانغو 1.11.يستخدم SQL الاتحاد مشغل الجمع بين نتائج اثنين أو أكثر من QuerySets.على سبيل المثال:

>>> qs1.union(qs2, qs3)

الاتحاد المشغل يختار فقط قيم واضحة بشكل افتراضي.للسماح القيم المكررة, استخدام جميع=True الحجة.

(الاتحاد) تقاطع () ، و الفرق() عودة نموذج حالات النوع الأول QuerySet حتى لو كانت الحجج QuerySets من نماذج أخرى.يمر نماذج مختلفة يعمل طالما حدد القائمة هو نفسه في جميع QuerySets (على الأقل من أنواع الأسماء لا المسألة ما دامت الأنواع في نفس الترتيب).

وبالإضافة إلى ذلك الحد فقط, تعويض, والنظام من قبل (أيتشريح ، order_by()) مسموح على الناتج QuerySet.أبعد من ذلك ، قواعد البيانات وضع قيود على ما هي العمليات المسموح بها في الجمع بين الاستعلامات. على سبيل المثال, معظم قواعد البيانات لا تسمح الحد أو تعويض في جنبا إلى جنب الاستعلامات.

https://docs.djangoproject.com/en/1.11/ref/models/querysets/#django.db.models.query.QuerySet.union

ويمكنك استخدام فئة QuerySetChain أدناه. عند استخدامه مع paginator جانغو، فإنه ينبغي أن تصل فقط قاعدة البيانات مع الاستعلامات COUNT(*) لجميع مجموعات طلبات واستفسارات SELECT() فقط لأولئك الذين مجموعات طلبات يتم عرضها على الصفحة الحالية السجلات.

ملحوظة التي تحتاج إلى تحديد template_name= في حالة استخدام QuerySetChain مع إطلالة عامة، حتى لو كانت مجموعات طلبات بالسلاسل كل استخدام نفس النموذج.

from itertools import islice, chain

class QuerySetChain(object):
    """
    Chains multiple subquerysets (possibly of different models) and behaves as
    one queryset.  Supports minimal methods needed for use with
    django.core.paginator.
    """

    def __init__(self, *subquerysets):
        self.querysets = subquerysets

    def count(self):
        """
        Performs a .count() for all subquerysets and returns the number of
        records as an integer.
        """
        return sum(qs.count() for qs in self.querysets)

    def _clone(self):
        "Returns a clone of this queryset chain"
        return self.__class__(*self.querysets)

    def _all(self):
        "Iterates records in all subquerysets"
        return chain(*self.querysets)

    def __getitem__(self, ndx):
        """
        Retrieves an item or slice from the chained set of results from all
        subquerysets.
        """
        if type(ndx) is slice:
            return list(islice(self._all(), ndx.start, ndx.stop, ndx.step or 1))
        else:
            return islice(self._all(), ndx, ndx+1).next()

في المثال الخاص بك، فإن الاستخدام على النحو التالي:

pages = Page.objects.filter(Q(title__icontains=cleaned_search_term) |
                            Q(body__icontains=cleaned_search_term))
articles = Article.objects.filter(Q(title__icontains=cleaned_search_term) |
                                  Q(body__icontains=cleaned_search_term) |
                                  Q(tags__icontains=cleaned_search_term))
posts = Post.objects.filter(Q(title__icontains=cleaned_search_term) |
                            Q(body__icontains=cleaned_search_term) | 
                            Q(tags__icontains=cleaned_search_term))
matches = QuerySetChain(pages, articles, posts)

وثم استخدم matches مع paginator مثل الذي استخدمته result_list في المثال الخاص بك.

وقدم وحدة itertools في بيثون 2.3، لذلك يجب أن تكون متوفرة في جميع إصدارات بيثون جانغو يعمل على.

والجانب السلبي الكبير من النهج الحالي هو عدم فاعليتها مع مجموعات نتيجة بحث كبيرة، لديك لهدم نتيجة كامل مجموعة من قاعدة البيانات في كل مرة، حتى ولو كنت تنوي فقط لعرض صفحة واحدة من النتائج.

في أجل سحب فقط أسفل الكائنات التي تحتاج بالفعل من قاعدة البيانات، لديك لاستخدام ترقيم الصفحات على مجموعة طلبات البحث، وليس القائمة. إذا قمت بذلك، بفك فعلا شرائح لمجموعة طلبات البحث قبل تنفيذ الاستعلام، وبالتالي فإن الاستعلام SQL سيستخدم إزاحة و LIMIT إلى فقط الحصول على السجلات التي ستعرض في الواقع. ولكنك لا تستطيع أن تفعل هذا إلا إذا كنت يمكن أن الالزام بحثك في استعلام واحد بطريقة أو بأخرى.

وبالنظر إلى أن كل ثلاثة من نماذج لديكم عنوان والجسم المجالات، لماذا لا تستخدم <لأ href = "http://docs.djangoproject.com/en/dev/topics/db/models/#model-inheritance" يختلط = "noreferrer"> نموذج الميراث ؟ فقط لدينا جميع النماذج الثلاثة ترث من سلف مشترك لها عنوان والجسم، وإجراء البحث كما استعلام واحد على نموذج السلف.

في حال كنت ترغب في سلسلة الكثير من مجموعات طلبات البحث، حاول هذا:

from itertools import chain
result = list(chain(*docs))

وحيث: مستندات عبارة عن قائمة من مجموعات طلبات

DATE_FIELD_MAPPING = {
    Model1: 'date',
    Model2: 'pubdate',
}

def my_key_func(obj):
    return getattr(obj, DATE_FIELD_MAPPING[type(obj)])

And then sorted(chain(Model1.objects.all(), Model2.objects.all()), key=my_key_func)

مقتبسة من https://groups.google.com/forum/#!الموضوع/جانغو-users/6wUNuJa4jVw.انظر أليكس جاينور

وهنا فكرة ... مجرد هدم صفحة كاملة واحدة من النتائج من كل من الثلاثة ثم طرد 20 أقلها مفيدة ... وهذا يلغي مجموعات طلبات الكبيرة وبهذه الطريقة يمكنك التضحية قليلا أداء فقط بدلا من الكثير

المتطلبات: Django==2.0.2, django-querysetsequence==0.8

في حال كنت ترغب في الجمع بين querysets و لا يزال يخرج مع QuerySet, ، قد ترغب في التحقق جانغو-queryset-تسلسل.

لكن ملاحظة واحدة حول هذا الموضوع.يستغرق سوى يومين querysets كما أنه حجة.ولكن مع الثعبان reduce يمكنك دائما تطبيق متعددة querysets.

from functools import reduce
from queryset_sequence import QuerySetSequence

combined_queryset = reduce(QuerySetSequence, list_of_queryset)

و هذا الأمر.أدناه هو الحالة التي صادفت وكيف يعمل list comprehension, reduce و django-queryset-sequence

from functools import reduce
from django.shortcuts import render    
from queryset_sequence import QuerySetSequence

class People(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    mentor = models.ForeignKey('self', null=True, on_delete=models.SET_NULL, related_name='my_mentees')

class Book(models.Model):
    name = models.CharField(max_length=20)
    owner = models.ForeignKey(Student, on_delete=models.CASCADE)

# as a mentor, I want to see all the books owned by all my mentees in one view.
def mentee_books(request):
    template = "my_mentee_books.html"
    mentor = People.objects.get(user=request.user)
    my_mentees = mentor.my_mentees.all() # returns QuerySet of all my mentees
    mentee_books = reduce(QuerySetSequence, [each.book_set.all() for each in my_mentees])

    return render(request, template, {'mentee_books' : mentee_books})

هذا يمكن أن يتحقق من خلال طريقتين إما.

1 طريقة للقيام بذلك

استخدام الاتحاد مشغل queryset | اتخاذ اتحاد اثنين من queryset.إذا كان كل queryset ينتمي إلى نفس نموذج / نموذج واحد من الممكن الجمع بين querysets باستخدام الاتحاد المشغل.

على سبيل المثال

pagelist1 = Page.objects.filter(
    Q(title__icontains=cleaned_search_term) | 
    Q(body__icontains=cleaned_search_term))
pagelist2 = Page.objects.filter(
    Q(title__icontains=cleaned_search_term) | 
    Q(body__icontains=cleaned_search_term))
combined_list = pagelist1 | pagelist2 # this would take union of two querysets

2 طريقة للقيام بذلك

طريقة واحدة لتحقيق الجمع بين العملية بين اثنين queryset هو استخدام itertools سلسلة وظيفة.

from itertools import chain
combined_results = list(chain(pagelist1, pagelist2))

وهذه وظيفة متكررة يسلسل مجموعة من مجموعات طلبات البحث إلى مجموعة طلبات البحث واحدة.

def merge_query(ar):
    if len(ar) ==0:
        return [ar]
    while len(ar)>1:
        tmp=ar[0] | ar[1]
        ar[0]=tmp
        ar.pop(1)
        return ar
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top