diff --git a/blog/models.py b/blog/models.py index d11f3de6..98a6b22c 100644 --- a/blog/models.py +++ b/blog/models.py @@ -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}" diff --git a/blog/urls.py b/blog/urls.py index ac04560f..cd00f4f7 100644 --- a/blog/urls.py +++ b/blog/urls.py @@ -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)), ] diff --git a/core/sitemaps.py b/core/sitemaps.py index 93ecd6ad..18496eb7 100644 --- a/core/sitemaps.py +++ b/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'}"