Skip to content

Commit

Permalink
feat: allow composite slugs in page models without a parent
Browse files Browse the repository at this point in the history
  • Loading branch information
bnznamco committed Mar 25, 2024
1 parent 684d01e commit 4265a76
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 3 deletions.
3 changes: 3 additions & 0 deletions camomilla/managers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .pages import PageQuerySet

__all__ = ["PageQuerySet"]
31 changes: 31 additions & 0 deletions camomilla/managers/pages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from typing import Any, Tuple
from django.db.models.query import QuerySet
from django.db import transaction
from django.apps import apps

URL_NODE_RELATED_NAME = "%(app_label)s_%(class)s"


class PageQuerySet(QuerySet):
def get_or_create(self, defaults=None, **kwargs) -> Tuple[Any, bool]:
if "permalink" in kwargs:
with transaction.atomic():
UrlNode = apps.get_model("camomilla", "UrlNode")
url_node, created = UrlNode.objects.get_or_create(
permalink=kwargs.pop("permalink"),
related_name=URL_NODE_RELATED_NAME % self.model_info
)
if created is False and url_node.page is not None:
page = self.get(**kwargs)
if page.pk != url_node.page.pk:
raise self.model.MultipleObjectsReturned(
"got more than one %s object for the same permalink: %s"
% (
self.model._meta.object_name,
kwargs["permalink"],
)
)
return url_node.page, False
kwargs["url_node"] = url_node
return super().get_or_create(defaults, **kwargs)
return super().get_or_create(defaults, **kwargs)
34 changes: 31 additions & 3 deletions camomilla/models/page.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from uuid import uuid4

from django.core.exceptions import ObjectDoesNotExist
from django.core.validators import RegexValidator

from django.db import ProgrammingError, OperationalError, models, transaction
from django.db.models.signals import post_delete
from django.dispatch import receiver
Expand All @@ -12,6 +14,7 @@
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _

from camomilla.managers.pages import PageQuerySet
from camomilla.models.mixins import MetaMixin, SeoMixin
from camomilla.utils import (
activate_languages,
Expand Down Expand Up @@ -164,6 +167,16 @@ def __new__(cls, name, bases, attrs, **kwargs):
return new_class


class UrlPathValidator(RegexValidator):

regex = r"^[a-zA-Z0-9_\-\/]+[^\/]$"
message = _(
"Enter a valid 'slug' consisting of lowercase letters, numbers, "
"underscores, hyphens and slashes."
)
flags = 0


class AbstractPage(SeoMixin, MetaMixin, models.Model, metaclass=PageBase):
date_created = models.DateTimeField(auto_now_add=True)
date_updated_at = models.DateTimeField(auto_now=True)
Expand All @@ -175,7 +188,9 @@ class AbstractPage(SeoMixin, MetaMixin, models.Model, metaclass=PageBase):
editable=False,
)
breadcrumbs_title = models.CharField(max_length=128, null=True, blank=True)
slug = models.SlugField(max_length=150, allow_unicode=True, null=True, blank=True)
slug = models.CharField(
max_length=150, null=True, blank=True, validators=[UrlPathValidator()]
)
status = models.CharField(
max_length=3,
choices=PAGE_STATUS,
Expand All @@ -195,6 +210,8 @@ class AbstractPage(SeoMixin, MetaMixin, models.Model, metaclass=PageBase):
on_delete=models.CASCADE,
)

objects = PageQuerySet.as_manager()

def __init__(self, *args, **kwargs):
super(AbstractPage, self).__init__(*args, **kwargs)
self._meta.get_field("template").choices = lazy(GET_TEMPLATE_CHOICES, list)()
Expand Down Expand Up @@ -294,7 +311,10 @@ def generate_permalink(self, safe: bool = True) -> str:
else fallback_slug
)
set_nofallbacks(self, "slug", slug)
permalink = "/%s" % slugify(slug or "", allow_unicode=True)
slug_parts = (slug or "").split("/")
for i, part in enumerate(slug_parts):
slug_parts[i] = slugify(part, allow_unicode=True)
permalink = "/%s" % "/".join(slug_parts)
if self.parent:
permalink = f"{self.parent.permalink}{permalink}"
qs = UrlNode.objects.exclude(pk=getattr(self.url_node or object, "pk", None))
Expand Down Expand Up @@ -376,12 +396,20 @@ def get_or_404(cls, request, *args, **kwargs) -> "AbstractPage":
raise Http404(ex)

def alternate_urls(self, *args, **kwargs) -> dict:
request = False
if len(args) > 0:
request = args[0]
if "request" in kwargs:
request = kwargs["request"]
preview = request and getattr(request, "GET", {}).get("preview", False)
permalinks = get_field_translations(self.url_node or object, "permalink", None)
for lang in activate_languages():
if lang in permalinks:
permalinks[lang] = (
UrlNode.reverse_url(permalinks[lang]) if self.is_public else None
UrlNode.reverse_url(permalinks[lang]) if preview or self.is_public else None
)
if preview:
permalinks = {k: f"{v}?preview=true" for k, v in permalinks.items()}
return permalinks

class Meta:
Expand Down

0 comments on commit 4265a76

Please sign in to comment.