Source code for nlpmed_portal.nlp.forms

# SPDX-FileCopyrightText: Copyright (C) 2026 Omid Jafari <omidjafari.com>
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

from django import forms

from nlpmed_portal.nlp.models import DuplicateChecker
from nlpmed_portal.nlp.models import EncodingFixer
from nlpmed_portal.nlp.models import Joiner
from nlpmed_portal.nlp.models import MLInference
from nlpmed_portal.nlp.models import NLPSetting
from nlpmed_portal.nlp.models import NoteFilter
from nlpmed_portal.nlp.models import PatternReplacer
from nlpmed_portal.nlp.models import SectionFilter
from nlpmed_portal.nlp.models import SectionSplitter
from nlpmed_portal.nlp.models import SentenceExpander
from nlpmed_portal.nlp.models import SentenceFilter
from nlpmed_portal.nlp.models import SentenceSegmenter
from nlpmed_portal.nlp.models import WordMasker


[docs] def process_multiline_text(data): return [line.strip() for line in data.splitlines() if line.strip()]
[docs] class MultilineJSONListWidget(forms.Textarea):
[docs] def format_value(self, value): if isinstance(value, list): return "\n".join(value) return value
[docs] def value_from_datadict(self, data, files, name): val = data.get(name, "") if val.strip(): return process_multiline_text(val) return []
[docs] class BaseComponentForm(forms.ModelForm):
[docs] def get_initial_for_field(self, field, field_name): value = super().get_initial_for_field(field, field_name) if field_name in { "pattern", "target", "delimiter", "sentence_delimiter", "section_delimiter", }: if isinstance(value, str): return value.replace("\n", "\\n") return value
[docs] def clean(self): cleaned_data = super().clean() or {} for field in [ "words_to_mask", "words_to_search", "section_inc_list", "section_exc_list", ]: if cleaned_data.get(field): cleaned_data[field] = process_multiline_text(cleaned_data[field]) return cleaned_data
[docs] def has_changed(self): if not self.instance.pk: return True return super().has_changed()
[docs] class EncodingFixerForm(BaseComponentForm):
[docs] class Meta: model = EncodingFixer fields = ["status"] widgets = { "status": forms.Select(attrs={"class": "form-select"}), }
[docs] class PatternReplacerForm(BaseComponentForm):
[docs] class Meta: model = PatternReplacer fields = ["status", "pattern", "target"] widgets = { "status": forms.Select(attrs={"class": "form-select"}), "pattern": forms.TextInput(attrs={"class": "form-control"}), "target": forms.TextInput(attrs={"class": "form-control"}), }
[docs] class WordMaskerForm(BaseComponentForm): words_to_mask = forms.CharField( required=False, widget=MultilineJSONListWidget( attrs={ "class": "form-control", "style": "height: 120px;", "placeholder": "word1\nword2\nword3", }, ), help_text="Enter words to mask, one per line.", )
[docs] class Meta: model = WordMasker fields = ["status", "words_to_mask", "mask_char"] widgets = { "status": forms.Select(attrs={"class": "form-select"}), "mask_char": forms.TextInput( attrs={"class": "form-control", "maxlength": 1}, ), }
[docs] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if not self.instance.pk: default = self.instance._meta.get_field("words_to_mask").default # ruff: ignore[private-member-access] default = default() if callable(default) else default self.fields["words_to_mask"].initial = default
[docs] class NoteFilterForm(BaseComponentForm): words_to_search = forms.CharField( required=False, widget=MultilineJSONListWidget( attrs={ "class": "form-control", "style": "height: 120px;", "placeholder": "keyword1\nkeyword2", }, ), help_text="Enter keywords to search in notes, one per line.", )
[docs] class Meta: model = NoteFilter fields = ["status", "words_to_search"] widgets = { "status": forms.Select(attrs={"class": "form-select"}), }
[docs] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if not self.instance.pk: default = self.instance._meta.get_field("words_to_search").default # ruff: ignore[private-member-access] default = default() if callable(default) else default self.fields["words_to_search"].initial = default
[docs] class SectionSplitterForm(BaseComponentForm):
[docs] class Meta: model = SectionSplitter fields = ["status", "delimiter"] widgets = { "status": forms.Select(attrs={"class": "form-select"}), "delimiter": forms.TextInput(attrs={"class": "form-control"}), }
[docs] class SectionFilterForm(BaseComponentForm): section_inc_list = forms.CharField( required=False, widget=MultilineJSONListWidget( attrs={ "class": "form-control", "style": "height: 120px;", "placeholder": "include1\ninclude2", }, ), help_text="Enter keywords to include sections, one per line.", ) section_exc_list = forms.CharField( required=False, widget=MultilineJSONListWidget( attrs={ "class": "form-control", "style": "height: 120px;", "placeholder": "exclude1\nexclude2", }, ), help_text="Enter keywords to exclude sections, one per line.", )
[docs] class Meta: model = SectionFilter fields = ["status", "section_inc_list", "section_exc_list", "fallback"] widgets = { "status": forms.Select(attrs={"class": "form-select"}), "fallback": forms.CheckboxInput(attrs={"class": "form-check-input"}), }
[docs] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if not self.instance.pk: default = self.instance._meta.get_field("section_inc_list").default # ruff: ignore[private-member-access] default = default() if callable(default) else default self.fields["section_inc_list"].initial = default default = self.instance._meta.get_field("section_exc_list").default # ruff: ignore[private-member-access] default = default() if callable(default) else default self.fields["section_exc_list"].initial = default
[docs] class SentenceSegmenterForm(BaseComponentForm):
[docs] class Meta: model = SentenceSegmenter fields = ["status", "model_name", "batch_size"] widgets = { "status": forms.Select(attrs={"class": "form-select"}), "model_name": forms.Select(attrs={"class": "form-select"}), "batch_size": forms.NumberInput(attrs={"class": "form-control", "min": 1}), }
[docs] class DuplicateCheckerForm(BaseComponentForm):
[docs] class Meta: model = DuplicateChecker fields = ["status", "num_perm", "sim_threshold", "length_threshold"] widgets = { "status": forms.Select(attrs={"class": "form-select"}), "num_perm": forms.NumberInput(attrs={"class": "form-control", "min": 1}), "sim_threshold": forms.NumberInput( attrs={"class": "form-control", "step": "0.01", "min": 0, "max": 1}, ), "length_threshold": forms.NumberInput( attrs={"class": "form-control", "min": 1}, ), }
[docs] class SentenceFilterForm(BaseComponentForm): words_to_search = forms.CharField( required=False, widget=MultilineJSONListWidget( attrs={ "class": "form-control", "style": "height: 120px;", "placeholder": "keyword1\nkeyword2", }, ), help_text="Enter keywords to filter sentences, one per line.", )
[docs] class Meta: model = SentenceFilter fields = ["status", "words_to_search"] widgets = { "status": forms.Select(attrs={"class": "form-select"}), }
[docs] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if not self.instance.pk: default = self.instance._meta.get_field("words_to_search").default # ruff: ignore[private-member-access] default = default() if callable(default) else default self.fields["words_to_search"].initial = default
[docs] class SentenceExpanderForm(BaseComponentForm):
[docs] class Meta: model = SentenceExpander fields = ["status", "length_threshold"] widgets = { "status": forms.Select(attrs={"class": "form-select"}), "length_threshold": forms.NumberInput(attrs={"class": "form-control"}), }
[docs] class JoinerForm(BaseComponentForm):
[docs] class Meta: model = Joiner fields = ["status", "sentence_delimiter", "section_delimiter"] widgets = { "status": forms.Select(attrs={"class": "form-select"}), "sentence_delimiter": forms.TextInput(attrs={"class": "form-control"}), "section_delimiter": forms.TextInput(attrs={"class": "form-control"}), }
[docs] class MLInferenceForm(BaseComponentForm):
[docs] class Meta: model = MLInference fields = ["status", "model_name", "use_preped_text"] widgets = { "status": forms.Select(attrs={"class": "form-select"}), "model_name": forms.Select(attrs={"class": "form-select"}), "use_preped_text": forms.CheckboxInput(attrs={"class": "form-check-input"}), }
[docs] class NLPForm(forms.ModelForm): input_text = forms.CharField( required=True, initial="", help_text="Input text to process", widget=forms.Textarea( attrs={"class": "form-control", "style": "height: 120px;"}, ), )
[docs] class Meta: model = NLPSetting fields = ["input_text", "debug"] widgets = { "debug": forms.CheckboxInput(attrs={"class": "form-check-input"}), }