from django.forms import ( Form, ModelForm, ModelMultipleChoiceField, ModelChoiceField, ChoiceField, CharField, HiddenInput, EmailField, IntegerField, DateField, SplitDateTimeField, SplitDateTimeWidget, ) from django.forms import inlineformset_factory from atlas.models import CaseCollection from django.contrib.auth.models import User from generic.models import ( CidUser, CidUserGroup, Examination, ExamCollection, QuestionNote, Supervisor, UserGrades, UserProfile, UserUserGroup, ) from django.contrib.admin.widgets import FilteredSelectMultiple from django.forms.widgets import RadioSelect, TextInput, Textarea from rapids.models import Rapid from anatomy.models import AnatomyQuestion from longs.models import Long from django.shortcuts import render, get_object_or_404, redirect from django.contrib.contenttypes.models import ContentType from physics.models import UserAnswer as PhysicsUserAnswer, Question from physics.models import Exam as PhysicsExam from physics.models import Question as SbasQuestion from anatomy.models import UserAnswer as AnatomyUserAnswer from anatomy.models import Exam as AnatomyExam from anatomy.models import AnatomyQuestion from anatomy.models import Answer as AnatomyAnswer from rapids.models import UserAnswer as RapidsUserAnswer from rapids.models import Exam as RapidsExam from rapids.models import Rapid from rapids.models import Answer as RapidAnswer from longs.models import UserAnswer as LongsUserAnswer from longs.models import Exam as LongsExam from longs.models import Long from sbas.models import UserAnswer as SbasUserAnswer from sbas.models import Exam as SbasExam from sbas.models import Question as SbasQuestion from django.core.exceptions import ValidationError import datetime from django.forms.utils import from_current_timezone, to_current_timezone class SplitDateTimeFieldDefaultTime(SplitDateTimeField): def compress(self, data_list): if data_list: # Raise a validation error if time or date is empty # (possible if SplitDateTimeField has required=False). if data_list[0] in self.empty_values: raise ValidationError( self.error_messages["invalid_date"], code="invalid_date" ) if data_list[1] in self.empty_values: data_list[1] = datetime.time(0, 0) #raise ValidationError( # self.error_messages["invalid_time"], code="invalid_time" #) result = datetime.datetime.combine(*data_list) return from_current_timezone(result) return None class SplitDateTimeFieldDefaultTimeEnd(SplitDateTimeField): def compress(self, data_list): if data_list: # Raise a validation error if time or date is empty # (possible if SplitDateTimeField has required=False). if data_list[0] in self.empty_values: raise ValidationError( self.error_messages["invalid_date"], code="invalid_date" ) if data_list[1] in self.empty_values: data_list[1] = datetime.time(23, 59) #raise ValidationError( # self.error_messages["invalid_time"], code="invalid_time" #) result = datetime.datetime.combine(*data_list) return from_current_timezone(result) return None class ExamFormMixin: def __init__(self, *args, user, **kwargs) -> None: super(ModelForm, self).__init__(*args, **kwargs) #if user.is_superuser or user.groups.filter(name="cid_user_manager").exists(): # cid_user_group_queryset = CidUserGroup.objects.filter(archive=False) # user_user_group_queryset = UserUserGroup.objects.filter(archive=False) #else: # cid_user_group_queryset = CidUserGroup.objects.none() # user_user_group_queryset = UserUserGroup.objects.none() #self.fields["cid_user_groups"] = ModelMultipleChoiceField( # required=False, # queryset=cid_user_group_queryset, #) #self.fields["user_user_groups"] = ModelMultipleChoiceField( # required=False, # queryset=user_user_group_queryset, #) self.fields["start_date"] = SplitDateTimeFieldDefaultTime( widget=SplitDateTimeWidget( date_attrs={"type": "date", "class": "datepicker"}, time_attrs={"type": "time", "class": "timepicker"}, date_format="%Y-%m-%d", time_format="%H:%M", ), required=False, ) self.fields["end_date"] = SplitDateTimeFieldDefaultTimeEnd( widget=SplitDateTimeWidget( date_attrs={"type": "date", "class": "datepicker"}, time_attrs={"type": "time", "class": "timepicker"}, date_format="%Y-%m-%d", time_format="%H:%M", ), required=False, ) class Meta: fields = [ "name", "time_limit", "open_access", "authors_only", "exam_mode", "include_history", "start_date", "end_date", "restrict_to_dates", "active", "publish_results", "archive", #"cid_user_groups", #"user_user_groups", # "author", ] # widgets = { # "start_date": TextInput(attrs={"type": "date"}), # "end_date": TextInput(attrs={"type": "date"}), # } def save(self, commit=True): instance = ModelForm.save(self, False) instance.save() self.save_m2m() return instance class ExamAuthorFormMixin(ModelForm): class Meta: 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 ExamMarkerFormMixin(ModelForm): class Meta: fields = ["markers"] def __init__(self, *args, **kwargs): ModelForm.__init__(self, *args, **kwargs) self.fields["markers"] = ModelMultipleChoiceField( queryset=User.objects.all(), widget=FilteredSelectMultiple(verbose_name="Markers", is_stacked=False), ) self.fields["markers"].required = False class ExamGroupsFormMixin(ModelForm): class Meta: #model = ExamCollection fields = ("cid_user_groups", "user_user_groups") def __init__(self, *args, user, **kwargs) -> None: super(ModelForm, self).__init__(*args, **kwargs) # TODO: decide how we should handle managing user groups # i.e who should be able to access a list of all users? if user.is_superuser or user.groups.filter(name="cid_user_manager").exists(): cid_user_group_queryset = CidUserGroup.objects.filter(archive=False) user_user_group_queryset = UserUserGroup.objects.filter(archive=False) else: cid_user_group_queryset = CidUserGroup.objects.filter(archive=False, open_access=True) user_user_group_queryset = UserUserGroup.objects.none(archive=False, open_access=True) #cid_user_group_queryset = CidUserGroup.objects.none() #user_user_group_queryset = UserUserGroup.objects.none() self.fields["cid_user_groups"] = ModelMultipleChoiceField( required=False, queryset=cid_user_group_queryset, label="CID User Groups", ) self.fields["user_user_groups"] = ModelMultipleChoiceField( required=False, queryset=user_user_group_queryset, label="User Groups", ) self.fields["cid_user_groups"].widget.attrs["size"] = 8 self.fields["user_user_groups"].widget.attrs["size"] = 8 class ExaminationForm(ModelForm): def __init__(self, *args, **kwargs): # Where is this coming from? if "user" in kwargs: user = kwargs.pop("user") ModelForm.__init__(self, *args, **kwargs) super(ExaminationForm, self).__init__(*args, **kwargs) class Meta: model = Examination fields = ["examination", "modality"] class ExaminationMergeForm(Form): examination_to_merge_into = ModelChoiceField(queryset=Examination.objects.all()) class QuestionNoteForm(ModelForm): class Meta: model = QuestionNote fields = ["content_type", "object_id", "author", "note_type", "note"] widgets = {"content_type": HiddenInput(), "object_id": HiddenInput()} def __init__(self, *args, **kwargs): question_type = kwargs.pop("question_type") pk = kwargs.pop("pk") user = kwargs.pop("user") # if question_type == "rapid": # question = Rapid # content_type = ContentType.objects.get(model="rapid") # elif question_type == "anatomy": # question = AnatomyQuestion # content_type = ContentType.objects.get(model="anatomyquestion") # elif question_type == "long": # question = Long # content_type = ContentType.objects.get(model="long") # else: # raise PermissionError() # get_object_or_404(question, pk=pk) # We get the 'initial' keyword argument or initialize it # as a dict if it didn't exist. # if kwargs.get("instance"): ModelForm.__init__(self, *args, **kwargs) super(QuestionNoteForm, self).__init__(*args, **kwargs) # initial = kwargs.setdefault("initial", {}) ## The widget for a ModelMultipleChoiceField expects ## a list of primary key for the selected data. ##initial["content_type"] = question ##initial["content_id"] = pk # self.fields["content_type"].initial = content_type # self.fields["object_id"].initial = pk class CidUserForm(ModelForm): class Meta: model = CidUser fields = [ "passcode", "active", "internal_candidate", "name", "email", "supervisor", "login_email_sent", "results_email_sent", "group", ] class CidUserExamForm(ModelForm): def __init__(self, *args, **kwargs): 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["physics_exams"] = [ t.pk for t in kwargs["instance"].physics_exams.all() ] initial["anatomy_exams"] = [ t.pk for t in kwargs["instance"].anatomy_exams.all() ] initial["sba_exams"] = [t.pk for t in kwargs["instance"].sba_exams.all()] initial["rapid_exams"] = [ t.pk for t in kwargs["instance"].rapid_exams.all() ] initial["longs_exams"] = [ t.pk for t in kwargs["instance"].longs_exams.all() ] initial["casecollection_exams"] = [ t.pk for t in kwargs["instance"].casecollection_exams.all() ] ModelForm.__init__(self, *args, **kwargs) super(CidUserExamForm, self).__init__(*args, **kwargs) self.fields["physics_exams"] = ModelMultipleChoiceField( required=False, queryset=PhysicsExam.objects.filter(archive=False), widget=FilteredSelectMultiple( verbose_name="Physics Exams", is_stacked=False ), ) self.fields["anatomy_exams"] = ModelMultipleChoiceField( required=False, queryset=AnatomyExam.objects.filter(archive=False), widget=FilteredSelectMultiple( verbose_name="Anatomy Exams", is_stacked=False ), ) self.fields["sba_exams"] = ModelMultipleChoiceField( required=False, queryset=SbasExam.objects.filter(archive=False), widget=FilteredSelectMultiple(verbose_name="SBA Exams", is_stacked=False), ) self.fields["rapid_exams"] = ModelMultipleChoiceField( required=False, queryset=RapidsExam.objects.filter(archive=False), widget=FilteredSelectMultiple(verbose_name="Rapid Exams", is_stacked=False), ) self.fields["longs_exams"] = ModelMultipleChoiceField( required=False, queryset=LongsExam.objects.filter(archive=False), widget=FilteredSelectMultiple(verbose_name="Long Exams", is_stacked=False), ) self.fields["casecollection_exams"] = ModelMultipleChoiceField( required=False, queryset=CaseCollection.objects.filter(archive=False), widget=FilteredSelectMultiple( verbose_name="Case Collection", 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.physics_exams.clear() for physics_exams in self.cleaned_data["physics_exams"]: instance.physics_exams.add(physics_exams.pk) instance.anatomy_exams.clear() for anatomy_exams in self.cleaned_data["anatomy_exams"]: instance.anatomy_exams.add(anatomy_exams.pk) instance.sba_exams.clear() for sba_exams in self.cleaned_data["sba_exams"]: instance.sba_exams.add(sba_exams.pk) instance.rapid_exams.clear() for rapid_exams in self.cleaned_data["rapid_exams"]: instance.rapid_exams.add(rapid_exams.pk) instance.longs_exams.clear() for longs_exams in self.cleaned_data["longs_exams"]: instance.longs_exams.add(longs_exams.pk) instance.casecollection_exams.clear() for casecollection_exams in self.cleaned_data["casecollection_exams"]: instance.casecollection_exams.add(casecollection_exams.pk) self.save_m2m = save_m2m # Do we need to save all changes now? # if commit: instance.save() self.save_m2m() return instance class Meta: model = CidUser fields = [] class CidUserGroupModelChoiceField(ModelMultipleChoiceField): def label_from_instance(self, obj): return f"{obj.cid} [{obj.email}]" class CidUserGroupForm(ModelForm): class Meta: model = CidUserGroup fields = ["name", "archive", "open_access"] class UserUserGroupModelChoiceField(ModelMultipleChoiceField): def label_from_instance(self, obj): return f"{obj.username} ({obj.userprofile.grade}) [{obj.email}]" class UserUserGroupForm(ModelForm): users = UserUserGroupModelChoiceField( required=False, queryset=User.objects.all(), widget=FilteredSelectMultiple(verbose_name="Users", is_stacked=False), ) class Meta: model = UserUserGroup fields = ["name", "archive", "open_access", "users"] class UserGroupExamForm(ModelForm): GROUP_TYPES = [ "anatomy_user_user_groups", "rapid_user_user_groups", "longs_user_user_groups", "physics_user_user_groups", "sba_user_user_groups", ] # users = UserUserGroupModelChoiceField( # required=False, # queryset=User.objects.all(), # widget=FilteredSelectMultiple(verbose_name="Users", is_stacked=False), # ) def __init__(self, *args, **kwargs): 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. for groups in self.GROUP_TYPES: # instance.anatomy_cid_user_groups.clear() # obj = getattr(instance,groups) initial[groups] = [ t.pk for t in getattr(kwargs["instance"], groups).all() ] ModelForm.__init__(self, *args, **kwargs) super(UserGroupExamForm, self).__init__(*args, **kwargs) self.fields["anatomy_user_user_groups"] = ModelMultipleChoiceField( required=False, queryset=AnatomyExam.objects.filter(archive=False), widget=FilteredSelectMultiple( verbose_name="Anatomy Exams", is_stacked=False ), ) self.fields["rapid_user_user_groups"] = ModelMultipleChoiceField( required=False, queryset=RapidsExam.objects.filter(archive=False), widget=FilteredSelectMultiple(verbose_name="Rapid Exams", is_stacked=False), ) self.fields["longs_user_user_groups"] = ModelMultipleChoiceField( required=False, queryset=LongsExam.objects.filter(archive=False), widget=FilteredSelectMultiple(verbose_name="Longs Exams", is_stacked=False), ) self.fields["physics_user_user_groups"] = ModelMultipleChoiceField( required=False, queryset=PhysicsExam.objects.filter(archive=False), widget=FilteredSelectMultiple( verbose_name="Physics Exams", is_stacked=False ), ) self.fields["sba_user_user_groups"] = ModelMultipleChoiceField( required=False, queryset=SbasExam.objects.filter(archive=False), widget=FilteredSelectMultiple(verbose_name="Sbas Exams", is_stacked=False), ) class Meta: model = UserUserGroup fields = [] 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() for groups in self.GROUP_TYPES: # instance.anatomy_cid_user_groups.clear() obj = getattr(instance, groups) obj.clear() for exam in self.cleaned_data[groups]: obj.add(exam.pk) self.save_m2m = save_m2m # Do we need to save all changes now? # if commit: instance.save() self.save_m2m() return instance class CidGroupExamForm(ModelForm): GROUP_TYPES = [ "anatomy_cid_user_groups", "rapid_cid_user_groups", "longs_cid_user_groups", "physics_cid_user_groups", "sba_cid_user_groups", ] # users = CidUserGroupModelChoiceField( # required=False, # queryset=User.objects.all(), # widget=FilteredSelectMultiple(verbose_name="Users", is_stacked=False), def __init__(self, *args, **kwargs): 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. for groups in self.GROUP_TYPES: # instance.anatomy_cid_user_groups.clear() # obj = getattr(instance,groups) initial[groups] = [ t.pk for t in getattr(kwargs["instance"], groups).all() ] ModelForm.__init__(self, *args, **kwargs) super(CidGroupExamForm, self).__init__(*args, **kwargs) self.fields["anatomy_cid_user_groups"] = ModelMultipleChoiceField( required=False, queryset=AnatomyExam.objects.filter(archive=False), widget=FilteredSelectMultiple( verbose_name="Anatomy Exams", is_stacked=False ), ) self.fields["rapid_cid_user_groups"] = ModelMultipleChoiceField( required=False, queryset=RapidsExam.objects.filter(archive=False), widget=FilteredSelectMultiple(verbose_name="Rapid Exams", is_stacked=False), ) self.fields["longs_cid_user_groups"] = ModelMultipleChoiceField( required=False, queryset=LongsExam.objects.filter(archive=False), widget=FilteredSelectMultiple(verbose_name="Longs Exams", is_stacked=False), ) self.fields["physics_cid_user_groups"] = ModelMultipleChoiceField( required=False, queryset=PhysicsExam.objects.filter(archive=False), widget=FilteredSelectMultiple( verbose_name="Physics Exams", is_stacked=False ), ) self.fields["sba_cid_user_groups"] = ModelMultipleChoiceField( required=False, queryset=SbasExam.objects.filter(archive=False), widget=FilteredSelectMultiple(verbose_name="Sbas Exams", is_stacked=False), ) class Meta: model = CidUserGroup fields = [] 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() for groups in self.GROUP_TYPES: # instance.anatomy_cid_user_groups.clear() obj = getattr(instance, groups) obj.clear() for exam in self.cleaned_data[groups]: obj.add(exam.pk) self.save_m2m = save_m2m # Do we need to save all changes now? # if commit: instance.save() self.save_m2m() return instance # class UserForm(ModelForm): # class Meta: # model = User # fields = ["first_name", "last_name", "email"] # # class UserProfileForm(ModelForm): # class Meta: # model = UserProfile # fields = ["supervisor_name", "supervisor_email", "registration_number", "peninsula_trainee"] GRADE_CHOICES = ( ("ST1", "ST1"), ("ST2", "ST2"), ("ST3", "ST3"), ("ST4", "ST4"), ("ST5", "ST5"), ) class UserUserForm(Form): username = EmailField( required=True, help_text="Username / email should be the same. Ideally this should be an nhs email.", ) first_name = CharField(max_length=255, required=True) last_name = CharField(max_length=255, required=True) grade = ModelChoiceField(UserGrades.objects.all(), required=False) supervisor = ModelChoiceField( Supervisor.objects.all(), required=False ) # Needs to be a user/object ref # # class Meta: # # fields = ("username", "first_name", "last_name") class SupervisorForm(ModelForm): class Meta: model = Supervisor exclude = ("",) class ExamCollectionForm(ModelForm): GROUP_TYPES = ( ("Rapid Exams", "rapids_exams", RapidsExam), ("Long Exams", "longs_exams", LongsExam), ("SBA Exams", "sbas_exams", SbasExam), ("Physic Exams", "physics_exams", PhysicsExam), ("Anatomy Exams", "anatomy_exams", AnatomyExam), ) class Meta: model = ExamCollection exclude = ("",) def __init__(self, *args, **kwargs): 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. for name, reverse_prop, exam_obj in self.GROUP_TYPES: # instance.anatomy_cid_user_groups.clear() # obj = getattr(instance,groups) initial[reverse_prop] = [ t.pk for t in getattr(kwargs["instance"], reverse_prop).all() ] ModelForm.__init__(self, *args, **kwargs) super(ExamCollectionForm, self).__init__(*args, **kwargs) for name, reverse_prop, exam_obj in self.GROUP_TYPES: self.fields[reverse_prop] = ModelMultipleChoiceField( required=False, queryset=exam_obj.objects.filter(archive=False), widget=FilteredSelectMultiple(verbose_name=name, 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() for name, reverse_prop, exam_obj in self.GROUP_TYPES: # instance.anatomy_cid_user_groups.clear() obj = getattr(instance, reverse_prop) obj.clear() for exam in self.cleaned_data[reverse_prop]: obj.add(exam) self.save_m2m = save_m2m # Do we need to save all changes now? # if commit: instance.save() self.save_m2m() return instance class ExamCollectionCloneForm(ModelForm): class Meta: model = ExamCollection fields = ("name", "date")