Skip to content

Forms

Form management for the Django dynamic site app

Classes:

Name Description
InstallationForm

placeholder

TemplateSelectForm

placeholder

PlaceholdersForm

placeholder

DocumentUpdateForm

placeholder

LogHazardForm

placeholder

HazardCommentForm

placeholder

UploadToGithubForm

placeholder

Functions:

Name Description
validated_response

placeholder

md_files

placeholder

DocumentNewForm

Bases: Form

Source code in app/dcsp/app/forms.py
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
class DocumentNewForm(forms.Form):
    """ """

    def __init__(self, project_id: int, *args: Any, **kwargs: Any) -> None:
        """Initialisation of the selection field

        Searches the docs folder and searches for markdown files, noting the
        any subfolders. These are then provided as a selection field.
        """
        super(DocumentNewForm, self).__init__(*args, **kwargs)
        self.project_id: int = project_id

        self.fields["document_name"] = forms.CharField(
            help_text="This must be a valid path and end in '.md'",
            required=True,
            widget=forms.TextInput(
                attrs={
                    "class": "form-control field-color-dcsp font-dcsp border-info"
                }
            ),
        )

    def clean(self) -> dict[str, Any]:
        """Checks if a valid path"""
        cleaned_data: dict[str, Any] = self.cleaned_data
        document_name: Optional[str] = cleaned_data.get("document_name")
        valid_1: bool = False
        valid_2: bool = False
        error_messages_1: list[str] = []
        error_messages_2: list[str] = []

        if not document_name:
            validated_response(
                self,
                "document_name",
                False,
                f"'document_name' is missing",
            )
            return cleaned_data

        (
            valid_1,
            error_messages_1,
        ) = valid_partial_linux_path(document_name)

        project = project_builder.ProjectBuilder(self.project_id)
        (
            valid_2,
            error_messages_2,
        ) = project.document_create_check(document_name)

        errors_all = list_to_string(error_messages_1 + error_messages_2)

        validated_response(
            self,
            "document_name",
            valid_1 and valid_2,
            f"The supplied path is invalid due to: { errors_all }",
        )

        return cleaned_data

__init__(project_id, *args, **kwargs)

Initialisation of the selection field

Searches the docs folder and searches for markdown files, noting the any subfolders. These are then provided as a selection field.

Source code in app/dcsp/app/forms.py
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
def __init__(self, project_id: int, *args: Any, **kwargs: Any) -> None:
    """Initialisation of the selection field

    Searches the docs folder and searches for markdown files, noting the
    any subfolders. These are then provided as a selection field.
    """
    super(DocumentNewForm, self).__init__(*args, **kwargs)
    self.project_id: int = project_id

    self.fields["document_name"] = forms.CharField(
        help_text="This must be a valid path and end in '.md'",
        required=True,
        widget=forms.TextInput(
            attrs={
                "class": "form-control field-color-dcsp font-dcsp border-info"
            }
        ),
    )

clean()

Checks if a valid path

Source code in app/dcsp/app/forms.py
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
def clean(self) -> dict[str, Any]:
    """Checks if a valid path"""
    cleaned_data: dict[str, Any] = self.cleaned_data
    document_name: Optional[str] = cleaned_data.get("document_name")
    valid_1: bool = False
    valid_2: bool = False
    error_messages_1: list[str] = []
    error_messages_2: list[str] = []

    if not document_name:
        validated_response(
            self,
            "document_name",
            False,
            f"'document_name' is missing",
        )
        return cleaned_data

    (
        valid_1,
        error_messages_1,
    ) = valid_partial_linux_path(document_name)

    project = project_builder.ProjectBuilder(self.project_id)
    (
        valid_2,
        error_messages_2,
    ) = project.document_create_check(document_name)

    errors_all = list_to_string(error_messages_1 + error_messages_2)

    validated_response(
        self,
        "document_name",
        valid_1 and valid_2,
        f"The supplied path is invalid due to: { errors_all }",
    )

    return cleaned_data

DocumentUpdateForm

Bases: Form

Text edit area of selected markdown file

Provides the raw markdown for the selected file.

Fields

document_name: TODO document_markdown: raw markdown of the file with placeholders evident in double curley brackets (eg {{ placeholder }})

