from django import forms from django.contrib.admin import widgets from django.forms import ( BaseInlineFormSet, 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 atlas.models import ( Case, CaseCollection, CaseDetail, CidReportAnswer, Differential, Finding, 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 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"] 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) 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"] widgets = { "findings": autocomplete.ModelSelect2Multiple( url="atlas:finding-autocomplete" ), "structures": autocomplete.ModelSelect2Multiple( url="atlas:structure-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 = 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 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, **kwargs): super(CaseCollectionCaseForm, self).__init__(*args, **kwargs) if not user.groups.filter(name="atlas_editor").exists(): queryset = Case.objects.filter(author__id=user.id) else: queryset = Case.objects.all() 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=[], can_delete=True, extra=0, max_num=50, ) 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 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}), } # help_texts = { # "answer": "Write your answer in here." # } def __init__(self, *args, **kwargs): super(BaseReportAnswerForm, self).__init__(*args, **kwargs) self.fields["answer"].required = False class CidReportAnswerForm(BaseReportAnswerForm): class Meta(BaseReportAnswerForm.Meta): model = CidReportAnswer class UserReportAnswerForm(BaseReportAnswerForm): class Meta(BaseReportAnswerForm.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) ))