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]
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"}),
}