Source code in app/dcsp/app/forms.py
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
class DocumentUpdateForm(forms.Form):
    """Text edit area of selected markdown file

    Provides the raw markdown for the selected file.

    Fields:
        document_name: TODO
        document_markdown: raw markdown of the file with placeholders evident in double
                 curley brackets (eg {{ placeholder }})
    """

    def __init__(
        self,
        project_id: int,
        document_name: str = "",
        *args: Any,
        **kwargs: Any,
    ) -> None:
        """Initialisation of the selection field

        Searches the docs folder and searches for markdown files, noting the
        any subfolders. These are then provided as a selection field.
        """
        super(DocumentUpdateForm, self).__init__(*args, **kwargs)
        self.project_id: int = project_id
        docs_dir: str = f"{ c.PROJECTS_FOLDER }project_{ project_id }/{ c.CLINICAL_SAFETY_FOLDER }docs/"
        initial_data: Mapping[str, str] = self.initial or {}
        document_markdown: str = ""

        # TODO - perhaps a message that docs folder is missing should be presented.
        if initial_data == {} and document_name == "":
            for _, __, files in os.walk(docs_dir):
                for name in files:
                    if fnmatch(name, "*.md"):
                        document_name = name
                        loop_exit = True
                        break
                if loop_exit:
                    break

            with open(
                f"{ docs_dir }{ document_name }",
                "r",
            ) as file:
                document_markdown = file.read()
                document_markdown = document_markdown.replace("\n", "\r\n")

        elif document_name != "":
            if not Path(f"{ docs_dir }{ document_name }").is_file():
                raise FileNotFoundError(f"File '{ document_name }' not found")

            with open(
                f"{ docs_dir }{ document_name }",
                "r",
            ) as file:
                document_markdown = file.read()
                document_markdown = document_markdown.replace("\n", "\r\n")

        else:
            document_name = initial_data.get("document_name", "")
            document_markdown = initial_data.get("document_markdown", "")

        CHOICES = tuple(md_files(self.project_id))

        self.fields["document_name"] = forms.ChoiceField(
            choices=CHOICES,
            initial=document_name,
            widget=forms.Select(
                attrs={
                    "class": c.SELECT_STYLE,
                    "onChange": "form.submit()",
                }
            ),
        )

        self.fields["document_markdown"] = forms.CharField(
            label="Markdown view",
            required=False,
            initial=document_markdown,
            widget=forms.Textarea(
                attrs={
                    "style": "width:100%; overflow:hidden;",
                    "class": "form-control field-color-dcsp font-dcsp border-info",
                    "onkeyup": "update_web_view()",
                }
            ),
        )

        self.fields["document_name_initial"] = forms.CharField(
            initial=document_name,
            widget=forms.HiddenInput(attrs={}),
        )

        self.fields["document_markdown_initial"] = forms.CharField(
            initial=document_markdown,
            required=False,
            widget=forms.HiddenInput(attrs={}),
        )

    def clean(self) -> dict[str, Any]:
        """ """
        cleaned_data: dict[str, Any] = self.cleaned_data
        """document_name: str = cleaned_data["document_name"]"""
        # document_markdown: str = cleaned_data["document_markdown"]
        # md_files_list: list = md_files(self.project_id)
        # doc_build: Builder
        linter_results: dict[str, str] = {}

        """validated_response(
            self,
            "document_markdown",
            document_name in md_files_list,
            "Internal error with document_name (hidden attribute)",
        )"""

        """# Not, mkdocs directory is not provided as an argument. But this should
        # Be ok just for linting.
        doc_build = Builder()
        linter_results = doc_build.linter_text(document_markdown)
        results_readable: str = """

        """ if linter_results["overal"] != "pass":
            self.add_error("document_markdown", "Error with syntax in markdown file")"""

        """for key, value in linter_results.items():
            if value == "pass":
                results_readable += f"{ key }: {value }</br>"
            else:
                results_readable += f"<u>{ key }: {value }</u></br>"

        validated_response(
            self,
            "document_markdown",
            linter_results["overal"] == "pass",
            f"There is invalid syntax in the markdown file, please correct:</br> { results_readable }",
        )"""

        return cleaned_data

__init__(project_id, document_name='', *args, **kwargs)

Initialisation of the selection field

Searches the docs folder and searches for markdown files, noting the any subfolders. These are then provided as a selection field.

Source code in app/dcsp/app/forms.py
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
def __init__(
    self,
    project_id: int,
    document_name: str = "",
    *args: Any,
    **kwargs: Any,
) -> None:
    """Initialisation of the selection field

    Searches the docs folder and searches for markdown files, noting the
    any subfolders. These are then provided as a selection field.
    """
    super(DocumentUpdateForm, self).__init__(*args, **kwargs)
    self.project_id: int = project_id
    docs_dir: str = f"{ c.PROJECTS_FOLDER }project_{ project_id }/{ c.CLINICAL_SAFETY_FOLDER }docs/"
    initial_data: Mapping[str, str] = self.initial or {}
    document_markdown: str = ""

    # TODO - perhaps a message that docs folder is missing should be presented.
    if initial_data == {} and document_name == "":
        for _, __, files in os.walk(docs_dir):
            for name in files:
                if fnmatch(name, "*.md"):
                    document_name = name
                    loop_exit = True
                    break
            if loop_exit:
                break

        with open(
            f"{ docs_dir }{ document_name }",
            "r",
        ) as file:
            document_markdown = file.read()
            document_markdown = document_markdown.replace("\n", "\r\n")

    elif document_name != "":
        if not Path(f"{ docs_dir }{ document_name }").is_file():
            raise FileNotFoundError(f"File '{ document_name }' not found")

        with open(
            f"{ docs_dir }{ document_name }",
            "r",
        ) as file:
            document_markdown = file.read()
            document_markdown = document_markdown.replace("\n", "\r\n")

    else:
        document_name = initial_data.get("document_name", "")
        document_markdown = initial_data.get("document_markdown", "")

    CHOICES = tuple(md_files(self.project_id))

    self.fields["document_name"] = forms.ChoiceField(
        choices=CHOICES,
        initial=document_name,
        widget=forms.Select(
            attrs={
                "class": c.SELECT_STYLE,
                "onChange": "form.submit()",
            }
        ),
    )

    self.fields["document_markdown"] = forms.CharField(
        label="Markdown view",
        required=False,
        initial=document_markdown,
        widget=forms.Textarea(
            attrs={
                "style": "width:100%; overflow:hidden;",
                "class": "form-control field-color-dcsp font-dcsp border-info",
                "onkeyup": "update_web_view()",
            }
        ),
    )

    self.fields["document_name_initial"] = forms.CharField(
        initial=document_name,
        widget=forms.HiddenInput(attrs={}),
    )

    self.fields["document_markdown_initial"] = forms.CharField(
        initial=document_markdown,
        required=False,
        widget=forms.HiddenInput(attrs={}),
    )

