Features: 1) Add dynamic static page generation in StaticPagesSitemap by integrating active blog posts marked as static pages; 2) Introduce SitemapLanguageMixin to handle language-based URL generation across sitemaps; 3) Add is_static_page field to Post model for designating posts as static pages;
Fixes: 1) Correct router naming in `blog/urls.py` from `payment_router` to `blog_router` for clarity; Extra: 1) Refactor obsolete `StaticPagesSitemap.PAGES` structure with a dynamic `items` method; 2) Create placeholder 404 URLs for non-existent slugs; 3) Update and simplify docstring for `Post` class, replacing inline details with a concise translation-aware docstring.
This commit is contained in:
parent
2712ccdeb7
commit
06290c0278
3 changed files with 59 additions and 92 deletions
|
|
@ -1,4 +1,4 @@
|
|||
from django.db.models import CASCADE, CharField, FileField, ForeignKey, ManyToManyField
|
||||
from django.db.models import CASCADE, CharField, FileField, ForeignKey, ManyToManyField, BooleanField
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_extensions.db.fields import AutoSlugField
|
||||
from markdown.extensions.toc import TocExtension
|
||||
|
|
@ -8,26 +8,13 @@ from core.abstract import NiceModel
|
|||
|
||||
|
||||
class Post(NiceModel): # type: ignore [django-manager-missing]
|
||||
"""
|
||||
Represents a blog post model extending NiceModel.
|
||||
|
||||
The Post class defines the structure and behavior of a blog post. It includes
|
||||
attributes for author, title, content, optional file attachment, slug,
|
||||
and associated tags. The class enforces constraints such as requiring either
|
||||
content or a file attachment but not both simultaneously. It also supports
|
||||
automatic slug generation based on the title. This model can be used in
|
||||
a blogging platform to manage posts created by users.
|
||||
|
||||
Attributes:
|
||||
is_publicly_visible (bool): Specifies whether the post is visible to the public.
|
||||
author (ForeignKey): A reference to the user who authored the post.
|
||||
title (CharField): The title of the post. Must be unique and non-empty.
|
||||
content (MarkdownField): The content of the post written in Markdown format.
|
||||
file (FileField): An optional file attachment for the post.
|
||||
slug (AutoSlugField): A unique, automatically generated slug based on the title.
|
||||
tags (ManyToManyField): Tags associated with the post for categorization.
|
||||
|
||||
"""
|
||||
__doc__ = _(
|
||||
"Represents a blog post model. "
|
||||
"The Post class defines the structure and behavior of a blog post. "
|
||||
"It includes attributes for author, title, content, optional file attachment, slug, and associated tags. "
|
||||
"The class enforces constraints such as requiring either content or a file attachment but not both simultaneously. "
|
||||
"It also supports automatic slug generation based on the title."
|
||||
)
|
||||
|
||||
is_publicly_visible = True
|
||||
|
||||
|
|
@ -76,6 +63,11 @@ class Post(NiceModel): # type: ignore [django-manager-missing]
|
|||
slug = AutoSlugField(populate_from="title", allow_unicode=True, unique=True, editable=False)
|
||||
tags = ManyToManyField(to="blog.PostTag", blank=True, related_name="posts")
|
||||
meta_description = CharField(max_length=150, blank=True, null=True)
|
||||
is_static_page = BooleanField(
|
||||
default=False,
|
||||
verbose_name=_("is static page"),
|
||||
help_text=_("is this a post for a page with static URL (e.g. `/help/delivery`)?"),
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.title} | {self.author.first_name} {self.author.last_name}"
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ from blog.viewsets import PostViewSet
|
|||
|
||||
app_name = "blog"
|
||||
|
||||
payment_router = DefaultRouter()
|
||||
payment_router.register(prefix=r"posts", viewset=PostViewSet, basename="posts")
|
||||
blog_router = DefaultRouter()
|
||||
blog_router.register(prefix=r"posts", viewset=PostViewSet, basename="posts")
|
||||
|
||||
urlpatterns = [
|
||||
path(r"", include(payment_router.urls)),
|
||||
path(r"", include(blog_router.urls)),
|
||||
]
|
||||
|
|
|
|||
111
core/sitemaps.py
111
core/sitemaps.py
|
|
@ -2,51 +2,52 @@ from django.conf import settings
|
|||
from django.contrib.sitemaps import Sitemap
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from blog.models import Post
|
||||
from core.models import Brand, Category, Product
|
||||
from core.utils.seo_builders import any_non_digital
|
||||
from evibes.settings import LANGUAGE_CODE
|
||||
|
||||
|
||||
class StaticPagesSitemap(Sitemap): # type: ignore [type-arg]
|
||||
class SitemapLanguageMixin:
|
||||
def _lang(self) -> str:
|
||||
req = getattr(self, "request", None)
|
||||
return getattr(req, "LANGUAGE_CODE", settings.LANGUAGE_CODE)
|
||||
|
||||
|
||||
class StaticPagesSitemap(SitemapLanguageMixin, Sitemap): # type: ignore [type-arg]
|
||||
protocol = "https"
|
||||
changefreq = "monthly"
|
||||
priority = 0.8
|
||||
limit = 1000
|
||||
|
||||
PAGES = [
|
||||
{
|
||||
"name": _("Home"),
|
||||
"path": f"/{LANGUAGE_CODE}",
|
||||
"lastmod": settings.RELEASE_DATE,
|
||||
},
|
||||
{
|
||||
"name": _("Contact Us"),
|
||||
"path": f"/{LANGUAGE_CODE}/contact-us",
|
||||
"lastmod": settings.RELEASE_DATE,
|
||||
},
|
||||
{
|
||||
"name": _("About Us"),
|
||||
"path": f"/{LANGUAGE_CODE}/about-us",
|
||||
"lastmod": settings.RELEASE_DATE,
|
||||
},
|
||||
{
|
||||
"name": _("Payment Information"),
|
||||
"path": f"/{LANGUAGE_CODE}/help/payments",
|
||||
"lastmod": settings.RELEASE_DATE,
|
||||
},
|
||||
]
|
||||
|
||||
if any_non_digital():
|
||||
PAGES.append(
|
||||
{
|
||||
"name": _("Delivery"),
|
||||
"path": f"/{LANGUAGE_CODE}/help/delivery",
|
||||
"lastmod": settings.RELEASE_DATE,
|
||||
}
|
||||
)
|
||||
|
||||
def items(self):
|
||||
return self.PAGES
|
||||
lang = self._lang()
|
||||
pages = [
|
||||
{
|
||||
"name": _("Home"),
|
||||
"path": f"/{lang}",
|
||||
"lastmod": settings.RELEASE_DATE,
|
||||
},
|
||||
{
|
||||
"name": _("Contact Us"),
|
||||
"path": f"/{lang}/contact-us",
|
||||
"lastmod": settings.RELEASE_DATE,
|
||||
},
|
||||
{
|
||||
"name": _("About Us"),
|
||||
"path": f"/{lang}/about-us",
|
||||
"lastmod": settings.RELEASE_DATE,
|
||||
},
|
||||
]
|
||||
|
||||
for static_post_page in Post.objects.filter(is_static_page=True, is_active=True).only("title", "slug", "modified"):
|
||||
pages.append(
|
||||
{
|
||||
"name": static_post_page.title,
|
||||
"path": f"/{lang}/information/{static_post_page.slug}",
|
||||
"lastmod": static_post_page.modified,
|
||||
}
|
||||
)
|
||||
|
||||
return pages
|
||||
|
||||
def location(self, obj):
|
||||
return obj["path"]
|
||||
|
|
@ -55,33 +56,7 @@ class StaticPagesSitemap(Sitemap): # type: ignore [type-arg]
|
|||
return obj.get("lastmod")
|
||||
|
||||
|
||||
# class FeaturedProductsSitemap(Sitemap): # type: ignore [type-arg]
|
||||
# protocol = "https"
|
||||
# changefreq = "daily"
|
||||
# priority = 0.9
|
||||
# limit = 25000
|
||||
#
|
||||
# def items(self):
|
||||
# return (
|
||||
# Product.objects.filter(
|
||||
# is_active=True,
|
||||
# brand__is_active=True,
|
||||
# category__is_active=True,
|
||||
# stocks__isnull=False,
|
||||
# stocks__vendor__is_active=True,
|
||||
# )
|
||||
# .only("uuid", "name", "modified", "slug")
|
||||
# .order_by("-modified")
|
||||
# )
|
||||
#
|
||||
# def lastmod(self, obj):
|
||||
# return obj.modified
|
||||
#
|
||||
# def location(self, obj):
|
||||
# return f"/{LANGUAGE_CODE}/product/{obj.slug}"
|
||||
|
||||
|
||||
class ProductSitemap(Sitemap): # type: ignore [type-arg]
|
||||
class ProductSitemap(SitemapLanguageMixin, Sitemap): # type: ignore [type-arg]
|
||||
protocol = "https"
|
||||
changefreq = "daily"
|
||||
priority = 0.9
|
||||
|
|
@ -104,10 +79,10 @@ class ProductSitemap(Sitemap): # type: ignore [type-arg]
|
|||
return obj.modified
|
||||
|
||||
def location(self, obj):
|
||||
return f"/{LANGUAGE_CODE}/product/{obj.slug}"
|
||||
return f"/{self._lang()}/product/{obj.slug if obj.slug else '404-non-existent-product'}"
|
||||
|
||||
|
||||
class CategorySitemap(Sitemap): # type: ignore [type-arg]
|
||||
class CategorySitemap(SitemapLanguageMixin, Sitemap): # type: ignore [type-arg]
|
||||
protocol = "https"
|
||||
changefreq = "weekly"
|
||||
priority = 0.7
|
||||
|
|
@ -120,10 +95,10 @@ class CategorySitemap(Sitemap): # type: ignore [type-arg]
|
|||
return obj.modified
|
||||
|
||||
def location(self, obj):
|
||||
return f"/{LANGUAGE_CODE}/catalog/{obj.slug}"
|
||||
return f"/{self._lang()}/catalog/{obj.slug if obj.slug else '404-non-existent-category'}"
|
||||
|
||||
|
||||
class BrandSitemap(Sitemap): # type: ignore [type-arg]
|
||||
class BrandSitemap(SitemapLanguageMixin, Sitemap): # type: ignore [type-arg]
|
||||
protocol = "https"
|
||||
changefreq = "weekly"
|
||||
priority = 0.6
|
||||
|
|
@ -136,4 +111,4 @@ class BrandSitemap(Sitemap): # type: ignore [type-arg]
|
|||
return obj.modified
|
||||
|
||||
def location(self, obj):
|
||||
return f"/{LANGUAGE_CODE}/brand/{obj.slug}"
|
||||
return f"/{self._lang()}/brand/{obj.slug if obj.slug else '404-non-existent-brand'}"
|
||||
|
|
|
|||
Loading…
Reference in a new issue