agdar/aba/views.py
Marwan Alwali 2f1681b18c update
2025-11-11 13:44:48 +03:00

1003 lines
35 KiB
Python

"""
ABA views for the Tenhal Multidisciplinary Healthcare Platform.
This module contains views for ABA (Applied Behavior Analysis) documentation including:
- ABA consultations (ABA-F-1)
- Functional behavior interviews
- Behavior tracking
- Antecedents and consequences analysis
- Recommendations
"""
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib import messages
from django.db.models import Q
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
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,
SignedDocumentEditPreventionMixin,
)
from core.models import User, Patient
from appointments.models import Appointment
from .models import ABAConsult, ABABehavior, ABASession, ABASkillTarget
from .forms import ABAConsultForm, ABABehaviorFormSet, ABASessionForm, ABASkillTargetFormSet
class ABAConsultListView(LoginRequiredMixin, TenantFilterMixin, PaginationMixin,
HTMXResponseMixin, ListView):
"""
ABA consultation list view (ABA-F-1).
Features:
- Filter by patient, provider, date range
- Search by patient name/MRN
- Role-based filtering
"""
model = ABAConsult
template_name = 'aba/consult_list.html'
htmx_template_name = 'aba/partials/consult_list_partial.html'
context_object_name = 'consults'
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.ABA:
queryset = queryset.filter(provider=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)
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').order_by('-consultation_date')
class ABAConsultDetailView(LoginRequiredMixin, TenantFilterMixin, DetailView):
"""
ABA consultation detail view (ABA-F-1).
Features:
- Full consultation details
- Reason of referral
- Parental and school concerns
- Respondents and interviewer info
- Diagnosed conditions
- Interaction hours
- Physiological/medical factors
- Behaviors with frequency, duration, intensity
- Antecedents (likely/least likely contexts)
- Recommendations
"""
model = ABAConsult
template_name = 'aba/consult_detail.html'
context_object_name = 'consult'
def get_context_data(self, **kwargs):
"""Add related behaviors."""
context = super().get_context_data(**kwargs)
# Get behaviors
context['behaviors'] = self.object.behaviors.all()
return context
class ABAConsultCreateView(ConsentRequiredMixin, LoginRequiredMixin, RolePermissionMixin,
AuditLogMixin, SuccessMessageMixin, CreateView):
"""
ABA consultation creation view (ABA-F-1).
Features:
- Complete functional interview form
- Reason of referral (radio options)
- Parental concern, school concern
- Respondents, interviewer, diagnosed
- Interaction hours
- Physiological/medical factors (booleans + text)
- Behaviors (1..n) with frequency, duration, intensity
- Antecedents (likely/least likely contexts)
- Recommendations
- Consent verification enforced before creation
"""
model = ABAConsult
form_class = ABAConsultForm
template_name = 'aba/consult_form.html'
success_message = _("ABA consultation recorded successfully!")
allowed_roles = [User.Role.ADMIN, User.Role.ABA]
# Consent enforcement
consent_service_type = 'ABA'
consent_error_message = _(
"Patient must sign ABA therapy consent and photo/video consent "
"before consultation can be documented."
)
def get_success_url(self):
"""Redirect to consult detail."""
return reverse_lazy('aba: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 get_context_data(self, **kwargs):
"""Add form title, patient/appointment info, and behavior formset."""
context = super().get_context_data(**kwargs)
context['form_title'] = _('ABA Consultation (ABA-F-1)')
context['submit_text'] = _('Save Consultation')
# Add behavior formset
if self.request.POST:
context['behavior_formset'] = ABABehaviorFormSet(self.request.POST)
else:
context['behavior_formset'] = ABABehaviorFormSet()
# 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
def form_valid(self, form):
"""Set tenant and provider, handle behavior formset."""
# 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()
# Get the formset
context = self.get_context_data()
behavior_formset = context['behavior_formset']
# Validate formset
if behavior_formset.is_valid():
# Save the consultation first
self.object = form.save()
# Save the behaviors
behavior_formset.instance = self.object
behavior_formset.save()
return super().form_valid(form)
else:
# Formset is invalid, return to form with errors
return self.form_invalid(form)
# ============================================================================
# PDF Generation Views
# ============================================================================
from core.pdf_service import BasePDFGenerator
from django.shortcuts import redirect
class ABAConsultPDFGenerator(BasePDFGenerator):
"""PDF generator for ABA Consultation (ABA-F-1)."""
def get_document_title(self):
"""Return document title in English and Arabic."""
consult = self.document
return (
f"ABA Consultation (ABA-F-1) - {consult.patient.mrn}",
"استشارة تحليل السلوك التطبيقي"
)
def get_pdf_filename(self):
"""Return PDF filename."""
consult = self.document
date_str = consult.consultation_date.strftime('%Y%m%d')
return f"aba_consultation_{consult.patient.mrn}_{date_str}.pdf"
def get_document_sections(self):
"""Return document sections to render."""
consult = self.document
patient = consult.patient
sections = []
# Patient Information Section
patient_name_ar = f"{patient.first_name_ar} {patient.last_name_ar}" if patient.first_name_ar else ""
sections.append({
'heading_en': 'Patient Information',
'heading_ar': 'معلومات المريض',
'type': 'table',
'content': [
('Name', 'الاسم', f"{patient.first_name_en} {patient.last_name_en}", patient_name_ar),
('MRN', 'رقم السجل الطبي', patient.mrn, ""),
('Date of Birth', 'تاريخ الميلاد', patient.date_of_birth.strftime('%Y-%m-%d'), ""),
('Age', 'العمر', f"{patient.age} years", ""),
]
})
# Consultation Details Section
sections.append({
'heading_en': 'Consultation Details',
'heading_ar': 'تفاصيل الاستشارة',
'type': 'table',
'content': [
('Date', 'التاريخ', consult.consultation_date.strftime('%Y-%m-%d'), ""),
('Provider', 'مقدم الخدمة', consult.provider.get_full_name() if consult.provider else 'N/A', ""),
('Reason of Referral', 'سبب الإحالة', consult.get_reason_of_referral_display(), ""),
('Diagnosed Condition', 'الحالة المشخصة', consult.diagnosed_condition or 'N/A', ""),
('Interaction Hours/Day', 'ساعات التفاعل/اليوم', str(consult.interaction_hours_per_day) if consult.interaction_hours_per_day else 'N/A', ""),
('Signed By', 'موقع من قبل', consult.signed_by.get_full_name() if consult.signed_by else 'Not signed', ""),
]
})
# Interview Information
if consult.respondents or consult.interviewer:
sections.append({
'heading_en': 'Interview Information',
'heading_ar': 'معلومات المقابلة',
'type': 'table',
'content': [
('Respondents', 'المستجيبون', consult.respondents or 'N/A', ""),
('Interviewer', 'المحاور', consult.interviewer or 'N/A', ""),
]
})
# Concerns
if consult.parental_concern:
sections.append({
'heading_en': 'Parental Concern',
'heading_ar': 'قلق الوالدين',
'type': 'text',
'content': [consult.parental_concern]
})
if consult.school_concern:
sections.append({
'heading_en': 'School Concern',
'heading_ar': 'قلق المدرسة',
'type': 'text',
'content': [consult.school_concern]
})
# Factors
if consult.physiological_factors:
sections.append({
'heading_en': 'Physiological Factors',
'heading_ar': 'العوامل الفسيولوجية',
'type': 'text',
'content': [consult.physiological_factors]
})
if consult.medical_factors:
sections.append({
'heading_en': 'Medical Factors',
'heading_ar': 'العوامل الطبية',
'type': 'text',
'content': [consult.medical_factors]
})
# Behaviors
behaviors = consult.behaviors.all()
if behaviors:
behavior_content = []
for behavior in behaviors:
behavior_text = f"<b>{behavior.behavior_description}</b><br/>"
behavior_text += f"Frequency: {behavior.get_frequency_display()}, "
behavior_text += f"Intensity: {behavior.get_intensity_display()}"
if behavior.duration:
behavior_text += f", Duration: {behavior.duration}"
if behavior.antecedents_likely:
behavior_text += f"<br/>Most Likely Context: {behavior.antecedents_likely}"
if behavior.antecedents_least_likely:
behavior_text += f"<br/>Least Likely Context: {behavior.antecedents_least_likely}"
if behavior.consequences:
behavior_text += f"<br/>Consequences: {behavior.consequences}"
behavior_content.append(behavior_text)
sections.append({
'heading_en': 'Behaviors',
'heading_ar': 'السلوكيات',
'type': 'text',
'content': behavior_content
})
# Recommendations
if consult.recommendations:
sections.append({
'heading_en': 'Recommendations',
'heading_ar': 'التوصيات',
'type': 'text',
'content': [consult.recommendations]
})
return sections
class ABAConsultPDFView(LoginRequiredMixin, TenantFilterMixin, View):
"""Generate PDF for ABA consultation."""
def get(self, request, pk):
"""Generate and return PDF."""
consult = get_object_or_404(
ABAConsult.objects.select_related(
'patient', 'provider', 'tenant', 'signed_by'
).prefetch_related('behaviors'),
pk=pk,
tenant=request.user.tenant
)
pdf_generator = ABAConsultPDFGenerator(consult, request)
view_mode = request.GET.get('view', 'download')
return pdf_generator.generate_pdf(view_mode=view_mode)
class ABAConsultEmailPDFView(LoginRequiredMixin, TenantFilterMixin, View):
"""Email ABA consultation PDF to patient."""
def post(self, request, pk):
"""Send PDF via email."""
consult = get_object_or_404(
ABAConsult.objects.select_related(
'patient', 'provider', 'tenant'
),
pk=pk,
tenant=request.user.tenant
)
email_address = request.POST.get('email_address', '').strip()
custom_message = request.POST.get('email_message', '').strip()
if not email_address:
messages.error(request, _('Email address is required.'))
return redirect('aba:consult_detail', pk=pk)
pdf_generator = ABAConsultPDFGenerator(consult, request)
subject = f"ABA Consultation - {consult.patient.mrn}"
body = f"""
Dear {consult.patient.first_name_en} {consult.patient.last_name_en},
Please find attached your ABA consultation details.
Consultation Date: {consult.consultation_date.strftime('%Y-%m-%d')}
Provider: {consult.provider.get_full_name() if consult.provider else 'N/A'}
Best regards,
{consult.tenant.name}
"""
success, message = pdf_generator.send_email(
email_address=email_address,
subject=subject,
body=body,
custom_message=custom_message
)
if success:
messages.success(request, _('PDF sent to %(email)s successfully!') % {'email': email_address})
else:
messages.error(request, _('Failed to send email: %(error)s') % {'error': message})
return redirect('aba:consult_detail', pk=pk)
class ABASessionSignView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin, View):
"""
Sign an ABA session.
Features:
- Only the provider or admin can sign
- Records signature timestamp and user
- Prevents re-signing already signed sessions
- Warns user that no editing will be allowed after signing
"""
allowed_roles = [User.Role.ADMIN, User.Role.ABA]
def post(self, request, pk):
"""Sign the session."""
session = get_object_or_404(
ABASession,
pk=pk,
tenant=request.user.tenant
)
# Check if already signed
if session.signed_by:
messages.warning(
request,
_("This session has already been signed.")
)
return HttpResponseRedirect(
reverse_lazy('aba:session_detail', kwargs={'pk': pk})
)
# Check if user is the provider or admin
if session.provider != request.user and request.user.role != User.Role.ADMIN:
messages.error(
request,
_("Only the session provider or an administrator can sign this session.")
)
return HttpResponseRedirect(
reverse_lazy('aba:session_detail', kwargs={'pk': pk})
)
# Sign the session
session.signed_by = request.user
session.signed_at = timezone.now()
session.save()
messages.success(
request,
_("Session signed successfully! This document can no longer be edited.")
)
return HttpResponseRedirect(
reverse_lazy('aba:session_detail', kwargs={'pk': pk})
)
class ABAConsultUpdateView(SignedDocumentEditPreventionMixin, LoginRequiredMixin,
RolePermissionMixin, TenantFilterMixin,
AuditLogMixin, SuccessMessageMixin, UpdateView):
"""
ABA consultation update view (ABA-F-1).
Features:
- Update consultation details
- Version history
- Audit trail
- Prevents editing of signed documents
"""
model = ABAConsult
form_class = ABAConsultForm
template_name = 'aba/consult_form.html'
success_message = _("ABA consultation updated successfully!")
allowed_roles = [User.Role.ADMIN, User.Role.ABA]
def get_success_url(self):
"""Redirect to consult detail."""
return reverse_lazy('aba:consult_detail', kwargs={'pk': self.object.pk})
def get_context_data(self, **kwargs):
"""Add form title, behavior formset, and history."""
context = super().get_context_data(**kwargs)
context['form_title'] = _('Update ABA Consultation - %(mrn)s') % {'mrn': self.object.patient.mrn}
context['submit_text'] = _('Update Consultation')
context['patient'] = self.object.patient
# Add behavior formset
if self.request.POST:
context['behavior_formset'] = ABABehaviorFormSet(self.request.POST, instance=self.object)
else:
context['behavior_formset'] = ABABehaviorFormSet(instance=self.object)
# Add version history if available
if hasattr(self.object, 'history'):
context['history'] = self.object.history.all()[:10]
return context
def form_valid(self, form):
"""Handle behavior formset."""
# Get the formset
context = self.get_context_data()
behavior_formset = context['behavior_formset']
# Validate formset
if behavior_formset.is_valid():
# Save the consultation first
self.object = form.save()
# Save the behaviors
behavior_formset.instance = self.object
behavior_formset.save()
return super().form_valid(form)
else:
# Formset is invalid, return to form with errors
return self.form_invalid(form)
class PatientABAHistoryView(LoginRequiredMixin, TenantFilterMixin, ListView):
"""
Patient ABA history view.
Features:
- All ABA consultations for specific patient
- Behavior tracking over time
- Progress visualization
"""
model = ABAConsult
template_name = 'aba/patient_history.html'
context_object_name = 'consults'
paginate_by = 50
def get_queryset(self):
"""Get all consults for patient."""
patient_id = self.kwargs.get('patient_id')
return ABAConsult.objects.filter(
patient_id=patient_id,
tenant=self.request.user.tenant
).order_by('-consultation_date')
def get_context_data(self, **kwargs):
"""Add patient and behavior tracking data."""
import json
context = super().get_context_data(**kwargs)
patient_id = self.kwargs.get('patient_id')
context['patient'] = get_object_or_404(
Patient,
pk=patient_id,
tenant=self.request.user.tenant
)
# Get all behaviors across consultations for this patient
consults = self.get_queryset()
all_behaviors = []
high_intensity_count = 0
for consult in consults:
behaviors = consult.behaviors.all()
for behavior in behaviors:
all_behaviors.append({
'date': consult.consultation_date.strftime('%Y-%m-%d'),
'behavior': behavior.behavior_description,
'frequency': behavior.get_frequency_display(),
'intensity': behavior.get_intensity_display(),
})
# Count high intensity behaviors
if behavior.intensity == 'SEVERE':
high_intensity_count += 1
# Add statistics
context['total_behaviors_count'] = len(all_behaviors)
context['high_intensity_count'] = high_intensity_count
# Properly serialize to JSON for chart
context['all_behaviors'] = json.dumps(all_behaviors)
return context
class BehaviorTrackingView(LoginRequiredMixin, TenantFilterMixin, ListView):
"""
Behavior tracking view across all patients.
Features:
- Track specific behaviors
- Frequency analysis
- Intensity trends
- Intervention effectiveness
"""
model = ABABehavior
template_name = 'aba/behavior_tracking.html'
context_object_name = 'behaviors'
paginate_by = 50
def get_queryset(self):
"""Get behaviors with filters."""
queryset = ABABehavior.objects.filter(
consult__tenant=self.request.user.tenant
).select_related('consult', 'consult__patient')
# Apply filters
behavior_type = self.request.GET.get('behavior_type')
if behavior_type:
queryset = queryset.filter(behavior_description__icontains=behavior_type)
frequency = self.request.GET.get('frequency')
if frequency:
queryset = queryset.filter(frequency=frequency)
intensity = self.request.GET.get('intensity')
if intensity:
queryset = queryset.filter(intensity=intensity)
return queryset.order_by('-consult__consultation_date')
def get_context_data(self, **kwargs):
"""Add statistics."""
context = super().get_context_data(**kwargs)
queryset = self.get_queryset()
# Add statistics
context['stats'] = {
'total_behaviors': queryset.count(),
'high_intensity': queryset.filter(intensity='SEVERE').count(),
'daily_frequency': queryset.filter(frequency='DAILY').count(),
}
context['frequency_choices'] = ABABehavior.Frequency.choices
context['intensity_choices'] = ABABehavior.Intensity.choices
return context
class RecommendationTrackingView(LoginRequiredMixin, TenantFilterMixin, ListView):
"""
Recommendation tracking view.
Features:
- Track recommendations across consultations
- Implementation status
- Effectiveness analysis
"""
model = ABAConsult
template_name = 'aba/recommendation_tracking.html'
context_object_name = 'consults'
paginate_by = 25
def get_queryset(self):
"""Get consults with recommendations."""
queryset = super().get_queryset()
# Filter only consults with recommendations
queryset = queryset.exclude(recommendations='').exclude(recommendations__isnull=True)
# Apply filters
patient_id = self.request.GET.get('patient')
if patient_id:
queryset = queryset.filter(patient_id=patient_id)
return queryset.select_related('patient', 'provider').order_by('-consultation_date')
class ABASessionListView(LoginRequiredMixin, TenantFilterMixin, PaginationMixin,
HTMXResponseMixin, ListView):
"""
ABA session list view.
Features:
- Filter by patient, provider, date range, session type
- Search by patient name/MRN
- Role-based filtering
- Shows unsigned sessions notification
"""
model = ABASession
template_name = 'aba/session_list.html'
htmx_template_name = 'aba/partials/session_list_partial.html'
context_object_name = 'sessions'
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.ABA:
queryset = queryset.filter(provider=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)
session_type = self.request.GET.get('session_type')
if session_type:
queryset = queryset.filter(session_type=session_type)
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', 'signed_by').order_by('-session_date')
def get_context_data(self, **kwargs):
"""Add unsigned sessions count for current user."""
context = super().get_context_data(**kwargs)
user = self.request.user
# Get unsigned sessions for current user
unsigned_sessions_query = ABASession.objects.filter(
tenant=user.tenant,
signed_by__isnull=True
)
# Filter by provider if ABA role
if user.role == User.Role.ABA:
unsigned_sessions_query = unsigned_sessions_query.filter(provider=user)
context['unsigned_sessions_count'] = unsigned_sessions_query.count()
context['unsigned_sessions'] = unsigned_sessions_query.select_related(
'patient', 'provider'
).order_by('-session_date')[:10] # Show latest 10 unsigned sessions
return context
class ABASessionDetailView(LoginRequiredMixin, TenantFilterMixin, DetailView):
"""
ABA session detail view.
Features:
- Full session details
- Session type, duration
- Engagement and cooperation levels
- Target behaviors and interventions
- Skill targets with mastery tracking
- Progress notes and recommendations
"""
model = ABASession
template_name = 'aba/session_detail.html'
context_object_name = 'session'
def get_context_data(self, **kwargs):
"""Add skill targets and progress data."""
context = super().get_context_data(**kwargs)
# Get skill targets
context['skill_targets'] = self.object.skill_targets.all()
# Get previous sessions for comparison
context['previous_sessions'] = ABASession.objects.filter(
patient=self.object.patient,
session_date__lt=self.object.session_date
).order_by('-session_date')[:5]
return context
class ABASessionCreateView(ConsentRequiredMixin, LoginRequiredMixin, RolePermissionMixin,
AuditLogMixin, SuccessMessageMixin, CreateView):
"""
ABA session creation view.
Features:
- Session date, type, duration
- Engagement and cooperation levels (1-5)
- Target behaviors and interventions
- Activities performed
- Behavioral observations
- Progress notes
- Recommendations and home program
- Skill targets with mastery tracking
- Consent verification enforced before creation
"""
model = ABASession
form_class = ABASessionForm
template_name = 'aba/session_form.html'
success_message = _("ABA session recorded successfully!")
allowed_roles = [User.Role.ADMIN, User.Role.ABA]
# Consent enforcement
consent_service_type = 'ABA'
consent_error_message = _(
"Patient must sign ABA therapy consent and photo/video consent "
"before session can be documented."
)
def get_success_url(self):
"""Redirect to session detail."""
return reverse_lazy('aba:session_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_context_data(self, **kwargs):
"""Add form title, patient/appointment info, and skill target formset."""
context = super().get_context_data(**kwargs)
context['form_title'] = _('ABA Session Note')
context['submit_text'] = _('Save Session')
# Add skill target formset
if self.request.POST:
context['skill_target_formset'] = ABASkillTargetFormSet(self.request.POST)
else:
context['skill_target_formset'] = ABASkillTargetFormSet()
# 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
def form_valid(self, form):
"""Set tenant and provider, handle skill target formset."""
# Set tenant
form.instance.tenant = self.request.user.tenant
# Set provider
form.instance.provider = self.request.user
# Set session date if not provided
if not form.instance.session_date:
form.instance.session_date = timezone.now().date()
# Get the formset
context = self.get_context_data()
skill_target_formset = context['skill_target_formset']
# Validate formset
if skill_target_formset.is_valid():
# Save the session first
self.object = form.save()
# Save the skill targets
skill_target_formset.instance = self.object
skill_target_formset.save()
return super().form_valid(form)
else:
# Formset is invalid, return to form with errors
return self.form_invalid(form)
class ABASessionUpdateView(SignedDocumentEditPreventionMixin, LoginRequiredMixin,
RolePermissionMixin, TenantFilterMixin,
AuditLogMixin, SuccessMessageMixin, UpdateView):
"""
ABA session update view.
Features:
- Update session details
- Version history
- Audit trail
- Prevents editing of signed documents
"""
model = ABASession
form_class = ABASessionForm
template_name = 'aba/session_form.html'
success_message = _("ABA session updated successfully!")
allowed_roles = [User.Role.ADMIN, User.Role.ABA]
def get_success_url(self):
"""Redirect to session detail."""
return reverse_lazy('aba:session_detail', kwargs={'pk': self.object.pk})
def get_context_data(self, **kwargs):
"""Add form title, skill target formset, and history."""
context = super().get_context_data(**kwargs)
context['form_title'] = _('Update ABA Session - %(mrn)s') % {'mrn': self.object.patient.mrn}
context['submit_text'] = _('Update Session')
context['patient'] = self.object.patient
# Add skill target formset
if self.request.POST:
context['skill_target_formset'] = ABASkillTargetFormSet(self.request.POST, instance=self.object)
else:
context['skill_target_formset'] = ABASkillTargetFormSet(instance=self.object)
# Add version history if available
if hasattr(self.object, 'history'):
context['history'] = self.object.history.all()[:10]
return context
def form_valid(self, form):
"""Handle skill target formset."""
# Get the formset
context = self.get_context_data()
skill_target_formset = context['skill_target_formset']
# Validate formset
if skill_target_formset.is_valid():
# Save the session first
self.object = form.save()
# Save the skill targets
skill_target_formset.instance = self.object
skill_target_formset.save()
return super().form_valid(form)
else:
# Formset is invalid, return to form with errors
return self.form_invalid(form)