diff --git a/CHANGELOG.md b/CHANGELOG.md
index cb654b3..78b0b78 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,7 +3,7 @@
> [!NOTE]
> Release notes are also available on the [wiki](https://github.com/beatonma/django-wm/wiki/Releases).
-## 4.1 (2024-02-09)
+## 4.1.0 (2024-02-10)
> [!WARNING]
> `python manage.py migrate` required for new fields.
@@ -14,12 +14,15 @@
- New `objects` manager for Webmention with some common filters and actions.
-- New [settings](https://github.com/beatonma/django-wm/wiki/Settings) for allowing or disabling webmentions to/from a set of domains:
- - `WEBMENTIONS_DOMAINS_INCOMING_ALLOW: Iterable[str] = None`
- - `WEBMENTIONS_DOMAINS_INCOMING_DENY: Iterable[str] = None`
- - `WEBMENTIONS_DOMAINS_OUTGOING_ALLOW: Iterable[str] = None`
- - `WEBMENTIONS_DOMAINS_OUTGOING_DENY: Iterable[str] = None`
- - `WEBMENTIONS_DOMAINS_OUTGOING_OVERRIDE_TAG: str = None`
+- New settings for allowing or disabling webmentions to/from a set of domains:
+ - `WEBMENTIONS_DOMAINS_INCOMING_ALLOW: Iterable[str] = None`
+ - `WEBMENTIONS_DOMAINS_INCOMING_DENY: Iterable[str] = None`
+ - `WEBMENTIONS_DOMAINS_OUTGOING_ALLOW: Iterable[str] = None`
+ - `WEBMENTIONS_DOMAINS_OUTGOING_DENY: Iterable[str] = None`
+
+- New settings for tags that can be added to your HTML links to allow/disable sending webmentions for just that specific link. (Overrides above allow/deny lists)
+ - `WEBMENTIONS_DOMAINS_OUTGOING_TAG_ALLOW: str = None`
+ - `WEBMENTIONS_DOMAINS_OUTGOING_TAG_DENY: str = None`
## 4.0.4 (2024-02-02)
diff --git a/mentions/__init__.py b/mentions/__init__.py
index 63fb28c..0926727 100644
--- a/mentions/__init__.py
+++ b/mentions/__init__.py
@@ -1,2 +1,2 @@
-__version__ = "4.1"
+__version__ = "4.1.0"
__url__ = "https://github.com/beatonma/django-wm/"
diff --git a/mentions/config.py b/mentions/config.py
index 9502bdc..de611b9 100644
--- a/mentions/config.py
+++ b/mentions/config.py
@@ -63,7 +63,6 @@ def accept_domain_incoming(
def accept_domain_outgoing(
url: str,
- inline_override: bool,
allow_self_mention: bool,
domains_allow: Optional[Set[str]],
domains_deny: Optional[Set[str]],
@@ -72,9 +71,6 @@ def accept_domain_outgoing(
Args:
url: The URL to check.
- inline_override: Provide True if the source of `url` is annotated with
- value of `options.outgoing_domains_override_attr()`,
- otherwise False.
allow_self_mention: Current value of `options.allow_self_mentions()`.
domains_allow: Current value of `options.outgoing_domains_allow()`.
domains_deny: Current value of `options.outgoing_domains_deny()`.
@@ -93,10 +89,10 @@ def accept_domain_outgoing(
)
if domains_deny:
- return _domain_in_set(domain, domains_deny) is inline_override
+ return not _domain_in_set(domain, domains_deny)
if domains_allow:
- return _domain_in_set(domain, domains_allow) is not inline_override
+ return _domain_in_set(domain, domains_allow)
return True
diff --git a/mentions/options.py b/mentions/options.py
index 0c87eb8..26c89be 100644
--- a/mentions/options.py
+++ b/mentions/options.py
@@ -34,7 +34,8 @@
"incoming_domains_deny",
"outgoing_domains_deny",
"outgoing_domains_allow",
- "outgoing_domains_override_tag",
+ "outgoing_domains_tag_allow",
+ "outgoing_domains_tag_deny",
"get_config",
"max_retries",
"retry_interval",
@@ -56,7 +57,8 @@
SETTING_DOMAINS_INCOMING_DENY = f"{NAMESPACE}_DOMAINS_INCOMING_DENY"
SETTING_DOMAINS_OUTGOING_ALLOW = f"{NAMESPACE}_DOMAINS_OUTGOING_ALLOW"
SETTING_DOMAINS_OUTGOING_DENY = f"{NAMESPACE}_DOMAINS_OUTGOING_DENY"
-SETTING_DOMAINS_OUTGOING_OVERRIDE_TAG = f"{NAMESPACE}_DOMAINS_OUTGOING_OVERRIDE_TAG"
+SETTING_DOMAINS_OUTGOING_TAG_ALLOW = f"{NAMESPACE}_DOMAINS_OUTGOING_TAG_ALLOW"
+SETTING_DOMAINS_OUTGOING_TAG_DENY = f"{NAMESPACE}_DOMAINS_OUTGOING_TAG_DENY"
SETTING_INCOMING_TARGET_MODEL_REQUIRED = f"{NAMESPACE}_INCOMING_TARGET_MODEL_REQUIRED"
SETTING_MAX_RETRIES = f"{NAMESPACE}_MAX_RETRIES"
SETTING_RETRY_INTERVAL = f"{NAMESPACE}_RETRY_INTERVAL"
@@ -80,7 +82,8 @@
SETTING_DOMAINS_INCOMING_DENY: None,
SETTING_DOMAINS_OUTGOING_ALLOW: None,
SETTING_DOMAINS_OUTGOING_DENY: None,
- SETTING_DOMAINS_OUTGOING_OVERRIDE_TAG: None,
+ SETTING_DOMAINS_OUTGOING_TAG_ALLOW: None,
+ SETTING_DOMAINS_OUTGOING_TAG_DENY: None,
SETTING_INCOMING_TARGET_MODEL_REQUIRED: False,
SETTING_MAX_RETRIES: 5,
SETTING_RETRY_INTERVAL: 60 * 10,
@@ -206,14 +209,22 @@ def outgoing_domains_deny() -> Set[str]:
return _get_attr(SETTING_DOMAINS_OUTGOING_DENY, _coerce_to_set)
-def outgoing_domains_override_tag() -> str:
- """Return settings.WEBMENTIONS_DOMAINS_OUTGOING_OVERRIDE_TAG.
+def outgoing_domains_tag_allow() -> str:
+ """Return settings.WEBMENTIONS_DOMAINS_OUTGOING_TAG_ALLOW.
- Name of CSS class or HTML `data-` attribute which, if present, reverses
- the behaviour of WEBMENTIONS_DOMAINS_OUTGOING_ALLOW or
- WEBMENTIONS_DOMAINS_OUTGOING_DENY.
+ Tag which can be included in HTML links as a class or attribute to override
+ the behaviour of WEBMENTIONS_DOMAINS_OUTGOING_DENY.
"""
- return _get_attr(SETTING_DOMAINS_OUTGOING_OVERRIDE_TAG)
+ return _get_attr(SETTING_DOMAINS_OUTGOING_TAG_ALLOW)
+
+
+def outgoing_domains_tag_deny() -> str:
+ """Return settings.WEBMENTIONS_DOMAINS_OUTGOING_TAG_DENY.
+
+ Tag which can be included in HTML links as a class or attribute to override
+ the behaviour of WEBMENTIONS_DOMAINS_OUTGOING_ALLOW.
+ """
+ return _get_attr(SETTING_DOMAINS_OUTGOING_TAG_DENY)
def max_retries() -> int:
diff --git a/mentions/tasks/outgoing/local.py b/mentions/tasks/outgoing/local.py
index f7cbe58..c6a6b03 100644
--- a/mentions/tasks/outgoing/local.py
+++ b/mentions/tasks/outgoing/local.py
@@ -1,5 +1,5 @@
import logging
-from typing import Iterable, List, Optional, Set
+from typing import Iterable, Optional, Set
from urllib.parse import urljoin
from bs4 import Tag
@@ -21,9 +21,10 @@ def get_target_links_in_html(
html: str,
source_path: str,
allow_self_mentions: bool = options.allow_self_mentions(),
- domains_allow: Optional[List[str]] = None,
- domains_deny: Optional[List[str]] = None,
- domain_override_attr: Optional[str] = options.outgoing_domains_override_tag(),
+ domains_allow: Optional[Iterable[str]] = None,
+ domains_deny: Optional[Iterable[str]] = None,
+ domains_allow_tag: Optional[str] = options.outgoing_domains_tag_allow(),
+ domains_deny_tag: Optional[str] = options.outgoing_domains_tag_deny(),
) -> Set[str]:
"""Get any links from `html` that should be treated as webmention targets.
@@ -37,7 +38,8 @@ def get_target_links_in_html(
allow_self_mentions: See `options.allow_self_mentions`.
domains_allow: See `options.outgoing_domains_allow`.
domains_deny: See `options.outgoing_domains_deny`.
- domain_override_attr: See `options.outgoing_domains_override_attr`
+ domains_allow_tag: See `options.outgoing_domains_tag_allow`
+ domains_deny_tag: See `options.outgoing_domains_tag_deny`
Returns:
Absolute URLs for any valid links from `html`.
"""
@@ -55,15 +57,18 @@ def get_target_links_in_html(
href = _resolve_relative_url(source_path, href)
+ if _has_class_or_attribute(link, domains_deny_tag):
+ continue
+
+ if _has_class_or_attribute(link, domains_allow_tag):
+ valid_links.add(href)
+ continue
+
if is_valid_target(
href,
allow_self_mentions,
domains_allow=domains_allow,
domains_deny=domains_deny,
- override_include_exclude=_has_class_or_attribute(
- tag=link,
- attr=domain_override_attr,
- ),
):
valid_links.add(href)
@@ -75,7 +80,6 @@ def is_valid_target(
allow_self_mention: bool,
domains_allow: Optional[Iterable[str]],
domains_deny: Optional[Iterable[str]],
- override_include_exclude: bool = False,
) -> bool:
"""
Args:
@@ -83,7 +87,6 @@ def is_valid_target(
allow_self_mention: See `options.allow_self_mentions`.
domains_allow: See `options.outgoing_domains_allow`.
domains_deny: See `options.outgoing_domains_deny`.
- override_include_exclude: See `options.domains_override_attr`
Returns:
True if `url` is a valid URL and should be treated as a possible
@@ -98,7 +101,6 @@ def is_valid_target(
return config.accept_domain_outgoing(
url=url,
- inline_override=override_include_exclude,
allow_self_mention=allow_self_mention,
domains_allow=domains_allow,
domains_deny=domains_deny,
diff --git a/tests/tests/test_options/test_config.py b/tests/tests/test_options/test_config.py
index a7c7101..4b407d3 100644
--- a/tests/tests/test_options/test_config.py
+++ b/tests/tests/test_options/test_config.py
@@ -6,10 +6,16 @@
class AcceptDomainIncomingTests(SimpleTestCase):
- """Tests for config.accept_domain_incoming neither domains_allow, domains_deny defined."""
+ """Tests for config.accept_domain_incoming with neither domains_allow, domains_deny defined."""
def test_with_no_restrictions(self):
- self.assertTrue(accept_domain_incoming(testfunc.random_url(), None, None))
+ self.assertTrue(
+ accept_domain_incoming(
+ testfunc.random_url(),
+ None,
+ None,
+ )
+ )
class AcceptDomainIncoming_withAllowList_Tests(SimpleTestCase):
@@ -115,18 +121,30 @@ def test_mixed_domains(self):
self._assertNotDenied("https://sub.deny.com", deny)
+class AcceptDomainOutgoing(SimpleTestCase):
+ """Tests for config.accept_domain_outgoing with neither domains_allow, domains_deny defined."""
+
+ def test_with_no_restrictions(self):
+ self.assertTrue(
+ accept_domain_outgoing(
+ testfunc.random_url(),
+ True,
+ None,
+ None,
+ )
+ )
+
+
class AcceptDomainOutgoing_withAllowList_Tests(SimpleTestCase):
def _assertAllowed(
self,
url: str,
domains_allow: Set[str],
- inline_override: bool = False,
allow_self_mention: bool = True,
):
self.assertTrue(
accept_domain_outgoing(
url,
- inline_override=inline_override,
allow_self_mention=allow_self_mention,
domains_allow=domains_allow,
domains_deny=None,
@@ -138,13 +156,11 @@ def _assertNotAllowed(
self,
url: str,
domains_allow: Set[str],
- inline_override: bool = False,
allow_self_mention: bool = True,
):
self.assertFalse(
accept_domain_outgoing(
url,
- inline_override=inline_override,
allow_self_mention=allow_self_mention,
domains_allow=domains_allow,
domains_deny=None,
@@ -179,26 +195,17 @@ def test_wildcard_domains_are_not_accepted(self):
self._assertNotAllowed("https://fallow.org", allow)
self._assertNotAllowed("https://not.allow.net", allow)
- def test_inline_override(self):
- allow = {"*.allow.org", "*.allow.com", ""}
- self._assertNotAllowed("https://allow.org", allow, inline_override=True)
- self._assertAllowed(
- "https://normally-disallowed.org", allow, inline_override=True
- )
-
class AcceptDomainOutgoing_withDenyList_Tests(SimpleTestCase):
def _assertDenied(
self,
url: str,
domains_deny: Set[str],
- inline_override: bool = False,
allow_self_mention: bool = True,
):
self.assertFalse(
accept_domain_outgoing(
url,
- inline_override=inline_override,
allow_self_mention=allow_self_mention,
domains_allow=None,
domains_deny=domains_deny,
@@ -210,13 +217,11 @@ def _assertNotDenied(
self,
url: str,
domains_deny: Set[str],
- inline_override: bool = False,
allow_self_mention: bool = True,
):
self.assertTrue(
accept_domain_outgoing(
url,
- inline_override=inline_override,
allow_self_mention=allow_self_mention,
domains_allow=None,
domains_deny=domains_deny,
@@ -243,8 +248,3 @@ def test_wildcard_domains_are_not_denied(self):
deny = {"*.deny.org", "*.also.deny.net"}
self._assertNotDenied(testfunc.random_url(), deny)
self._assertNotDenied("https://other.deny.net/123", deny)
-
- def test_inline_override(self):
- deny = {"*.deny.org", "*.deny.com", ""}
- self._assertNotDenied("https://deny.org", deny, inline_override=True)
- self._assertDenied("https://normally-denied.org", deny, inline_override=True)
diff --git a/tests/tests/test_options/test_option_formats.py b/tests/tests/test_options/test_option_formats.py
index 8853e23..cc3ec0c 100644
--- a/tests/tests/test_options/test_option_formats.py
+++ b/tests/tests/test_options/test_option_formats.py
@@ -20,7 +20,8 @@ def test_flat_settings(self):
settings.WEBMENTIONS_DEFAULT_URL_PARAMETER_MAPPING = {"foo": "bar"}
settings.WEBMENTIONS_DOMAINS_OUTGOING_ALLOW = {"include"}
settings.WEBMENTIONS_DOMAINS_OUTGOING_DENY = {"exclude"}
- settings.WEBMENTIONS_DOMAINS_OUTGOING_OVERRIDE_TAG = "wm-override"
+ settings.WEBMENTIONS_DOMAINS_OUTGOING_TAG_ALLOW = "wm-send"
+ settings.WEBMENTIONS_DOMAINS_OUTGOING_TAG_DENY = "wm-nosend"
settings.WEBMENTIONS_INCOMING_TARGET_MODEL_REQUIRED = True
settings.WEBMENTIONS_MAX_RETRIES = 25
settings.WEBMENTIONS_RETRY_INTERVAL = 30
@@ -42,7 +43,8 @@ def test_flat_settings(self):
self.assertDictEqual(options.default_url_parameter_mapping(), dict(foo="bar"))
self.assertSetEqual(options.outgoing_domains_allow(), {"include"})
self.assertSetEqual(options.outgoing_domains_deny(), {"exclude"})
- self.assertEqual(options.outgoing_domains_override_tag(), "wm-override")
+ self.assertEqual(options.outgoing_domains_tag_allow(), "wm-send")
+ self.assertEqual(options.outgoing_domains_tag_deny(), "wm-nosend")
self.assertEqual(options.user_agent(), "empty")
def test_namespaced_settings(self):
@@ -56,7 +58,8 @@ def test_namespaced_settings(self):
"DEFAULT_URL_PARAMETER_MAPPING": {"bar": "foo"},
"DOMAINS_OUTGOING_ALLOW": ("included",),
"DOMAINS_OUTGOING_DENY": ["excluded"],
- "DOMAINS_OUTGOING_OVERRIDE_TAG": "override-wm",
+ "DOMAINS_OUTGOING_TAG_ALLOW": "send-wm",
+ "DOMAINS_OUTGOING_TAG_DENY": "nosend-wm",
"INCOMING_TARGET_MODEL_REQUIRED": False,
"MAX_RETRIES": 26,
"RETRY_INTERVAL": 31,
@@ -79,5 +82,6 @@ def test_namespaced_settings(self):
self.assertDictEqual(options.default_url_parameter_mapping(), dict(bar="foo"))
self.assertSetEqual(options.outgoing_domains_allow(), {"included"})
self.assertSetEqual(options.outgoing_domains_deny(), {"excluded"})
- self.assertEqual(options.outgoing_domains_override_tag(), "override-wm")
+ self.assertEqual(options.outgoing_domains_tag_allow(), "send-wm")
+ self.assertEqual(options.outgoing_domains_tag_deny(), "nosend-wm")
self.assertEqual(options.user_agent(), "nope")
diff --git a/tests/tests/test_parsing/html/test_find_target_links.py b/tests/tests/test_parsing/html/test_find_target_links.py
index 5e4a3a5..0c1a707 100644
--- a/tests/tests/test_parsing/html/test_find_target_links.py
+++ b/tests/tests/test_parsing/html/test_find_target_links.py
@@ -121,55 +121,83 @@ def test_with_domains_deny(self):
},
)
- def test_override_allow(self):
- attr = "wm-deny"
+ def test_with_no_domains_and_tag_override(self):
+ send_tag = "wm-send"
+ nosend_tag = "wm-nosend"
+ html = f"""
+
+
+
+
+
+ """
- def _get(html: str):
- return get_target_links_in_html(
+ self.assertSetEqual(
+ get_target_links_in_html(
html,
"",
- domains_allow=["allow.org"],
+ domains_allow=None,
domains_deny=None,
- domain_override_attr=attr,
- )
+ domains_deny_tag=nosend_tag,
+ ),
+ {"https://allow.org/by-default/", "https://attr.org/unaffected_by_tag"},
+ )
+
+ def test_with_domains_allow_and_tag_override(self):
+ send_tag = "wm_send"
+ nosend_tag = "wm-no-send"
+ html = f"""
+
+
+
+
+
+ """
self.assertSetEqual(
- _get(""" unrelated-attr"""),
- {"https://allow.org"},
- )
- self.assertSetEqual(_get(f""""""), set())
- self.assertSetEqual(
- _get(f""""""), set()
- )
- self.assertSetEqual(
- _get(f""""""),
- set(),
- )
-
- def test_override_deny(self):
- attr = "wm-allow"
-
- def _get(html: str):
- return get_target_links_in_html(
+ get_target_links_in_html(
html,
"",
- domains_allow=None,
- domains_deny=["deny.org"],
- domain_override_attr=attr,
- )
+ domains_allow={
+ "allow.org",
+ "attr.org",
+ "class.org",
+ "data.org",
+ },
+ domains_deny=None,
+ domains_deny_tag=nosend_tag,
+ ),
+ {"https://allow.org", "https://allow.org/unaffected-by-tag/"},
+ )
+
+ def test_with_domains_deny_and_tag_override(self):
+ send_tag = "wm-send"
+ nosend_tag = "wm-no_send"
+ html = f"""
+
+
+
+
+
+ """
self.assertSetEqual(
- _get(""""""), set()
- )
- self.assertSetEqual(
- _get(f""""""),
- {"https://deny.org"},
- )
- self.assertSetEqual(
- _get(f""""""),
- {"https://deny.org"},
- )
- self.assertSetEqual(
- _get(f""""""),
- {"https://deny.org"},
+ get_target_links_in_html(
+ html,
+ "",
+ domains_allow=None,
+ domains_deny={
+ "deny.org",
+ "attr.org",
+ "class.org",
+ "data.org",
+ },
+ domains_allow_tag=send_tag,
+ domains_deny_tag=nosend_tag,
+ ),
+ {
+ "https://class.org/allow-by-override",
+ "https://data.org/allow-by-override",
+ "https://attr.org/allow-by-override",
+ },
)