0

I am using Django, PyCharm 2018.3.7, Python 3.7, and Postgres.

Unfortunately, I do not understand how to display on one screenpage (Django form) the results for 2 separate querysets that need the same ID passed by a dynamic filter request (GET). There is a table/model (ASSIGNMENTS) with a One-To-Many relationship to another table/model (PRODUCTS). The ID being passed is the ASSIGNMENTS.ID, which is the PK of ASSIGNMENTS and the FK of PRODUCTS.

I am not trying to join these 2 querysets. I need the user to see the information for the ASSIGNMENT first and then below I want them to see all of the PRODUCTS that belong to that ASSIGNMENT.

I do not need to update/create data, so no POST is needed. Only want to retrieve, GET, data that exists.

Below are the code for models.py, views.py, and templates. It works perfect with just ASSIGNMENTS.

MODELS.PY

    class Assignments(models.Model):
        id = models.DecimalField(db_column='ID', primary_key=True, max_digits=9, decimal_places=0)    
        name = models.CharField(db_column='NAME', unique=True, max_length=40)

        def __str__(self):
            return self.name + ' ' + '(' + '#' + str(self.id) + ')'

        class Meta:
            ordering = 'name',
            db_table = 'ASSIGNMENTS'

    class Products(models.Model):
        id = models.DecimalField(db_column='ID', primary_key=True, max_digits=11, decimal_places=0)  
        assignment = models.ForeignKey(Assignments, models.DO_NOTHING, related_name='productsfor_assignment', db_column='ASSIGNMENT_ID', blank=True, null=True)
        name = models.CharField(db_column='NAME', max_length=80)

        def __str__(self):
            return self.name + ' ' + '(' + '#' + str(self.id) + ')'

        class Meta:
            ordering = 'name',
            db_table = 'PRODUCTS'

VIEWS.PY

    def search_form(request):
        return render(request, 'assignments/search_form.html')

    def search(request):
        if 'q' in request.GET and request.GET['q']:
            q = request.GET['q']
            assign = Assignments.objects.filter(id__icontains=q)
            return render(request, 'assignments/search_results.html',
                {'AssignmentsResults': assign, 'query': q})
        else:
        # if the query is empty, render the 'search_form html' template again.
        # display an error message in that template.  So, pass a template variable.
        # 'search_form html' will check for the 'error' variable
            return render(request, 'assignments/search_form.html', {'error': True})

TEMPLATE 'search_form.html'

    {% extends "base.html" %}

    {% block content %}
    <br><br>
    <h1> Assignment ID Search Form </h1>
    <br><br>
        {% if error %}
            <p style="color: red;">Please submit a search term.</p>
        {% endif %}
        <form action="/search/" method="get">
            <input type="text" name="q" placeholder="Enter ID here ...">
            <input type="submit" value="Search">
        </form>

    <p>
        Page last updated: {{ last_updated|date:'D d F Y' }}
    </p>
    {% endblock content %}

TEMPLATE 'search_results.html'

    {% extends "base.html" %}

    {% block content %}

    <br><br>
    <h1> Assignment ID Search Results</h1>

    <p>You searched for: <strong>{{ query }}</strong></p>

    {% if AssignmentsResults %}
        <ul>
            {% for assignments in AssignmentsResults %}
            <li>
                {{ assignments.id }}, {{ assignments.name }}
            </li>
            {% endfor %}
        </ul>
    {% else %}
        <p>No assignments matched your search criteria.</p>
    {% endif %}

    {% endblock content %}

This is what I finally put together to include Products (shown below in the failed views.py and template). But, it does not work.

This was the FAILED VIEWS.PY

    def search_form(request):
        return render(request, 'assignments/search_form.html')

    def search(request):
        if 'q' in request.GET and request.GET['q']:
            q = request.GET['q']
            assign = Assignments.objects.filter(id__icontains=q)
            prod = Products.objects.filter(assignment__icontains=q)
            return render(request, 'assignments/search_results.html',
                {'AssignmentsResults': assign, 'query': q})
            return render(request, 'assignments/search_results.html',
                          'ProductsResults': prod, 'query': q)
        else:
            # if the query is empty, render the 'search_form html' template again.
            # display an error message in that template.  So, pass a template variable.
            # 'search_form html' will check for the 'error' variable
            return render(request, 'assignments/search_form.html', {'error': True})

This was the FAILED TEMPLATE CODE 'search_results.html

    {% extends "base.html" %}

    {% block content %}

    <br><br>
    <h1> Assignment ID Search Results</h1>

    <p>You searched for: <strong>{{ query }}</strong></p>

    {% if AssignmentsResults %}
        <ul>
            {% for assignments in AssignmentsResults %}
            <li>
                {{ assignments.id }}, {{ assignments.name }}
            </li>
            {% endfor %}
        </ul>

        <p> These are the Products attached to the Assignment.</p>
        {% if ProductsResults %}
        <ul>
            {% for products in ProductsResults %}
            <li>
                {{ products.assignment }}, {{ products.name }}
            </li>
            {% endfor %}
        </ul>
        {% else %}
            <p> No products for this assignment exist.</p>
        {% endif %}
    {% else %}
        <p>No assignments matched your search criteria.</p>
    {% endif %}

    {% endblock content %}

