Skip to content

Commit b7a46c9

Browse files
Add read only validation to read only fields (#33413)
* Add read only validation to read only fields Add read only validation to DagRunEditForm and TaskInstanceEditForm read only fields. * Improve docstring --------- Co-authored-by: Hussein Awala <[email protected]>
1 parent 843a3b8 commit b7a46c9

File tree

3 files changed

+60
-16
lines changed

3 files changed

+60
-16
lines changed

airflow/www/forms.py

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
from airflow.providers_manager import ProvidersManager
4242
from airflow.utils import timezone
4343
from airflow.utils.types import DagRunType
44-
from airflow.www.validators import ValidKey
44+
from airflow.www.validators import ReadOnly, ValidKey
4545
from airflow.www.widgets import (
4646
AirflowDateTimePickerROWidget,
4747
AirflowDateTimePickerWidget,
@@ -121,38 +121,54 @@ class DateTimeWithNumRunsForm(FlaskForm):
121121
class DagRunEditForm(DynamicForm):
122122
"""Form for editing DAG Run.
123123
124-
We don't actually want to allow editing, so everything is read-only here.
124+
Only note field is editable, so everything else is read-only here.
125125
"""
126126

127-
dag_id = StringField(lazy_gettext("Dag Id"), widget=BS3TextFieldROWidget())
128-
start_date = DateTimeWithTimezoneField(lazy_gettext("Start Date"), widget=AirflowDateTimePickerROWidget())
129-
end_date = DateTimeWithTimezoneField(lazy_gettext("End Date"), widget=AirflowDateTimePickerROWidget())
130-
run_id = StringField(lazy_gettext("Run Id"), widget=BS3TextFieldROWidget())
131-
state = StringField(lazy_gettext("State"), widget=BS3TextFieldROWidget())
127+
dag_id = StringField(lazy_gettext("Dag Id"), validators=[ReadOnly()], widget=BS3TextFieldROWidget())
128+
start_date = DateTimeWithTimezoneField(
129+
lazy_gettext("Start Date"), validators=[ReadOnly()], widget=AirflowDateTimePickerROWidget()
130+
)
131+
end_date = DateTimeWithTimezoneField(
132+
lazy_gettext("End Date"), validators=[ReadOnly()], widget=AirflowDateTimePickerROWidget()
133+
)
134+
run_id = StringField(lazy_gettext("Run Id"), validators=[ReadOnly()], widget=BS3TextFieldROWidget())
135+
state = StringField(lazy_gettext("State"), validators=[ReadOnly()], widget=BS3TextFieldROWidget())
132136
execution_date = DateTimeWithTimezoneField(
133137
lazy_gettext("Logical Date"),
138+
validators=[ReadOnly()],
134139
widget=AirflowDateTimePickerROWidget(),
135140
)
136-
conf = TextAreaField(lazy_gettext("Conf"), widget=BS3TextAreaROWidget())
141+
conf = TextAreaField(lazy_gettext("Conf"), validators=[ReadOnly()], widget=BS3TextAreaROWidget())
137142
note = TextAreaField(lazy_gettext("User Note"), widget=BS3TextAreaFieldWidget())
138143

139144
def populate_obj(self, item):
140-
"""Populates the attributes of the passed obj with data from the form's fields."""
141-
super().populate_obj(item)
145+
"""Populates the attributes of the passed obj with data from the form's not-read-only fields."""
146+
for name, field in self._fields.items():
147+
if not field.flags.readonly:
148+
field.populate_obj(item, name)
142149
item.run_type = DagRunType.from_run_id(item.run_id)
143150
if item.conf:
144151
item.conf = json.loads(item.conf)
145152

146153

147154
class TaskInstanceEditForm(DynamicForm):
148-
"""Form for editing TaskInstance."""
155+
"""Form for editing TaskInstance.
149156
150-
dag_id = StringField(lazy_gettext("Dag Id"), validators=[InputRequired()], widget=BS3TextFieldROWidget())
157+
Only note and state fields are editable, so everything else is read-only here.
158+
"""
159+
160+
dag_id = StringField(
161+
lazy_gettext("Dag Id"), validators=[InputRequired(), ReadOnly()], widget=BS3TextFieldROWidget()
162+
)
151163
task_id = StringField(
152-
lazy_gettext("Task Id"), validators=[InputRequired()], widget=BS3TextFieldROWidget()
164+
lazy_gettext("Task Id"), validators=[InputRequired(), ReadOnly()], widget=BS3TextFieldROWidget()
165+
)
166+
start_date = DateTimeWithTimezoneField(
167+
lazy_gettext("Start Date"), validators=[ReadOnly()], widget=AirflowDateTimePickerROWidget()
168+
)
169+
end_date = DateTimeWithTimezoneField(
170+
lazy_gettext("End Date"), validators=[ReadOnly()], widget=AirflowDateTimePickerROWidget()
153171
)
154-
start_date = DateTimeWithTimezoneField(lazy_gettext("Start Date"), widget=AirflowDateTimePickerROWidget())
155-
end_date = DateTimeWithTimezoneField(lazy_gettext("End Date"), widget=AirflowDateTimePickerROWidget())
156172
state = SelectField(
157173
lazy_gettext("State"),
158174
choices=(
@@ -167,10 +183,16 @@ class TaskInstanceEditForm(DynamicForm):
167183
execution_date = DateTimeWithTimezoneField(
168184
lazy_gettext("Logical Date"),
169185
widget=AirflowDateTimePickerROWidget(),
170-
validators=[InputRequired()],
186+
validators=[InputRequired(), ReadOnly()],
171187
)
172188
note = TextAreaField(lazy_gettext("User Note"), widget=BS3TextAreaFieldWidget())
173189

190+
def populate_obj(self, item):
191+
"""Populates the attributes of the passed obj with data from the form's not-read-only fields."""
192+
for name, field in self._fields.items():
193+
if not field.flags.readonly:
194+
field.populate_obj(item, name)
195+
174196

175197
@cache
176198
def create_connection_form_class() -> type[DynamicForm]:

airflow/www/validators.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,14 @@ def __call__(self, form, field):
9797
helpers.validate_key(field.data, self.max_length)
9898
except Exception as e:
9999
raise ValidationError(str(e))
100+
101+
102+
class ReadOnly:
103+
"""Adds readonly flag to a field.
104+
105+
When using this you normally will need to override the form's populate_obj method,
106+
so field.populate_obj is not called for read-only fields.
107+
"""
108+
109+
def __call__(self, form, field):
110+
field.flags.readonly = True

tests/www/test_validators.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,14 @@ def test_validation_fails_with_too_many_characters(self):
155155
match=r"The key has to be less than [0-9]+ characters",
156156
):
157157
self._validate()
158+
159+
160+
class TestReadOnly:
161+
def setup_method(self):
162+
self.form_read_only_field_mock = mock.MagicMock(data="readOnlyField")
163+
self.form_mock = mock.MagicMock(spec_set=dict)
164+
165+
def test_read_only_validator(self):
166+
validator = validators.ReadOnly()
167+
assert validator(self.form_mock, self.form_read_only_field_mock) is None
168+
assert self.form_read_only_field_mock.flags.readonly is True

0 commit comments

Comments
 (0)