'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 |
