diff --git a/src/core/models.py b/src/core/models.py index a6b0fdc..93b45e8 100644 --- a/src/core/models.py +++ b/src/core/models.py @@ -881,6 +881,10 @@ class Operation(models.TextChoices): "remove_reference", pgettext_lazy("batchcommand-py-operation-remove-reference", "Remove reference"), ) + REMOVE_REFERENCE_BLOCK = ( + "remove_reference_block", + pgettext_lazy("batchcommand-py-operation-remove-reference-block", "Remove reference block"), + ) # SET_SITELINK = ( "set_sitelink", @@ -1535,6 +1539,7 @@ def operation_is_combinable(self): self.Operation.REMOVE_STATEMENT_BY_VALUE, self.Operation.REMOVE_QUALIFIER, self.Operation.REMOVE_REFERENCE, + self.Operation.REMOVE_REFERENCE_BLOCK, self.Operation.ADD_ALIAS, self.Operation.SET_LABEL, self.Operation.SET_DESCRIPTION, @@ -1686,6 +1691,7 @@ def update_entity_json(self, entity: dict): elif self.operation in ( self.Operation.REMOVE_QUALIFIER, self.Operation.REMOVE_REFERENCE, + self.Operation.REMOVE_REFERENCE_BLOCK, ): self._remove_qualifier_or_reference(entity) elif self.operation == self.Operation.SET_SITELINK: @@ -1747,7 +1753,7 @@ def _update_entity_statements(self, entity: dict): def _remove_qualifier_or_reference(self, entity: dict): """ - Removes a qualifier or a reference from the entity. + Removes a qualifier, a reference part or a reference block from the entity. """ statements = self._get_statements(entity) found_qualifier = False @@ -1768,9 +1774,9 @@ def _remove_qualifier_or_reference(self, entity: dict): found_ref_part = True break if found_ref_part: + if self.operation == self.Operation.REMOVE_REFERENCE_BLOCK: + statement["references"].pop(i) break - # any better way to do this? :P - # (without refactoring into a different function...) if found_ref_part: break if not found_qualifier and len(self.qualifiers_for_api()) > 0: diff --git a/src/core/parsers/v1.py b/src/core/parsers/v1.py index f9ccdf7..bb371c0 100644 --- a/src/core/parsers/v1.py +++ b/src/core/parsers/v1.py @@ -94,17 +94,19 @@ def parse_remove_qualifier(self, elements): return data def parse_remove_reference(self, elements): + oprt = str(elements[0]) + is_block = oprt == "REMOVE_REF_BLOCK" llen = len(elements) if llen != 6: - raise ParserException("REMOVE_REF command must be Qid|Pid|value|Sid|value") + raise ParserException(f"{oprt} command must be Qid|Pid|value|Sid|value") elements.pop(0) data = self.parse_statement(elements, elements[0].upper()) data["action"] = "remove" - data["what"] = "reference" + data["what"] = "reference_block" if is_block else "reference" if len(data.get("references", [])) != 1: - raise ParserException("REMOVE_REF command must have 1 reference") + raise ParserException(f"{oprt} command must have 1 reference") if len(data.get("qualifiers", [])) != 0: - raise ParserException("REMOVE_REF command must have no qualifiers") + raise ParserException(f"{oprt} command must have no qualifiers") return data def parse_statement_by_id(self, elements): @@ -354,7 +356,7 @@ def parse_command(self, raw_command): elif first_command == "REMOVE_QUAL": logger.debug(f"parsing remove qualifier: {elements}") data = self.parse_remove_qualifier(elements) - elif first_command == "REMOVE_REF": + elif first_command in ("REMOVE_REF", "REMOVE_REF_BLOCK"): logger.debug(f"parsing remove reference: {elements}") data = self.parse_remove_reference(elements) elif first_command == "SWITCH_VALUE": @@ -447,6 +449,8 @@ def restore_placeholders(command): bc.operation = bc.Operation.REMOVE_QUALIFIER elif what == "reference": bc.operation = bc.Operation.REMOVE_REFERENCE + elif what == "reference_block": + bc.operation = bc.Operation.REMOVE_REFERENCE_BLOCK elif command["action"] == "create": bc.action = BatchCommand.ACTION_CREATE what_or_type = command.get("type", command.get("what")) diff --git a/src/core/tests/test_entity_patching.py b/src/core/tests/test_entity_patching.py index bcf322c..237521d 100644 --- a/src/core/tests/test_entity_patching.py +++ b/src/core/tests/test_entity_patching.py @@ -427,6 +427,49 @@ def test_remove_reference(self): prop = entity["statements"]["P31"][0]["references"][1]["parts"][0]["property"] self.assertEqual(prop["id"], "P74") + def test_remove_reference_block(self): + text = """ + REMOVE_REF_BLOCK|Q12345678|P65|42|S31|somevalue + REMOVE_REF_BLOCK|Q12345678|P31|somevalue|S93|"https://www.mediawiki.org/" + REMOVE_REF_BLOCK|Q12345678|P31|somevalue|S84267|42 + REMOVE_REF_BLOCK|Q12345678|P31|somevalue|S84267|42 + """ + batch = self.parse(text) + entity = copy.deepcopy(self.INITIAL) + # ----- + remove_nothing = batch.commands()[0] + self.assertRefCount(entity, "P65", 0) + with self.assertRaises(NoReferenceParts): + remove_nothing.update_entity_json(entity) + self.assertRefCount(entity, "P65", 0) + # --- + prop = entity["statements"]["P31"][0]["references"][0]["parts"][0]["property"] + self.assertEqual(prop["id"], "P93") + prop = entity["statements"]["P31"][0]["references"][1]["parts"][0]["property"] + self.assertEqual(prop["id"], "P93") + # ----- + remove_part_mediawiki = batch.commands()[1] + self.assertRefCount(entity, "P31", 2) + self.assertRefPartsCount(entity, "P31", 2, ipart=0) + self.assertRefPartsCount(entity, "P31", 3, ipart=1) + remove_part_mediawiki.update_entity_json(entity) + self.assertRefCount(entity, "P31", 1) + self.assertRefPartsCount(entity, "P31", 2, ipart=0) + with self.assertRaises(NoReferenceParts): + # try to remove it again + remove_part_mediawiki.update_entity_json(entity) + # ----- + remove_42 = batch.commands()[2] + self.assertRefCount(entity, "P31", 1) + self.assertRefPartsCount(entity, "P31", 2, ipart=0) + remove_42.update_entity_json(entity) + self.assertRefCount(entity, "P31", 0) + # ----- + remove_42_again = batch.commands()[3] + with self.assertRaises(NoReferenceParts): + # try to remove it again + remove_42_again.update_entity_json(entity) + def test_add_reference(self): text = """ Q12345678|P31|somevalue|S93|"https://example.com/" diff --git a/translations/en.json b/translations/en.json index 51a087b..17bdc8e 100644 --- a/translations/en.json +++ b/translations/en.json @@ -26,6 +26,7 @@ "batchcommand-py-operation-remove-statement-by-value": "Remove statement by value", "batchcommand-py-operation-remove-qualifier": "Remove qualifier", "batchcommand-py-operation-remove-reference": "Remove reference", + "batchcommand-py-operation-remove-reference-block": "Remove reference block", "batchcommand-py-operation-set-sitelink": "Set sitelink", "batchcommand-py-operation-set-label": "Set label", "batchcommand-py-operation-set-description": "Set description", diff --git a/translations/pt-br.json b/translations/pt-br.json index 78960dc..e230a7b 100644 --- a/translations/pt-br.json +++ b/translations/pt-br.json @@ -30,6 +30,7 @@ "batchcommand-py-operation-remove-statement-by-value": "Remover declaração por valor", "batchcommand-py-operation-remove-qualifier": "Remover qualificador", "batchcommand-py-operation-remove-reference": "Remover referência", + "batchcommand-py-operation-remove-reference-block": "Remover bloco de referência", "batchcommand-py-operation-set-sitelink": "Definir hiperligação de site", "batchcommand-py-operation-set-label": "Definir rótulo", "batchcommand-py-operation-set-description": "Definir descrição", diff --git a/translations/qqq.json b/translations/qqq.json index c644518..688cf0c 100644 --- a/translations/qqq.json +++ b/translations/qqq.json @@ -31,6 +31,7 @@ "batchcommand-py-operation-remove-statement-by-value": "Individual command operation type displayed in command list", "batchcommand-py-operation-remove-qualifier": "Individual command operation type displayed in command list", "batchcommand-py-operation-remove-reference": "Individual command operation type displayed in command list", + "batchcommand-py-operation-remove-reference-block": "Individual command operation type displayed in command list", "batchcommand-py-operation-set-sitelink": "Individual command operation type displayed in command list", "batchcommand-py-operation-set-label": "Individual command operation type displayed in command list", "batchcommand-py-operation-set-description": "Individual command operation type displayed in command list",