Browse Source

Fixed #33662 -- Allowed Sitemap to customize languages for each item.

Roxane 2 years ago
parent
commit
289e9a75af

+ 8 - 3
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)

+ 9 - 0
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
 =========
 

+ 2 - 1
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`
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~

+ 67 - 1
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><loc>{url}/en/i18n/testmodel/{pk}/</loc>"
+            f"<changefreq>never</changefreq><priority>0.5</priority></url>"
+            f"<url><loc>{url}/pt/i18n/testmodel/{pk}/</loc>"
+            f"<changefreq>never</changefreq><priority>0.5</priority></url>"
+            f"<url><loc>{url}/pt/i18n/testmodel/{only_pt_pk}/</loc>"
+            f"<changefreq>never</changefreq><priority>0.5</priority></url>"
+        )
+        expected_content = (
+            f'<?xml version="1.0" encoding="UTF-8"?>\n'
+            f'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" '
+            f'xmlns:xhtml="http://www.w3.org/1999/xhtml">\n'
+            f"{expected_urls}\n"
+            f"</urlset>"
+        )
+        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><loc>{url}/en/i18n/testmodel/{pk}/</loc>"
+            f"<changefreq>never</changefreq><priority>0.5</priority>"
+            f'<xhtml:link rel="alternate" '
+            f'hreflang="en" href="{url}/en/i18n/testmodel/{pk}/"/>'
+            f'<xhtml:link rel="alternate" '
+            f'hreflang="pt" href="{url}/pt/i18n/testmodel/{pk}/"/>'
+            f'<xhtml:link rel="alternate" '
+            f'hreflang="x-default" href="{url}/i18n/testmodel/{pk}/"/></url>'
+            f"<url><loc>{url}/pt/i18n/testmodel/{pk}/</loc>"
+            f"<changefreq>never</changefreq><priority>0.5</priority>"
+            f'<xhtml:link rel="alternate" '
+            f'hreflang="en" href="{url}/en/i18n/testmodel/{pk}/"/>'
+            f'<xhtml:link rel="alternate" '
+            f'hreflang="pt" href="{url}/pt/i18n/testmodel/{pk}/"/>'
+            f'<xhtml:link rel="alternate" '
+            f'hreflang="x-default" href="{url}/i18n/testmodel/{pk}/"/></url>'
+            f"<url><loc>{url}/pt/i18n/testmodel/{only_pt_pk}/</loc>"
+            f"<changefreq>never</changefreq><priority>0.5</priority>"
+            f'<xhtml:link rel="alternate" '
+            f'hreflang="pt" href="{url}/pt/i18n/testmodel/{only_pt_pk}/"/></url>'
+        )
+        expected_content = (
+            f'<?xml version="1.0" encoding="UTF-8"?>\n'
+            f'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" '
+            f'xmlns:xhtml="http://www.w3.org/1999/xhtml">\n'
+            f"{expected_urls}\n"
+            f"</urlset>"
+        )
+        self.assertXMLEqual(response.content.decode(), expected_content)
+
     def test_sitemap_without_entries(self):
         response = self.client.get("/sitemap-without-entries/sitemap.xml")
         expected_content = (

+ 36 - 0
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,