简体   繁体   中英

Django combine 3 queryset

Good day mates, I am not very familiar with combining querysets into one. How do I achieve my desired result without changing the template design?

Note: I have already asked this question . I just found out that there is such a thing as combining multiple querysets in Django. It is not the same question though.

This is my views.py:

Markingbehaviors = StudentsBehaviorGrades.objects.filter(Teacher=teacher) \
        .filter(Students_Enrollment_Records__in=Students.values_list('id')).values('Grading_Period').distinct('Grading_Period')\
        .order_by('Grading_Period')

Marking = StudentsBehaviorGrades.objects.filter(Teacher=teacher) \
        .filter(Students_Enrollment_Records__in=Students.values_list('id'))

cores = StudentsBehaviorGrades.objects.filter(Teacher=teacher) \
        .filter(Students_Enrollment_Records__in=Students.values_list('id')).values(
        'Marking__Marking',
        'Grading_Behavior__Grading_Behavior__Name',
        'Grading_Behavior__Grading_Behavior__GroupName').distinct(
        'Grading_Behavior__Grading_Behavior__Name') \
        .order_by('Grading_Behavior__Grading_Behavior__Name')

behaviors = StudentsBehaviorGrades.objects.filter(Teacher=teacher) \
        .filter(Students_Enrollment_Records__in=Students.values_list('id')).values(
        'Marking__Marking',
        'Grading_Behavior__Grading_Behavior__Name',
        'Grading_Behavior__Grading_Behavior__GroupName').distinct(
        'Grading_Behavior__Grading_Behavior__GroupName') \
        .order_by('Grading_Behavior__Grading_Behavior__GroupName')

matches = cores.union(Marking,behaviors)

This is my html:

<tr>
    {% for quarter in Markingbehaviors %}
        td class="tdquarter">Q {{quarter.Grading_Period}}</td>
    {% endfor %}
</tr>


{% for match in matches %}
    <tr>
       <td rowspan="2" colspan="4" class="tblcoretitle">{{match.Grading_Behavior__Grading_Behavior__Name}}</td>
    </tr>
    <tr>
       <td colspan="4" class="tblcore">{{match.Grading_Behavior__Grading_Behavior__GroupName}}</td>
       <td class="tblcore">{{match.Marking.Marking}}</td>
    </tr>
{% endfor %}

This is my adminsite view of StudentsBehaviorGrades :

在此处输入图片说明

This is my current result:

在此处输入图片说明

I want result:

在此处输入图片说明

Update

When I tried this in my views.py:

matches = set(itertools.chain(cores, behaviors,Marking))

I received this error:

在此处输入图片说明

mate if you have a better solution or idea please share your answer.

This is how i render

    Marking = StudentsBehaviorGrades.objects.filter(Teacher=teacher) \
        .filter(Students_Enrollment_Records__in=Students.values_list('id'))\
        .order_by('Grading_Behavior__Grading_Behavior__Name')

    cores = StudentsBehaviorGrades.objects.filter(Teacher=teacher) \
        .filter(Students_Enrollment_Records__in=Students.values_list('id')).values(
        'id',
        'Marking__Marking',
        'Grading_Period',
        'Grading_Behavior__Grading_Behavior__Name',
        'Grading_Behavior__Grading_Behavior__GroupName').distinct(
        'id',
        'Grading_Behavior__Grading_Behavior__Name').order_by(
        'id',
        'Grading_Behavior__Grading_Behavior__Name'
        )

    behaviors = StudentsBehaviorGrades.objects.filter(Teacher=teacher) \
        .filter(Students_Enrollment_Records__in=Students.values_list('id')).values(
        'id',
        'Marking__Marking',
        'Grading_Period',
        'Grading_Behavior__Grading_Behavior__Name',
        'Grading_Behavior__Grading_Behavior__GroupName').distinct(
        'id',
        'Grading_Behavior__Grading_Behavior__GroupName').order_by(
        'id',
        'Grading_Behavior__Grading_Behavior__GroupName'
    )

matches = cores.union(Marking,behaviors)

return render(request, 'Homepage/mystudentperreport.html',{ "matches":matches,})

Combining querysets is very straight forward. You have two options when combining which SOMTIMES act like if they're the same.

Models deeply are just raw SQL statements, Combinations in SQL happen with AND or OR , Which exist in python. There're Q objects for more complex look ups. Let's see.

If you have 2 querysets or more.

You can OR or AND them, qs_3 = qs_1 & qs_2 will return both of them concatenated IF THEY ARE OF THE SAME TYPE

Same with |, However, you have different types, That's why Q objects exist.

eg

from django.db.models import Q
my_concatenated_qs = Q(MyModel__my_attr_if_I_want="") & Q(MyAnotherModel)  