clean()

Source code in app/dcsp/app/forms.py
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
def clean(self) -> dict[str, Any]:
    """ """
    cleaned_data: dict[str, Any] = self.cleaned_data
    """document_name: str = cleaned_data["document_name"]"""
    # document_markdown: str = cleaned_data["document_markdown"]
    # md_files_list: list = md_files(self.project_id)
    # doc_build: Builder
    linter_results: dict[str, str] = {}

    """validated_response(
        self,
        "document_markdown",
        document_name in md_files_list,
        "Internal error with document_name (hidden attribute)",
    )"""

    """# Not, mkdocs directory is not provided as an argument. But this should
    # Be ok just for linting.
    doc_build = Builder()
    linter_results = doc_build.linter_text(document_markdown)
    results_readable: str = """

    """ if linter_results["overal"] != "pass":
        self.add_error("document_markdown", "Error with syntax in markdown file")"""

    """for key, value in linter_results.items():
        if value == "pass":
            results_readable += f"{ key }: {value }</br>"
        else:
            results_readable += f"<u>{ key }: {value }</u></br>"

    validated_response(
        self,
        "document_markdown",
        linter_results["overal"] == "pass",
        f"There is invalid syntax in the markdown file, please correct:</br> { results_readable }",
    )"""

    return cleaned_data

EntryUpdateForm

Bases: Form

Source code in app/dcsp/app/forms.py
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
class EntryUpdateForm(forms.Form):
    def __init__(
        self,
        project_id: int,
        entry_type: str,
        *args: Any,
        **kwargs: Any,
    ) -> None:
        """Initialise the entry form

        Gets available entry labels for entry form

        Fields:
            horizontal_line: a horizontal line across the web page.
            TODO:TODO
        """
        super(EntryUpdateForm, self).__init__(*args, **kwargs)
        project: project_builder.ProjectBuilder = (
            project_builder.ProjectBuilder(project_id)
        )
        entry_template: list[dict[str, Any]] = project.entry_file_read(
            entry_type
        )

        field_type: str = ""
        help_text: str = ""
        self.labels_for_calculations: dict[str, str] = {}
        labels_for_calculations: dict[str, str] = {}
        self.calculation_field: list[dict[str, Any]] = []

        for index, field in enumerate(entry_template):
            help_text = ""

            if field["field_type"] == "horizontal_line":
                self.fields[field["heading"]] = forms.CharField(
                    label="",
                    required=False,
                    widget=forms.HiddenInput(attrs={}),
                )

            elif field["field_type"] == "new_line":
                self.fields[field["heading"]] = forms.CharField(
                    label="",
                    required=False,
                    widget=forms.HiddenInput(attrs={}),
                )

            elif field["field_type"] == "icon":
                self.fields[field["heading"]] = forms.CharField(
                    label="",
                    required=False,
                    widget=forms.HiddenInput(attrs={}),
                )

            elif field["field_type"] == "code":
                self.fields[field["heading"]] = forms.CharField(
                    label="",
                    required=False,
                    widget=forms.HiddenInput(attrs={}),
                )

            elif field["field_type"] == "select":
                help_text = field["text"].replace("\n", "<br>")
                self.fields[field["heading"]] = forms.ChoiceField(
                    label=field["gui_label"],
                    required=False,
                    choices=field["choices"],
                    help_text=f"{index}|{help_text}",
                    widget=forms.Select(
                        attrs={
                            "class": c.SELECT_STYLE,
                        }
                    ),
                )

                if "labels" in field:
                    self.labels_for_calculations[
                        f"id_{ field['heading'] }"
                    ] = field["labels"]

            elif field["field_type"] == "multiselect":
                help_text = field["text"].replace("\n", "<br>")
                self.fields[field["heading"]] = forms.MultipleChoiceField(
                    label=field["gui_label"],
                    required=False,
                    choices=field["choices"],
                    help_text=f"{index}|{help_text}",
                    widget=forms.SelectMultiple(
                        attrs={
                            "class": "selectpicker font-dcsp border-info",
                            "multiple": "true",
                        }
                    ),
                )

            elif field["field_type"] == "calculate":
                labels_for_calculations = {}

                help_text = field["text"].replace("\n", "<br>")
                self.fields[field["heading"]] = forms.CharField(
                    label=f"{ field['gui_label'] } (read only)",
                    required=False,
                    help_text=f"{index}|{help_text}",
                    widget=forms.TextInput(
                        attrs={
                            "class": "form-control field-color-dcsp font-dcsp border-info no-pointer-cursor",
                            "readonly": "readonly",
                        }
                    ),
                )

                # Match only labels that are required for this calculation field
                for label in field["labels"]:
                    for (
                        key,
                        value,
                    ) in self.labels_for_calculations.items():
                        for label2 in value:
                            if label2 == label:
                                labels_for_calculations[key] = value

                self.calculation_field.append(
                    {
                        "id": f"id_{ field['heading'] }",
                        "monitor_labels": labels_for_calculations,
                        "choices": field["choices"],
                    },
                )

            elif field["field_type"] == "readonly":
                self.fields[field["heading"]] = forms.CharField(
                    label=field["gui_label"],
                    required=False,
                    widget=ReadOnlyInput(
                        attrs={
                            "class": "readonly-field-dcsp",
                            "value": field["text"],
                            "label_class": heading_level(field["heading"]),
                        }
                    ),
                )

            elif field["field_type"] == "date":
                self.fields[field["heading"]] = forms.DateField(
                    required=False,
                    widget=forms.DateInput(
                        attrs={
                            "class": f"date-dcsp { c.SELECT_STYLE }",
                            "type": "date",
                        }
                    ),
                )

            elif field["field_type"] == "text_area":
                self.fields[field["heading"]] = forms.CharField(
                    label=field["gui_label"],
                    # initial="test data",
                    required=False,
                    widget=forms.Textarea(
                        attrs={
                            "class": "form-control field-color-dcsp font-dcsp border-info",
                            "rows": 3,
                            "placeholder": field["text"],
                        }
                    ),
                )
            else:
                # TODO #48 - need a soft fail here
                raise ValueError(
                    f"'field_type' has wrong value of '{ field_type }'"
                )

