agdar/slp/views.py
Marwan Alwali a04817ef6e update
2025-11-02 19:25:08 +03:00

975 lines
38 KiB
Python

"""
SLP views for the Tenhal Multidisciplinary Healthcare Platform.
This module contains views for Speech-Language Pathology documentation including:
- SLP consultations (SLP-F-1) - variants for ASD/LanguageDelay/Fluency
- SLP assessments (SLP-F-2)
- SLP interventions (SLP-F-3)
- SLP progress reports (SLP-F-4)
"""
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib import messages
from django.db.models import Q, Avg
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.utils import timezone
from django.views.generic import ListView, DetailView, CreateView, UpdateView, View
from django.urls import reverse_lazy
from core.mixins import (
TenantFilterMixin,
RolePermissionMixin,
AuditLogMixin,
HTMXResponseMixin,
SuccessMessageMixin,
PaginationMixin,
ConsentRequiredMixin,
)
from core.models import User, Patient
from appointments.models import Appointment
from .models import SLPConsult, SLPAssessment, SLPIntervention, SLPProgressReport, SLPTarget
from .forms import SLPConsultForm, SLPAssessmentForm, SLPInterventionForm, SLPProgressReportForm
# Sign Views
class SLPConsultSignView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin, View):
"""Sign an SLP consultation."""
allowed_roles = [User.Role.ADMIN, User.Role.SLP]
def post(self, request, pk):
consult = get_object_or_404(SLPConsult, pk=pk, tenant=request.user.tenant)
if consult.signed_by:
messages.warning(request, "This consultation has already been signed.")
return HttpResponseRedirect(reverse_lazy('slp:consult_detail', kwargs={'pk': pk}))
if consult.provider != request.user and request.user.role != User.Role.ADMIN:
messages.error(request, "Only the consultation provider or an administrator can sign this consultation.")
return HttpResponseRedirect(reverse_lazy('slp:consult_detail', kwargs={'pk': pk}))
consult.signed_by = request.user
consult.signed_at = timezone.now()
consult.save()
messages.success(request, "Consultation signed successfully!")
return HttpResponseRedirect(reverse_lazy('slp:consult_detail', kwargs={'pk': pk}))
class SLPAssessmentSignView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin, View):
"""Sign an SLP assessment."""
allowed_roles = [User.Role.ADMIN, User.Role.SLP]
def post(self, request, pk):
assessment = get_object_or_404(SLPAssessment, pk=pk, tenant=request.user.tenant)
if assessment.signed_by:
messages.warning(request, "This assessment has already been signed.")
return HttpResponseRedirect(reverse_lazy('slp:assessment_detail', kwargs={'pk': pk}))
if assessment.provider != request.user and request.user.role != User.Role.ADMIN:
messages.error(request, "Only the assessment provider or an administrator can sign this assessment.")
return HttpResponseRedirect(reverse_lazy('slp:assessment_detail', kwargs={'pk': pk}))
assessment.signed_by = request.user
assessment.signed_at = timezone.now()
assessment.save()
messages.success(request, "Assessment signed successfully!")
return HttpResponseRedirect(reverse_lazy('slp:assessment_detail', kwargs={'pk': pk}))
class SLPInterventionSignView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin, View):
"""Sign an SLP intervention."""
allowed_roles = [User.Role.ADMIN, User.Role.SLP]
def post(self, request, pk):
intervention = get_object_or_404(SLPIntervention, pk=pk, tenant=request.user.tenant)
if intervention.signed_by:
messages.warning(request, "This intervention has already been signed.")
return HttpResponseRedirect(reverse_lazy('slp:intervention_detail', kwargs={'pk': pk}))
if intervention.provider != request.user and request.user.role != User.Role.ADMIN:
messages.error(request, "Only the intervention provider or an administrator can sign this intervention.")
return HttpResponseRedirect(reverse_lazy('slp:intervention_detail', kwargs={'pk': pk}))
intervention.signed_by = request.user
intervention.signed_at = timezone.now()
intervention.save()
messages.success(request, "Intervention signed successfully!")
return HttpResponseRedirect(reverse_lazy('slp:intervention_detail', kwargs={'pk': pk}))
class SLPProgressReportSignView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin, View):
"""Sign an SLP progress report."""
allowed_roles = [User.Role.ADMIN, User.Role.SLP]
def post(self, request, pk):
report = get_object_or_404(SLPProgressReport, pk=pk, tenant=request.user.tenant)
if report.signed_by:
messages.warning(request, "This progress report has already been signed.")
return HttpResponseRedirect(reverse_lazy('slp:progress_report_detail', kwargs={'pk': pk}))
if report.provider != request.user and request.user.role != User.Role.ADMIN:
messages.error(request, "Only the report provider or an administrator can sign this progress report.")
return HttpResponseRedirect(reverse_lazy('slp:progress_report_detail', kwargs={'pk': pk}))
report.signed_by = request.user
report.signed_at = timezone.now()
report.save()
messages.success(request, "Progress report signed successfully!")
return HttpResponseRedirect(reverse_lazy('slp:progress_report_detail', kwargs={'pk': pk}))
class SLPConsultListView(LoginRequiredMixin, TenantFilterMixin, PaginationMixin,
HTMXResponseMixin, ListView):
"""
SLP consultation list view (SLP-F-1).
Features:
- Filter by patient, provider, date range, consult type
- Search by patient name/MRN
- Role-based filtering
"""
model = SLPConsult
template_name = 'slp/consultation_list.html'
htmx_template_name = 'slp/partials/consult_list_partial.html'
context_object_name = 'consultations'
paginate_by = 25
def get_queryset(self):
"""Get filtered queryset."""
queryset = super().get_queryset()
user = self.request.user
# Role-based filtering
if user.role == User.Role.SLP:
queryset = queryset.filter(provider__user=user)
# Apply search
search_query = self.request.GET.get('search', '').strip()
if search_query:
queryset = queryset.filter(
Q(patient__first_name_en__icontains=search_query) |
Q(patient__last_name_en__icontains=search_query) |
Q(patient__mrn__icontains=search_query)
)
# Apply filters
patient_id = self.request.GET.get('patient')
if patient_id:
queryset = queryset.filter(patient_id=patient_id)
consult_type = self.request.GET.get('consult_type')
if consult_type:
if consult_type == 'ASD':
queryset = queryset.filter(is_asd_variant=True)
elif consult_type == 'LANGUAGE':
queryset = queryset.filter(is_language_delay_variant=True)
elif consult_type == 'FLUENCY':
queryset = queryset.filter(is_fluency_variant=True)
date_from = self.request.GET.get('date_from')
if date_from:
queryset = queryset.filter(consultation_date__gte=date_from)
date_to = self.request.GET.get('date_to')
if date_to:
queryset = queryset.filter(consultation_date__lte=date_to)
return queryset.select_related('patient', 'provider', 'appointment', 'signed_by').order_by('-consultation_date')
def get_context_data(self, **kwargs):
"""Add unsigned consultations count for current user."""
context = super().get_context_data(**kwargs)
user = self.request.user
unsigned_query = SLPConsult.objects.filter(tenant=user.tenant, signed_by__isnull=True)
if user.role == User.Role.SLP:
unsigned_query = unsigned_query.filter(provider=user)
context['unsigned_count'] = unsigned_query.count()
context['unsigned_items'] = unsigned_query.select_related('patient', 'provider').order_by('-consultation_date')[:10]
return context
class SLPConsultDetailView(LoginRequiredMixin, TenantFilterMixin, DetailView):
"""
SLP consultation detail view (SLP-F-1).
Features:
- Full consultation details
- Primary concern, suspected areas
- Type of service (Consult/Eval/Intervention/ParentTraining)
- Communication modes, screen time
- Variant-specific questionnaires (ASD/LanguageDelay/Fluency)
- Skills-to-observe matrices
- Oral-motor screening
"""
model = SLPConsult
template_name = 'slp/consultation_detail.html'
context_object_name = 'consultation'
def get_context_data(self, **kwargs):
"""Add related assessments and interventions."""
context = super().get_context_data(**kwargs)
# Get related assessments
context['assessments'] = SLPAssessment.objects.filter(
patient=self.object.patient
).order_by('-assessment_date')[:5]
# Get related interventions
context['interventions'] = SLPIntervention.objects.filter(
patient=self.object.patient
).order_by('-session_date')[:10]
return context
class SLPConsultCreateView(ConsentRequiredMixin, LoginRequiredMixin, RolePermissionMixin,
AuditLogMixin, SuccessMessageMixin, CreateView):
"""
SLP consultation creation view (SLP-F-1).
Features:
- Base consultation + subtype flags (ASD/LanguageDelay/Fluency)
- Primary concern, suspected areas
- Type of service
- Communication modes, screen time
- Variant-specific questionnaires
- Skills-to-observe matrices
- Oral-motor screening
- Consent verification enforced before creation
"""
model = SLPConsult
form_class = SLPConsultForm
template_name = 'slp/consult_form.html'
success_message = "SLP consultation recorded successfully!"
allowed_roles = [User.Role.ADMIN, User.Role.SLP]
# Consent enforcement
consent_service_type = 'SLP'
consent_error_message = (
"Patient must sign SLP therapy consent before consultation can be documented."
)
def get_success_url(self):
"""Redirect to consult detail."""
return reverse_lazy('slp:consult_detail', kwargs={'pk': self.object.pk})
def get_patient(self):
"""Get patient for consent verification."""
patient_id = self.request.GET.get('patient')
appointment_id = self.request.GET.get('appointment_id')
if patient_id:
return Patient.objects.get(
pk=patient_id,
tenant=self.request.user.tenant
)
elif appointment_id:
appointment = Appointment.objects.get(
pk=appointment_id,
tenant=self.request.user.tenant
)
return appointment.patient
return None
def form_valid(self, form):
"""Set tenant and provider."""
# Set tenant
form.instance.tenant = self.request.user.tenant
# Set provider
form.instance.provider = self.request.user
# Set consultation date if not provided
if not form.instance.consultation_date:
form.instance.consultation_date = timezone.now().date()
# Save consultation
response = super().form_valid(form)
return response
def get_context_data(self, **kwargs):
"""Add form title and patient/appointment info."""
context = super().get_context_data(**kwargs)
context['form_title'] = 'SLP Consultation (SLP-F-1)'
context['submit_text'] = 'Save Consultation'
# Get patient if provided
patient_id = self.request.GET.get('patient')
if patient_id:
try:
context['patient'] = Patient.objects.get(
pk=patient_id,
tenant=self.request.user.tenant
)
except Patient.DoesNotExist:
pass
# Get appointment if provided
appointment_id = self.request.GET.get('appointment_id')
if appointment_id:
try:
context['appointment'] = Appointment.objects.get(
pk=appointment_id,
tenant=self.request.user.tenant
)
context['patient'] = context['appointment'].patient
except Appointment.DoesNotExist:
pass
return context
class SLPConsultUpdateView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin,
AuditLogMixin, SuccessMessageMixin, UpdateView):
"""SLP consultation update view (SLP-F-1)."""
model = SLPConsult
form_class = SLPConsultForm
template_name = 'slp/consult_form.html'
success_message = "SLP consultation updated successfully!"
allowed_roles = [User.Role.ADMIN, User.Role.SLP]
def get_success_url(self):
return reverse_lazy('slp:consult_detail', kwargs={'pk': self.object.pk})
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form_title'] = f'Update SLP Consultation - {self.object.patient.mrn}'
context['submit_text'] = 'Update Consultation'
if hasattr(self.object, 'history'):
context['history'] = self.object.history.all()[:10]
return context
class SLPAssessmentListView(LoginRequiredMixin, TenantFilterMixin, PaginationMixin,
HTMXResponseMixin, ListView):
"""
SLP assessment list view (SLP-F-2).
Features:
- Filter by patient, provider, date range
- Search by patient name/MRN
- Role-based filtering
"""
model = SLPAssessment
template_name = 'slp/assessment_list.html'
htmx_template_name = 'slp/partials/assessment_list_partial.html'
context_object_name = 'assessments'
paginate_by = 25
def get_queryset(self):
"""Get filtered queryset."""
queryset = super().get_queryset()
user = self.request.user
if user.role == User.Role.SLP:
queryset = queryset.filter(provider__user=user)
search_query = self.request.GET.get('search', '').strip()
if search_query:
queryset = queryset.filter(
Q(patient__first_name_en__icontains=search_query) |
Q(patient__last_name_en__icontains=search_query) |
Q(patient__mrn__icontains=search_query)
)
patient_id = self.request.GET.get('patient')
if patient_id:
queryset = queryset.filter(patient_id=patient_id)
date_from = self.request.GET.get('date_from')
if date_from:
queryset = queryset.filter(assessment_date__gte=date_from)
date_to = self.request.GET.get('date_to')
if date_to:
queryset = queryset.filter(assessment_date__lte=date_to)
return queryset.select_related('patient', 'provider', 'appointment', 'signed_by').order_by('-assessment_date')
def get_context_data(self, **kwargs):
"""Add unsigned assessments count for current user."""
context = super().get_context_data(**kwargs)
user = self.request.user
unsigned_query = SLPAssessment.objects.filter(tenant=user.tenant, signed_by__isnull=True)
if user.role == User.Role.SLP:
unsigned_query = unsigned_query.filter(provider=user)
context['unsigned_count'] = unsigned_query.count()
context['unsigned_items'] = unsigned_query.select_related('patient', 'provider').order_by('-assessment_date')[:10]
return context
class SLPAssessmentDetailView(LoginRequiredMixin, TenantFilterMixin, DetailView):
"""
SLP assessment detail view (SLP-F-2).
Features:
- Diagnosis statement, case history
- Prenatal/perinatal/postnatal history
- Developmental history
- Medical status
- Speech/language detail (GFTA-3/JAT, SSI scores)
- Oral mechanism
- Language skills via Rossetti scale (domain/age levels)
- Joint-attention skills (present/absent) summaries
- Clinical summary
- Recommendations (frequency 1-2x/wk, 45-60 mins)
- Plan & referral rules
"""
model = SLPAssessment
template_name = 'slp/assessment_detail.html'
context_object_name = 'assessment'
def get_context_data(self, **kwargs):
"""Add related interventions."""
context = super().get_context_data(**kwargs)
# Get related interventions
context['interventions'] = SLPIntervention.objects.filter(
patient=self.object.patient,
session_date__gte=self.object.assessment_date
).order_by('-session_date')[:10]
return context
class SLPAssessmentCreateView(ConsentRequiredMixin, LoginRequiredMixin, RolePermissionMixin,
AuditLogMixin, SuccessMessageMixin, CreateView):
"""
SLP assessment creation view (SLP-F-2).
Features:
- Complete assessment/reassessment form
- All sections from diagnosis to recommendations
- Consent verification enforced before creation
"""
model = SLPAssessment
form_class = SLPAssessmentForm
template_name = 'slp/assessment_form.html'
success_message = "SLP assessment recorded successfully!"
allowed_roles = [User.Role.ADMIN, User.Role.SLP]
# Consent enforcement
consent_service_type = 'SLP'
consent_error_message = (
"Patient must sign SLP therapy consent before assessment can be documented."
)
def get_success_url(self):
return reverse_lazy('slp:assessment_detail', kwargs={'pk': self.object.pk})
def get_patient(self):
"""Get patient for consent verification."""
patient_id = self.request.GET.get('patient')
appointment_id = self.request.GET.get('appointment_id')
if patient_id:
return Patient.objects.get(
pk=patient_id,
tenant=self.request.user.tenant
)
elif appointment_id:
appointment = Appointment.objects.get(
pk=appointment_id,
tenant=self.request.user.tenant
)
return appointment.patient
return None
def get_initial(self):
"""Pre-populate form fields from appointment."""
initial = super().get_initial()
appointment_id = self.request.GET.get('appointment_id')
if appointment_id:
try:
appointment = Appointment.objects.get(pk=appointment_id, tenant=self.request.user.tenant)
initial['patient'] = appointment.patient.pk
initial['appointment'] = appointment.pk
initial['assessment_date'] = appointment.appointment_date
initial['provider'] = self.request.user.pk
except Appointment.DoesNotExist:
pass
patient_id = self.request.GET.get('patient')
if patient_id and not appointment_id:
initial['patient'] = patient_id
initial['provider'] = self.request.user.pk
initial['assessment_date'] = timezone.now().date()
return initial
def form_valid(self, form):
form.instance.tenant = self.request.user.tenant
if not form.instance.provider:
form.instance.provider = self.request.user
if not form.instance.assessment_date:
form.instance.assessment_date = timezone.now().date()
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form_title'] = 'SLP Assessment (SLP-F-2)'
context['submit_text'] = 'Save Assessment'
patient_id = self.request.GET.get('patient')
if patient_id:
try:
context['patient'] = Patient.objects.get(pk=patient_id, tenant=self.request.user.tenant)
except Patient.DoesNotExist:
pass
appointment_id = self.request.GET.get('appointment_id')
if appointment_id:
try:
context['appointment'] = Appointment.objects.get(pk=appointment_id, tenant=self.request.user.tenant)
context['patient'] = context['appointment'].patient
except Appointment.DoesNotExist:
pass
return context
class SLPAssessmentUpdateView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin,
AuditLogMixin, SuccessMessageMixin, UpdateView):
"""SLP assessment update view (SLP-F-2)."""
model = SLPAssessment
form_class = SLPAssessmentForm
template_name = 'slp/assessment_form.html'
success_message = "SLP assessment updated successfully!"
allowed_roles = [User.Role.ADMIN, User.Role.SLP]
def get_success_url(self):
return reverse_lazy('slp:assessment_detail', kwargs={'pk': self.object.pk})
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form_title'] = f'Update SLP Assessment - {self.object.patient.mrn}'
context['submit_text'] = 'Update Assessment'
if hasattr(self.object, 'history'):
context['history'] = self.object.history.all()[:10]
return context
class SLPInterventionListView(LoginRequiredMixin, TenantFilterMixin, PaginationMixin,
HTMXResponseMixin, ListView):
"""
SLP intervention list view (SLP-F-3).
Features:
- Filter by patient, provider, date range
- Search by patient name/MRN
- Role-based filtering
"""
model = SLPIntervention
template_name = 'slp/intervention_list.html'
htmx_template_name = 'slp/partials/intervention_list_partial.html'
context_object_name = 'interventions'
paginate_by = 25
def get_queryset(self):
queryset = super().get_queryset()
user = self.request.user
if user.role == User.Role.SLP:
queryset = queryset.filter(provider__user=user)
search_query = self.request.GET.get('search', '').strip()
if search_query:
queryset = queryset.filter(
Q(patient__first_name_en__icontains=search_query) |
Q(patient__last_name_en__icontains=search_query) |
Q(patient__mrn__icontains=search_query)
)
patient_id = self.request.GET.get('patient')
if patient_id:
queryset = queryset.filter(patient_id=patient_id)
date_from = self.request.GET.get('date_from')
if date_from:
queryset = queryset.filter(session_date__gte=date_from)
date_to = self.request.GET.get('date_to')
if date_to:
queryset = queryset.filter(session_date__lte=date_to)
return queryset.select_related('patient', 'provider', 'appointment', 'previous_session', 'signed_by').order_by('-session_date')
def get_context_data(self, **kwargs):
"""Add unsigned interventions count for current user."""
context = super().get_context_data(**kwargs)
user = self.request.user
unsigned_query = SLPIntervention.objects.filter(tenant=user.tenant, signed_by__isnull=True)
if user.role == User.Role.SLP:
unsigned_query = unsigned_query.filter(provider=user)
context['unsigned_count'] = unsigned_query.count()
context['unsigned_items'] = unsigned_query.select_related('patient', 'provider').order_by('-session_date')[:10]
return context
class SLPInterventionDetailView(LoginRequiredMixin, TenantFilterMixin, DetailView):
"""
SLP intervention detail view (SLP-F-3).
Features:
- Session number/time
- Target#1/2 entries with SOAP (Subjective/Objective/Assessment/Plan)
- Prompt strategies
- Preview previous SLP-F-3 relation
"""
model = SLPIntervention
template_name = 'slp/intervention_detail.html'
context_object_name = 'intervention'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Get targets
context['targets'] = self.object.targets.all()
# Get previous session if linked
if self.object.previous_session:
context['previous_session'] = self.object.previous_session
context['previous_targets'] = self.object.previous_session.targets.all()
return context
class SLPInterventionCreateView(ConsentRequiredMixin, LoginRequiredMixin, RolePermissionMixin,
AuditLogMixin, SuccessMessageMixin, CreateView):
"""
SLP intervention creation view (SLP-F-3).
Features:
- Session number/time
- Target entries with SOAP + prompt strategies
- Link to previous session
- Consent verification enforced before creation
"""
model = SLPIntervention
form_class = SLPInterventionForm
template_name = 'slp/intervention_form.html'
success_message = "SLP intervention recorded successfully!"
allowed_roles = [User.Role.ADMIN, User.Role.SLP]
# Consent enforcement
consent_service_type = 'SLP'
consent_error_message = (
"Patient must sign SLP therapy consent before intervention can be documented."
)
def get_success_url(self):
return reverse_lazy('slp:intervention_detail', kwargs={'pk': self.object.pk})
def get_patient(self):
"""Get patient for consent verification."""
patient_id = self.request.GET.get('patient')
appointment_id = self.request.GET.get('appointment_id')
if patient_id:
return Patient.objects.get(
pk=patient_id,
tenant=self.request.user.tenant
)
elif appointment_id:
appointment = Appointment.objects.get(
pk=appointment_id,
tenant=self.request.user.tenant
)
return appointment.patient
return None
def get_initial(self):
"""Pre-populate form fields from appointment."""
initial = super().get_initial()
appointment_id = self.request.GET.get('appointment_id')
if appointment_id:
try:
appointment = Appointment.objects.get(pk=appointment_id, tenant=self.request.user.tenant)
initial['patient'] = appointment.patient.pk
initial['appointment'] = appointment.pk
initial['session_date'] = appointment.appointment_date
initial['session_time'] = appointment.start_time
initial['provider'] = self.request.user.pk
# Auto-calculate session number based on previous sessions
previous_count = SLPIntervention.objects.filter(
patient=appointment.patient,
tenant=self.request.user.tenant
).count()
initial['session_number'] = previous_count + 1
except Appointment.DoesNotExist:
pass
patient_id = self.request.GET.get('patient')
if patient_id and not appointment_id:
initial['patient'] = patient_id
initial['provider'] = self.request.user.pk
initial['session_date'] = timezone.now().date()
initial['session_time'] = timezone.now().time()
# Auto-calculate session number
try:
patient = Patient.objects.get(pk=patient_id, tenant=self.request.user.tenant)
previous_count = SLPIntervention.objects.filter(
patient=patient,
tenant=self.request.user.tenant
).count()
initial['session_number'] = previous_count + 1
except Patient.DoesNotExist:
pass
return initial
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form_title'] = 'SLP Intervention (SLP-F-3)'
context['submit_text'] = 'Save Intervention'
patient_id = self.request.GET.get('patient')
if patient_id:
try:
patient = Patient.objects.get(pk=patient_id, tenant=self.request.user.tenant)
context['patient'] = patient
# Get previous sessions for linking
context['previous_sessions'] = SLPIntervention.objects.filter(
patient=patient
).order_by('-session_date')[:10]
except Patient.DoesNotExist:
pass
appointment_id = self.request.GET.get('appointment_id')
if appointment_id:
try:
context['appointment'] = Appointment.objects.get(pk=appointment_id, tenant=self.request.user.tenant)
context['patient'] = context['appointment'].patient
# Get previous sessions for this patient
context['previous_sessions'] = SLPIntervention.objects.filter(
patient=context['appointment'].patient
).order_by('-session_date')[:10]
except Appointment.DoesNotExist:
pass
return context
def form_valid(self, form):
form.instance.tenant = self.request.user.tenant
if not form.instance.provider:
form.instance.provider = self.request.user
if not form.instance.session_date:
form.instance.session_date = timezone.now().date()
return super().form_valid(form)
class SLPInterventionUpdateView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin,
AuditLogMixin, SuccessMessageMixin, UpdateView):
"""SLP intervention update view (SLP-F-3)."""
model = SLPIntervention
form_class = SLPInterventionForm
template_name = 'slp/intervention_form.html'
success_message = "SLP intervention updated successfully!"
allowed_roles = [User.Role.ADMIN, User.Role.SLP]
def get_success_url(self):
return reverse_lazy('slp:intervention_detail', kwargs={'pk': self.object.pk})
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form_title'] = f'Update SLP Intervention - {self.object.patient.mrn}'
context['submit_text'] = 'Update Intervention'
if hasattr(self.object, 'history'):
context['history'] = self.object.history.all()[:10]
return context
class SLPProgressReportListView(LoginRequiredMixin, TenantFilterMixin, PaginationMixin, ListView):
"""
SLP progress report list view (SLP-F-4).
Features:
- Filter by patient, provider, date range
- Search by patient name/MRN
"""
model = SLPProgressReport
template_name = 'slp/progress_list.html'
context_object_name = 'progress_reports'
paginate_by = 25
def get_queryset(self):
queryset = super().get_queryset()
user = self.request.user
if user.role == User.Role.SLP:
queryset = queryset.filter(provider__user=user)
search_query = self.request.GET.get('search', '').strip()
if search_query:
queryset = queryset.filter(
Q(patient__first_name_en__icontains=search_query) |
Q(patient__last_name_en__icontains=search_query) |
Q(patient__mrn__icontains=search_query)
)
patient_id = self.request.GET.get('patient')
if patient_id:
queryset = queryset.filter(patient_id=patient_id)
return queryset.select_related('patient', 'provider', 'signed_by').order_by('-report_date')
def get_context_data(self, **kwargs):
"""Add unsigned progress reports count for current user."""
context = super().get_context_data(**kwargs)
user = self.request.user
unsigned_query = SLPProgressReport.objects.filter(tenant=user.tenant, signed_by__isnull=True)
if user.role == User.Role.SLP:
unsigned_query = unsigned_query.filter(provider=user)
context['unsigned_count'] = unsigned_query.count()
context['unsigned_items'] = unsigned_query.select_related('patient', 'provider').order_by('-report_date')[:10]
return context
class SLPProgressReportDetailView(LoginRequiredMixin, TenantFilterMixin, DetailView):
"""
SLP progress report detail view (SLP-F-4).
Features:
- Demographic snapshot
- Sessions scheduled/attended
- Final diagnosis
- Objectives & progress (% accuracy)
- Plan (continue/add/fade prompts/generalization)
- Overall progress, participation, attendance, carryover
- Prognosis, recommendations with package & sessions count
- Reassessment tick
"""
model = SLPProgressReport
template_name = 'slp/progress_detail.html'
context_object_name = 'report'
class SLPProgressReportCreateView(LoginRequiredMixin, RolePermissionMixin, AuditLogMixin,
SuccessMessageMixin, CreateView):
"""
SLP progress report creation view (SLP-F-4).
Features:
- Complete progress report
- Aggregates sessions and produces goals with % accuracy
"""
model = SLPProgressReport
form_class = SLPProgressReportForm
template_name = 'slp/progress_form.html'
success_message = "SLP progress report created successfully!"
allowed_roles = [User.Role.ADMIN, User.Role.SLP]
def get_success_url(self):
return reverse_lazy('slp:progress_report_detail', kwargs={'pk': self.object.pk})
def form_valid(self, form):
form.instance.tenant = self.request.user.tenant
form.instance.provider = self.request.user
if not form.instance.report_date:
form.instance.report_date = timezone.now().date()
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form_title'] = 'SLP Progress Report (SLP-F-4)'
context['submit_text'] = 'Save Report'
patient_id = self.request.GET.get('patient')
if patient_id:
try:
patient = Patient.objects.get(pk=patient_id, tenant=self.request.user.tenant)
context['patient'] = patient
# Get session statistics
interventions = SLPIntervention.objects.filter(patient=patient)
context['session_stats'] = {
'total_sessions': interventions.count(),
'date_range': {
'start': interventions.order_by('session_date').first().session_date if interventions.exists() else None,
'end': interventions.order_by('-session_date').first().session_date if interventions.exists() else None,
}
}
except Patient.DoesNotExist:
pass
return context
class SLPProgressReportUpdateView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin,
AuditLogMixin, SuccessMessageMixin, UpdateView):
"""SLP progress report update view (SLP-F-4)."""
model = SLPProgressReport
form_class = SLPProgressReportForm
template_name = 'slp/progress_form.html'
success_message = "SLP progress report updated successfully!"
allowed_roles = [User.Role.ADMIN, User.Role.SLP]
def get_success_url(self):
return reverse_lazy('slp:progress_report_detail', kwargs={'pk': self.object.pk})
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form_title'] = f'Update SLP Progress Report - {self.object.patient.mrn}'
context['submit_text'] = 'Update Report'
if hasattr(self.object, 'history'):
context['history'] = self.object.history.all()[:10]
return context
class PatientProgressView(LoginRequiredMixin, TenantFilterMixin, DetailView):
"""
Patient progress overview view.
Shows all SLP documentation for a specific patient:
- Progress reports
- Assessments
- Interventions
- Consultations
"""
model = Patient
template_name = 'slp/patient_progress.html'
context_object_name = 'patient'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
patient = self.object
# Get all SLP documentation for this patient
context['progress_reports'] = SLPProgressReport.objects.filter(
patient=patient
).select_related('provider').order_by('-report_date')
context['assessments'] = SLPAssessment.objects.filter(
patient=patient
).select_related('provider').order_by('-assessment_date')
context['interventions'] = SLPIntervention.objects.filter(
patient=patient
).select_related('provider').order_by('-session_date')[:20]
context['consults'] = SLPConsult.objects.filter(
patient=patient
).select_related('provider').order_by('-consultation_date')
# Calculate statistics
interventions_all = SLPIntervention.objects.filter(patient=patient)
context['stats'] = {
'total_sessions': interventions_all.count(),
'total_reports': context['progress_reports'].count(),
'total_assessments': context['assessments'].count(),
'total_consults': context['consults'].count(),
}
return context