This is an automated email from the ASF dual-hosted git repository. ephraimanierobi pushed a commit to branch v2-7-test in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 2a0106e4edf67c5905ebfcb82a6008662ae0f7ad Author: Augusto Hidalgo <augus...@google.com> AuthorDate: Wed Aug 16 14:33:56 2023 +0200 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 <huss...@awala.fr> (cherry picked from commit b7a46c970d638028a4a7643ad000dcee951fb9ef) --- airflow/www/forms.py | 54 +++++++++++++++++++++++++++++++------------- airflow/www/validators.py | 11 +++++++++ tests/www/test_validators.py | 11 +++++++++ 3 files changed, 60 insertions(+), 16 deletions(-) diff --git a/airflow/www/forms.py b/airflow/www/forms.py index 61b203804a..d36a189828 100644 --- a/airflow/www/forms.py +++ b/airflow/www/forms.py @@ -41,7 +41,7 @@ from airflow.configuration import conf 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, @@ -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()) 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=( @@ -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]: diff --git a/airflow/www/validators.py b/airflow/www/validators.py index 8dbe640e8c..ce273308df 100644 --- a/airflow/www/validators.py +++ b/airflow/www/validators.py @@ -97,3 +97,14 @@ class ValidKey: 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 diff --git a/tests/www/test_validators.py b/tests/www/test_validators.py index 718091214c..e77459f1d8 100644 --- a/tests/www/test_validators.py +++ b/tests/www/test_validators.py @@ -155,3 +155,14 @@ class TestValidKey: 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