__init__(project_id, entry_type, *args, **kwargs)

Initialise the entry form

Gets available entry labels for entry form

Fields

horizontal_line: a horizontal line across the web page. TODO:TODO

Source code in app/dcsp/app/forms.py
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
def __init__(
    self,
    project_id: int,
    entry_type: str,
    *args: Any,
    **kwargs: Any,
) -> None:
    """Initialise the entry form

    Gets available entry labels for entry form

    Fields:
        horizontal_line: a horizontal line across the web page.
        TODO:TODO
    """
    super(EntryUpdateForm, self).__init__(*args, **kwargs)
    project: project_builder.ProjectBuilder = (
        project_builder.ProjectBuilder(project_id)
    )
    entry_template: list[dict[str, Any]] = project.entry_file_read(
        entry_type
    )

    field_type: str = ""
    help_text: str = ""
    self.labels_for_calculations: dict[str, str] = {}
    labels_for_calculations: dict[str, str] = {}
    self.calculation_field: list[dict[str, Any]] = []

    for index, field in enumerate(entry_template):
        help_text = ""

        if field["field_type"] == "horizontal_line":
            self.fields[field["heading"]] = forms.CharField(
                label="",
                required=False,
                widget=forms.HiddenInput(attrs={}),
            )

        elif field["field_type"] == "new_line":
            self.fields[field["heading"]] = forms.CharField(
                label="",
                required=False,
                widget=forms.HiddenInput(attrs={}),
            )

        elif field["field_type"] == "icon":
            self.fields[field["heading"]] = forms.CharField(
                label="",
                required=False,
                widget=forms.HiddenInput(attrs={}),
            )

        elif field["field_type"] == "code":
            self.fields[field["heading"]] = forms.CharField(
                label="",
                required=False,
                widget=forms.HiddenInput(attrs={}),
            )

        elif field["field_type"] == "select":
            help_text = field["text"].replace("\n", "<br>")
            self.fields[field["heading"]] = forms.ChoiceField(
                label=field["gui_label"],
                required=False,
                choices=field["choices"],
                help_text=f"{index}|{help_text}",
                widget=forms.Select(
                    attrs={
                        "class": c.SELECT_STYLE,
                    }
                ),
            )

            if "labels" in field:
                self.labels_for_calculations[
                    f"id_{ field['heading'] }"
                ] = field["labels"]

        elif field["field_type"] == "multiselect":
            help_text = field["text"].replace("\n", "<br>")
            self.fields[field["heading"]] = forms.MultipleChoiceField(
                label=field["gui_label"],
                required=False,
                choices=field["choices"],
                help_text=f"{index}|{help_text}",
                widget=forms.SelectMultiple(
                    attrs={
                        "class": "selectpicker font-dcsp border-info",
                        "multiple": "true",
                    }
                ),
            )

        elif field["field_type"] == "calculate":
            labels_for_calculations = {}

            help_text = field["text"].replace("\n", "<br>")
            self.fields[field["heading"]] = forms.CharField(
                label=f"{ field['gui_label'] } (read only)",
                required=False,
                help_text=f"{index}|{help_text}",
                widget=forms.TextInput(
                    attrs={
                        "class": "form-control field-color-dcsp font-dcsp border-info no-pointer-cursor",
                        "readonly": "readonly",
                    }
                ),
            )

            # Match only labels that are required for this calculation field
            for label in field["labels"]:
                for (
                    key,
                    value,
                ) in self.labels_for_calculations.items():
                    for label2 in value:
                        if label2 == label:
                            labels_for_calculations[key] = value

            self.calculation_field.append(
                {
                    "id": f"id_{ field['heading'] }",
                    "monitor_labels": labels_for_calculations,
                    "choices": field["choices"],
                },
            )

        elif field["field_type"] == "readonly":
            self.fields[field["heading"]] = forms.CharField(
                label=field["gui_label"],
                required=False,
                widget=ReadOnlyInput(
                    attrs={
                        "class": "readonly-field-dcsp",
                        "value": field["text"],
                        "label_class": heading_level(field["heading"]),
                    }
                ),
            )

        elif field["field_type"] == "date":
            self.fields[field["heading"]] = forms.DateField(
                required=False,
                widget=forms.DateInput(
                    attrs={
                        "class": f"date-dcsp { c.SELECT_STYLE }",
                        "type": "date",
                    }
                ),
            )

        elif field["field_type"] == "text_area":
            self.fields[field["heading"]] = forms.CharField(
                label=field["gui_label"],
                # initial="test data",
                required=False,
                widget=forms.Textarea(
                    attrs={
                        "class": "form-control field-color-dcsp font-dcsp border-info",
                        "rows": 3,
                        "placeholder": field["text"],
                    }
                ),
            )
        else:
            # TODO #48 - need a soft fail here
            raise ValueError(
                f"'field_type' has wrong value of '{ field_type }'"
            )

