Initial commit: Diario Conversazioni Olimpic Nastri
- Django 5.2 + PostgreSQL + Gunicorn - Conversazioni, Obiettivi, Documenti PDF, Persone - Commenti e aggiornamenti con modifica/eliminazione - Agenda, ricerca live, giorni rimanenti scadenze - Bootstrap 5 + HTMX + toast notifications - Deploy: Nginx + Gunicorn + SSL
This commit is contained in:
517
diario/views.py
Normal file
517
diario/views.py
Normal file
@@ -0,0 +1,517 @@
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib import messages
|
||||
from django.http import JsonResponse, HttpResponseForbidden
|
||||
from django.views.decorators.http import require_POST
|
||||
from django.db.models import Q, Count
|
||||
from django.utils import timezone
|
||||
from itertools import chain
|
||||
from datetime import timedelta
|
||||
|
||||
from .models import Conversazione, Obiettivo, AggiornamentoObiettivo, CommentoConversazione, Documento
|
||||
from .forms import ConversazioneForm, ObiettivoForm, AggiornamentoObiettivoForm, CommentoConversazioneForm, DocumentoForm
|
||||
|
||||
|
||||
def _can_edit(user, obj):
|
||||
"""Autore o superuser possono modificare/eliminare."""
|
||||
owner = getattr(obj, 'registrato_da', None) or getattr(obj, 'creato_da', None) or getattr(obj, 'caricato_da', None) or getattr(obj, 'autore', None)
|
||||
return user == owner or user.is_superuser
|
||||
|
||||
|
||||
# ── Dashboard ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@login_required
|
||||
def dashboard(request):
|
||||
conversazioni = Conversazione.objects.select_related('registrato_da').order_by('-data')[:20]
|
||||
aggiornamenti = AggiornamentoObiettivo.objects.select_related('autore', 'obiettivo').order_by('-data')[:20]
|
||||
documenti_recenti = Documento.objects.select_related('caricato_da').order_by('-data_caricamento')[:10]
|
||||
|
||||
eventi = sorted(
|
||||
chain(
|
||||
[{'tipo': 'conversazione', 'data': c.data, 'obj': c} for c in conversazioni],
|
||||
[{'tipo': 'aggiornamento', 'data': a.data, 'obj': a} for a in aggiornamenti],
|
||||
[{'tipo': 'documento', 'data': d.data_caricamento, 'obj': d} for d in documenti_recenti],
|
||||
),
|
||||
key=lambda x: x['data'],
|
||||
reverse=True,
|
||||
)[:30]
|
||||
|
||||
obiettivi_aperti = Obiettivo.objects.exclude(stato='completato').order_by('-data_creazione')[:5]
|
||||
|
||||
# Agenda: prossime scadenze obiettivi (prossimi 30 giorni)
|
||||
oggi = timezone.now().date()
|
||||
scadenze_prossime = Obiettivo.objects.filter(
|
||||
data_scadenza__gte=oggi,
|
||||
data_scadenza__lte=oggi + timedelta(days=30),
|
||||
).exclude(stato='completato').order_by('data_scadenza')[:8]
|
||||
|
||||
# Scadenze passate non completate
|
||||
scaduti = Obiettivo.objects.filter(
|
||||
data_scadenza__lt=oggi,
|
||||
).exclude(stato='completato').order_by('-data_scadenza')[:5]
|
||||
|
||||
return render(request, 'diario/dashboard.html', {
|
||||
'eventi': eventi,
|
||||
'obiettivi_aperti': obiettivi_aperti,
|
||||
'scadenze_prossime': scadenze_prossime,
|
||||
'scaduti': scaduti,
|
||||
'oggi': oggi,
|
||||
})
|
||||
|
||||
|
||||
# ── Agenda ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
@login_required
|
||||
def agenda(request):
|
||||
oggi = timezone.now().date()
|
||||
|
||||
# Prossimi eventi: scadenze obiettivi, conversazioni programmate nel futuro
|
||||
scadenze_future = Obiettivo.objects.filter(
|
||||
data_scadenza__gte=oggi,
|
||||
).exclude(stato='completato').order_by('data_scadenza')
|
||||
|
||||
conversazioni_future = Conversazione.objects.filter(
|
||||
data__date__gte=oggi,
|
||||
).select_related('registrato_da').order_by('data')
|
||||
|
||||
# Eventi futuri unificati
|
||||
eventi_futuri = sorted(
|
||||
chain(
|
||||
[{'tipo': 'scadenza', 'data': o.data_scadenza, 'obj': o} for o in scadenze_future],
|
||||
[{'tipo': 'conversazione', 'data': c.data.date(), 'obj': c} for c in conversazioni_future],
|
||||
),
|
||||
key=lambda x: x['data'],
|
||||
)
|
||||
|
||||
# Eventi passati (ultimi 30 giorni)
|
||||
data_inizio = oggi - timedelta(days=30)
|
||||
conversazioni_passate = Conversazione.objects.filter(
|
||||
data__date__gte=data_inizio,
|
||||
data__date__lt=oggi,
|
||||
).select_related('registrato_da').order_by('-data')
|
||||
|
||||
scadenze_passate = Obiettivo.objects.filter(
|
||||
data_scadenza__gte=data_inizio,
|
||||
data_scadenza__lt=oggi,
|
||||
).order_by('-data_scadenza')
|
||||
|
||||
eventi_passati = sorted(
|
||||
chain(
|
||||
[{'tipo': 'scadenza', 'data': o.data_scadenza, 'obj': o, 'scaduto': o.stato != 'completato'} for o in scadenze_passate],
|
||||
[{'tipo': 'conversazione', 'data': c.data.date(), 'obj': c, 'scaduto': False} for c in conversazioni_passate],
|
||||
),
|
||||
key=lambda x: x['data'],
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
return render(request, 'diario/agenda.html', {
|
||||
'eventi_futuri': eventi_futuri,
|
||||
'eventi_passati': eventi_passati,
|
||||
'oggi': oggi,
|
||||
})
|
||||
|
||||
|
||||
# ── Conversazioni ──────────────────────────────────────────────────────────────
|
||||
|
||||
@login_required
|
||||
def conversazioni_lista(request):
|
||||
qs = Conversazione.objects.select_related('registrato_da').order_by('-data')
|
||||
return render(request, 'diario/conversazioni/lista.html', {'conversazioni': qs})
|
||||
|
||||
|
||||
@login_required
|
||||
def conversazione_nuova(request):
|
||||
if request.method == 'POST':
|
||||
form = ConversazioneForm(request.POST)
|
||||
if form.is_valid():
|
||||
conv = form.save(commit=False)
|
||||
conv.registrato_da = request.user
|
||||
conv.save()
|
||||
form.save_m2m()
|
||||
messages.success(request, 'Conversazione registrata.')
|
||||
return redirect('conversazione_dettaglio', pk=conv.pk)
|
||||
else:
|
||||
form = ConversazioneForm()
|
||||
return render(request, 'diario/conversazioni/form.html', {'form': form, 'titolo_pagina': 'Nuova conversazione'})
|
||||
|
||||
|
||||
@login_required
|
||||
def conversazione_dettaglio(request, pk):
|
||||
conv = get_object_or_404(Conversazione, pk=pk)
|
||||
commenti = conv.commenti.select_related('autore').order_by('-data')
|
||||
documenti = conv.documenti.select_related('caricato_da').all()
|
||||
|
||||
if request.method == 'POST':
|
||||
comment_form = CommentoConversazioneForm(request.POST)
|
||||
if comment_form.is_valid():
|
||||
c = comment_form.save(commit=False)
|
||||
c.conversazione = conv
|
||||
c.autore = request.user
|
||||
c.save()
|
||||
messages.success(request, 'Commento aggiunto.')
|
||||
return redirect('conversazione_dettaglio', pk=conv.pk)
|
||||
else:
|
||||
comment_form = CommentoConversazioneForm()
|
||||
|
||||
return render(request, 'diario/conversazioni/dettaglio.html', {
|
||||
'conv': conv,
|
||||
'commenti': commenti,
|
||||
'comment_form': comment_form,
|
||||
'documenti': documenti,
|
||||
'can_edit': _can_edit(request.user, conv),
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
def conversazione_modifica(request, pk):
|
||||
conv = get_object_or_404(Conversazione, pk=pk)
|
||||
if not _can_edit(request.user, conv):
|
||||
return HttpResponseForbidden('Non hai i permessi per modificare questa conversazione.')
|
||||
if request.method == 'POST':
|
||||
form = ConversazioneForm(request.POST, instance=conv)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, 'Conversazione aggiornata.')
|
||||
return redirect('conversazione_dettaglio', pk=conv.pk)
|
||||
else:
|
||||
form = ConversazioneForm(instance=conv)
|
||||
return render(request, 'diario/conversazioni/form.html', {'form': form, 'titolo_pagina': 'Modifica conversazione', 'conv': conv})
|
||||
|
||||
|
||||
@login_required
|
||||
def conversazione_elimina(request, pk):
|
||||
conv = get_object_or_404(Conversazione, pk=pk)
|
||||
if not _can_edit(request.user, conv):
|
||||
return HttpResponseForbidden('Non hai i permessi per eliminare questa conversazione.')
|
||||
if request.method == 'POST':
|
||||
conv.delete()
|
||||
messages.success(request, 'Conversazione eliminata.')
|
||||
return redirect('conversazioni_lista')
|
||||
return render(request, 'diario/conferma_elimina.html', {
|
||||
'oggetto': conv,
|
||||
'tipo': 'conversazione',
|
||||
'cancel_url': 'conversazione_dettaglio',
|
||||
})
|
||||
|
||||
|
||||
# ── Commenti conversazione — modifica/elimina ─────────────────────────────────
|
||||
|
||||
@login_required
|
||||
def commento_modifica(request, pk):
|
||||
commento = get_object_or_404(CommentoConversazione, pk=pk)
|
||||
if not _can_edit(request.user, commento):
|
||||
return HttpResponseForbidden('Non puoi modificare questo commento.')
|
||||
if request.method == 'POST':
|
||||
form = CommentoConversazioneForm(request.POST, instance=commento)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, 'Commento aggiornato.')
|
||||
return redirect('conversazione_dettaglio', pk=commento.conversazione.pk)
|
||||
else:
|
||||
form = CommentoConversazioneForm(instance=commento)
|
||||
return render(request, 'diario/commento_modifica.html', {
|
||||
'form': form,
|
||||
'commento': commento,
|
||||
'tipo': 'commento',
|
||||
'back_url': redirect('conversazione_dettaglio', pk=commento.conversazione.pk).url,
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
def commento_elimina(request, pk):
|
||||
commento = get_object_or_404(CommentoConversazione, pk=pk)
|
||||
if not _can_edit(request.user, commento):
|
||||
return HttpResponseForbidden('Non puoi eliminare questo commento.')
|
||||
conv_pk = commento.conversazione.pk
|
||||
if request.method == 'POST':
|
||||
commento.delete()
|
||||
messages.success(request, 'Commento eliminato.')
|
||||
return redirect('conversazione_dettaglio', pk=conv_pk)
|
||||
return render(request, 'diario/conferma_elimina.html', {
|
||||
'oggetto': commento,
|
||||
'tipo': 'commento',
|
||||
'cancel_url': 'conversazione_dettaglio',
|
||||
'cancel_pk': conv_pk,
|
||||
})
|
||||
|
||||
|
||||
# ── Aggiornamenti obiettivo — modifica/elimina ────────────────────────────────
|
||||
|
||||
@login_required
|
||||
def aggiornamento_modifica(request, pk):
|
||||
agg = get_object_or_404(AggiornamentoObiettivo, pk=pk)
|
||||
if not _can_edit(request.user, agg):
|
||||
return HttpResponseForbidden('Non puoi modificare questo aggiornamento.')
|
||||
if request.method == 'POST':
|
||||
form = AggiornamentoObiettivoForm(request.POST, instance=agg)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, 'Aggiornamento modificato.')
|
||||
return redirect('obiettivo_dettaglio', pk=agg.obiettivo.pk)
|
||||
else:
|
||||
form = AggiornamentoObiettivoForm(instance=agg)
|
||||
return render(request, 'diario/commento_modifica.html', {
|
||||
'form': form,
|
||||
'commento': agg,
|
||||
'tipo': 'aggiornamento',
|
||||
'back_url': redirect('obiettivo_dettaglio', pk=agg.obiettivo.pk).url,
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
def aggiornamento_elimina(request, pk):
|
||||
agg = get_object_or_404(AggiornamentoObiettivo, pk=pk)
|
||||
if not _can_edit(request.user, agg):
|
||||
return HttpResponseForbidden('Non puoi eliminare questo aggiornamento.')
|
||||
obj_pk = agg.obiettivo.pk
|
||||
if request.method == 'POST':
|
||||
agg.delete()
|
||||
messages.success(request, 'Aggiornamento eliminato.')
|
||||
return redirect('obiettivo_dettaglio', pk=obj_pk)
|
||||
return render(request, 'diario/conferma_elimina.html', {
|
||||
'oggetto': agg,
|
||||
'tipo': 'aggiornamento',
|
||||
'cancel_url': 'obiettivo_dettaglio',
|
||||
'cancel_pk': obj_pk,
|
||||
})
|
||||
|
||||
|
||||
# ── Obiettivi ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@login_required
|
||||
def obiettivi_lista(request):
|
||||
filtro = request.GET.get('filtro', 'tutti')
|
||||
qs = Obiettivo.objects.prefetch_related('assegnato_a').select_related('creato_da')
|
||||
|
||||
if filtro == 'collettivi':
|
||||
qs = qs.filter(tipo='collettivo')
|
||||
elif filtro == 'individuali':
|
||||
qs = qs.filter(tipo='individuale')
|
||||
elif filtro == 'miei':
|
||||
qs = qs.filter(assegnato_a=request.user)
|
||||
|
||||
return render(request, 'diario/obiettivi/lista.html', {'obiettivi': qs, 'filtro': filtro})
|
||||
|
||||
|
||||
@login_required
|
||||
def obiettivo_nuovo(request):
|
||||
if request.method == 'POST':
|
||||
form = ObiettivoForm(request.POST)
|
||||
if form.is_valid():
|
||||
obj = form.save(commit=False)
|
||||
obj.creato_da = request.user
|
||||
obj.save()
|
||||
form.save_m2m()
|
||||
messages.success(request, 'Obiettivo creato.')
|
||||
return redirect('obiettivo_dettaglio', pk=obj.pk)
|
||||
else:
|
||||
form = ObiettivoForm()
|
||||
return render(request, 'diario/obiettivi/form.html', {'form': form, 'titolo_pagina': 'Nuovo obiettivo'})
|
||||
|
||||
|
||||
@login_required
|
||||
def obiettivo_dettaglio(request, pk):
|
||||
obj = get_object_or_404(Obiettivo, pk=pk)
|
||||
aggiornamenti = obj.aggiornamenti.select_related('autore').order_by('-data')
|
||||
documenti = obj.documenti.select_related('caricato_da').all()
|
||||
|
||||
if request.method == 'POST':
|
||||
agg_form = AggiornamentoObiettivoForm(request.POST)
|
||||
if agg_form.is_valid():
|
||||
agg = agg_form.save(commit=False)
|
||||
agg.obiettivo = obj
|
||||
agg.autore = request.user
|
||||
agg.save()
|
||||
messages.success(request, 'Aggiornamento aggiunto.')
|
||||
return redirect('obiettivo_dettaglio', pk=obj.pk)
|
||||
else:
|
||||
agg_form = AggiornamentoObiettivoForm()
|
||||
|
||||
return render(request, 'diario/obiettivi/dettaglio.html', {
|
||||
'obj': obj,
|
||||
'aggiornamenti': aggiornamenti,
|
||||
'documenti': documenti,
|
||||
'agg_form': agg_form,
|
||||
'can_edit': _can_edit(request.user, obj),
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
def obiettivo_modifica(request, pk):
|
||||
obj = get_object_or_404(Obiettivo, pk=pk)
|
||||
if not _can_edit(request.user, obj):
|
||||
return HttpResponseForbidden('Non hai i permessi per modificare questo obiettivo.')
|
||||
if request.method == 'POST':
|
||||
form = ObiettivoForm(request.POST, instance=obj)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, 'Obiettivo aggiornato.')
|
||||
return redirect('obiettivo_dettaglio', pk=obj.pk)
|
||||
else:
|
||||
form = ObiettivoForm(instance=obj)
|
||||
return render(request, 'diario/obiettivi/form.html', {'form': form, 'titolo_pagina': 'Modifica obiettivo', 'obj': obj})
|
||||
|
||||
|
||||
@login_required
|
||||
def obiettivo_elimina(request, pk):
|
||||
obj = get_object_or_404(Obiettivo, pk=pk)
|
||||
if not _can_edit(request.user, obj):
|
||||
return HttpResponseForbidden('Non hai i permessi per eliminare questo obiettivo.')
|
||||
if request.method == 'POST':
|
||||
obj.delete()
|
||||
messages.success(request, 'Obiettivo eliminato.')
|
||||
return redirect('obiettivi_lista')
|
||||
return render(request, 'diario/conferma_elimina.html', {
|
||||
'oggetto': obj,
|
||||
'tipo': 'obiettivo',
|
||||
'cancel_url': 'obiettivo_dettaglio',
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@require_POST
|
||||
def obiettivo_avanzamento_ajax(request, pk):
|
||||
obj = get_object_or_404(Obiettivo, pk=pk)
|
||||
try:
|
||||
valore = int(request.POST.get('avanzamento', 0))
|
||||
if not 0 <= valore <= 100:
|
||||
return JsonResponse({'ok': False, 'error': 'Valore fuori range'}, status=400)
|
||||
obj.avanzamento = valore
|
||||
obj.save(update_fields=['avanzamento'])
|
||||
return JsonResponse({'ok': True, 'avanzamento': obj.avanzamento})
|
||||
except (ValueError, TypeError):
|
||||
return JsonResponse({'ok': False, 'error': 'Valore non valido'}, status=400)
|
||||
|
||||
|
||||
# ── Documenti ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@login_required
|
||||
def documenti_lista(request):
|
||||
qs = Documento.objects.select_related('caricato_da', 'conversazione', 'obiettivo').order_by('-data_caricamento')
|
||||
return render(request, 'diario/documenti/lista.html', {'documenti': qs})
|
||||
|
||||
|
||||
@login_required
|
||||
def documento_nuovo(request):
|
||||
conversazione_pk = request.GET.get('conversazione')
|
||||
obiettivo_pk = request.GET.get('obiettivo')
|
||||
initial = {}
|
||||
if conversazione_pk:
|
||||
initial['conversazione'] = conversazione_pk
|
||||
if obiettivo_pk:
|
||||
initial['obiettivo'] = obiettivo_pk
|
||||
|
||||
if request.method == 'POST':
|
||||
form = DocumentoForm(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
doc = form.save(commit=False)
|
||||
doc.caricato_da = request.user
|
||||
doc.save()
|
||||
messages.success(request, 'Documento caricato.')
|
||||
if doc.conversazione:
|
||||
return redirect('conversazione_dettaglio', pk=doc.conversazione.pk)
|
||||
if doc.obiettivo:
|
||||
return redirect('obiettivo_dettaglio', pk=doc.obiettivo.pk)
|
||||
return redirect('documento_dettaglio', pk=doc.pk)
|
||||
else:
|
||||
form = DocumentoForm(initial=initial)
|
||||
return render(request, 'diario/documenti/form.html', {'form': form, 'titolo_pagina': 'Carica documento'})
|
||||
|
||||
|
||||
@login_required
|
||||
def documento_dettaglio(request, pk):
|
||||
doc = get_object_or_404(Documento.objects.select_related('caricato_da', 'conversazione', 'obiettivo'), pk=pk)
|
||||
return render(request, 'diario/documenti/dettaglio.html', {
|
||||
'doc': doc,
|
||||
'can_edit': _can_edit(request.user, doc),
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
def documento_elimina(request, pk):
|
||||
doc = get_object_or_404(Documento, pk=pk)
|
||||
if not _can_edit(request.user, doc):
|
||||
return HttpResponseForbidden('Non hai i permessi per eliminare questo documento.')
|
||||
if request.method == 'POST':
|
||||
doc.file.delete(save=False)
|
||||
doc.delete()
|
||||
messages.success(request, 'Documento eliminato.')
|
||||
return redirect('documenti_lista')
|
||||
return render(request, 'diario/conferma_elimina.html', {
|
||||
'oggetto': doc,
|
||||
'tipo': 'documento',
|
||||
'cancel_url': 'documento_dettaglio',
|
||||
})
|
||||
|
||||
|
||||
# ── Ricerca ────────────────────────────────────────────────────────────────────
|
||||
|
||||
@login_required
|
||||
def ricerca(request):
|
||||
q = request.GET.get('q', '').strip()
|
||||
risultati = {'conversazioni': [], 'obiettivi': [], 'documenti': [], 'persone': []}
|
||||
total = 0
|
||||
|
||||
if len(q) >= 2:
|
||||
risultati['conversazioni'] = Conversazione.objects.filter(
|
||||
Q(titolo__icontains=q) | Q(contenuto__icontains=q)
|
||||
).select_related('registrato_da')[:20]
|
||||
|
||||
risultati['obiettivi'] = Obiettivo.objects.filter(
|
||||
Q(titolo__icontains=q) | Q(descrizione__icontains=q)
|
||||
).select_related('creato_da')[:20]
|
||||
|
||||
risultati['documenti'] = Documento.objects.filter(
|
||||
Q(titolo__icontains=q) | Q(descrizione__icontains=q)
|
||||
).select_related('caricato_da')[:20]
|
||||
|
||||
risultati['persone'] = User.objects.filter(
|
||||
Q(first_name__icontains=q) | Q(last_name__icontains=q) | Q(username__icontains=q),
|
||||
is_active=True,
|
||||
)[:20]
|
||||
|
||||
total = sum(len(v) if hasattr(v, '__len__') else v.count() for v in risultati.values())
|
||||
|
||||
ctx = {'q': q, 'risultati': risultati, 'total': total}
|
||||
|
||||
if getattr(request, 'htmx', False):
|
||||
return render(request, 'diario/ricerca_risultati.html', ctx)
|
||||
|
||||
return render(request, 'diario/ricerca.html', ctx)
|
||||
|
||||
|
||||
# ── Persone ────────────────────────────────────────────────────────────────────
|
||||
|
||||
@login_required
|
||||
def persone_lista(request):
|
||||
persone = User.objects.filter(is_active=True).annotate(
|
||||
num_conversazioni=Count('conversazioni_registrate', distinct=True),
|
||||
num_obiettivi=Count('obiettivi_creati', distinct=True),
|
||||
num_documenti=Count('documenti_caricati', distinct=True),
|
||||
).order_by('first_name', 'username')
|
||||
return render(request, 'diario/persone/lista.html', {'persone': persone})
|
||||
|
||||
|
||||
@login_required
|
||||
def persona_dettaglio(request, pk):
|
||||
persona = get_object_or_404(User, pk=pk, is_active=True)
|
||||
conversazioni = Conversazione.objects.filter(
|
||||
Q(registrato_da=persona) | Q(partecipanti=persona)
|
||||
).distinct().select_related('registrato_da').order_by('-data')[:20]
|
||||
|
||||
obiettivi = Obiettivo.objects.filter(
|
||||
Q(creato_da=persona) | Q(assegnato_a=persona)
|
||||
).distinct().select_related('creato_da').prefetch_related('assegnato_a').order_by('-data_creazione')[:20]
|
||||
|
||||
documenti = Documento.objects.filter(
|
||||
caricato_da=persona
|
||||
).select_related('conversazione', 'obiettivo').order_by('-data_caricamento')[:20]
|
||||
|
||||
return render(request, 'diario/persone/dettaglio.html', {
|
||||
'persona': persona,
|
||||
'conversazioni': conversazioni,
|
||||
'obiettivi': obiettivi,
|
||||
'documenti': documenti,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user