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