Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add management command to update link text #3390

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
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
17 changes: 17 additions & 0 deletions docs/src/management-commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,23 @@ Reset MT budget of regions whose renewal month is the current month::
* ``--force``: Allow to reset the budget even if it's not the first day of the month


``update_link_text``
~~~~~~~~~~~~~~~~~~~~~~

Update ALL link text of links wof the given URL::

integreat-cms-cli update_link_text [--target_url TARGET_URL] [--new_link_text NEW_LINK_TEXT] [--username USERNAME]

**Arguments:**

* ``TARGET_URL``: Update the link text of ALL links of the url ``TARGET_URL``
* ``NEW_LINK_TEXT``: Update the link texts to ``NEW_LINK_TEXT``

**Options:**

* ``USERNAME``: Associate any new created translations with ``USERNAME``


Create new commands
-------------------

Expand Down
141 changes: 141 additions & 0 deletions integreat_cms/core/management/commands/update_link_text.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
from __future__ import annotations

import logging
import time
from html import unescape
from typing import TYPE_CHECKING

from django.contrib.auth import get_user_model
from django.core.management.base import CommandError
from linkcheck.listeners import tasks_queue
from linkcheck.models import Url
from lxml.etree import ParserError
from lxml.html import fromstring, tostring

if TYPE_CHECKING:
from typing import Any

from django.core.management.base import CommandParser

from .regions.region import Region
from .users.user import User


from ..log_command import LogCommand

logger = logging.getLogger(__name__)


def get_url(url: str) -> Region:
"""
Get an URL object by url or raise an error if not found

:param url: url as string
:return: URL
"""
try:
return Url.objects.get(url=url)
except Url.DoesNotExist as e:
raise CommandError(f'URL object with url "{url}" does not exist.') from e


def get_user(username: str) -> User:
"""
Get a user by username or raise an error if not found

:param username: Username
:return: User
"""
try:
return get_user_model().objects.get(username=username)
except get_user_model().DoesNotExist as e:
raise CommandError(f'User with username "{username}" does not exist.') from e


class Command(LogCommand):
"""
Management command to update the link text of ALL links with the given URL.
ALL links receive the SAME new link text.
This applies in ALL regions.
"""

help = "Update link text of the URL"

def add_arguments(self, parser: CommandParser) -> None:
"""
Define the arguments of this command

:param parser: The argument parser
"""
parser.add_argument(
"--target-url", help="The URL whose link text needs to be changed"
)
parser.add_argument(
"--new-link-text", help="The URL whose link text needs to be changed"
)
parser.add_argument("--username", help="The username of the creator")

def handle(
self,
*args: Any,
target_url: str,
new_link_text: str,
username: str,
**options: Any,
) -> None:
r"""
Try to run the command

:param \*args: The supplied arguments
:param target_url: URL whose link texts need to be updated
:param new_link_text: New link text to replace with the current link texts
:param username: The username of the creator
:param \**options: The supplied keyword options
:raises ~django.core.management.base.CommandError: When the input is invalid
"""
self.set_logging_stream()

if not target_url:
raise CommandError("Please specify the target URL.")

if not new_link_text:
raise CommandError("Please specify the new link text.")

target_url_object = get_url(target_url)
user = get_user(username) if username else None

target_links = target_url_object.links.all()

target_contents = {target_link.content_object for target_link in target_links}

for target_content in target_contents:
if target_content._meta.default_related_name in [
"page_translations",
"event_translations",
"poi_translations",
"imprint_translations",
]:
try:
content = fromstring(target_content.content)
except ParserError:
logger.info(
"A link text in %r could not be updated automatically.",
target_content,
)

for elem, _, link, _ in content.iterlinks():
if link == target_url:
elem.text = new_link_text

new_translation = target_content.create_new_version_copy(user)
new_translation.content = unescape(
tostring(content, encoding="unicode", with_tail=False)
)

if new_translation.content != target_content.content:
target_content.links.all().delete()
new_translation.save()
time.sleep(0.1)
tasks_queue.join()

logger.success("✔ Successfully finished updating link texts.") # type: ignore[attr-defined]
110 changes: 110 additions & 0 deletions tests/core/management/commands/test_update_link_text.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from typing import Any

import pytest
from django.core.management.base import CommandError
from linkcheck.listeners import enable_listeners
from linkcheck.models import Link, Url

from ..utils import get_command_output


@pytest.mark.django_db
def test_no_argument_fails() -> None:
"""
Tests that the command fails when no argument is supplied.
"""
with pytest.raises(CommandError) as exc_info:
assert not any(get_command_output("update_link_text"))

assert str(exc_info.value) == "Please specify the target URL."


@pytest.mark.django_db
def test_no_url_fails() -> None:
"""
Tests that the command fails when no URL is supplied.
"""
with pytest.raises(CommandError) as exc_info:
assert not any(
get_command_output("update_link_text", new_link_text="New Link Text")
)

assert str(exc_info.value) == "Please specify the target URL."


@pytest.mark.django_db
def test_no_new_link_text_fails() -> None:
"""
Tests that the command fails when no link text is supplied.
"""
with pytest.raises(CommandError) as exc_info:
assert not any(
get_command_output(
"update_link_text",
target_url="https://not-used-in-our-system.nonexistence",
)
)

assert str(exc_info.value) == "Please specify the new link text."


NOT_USED_URL = "https://not-used-in-our-system.nonexistence"


@pytest.mark.django_db
def test_fails_if_no_url_found(load_test_data: None) -> None:
"""
Tests that the command fails when there is no URL object that has the given URL.
"""
assert not Url.objects.filter(url=NOT_USED_URL).exists()
with pytest.raises(CommandError) as exc_info:
assert not any(
get_command_output(
"update_link_text",
target_url=NOT_USED_URL,
new_link_text="New Link Text",
)
)

assert (
str(exc_info.value) == f'URL object with url "{NOT_USED_URL}" does not exist.'
)


USED_URL = "https://integreat.app/augsburg/de/willkommen/"
NEW_LINK_TEXT = "New Link Text"
NEW_LINK = f'<a href="{USED_URL}">{NEW_LINK_TEXT}</a>'


@pytest.mark.django_db(transaction=True, serialized_rollback=True)
def test_command_succeeds(load_test_data_transactional: Any | None) -> None:
"""
Tests that the command runs successfully.
"""
target_url = Url.objects.filter(url=USED_URL).first()
assert target_url
target_links = Link.objects.filter(url=target_url)
assert target_links.exists()
for link in target_links:
assert link.text != NEW_LINK_TEXT

number_of_target_links = target_links.count()

with enable_listeners():
out, _ = get_command_output(
"update_link_text", target_url=USED_URL, new_link_text=NEW_LINK_TEXT
)

target_url = Url.objects.filter(url=USED_URL).first()
updated_links = Link.objects.filter(url=target_url)
assert updated_links.count() == number_of_target_links
for link in updated_links:
assert link.text == NEW_LINK_TEXT
assert NEW_LINK in link.content_object.content

assert "✔ Successfully finished updating link texts." in out
Loading