from django import forms from django.contrib.admin import widgets from django.forms import ( BaseInlineFormSet, BooleanField, Form, HiddenInput, ModelForm, ModelMultipleChoiceField, ModelChoiceField, ChoiceField, CharField, ValidationError, modelformset_factory, CheckboxSelectMultiple ) from django.forms import inlineformset_factory from django.shortcuts import get_object_or_404 from django_jsonforms.forms import JSONSchemaField from django_svelte_jsoneditor.widgets import SvelteJSONEditorWidget from atlas.models import ( Case, CaseCollection, CaseDetail, CidReportAnswer, Differential, Finding, QuestionSchema, Resource, SelfReview, Series, SeriesImage, SeriesFinding, Condition, Structure, Subspecialty, UncategorisedDicom, UserReportAnswer, ) from anatomy.models import Modality from generic.models import Examination#, Sign # from generic.models import Examination, Site, Condition, Sign from django.contrib.admin.widgets import FilteredSelectMultiple from django.forms.widgets import RadioSelect, TextInput, Textarea from tinymce.widgets import TinyMCE from dal import autocomplete from autocomplete import widgets as htmx_widgets import logging from generic.forms import ExamAuthorFormMixin, ExamGroupsFormMixin from crispy_forms.helper import FormHelper from atlas.helpers import get_cases_available_to_user class ConditionForm(ModelForm): class Meta: model = Condition exclude = ["rcr_curriculum"] widgets = { "synonym": autocomplete.ModelSelect2Multiple( url="atlas:condition-autocomplete" ), "parent": autocomplete.ModelSelect2Multiple( url="atlas:condition-autocomplete" ), "rcr_curriculum_map": autocomplete.ModelSelect2Multiple( url="atlas:condition-autocomplete-rcr-curriculum" ), } class CaseCollectionForm(ModelForm): class Meta: model = CaseCollection exclude = ["cases", "valid_cid_users", "valid_user_users", "author"] def __init__(self, *args, **kwargs): self.user = kwargs.pop( "user" ) # To get request.user. Do not use kwargs.pop('user', None) due to potential security hole if kwargs.get("instance"): # We get the 'initial' keyword argument or initialize it # as a dict if it didn't exist. initial = kwargs.setdefault("initial", {}) # The widget for a ModelMultipleChoiceField expects # a list of primary key for the selected data. # initial["exams"] = [t.pk for t in kwargs["instance"].exams.all()] ModelForm.__init__(self, *args, **kwargs) super(CaseCollectionForm, self).__init__(*args, **kwargs) def save(self, commit=True): # Get the unsaved Case instance instance = ModelForm.save(self, False) # Do we need to save all changes now? # if commit: instance.save() self.save_m2m() return instance class FindingForm(ModelForm): class Meta: model = Finding exclude = [] widgets = { "synonym": autocomplete.ModelSelect2Multiple( url="atlas:finding-autocomplete" ), } class StructureForm(ModelForm): class Meta: model = Structure exclude = [] widgets = { "synonym": autocomplete.ModelSelect2Multiple( url="atlas:structure-autocomplete" ), } class SeriesFindingForm(ModelForm): class Meta: model = SeriesFinding exclude = ["series", "annotation_json", "viewport_json", "current_image_id_index"] widgets = { "findings": autocomplete.ModelSelect2Multiple( url="atlas:finding-autocomplete" ), "structures": autocomplete.ModelSelect2Multiple( url="atlas:structure-autocomplete" ), "conditions": autocomplete.ModelSelect2Multiple( url="atlas:condition-autocomplete" ), } def __init__(self, *args, **kwargs): if kwargs.get("series_id"): # We get the 'initial' keyword argument or initialize it # as a dict if it didn't exist. initial = kwargs.setdefault("initial", {}) # The widget for a ModelMultipleChoiceField expects # a list of primary key for the selected data. initial["series"] = kwargs.pop("series_id") # elif kwargs.get("instance"): # # We get the 'initial' keyword argument or initialize it # # as a dict if it didn't exist. # initial = kwargs.setdefault("initial", {}) # # The widget for a ModelMultipleChoiceField expects # # a list of primary key for the selected data. # #initial["structures"] = [t.pk for t in kwargs["structures"].case.all()] # #initial["structures"] = [t.pk for t in kwargs["structures"].case.all()] super(SeriesFindingForm, self).__init__(*args, **kwargs) ModelForm.__init__(self, *args, **kwargs) class SeriesForm(ModelForm): class Media: # Django also includes a few javascript files necessary # for the operation of this form element. You need to # include # in the template. css = { "all": ["css/widgets.css"], } # Adding this javascript is crucial js = ["jsi18n.js", "tesseract.min.js", "js/upload_form_helpers.js"] def __init__(self, *args, **kwargs): self.user = kwargs.pop( "user" ) # To get request.user. Do not use kwargs.pop('user', None) due to potential security hole if kwargs.get("instance"): # We get the 'initial' keyword argument or initialize it # as a dict if it didn't exist. initial = kwargs.setdefault("initial", {}) # The widget for a ModelMultipleChoiceField expects # a list of primary key for the selected data. initial["case"] = [t.pk for t in kwargs["instance"].case.all()] super(SeriesForm, self).__init__(*args, **kwargs) # self.fields["examination"] = ModelChoiceField( # queryset=Examination.objects.all(), # widget=FilteredSelectMultiple(verbose_name="Examination", is_stacked=False), # ) self.fields["modality"] = ModelChoiceField( required=True, queryset=Modality.objects.all(), ) ModelForm.__init__(self, *args, **kwargs) if self.user.groups.filter(name="atlas_editor").exists(): case_queryset = Case.objects.all() else: case_queryset = Case.objects.filter( author__id=self.user.id ) # | Case.objects.filter(open_access=True) self.fields["case"] = ModelMultipleChoiceField( required=False, queryset=case_queryset, widget=FilteredSelectMultiple(verbose_name="Case", is_stacked=False), ) def save(self, commit=True): # Get the unsaved Long instance instance = ModelForm.save(self, False) # Prepare a 'save_m2m' method for the form, old_save_m2m = self.save_m2m def save_m2m(): old_save_m2m() instance.case.clear() for case in self.cleaned_data["case"]: # We need the id here instance.case.add(case.pk) self.save_m2m = save_m2m # Do we need to save all changes now? # if commit: instance.save() self.save_m2m() # instance.anonymise_images() return instance class Meta: model = Series exclude = ["author", "series_instance_uid"] widgets = { "examination": autocomplete.ModelSelect2( url="generic:examination-autocomplete" ), "description": Textarea(attrs={"maxlength": 1000, "rows": 5, "cols": 80}), } class CaseForm(ModelForm): # exams = ModelMultipleChoiceField(required=False, queryset=Exam.objects.all(),widget=FilteredSelectMultiple(verbose_name="Exams", is_stacked=False)) class Media: # Django also includes a few javascript files necessary # for the operation of this form element. You need to # include # in the template. css = { "all": ["css/widgets.css"], } # Adding this javascript is crucial js = ["jsi18n.js", "tesseract.min.js", "js/upload_form_helpers.js"] def __init__(self, *args, **kwargs): logging.info("LOG") logging.debug(kwargs) self.user = kwargs.pop( "user" ) # To get request.user. Do not use kwargs.pop('user', None) due to potential security hole # This is populated if we create the case from an exam/collection self.collection = None if kwargs["initial"] is not None and "exams" in kwargs["initial"]: collections = kwargs["initial"].pop("exams") logging.debug(collections) self.collection = get_object_or_404(CaseCollection, pk=collections[0]) if kwargs.get("instance"): # We get the 'initial' keyword argument or initialize it # as a dict if it didn't exist. initial = kwargs.setdefault("initial", {}) # The widget for a ModelMultipleChoiceField expects # a list of primary key for the selected data. # initial["exams"] = [t.pk for t in kwargs["instance"].exams.all()] ModelForm.__init__(self, *args, **kwargs) super(CaseForm, self).__init__(*args, **kwargs) # self.fields["series"].queryset = if self.user.groups.filter(name="atlas_editor").exists(): case_queryset = Case.objects.all().order_by("pk") else: case_queryset = get_cases_available_to_user(self.user) self.fields["previous_case"].queryset=case_queryset def get_queryset(self, request): return ( super() .get_queryset(request) .prefetch_related( "condition", "subspecialty", "pathological_process", "series" ) ) class Meta: model = Case # fields = ['due_back'] # fields = '__all__' fields = [ # "examination", # "site", "title", "subspecialty", "description", "history", "presentation", "discussion", "condition", "pathological_process", "report", "open_access", "previous_case", "diagnostic_certainty", ] # fields = ['question', 'findings', 'subspecialty', 'references'] widgets = { # "normal": RadioSelect( # choices=[(True, 'Yes'), # (False, 'No')]) # "findings": TinyMCE(attrs={"cols": 80, "rows": 20}), # "mark_scheme": TinyMCE(attrs={"cols": 80, "rows": 30}), "description": Textarea(attrs={"cols": 80, "rows": 5}), "history": Textarea(attrs={"cols": 80, "rows": 5}), "discussion": Textarea(attrs={"cols": 80, "rows": 5}), "condition": autocomplete.ModelSelect2Multiple( url="atlas:condition-autocomplete" ), "presentation": autocomplete.ModelSelect2Multiple( url="atlas:presentation-autocomplete" ), "subspecialty": CheckboxSelectMultiple(), "pathological_process": CheckboxSelectMultiple() } def save(self, commit=True): # Get the unsaved Case instance instance = ModelForm.save(self, False) # Prepare a 'save_m2m' method for the form, old_save_m2m = self.save_m2m def save_m2m(): old_save_m2m() # instance.exams.clear() # for exam in self.cleaned_data["exams"]: # # We need the id here # instance.exams.add(exam.pk) self.save_m2m = save_m2m # Do we need to save all changes now? # if commit: instance.save() self.save_m2m() logging.debug(f"{self.collection=}") if self.collection is not None: logging.debug(f"{self.collection=}") case_no = self.collection.cases.count() + 1 logging.debug(f"{case_no=}") logging.debug(f"{instance=}") # Create through model #cd = CaseDetail(case=instance, collection=exam, sort_order=case_no) self.collection.cases.add(instance, through_defaults={"sort_order": case_no}) return instance CaseDifferentialFormSet = inlineformset_factory( Case, Differential, exclude=[], can_delete=True, extra=1, max_num=10, widgets={ "condition": autocomplete.ModelSelect2(url="atlas:condition-autocomplete"), "text": Textarea(attrs={"maxlength": 1000, "rows": 3, "cols": 80}), }, ) class ResourceForm(ModelForm): class Meta: model = Resource exclude = ["author"] widgets = { "description": Textarea(attrs={"cols": 80, "rows": 5}), "url": TextInput(attrs={"cols": 80, "rows": 1}), } class CaseResourceForm(ModelForm): def __init__(self, *args, user, **kwargs): super(CaseResourceForm, self).__init__(*args, **kwargs) if not user.groups.filter(name="atlas_editor").exists(): queryset = Resource.objects.filter(author__id=user.id) else: queryset = Resource.objects.all() self.fields["resource"] = ModelChoiceField( required=False, queryset=queryset, # widget=Select(verbose_name="Series", is_stacked=False), ) self.fields["pre_review"] = BooleanField(required=False) class CaseSeriesForm(ModelForm): def __init__(self, *args, user, **kwargs): super(CaseSeriesForm, self).__init__(*args, **kwargs) if not user.groups.filter(name="atlas_editor").exists(): queryset = Series.objects.filter(author__id=user.id) else: queryset = Series.objects.all() self.fields["series"] = ModelChoiceField( required=False, queryset=queryset, # widget=Select(verbose_name="Series", is_stacked=False), ) class CaseCollectionCaseForm(ModelForm): def __init__(self, *args, user, collection=None, **kwargs): super(CaseCollectionCaseForm, self).__init__(*args, **kwargs) queryset = get_cases_available_to_user(user, collection=collection) self.fields["case"] = ModelChoiceField( required=False, queryset=queryset, # widget=Select(verbose_name="Series", is_stacked=False), ) SeriesFormSet = inlineformset_factory( Case, Series.case.through, form=CaseSeriesForm, exclude=[], can_delete=True, extra=0, max_num=10, ) CaseCollectionCaseFormSet = inlineformset_factory( CaseCollection, Case.casecollection_set.through, form=CaseCollectionCaseForm, exclude=["question_schema", "question_answers"], can_delete=True, extra=0, max_num=50, ) CaseResourceFormSet = inlineformset_factory( Case, Resource.case_set.through, form=CaseResourceForm, exclude=[], can_delete=True, extra=1, max_num=10, ) SeriesImageFormSet = inlineformset_factory( Series, SeriesImage, exclude=["dicom_tags_ohif", "replaced", "image_md5_hash", "image_blake3_hash", "is_dicom"], can_delete=True, extra=0, max_num=2000, ) class JsonAnswerForm(Form): json_answer = JSONSchemaField( schema = {}, #options = 'schema/options.json' options = 'schema/options.json' ) class Meta: fields = ["json_answer"] def __init__(self, *args, question_schema=None, **kwargs): super(JsonAnswerForm, self).__init__(*args, **kwargs) if question_schema is not None: self.fields["json_answer"] = JSONSchemaField(schema=question_schema, options='schema/options.json') self.fields["json_answer"].label = False class BaseReportAnswerForm(ModelForm): class Meta: fields = ["answer"] widgets = { # "normal": RadioSelect( # choices=[(True, 'Yes'), # (False, 'No')]) # "findings": TinyMCE(attrs={"cols": 80, "rows": 20}), # "mark_scheme": TinyMCE(attrs={"cols": 80, "rows": 30}), "answer": Textarea(attrs={"cols": 100, "rows": 5, "placeholder":"Report text"}), } # help_texts = { # "answer": "Write your answer in here." # } def __init__(self, *args, case_detail, **kwargs): super(BaseReportAnswerForm, self).__init__(*args, **kwargs) self.fields["answer"].required = False class BaseQuestionAnswerForm(ModelForm): json_answer = JSONSchemaField( schema = {}, options = 'schema/options.json' ) class Meta: fields = ["json_answer"] labels = {"json_answer": ""} # help_texts = { # "answer": "Write your answer in here." # } def __init__(self, *args, case_detail, **kwargs): super(BaseQuestionAnswerForm, self).__init__(*args, **kwargs) #self.fields["json_answer"].schema = case_detail.question_schema if case_detail.question_schema is not None: self.fields["json_answer"] = JSONSchemaField(schema=case_detail.question_schema, options="schema/options.json") self.fields["json_answer"].label = "" class CidReportAnswerForm(BaseReportAnswerForm): class Meta(BaseReportAnswerForm.Meta): model = CidReportAnswer class UserReportAnswerForm(BaseReportAnswerForm): class Meta(BaseReportAnswerForm.Meta): model = UserReportAnswer class CidQuestionAnswerForm(BaseQuestionAnswerForm): class Meta(BaseQuestionAnswerForm.Meta): model = CidReportAnswer class UserQuestionAnswerForm(BaseQuestionAnswerForm): class Meta(BaseQuestionAnswerForm.Meta): model = UserReportAnswer class CidReportAnswerMarkForm(ModelForm): class Meta: model = CidReportAnswer fields = ["score", "feedback"] widgets = { # "normal": RadioSelect( # choices=[(True, 'Yes'), # (False, 'No')]) # "findings": TinyMCE(attrs={"cols": 80, "rows": 20}), # "mark_scheme": TinyMCE(attrs={"cols": 80, "rows": 30}), "feedback": Textarea(attrs={"cols": 80, "rows": 4}), } def __init__(self, *args, **kwargs): super(CidReportAnswerMarkForm, self).__init__(*args, **kwargs) self.fields["score"].required = False #class AddCollectionToCaseForm(Form): # # def __init__(self, *args, user, **kwargs): # super(AddCollectionToCaseForm, self).__init__(*args, **kwargs) # print("INIT", args, kwargs) # # if not user.groups.filter(name="atlas_editor").exists(): # queryset = CaseCollection.objects.filter(author__id=user.id) # else: # queryset = CaseCollection.objects.all() # # self.valid_collections = queryset # self.case = get_object_or_404(Case, pk=pk) # # self.fields["casecollection"] = ModelMultipleChoiceField( # required=False, # queryset=queryset, # # widget=Select(verbose_name="Series", is_stacked=False), # ) # # def save(self, *args, **kwargs): # print("SAVE", args, kwargs) # instance = super(AddCollectionToCaseForm, self).save(*args, **kwargs) # # # Here we save the modified project selection back into the database # instance.casecollection_set.set(self.cleaned_data['casecollection']) # # print(f"{instance=}") # # return instance # # def clean_casecollection(self): # collection_set = self.cleaned_data["casecollection"] # # print(f"{collection_set=}") # # for col in collection_set: # if col not in self.valid_collections: # raise ValidationError("Invalid collection") # # return collection_set class AddCollectionToCaseForm(Form): def __init__(self, *args, user, **kwargs): super(AddCollectionToCaseForm, self).__init__(*args, **kwargs) print("INIT", args, kwargs) if not user.groups.filter(name="atlas_editor").exists(): queryset = CaseCollection.objects.filter(author__id=user.id) else: queryset = CaseCollection.objects.all() self.valid_collections = queryset self.fields["casecollection"] = ModelMultipleChoiceField( required=False, queryset=queryset, # widget=Select(verbose_name="Series", is_stacked=False), ) def save(self, *args, **kwargs): print("SAVE", args, kwargs) instance = super(AddCollectionToCaseForm, self).save(*args, **kwargs) # Here we save the modified project selection back into the database instance.casecollection_set.set(self.cleaned_data['casecollection']) print(f"{instance=}") return instance def clean_casecollection(self): collection_set = self.cleaned_data["casecollection"] print(f"{collection_set=}") for col in collection_set: if col not in self.valid_collections: raise ValidationError("Invalid collection") return collection_set class SelfReviewForm(ModelForm): class Meta: model = SelfReview fields = ["user_exam", "case", "comments", "findings", "interpretation"] widgets = {"user_exam": HiddenInput(), "case": HiddenInput()} def __init__(self, *args, **kwargs): if "user_exam_id" in kwargs: user_exam_id = kwargs.pop("user_exam_id") if "case_id" in kwargs: case_id = kwargs.pop("case_id") ModelForm.__init__(self, *args, **kwargs) super(SelfReviewForm, self).__init__(*args, **kwargs) class UncategorisedDicomForm(ModelForm): class Meta: model = UncategorisedDicom fields = ["image"]#, "user"] #UncategorisedDicomFormset = formset_factory(UncategorisedDicomForm) #class ConditionAutocompleteFormModel(forms.ModelForm): # class Meta: # model = Condition # fields = ["name"] # widgets = { # 'members': widgets.Autocomplete( # name='members', # options=dict(multiselect=True, model=Condition) # ) # } class ConditionAutocompleteForm(Form): condition = ModelChoiceField(queryset=Condition.objects.all(), widget=htmx_widgets.Autocomplete( name="conditions", options=dict(model=Condition) )) class CaseCollectionAuthorForm(ExamAuthorFormMixin): class Meta(ExamAuthorFormMixin.Meta): model = CaseCollection class CaseAuthorForm(ExamAuthorFormMixin): class Meta(ExamAuthorFormMixin.Meta): model = Case class SeriesAuthorForm(ExamAuthorFormMixin): class Meta(ExamAuthorFormMixin.Meta): model = Series class ExamGroupsForm(ExamGroupsFormMixin): class Meta(ExamGroupsFormMixin.Meta): model = CaseCollection class AnswerJSONForm(Form): json = JSONSchemaField( schema = 'schema/schema.json', options = 'schema/options.json' ) class SvelteJSONEditorWidgetOverride(SvelteJSONEditorWidget): template_name = "atlas/svelte_jsoneditor_widget_override.html" class CaseDetailForm(ModelForm): class Meta: model = CaseDetail fields = ["question_schema", "question_answers"]#, "user"] widgets = { "question_schema": SvelteJSONEditorWidgetOverride(), "question_answers": SvelteJSONEditorWidgetOverride(), } class QuestionSchemaForm(ModelForm): class Meta: model = QuestionSchema fields = ["name", "description", "schema"] widgets = { "schema": SvelteJSONEditorWidgetOverride(), } class PriorCaseForm(Form): relation = CharField(max_length=255, required=True) #def __init__(self, *args, **kwargs): # self.case_detail = kwargs.pop( # "case_detail" # ) # To get request.user. Do not use kwargs.pop('user', None) due to potential security hole # super(PriorCaseForm, self).__init__(*args, **kwargs) # prior_cases = self.case_detail.case.get_all_prior_cases() # if not prior_cases: # prior_cases = Case.objects.none() # self.fields["case"] = ChoiceField( # required=False, # # widget=Select(verbose_name="Series", is_stacked=False), # )