I have created the project by following 'Build a website with Django 2, updated for Django 2.2' by Nigel George. I have looked at several youtube instructional videos and read questions on stackoverflow. (These are only a couple.)

How to combine two or more querysets in a Django view?

Displaying a series of inline forms for a queryset of Django models

Django: Add queryset to inlineformsets

Django nested QuerySets

InlineFormSet with queryset of different model

However, none of them have been able to help me with my problem of presenting 2 separate queryset results on one form.

I'd certainly appreciate any suggestions and help provided. Thanks

MAG
  • 17
  • 6
  • Oh boy... There are a lot of weird things out there, but okay. Basically what you want is to show Assignment and Product info in the search_results.html template, right? – revliscano Jan 27 '20 at 23:14
  • @rvliscano - yes, you are correct. I am wanting to show the Assignment queried and then all of the Products related to that Assignment in the search_results.html template. – MAG Jan 28 '20 at 00:11

1 Answers1

0

Okay, let's see. If you just want to display the information that it seems to be intended in search_results.html, I suggest you to refactor a little bit your query as follows. (I will try to clean out the weird things (for example having two consecutive return statements) as much as I can to illustrate my point)

views.py:

from django.shortcuts import render, redirect
from django.urls import reverse


def search(request):
    q = request.GET.get('q', None)
    if q:
        assignments_qs = Assignments.objects.filter(id__icontains=q) \
            .prefetch_related('productsfor_assignment')
        context_data = {'assignments': assignments_qs}
        return render(request, 'assignments/search_results.html', context_data)
    else:
        return redirect(reverse('search_form', kwargs={'error': True}))


def search_form(request, error=False):
    return render(request, 'assignments/search_form.html', {'error': error})

search_results.html

{% if assignments %}
    <ul>
        {% for assignment in assignments %}
            <li>
                {{ assignment.id }}, {{ assignment.name }}
            </li>                 
            {% if assignment.productsfor_assignment.exists %}
                <p> These are the Products attached to the Assignment.</p>
                <ul>
                {% for product in assignment.productsfor_assignment.all %}
                    <li>
                        {{ product.assignment }}, {{ product.name }}
                    </li>
                {% endfor %}
                </ul>
            {% endif %}
        {% endfor %}
    </ul>
{% else %}
    <p>No assignments matched your search criteria.</p>
{% endif %}

I think this is the best and the most similar way to achieve what you want considering what you currently have.

revliscano
  • 2,227
  • 2
  • 12
  • 21
  • I noticed the changes in the views.py and search_results.html Thank you. However, I got an error. I thought I might because I did not see where the table/model 'Products' was used in the code for 'products_set'. The parent table/model is 'Assignments' and the child table/model is 'Products'. So the error I got was the following: **AttributeError at /search/ Cannot find 'products_set' on Assignments object, 'products_set' is an invalid parameter to prefetch_related()** Thank you for helping me with this problem. Much appreciated. – MAG Jan 28 '20 at 15:15
  • Oh, yes, sorry; I didn't note that you had defined a related_name attribute for the field assignment in the Products model. Let me edit and update the answer – revliscano Jan 28 '20 at 16:12
  • Please note that I updated the name "products_set" for "productsfor_assignment" in both views.py and search_results.html – revliscano Jan 28 '20 at 16:24
  • OMGOODNESS!!!! You did it! I now have an Assignments parent record and many Products children records displayed. Thank You So Much!!! – MAG Jan 28 '20 at 18:08
  • I tried to upvote the answer but got the message from StackOverflow that I did not have a high enough reputation count (must have 15 points) to have my upvote displayed. I am sorry the upvote does not show up. – MAG Jan 28 '20 at 19:36
  • I hate to bother you again, but something is wrong with this line of code in the views.py. It is **return redirect(reverse('search_form', kwargs={'error': True}))** I am getting the error **NoReverseMatch at /search/** and then the next line reads **Reverse for 'search_form' not found. 'search_form' is not a valid view function or pattern name.** I tried to read about this NoReverseMatch error (https://stackoverflow.com/questions/38390177/what-is-a-noreversematch-error-and-how-do-i-fix-it) However, I am a newbie and could not figure what to fix. Can you help? Should I repost? – MAG Jan 28 '20 at 21:41
  • You just need to replace the argument "search _form" inside the reverse function with the "name" attribute of the path you have assigned for that specific url in urls.py. I just put "search_form" there because I didn't know the actual name of it. – revliscano Jan 28 '20 at 22:18