diff --git a/measures/editors.py b/measures/editors.py index d2ebbeffc..0a55451f2 100644 --- a/measures/editors.py +++ b/measures/editors.py @@ -14,6 +14,7 @@ from measures.util import update_measure_excluded_geographical_areas from measures.util import update_measure_footnote_associations + class MeasuresEditor: """Utility class used to edit measures from measures wizard accumulated data.""" @@ -28,7 +29,12 @@ class MeasuresEditor: """Validated, cleaned and accumulated data created by the Form instances of `MeasureEditWizard`.""" - def __init__(self, workbasket: Type["workbasket_models.WorkBasket"], selected_measures: List, data: Dict): + def __init__( + self, + workbasket: Type["workbasket_models.WorkBasket"], + selected_measures: List, + data: Dict, + ): self.workbasket = workbasket self.selected_measures = selected_measures self.data = data @@ -106,7 +112,7 @@ def edit_measures(self) -> List["measure_models.Measure"]: measure=new_measure, workbasket=self.workbasket, ) - + edited_measures.append(new_measure.id) return edited_measures diff --git a/measures/forms/wizard.py b/measures/forms/wizard.py index c80f89372..d83142df3 100644 --- a/measures/forms/wizard.py +++ b/measures/forms/wizard.py @@ -818,7 +818,7 @@ def clean(self): ) return cleaned_data - + @classmethod def serializable_init_kwargs(cls, kwargs: Dict) -> Dict: selected_measures = kwargs.get("selected_measures") @@ -835,7 +835,9 @@ def serializable_init_kwargs(cls, kwargs: Dict) -> Dict: @classmethod def deserialize_init_kwargs(cls, form_kwargs: Dict) -> Dict: serialized_selected_measures_pks = form_kwargs.get("selected_measures") - deserialized_selected_measures = models.Measure.objects.filter(pk__in=serialized_selected_measures_pks) + deserialized_selected_measures = models.Measure.objects.filter( + pk__in=serialized_selected_measures_pks + ) kwargs = { "selected_measures": deserialized_selected_measures, @@ -884,7 +886,7 @@ def clean(self): cleaned_data["end_date"] = None return cleaned_data - + @classmethod def serializable_init_kwargs(cls, kwargs: Dict) -> Dict: selected_measures = kwargs.get("selected_measures") @@ -901,7 +903,9 @@ def serializable_init_kwargs(cls, kwargs: Dict) -> Dict: @classmethod def deserialize_init_kwargs(cls, form_kwargs: Dict) -> Dict: serialized_selected_measures_pks = form_kwargs.get("selected_measures") - deserialized_selected_measures = models.Measure.objects.filter(pk__in=serialized_selected_measures_pks) + deserialized_selected_measures = models.Measure.objects.filter( + pk__in=serialized_selected_measures_pks + ) kwargs = { "selected_measures": deserialized_selected_measures, @@ -952,7 +956,9 @@ def serializable_init_kwargs(cls, kwargs: Dict) -> Dict: @classmethod def deserialize_init_kwargs(cls, form_kwargs: Dict) -> Dict: serialized_selected_measures_pks = form_kwargs.get("selected_measures") - deserialized_selected_measures = models.Measure.objects.filter(pk__in=serialized_selected_measures_pks) + deserialized_selected_measures = models.Measure.objects.filter( + pk__in=serialized_selected_measures_pks + ) kwargs = { "selected_measures": deserialized_selected_measures, @@ -1003,7 +1009,7 @@ def clean(self): validate_duties(duties, measure.valid_between.lower) return cleaned_data - + @classmethod def serializable_init_kwargs(cls, kwargs: Dict) -> Dict: selected_measures = kwargs.get("selected_measures") @@ -1020,7 +1026,9 @@ def serializable_init_kwargs(cls, kwargs: Dict) -> Dict: @classmethod def deserialize_init_kwargs(cls, form_kwargs: Dict) -> Dict: serialized_selected_measures_pks = form_kwargs.get("selected_measures") - deserialized_selected_measures = models.Measure.objects.filter(pk__in=serialized_selected_measures_pks) + deserialized_selected_measures = models.Measure.objects.filter( + pk__in=serialized_selected_measures_pks + ) kwargs = { "selected_measures": deserialized_selected_measures, diff --git a/measures/models/bulk_processing.py b/measures/models/bulk_processing.py index 1f8955167..908e90d98 100644 --- a/measures/models/bulk_processing.py +++ b/measures/models/bulk_processing.py @@ -508,11 +508,14 @@ def edit_measures(self) -> Iterable[Measure]: transaction=self.workbasket.current_transaction, ): cleaned_data = self.get_forms_cleaned_data() - deserialized_selected_measures = Measure.objects.filter(pk__in=self.selected_measures) - - measures_editor = MeasuresEditor(self.workbasket, deserialized_selected_measures, cleaned_data) - return measures_editor.edit_measures() + deserialized_selected_measures = Measure.objects.filter( + pk__in=self.selected_measures + ) + measures_editor = MeasuresEditor( + self.workbasket, deserialized_selected_measures, cleaned_data + ) + return measures_editor.edit_measures() def get_forms_cleaned_data(self) -> Dict: """ @@ -527,6 +530,7 @@ def get_forms_cleaned_data(self) -> Dict: all_cleaned_data = {} from measures.views import MeasureEditWizard + for form_key, form_class in MeasureEditWizard.data_form_list: if form_key not in self.form_data: @@ -549,7 +553,7 @@ def get_forms_cleaned_data(self) -> Dict: all_cleaned_data[f"formset-{form_key}"] = form.cleaned_data else: all_cleaned_data.update(form.cleaned_data) - + return all_cleaned_data def _log_form_errors(self, form_class, form_or_formset) -> None: @@ -575,4 +579,4 @@ def _log_form_errors(self, form_class, form_or_formset) -> None: for form_errors in errors: for error_key, error_values in form_errors.items(): - logger.error(f"{error_key}: {error_values}") \ No newline at end of file + logger.error(f"{error_key}: {error_values}") diff --git a/measures/tasks.py b/measures/tasks.py index 20387a94d..755e64fc8 100644 --- a/measures/tasks.py +++ b/measures/tasks.py @@ -45,6 +45,7 @@ def bulk_create_measures(measures_bulk_creator_pk: int) -> None: f"WorkBasket({measures_bulk_creator.workbasket.pk}).", ) + @app.task def bulk_edit_measures(measures_bulk_editor_pk: int) -> None: """Bulk edit measures from serialized measures form data saved within an @@ -65,7 +66,7 @@ def bulk_edit_measures(measures_bulk_editor_pk: int) -> None: f"WorkBasket({measures_bulk_editor.workbasket.pk}).", ) raise e - + measures_bulk_editor.processing_succeeded() measures_bulk_editor.successfully_processed_count = len(measures) measures_bulk_editor.save() @@ -75,4 +76,4 @@ def bulk_edit_measures(measures_bulk_editor_pk: int) -> None: f"MeasuresBulkEditor({measures_bulk_editor.pk}) task " f"succeeded in editing {len(measures)} Measures in " f"WorkBasket({measures_bulk_editor.workbasket.pk}).", - ) \ No newline at end of file + ) diff --git a/measures/tests/conftest.py b/measures/tests/conftest.py index 0bded8d7a..df5d8e12c 100644 --- a/measures/tests/conftest.py +++ b/measures/tests/conftest.py @@ -324,22 +324,23 @@ def mock_request(rf, valid_user, valid_user_client): request.requests_session = requests.Session() return request + @pytest.fixture() def measure_edit_start_date_form_data(): return { - "start_date_0": 1, - "start_date_1": 1, - "start_date_2": 2023, - } + "start_date_0": 1, + "start_date_1": 1, + "start_date_2": 2023, + } @pytest.fixture() def measure_edit_end_date_form_data(): return { - "end_date_0": 2, - "end_date_1": 2, - "end_date_2": 2026, - } + "end_date_0": 2, + "end_date_1": 2, + "end_date_2": 2026, + } @pytest.fixture() @@ -351,6 +352,7 @@ def measure_regulation_id_form_data(): def measure_edit_regulation_form_data(): return {"generating_regulation": factories.RegulationFactory.create()} + @pytest.fixture() def measure_details_form_data(date_ranges): return { @@ -510,7 +512,7 @@ def measure_geo_area_geo_group_exclusions_form_data(erga_omnes): @pytest.fixture() def measure_edit_duties_form_data(): - return {"duties": '4%'} + return {"duties": "4%"} @pytest.fixture() @@ -552,10 +554,11 @@ def simple_measures_bulk_editor( user=None, ) + @pytest.fixture() def mocked_edit_schedule_apply_async(): with patch( "measures.tasks.bulk_edit_measures.apply_async", return_value=MagicMock(id=faker.Faker().uuid4()), ) as apply_async_mock: - yield apply_async_mock \ No newline at end of file + yield apply_async_mock diff --git a/measures/tests/test_bulk_processing.py b/measures/tests/test_bulk_processing.py index 8336ffd99..e58f05ec0 100644 --- a/measures/tests/test_bulk_processing.py +++ b/measures/tests/test_bulk_processing.py @@ -186,7 +186,7 @@ def test_bulk_creator_get_forms_cleaned_data( } -# Run the form and get the form data from the sync done +# Run the form and get the form data from the sync done @patch("measures.parsers.DutySentenceParser") def test_bulk_editor_get_forms_cleaned_data( mock_duty_sentence_parser, @@ -219,19 +219,19 @@ def test_bulk_editor_get_forms_cleaned_data( }, "quota_order_number": {"order_number": order_number.pk}, "regulation": {"generating_regulation": regulation.pk}, - "duties": {"duties": '4%'}, + "duties": {"duties": "4%"}, "geographical_area_exclusions": { "form-0-excluded_area": geo_area1.pk, - "form-1-excluded_area": geo_area2.pk - } + "form-1-excluded_area": geo_area2.pk, + }, } form_kwargs = { - "start_date": {'selected_measures': selected_measures}, - "end_date": {'selected_measures': selected_measures}, + "start_date": {"selected_measures": selected_measures}, + "end_date": {"selected_measures": selected_measures}, "quota_order_number": {}, - "regulation": {'selected_measures': selected_measures}, - "duties": {'selected_measures': selected_measures}, + "regulation": {"selected_measures": selected_measures}, + "duties": {"selected_measures": selected_measures}, "geographical_area_exclusions": {}, } @@ -249,8 +249,11 @@ def test_bulk_editor_get_forms_cleaned_data( "end_date": datetime.date(2026, 2, 2), "generating_regulation": regulation, "order_number": order_number, - "duties": '4%', - "formset-geographical_area_exclusions": [{'excluded_area': geo_area1, 'DELETE': False }, {'excluded_area': geo_area2, 'DELETE': False }] + "duties": "4%", + "formset-geographical_area_exclusions": [ + {"excluded_area": geo_area1, "DELETE": False}, + {"excluded_area": geo_area2, "DELETE": False}, + ], } @@ -341,19 +344,19 @@ def test_bulk_editor_get_forms_cleaned_data_errors( }, "quota_order_number": {"order_number": ""}, "regulation": {"generating_regulation": ""}, - "duties": {"duties": ''}, + "duties": {"duties": ""}, "geographical_area_exclusions": { "form-0-excluded_area": "", - "form-1-excluded_area": "" - } + "form-1-excluded_area": "", + }, } form_kwargs = { - "start_date": {'selected_measures': selected_measures}, - "end_date": {'selected_measures': selected_measures}, + "start_date": {"selected_measures": selected_measures}, + "end_date": {"selected_measures": selected_measures}, "quota_order_number": {}, - "regulation": {'selected_measures': selected_measures}, - "duties": {'selected_measures': selected_measures}, + "regulation": {"selected_measures": selected_measures}, + "duties": {"selected_measures": selected_measures}, "geographical_area_exclusions": {}, } @@ -368,4 +371,3 @@ def test_bulk_editor_get_forms_cleaned_data_errors( with override_current_transaction(user_empty_workbasket.current_transaction): with pytest.raises(ValidationError): mock_bulk_editor.get_forms_cleaned_data() - diff --git a/measures/tests/test_forms.py b/measures/tests/test_forms.py index 15bc69d44..294fe9ea5 100644 --- a/measures/tests/test_forms.py +++ b/measures/tests/test_forms.py @@ -2023,10 +2023,7 @@ def test_measure_edit_forms_geo_area_exclusions_serialize_deserialize(): geo_area1 = factories.GeographicalAreaFactory.create() geo_area2 = factories.GeographicalAreaFactory.create() - form_data = { - "form-0-excluded_area": geo_area1, - "form-1-excluded_area": geo_area2 - } + form_data = {"form-0-excluded_area": geo_area1, "form-1-excluded_area": geo_area2} with override_current_transaction(Transaction.objects.last()): form = forms.MeasureGeographicalAreaExclusionsFormSet( form_data, @@ -2040,4 +2037,4 @@ def test_measure_edit_forms_geo_area_exclusions_serialize_deserialize(): ) assert deserialized_form.is_valid() assert type(deserialized_form) == forms.MeasureGeographicalAreaExclusionsFormSet - assert deserialized_form.data == form.data \ No newline at end of file + assert deserialized_form.data == form.data diff --git a/measures/tests/test_views.py b/measures/tests/test_views.py index 5efa390d5..f6faf38b6 100644 --- a/measures/tests/test_views.py +++ b/measures/tests/test_views.py @@ -1833,6 +1833,7 @@ def test_measuretype_api_list_view(valid_user_client): equals=True, ) + @override_settings(MEASURES_ASYNC_EDIT=False) def test_multiple_measure_start_and_end_date_edit_functionality( valid_user_client, @@ -1927,6 +1928,7 @@ def test_multiple_measure_start_and_end_date_edit_functionality( assert measure.valid_between.upper == datetime.date(2100, 1, 1) assert measure.effective_end_date == datetime.date(2100, 1, 1) + @pytest.mark.parametrize( "step, form_data, updated_attribute, expected_data", [ @@ -2038,6 +2040,7 @@ def test_multiple_measure_edit_single_form_functionality( assert measure.update_type == UpdateType.UPDATE assert reduce(getattr, updated_attribute.split("."), measure) == expected_data + @override_settings(MEASURES_ASYNC_EDIT=False) def test_multiple_measure_edit_only_regulation( valid_user_client, @@ -2263,6 +2266,7 @@ def test_measure_list_selected_measures_list(valid_user_client): assert not measure_ids_in_table.difference(selected_measures_ids) + @override_settings(MEASURES_ASYNC_EDIT=False) def test_multiple_measure_edit_only_quota_order_number( valid_user_client, @@ -2334,6 +2338,7 @@ def test_multiple_measure_edit_only_quota_order_number( assert measure.update_type == UpdateType.UPDATE assert measure.order_number == quota_order_number + @override_settings(MEASURES_ASYNC_EDIT=False) def test_multiple_measure_edit_only_duties( valid_user_client, @@ -2405,6 +2410,7 @@ def test_multiple_measure_edit_only_duties( assert measure.update_type == UpdateType.UPDATE assert measure.duty_sentence == duties + @override_settings(MEASURES_ASYNC_EDIT=False) def test_multiple_measure_edit_preserves_footnote_associations( valid_user_client, @@ -2481,6 +2487,7 @@ def test_multiple_measure_edit_preserves_footnote_associations( for footnote in measure.footnotes.all(): assert footnote in expected_footnotes + @override_settings(MEASURES_ASYNC_EDIT=False) def test_multiple_measure_edit_geographical_area_exclusions( valid_user_client, diff --git a/measures/util.py b/measures/util.py index 2ca1cdf09..80849bde4 100644 --- a/measures/util.py +++ b/measures/util.py @@ -14,6 +14,7 @@ from workbaskets import models as workbasket_models import logging + logger = logging.getLogger(__name__) @@ -56,10 +57,11 @@ def diff_components( """ from measures.parsers import DutySentenceParser - # We add in the component output type here as otherwise we run into circular import issues. - component_output = measure_models.MeasureComponent if not component_output else component_output + # We add in the component output type here as otherwise we run into circular import issues. + component_output = ( + measure_models.MeasureComponent if not component_output else component_output + ) - parser = DutySentenceParser.create( start_date, component_output=component_output, diff --git a/measures/views/mixins.py b/measures/views/mixins.py index 9d283d061..feb0d21ac 100644 --- a/measures/views/mixins.py +++ b/measures/views/mixins.py @@ -53,9 +53,10 @@ def get_queryset(self): return models.Measure.objects.filter(pk__in=self.measure_selections) -class MeasureSerializableWizardMixin(): - """ A Mixin for the wizard forms that utilise asynchronous bulk processing. This mixin provides the functionality to go through each form +class MeasureSerializableWizardMixin: + """A Mixin for the wizard forms that utilise asynchronous bulk processing. This mixin provides the functionality to go through each form and serialize the data ready for storing in the database.""" + def get_data_form_list(self) -> dict: """ Returns a form list based on form_list, conditionally including only @@ -119,4 +120,4 @@ def serializable_form_kwargs_for_step(self, step) -> Dict: form_kwargs = self.get_form_kwargs(step) form_class = self.form_list[step] - return form_class.serializable_init_kwargs(form_kwargs) \ No newline at end of file + return form_class.serializable_init_kwargs(form_kwargs) diff --git a/measures/views/wizard.py b/measures/views/wizard.py index 99ca00279..b69b6d1b9 100644 --- a/measures/views/wizard.py +++ b/measures/views/wizard.py @@ -105,7 +105,7 @@ def get_template_names(self): self.steps.current, "measures/edit-wizard-step.jinja", ) - + @property def workbasket(self) -> WorkBasket: return WorkBasket.current(self.request) @@ -172,13 +172,15 @@ def async_done(self, form_list, **kwargs): kwargs={"pk": self.workbasket.pk}, ), ) - + def edit_measures(self, selected_measures, cleaned_data): """Synchronously edit measures within the context of the view / web worker using accumulated data, `cleaned_data`, from all the necessary wizard forms.""" - measures_editor = MeasuresEditor(self.workbasket, selected_measures, cleaned_data) + measures_editor = MeasuresEditor( + self.workbasket, selected_measures, cleaned_data + ) return measures_editor.edit_measures() def sync_done(self, form_list, **kwargs): @@ -208,10 +210,7 @@ def sync_done(self, form_list, **kwargs): @method_decorator(require_current_workbasket, name="dispatch") class MeasureCreateWizard( - PermissionRequiredMixin, - NamedUrlSessionWizardView, - MeasureSerializableWizardMixin - + PermissionRequiredMixin, NamedUrlSessionWizardView, MeasureSerializableWizardMixin ): """ Multipart form wizard for creating a single measure.