HazardCommentForm

Bases: Form

Form for adding comments to a preexisting hazard

A simple form to add a comment to a pre-existing hazard.

Fields

comment: a new comment for the hazard.

Source code in app/dcsp/app/forms.py
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
class HazardCommentForm(forms.Form):
    """Form for adding comments to a preexisting hazard

    A simple form to add a comment to a pre-existing hazard.

    Fields:
        comment: a new comment for the hazard.
    """

    comment = forms.CharField(
        widget=forms.Textarea(
            attrs={
                "class": "form-control field-color-dcsp font-dcsp border-info",
                "style": "height: 500px",
                "onkeyup": "update_web_view()",
            }
        ),
    )

PlaceholdersForm

Bases: Form

Creates fields for all available placeholders

Searches through the docs folder in mkdocs for markdown files. If a placeholder is found, then this is made available for the user to provide a value for.

Methods:

Name Description
clean

placeholder

Fields

[Automatically created]

Source code in app/dcsp/app/forms.py
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
class PlaceholdersForm(forms.Form):
    """Creates fields for all available placeholders

    Searches through the docs folder in mkdocs for markdown files. If a
    placeholder is found, then this is made available for the user to provide a
    value for.

    Methods:
        clean: placeholder

    Fields:
        [Automatically created]
    """

    def __init__(self, project_id: int, *args: Any, **kwargs: Any) -> None:
        """Find placeholders and initialises web app fields

        Searches for placeholders in markdown files in doc folder and creates
        fields for each.
        """
        super(PlaceholdersForm, self).__init__(*args, **kwargs)
        placeholders: dict[str, str] = {}
        placeholder: str = ""
        value: str = ""

        project_build = project_builder.ProjectBuilder(project_id)
        placeholders = project_build.get_placeholders()

        for (
            placeholder,
            value,
        ) in placeholders.items():
            self.fields[placeholder] = forms.CharField(
                required=False,
                initial=value,
                widget=forms.TextInput(
                    attrs={
                        "class": "form-control field-color-dcsp font-dcsp border-info"
                    }
                ),
            )

    def clean(self) -> dict[str, Any]:
        """Checks placeholders for invalid characters

        Current invalid characters are "{}\"'"
        """
        INVALID_CHARACTERS: str = "{}\"'"
        cleaned_data: dict[str, Any] = self.cleaned_data.copy()
        key: str = ""
        value: str = ""

        for key, value in cleaned_data.items():
            validated_response(
                self,
                key,
                not any(illegal in value for illegal in INVALID_CHARACTERS),
                f"Invalid character in placeholder '{ key }' - '{ value }'",
            )

        return cleaned_data

__init__(project_id, *args, **kwargs)

Find placeholders and initialises web app fields

Searches for placeholders in markdown files in doc folder and creates fields for each.

Source code in app/dcsp/app/forms.py
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
def __init__(self, project_id: int, *args: Any, **kwargs: Any) -> None:
    """Find placeholders and initialises web app fields

    Searches for placeholders in markdown files in doc folder and creates
    fields for each.
    """
    super(PlaceholdersForm, self).__init__(*args, **kwargs)
    placeholders: dict[str, str] = {}
    placeholder: str = ""
    value: str = ""

    project_build = project_builder.ProjectBuilder(project_id)
    placeholders = project_build.get_placeholders()

    for (
        placeholder,
        value,
    ) in placeholders.items():
        self.fields[placeholder] = forms.CharField(
            required=False,
            initial=value,
            widget=forms.TextInput(
                attrs={
                    "class": "form-control field-color-dcsp font-dcsp border-info"
                }
            ),
        )

