Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 38 additions & 16 deletions airflow/www/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
from airflow.providers_manager import ProvidersManager
from airflow.utils import timezone
from airflow.utils.types import DagRunType
from airflow.www.validators import ValidKey
from airflow.www.validators import ReadOnly, ValidKey
from airflow.www.widgets import (
AirflowDateTimePickerROWidget,
AirflowDateTimePickerWidget,
Expand Down Expand Up @@ -121,38 +121,54 @@ class DateTimeWithNumRunsForm(FlaskForm):
class DagRunEditForm(DynamicForm):
"""Form for editing DAG Run.

We don't actually want to allow editing, so everything is read-only here.
Only note field is editable, so everything else is read-only here.
"""

dag_id = StringField(lazy_gettext("Dag Id"), widget=BS3TextFieldROWidget())
start_date = DateTimeWithTimezoneField(lazy_gettext("Start Date"), widget=AirflowDateTimePickerROWidget())
end_date = DateTimeWithTimezoneField(lazy_gettext("End Date"), widget=AirflowDateTimePickerROWidget())
run_id = StringField(lazy_gettext("Run Id"), widget=BS3TextFieldROWidget())
state = StringField(lazy_gettext("State"), widget=BS3TextFieldROWidget())
dag_id = StringField(lazy_gettext("Dag Id"), validators=[ReadOnly()], widget=BS3TextFieldROWidget())
start_date = DateTimeWithTimezoneField(
lazy_gettext("Start Date"), validators=[ReadOnly()], widget=AirflowDateTimePickerROWidget()
)
end_date = DateTimeWithTimezoneField(
lazy_gettext("End Date"), validators=[ReadOnly()], widget=AirflowDateTimePickerROWidget()
)
run_id = StringField(lazy_gettext("Run Id"), validators=[ReadOnly()], widget=BS3TextFieldROWidget())
state = StringField(lazy_gettext("State"), validators=[ReadOnly()], widget=BS3TextFieldROWidget())
execution_date = DateTimeWithTimezoneField(
lazy_gettext("Logical Date"),
validators=[ReadOnly()],
widget=AirflowDateTimePickerROWidget(),
)
conf = TextAreaField(lazy_gettext("Conf"), widget=BS3TextAreaROWidget())
conf = TextAreaField(lazy_gettext("Conf"), validators=[ReadOnly()], widget=BS3TextAreaROWidget())
note = TextAreaField(lazy_gettext("User Note"), widget=BS3TextAreaFieldWidget())
Comment thread
ahidalgob marked this conversation as resolved.

def populate_obj(self, item):
"""Populates the attributes of the passed obj with data from the form's fields."""
super().populate_obj(item)
"""Populates the attributes of the passed obj with data from the form's not-read-only fields."""
for name, field in self._fields.items():
if not field.flags.readonly:
field.populate_obj(item, name)
item.run_type = DagRunType.from_run_id(item.run_id)
if item.conf:
item.conf = json.loads(item.conf)


class TaskInstanceEditForm(DynamicForm):
"""Form for editing TaskInstance."""
"""Form for editing TaskInstance.

dag_id = StringField(lazy_gettext("Dag Id"), validators=[InputRequired()], widget=BS3TextFieldROWidget())
Only note and state fields are editable, so everything else is read-only here.
"""

dag_id = StringField(
lazy_gettext("Dag Id"), validators=[InputRequired(), ReadOnly()], widget=BS3TextFieldROWidget()
)
task_id = StringField(
lazy_gettext("Task Id"), validators=[InputRequired()], widget=BS3TextFieldROWidget()
lazy_gettext("Task Id"), validators=[InputRequired(), ReadOnly()], widget=BS3TextFieldROWidget()
)
start_date = DateTimeWithTimezoneField(
lazy_gettext("Start Date"), validators=[ReadOnly()], widget=AirflowDateTimePickerROWidget()
)
end_date = DateTimeWithTimezoneField(
lazy_gettext("End Date"), validators=[ReadOnly()], widget=AirflowDateTimePickerROWidget()
)
start_date = DateTimeWithTimezoneField(lazy_gettext("Start Date"), widget=AirflowDateTimePickerROWidget())
end_date = DateTimeWithTimezoneField(lazy_gettext("End Date"), widget=AirflowDateTimePickerROWidget())
state = SelectField(
lazy_gettext("State"),
choices=(
Expand All @@ -167,10 +183,16 @@ class TaskInstanceEditForm(DynamicForm):
execution_date = DateTimeWithTimezoneField(
lazy_gettext("Logical Date"),
widget=AirflowDateTimePickerROWidget(),
validators=[InputRequired()],
validators=[InputRequired(), ReadOnly()],
)
note = TextAreaField(lazy_gettext("User Note"), widget=BS3TextAreaFieldWidget())

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


@cache
def create_connection_form_class() -> type[DynamicForm]:
Expand Down
11 changes: 11 additions & 0 deletions airflow/www/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,14 @@ def __call__(self, form, field):
helpers.validate_key(field.data, self.max_length)
except Exception as e:
raise ValidationError(str(e))


class ReadOnly:
"""Adds readonly flag to a field.

When using this you normally will need to override the form's populate_obj method,
so field.populate_obj is not called for read-only fields.
"""

def __call__(self, form, field):
field.flags.readonly = True
11 changes: 11 additions & 0 deletions tests/www/test_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,14 @@ def test_validation_fails_with_too_many_characters(self):
match=r"The key has to be less than [0-9]+ characters",
):
self._validate()


class TestReadOnly:
def setup_method(self):
self.form_read_only_field_mock = mock.MagicMock(data="readOnlyField")
self.form_mock = mock.MagicMock(spec_set=dict)

def test_read_only_validator(self):
validator = validators.ReadOnly()
assert validator(self.form_mock, self.form_read_only_field_mock) is None
assert self.form_read_only_field_mock.flags.readonly is True