Files
penracourses/research/admin.py
T
Ross c94d2fb1a7 feat(research): add research study management functionality
- Implemented models for ResearchStudy, ResearchStudyArm, and ResearchParticipant.
- Created views for listing, creating, updating, and exporting research studies.
- Added bulk participant generation feature with CSV and JSON support.
- Developed templates for study management, participant entry, and bulk generation.
- Introduced URL routing for research study operations.
- Added utility functions for randomisation and participant assignment.
- Implemented participant intake process with demographic data collection.
- Ensured proper handling of participant CIDs and assignment methods.
2026-05-12 21:37:57 +01:00

117 lines
4.4 KiB
Python

from django.contrib import admin
from django.utils.html import format_html
from research.models import ResearchStudy, ResearchStudyArm, ResearchParticipant
@admin.register(ResearchStudy)
class ResearchStudyAdmin(admin.ModelAdmin):
list_display = ['name', 'slug', 'active', 'generation_mode', 'randomisation_mode', 'created_at']
list_filter = ['active', 'generation_mode', 'randomisation_mode', 'created_at']
search_fields = ['name', 'slug', 'description']
readonly_fields = ['created_at', 'updated_at']
fieldsets = (
('Study Information', {
'fields': ('name', 'slug', 'description', 'active')
}),
('Participant Creation', {
'fields': ('generation_mode', 'collect_demographics'),
'description': 'Configure how participants are created and what data is collected.'
}),
('Randomisation Strategy', {
'fields': ('randomisation_mode', 'balance_within_candidate_group', 'allocation_targets'),
'description': 'Configure how participants are assigned to arms.'
}),
('Authors', {
'fields': ('author',),
}),
('Timestamps', {
'fields': ('created_at', 'updated_at'),
'classes': ('collapse',)
}),
)
@admin.register(ResearchStudyArm)
class ResearchStudyArmAdmin(admin.ModelAdmin):
list_display = ['name', 'study', 'packet', 'active', 'sort_order', 'order_sequence']
list_filter = ['study', 'active', 'order_sequence']
search_fields = ['name', 'study__name', 'packet__name']
fieldsets = (
('Arm Information', {
'fields': ('study', 'name', 'packet', 'active', 'sort_order')
}),
('Randomisation', {
'fields': ('allocation_weight',),
'description': 'Used for completely-random allocation.'
}),
('Ordering & Prerequisites', {
'fields': ('order_sequence', 'required_previous_arm'),
'description': 'Configure sequential completion requirements.'
}),
)
def get_form(self, request, obj=None, **kwargs):
"""Pre-filter related fields based on study."""
form = super().get_form(request, obj, **kwargs)
if obj and hasattr(obj, 'study'):
form.base_fields['required_previous_arm'].queryset = ResearchStudyArm.objects.filter(
study=obj.study
).exclude(pk=obj.pk)
return form
@admin.register(ResearchParticipant)
class ResearchParticipantAdmin(admin.ModelAdmin):
list_display = ['pseudo_id', 'study', 'assigned_arm', 'cid_user_display', 'consented', 'assigned_at']
list_filter = ['study', 'assigned_arm', 'consented', 'created_at']
search_fields = ['pseudo_id', 'study__slug', 'cid_user__cid', 'email']
readonly_fields = ['created_at', 'updated_at', 'cid_user_info']
fieldsets = (
('Study & ID', {
'fields': ('study', 'pseudo_id')
}),
('CID User', {
'fields': ('cid_user', 'cid_user_info'),
'description': 'Associated CID user for study access.'
}),
('Demographics', {
'fields': ('candidate_group', 'age_band', 'sex', 'training_grade', 'years_experience', 'email'),
}),
('Assignment', {
'fields': ('assigned_arm', 'assignment_method', 'assigned_at'),
}),
('Consent & Status', {
'fields': ('consented', 'completed_arms'),
}),
('User Link', {
'fields': ('user_user',),
'classes': ('collapse',)
}),
('Timestamps', {
'fields': ('created_at', 'updated_at'),
'classes': ('collapse',)
}),
)
def cid_user_display(self, obj):
"""Display CID number with link."""
if obj.cid_user:
return format_html(
'<code>{}</code>',
obj.cid_user.cid
)
return '-'
cid_user_display.short_description = 'CID'
def cid_user_info(self, obj):
"""Display full CID user info."""
if obj.cid_user:
return format_html(
'<strong>CID:</strong> {}<br><strong>Passcode:</strong> <code>{}</code><br><strong>Name:</strong> {}',
obj.cid_user.cid,
obj.cid_user.passcode,
obj.cid_user.name
)
return 'Not yet assigned'
cid_user_info.short_description = 'CID User Information'