clean()

Checks placeholders for invalid characters

Current invalid characters are "{}"'"

Source code in app/dcsp/app/forms.py
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
def clean(self) -> dict[str, Any]:
    """Checks placeholders for invalid characters

    Current invalid characters are "{}\"'"
    """
    INVALID_CHARACTERS: str = "{}\"'"
    cleaned_data: dict[str, Any] = self.cleaned_data.copy()
    key: str = ""
    value: str = ""

    for key, value in cleaned_data.items():
        validated_response(
            self,
            key,
            not any(illegal in value for illegal in INVALID_CHARACTERS),
            f"Invalid character in placeholder '{ key }' - '{ value }'",
        )

    return cleaned_data

ProjectSetupInitialForm

Bases: Form

Setup a project

Source code in app/dcsp/app/forms.py
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
class ProjectSetupInitialForm(forms.Form):
    """Setup a project"""

    CHOICES_1 = (
        ("", ""),
        (
            "import",
            "Import from external source",
        ),
        (
            "start_anew",
            "Start a new project from scratch",
        ),
    )

    setup_choice = forms.ChoiceField(
        choices=CHOICES_1,
        widget=forms.Select(
            attrs={
                "class": c.SELECT_STYLE,
                "onChange": "change_visibility()",
            }
        ),
    )

    external_repository_url_import = forms.CharField(
        label="Respository URL",
        required=False,
        widget=forms.TextInput(
            attrs={
                "class": f"form-control field-color-dcsp font-dcsp border-info { c.FORM_ELEMENTS_MAX_WIDTH }",
                "autocomplete": "url",
            }
        ),
    )

    external_repository_username_import = forms.CharField(
        label="Respository username",
        required=False,
        widget=forms.TextInput(
            attrs={
                "class": f"form-control field-color-dcsp font-dcsp border-info { c.FORM_ELEMENTS_MAX_WIDTH }",
                "autocomplete": "username",
            }
        ),
    )

    external_repository_password_token_import = forms.CharField(
        label="Repository token",
        help_text="You can get your Github <a class='link-dcsp' href='https://github.com/settings/tokens/new' target='_blank'> token here</a>",
        required=False,
        widget=forms.PasswordInput(
            attrs={
                "class": f"form-control field-color-dcsp font-dcsp border-info { c.FORM_ELEMENTS_MAX_WIDTH }",
                "autocomplete": "current-password",
            }
        ),
    )

    def clean(self) -> dict[str, str]:
        """ """
        cleaned_data: dict[str, str] = self.cleaned_data
        setup_choice: str = cleaned_data["setup_choice"]
        external_repository_url_import: str = cleaned_data[
            "external_repository_url_import"
        ]

        if setup_choice == "start_anew":
            cleaned_data.pop("external_repository_url_import", None)
            cleaned_data.pop("external_repository_username_import", None)
            cleaned_data.pop("external_repository_password_token_import", None)
        else:
            validated_response(
                self,
                "external_repository_url_import",
                not " " in external_repository_url_import,
                "Spaces are not allowed in a url",
            )

        return cleaned_data

clean()

Source code in app/dcsp/app/forms.py
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
def clean(self) -> dict[str, str]:
    """ """
    cleaned_data: dict[str, str] = self.cleaned_data
    setup_choice: str = cleaned_data["setup_choice"]
    external_repository_url_import: str = cleaned_data[
        "external_repository_url_import"
    ]

    if setup_choice == "start_anew":
        cleaned_data.pop("external_repository_url_import", None)
        cleaned_data.pop("external_repository_username_import", None)
        cleaned_data.pop("external_repository_password_token_import", None)
    else:
        validated_response(
            self,
            "external_repository_url_import",
            not " " in external_repository_url_import,
            "Spaces are not allowed in a url",
        )

    return cleaned_data

ProjectSetupStepTwoForm

Bases: Form

ProjectSetupStepTwoForm

Fields

project_name_import_start_anew: name of the project

