diff --git a/django/contrib/sitemaps/__init__.py b/django/contrib/sitemaps/__init__.py
index 3d276b60d4..df57f1cd5c 100644
--- a/django/contrib/sitemaps/__init__.py
+++ b/django/contrib/sitemaps/__init__.py
@@ -92,6 +92,10 @@ class Sitemap:
return attr(item)
return attr
+ def get_languages_for_item(self, item):
+ """Languages for which this item is displayed."""
+ return self._languages()
+
def _languages(self):
if self.languages is not None:
return self.languages
@@ -103,8 +107,8 @@ class Sitemap:
# This is necessary to paginate with all languages already considered.
items = [
(item, lang_code)
- for lang_code in self._languages()
for item in self.items()
+ for lang_code in self.get_languages_for_item(item)
]
return items
return self.items()
@@ -201,7 +205,8 @@ class Sitemap:
}
if self.i18n and self.alternates:
- for lang_code in self._languages():
+ item_languages = self.get_languages_for_item(item[0])
+ for lang_code in item_languages:
loc = f"{protocol}://{domain}{self._location(item, lang_code)}"
url_info["alternates"].append(
{
@@ -209,7 +214,7 @@ class Sitemap:
"lang_code": lang_code,
}
)
- if self.x_default:
+ if self.x_default and settings.LANGUAGE_CODE in item_languages:
lang_code = settings.LANGUAGE_CODE
loc = f"{protocol}://{domain}{self._location(item, lang_code)}"
loc = loc.replace(f"/{lang_code}/", "/", 1)
diff --git a/docs/ref/contrib/sitemaps.txt b/docs/ref/contrib/sitemaps.txt
index d3225405a3..7dc3dced51 100644
--- a/docs/ref/contrib/sitemaps.txt
+++ b/docs/ref/contrib/sitemaps.txt
@@ -311,6 +311,15 @@ Note:
The latest ``lastmod`` returned by calling the method with all
items returned by :meth:`Sitemap.items`.
+ .. method:: Sitemap.get_languages_for_item(item, lang_code)
+
+ .. versionadded:: 4.2
+
+ **Optional.** A method that returns the sequence of language codes for
+ which the item is displayed. By default
+ :meth:`~Sitemap.get_languages_for_item` returns
+ :attr:`~Sitemap.languages`.
+
Shortcuts
=========
diff --git a/docs/releases/4.2.txt b/docs/releases/4.2.txt
index 682fce2a53..7e93df0702 100644
--- a/docs/releases/4.2.txt
+++ b/docs/releases/4.2.txt
@@ -145,7 +145,8 @@ Minor features
:mod:`django.contrib.sitemaps`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-* ...
+* The new :meth:`.Sitemap.get_languages_for_item` method allows customizing the
+ list of languages for which the item is displayed.
:mod:`django.contrib.sites`
~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/tests/sitemaps_tests/test_http.py b/tests/sitemaps_tests/test_http.py
index 8c16f66896..12e387757b 100644
--- a/tests/sitemaps_tests/test_http.py
+++ b/tests/sitemaps_tests/test_http.py
@@ -10,7 +10,7 @@ from django.utils.deprecation import RemovedInDjango50Warning
from django.utils.formats import localize
from .base import SitemapTestsBase
-from .models import TestModel
+from .models import I18nTestModel, TestModel
class HTTPSitemapTests(SitemapTestsBase):
@@ -440,6 +440,72 @@ class HTTPSitemapTests(SitemapTestsBase):
)
self.assertXMLEqual(response.content.decode(), expected_content)
+ @override_settings(LANGUAGES=(("en", "English"), ("pt", "Portuguese")))
+ def test_language_for_item_i18n_sitemap(self):
+ """
+ A i18n sitemap index in which item can be chosen to be displayed for a
+ lang or not.
+ """
+ only_pt = I18nTestModel.objects.create(name="Only for PT")
+ response = self.client.get("/item-by-lang/i18n.xml")
+ url, pk, only_pt_pk = self.base_url, self.i18n_model.pk, only_pt.pk
+ expected_urls = (
+ f"{url}/en/i18n/testmodel/{pk}/"
+ f"never0.5"
+ f"{url}/pt/i18n/testmodel/{pk}/"
+ f"never0.5"
+ f"{url}/pt/i18n/testmodel/{only_pt_pk}/"
+ f"never0.5"
+ )
+ expected_content = (
+ f'\n'
+ f'\n'
+ f"{expected_urls}\n"
+ f""
+ )
+ self.assertXMLEqual(response.content.decode(), expected_content)
+
+ @override_settings(LANGUAGES=(("en", "English"), ("pt", "Portuguese")))
+ def test_alternate_language_for_item_i18n_sitemap(self):
+ """
+ A i18n sitemap index in which item can be chosen to be displayed for a
+ lang or not.
+ """
+ only_pt = I18nTestModel.objects.create(name="Only for PT")
+ response = self.client.get("/item-by-lang-alternates/i18n.xml")
+ url, pk, only_pt_pk = self.base_url, self.i18n_model.pk, only_pt.pk
+ expected_urls = (
+ f"{url}/en/i18n/testmodel/{pk}/"
+ f"never0.5"
+ f''
+ f''
+ f''
+ f"{url}/pt/i18n/testmodel/{pk}/"
+ f"never0.5"
+ f''
+ f''
+ f''
+ f"{url}/pt/i18n/testmodel/{only_pt_pk}/"
+ f"never0.5"
+ f''
+ )
+ expected_content = (
+ f'\n'
+ f'\n'
+ f"{expected_urls}\n"
+ f""
+ )
+ self.assertXMLEqual(response.content.decode(), expected_content)
+
def test_sitemap_without_entries(self):
response = self.client.get("/sitemap-without-entries/sitemap.xml")
expected_content = (
diff --git a/tests/sitemaps_tests/urls/http.py b/tests/sitemaps_tests/urls/http.py
index 75dd4834c0..2b512cfd69 100644
--- a/tests/sitemaps_tests/urls/http.py
+++ b/tests/sitemaps_tests/urls/http.py
@@ -48,6 +48,22 @@ class XDefaultI18nSitemap(AlternatesI18nSitemap):
x_default = True
+class ItemByLangSitemap(SimpleI18nSitemap):
+ def get_languages_for_item(self, item):
+ if item.name == "Only for PT":
+ return ["pt"]
+ return super().get_languages_for_item(item)
+
+
+class ItemByLangAlternatesSitemap(AlternatesI18nSitemap):
+ x_default = True
+
+ def get_languages_for_item(self, item):
+ if item.name == "Only for PT":
+ return ["pt"]
+ return super().get_languages_for_item(item)
+
+
class EmptySitemap(Sitemap):
changefreq = "never"
priority = 0.5
@@ -168,6 +184,14 @@ xdefault_i18n_sitemaps = {
"i18n-xdefault": XDefaultI18nSitemap,
}
+item_by_lang_i18n_sitemaps = {
+ "i18n-item-by-lang": ItemByLangSitemap,
+}
+
+item_by_lang_alternates_i18n_sitemaps = {
+ "i18n-item-by-lang-alternates": ItemByLangAlternatesSitemap,
+}
+
simple_sitemaps_not_callable = {
"simple": SimpleSitemap(),
}
@@ -358,6 +382,18 @@ urlpatterns = [
{"sitemaps": sitemaps_lastmod_ascending},
name="django.contrib.sitemaps.views.sitemap",
),
+ path(
+ "item-by-lang/i18n.xml",
+ views.sitemap,
+ {"sitemaps": item_by_lang_i18n_sitemaps},
+ name="django.contrib.sitemaps.views.sitemap",
+ ),
+ path(
+ "item-by-lang-alternates/i18n.xml",
+ views.sitemap,
+ {"sitemaps": item_by_lang_alternates_i18n_sitemaps},
+ name="django.contrib.sitemaps.views.sitemap",
+ ),
path(
"lastmod-sitemaps/descending.xml",
views.sitemap,