| works too.

NOTE: MyModel, MyAnotherModel is not a model instance, It's just the name, not a string too. Just the raw name without quotes.

You can also remove the my_attr_if_I_want , I just wanted to show it's possible to use the __ syntax here.

Q objects are not limited to model queries like I've shown

eg

complex_queryset = MyModel.objects.filter(
    Q(my_attr__startswith='R') & ~Q(my_another_attr__startswith='Z')
    | Q(my_third_attr__startswith='R') & Q(my_fourth_attr__startswith='R')
)

You can also negate querysets

eg

X = True
Y = not X

This is Negation , To negate Q objects (NOT query)

~Q(MyModel__startswith="A") | Q(MyModel__whatever__whatever=[])

Further more, You can read about Q objects here

You want your matches object to a list of matches with a shape like the following:

matches = [{
        "core_value_name": "core_value_1",
        "behaviour_statements": [{
                "name": "behaviour_statement_1",
                "grades": {
                    "q1": "grade_q1",
                    "q2": "grade_q2",
                    "q3": "grade_q3",
                    "q4": "grade_q4"
                }
            },
            {
                "name": "behaviour_statement_2",
                "grades": {
                    "q1": "grade_q1",
                    "q2": "grade_q2",
                    "q3": "grade_q3",
                    "q4": "grade_q4"
                }
            },
            ...
        ]
    }, {
        "core_value_name": "core_value_2",
        "behaviour_statements": [{
                "name": "behaviour_statement_1",
                "grades": {
                    "q1": "grade_q1",
                    "q2": "grade_q2",
                    "q3": "grade_q3",
                    "q4": "grade_q4"
                }
            },
            ...
        ]
    }
    ...
]

Then you can use your template with a minor tweak like so:

{% for match in matches %} 
{% set first_row=True %} 
{% for behaviour_statement in match.behaviour_statements %} 
{% if first_row %}
<tr>
  <td rowspan="{% len(match.behaviour_statements) %}" colspan="4" class="tblcoretitle">{{match.core_value_name}}</td>
{% set first_row=False %} 
{% elif %}
<tr>
{% endif %}
  <td colspan="4" class="tblcore">{{behaviour_statement.name}}</td>
  <td class="tblcore">{{behaviour_statement.grades.q1}}</td>
  <td class="tblcore">{{behaviour_statement.grades.q2}}</td>
  <td class="tblcore">{{behaviour_statement.grades.q3}}</td>
  <td class="tblcore">{{behaviour_statement.grades.q4}}</td>
</tr>
{% endfor %} 
{% endfor %}

This gets you your desired result.

There is quite a simple solution for your problem, and it's not in the queryset area (kind of).

From what you have told us, you are trying to group every result by, in order:

  1. Grading_Behavior__Grading_Behavior__GroupName (group)
  2. Grading_Behavior__Grading_Behavior__Name (name)
  3. Grading_Period (quarter)

I think the queryset should remain very simple, and then you can do the huge part in your template.

So first things first, get all your marking with the order you want, beginning with groups and going more into detail (eg: core -> name -> quarter). From what I've understood, the 3-item list above should be quite close.

Then in your template, use the magic tag {% ifchanged %} (see docs ), it does wonders when having this kind of problem with tables. But, I will give you that: the template may be quite hard to read.

Anyway, it should look like this:

<table>
<thead>
  {# Your table headings, quite static, with maybe a loop for quarters #}
</thead>
<tbody>
{% for match in matches %}
    {% ifchanged match.Grading_Behavior__Grading_Behavior__Name %}
        {#  We need a need row for each new name #}
        {% if not forloop.first %}
            {#  Close the previous one if not first loop #}
            </tr>
        {% endif %}
        <tr>
    {% endifchanged %}

    {# First, your group name  #}
    {% ifchanged match.Grading_Behavior__Grading_Behavior__GroupName %}
        <td rowspan="2" colspan="4" class="tblcoretitle">
            {{ match.Grading_Behavior__Grading_Behavior__GroupName }}
       </td>
    {% endif %}

    {# First, then the name  #}
    {% ifchanged match.Grading_Behavior__Grading_Behavior__Name %}
        <td colspan="4" class="tblcore">
            {{ match.Grading_Behavior__Grading_Behavior__Name }}
       </td>
    {% endif %}

    {# Then your marking #}
    <td class="tblcore">{{match.Marking.Marking}}</td>

    {% if forloop.last %}
        {# Close the last row in the last loop #}
        </tr>
    {% endif %}
{% endfor %}
</tbody>
</table>

It may not be quite right, but once you get the hang of ifchanged, and you focus on the markup that it renders, you should be ok.

Also don't forget the variables you can use in a {% for %} , see docs

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM