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
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
|