You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

100 lines
3.8 KiB

import datetime
from django.contrib.sitemaps import Sitemap
from django.conf import settings
from django.utils import timezone
from apps.article.models import Article
class BaseCanonicalSitemap(Sitemap):
"""
Base Sitemap class that supports absolute frontend URLs.
Strips out the django-prepended backend scheme and domain from the final URLs
if the location returned is already an absolute URL.
Also ensures all `lastmod` datetimes are rendered in strict W3C / ISO 8601 format with timezone offsets.
"""
limit = 10000
def _urls(self, page, protocol, domain):
urls = super()._urls(page, protocol, domain)
for url_info in urls:
# Fix absolute locations
url_info["location"] = self._fix_absolute_url(url_info["location"])
if "alternates" in url_info:
for alt in url_info["alternates"]:
alt["location"] = self._fix_absolute_url(alt["location"])
# Format naive/aware lastmod datetime objects to strict ISO 8601 / W3C format
lastmod_dt = url_info.get("lastmod")
if isinstance(lastmod_dt, (datetime.datetime, datetime.date)):
if isinstance(lastmod_dt, datetime.datetime):
if timezone.is_naive(lastmod_dt):
lastmod_dt = timezone.make_aware(lastmod_dt)
url_info["lastmod"] = lastmod_dt.isoformat()
return urls
def _fix_absolute_url(self, url):
# Look for a secondary protocol pattern (e.g., http://testserverhttps://domain.com)
for scheme in ("https://", "http://"):
idx = url.find(scheme, 7) # search after the first scheme prefix
if idx != -1:
return url[idx:]
return url
class ArticleSitemap(BaseCanonicalSitemap):
"""
Sitemap class for Article model.
Overridden to dynamically build alternate (hreflang) URLs for all supported languages,
format lastmod to strict W3C / ISO 8601 with timezone offsets, and output directly to XML.
"""
changefreq = "weekly"
priority = 0.6
def items(self):
# Only include active/published articles, sorted by updated_at
return Article.objects.filter(status=True).order_by("-updated_at")
def lastmod(self, obj):
# Use the article's update timestamp for last modification info
return obj.updated_at
def location(self, obj):
# Use the frontend URL via the share_link property
return obj.share_link
def get_urls(self, page=1, site=None, protocol=None):
urls = []
paginator_page = self.paginator.page(page)
for item in paginator_page.object_list:
alternates = []
# Dynamically build alternate URLs for all supported languages
for lang_code, lang_name in settings.LANGUAGES:
localized_url = f"{settings.DOVODI_DOMAIN}/{lang_code}/articles/{item.slug}"
alternates.append({
"location": localized_url,
"lang_code": lang_code,
})
# Safely format naive/aware lastmod datetime to strict ISO 8601 / W3C format
lastmod_dt = item.updated_at
if lastmod_dt:
if timezone.is_naive(lastmod_dt):
lastmod_dt = timezone.make_aware(lastmod_dt)
lastmod_str = lastmod_dt.isoformat()
else:
lastmod_str = None
# Construct URL metadata dictionary
url_info = {
"item": item,
"location": item.share_link,
"lastmod": lastmod_str,
"changefreq": self.changefreq,
"priority": str(self.priority),
"alternates": alternates,
}
urls.append(url_info)
return urls