Source code in app/dcsp/app/forms.py
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
class ProjectSetupStepTwoForm(forms.Form):
    """ProjectSetupStepTwoForm

    Fields:
        project_name_import_start_anew: name of the project
    """

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        """Initialise the fields for the second step of project setup"""
        super(ProjectSetupStepTwoForm, self).__init__(*args, **kwargs)
        groups_list: QuerySet[Any] = ProjectGroup.objects.none()
        choices_list_1: list[Any] = []
        choices_list_2: list[Any] = []

        self.fields["project_name"] = forms.CharField(
            label="Project name",
            required=True,
            widget=forms.TextInput(
                attrs={
                    "class": f"form-control field-color-dcsp font-dcsp border-info { c.FORM_ELEMENTS_MAX_WIDTH }",
                    "autocomplete": "off",
                }
            ),
        )

        self.fields["description"] = forms.CharField(
            required=True,
            widget=forms.Textarea(
                attrs={
                    "class": f"form-control field-color-dcsp font-dcsp border-info { c.FORM_ELEMENTS_MAX_WIDTH }",
                    "rows": 3,
                    "autocomplete": "off",
                }
            ),
        )

        self.fields["access"] = forms.ChoiceField(
            choices=ViewAccess.choices,
            initial=ViewAccess.PUBLIC,
            widget=forms.Select(
                attrs={
                    "class": c.SELECT_STYLE,
                    "onChange": "change_visibility()",
                }
            ),
        )

        groups_list = ProjectGroup.objects.values("id", "name")

        for group in groups_list:
            choices_list_1.append([group["id"], group["name"]])

        CHOICES_1 = tuple(choices_list_1)

        self.fields["groups"] = forms.MultipleChoiceField(
            label="Group select",
            help_text="Press CTRL or Command (&#8984;) to select multiple groups",
            required=False,
            choices=CHOICES_1,
            widget=forms.SelectMultiple(
                attrs={
                    "class": "form-select w-auto",
                    # "style": "height: 80px",
                }
            ),
        )

        # TODO #40 - will need to figure out who you can see to add (some people may want to have membership hidden)
        members_list = User.objects.values("id", "first_name", "last_name")

        for member in members_list:
            choices_list_2.append(
                [
                    member["id"],
                    f"{ member['first_name']} { member['last_name']}",
                ]
            )

        CHOICES_2 = tuple(choices_list_2)

        self.fields["members"] = forms.MultipleChoiceField(
            label="Add members ",
            help_text="Press CTRL or Command (&#8984;) to select multiple members",
            required=False,
            choices=CHOICES_2,
            widget=forms.SelectMultiple(
                attrs={
                    "class": "form-select w-auto",
                    # "style": "height: 80px",
                }
            ),
        )

__init__(*args, **kwargs)

Initialise the fields for the second step of project setup

Source code in app/dcsp/app/forms.py
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
def __init__(self, *args: Any, **kwargs: Any) -> None:
    """Initialise the fields for the second step of project setup"""
    super(ProjectSetupStepTwoForm, self).__init__(*args, **kwargs)
    groups_list: QuerySet[Any] = ProjectGroup.objects.none()
    choices_list_1: list[Any] = []
    choices_list_2: list[Any] = []

    self.fields["project_name"] = forms.CharField(
        label="Project name",
        required=True,
        widget=forms.TextInput(
            attrs={
                "class": f"form-control field-color-dcsp font-dcsp border-info { c.FORM_ELEMENTS_MAX_WIDTH }",
                "autocomplete": "off",
            }
        ),
    )

    self.fields["description"] = forms.CharField(
        required=True,
        widget=forms.Textarea(
            attrs={
                "class": f"form-control field-color-dcsp font-dcsp border-info { c.FORM_ELEMENTS_MAX_WIDTH }",
                "rows": 3,
                "autocomplete": "off",
            }
        ),
    )

    self.fields["access"] = forms.ChoiceField(
        choices=ViewAccess.choices,
        initial=ViewAccess.PUBLIC,
        widget=forms.Select(
            attrs={
                "class": c.SELECT_STYLE,
                "onChange": "change_visibility()",
            }
        ),
    )

    groups_list = ProjectGroup.objects.values("id", "name")

    for group in groups_list:
        choices_list_1.append([group["id"], group["name"]])

    CHOICES_1 = tuple(choices_list_1)

    self.fields["groups"] = forms.MultipleChoiceField(
        label="Group select",
        help_text="Press CTRL or Command (&#8984;) to select multiple groups",
        required=False,
        choices=CHOICES_1,
        widget=forms.SelectMultiple(
            attrs={
                "class": "form-select w-auto",
                # "style": "height: 80px",
            }
        ),
    )

    # TODO #40 - will need to figure out who you can see to add (some people may want to have membership hidden)
    members_list = User.objects.values("id", "first_name", "last_name")

    for member in members_list:
        choices_list_2.append(
            [
                member["id"],
                f"{ member['first_name']} { member['last_name']}",
            ]
        )

    CHOICES_2 = tuple(choices_list_2)

    self.fields["members"] = forms.MultipleChoiceField(
        label="Add members ",
        help_text="Press CTRL or Command (&#8984;) to select multiple members",
        required=False,
        choices=CHOICES_2,
        widget=forms.SelectMultiple(
            attrs={
                "class": "form-select w-auto",
                # "style": "height: 80px",
            }
        ),
    )

TemplateSelectForm

Bases: Form

Template selection form

Provides available templates.

Fields

template_choice: pick the template to use for hazard documentation.

