from django.forms import ( Form, ModelForm, ModelMultipleChoiceField, ModelChoiceField, ChoiceField, CharField, ) from django.forms import inlineformset_factory from django.contrib.postgres.forms import SimpleArrayField, SplitArrayField , SplitArrayWidget from generic.forms import ExamAuthorFormMixin, ExamFormMixin, ExamGroupsFormMixin, ExamMarkerFormMixin from .models import ( Answer, UserAnswer, AnatomyQuestion, # Examination, BodyPart, #Structure, Region, Modality, QuestionType, Exam, ) from generic.models import CidUserGroup, Examination from django.contrib.admin.widgets import FilteredSelectMultiple from django.forms.widgets import RadioSelect, TextInput, Textarea from dal import autocomplete from tinymce.widgets import TinyMCE from crispy_forms.helper import FormHelper from crispy_forms.layout import Submit, Layout, Div from crispy_forms.bootstrap import Accordion, AccordionGroup from generic.widgets import ExamSearchWidget class AnatomyAnswerForm(ModelForm): class Meta: model = UserAnswer fields = ("answer",) class MarkAnatomyQuestionForm(Form): # correct = forms.CharField(required=False) # half_correct = forms.CharField(required=False) # incorrect = forms.CharField(required=False) marked_answers = CharField(required=False) class AnatomyQuestionForm(ModelForm): # exams = ModelMultipleChoiceField(required=False, queryset=Exam.objects.all()) 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"] 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(AnatomyQuestionForm, self).__init__(*args, **kwargs) self.helper = FormHelper() self.helper.form_id = 'id-anatomy-question-form' #self.helper.form_class = 'blueForms' #self.helper.form_method = 'post' #self.helper.form_action = '' #self.helper.add_input(Submit('submit', 'Submit')) # self.fields['question'].widget.attrs = {'class': 'question-form', 'rows': 10, 'cols': 100} # self.fields['feedback'].widget.attrs = {'class': 'feedback-form', 'rows': 10, 'cols': 100} self.fields["question_type"] = ModelChoiceField( required=True, queryset=QuestionType.objects.all(), initial=1, ) self.fields["region"] = ModelChoiceField( required=False, queryset=Region.objects.all(), ) self.fields["modality"] = ModelChoiceField( required=True, queryset=Modality.objects.all(), ) self.fields["examination"] = ModelChoiceField( required=False, queryset=Examination.objects.all(), ) self.fields["body_part"] = ModelChoiceField( required=False, queryset=BodyPart.objects.all(), ) #self.fields["answer_suggest_incorrect"] = CharField(max_length=255, required=False) if self.user.groups.filter(name="anatomy_checker").exists(): exam_queryset = Exam.objects.all() else: exam_queryset = Exam.objects.filter( author__id=self.user.id ) | Exam.objects.filter(open_access=True) self.fields["exams"] = ModelMultipleChoiceField( required=False, queryset=exam_queryset, widget=ExamSearchWidget(exam_model=Exam), ) # Now that all fields have been created, set the helper layout so # Crispy can find the referenced field names (including # `answer_suggest_incorrect`). Placing the layout here ensures the # collapse/accordion renders server-side. self.helper.layout = Layout( "question_type", "image", "description", # Place modality → body_part fields in a horizontal flex row Div( Div("modality", css_class="flex-fill me-2"), Div("region", css_class="flex-fill me-2"), Div("structure", css_class="flex-fill me-2"), Div("examination", css_class="flex-fill me-2"), Div("body_part", css_class="flex-fill"), css_class="d-flex flex-wrap align-items-start mb-2", ), "answer_help", Accordion( AccordionGroup( "Suggest incorrect answers", "answer_suggest_incorrect", active=False, ) ), "feedback", "open_access", "exams", Div(Submit("submit", "Submit"), css_class="form-group"), ) def save(self, commit=True): # Get the unsaved Pizza instance instance = ModelForm.save(self, False) instance.save() old_exams = instance.exams.all() new_exams = self.cleaned_data["exams"] for exam in old_exams: if exam not in new_exams: exam.exam_questions.remove(instance) for exam in new_exams: exam.exam_questions.add(instance) self.save_m2m() return instance class Meta: model = AnatomyQuestion fields = [ "question_type", "image", "description", "modality", "region", "structure", "feedback", "examination", "body_part", "answer_help", "answer_suggest_incorrect", "open_access", ] widgets = { "structure": autocomplete.ModelSelect2(url="atlas:structure-autocomplete"), "answer_help": TinyMCE(attrs={"cols": 80, "rows": 5}), "feedback": TinyMCE(attrs={"cols": 80, "rows": 5}), } class AnatomyQuestionAnswerForm(ModelForm): class Meta: model = AnatomyQuestion fields = [] AnswerFormSet = inlineformset_factory( AnatomyQuestion, Answer, fields=["answer", "status"], widgets={ "answer": Textarea( attrs={"required": "true", "minlength": 2, "maxlength": 500, "rows": 3} ) }, exclude=[], can_delete=True, extra=1, max_num=10, ) AnswerUpdateFormSet = inlineformset_factory( AnatomyQuestion, Answer, fields=["answer", "status"], widgets={ "answer": Textarea( attrs={"required": "true", "minlength": 2, "maxlength": 500, "rows": 3} ) }, exclude=[], can_delete=True, extra=0, max_num=10, ) class ExaminationForm(ModelForm): class Meta: model = Examination fields = ["examination"] class BodyPartForm(ModelForm): class Meta: model = BodyPart fields = ["bodypart"] class ExamForm(ExamFormMixin, ModelForm): class Meta(ExamFormMixin.Meta): model = Exam #class ExamAuthorForm(ModelForm): # class Meta: # model = Exam # fields = ["author"] # # def __init__(self, *args, **kwargs): # ModelForm.__init__(self, *args, **kwargs) # self.fields["author"] = ModelMultipleChoiceField( # queryset=User.objects.all(), # widget=FilteredSelectMultiple(verbose_name="Authors", is_stacked=False), # ) class ExamAuthorForm(ExamAuthorFormMixin): class Meta(ExamAuthorFormMixin.Meta): model = Exam class ExamMarkerForm(ExamMarkerFormMixin): class Meta(ExamMarkerFormMixin.Meta): model = Exam class ExamGroupsForm(ExamGroupsFormMixin): class Meta(ExamGroupsFormMixin.Meta): model = Exam class SuggestIncorrectAnswerForm(ModelForm): class Meta: model = AnatomyQuestion fields = ["answer_suggest_incorrect"] class PrimaryAnswerForm(Form): existing_answer = ChoiceField(required=False, choices=[], label="Use existing answer") new_answer = CharField(required=False, widget=Textarea(attrs={"rows":3}), label="Or create new primary answer") def __init__(self, *args, **kwargs): question = kwargs.pop("question", None) super().__init__(*args, **kwargs) choices = [("", "-- choose existing --")] if question is not None: # Only include existing non-proposed CORRECT answers, ordered by answer text for a in question.answers.filter(proposed=False, status=Answer.MarkOptions.CORRECT).order_by('answer'): choices.append((str(a.pk), a.answer)) self.fields["existing_answer"].choices = choices