Source code for patientapp.forms
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User, Group
from .models import (
Patient, Institution, Treatment, TreatmentType, TreatmentIntentChoices, Diagnosis, DiagnosisList, Project, PatientProject,
ProjectRedcapMapping, RedcapFormToQuestionnaireMapping,
RedcapFieldToItemMapping, RedcapStudyIDtoPatientIDMap,
)
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Field, Div, Submit, HTML
from django.utils.translation import gettext_lazy as _
INPUT_CLASS = 'w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500'
SELECT_CLASS = 'w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white'
CHECKBOX_CLASS = 'h-4 w-4 text-blue-600 border-gray-300 rounded'
[docs]
class DiagnosisTreatmentBlockForm(forms.Form):
"""
A single block containing one Diagnosis and one Treatment for initial patient registration.
Multiple blocks can be added dynamically via HTMX.
"""
# Hidden field to identify the block
block_prefix = forms.CharField(widget=forms.HiddenInput, required=False)
# Diagnosis fields
diagnosis = forms.ModelChoiceField(
queryset=DiagnosisList.objects.all().order_by('diagnosis'),
required=False,
label=_('Diagnosis'),
help_text=_('Select the diagnosis from the list'),
widget=forms.Select(attrs={'class': 'select2-diagnosis', 'style': 'width: 100%;'})
)
date_of_diagnosis = forms.DateField(
required=False,
label=_('Date of Diagnosis'),
widget=forms.DateInput(attrs={'type': 'date'})
)
# Treatment fields (linked to the above diagnosis)
treatment_type = forms.ModelMultipleChoiceField(
queryset=TreatmentType.objects.all().order_by('treatment_type'),
required=False,
label=_('Treatment Type(s)'),
help_text=_('Select treatment types delivered synchronously for this diagnosis'),
widget=forms.CheckboxSelectMultiple
)
treatment_intent = forms.ChoiceField(
choices=[('', _('--- Select Intent ---'))] + list(TreatmentIntentChoices.choices),
required=False,
label=_('Treatment Intent')
)
date_of_start_of_treatment = forms.DateField(
required=False,
label=_('Date of Start of Treatment'),
widget=forms.DateInput(attrs={'type': 'date'})
)
currently_ongoing_treatment = forms.BooleanField(
required=False,
label=_('Currently Ongoing Treatment'),
help_text=_('Check if the treatment is currently ongoing'
)
)
date_of_end_of_treatment = forms.DateField(
required=False,
label=_('Date of End of Treatment'),
widget=forms.DateInput(attrs={'type': 'date'})
)
[docs]
def clean(self):
cleaned_data = super().clean()
# Only validate if diagnosis is provided
diagnosis = cleaned_data.get('diagnosis')
if not diagnosis:
return cleaned_data
# Treatment validation
start_date = cleaned_data.get('date_of_start_of_treatment')
end_date = cleaned_data.get('date_of_end_of_treatment')
ongoing = cleaned_data.get('currently_ongoing_treatment')
if start_date and end_date and end_date < start_date:
self.add_error('date_of_end_of_treatment', _('End date cannot be before the start date.'))
if ongoing and end_date:
self.add_error('date_of_end_of_treatment', _('End date should not be specified for ongoing treatments.'))
return cleaned_data
[docs]
class PatientForm(forms.ModelForm):
# User fields
username = forms.CharField(max_length=150, required=True)
email = forms.EmailField(required=True)
password1 = forms.CharField(
widget=forms.PasswordInput(),
required=True,
label=_('Password')
)
password2 = forms.CharField(
widget=forms.PasswordInput(),
required=True,
label=_('Repeat Password')
)
groups = forms.ModelMultipleChoiceField(
queryset=Group.objects.all(),
required=False,
widget=forms.CheckboxSelectMultiple,
label=_('Groups')
)
# Project fields
project = forms.ModelChoiceField(
queryset=Project.objects.all().order_by('project_name'),
required=False,
label=_('Project'),
help_text=_('Select the project to assign the patient to')
)
date_patient_enrolled_in_project = forms.DateField(
required=False,
label=_('Date Enrolled in Project'),
widget=forms.DateInput(attrs={'type': 'date'})
)
date_patient_exited_from_project = forms.DateField(
required=False,
label=_('Date Exited from Project'),
widget=forms.DateInput(attrs={'type': 'date'})
)
[docs]
class Meta:
model = Patient
fields = ['patient_id', 'name', 'age', 'gender', 'institution','date_of_registration', 'preferred_language', 'username', 'email', 'password1', 'password2', 'groups']
widgets = {
'age': forms.NumberInput(attrs={'min': 0, 'max': 150}),
'password1': forms.PasswordInput(),
'password2': forms.PasswordInput(),
'date_of_registration': forms.DateInput(attrs={'type': 'date'}),
}
[docs]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.layout = Layout(
# User Account Information
HTML('<h3 class="text-lg font-medium text-gray-900 mb-4">{% translate "User Account Information" %}</h3>'),
Div(
Field('username', css_class='w-full px-3 py-2 border rounded'),
Field('email', css_class='w-full px-3 py-2 border rounded'),
css_class='space-y-4'
),
# Password Section
HTML('<h3 class="text-lg font-medium text-gray-900 mt-6 mb-4">{% translate "Password" %}</h3>'),
Div(
Field('password1', css_class='w-full px-3 py-2 border rounded'),
Field('password2', css_class='w-full px-3 py-2 border rounded'),
css_class='space-y-4'
),
# Patient Information
HTML('<h3 class="text-lg font-medium text-gray-900 mt-6 mb-4">{% translate "Patient Information" %}</h3>'),
Div(
Field('name', css_class='w-full px-3 py-2 border rounded'),
Field('patient_id', css_class='w-full px-3 py-2 border rounded'),
Field('age', css_class='w-full px-3 py-2 border rounded'),
Field('gender', css_class='w-full px-3 py-2 border rounded'),
Field('institution', css_class='w-full px-3 py-2 border rounded'),
Field('date_of_registration',css_class='w-full px-3 py-2 border rounded'),
Field('preferred_language', css_class='w-full px-3 py-2 border rounded'),
css_class='space-y-4'
),
# Groups Section
HTML('<h3 class="text-lg font-medium text-gray-900 mt-6 mb-4">{% translate "User Groups" %}</h3>'),
Div(
Field('groups', css_class='space-y-2'),
css_class='mt-2'
)
)
[docs]
def clean_password2(self):
password1 = self.cleaned_data.get('password1')
password2 = self.cleaned_data.get('password2')
if password1 and password2 and password1 != password2:
raise forms.ValidationError(_("Passwords don't match"))
return password2
[docs]
def clean_username(self):
username = self.cleaned_data.get('username')
if User.objects.filter(username=username).exists():
raise forms.ValidationError(_("A user with that username already exists."))
return username
[docs]
def clean_email(self):
email = self.cleaned_data.get('email')
if User.objects.filter(email=email).exists():
raise forms.ValidationError(_("A user with that email already exists."))
return email
[docs]
def clean(self):
cleaned_data = super().clean()
# Project validation
enrollment_date = cleaned_data.get('date_patient_enrolled_in_project')
exit_date = cleaned_data.get('date_patient_exited_from_project')
if enrollment_date and exit_date and exit_date < enrollment_date:
self.add_error('date_patient_exited_from_project', _('Exit date cannot be before enrollment date.'))
return cleaned_data
[docs]
class DiagnosisListForm(forms.ModelForm):
[docs]
class Meta:
model = DiagnosisList
fields = ['diagnosis', 'icd_11_code']
labels = {
'diagnosis': _('Diagnosis Name'),
'icd_11_code': _('ICD-11 Code'),
}
[docs]
class DiagnosisForm(forms.ModelForm):
[docs]
class Meta:
model = Diagnosis
fields = ['diagnosis', 'date_of_diagnosis']
widgets = {
'date_of_diagnosis': forms.DateInput(attrs={'type': 'date'}),
}
[docs]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance and self.instance.pk and self.instance.date_of_diagnosis:
self.initial['date_of_diagnosis'] = self.instance.date_of_diagnosis.strftime('%Y-%m-%d')
[docs]
class PatientRestrictedUpdateForm(forms.ModelForm):
[docs]
class Meta:
model = Patient
fields = ['age', 'gender', 'institution', 'date_of_registration', 'preferred_language']
widgets = {
'age': forms.NumberInput(attrs={'min': 0, 'max': 150}),
'date_of_registration': forms.DateInput(attrs={'type': 'date'}),
}
[docs]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance and self.instance.pk and self.instance.date_of_registration:
self.initial['date_of_registration'] = self.instance.date_of_registration.strftime('%Y-%m-%d')
# If you want to use crispy forms helper for this new form:
# self.helper = FormHelper()
# self.helper.form_tag = False # To prevent crispy from rendering the <form> tag
# self.helper.layout = Layout(
# Field('age', css_class='w-full px-3 py-2 border rounded'),
# Field('gender', css_class='w-full px-3 py-2 border rounded'),
# Field('institution', css_class='w-full px-3 py-2 border rounded'),
# Field('date_of_registration', css_class='w-full px-3 py-2 border rounded'),
# )
[docs]
class TreatmentForm(forms.ModelForm):
[docs]
class Meta:
model = Treatment
fields = ['treatment_type', 'treatment_intent', 'date_of_start_of_treatment', 'currently_ongoing_treatment', 'date_of_end_of_treatment']
widgets = {
'date_of_start_of_treatment': forms.DateInput(attrs={'type': 'date'}),
'date_of_end_of_treatment': forms.DateInput(attrs={'type': 'date'}),
}
[docs]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance and self.instance.pk:
if self.instance.date_of_start_of_treatment:
self.initial['date_of_start_of_treatment'] = self.instance.date_of_start_of_treatment.strftime('%Y-%m-%d')
if self.instance.date_of_end_of_treatment:
self.initial['date_of_end_of_treatment'] = self.instance.date_of_end_of_treatment.strftime('%Y-%m-%d')
[docs]
class ProjectForm(forms.ModelForm):
[docs]
class Meta:
model = Project
fields = ['project_name']
labels = {
'project_name': _('Project Name'),
}
widgets = {
'project_name': forms.TextInput(attrs={
'class': 'w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500',
'placeholder': _('Enter project name')
}),
}
[docs]
class PatientProjectForm(forms.ModelForm):
[docs]
class Meta:
model = PatientProject
fields = ['project', 'date_patient_enrolled_in_project', 'date_patient_exited_from_project']
labels = {
'project': _('Project'),
'date_patient_enrolled_in_project': _('Date Enrolled'),
'date_patient_exited_from_project': _('Date Exited'),
}
widgets = {
'project': forms.Select(attrs={
'class': 'w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500',
}),
'date_patient_enrolled_in_project': forms.DateInput(attrs={
'type': 'date',
'class': 'w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500',
}),
'date_patient_exited_from_project': forms.DateInput(attrs={
'type': 'date',
'class': 'w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500',
}),
}
[docs]
def __init__(self, *args, **kwargs):
patient = kwargs.pop('patient', None)
super().__init__(*args, **kwargs)
# Filter projects to exclude ones already assigned to this patient (for new assignments)
if patient and not self.instance.pk:
existing_project_ids = PatientProject.objects.filter(patient=patient).values_list('project_id', flat=True)
self.fields['project'].queryset = Project.objects.exclude(id__in=existing_project_ids)
# Set initial values for date fields if instance exists
if self.instance and self.instance.pk:
if self.instance.date_patient_enrolled_in_project:
self.initial['date_patient_enrolled_in_project'] = self.instance.date_patient_enrolled_in_project.strftime('%Y-%m-%d')
if self.instance.date_patient_exited_from_project:
self.initial['date_patient_exited_from_project'] = self.instance.date_patient_exited_from_project.strftime('%Y-%m-%d')
[docs]
def clean(self):
cleaned_data = super().clean()
project = cleaned_data.get('project')
enrollment_date = cleaned_data.get('date_patient_enrolled_in_project')
exit_date = cleaned_data.get('date_patient_exited_from_project')
# Check for duplicate patient-project assignment (for new assignments only)
if project and not self.instance.pk and hasattr(self, 'patient') and self.patient:
if PatientProject.objects.filter(patient=self.patient, project=project).exists():
raise forms.ValidationError(_('This patient is already assigned to this project.'))
# Validate that exit date is after enrollment date
if enrollment_date and exit_date and exit_date < enrollment_date:
raise forms.ValidationError(_('Exit date cannot be before enrollment date.'))
return cleaned_data
[docs]
class ProjectRedcapMappingForm(forms.ModelForm):
redcap_project_token = forms.CharField(
widget=forms.PasswordInput(render_value=False, attrs={'class': INPUT_CLASS, 'autocomplete': 'new-password'}),
label=_('REDCap API Token'),
help_text=_('Enter the API token. Leave blank on edit to keep the existing token.'),
required=False,
)
[docs]
class Meta:
model = ProjectRedcapMapping
fields = [
'redcap_project_url', 'redcap_project_token',
'redcap_project_token_allows_import', 'redcap_project_token_allows_export',
'export_type',
]
labels = {
'redcap_project_url': _('REDCap Project URL'),
'redcap_project_token_allows_import': _('Token allows import into REDCap'),
'redcap_project_token_allows_export': _('Token allows export from REDCap'),
'export_type': _('Export Type'),
}
widgets = {
'redcap_project_url': forms.URLInput(attrs={'class': INPUT_CLASS}),
'export_type': forms.Select(attrs={'class': SELECT_CLASS}),
'redcap_project_token_allows_import': forms.CheckboxInput(attrs={'class': CHECKBOX_CLASS}),
'redcap_project_token_allows_export': forms.CheckboxInput(attrs={'class': CHECKBOX_CLASS}),
}
[docs]
def clean_redcap_project_token(self):
token = self.cleaned_data.get('redcap_project_token', '').strip()
if not token:
if self.instance and self.instance.pk:
return self.instance.redcap_project_token
raise forms.ValidationError(_('API token is required when creating a new configuration.'))
return token
[docs]
class RedcapIDFieldsForm(forms.ModelForm):
[docs]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
instance = kwargs.get('instance')
# Build grouped choices: [(form_name, [(field_name, field_name), ...]), ...]
# Labels are field_name only — field labels can contain rich text/HTML.
grouped = {} # form_name -> [(value, label)]
flat = [] # flat list for secondary (no optgroup blank)
if instance and instance.redcap_project_info:
for field in instance.redcap_project_info.get('metadata', []):
fname = field.get('field_name', '')
fform = field.get('form_name', '') or _('(unknown form)')
if fname:
grouped.setdefault(fform, []).append((fname, fname))
flat.append((fname, fname))
grouped_choices = [('', _('--- Select Field ---'))] + [
(form_name, opts) for form_name, opts in grouped.items()
]
secondary_choices = [('', _('--- None ---'))] + [
(form_name, opts) for form_name, opts in grouped.items()
]
self.fields['redcap_study_id_field'] = forms.ChoiceField(
choices=grouped_choices,
label=_('Study ID Field'),
help_text=_('The REDCap field used as the primary record/study identifier.'),
widget=forms.Select(attrs={'class': SELECT_CLASS}),
)
self.fields['redcap_secondary_id_field'] = forms.ChoiceField(
choices=secondary_choices,
required=False,
label=_('Secondary ID Field'),
help_text=_('Optional secondary identifier (e.g. when auto-numbering is used in REDCap).'),
widget=forms.Select(attrs={'class': SELECT_CLASS}),
)
[docs]
class Meta:
model = ProjectRedcapMapping
fields = ['redcap_study_id_field', 'redcap_secondary_id_field']
[docs]
class RedcapFormToQuestionnaireMappingForm(forms.ModelForm):
[docs]
def __init__(self, *args, project_redcap_mapping=None, **kwargs):
super().__init__(*args, **kwargs)
self._project_redcap_mapping = project_redcap_mapping
redcap_form_choices = [('', _('--- Select REDCap Form ---'))]
self._date_fields_by_form = {}
_date_mapping_grouped = {}
if project_redcap_mapping and project_redcap_mapping.redcap_project_info:
info = project_redcap_mapping.redcap_project_info
forms_seen = set()
for instrument in info.get('instruments', []):
name = instrument.get('instrument_name', '')
label = instrument.get('instrument_label', name)
if name and name not in forms_seen:
redcap_form_choices.append((name, f"{label} ({name})"))
forms_seen.add(name)
# Group fields by form for redcap_date_mapping_field grouped choices
_date_mapping_grouped = {} # form_name -> [(fname, fname)]
for field in info.get('metadata', []):
fname = field.get('field_name', '')
fform = field.get('form_name', '') or _('(unknown form)')
validation = field.get('text_validation_type_or_show_slider_number', '')
if fname:
# Date mapping field: all fields, grouped by form, name-only labels
_date_mapping_grouped.setdefault(fform, []).append((fname, fname))
if validation and validation.startswith(('date_', 'datetime_')):
self._date_fields_by_form.setdefault(fform, {})[fname] = validation
# Build grouped choices for redcap_date_mapping_field
date_mapping_grouped_choices = [('', _('--- None ---'))] + [
(form_name, opts) for form_name, opts in _date_mapping_grouped.items()
]
self.fields['redcap_form_name'] = forms.ChoiceField(
choices=redcap_form_choices,
label=_('REDCap Form'),
widget=forms.Select(attrs={'class': SELECT_CLASS, 'id': 'id_redcap_form_name'}),
)
self.fields['redcap_date_mapping_field'] = forms.ChoiceField(
choices=date_mapping_grouped_choices,
required=False,
label=_('Date Mapping Field in REDCap'),
help_text=_(
'Optional. Only relevant when the form is part of an event, a repeating form, or a repeating event. '
'When set, the system uses this field\'s existing date value in REDCap to automatically suggest which '
'questionnaire submission corresponds to which event or instance. '
'For example, if a visit form records the visit date and your quality-of-life form is in the same event, '
'assigning the visit date field here lets the system match submissions by date proximity. '
'This field may belong to a different REDCap form — the system resolves the correct form from metadata.'
),
widget=forms.Select(attrs={'class': SELECT_CLASS}),
)
# Build choices from ALL date/datetime fields across all forms so that any
# submitted value passes ChoiceField's built-in validation. The clean() method
# enforces that the chosen field actually belongs to the selected form.
date_field_choices = [('', _('--- None (no submission date export) ---'))]
seen = set()
for form_fields in self._date_fields_by_form.values():
for fname, ftype in form_fields.items():
if fname not in seen:
date_field_choices.append((fname, f"{fname} ({ftype})"))
seen.add(fname)
self.fields['submission_date_field'] = forms.ChoiceField(
choices=date_field_choices,
required=False,
label=_('Submission Date Field'),
help_text=_('Select a date or datetime field in this REDCap form to receive the questionnaire submission date on export. The format is determined automatically from the REDCap field validation.'),
widget=forms.Select(attrs={'class': SELECT_CLASS, 'id': 'id_submission_date_field'}),
)
@staticmethod
def _normalise_date_format(validation):
"""Map any REDCap date validation type to its canonical ymd export format."""
if validation.startswith('datetime_seconds_'):
return 'datetime_seconds_ymd'
if validation.startswith('datetime_'):
return 'datetime_ymd'
if validation.startswith('date_'):
return 'date_ymd'
return None
[docs]
def clean(self):
cleaned_data = super().clean()
submission_date_field = cleaned_data.get('submission_date_field') or None
redcap_form_name = cleaned_data.get('redcap_form_name', '')
if submission_date_field:
form_date_fields = self._date_fields_by_form.get(redcap_form_name, {})
validation = form_date_fields.get(submission_date_field)
if not validation:
self.add_error('submission_date_field', _(
'"%(field)s" is not a recognised date/datetime field for the selected form.'
) % {'field': submission_date_field})
else:
resolved = self._normalise_date_format(validation)
if not resolved:
self.add_error('submission_date_field', _(
'The REDCap validation type "%(fmt)s" could not be mapped to an export format.'
) % {'fmt': validation})
else:
cleaned_data['_resolved_submission_date_format'] = resolved
else:
cleaned_data['submission_date_field'] = None
cleaned_data['_resolved_submission_date_format'] = None
return cleaned_data
[docs]
def save(self, commit=True):
instance = super().save(commit=False)
instance.submission_date_field = self.cleaned_data.get('submission_date_field') or None
instance.submission_date_format = self.cleaned_data.get('_resolved_submission_date_format') or None
if commit:
instance.save()
return instance
[docs]
class Meta:
model = RedcapFormToQuestionnaireMapping
fields = [
'redcap_form_name', 'redcap_event_name',
'redcap_form_is_repeating', 'redcap_form_is_in_event', 'redcap_event_is_repeating',
'redcap_date_mapping_field', 'questionnaire', 'submission_date_field',
]
labels = {
'redcap_event_name': _('REDCap Event Name'),
'redcap_form_is_repeating': _('Form is a repeating form'),
'redcap_form_is_in_event': _('Form is part of an event'),
'redcap_event_is_repeating': _('Event is repeating'),
'questionnaire': _('CHAVI PROM Questionnaire'),
}
widgets = {
'redcap_event_name': forms.TextInput(attrs={'class': INPUT_CLASS}),
'redcap_form_is_repeating': forms.CheckboxInput(attrs={'class': CHECKBOX_CLASS}),
'redcap_form_is_in_event': forms.CheckboxInput(attrs={'class': CHECKBOX_CLASS}),
'redcap_event_is_repeating': forms.CheckboxInput(attrs={'class': CHECKBOX_CLASS}),
'questionnaire': forms.Select(attrs={'class': SELECT_CLASS}),
}
DATE_VALIDATION_PREFIXES = ('date_', 'datetime_')
[docs]
class RedcapFieldToItemMappingForm(forms.ModelForm):
[docs]
def __init__(self, *args, project_redcap_mapping=None, form_mapping=None, **kwargs):
super().__init__(*args, **kwargs)
self._date_field_validation_map = {}
self._form_mapping = form_mapping
redcap_field_choices = [('', _('--- Select Field ---'))]
if project_redcap_mapping and project_redcap_mapping.redcap_project_info:
form_name = form_mapping.redcap_form_name if form_mapping else None
for field in project_redcap_mapping.redcap_project_info.get('metadata', []):
if form_name and field.get('form_name') != form_name:
continue
fname = field.get('field_name', '')
flabel = field.get('field_label', fname)
validation = field.get('text_validation_type_or_show_slider_number', '')
if fname:
if validation and validation.startswith(DATE_VALIDATION_PREFIXES):
self._date_field_validation_map[fname] = validation
redcap_field_choices.append((fname, f"{flabel} ({fname}) \u2014 \U0001f4c5 {validation}"))
else:
redcap_field_choices.append((fname, f"{flabel} ({fname})"))
self.fields['redcap_field_name'] = forms.ChoiceField(
choices=redcap_field_choices,
label=_('REDCap Field'),
widget=forms.Select(attrs={'class': SELECT_CLASS}),
)
if form_mapping:
from promapp.models import QuestionnaireItem
self.fields['questionnaire_item'].queryset = QuestionnaireItem.objects.filter(
questionnaire=form_mapping.questionnaire
).select_related('item')
[docs]
class Meta:
model = RedcapFieldToItemMapping
fields = ['redcap_field_name', 'questionnaire_item']
labels = {
'questionnaire_item': _('Questionnaire Item'),
}
widgets = {
'questionnaire_item': forms.Select(attrs={'class': SELECT_CLASS}),
}
RedcapFieldToItemMappingFormSet = forms.modelformset_factory(
RedcapFieldToItemMapping,
form=RedcapFieldToItemMappingForm,
extra=1,
can_delete=True,
)