Source code in app/dcsp/app/forms.py
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
class TemplateSelectForm(forms.Form):
    """Template selection form

    Provides available templates.

    Fields:
        template_choice: pick the template to use for hazard documentation.
    """

    def __init__(self, project_id: int, *args: Any, **kwargs: Any) -> None:
        """Initialise with available templates

        Searches in the templates folder for template sub-folders and provides
        these as options in a selection field for the user.
        """
        super(TemplateSelectForm, self).__init__(*args, **kwargs)
        project: project_builder.ProjectBuilder = (
            project_builder.ProjectBuilder(project_id)
        )
        templates: list[str] = project.master_template_get()
        template: str = ""
        choices_list: list[Any] = []

        if len(templates) == 0:
            raise Exception("No templates found in templates folder!")

        for template in templates:
            choices_list.append([template, template])

        CHOICES = tuple(choices_list)

        self.fields["template_choice"] = forms.ChoiceField(
            choices=CHOICES,
            widget=forms.Select(attrs={"class": c.SELECT_STYLE}),
        )

__init__(project_id, *args, **kwargs)

Initialise with available templates

Searches in the templates folder for template sub-folders and provides these as options in a selection field for the user.

Source code in app/dcsp/app/forms.py
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
def __init__(self, project_id: int, *args: Any, **kwargs: Any) -> None:
    """Initialise with available templates

    Searches in the templates folder for template sub-folders and provides
    these as options in a selection field for the user.
    """
    super(TemplateSelectForm, self).__init__(*args, **kwargs)
    project: project_builder.ProjectBuilder = (
        project_builder.ProjectBuilder(project_id)
    )
    templates: list[str] = project.master_template_get()
    template: str = ""
    choices_list: list[Any] = []

    if len(templates) == 0:
        raise Exception("No templates found in templates folder!")

    for template in templates:
        choices_list.append([template, template])

    CHOICES = tuple(choices_list)

    self.fields["template_choice"] = forms.ChoiceField(
        choices=CHOICES,
        widget=forms.Select(attrs={"class": c.SELECT_STYLE}),
    )

UploadToGithubForm

Bases: Form

Add comment for commit

Form to add comment to then add to commit and then push to GitHub

Fields

comment

Source code in app/dcsp/app/forms.py
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
class UploadToGithubForm(forms.Form):
    """Add comment for commit

    Form to add comment to then add to commit and then push to GitHub

    Fields:
        comment
    """

    comment = forms.CharField(
        widget=forms.Textarea(
            attrs={
                "class": "form-control field-color-dcsp font-dcsp border-info",
                "style": "height: 150px",
            }
        ),
    )

heading_level(heading)

Determine the heading level

Parameters:

Name Type Description Default
heading str

heading to check

required

Returns:

Name Type Description
str str

the heading css class

Source code in app/dcsp/app/forms.py
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
def heading_level(heading: str) -> str:
    """Determine the heading level

    Args:
        heading: heading to check

    Returns:
        str: the heading css class
    """
    if heading.startswith("# "):
        return "h1-dcsp"
    elif heading.startswith("## "):
        return "h2-dcsp"
    elif heading.startswith("### "):
        return "h3-dcsp"
    elif heading.startswith("#### "):
        return "h4-dcsp"
    else:
        return "h5-dcsp"

md_files(project_id)

Finds markdown files

Looks for markdown files in MKDOCS_PATH. Returns a list of paths.

Parameters:

Name Type Description Default
project_id int

project database id number

required

Returns:

Name Type Description
list list[tuple[Any, Any]]

list of paths of markdown files relative to MKDOCS_PATH

Raises:

Type Description
FileNotFoundError

if the docs location is not a valid directory

Source code in app/dcsp/app/forms.py
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
def md_files(project_id: int) -> list[tuple[Any, Any]]:
    """Finds markdown files

    Looks for markdown files in MKDOCS_PATH. Returns a list of paths.

    Args:
        project_id (int): project database id number

    Returns:
        list: list of paths of markdown files relative to MKDOCS_PATH

    Raises:
        FileNotFoundError: if the docs location is not a valid directory
    """
    md_files: list[Any] = []
    file: Any = ""
    choices_list: list[tuple[Any, Any]] = []

    project: project_builder.ProjectBuilder = project_builder.ProjectBuilder(
        project_id
    )
    md_files = project.documents_list()

    for file in md_files:
        choices_list.append((file, file))

    return choices_list

validated_response(self, field, valid, error_message)

A general function to create form validation results

Provides the field class and error messages to work with Bootstrap.

Parameters:

Name Type Description Default
field str

name of field.

required
valid bool

if the validation of the field passes, True = passes, False = does not pass.

required
error_message str

message to display if data does not pass validation.

required
Source code in app/dcsp/app/forms.py
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
def validated_response(  # type: ignore[no-untyped-def]
    self,
    field: str,
    valid: bool,
    error_message: str,
) -> None:
    """A general function to create form validation results

    Provides the field class and error messages to work with Bootstrap.

    Args:
        field: name of field.
        valid: if the validation of the field passes, True = passes, False = does
               not pass.
        error_message: message to display if data does not pass validation.
    """
    if valid:
        self.fields[field].widget.attrs[
            "class"
        ] = f"form-control is-valid { c.FORM_ELEMENTS_MAX_WIDTH }"
    else:
        self.add_error(field, error_message)
        self.fields[field].widget.attrs[
            "class"
        ] = f"form-control is-invalid { c.FORM_ELEMENTS_MAX_WIDTH }"
    return