'DJANGO - How to display multiple database rows in one row in view

I'm working with a 1-N relationship (1 ticket has N messages), in the admin view I'm trying to pull things from the database, but the N msg's related to ticket X, are coming as N lines (one line for each msg from ticket X) in the view (I display it in a table), I need the N messages to be passed in one line only, below is an example of how this is happening and how I need it to be.

Current output:

| Ticket id | Msg id  |      Content      | Status |
|:---------:|:-------:|:-----------------:|:------:|
| 1         |  1      | msg 1 of ticket 1 |  Open  |
| 2         |  2      | msg 1 of ticket 2 | Closed |
| 2         |  3      | msg 2 of ticket 2 | Closed |
| 3         |  4      | ticket 3 - msg 1  |  Open  |
| 4         |  5      | msg 1 of ticket 2 | Closed |
| 3         |  6      | ticket 3 - msg 2  |  Open  |

How I need it to be:

| Ticket id | Msg id  |                Content               | Status |
|:---------:|:-------:|:------------------------------------:|:------:|
| 1         |  1      | msg 1 of ticket 1                    |  Open  |
| 2         |  2      | msg 1 of ticket 2; msg 2 of ticket 2 | Closed |
| 3         |  4      | ticket 3 - msg 1; ticket 3 - msg 2   |  Open  |
| 4         |  5      | msg 1 of ticket 2                    | Closed |

For example in my detail view of each ticket I managed to do what I wanted above, but in this case I only pull ticket X and its messages, now that I pull everything from the bank I don't know how to do it

My detail view of each ticket:

def ticket_by_id(request, ticket_id):
    ticket = Ticket.objects.get(id=ticket_id)
    mesTicket = MessageTicket.objects.filter(fk_ticket=ticket_id)
    aux = ''
    for itens in mesTicket:
        aux += str(itens.content) + '; '


Solution 1:[1]

If your database is Postgres, you can aggregate with StringAgg or ArrayAgg. Probably something like this:

from django.contrib.postgres.aggregates import StringAgg
from django.db.models import Min

messages = MessageTicket.objects\
    .values('ticket_id', 'status')\
    .annotate(
        agg_content=StringAgg('content', ','),
        min_msg_id=Min('msg_id'))

or

from django.contrib.postgres.aggregates import ArrayAgg
from django.db.models import Min

messages = MessageTicket.objects\
    .values('ticket_id', 'status')\
    .annotate(
        agg_content=ArrayAgg('content'),
        min_msg_id=Min('msg_id'))

If you cannot use DB aggregation, you can do that in pandas with only a couple lines of code:

import pandas as pd
messages = MessageTicket.objects.values('ticket_id', 'msg_id', 'content', 'status')
df = pd.DataFrame(messages)
agg_messages = df.groupby(['ticket_id', 'status']).agg({'msg_id': min, 'content': list})
print(agg_messages.reset_index().to_dict('records'))

Solution 2:[2]

I managed to solve it like this:

Query from the bank (query set)

tickets = Ticket.objects.order_by('id')[:]
mesTickets = MessageTicket.objects.order_by('id')[:]
array = [] # list array auxiliar

For each ticket in 'tickets' I created a new 'msg' field and allocated ticket in array

for i in tickets: 
    i.msg = []
    array.append(i)

For each msg in 'mesTickets' I confirmed if the msg.id was equal to the ticket.id, if so, I assigned the msgs in the list 'msg' inside the list 'array'

{% for i in array %}
     <tr>
          <td>{{i.id}}</td>
          <td>
              <ul>
                  {% for j in i.msg %}
                      <li>{{j.content}}</li>
                  {% endfor %}
              </ul>
          </td>
     </tr>
{% endfor %}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Pedro - Maho