Skip to content

Core Internal Modules

These modules back the public core helpers and expose the implementation-level docstrings for context processors, mixins, selectors, and Redis integration.

Context processors

codex_django.core.context_processors

Context processors for cross-project core concerns.

Currently this module provides template access to resolved static-page SEO metadata so frontend templates can render canonical titles and descriptions without repeating selector logic in views.

Functions

seo_settings(request)

Expose SEO metadata for the current resolved page in templates.

The returned mapping is injected into the template context as seo. The processor expects CODEX_STATIC_PAGE_SEO_MODEL to point to a model that can be consumed by :func:codex_django.core.seo.selectors.get_static_page_seo.

Parameters:

Name Type Description Default
request HttpRequest

Incoming Django request used to resolve the current URL name.

required

Returns:

Type Description
dict[str, Any]

A template context mapping with the seo key. Returns an empty

dict[str, Any]

dictionary payload when the current route cannot be resolved or when

dict[str, Any]

no SEO data is available for the page.

Source code in src/codex_django/core/context_processors.py
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def seo_settings(request: HttpRequest) -> dict[str, Any]:
    """Expose SEO metadata for the current resolved page in templates.

    The returned mapping is injected into the template context as ``seo``.
    The processor expects ``CODEX_STATIC_PAGE_SEO_MODEL`` to point to a
    model that can be consumed by
    :func:`codex_django.core.seo.selectors.get_static_page_seo`.

    Args:
        request: Incoming Django request used to resolve the current URL name.

    Returns:
        A template context mapping with the ``seo`` key. Returns an empty
        dictionary payload when the current route cannot be resolved or when
        no SEO data is available for the page.
    """
    # Try to determine page key from URL name
    url_name = request.resolver_match.url_name if request.resolver_match else None
    if not url_name:
        return {"seo": {}}

    # get_static_page_seo handles all exceptions internally and returns None on failure
    data = get_static_page_seo(url_name)
    return {"seo": data or {}}

Model mixins

codex_django.core.mixins.models

Reusable abstract Django model mixins shared across codex-django packages.

Examples:

Combine several mixins in a project model::

from django.db import models
from codex_django.core.mixins.models import TimestampMixin, ActiveMixin, SeoMixin

class Article(TimestampMixin, ActiveMixin, SeoMixin, models.Model):
    title = models.CharField(max_length=200)

Classes

TimestampMixin

Bases: Model

Add creation and update timestamps to a model.

Notes

created_at is written once on insert, while updated_at is refreshed on every save.

Admin

fieldsets: (_("Timestamps"), {"fields": ("created_at", "updated_at"), "classes": ("collapse",)}) readonly_fields: ("created_at", "updated_at")

Source code in src/codex_django/core/mixins/models.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class TimestampMixin(models.Model):
    """Add creation and update timestamps to a model.

    Notes:
        ``created_at`` is written once on insert, while ``updated_at`` is
        refreshed on every save.

    Admin:
        fieldsets:
            (_("Timestamps"), {"fields": ("created_at", "updated_at"), "classes": ("collapse",)})
        readonly_fields: ("created_at", "updated_at")
    """

    created_at = models.DateTimeField(_("Created at"), auto_now_add=True)
    updated_at = models.DateTimeField(_("Updated at"), auto_now=True)

    class Meta:
        abstract = True

ActiveMixin

Bases: Model

Add an is_active flag for publication and visibility control.

Notes

Use this mixin when objects should remain queryable in the database but be hidden from public listings, navigation, or API responses.

Admin

fieldsets: (_("Status"), {"fields": ("is_active",)}) list_filter: ("is_active",)

Source code in src/codex_django/core/mixins/models.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class ActiveMixin(models.Model):
    """Add an ``is_active`` flag for publication and visibility control.

    Notes:
        Use this mixin when objects should remain queryable in the database
        but be hidden from public listings, navigation, or API responses.

    Admin:
        fieldsets:
            (_("Status"), {"fields": ("is_active",)})
        list_filter: ("is_active",)
    """

    is_active = models.BooleanField(
        _("Active"),
        default=True,
        help_text=_("If unchecked, the object will be hidden on the site."),
    )

    class Meta:
        abstract = True

SeoMixin

Bases: Model

Add basic per-object SEO fields for title, description, and OG image.

Notes

This mixin is intended for content models that need per-instance metadata overrides in templates, cards, or sitemap-adjacent views.

Admin

fieldsets: (_("SEO Settings"), {"fields": ("seo_title", "seo_description", "seo_image"), "classes": ("collapse",)})

Source code in src/codex_django/core/mixins/models.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
class SeoMixin(models.Model):
    """Add basic per-object SEO fields for title, description, and OG image.

    Notes:
        This mixin is intended for content models that need per-instance
        metadata overrides in templates, cards, or sitemap-adjacent views.

    Admin:
        fieldsets:
            (_("SEO Settings"), {"fields": ("seo_title", "seo_description", "seo_image"), "classes": ("collapse",)})
    """

    seo_title = models.CharField(_("SEO Title"), max_length=255, blank=True)
    seo_description = models.TextField(_("SEO Description"), blank=True)
    seo_image = models.ImageField(_("OG Image"), upload_to="seo/", blank=True, null=True)

    class Meta:
        abstract = True

OrderableMixin

Bases: Model

Add a generic order field for custom manual sorting.

Notes

The mixin sets Meta.ordering = ["order"] on the abstract base.

Admin

fieldsets: (_("Ordering"), {"fields": ("order",)}) list_display: include order when manual ordering should be visible in admin.

Source code in src/codex_django/core/mixins/models.py
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
class OrderableMixin(models.Model):
    """Add a generic ``order`` field for custom manual sorting.

    Notes:
        The mixin sets ``Meta.ordering = ["order"]`` on the abstract base.

    Admin:
        fieldsets:
            (_("Ordering"), {"fields": ("order",)})
        list_display:
            include ``order`` when manual ordering should be visible in admin.
    """

    order = models.PositiveIntegerField(_("Sorting Order"), default=0, db_index=True)

    class Meta:
        abstract = True
        ordering = ["order"]

SoftDeleteMixin

Bases: Model

Add an is_deleted flag for soft-deletion workflows.

Notes

The model instance remains in the database. Call soft_delete() when the project wants reversible deletion semantics.

Admin

fieldsets: (_("Archive / Deletion"), {"fields": ("is_deleted",), "classes": ("collapse",)}) list_filter: ("is_deleted",)

Source code in src/codex_django/core/mixins/models.py
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
class SoftDeleteMixin(models.Model):
    """Add an ``is_deleted`` flag for soft-deletion workflows.

    Notes:
        The model instance remains in the database. Call ``soft_delete()``
        when the project wants reversible deletion semantics.

    Admin:
        fieldsets:
            (_("Archive / Deletion"), {"fields": ("is_deleted",), "classes": ("collapse",)})
        list_filter: ("is_deleted",)
    """

    is_deleted = models.BooleanField(_("Is Deleted"), default=False)

    class Meta:
        abstract = True

    def soft_delete(self) -> None:
        """Mark the object as deleted without removing the database row.

        Notes:
            The method updates ``is_deleted`` and immediately saves the model
            instance using the default manager behavior.
        """
        self.is_deleted = True
        self.save()
Functions
soft_delete()

Mark the object as deleted without removing the database row.

Notes

The method updates is_deleted and immediately saves the model instance using the default manager behavior.

Source code in src/codex_django/core/mixins/models.py
120
121
122
123
124
125
126
127
128
def soft_delete(self) -> None:
    """Mark the object as deleted without removing the database row.

    Notes:
        The method updates ``is_deleted`` and immediately saves the model
        instance using the default manager behavior.
    """
    self.is_deleted = True
    self.save()

UUIDMixin

Bases: Model

Replace the default integer primary key with a UUID4 identifier.

Notes

This mixin is useful for public-facing URLs or distributed systems where sequential integer identifiers should not be exposed.

Admin

fieldsets: (_("Identifiers"), {"fields": ("id",), "classes": ("collapse",)}) readonly_fields: ("id",)

Source code in src/codex_django/core/mixins/models.py
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
class UUIDMixin(models.Model):
    """Replace the default integer primary key with a UUID4 identifier.

    Notes:
        This mixin is useful for public-facing URLs or distributed systems
        where sequential integer identifiers should not be exposed.

    Admin:
        fieldsets:
            (_("Identifiers"), {"fields": ("id",), "classes": ("collapse",)})
        readonly_fields: ("id",)
    """

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)

    class Meta:
        abstract = True

SlugMixin

Bases: Model

Add a unique slug field suitable for URL routing.

Notes

The field is intentionally declared with blank=True so projects can populate it through admin prepopulation, model hooks, or custom save logic.

Admin

fieldsets: (_("URL"), {"fields": ("slug",)}) prepopulated_fields: {"slug": ("title",)} # Replace title with the concrete source field.

Source code in src/codex_django/core/mixins/models.py
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
class SlugMixin(models.Model):
    """Add a unique slug field suitable for URL routing.

    Notes:
        The field is intentionally declared with ``blank=True`` so projects
        can populate it through admin prepopulation, model hooks, or custom
        save logic.

    Admin:
        fieldsets:
            (_("URL"), {"fields": ("slug",)})
        prepopulated_fields:
            {"slug": ("title",)}  # Replace ``title`` with the concrete source field.
    """

    slug = models.SlugField(_("URL Slug"), max_length=255, unique=True, blank=True)

    class Meta:
        abstract = True

Sitemaps

codex_django.core.sitemaps

Sitemap primitives with codex-django defaults.

The base sitemap centralizes canonical-domain handling, multilingual URL generation, and namespace-aware reverse lookups so project sitemaps can stay small and focused.

Classes

BaseSitemap

Bases: Sitemap

Base sitemap with Codex defaults for multilingual canonical URLs.

The implementation enables Django's i18n sitemap mode, forces canonical HTTPS URLs, and centralizes domain resolution through settings.CANONICAL_DOMAIN.

Source code in src/codex_django/core/sitemaps.py
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
class BaseSitemap(Sitemap):  # type: ignore[type-arg]
    """Base sitemap with Codex defaults for multilingual canonical URLs.

    The implementation enables Django's i18n sitemap mode, forces canonical
    HTTPS URLs, and centralizes domain resolution through
    ``settings.CANONICAL_DOMAIN``.
    """

    i18n = True
    x_default = True

    @property
    def languages(self) -> list[str]:  # type: ignore[override]
        return [lang[0] for lang in settings.LANGUAGES]

    def get_domain(self, site: Any = None) -> str:
        # Always use the canonical domain from settings (strip protocol if present)
        domain = getattr(settings, "CANONICAL_DOMAIN", "localhost")
        if "://" in domain:
            return domain.split("://")[-1]
        return domain

    def get_urls(self, page: int | str = 1, site: Any = None, protocol: str | None = None) -> list[dict[str, Any]]:
        # Force HTTPS and use our canonical domain
        domain = self.get_domain(site)
        # We pass protocol="https" to the original get_urls
        urls: list[dict[str, Any]] = super().get_urls(page=page, site=None, protocol="https")

        for url_info in urls:
            item = url_info["item"]
            # Django passes (item, lang_code) if i18n=True
            actual_item: Any = item[0] if isinstance(item, list | tuple) else item

            alternates: list[dict[str, str]] = []
            for lang in self.languages:
                with translation.override(lang):
                    loc = self.location(actual_item)
                alternates.append({"lang_code": lang, "location": f"https://{domain}{loc}"})

            # Add x-default (usually pointing to the first language or a specific one)
            default_lang = getattr(settings, "LANGUAGE_CODE", "en")
            with translation.override(default_lang):
                alternates.append(
                    {"lang_code": "x-default", "location": f"https://{domain}{self.location(actual_item)}"}
                )

        return urls

    def location(self, item: Any) -> str:
        """Resolve sitemap items to absolute path components.

        String items are treated as URL pattern names and are first reversed
        directly, then retried against namespaces listed in
        ``settings.SITEMAP_LOOKUP_NAMESPACES``. Non-string items are expected
        to implement ``get_absolute_url()``.

        Args:
            item: Sitemap item emitted by Django. In i18n mode Django may pass
                either the raw item or an ``(item, language)`` pair.

        Returns:
            The resolved path component for the sitemap entry.

        Raises:
            django.urls.NoReverseMatch: If a string item cannot be reversed
                directly or through the configured namespace fallbacks.
        """
        actual_item: Any = item[0] if isinstance(item, list | tuple) else item
        if isinstance(actual_item, str):
            try:
                return reverse(actual_item)
            except NoReverseMatch:
                # Common pattern in Codex projects: check if it's in a namespace
                # You might want to customize this list in subclasses or settings
                namespaces: list[str] = getattr(settings, "SITEMAP_LOOKUP_NAMESPACES", [])
                for ns in namespaces:
                    try:
                        return reverse(f"{ns}:{actual_item}")
                    except NoReverseMatch:
                        continue
                # If still not found, let the final retry raise or return original attempt
                return reverse(actual_item)

        # Assuming actual_item has a get_absolute_url method if it's not a string
        return actual_item.get_absolute_url()  # type: ignore[no-any-return]
Functions
location(item)

Resolve sitemap items to absolute path components.

String items are treated as URL pattern names and are first reversed directly, then retried against namespaces listed in settings.SITEMAP_LOOKUP_NAMESPACES. Non-string items are expected to implement get_absolute_url().

Parameters:

Name Type Description Default
item Any

Sitemap item emitted by Django. In i18n mode Django may pass either the raw item or an (item, language) pair.

required

Returns:

Type Description
str

The resolved path component for the sitemap entry.

Raises:

Type Description
NoReverseMatch

If a string item cannot be reversed directly or through the configured namespace fallbacks.

Source code in src/codex_django/core/sitemaps.py
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
def location(self, item: Any) -> str:
    """Resolve sitemap items to absolute path components.

    String items are treated as URL pattern names and are first reversed
    directly, then retried against namespaces listed in
    ``settings.SITEMAP_LOOKUP_NAMESPACES``. Non-string items are expected
    to implement ``get_absolute_url()``.

    Args:
        item: Sitemap item emitted by Django. In i18n mode Django may pass
            either the raw item or an ``(item, language)`` pair.

    Returns:
        The resolved path component for the sitemap entry.

    Raises:
        django.urls.NoReverseMatch: If a string item cannot be reversed
            directly or through the configured namespace fallbacks.
    """
    actual_item: Any = item[0] if isinstance(item, list | tuple) else item
    if isinstance(actual_item, str):
        try:
            return reverse(actual_item)
        except NoReverseMatch:
            # Common pattern in Codex projects: check if it's in a namespace
            # You might want to customize this list in subclasses or settings
            namespaces: list[str] = getattr(settings, "SITEMAP_LOOKUP_NAMESPACES", [])
            for ns in namespaces:
                try:
                    return reverse(f"{ns}:{actual_item}")
                except NoReverseMatch:
                    continue
            # If still not found, let the final retry raise or return original attempt
            return reverse(actual_item)

    # Assuming actual_item has a get_absolute_url method if it's not a string
    return actual_item.get_absolute_url()  # type: ignore[no-any-return]

SEO selectors

codex_django.core.seo.selectors

Selectors for static-page SEO metadata.

Examples:

Load cached SEO payload for a resolved page name::

seo = get_static_page_seo("home")

Functions

get_static_page_seo(page_key)

Load SEO metadata for a static page, using Redis as a read-through cache.

The helper first checks the centralized SEO Redis manager. On a cache miss, it resolves the model declared by settings.CODEX_STATIC_PAGE_SEO_MODEL, fetches the matching record by page_key, flattens it to a string-only mapping, and stores the result back in Redis.

Parameters:

Name Type Description Default
page_key str

Logical key that identifies the static page.

required

Returns:

Type Description
Any

Cached or freshly loaded SEO data as a mapping-like object, or

Any

None when the model is not configured, the record does not exist,

Any

or loading fails.

Source code in src/codex_django/core/seo/selectors.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
def get_static_page_seo(page_key: str) -> Any:
    """Load SEO metadata for a static page, using Redis as a read-through cache.

    The helper first checks the centralized SEO Redis manager. On a cache
    miss, it resolves the model declared by
    ``settings.CODEX_STATIC_PAGE_SEO_MODEL``, fetches the matching record by
    ``page_key``, flattens it to a string-only mapping, and stores the result
    back in Redis.

    Args:
        page_key: Logical key that identifies the static page.

    Returns:
        Cached or freshly loaded SEO data as a mapping-like object, or
        ``None`` when the model is not configured, the record does not exist,
        or loading fails.
    """
    manager = get_seo_redis_manager()

    cached_data = manager.get_page(page_key)
    if cached_data:
        return cached_data

    model_path = getattr(settings, "CODEX_STATIC_PAGE_SEO_MODEL", None)
    if not model_path:
        return None

    try:
        model = apps.get_model(model_path)
        obj = model.objects.filter(page_key=page_key).first()
        if obj:
            data = obj.to_dict() if hasattr(obj, "to_dict") else model_to_dict(obj)

            # Cache as Redis Hash (no JSON serialization needed)
            # Redis strictly requires string values for mapping. Convert all values to strings.
            flat_data = {k: str(v) if v is not None else "" for k, v in data.items()}

            manager.set_page(page_key, mapping=flat_data, timeout=3600 * 24)
            return flat_data
    except Exception as e:
        log.warning(f"Error fetching static page SEO for key {page_key}: {e}")
        pass

    return None

i18n discovery

codex_django.core.i18n.discovery

Helpers for discovering Django locale directories.

Examples:

Resolve locale paths for a generated project root::

from pathlib import Path
from codex_django.core.i18n import discover_locale_paths

LOCALE_PATHS = discover_locale_paths(Path(BASE_DIR))

Functions

discover_locale_paths(base_dir, include_features=True)

Discover locale directories that should be added to LOCALE_PATHS.

The helper supports both a centralized locale/<domain>/<lang> structure and per-app locale/ directories. Feature apps under base_dir / "features" can be included or skipped explicitly.

Parameters:

Name Type Description Default
base_dir Path

Project root that contains apps, optional locale/, and optional features/ directories.

required
include_features bool

Whether locale directories inside the features subtree should be included in the result.

True

Returns:

Type Description
list[str]

A list of locale directory paths suitable for Django's

list[str]

LOCALE_PATHS setting. Paths are returned in discovery order and

list[str]

are de-duplicated across supported layouts.

Source code in src/codex_django/core/i18n/discovery.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
def discover_locale_paths(base_dir: Path, include_features: bool = True) -> list[str]:
    """Discover locale directories that should be added to ``LOCALE_PATHS``.

    The helper supports both a centralized ``locale/<domain>/<lang>``
    structure and per-app ``locale/`` directories. Feature apps under
    ``base_dir / "features"`` can be included or skipped explicitly.

    Args:
        base_dir: Project root that contains apps, optional ``locale/``, and
            optional ``features/`` directories.
        include_features: Whether locale directories inside the ``features``
            subtree should be included in the result.

    Returns:
        A list of locale directory paths suitable for Django's
        ``LOCALE_PATHS`` setting. Paths are returned in discovery order and
        are de-duplicated across supported layouts.
    """
    paths = []

    # 1. Check centralized locale directory (for modular domains)
    central_locale = base_dir / "locale"
    if central_locale.exists():
        # A modular domain is a subdirectory that contains <lang>/LC_MESSAGES
        # e.g., locale/cabinet/en/LC_MESSAGES/django.po
        for domain_dir in central_locale.iterdir():
            if domain_dir.is_dir():
                # Check if it has any language subdirectories
                for lang_dir in domain_dir.iterdir():
                    if lang_dir.is_dir() and (lang_dir / "LC_MESSAGES").exists():
                        paths.append(str(domain_dir))
                        break

    # 2. Backward compatibility / alternative structure:
    # Check top-level directories and features for their own locale/ folders
    for item in base_dir.iterdir():
        if item.is_dir() and (item / "locale").exists() and str(item / "locale") not in paths:
            paths.append(str(item / "locale"))

    features_dir = base_dir / "features"
    if include_features and features_dir.exists():
        for item in features_dir.iterdir():
            if item.is_dir() and (item / "locale").exists() and str(item / "locale") not in paths:
                paths.append(str(item / "locale"))

    return paths

Translation command

codex_django.core.management.commands.codex_makemessages

Domain-aware wrapper around Django's makemessages command.

The command scans a Codex project for template-bearing domains, creates centralized locale/<domain>/ directories, and runs Django's makemessages command separately for each domain plus a shared common bucket.

Examples:

Preview what would be generated without writing files::

python manage.py codex_makemessages --dry-run

Classes

Command

Bases: BaseCommand

Generate modular translation catalogs for Codex project layouts.

The command understands three common template layouts:

  • root-level templates/<domain>/
  • app-local <app>/templates/
  • feature-local features/<feature>/templates/
Source code in src/codex_django/core/management/commands/codex_makemessages.py
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
class Command(BaseCommand):
    """Generate modular translation catalogs for Codex project layouts.

    The command understands three common template layouts:

    - root-level ``templates/<domain>/``
    - app-local ``<app>/templates/``
    - feature-local ``features/<feature>/templates/``
    """

    help = (
        "Template-driven modular makemessages: creates locale files based on "
        "subfolders in the root templates directory."
    )

    def add_arguments(self, parser: ArgumentParser) -> None:
        """Register command-line arguments for the management command.

        Args:
            parser: Django/argparse parser instance for this command.
        """
        parser.add_argument(
            "--dry-run",
            action="store_true",
            help="Print what would be done without actual processing.",
        )

    def handle(self, *args: Any, **options: Any) -> None:
        """Discover domains and run ``makemessages`` for each one.

        Args:
            *args: Positional arguments forwarded by Django's command runner.
            **options: Parsed command-line options.
        """
        base_dir = Path(cast(str, settings.BASE_DIR))
        languages = [lang[0] for lang in settings.LANGUAGES]
        dry_run = cast(bool, options["dry_run"])

        if not languages:
            self.stdout.write(self.style.ERROR("No LANGUAGES defined in settings."))
            return

        self.stdout.write(self.style.SUCCESS(f"Languages: {', '.join(languages)}"))

        # 1. Discover domains
        domains: dict[str, str] = {}  # domain -> source_desc

        # From root templates subfolders (modular features style)
        templates_dir = base_dir / "templates"
        if templates_dir.exists():
            for item in templates_dir.iterdir():
                if item.is_dir():
                    domains[item.name] = "root templates"

        # From autonomous apps in root (like 'cabinet/')
        excludes = {"venv", "node_modules", "static", "media", "locale", "templates", ".git", ".idea", "__pycache__"}
        for item in base_dir.iterdir():
            if (
                item.is_dir()
                and item.name not in excludes
                and not item.name.startswith(".")
                and (item / "templates").exists()
            ):
                if item.name in domains:
                    domains[item.name] = "hybrid (root + app)"
                else:
                    domains[item.name] = "app templates"

        # From features (features/home/templates/...)
        features_dir = base_dir / "features"
        if features_dir.exists():
            for item in features_dir.iterdir():
                if item.is_dir() and (item / "templates").exists():
                    if item.name in domains:
                        domains[item.name] = "hybrid (root + feature)"
                    else:
                        domains[item.name] = "feature templates"

        manage_py = base_dir / "manage.py"
        all_domain_names = sorted(domains.keys())

        # 2. Process each domain
        for domain, source in sorted(domains.items()):
            self.stdout.write(self.style.MIGRATE_HEADING(f"==> Domain: [{domain}] ({source})"))

            # Centralized locale folder
            locale_dir = base_dir / "locale" / domain
            if not dry_run:
                locale_dir.mkdir(exist_ok=True, parents=True)

            cmd = self._build_cmd(manage_py, languages, domain, base_dir, all_domain_names)

            self.stdout.write(f"  Scanning for {domain}...")
            self._run_makemessages(cmd, base_dir, domain, dry_run)

        # 3. Process Global/Common
        self.stdout.write(self.style.MIGRATE_HEADING("==> Domain: [common]"))
        common_locale_dir = base_dir / "locale" / "common"
        if not dry_run:
            common_locale_dir.mkdir(exist_ok=True, parents=True)

        common_cmd = self._build_cmd(manage_py, languages, "common", base_dir, all_domain_names)
        self.stdout.write("  Scanning for common strings...")
        self._run_makemessages(common_cmd, base_dir, "common", dry_run)

    def _build_cmd(
        self, manage_py: Path, languages: list[str], domain: str, base_dir: Path, all_domains: list[str]
    ) -> list[str]:
        """Build the concrete ``makemessages`` subprocess command.

        Args:
            manage_py: Path to the project's ``manage.py`` script.
            languages: Configured Django language codes.
            domain: Domain currently being processed.
            base_dir: Project root directory.
            all_domains: Every discovered domain name used to construct
                ignore patterns.

        Returns:
            A subprocess argument list ready to pass to ``subprocess.run``.
        """
        cmd = [sys.executable, str(manage_py), "makemessages"]
        for lang in languages:
            cmd.extend(["-l", lang])

        ignores = ["venv/*", "node_modules/*", "static/*", "media/*"]

        if domain == "common":
            # Ignore all modular folders
            for d in all_domains:
                ignores.append(f"templates/{d}/*")
                ignores.append(f"{d}/*")
                ignores.append(f"features/{d}/*")
                # Also ignore the output locale directories to avoid scanning .po files
                ignores.append(f"locale/{d}/*")
        else:
            # Domain-specific scan:
            # We want to scan only templates/<domain> and either <domain>/ or features/<domain>/
            # The easiest way with makemessages -i is to ignore everything else.

            for d in all_domains:
                if d != domain:
                    ignores.append(f"templates/{d}/*")
                    ignores.append(f"{d}/*")
                    ignores.append(f"features/{d}/*")

            # Also ignore the common locale directory
            ignores.append("locale/common/*")
            # And ignore other domains' locale directories
            for d in all_domains:
                if d != domain:
                    ignores.append(f"locale/{d}/*")

            # Also ignore root templates for specific domains
            templates_dir = base_dir / "templates"
            if templates_dir.exists():
                for item in templates_dir.iterdir():
                    if item.is_file():
                        ignores.append(f"templates/{item.name}")

        for ignore in ignores:
            cmd.extend(["-i", ignore])

        return cmd

    def _run_makemessages(self, cmd: list[str], base_dir: Path, domain: str, dry_run: bool) -> None:
        """Execute or preview a single ``makemessages`` subprocess call.

        Args:
            cmd: Fully constructed subprocess command.
            base_dir: Project root used as the subprocess working directory.
            domain: Domain label used for status output.
            dry_run: When ``True``, print the command instead of executing it.
        """
        if dry_run:
            self.stdout.write(f"  [DRY-RUN] Executing: {' '.join(cmd)}")
            return

        try:
            result = subprocess.run(cmd, cwd=base_dir, capture_output=True, text=True)  # nosec
            if result.returncode == 0:
                self.stdout.write(self.style.SUCCESS(f"  [OK] Updated locale for {domain}"))
            else:
                self.stdout.write(self.style.ERROR(f"  [ERROR] {result.stderr}"))
        except Exception as e:
            self.stdout.write(self.style.ERROR(f"  [FATAL] {e}"))
Functions
add_arguments(parser)

Register command-line arguments for the management command.

Parameters:

Name Type Description Default
parser ArgumentParser

Django/argparse parser instance for this command.

required
Source code in src/codex_django/core/management/commands/codex_makemessages.py
39
40
41
42
43
44
45
46
47
48
49
def add_arguments(self, parser: ArgumentParser) -> None:
    """Register command-line arguments for the management command.

    Args:
        parser: Django/argparse parser instance for this command.
    """
    parser.add_argument(
        "--dry-run",
        action="store_true",
        help="Print what would be done without actual processing.",
    )
handle(*args, **options)

Discover domains and run makemessages for each one.

Parameters:

Name Type Description Default
*args Any

Positional arguments forwarded by Django's command runner.

()
**options Any

Parsed command-line options.

{}
Source code in src/codex_django/core/management/commands/codex_makemessages.py
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
def handle(self, *args: Any, **options: Any) -> None:
    """Discover domains and run ``makemessages`` for each one.

    Args:
        *args: Positional arguments forwarded by Django's command runner.
        **options: Parsed command-line options.
    """
    base_dir = Path(cast(str, settings.BASE_DIR))
    languages = [lang[0] for lang in settings.LANGUAGES]
    dry_run = cast(bool, options["dry_run"])

    if not languages:
        self.stdout.write(self.style.ERROR("No LANGUAGES defined in settings."))
        return

    self.stdout.write(self.style.SUCCESS(f"Languages: {', '.join(languages)}"))

    # 1. Discover domains
    domains: dict[str, str] = {}  # domain -> source_desc

    # From root templates subfolders (modular features style)
    templates_dir = base_dir / "templates"
    if templates_dir.exists():
        for item in templates_dir.iterdir():
            if item.is_dir():
                domains[item.name] = "root templates"

    # From autonomous apps in root (like 'cabinet/')
    excludes = {"venv", "node_modules", "static", "media", "locale", "templates", ".git", ".idea", "__pycache__"}
    for item in base_dir.iterdir():
        if (
            item.is_dir()
            and item.name not in excludes
            and not item.name.startswith(".")
            and (item / "templates").exists()
        ):
            if item.name in domains:
                domains[item.name] = "hybrid (root + app)"
            else:
                domains[item.name] = "app templates"

    # From features (features/home/templates/...)
    features_dir = base_dir / "features"
    if features_dir.exists():
        for item in features_dir.iterdir():
            if item.is_dir() and (item / "templates").exists():
                if item.name in domains:
                    domains[item.name] = "hybrid (root + feature)"
                else:
                    domains[item.name] = "feature templates"

    manage_py = base_dir / "manage.py"
    all_domain_names = sorted(domains.keys())

    # 2. Process each domain
    for domain, source in sorted(domains.items()):
        self.stdout.write(self.style.MIGRATE_HEADING(f"==> Domain: [{domain}] ({source})"))

        # Centralized locale folder
        locale_dir = base_dir / "locale" / domain
        if not dry_run:
            locale_dir.mkdir(exist_ok=True, parents=True)

        cmd = self._build_cmd(manage_py, languages, domain, base_dir, all_domain_names)

        self.stdout.write(f"  Scanning for {domain}...")
        self._run_makemessages(cmd, base_dir, domain, dry_run)

    # 3. Process Global/Common
    self.stdout.write(self.style.MIGRATE_HEADING("==> Domain: [common]"))
    common_locale_dir = base_dir / "locale" / "common"
    if not dry_run:
        common_locale_dir.mkdir(exist_ok=True, parents=True)

    common_cmd = self._build_cmd(manage_py, languages, "common", base_dir, all_domain_names)
    self.stdout.write("  Scanning for common strings...")
    self._run_makemessages(common_cmd, base_dir, "common", dry_run)

Template tags

codex_django.core.templatetags.codex_i18n

Template tags for multilingual navigation helpers.

The tags in this module are intentionally small wrappers around Django's i18n utilities so templates can stay declarative.

Functions

translate_url(context, lang_code)

Translate the current request path into another active language.

Parameters:

Name Type Description Default
context dict[str, Any]

Template context expected to contain the current request.

required
lang_code str

Target Django language code.

required

Returns:

Type Description
str

The translated URL path for the current request, or an empty string

str

when the request is unavailable.

Source code in src/codex_django/core/templatetags/codex_i18n.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@register.simple_tag(takes_context=True)
def translate_url(context: dict[str, Any], lang_code: str) -> str:
    """Translate the current request path into another active language.

    Args:
        context: Template context expected to contain the current ``request``.
        lang_code: Target Django language code.

    Returns:
        The translated URL path for the current request, or an empty string
        when the request is unavailable.
    """
    request = context.get("request")
    if not request:
        return ""

    path = request.path
    if not path:
        return ""

    return django_translate_url(path, lang_code)

Redis base manager

codex_django.core.redis.managers.base

Base Redis manager utilities adapted to Django settings.

This module is the bridge between codex-platform Redis helpers and the Django settings conventions used by codex-django projects.

Classes

BaseDjangoRedisManager

Bases: BaseRedisManager

Base Redis manager adapted for Django settings and project namespacing.

Source code in src/codex_django/core/redis/managers/base.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class BaseDjangoRedisManager(BaseRedisManager):  # type: ignore[misc]
    """Base Redis manager adapted for Django settings and project namespacing."""

    def __init__(self, prefix: str = "", **kwargs: Any) -> None:
        self.redis_url = getattr(settings, "REDIS_URL", "redis://localhost:6379/0")
        client: Any = Redis.from_url(self.redis_url, decode_responses=True)
        super().__init__(client)

        self.project_name = getattr(settings, "PROJECT_NAME", "")
        self.prefix = prefix

    def make_key(self, key: str) -> str:
        """Build a namespaced Redis key for the current Django project.

        Args:
            key: Logical key suffix for the concrete cache entry.

        Returns:
            A colon-delimited Redis key in the
            ``{PROJECT_NAME}:{prefix}:{key}`` format with empty segments
            omitted.
        """
        parts = [p for p in (self.project_name, self.prefix, key) if p]
        return ":".join(parts)

    def _is_disabled(self) -> bool:
        """Return ``True`` when Redis-backed behavior is disabled locally."""
        return bool(settings.DEBUG and not getattr(settings, "CODEX_REDIS_ENABLED", False))
Functions
make_key(key)

Build a namespaced Redis key for the current Django project.

Parameters:

Name Type Description Default
key str

Logical key suffix for the concrete cache entry.

required

Returns:

Type Description
str

A colon-delimited Redis key in the

str

{PROJECT_NAME}:{prefix}:{key} format with empty segments

str

omitted.

Source code in src/codex_django/core/redis/managers/base.py
25
26
27
28
29
30
31
32
33
34
35
36
37
def make_key(self, key: str) -> str:
    """Build a namespaced Redis key for the current Django project.

    Args:
        key: Logical key suffix for the concrete cache entry.

    Returns:
        A colon-delimited Redis key in the
        ``{PROJECT_NAME}:{prefix}:{key}`` format with empty segments
        omitted.
    """
    parts = [p for p in (self.project_name, self.prefix, key) if p]
    return ":".join(parts)

Redis settings manager

codex_django.core.redis.managers.settings

Redis-backed site settings storage helpers.

The settings manager caches the concrete project settings model in a single Redis hash and exposes a lightweight proxy for template access.

Classes

SettingsProxy

Expose dictionary-backed settings through attribute-style access in templates.

Parameters:

Name Type Description Default
data dict[str, Any]

Flat settings payload loaded from Redis or the database.

required
Source code in src/codex_django/core/redis/managers/settings.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class SettingsProxy:
    """Expose dictionary-backed settings through attribute-style access in templates.

    Args:
        data: Flat settings payload loaded from Redis or the database.
    """

    def __init__(self, data: dict[str, Any]) -> None:
        self.data = data

    def __getattr__(self, name: str) -> Any:
        """Return a setting value using attribute access semantics.

        Args:
            name: Setting key to resolve.

        Returns:
            The stored value, or an empty string when the key is missing.
        """
        return self.data.get(name, "")

    def __getitem__(self, name: str) -> Any:
        """Return a setting value using dictionary-style indexing.

        Args:
            name: Setting key to resolve.

        Returns:
            The stored value, or an empty string when the key is missing.
        """
        return self.data.get(name, "")
Functions
__getattr__(name)

Return a setting value using attribute access semantics.

Parameters:

Name Type Description Default
name str

Setting key to resolve.

required

Returns:

Type Description
Any

The stored value, or an empty string when the key is missing.

Source code in src/codex_django/core/redis/managers/settings.py
25
26
27
28
29
30
31
32
33
34
def __getattr__(self, name: str) -> Any:
    """Return a setting value using attribute access semantics.

    Args:
        name: Setting key to resolve.

    Returns:
        The stored value, or an empty string when the key is missing.
    """
    return self.data.get(name, "")
__getitem__(name)

Return a setting value using dictionary-style indexing.

Parameters:

Name Type Description Default
name str

Setting key to resolve.

required

Returns:

Type Description
Any

The stored value, or an empty string when the key is missing.

Source code in src/codex_django/core/redis/managers/settings.py
36
37
38
39
40
41
42
43
44
45
def __getitem__(self, name: str) -> Any:
    """Return a setting value using dictionary-style indexing.

    Args:
        name: Setting key to resolve.

    Returns:
        The stored value, or an empty string when the key is missing.
    """
    return self.data.get(name, "")

DjangoSiteSettingsManager

Bases: BaseDjangoRedisManager

Load and persist site settings in Redis with sync and async helpers.

Notes

The manager stores one project-wide settings payload in a single Redis hash identified by site_settings.

Source code in src/codex_django/core/redis/managers/settings.py
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
class DjangoSiteSettingsManager(BaseDjangoRedisManager):
    """Load and persist site settings in Redis with sync and async helpers.

    Notes:
        The manager stores one project-wide settings payload in a single
        Redis hash identified by ``site_settings``.
    """

    def __init__(self, **kwargs: Any) -> None:
        super().__init__(prefix="", **kwargs)
        self.CACHE_KEY = "site_settings"

    async def aload_cached(self, model_cls: type[models.Model]) -> dict[str, Any]:
        """Load cached site settings from Redis asynchronously.

        Args:
            model_cls: Django model class for the concrete site settings
                model. The argument is accepted for API symmetry with
                :meth:`load_cached`.

        Returns:
            Cached settings data, or an empty dictionary when caching is
            disabled or no Redis entry exists.
        """
        if self._is_disabled():
            return {}
        result = await self.hash.get_all(self.make_key(self.CACHE_KEY))
        return result or {}

    def load_cached(self, model_cls: type[models.Model]) -> dict[str, Any]:
        """Load cached settings or fall back to the first database row.

        Args:
            model_cls: Django model class that stores the singleton site
                settings payload and optionally implements ``to_dict()``.

        Returns:
            Cached or freshly loaded site settings data.
        """
        data = async_to_sync(self.aload_cached)(model_cls)
        if not data:
            instance = model_cls.objects.first()  # type: ignore[attr-defined]
            if instance and hasattr(instance, "to_dict"):
                data = instance.to_dict()
                self.save_instance(instance)
        return data

    async def asave_instance(self, instance: models.Model) -> None:
        """Persist a settings instance to Redis asynchronously.

        Args:
            instance: Concrete site settings model instance that implements
                ``to_dict()``.
        """
        if self._is_disabled() or not hasattr(instance, "to_dict"):
            return
        data = instance.to_dict()
        if data:
            await self.hash.set_fields(self.make_key(self.CACHE_KEY), data)

    def save_instance(self, instance: models.Model) -> None:
        """Synchronously persist a settings instance to Redis.

        Args:
            instance: Concrete site settings model instance that implements
                ``to_dict()``.
        """
        async_to_sync(self.asave_instance)(instance)
Functions
aload_cached(model_cls) async

Load cached site settings from Redis asynchronously.

Parameters:

Name Type Description Default
model_cls type[Model]

Django model class for the concrete site settings model. The argument is accepted for API symmetry with :meth:load_cached.

required

Returns:

Type Description
dict[str, Any]

Cached settings data, or an empty dictionary when caching is

dict[str, Any]

disabled or no Redis entry exists.

Source code in src/codex_django/core/redis/managers/settings.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
async def aload_cached(self, model_cls: type[models.Model]) -> dict[str, Any]:
    """Load cached site settings from Redis asynchronously.

    Args:
        model_cls: Django model class for the concrete site settings
            model. The argument is accepted for API symmetry with
            :meth:`load_cached`.

    Returns:
        Cached settings data, or an empty dictionary when caching is
        disabled or no Redis entry exists.
    """
    if self._is_disabled():
        return {}
    result = await self.hash.get_all(self.make_key(self.CACHE_KEY))
    return result or {}
load_cached(model_cls)

Load cached settings or fall back to the first database row.

Parameters:

Name Type Description Default
model_cls type[Model]

Django model class that stores the singleton site settings payload and optionally implements to_dict().

required

Returns:

Type Description
dict[str, Any]

Cached or freshly loaded site settings data.

Source code in src/codex_django/core/redis/managers/settings.py
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def load_cached(self, model_cls: type[models.Model]) -> dict[str, Any]:
    """Load cached settings or fall back to the first database row.

    Args:
        model_cls: Django model class that stores the singleton site
            settings payload and optionally implements ``to_dict()``.

    Returns:
        Cached or freshly loaded site settings data.
    """
    data = async_to_sync(self.aload_cached)(model_cls)
    if not data:
        instance = model_cls.objects.first()  # type: ignore[attr-defined]
        if instance and hasattr(instance, "to_dict"):
            data = instance.to_dict()
            self.save_instance(instance)
    return data
asave_instance(instance) async

Persist a settings instance to Redis asynchronously.

Parameters:

Name Type Description Default
instance Model

Concrete site settings model instance that implements to_dict().

required
Source code in src/codex_django/core/redis/managers/settings.py
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
async def asave_instance(self, instance: models.Model) -> None:
    """Persist a settings instance to Redis asynchronously.

    Args:
        instance: Concrete site settings model instance that implements
            ``to_dict()``.
    """
    if self._is_disabled() or not hasattr(instance, "to_dict"):
        return
    data = instance.to_dict()
    if data:
        await self.hash.set_fields(self.make_key(self.CACHE_KEY), data)
save_instance(instance)

Synchronously persist a settings instance to Redis.

Parameters:

Name Type Description Default
instance Model

Concrete site settings model instance that implements to_dict().

required
Source code in src/codex_django/core/redis/managers/settings.py
108
109
110
111
112
113
114
115
def save_instance(self, instance: models.Model) -> None:
    """Synchronously persist a settings instance to Redis.

    Args:
        instance: Concrete site settings model instance that implements
            ``to_dict()``.
    """
    async_to_sync(self.asave_instance)(instance)

Functions

get_site_settings_manager()

Return a site settings manager configured from Django settings.

Returns:

Type Description
DjangoSiteSettingsManager

A ready-to-use :class:DjangoSiteSettingsManager instance.

Source code in src/codex_django/core/redis/managers/settings.py
118
119
120
121
122
123
124
def get_site_settings_manager() -> DjangoSiteSettingsManager:
    """Return a site settings manager configured from Django settings.

    Returns:
        A ready-to-use :class:`DjangoSiteSettingsManager` instance.
    """
    return DjangoSiteSettingsManager()

Redis SEO manager

codex_django.core.redis.managers.seo

Redis manager for cached static-page SEO payloads.

Classes

SeoRedisManager

Bases: BaseDjangoRedisManager

Manage cached SEO payloads for static pages.

Notes

SEO data is stored as a Redis hash per page key so templates and selectors can read the payload without repeated database hits.

Source code in src/codex_django/core/redis/managers/seo.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
class SeoRedisManager(BaseDjangoRedisManager):
    """Manage cached SEO payloads for static pages.

    Notes:
        SEO data is stored as a Redis hash per page key so templates and
        selectors can read the payload without repeated database hits.
    """

    def __init__(self, **kwargs: Any) -> None:
        super().__init__(prefix="seo", **kwargs)

    async def aget_page(self, page_key: str) -> dict[str, str]:
        """Return cached SEO data for a page as a flat string mapping.

        Args:
            page_key: Logical page identifier used by the SEO model.

        Returns:
            A flat string mapping for the page, or an empty dictionary when
            no cache entry exists.
        """
        if self._is_disabled():
            return {}
        result = await self.hash.get_all(self.make_key(f"static_page:{page_key}"))
        return result or {}

    async def aset_page(self, page_key: str, mapping: dict[str, str], timeout: int | None = None) -> None:
        """Store SEO data for a page in a Redis hash.

        Args:
            page_key: Logical page identifier used by the SEO model.
            mapping: Flat string payload to cache.
            timeout: Optional TTL in seconds.
        """
        if self._is_disabled() or not mapping:
            return
        full_key = self.make_key(f"static_page:{page_key}")
        await self.hash.set_fields(full_key, mapping)
        if timeout is not None:
            await self.string.expire(full_key, timeout)

    async def ainvalidate_page(self, page_key: str) -> None:
        """Delete the cached SEO payload for a specific page.

        Args:
            page_key: Logical page identifier used by the SEO model.
        """
        if self._is_disabled():
            return
        await self.string.delete(self.make_key(f"static_page:{page_key}"))

    def get_page(self, page_key: str) -> dict[str, str]:
        """Synchronously return cached SEO data for a page.

        Args:
            page_key: Logical page identifier used by the SEO model.

        Returns:
            A flat string mapping for the page, or an empty dictionary when
            no cache entry exists.
        """
        return async_to_sync(self.aget_page)(page_key)

    def set_page(self, page_key: str, mapping: dict[str, str], timeout: int | None = None) -> None:
        """Synchronously cache SEO data for a page.

        Args:
            page_key: Logical page identifier used by the SEO model.
            mapping: Flat string payload to cache.
            timeout: Optional TTL in seconds.
        """
        async_to_sync(self.aset_page)(page_key, mapping, timeout)

    def invalidate_page(self, page_key: str) -> None:
        """Synchronously invalidate SEO cache for a page.

        Args:
            page_key: Logical page identifier used by the SEO model.
        """
        async_to_sync(self.ainvalidate_page)(page_key)
Functions
aget_page(page_key) async

Return cached SEO data for a page as a flat string mapping.

Parameters:

Name Type Description Default
page_key str

Logical page identifier used by the SEO model.

required

Returns:

Type Description
dict[str, str]

A flat string mapping for the page, or an empty dictionary when

dict[str, str]

no cache entry exists.

Source code in src/codex_django/core/redis/managers/seo.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
async def aget_page(self, page_key: str) -> dict[str, str]:
    """Return cached SEO data for a page as a flat string mapping.

    Args:
        page_key: Logical page identifier used by the SEO model.

    Returns:
        A flat string mapping for the page, or an empty dictionary when
        no cache entry exists.
    """
    if self._is_disabled():
        return {}
    result = await self.hash.get_all(self.make_key(f"static_page:{page_key}"))
    return result or {}
aset_page(page_key, mapping, timeout=None) async

Store SEO data for a page in a Redis hash.

Parameters:

Name Type Description Default
page_key str

Logical page identifier used by the SEO model.

required
mapping dict[str, str]

Flat string payload to cache.

required
timeout int | None

Optional TTL in seconds.

None
Source code in src/codex_django/core/redis/managers/seo.py
36
37
38
39
40
41
42
43
44
45
46
47
48
49
async def aset_page(self, page_key: str, mapping: dict[str, str], timeout: int | None = None) -> None:
    """Store SEO data for a page in a Redis hash.

    Args:
        page_key: Logical page identifier used by the SEO model.
        mapping: Flat string payload to cache.
        timeout: Optional TTL in seconds.
    """
    if self._is_disabled() or not mapping:
        return
    full_key = self.make_key(f"static_page:{page_key}")
    await self.hash.set_fields(full_key, mapping)
    if timeout is not None:
        await self.string.expire(full_key, timeout)
ainvalidate_page(page_key) async

Delete the cached SEO payload for a specific page.

Parameters:

Name Type Description Default
page_key str

Logical page identifier used by the SEO model.

required
Source code in src/codex_django/core/redis/managers/seo.py
51
52
53
54
55
56
57
58
59
async def ainvalidate_page(self, page_key: str) -> None:
    """Delete the cached SEO payload for a specific page.

    Args:
        page_key: Logical page identifier used by the SEO model.
    """
    if self._is_disabled():
        return
    await self.string.delete(self.make_key(f"static_page:{page_key}"))
get_page(page_key)

Synchronously return cached SEO data for a page.

Parameters:

Name Type Description Default
page_key str

Logical page identifier used by the SEO model.

required

Returns:

Type Description
dict[str, str]

A flat string mapping for the page, or an empty dictionary when

dict[str, str]

no cache entry exists.

Source code in src/codex_django/core/redis/managers/seo.py
61
62
63
64
65
66
67
68
69
70
71
def get_page(self, page_key: str) -> dict[str, str]:
    """Synchronously return cached SEO data for a page.

    Args:
        page_key: Logical page identifier used by the SEO model.

    Returns:
        A flat string mapping for the page, or an empty dictionary when
        no cache entry exists.
    """
    return async_to_sync(self.aget_page)(page_key)
set_page(page_key, mapping, timeout=None)

Synchronously cache SEO data for a page.

Parameters:

Name Type Description Default
page_key str

Logical page identifier used by the SEO model.

required
mapping dict[str, str]

Flat string payload to cache.

required
timeout int | None

Optional TTL in seconds.

None
Source code in src/codex_django/core/redis/managers/seo.py
73
74
75
76
77
78
79
80
81
def set_page(self, page_key: str, mapping: dict[str, str], timeout: int | None = None) -> None:
    """Synchronously cache SEO data for a page.

    Args:
        page_key: Logical page identifier used by the SEO model.
        mapping: Flat string payload to cache.
        timeout: Optional TTL in seconds.
    """
    async_to_sync(self.aset_page)(page_key, mapping, timeout)
invalidate_page(page_key)

Synchronously invalidate SEO cache for a page.

Parameters:

Name Type Description Default
page_key str

Logical page identifier used by the SEO model.

required
Source code in src/codex_django/core/redis/managers/seo.py
83
84
85
86
87
88
89
def invalidate_page(self, page_key: str) -> None:
    """Synchronously invalidate SEO cache for a page.

    Args:
        page_key: Logical page identifier used by the SEO model.
    """
    async_to_sync(self.ainvalidate_page)(page_key)

Functions

get_seo_redis_manager()

Return an SEO Redis manager configured from Django settings.

Returns:

Type Description
SeoRedisManager

A ready-to-use :class:SeoRedisManager instance.

Source code in src/codex_django/core/redis/managers/seo.py
92
93
94
95
96
97
98
def get_seo_redis_manager() -> SeoRedisManager:
    """Return an SEO Redis manager configured from Django settings.

    Returns:
        A ready-to-use :class:`SeoRedisManager` instance.
    """
    return SeoRedisManager()

Redis notifications manager

codex_django.core.redis.managers.notifications

Redis manager for lightweight notification-related cache entries.

Classes

NotificationsCacheManager

Bases: BaseDjangoRedisManager

Read and write notification-related cache entries in Redis.

Notes

This manager is intentionally generic and stores simple key/value entries for notification subjects, templates, or other derived metadata that benefits from Redis-backed reuse.

Source code in src/codex_django/core/redis/managers/notifications.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
class NotificationsCacheManager(BaseDjangoRedisManager):
    """Read and write notification-related cache entries in Redis.

    Notes:
        This manager is intentionally generic and stores simple key/value
        entries for notification subjects, templates, or other derived
        metadata that benefits from Redis-backed reuse.
    """

    def __init__(self, **kwargs: Any) -> None:
        super().__init__(prefix="notif", **kwargs)

    async def aget(self, key: str) -> str | None:
        """Return a cached notification value by key, if present.

        Args:
            key: Logical cache key inside the notification namespace.

        Returns:
            The cached string value or ``None`` when the entry is missing.
        """
        if self._is_disabled():
            return None
        return await self.string.get(self.make_key(key))  # type: ignore[no-any-return]

    async def aset(self, key: str, value: Any, timeout: int | None = None) -> None:
        """Store a notification cache value with an optional TTL.

        Args:
            key: Logical cache key inside the notification namespace.
            value: Value to store in Redis.
            timeout: Optional TTL in seconds.
        """
        if self._is_disabled():
            return
        await self.string.set(self.make_key(key), value, ttl=timeout)

    def get(self, key: str) -> str | None:
        """Synchronously return a cached notification value.

        Args:
            key: Logical cache key inside the notification namespace.

        Returns:
            The cached string value or ``None`` when the entry is missing.
        """
        return async_to_sync(self.aget)(key)

    def set(self, key: str, value: Any, timeout: int | None = None) -> None:
        """Synchronously store a notification cache value.

        Args:
            key: Logical cache key inside the notification namespace.
            value: Value to store in Redis.
            timeout: Optional TTL in seconds.
        """
        async_to_sync(self.aset)(key, value, timeout)
Functions
aget(key) async

Return a cached notification value by key, if present.

Parameters:

Name Type Description Default
key str

Logical cache key inside the notification namespace.

required

Returns:

Type Description
str | None

The cached string value or None when the entry is missing.

Source code in src/codex_django/core/redis/managers/notifications.py
22
23
24
25
26
27
28
29
30
31
32
33
async def aget(self, key: str) -> str | None:
    """Return a cached notification value by key, if present.

    Args:
        key: Logical cache key inside the notification namespace.

    Returns:
        The cached string value or ``None`` when the entry is missing.
    """
    if self._is_disabled():
        return None
    return await self.string.get(self.make_key(key))  # type: ignore[no-any-return]
aset(key, value, timeout=None) async

Store a notification cache value with an optional TTL.

Parameters:

Name Type Description Default
key str

Logical cache key inside the notification namespace.

required
value Any

Value to store in Redis.

required
timeout int | None

Optional TTL in seconds.

None
Source code in src/codex_django/core/redis/managers/notifications.py
35
36
37
38
39
40
41
42
43
44
45
async def aset(self, key: str, value: Any, timeout: int | None = None) -> None:
    """Store a notification cache value with an optional TTL.

    Args:
        key: Logical cache key inside the notification namespace.
        value: Value to store in Redis.
        timeout: Optional TTL in seconds.
    """
    if self._is_disabled():
        return
    await self.string.set(self.make_key(key), value, ttl=timeout)
get(key)

Synchronously return a cached notification value.

Parameters:

Name Type Description Default
key str

Logical cache key inside the notification namespace.

required

Returns:

Type Description
str | None

The cached string value or None when the entry is missing.

Source code in src/codex_django/core/redis/managers/notifications.py
47
48
49
50
51
52
53
54
55
56
def get(self, key: str) -> str | None:
    """Synchronously return a cached notification value.

    Args:
        key: Logical cache key inside the notification namespace.

    Returns:
        The cached string value or ``None`` when the entry is missing.
    """
    return async_to_sync(self.aget)(key)
set(key, value, timeout=None)

Synchronously store a notification cache value.

Parameters:

Name Type Description Default
key str

Logical cache key inside the notification namespace.

required
value Any

Value to store in Redis.

required
timeout int | None

Optional TTL in seconds.

None
Source code in src/codex_django/core/redis/managers/notifications.py
58
59
60
61
62
63
64
65
66
def set(self, key: str, value: Any, timeout: int | None = None) -> None:
    """Synchronously store a notification cache value.

    Args:
        key: Logical cache key inside the notification namespace.
        value: Value to store in Redis.
        timeout: Optional TTL in seconds.
    """
    async_to_sync(self.aset)(key, value, timeout)

Functions

get_notifications_cache_manager()

Return a notifications cache manager configured from Django settings.

Returns:

Type Description
NotificationsCacheManager

A ready-to-use :class:NotificationsCacheManager instance.

Source code in src/codex_django/core/redis/managers/notifications.py
69
70
71
72
73
74
75
def get_notifications_cache_manager() -> NotificationsCacheManager:
    """Return a notifications cache manager configured from Django settings.

    Returns:
        A ready-to-use :class:`NotificationsCacheManager` instance.
    """
    return NotificationsCacheManager()

Redis booking manager

codex_django.core.redis.managers.booking

codex_django.core.redis.managers.booking

Redis cache manager for booking busy slot intervals.

Caches busy intervals (not free slots) per master per date. ChainFinder computes free slots on the fly — cheap math when busy data is in memory.

Invalidation is surgical: master_id + date.

Classes

BookingCacheManager

Bases: BaseDjangoRedisManager

Cache for busy slot intervals.

Notes

Key format: booking:busy:{master_id}:{date} Value: JSON list of [[start_iso, end_iso], ...]

Source code in src/codex_django/core/redis/managers/booking.py
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
class BookingCacheManager(BaseDjangoRedisManager):
    """Cache for busy slot intervals.

    Notes:
        Key format: ``booking:busy:{master_id}:{date}``
        Value: JSON list of ``[[start_iso, end_iso], ...]``
    """

    def __init__(self, **kwargs: Any) -> None:
        super().__init__(prefix="booking", **kwargs)

    # ------------------------------------------------------------------
    # Async API
    # ------------------------------------------------------------------

    async def aget_busy(self, master_id: str, date_str: str) -> list[list[str]] | None:
        """Return cached busy intervals for a master on a date.

        Args:
            master_id: Resource identifier used in booking availability.
            date_str: ISO-like date string for the cached day bucket.

        Returns:
            Cached intervals or ``None`` when the cache entry does not exist.
        """
        if self._is_disabled():
            return None
        raw = await self.string.get(self.make_key(f"busy:{master_id}:{date_str}"))
        if raw is None:
            return None
        return json.loads(raw)  # type: ignore[no-any-return]

    async def aset_busy(
        self,
        master_id: str,
        date_str: str,
        intervals: list[list[str]],
        timeout: int = 300,
    ) -> None:
        """Store busy intervals for one resource-day pair in cache.

        Args:
            master_id: Resource identifier used in booking availability.
            date_str: ISO-like date string for the cached day bucket.
            intervals: Busy intervals encoded as ``[[start_iso, end_iso], ...]``.
            timeout: Cache lifetime in seconds.
        """
        if self._is_disabled():
            return
        key = self.make_key(f"busy:{master_id}:{date_str}")
        await self.string.set(key, json.dumps(intervals), ttl=timeout)

    async def ainvalidate_master_date(self, master_id: str, date_str: str) -> None:
        """Delete cached busy intervals for a specific resource-day pair."""
        if self._is_disabled():
            return
        await self.string.delete(self.make_key(f"busy:{master_id}:{date_str}"))

    # ------------------------------------------------------------------
    # Sync wrappers
    # ------------------------------------------------------------------

    def get_busy(self, master_id: str, date_str: str) -> list[list[str]] | None:
        """Synchronously return cached busy intervals for a resource-day pair.

        Args:
            master_id: Resource identifier used in booking availability.
            date_str: ISO-like date string for the cached day bucket.

        Returns:
            Cached intervals or ``None`` when the cache entry does not exist.
        """
        return async_to_sync(self.aget_busy)(master_id, date_str)

    def set_busy(
        self,
        master_id: str,
        date_str: str,
        intervals: list[list[str]],
        timeout: int = 300,
    ) -> None:
        """Synchronously store busy intervals for one resource-day pair.

        Args:
            master_id: Resource identifier used in booking availability.
            date_str: ISO-like date string for the cached day bucket.
            intervals: Busy intervals encoded as ``[[start_iso, end_iso], ...]``.
            timeout: Cache lifetime in seconds.
        """
        async_to_sync(self.aset_busy)(master_id, date_str, intervals, timeout)

    def invalidate_master_date(self, master_id: str, date_str: str) -> None:
        """Synchronously invalidate busy-slot cache for one resource-day pair.

        Args:
            master_id: Resource identifier used in booking availability.
            date_str: ISO-like date string for the cached day bucket.
        """
        async_to_sync(self.ainvalidate_master_date)(master_id, date_str)
Functions
aget_busy(master_id, date_str) async

Return cached busy intervals for a master on a date.

Parameters:

Name Type Description Default
master_id str

Resource identifier used in booking availability.

required
date_str str

ISO-like date string for the cached day bucket.

required

Returns:

Type Description
list[list[str]] | None

Cached intervals or None when the cache entry does not exist.

Source code in src/codex_django/core/redis/managers/booking.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
async def aget_busy(self, master_id: str, date_str: str) -> list[list[str]] | None:
    """Return cached busy intervals for a master on a date.

    Args:
        master_id: Resource identifier used in booking availability.
        date_str: ISO-like date string for the cached day bucket.

    Returns:
        Cached intervals or ``None`` when the cache entry does not exist.
    """
    if self._is_disabled():
        return None
    raw = await self.string.get(self.make_key(f"busy:{master_id}:{date_str}"))
    if raw is None:
        return None
    return json.loads(raw)  # type: ignore[no-any-return]
aset_busy(master_id, date_str, intervals, timeout=300) async

Store busy intervals for one resource-day pair in cache.

Parameters:

Name Type Description Default
master_id str

Resource identifier used in booking availability.

required
date_str str

ISO-like date string for the cached day bucket.

required
intervals list[list[str]]

Busy intervals encoded as [[start_iso, end_iso], ...].

required
timeout int

Cache lifetime in seconds.

300
Source code in src/codex_django/core/redis/managers/booking.py
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
async def aset_busy(
    self,
    master_id: str,
    date_str: str,
    intervals: list[list[str]],
    timeout: int = 300,
) -> None:
    """Store busy intervals for one resource-day pair in cache.

    Args:
        master_id: Resource identifier used in booking availability.
        date_str: ISO-like date string for the cached day bucket.
        intervals: Busy intervals encoded as ``[[start_iso, end_iso], ...]``.
        timeout: Cache lifetime in seconds.
    """
    if self._is_disabled():
        return
    key = self.make_key(f"busy:{master_id}:{date_str}")
    await self.string.set(key, json.dumps(intervals), ttl=timeout)
ainvalidate_master_date(master_id, date_str) async

Delete cached busy intervals for a specific resource-day pair.

Source code in src/codex_django/core/redis/managers/booking.py
72
73
74
75
76
async def ainvalidate_master_date(self, master_id: str, date_str: str) -> None:
    """Delete cached busy intervals for a specific resource-day pair."""
    if self._is_disabled():
        return
    await self.string.delete(self.make_key(f"busy:{master_id}:{date_str}"))
get_busy(master_id, date_str)

Synchronously return cached busy intervals for a resource-day pair.

Parameters:

Name Type Description Default
master_id str

Resource identifier used in booking availability.

required
date_str str

ISO-like date string for the cached day bucket.

required

Returns:

Type Description
list[list[str]] | None

Cached intervals or None when the cache entry does not exist.

Source code in src/codex_django/core/redis/managers/booking.py
82
83
84
85
86
87
88
89
90
91
92
def get_busy(self, master_id: str, date_str: str) -> list[list[str]] | None:
    """Synchronously return cached busy intervals for a resource-day pair.

    Args:
        master_id: Resource identifier used in booking availability.
        date_str: ISO-like date string for the cached day bucket.

    Returns:
        Cached intervals or ``None`` when the cache entry does not exist.
    """
    return async_to_sync(self.aget_busy)(master_id, date_str)
set_busy(master_id, date_str, intervals, timeout=300)

Synchronously store busy intervals for one resource-day pair.

Parameters:

Name Type Description Default
master_id str

Resource identifier used in booking availability.

required
date_str str

ISO-like date string for the cached day bucket.

required
intervals list[list[str]]

Busy intervals encoded as [[start_iso, end_iso], ...].

required
timeout int

Cache lifetime in seconds.

300
Source code in src/codex_django/core/redis/managers/booking.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
def set_busy(
    self,
    master_id: str,
    date_str: str,
    intervals: list[list[str]],
    timeout: int = 300,
) -> None:
    """Synchronously store busy intervals for one resource-day pair.

    Args:
        master_id: Resource identifier used in booking availability.
        date_str: ISO-like date string for the cached day bucket.
        intervals: Busy intervals encoded as ``[[start_iso, end_iso], ...]``.
        timeout: Cache lifetime in seconds.
    """
    async_to_sync(self.aset_busy)(master_id, date_str, intervals, timeout)
invalidate_master_date(master_id, date_str)

Synchronously invalidate busy-slot cache for one resource-day pair.

Parameters:

Name Type Description Default
master_id str

Resource identifier used in booking availability.

required
date_str str

ISO-like date string for the cached day bucket.

required
Source code in src/codex_django/core/redis/managers/booking.py
111
112
113
114
115
116
117
118
def invalidate_master_date(self, master_id: str, date_str: str) -> None:
    """Synchronously invalidate busy-slot cache for one resource-day pair.

    Args:
        master_id: Resource identifier used in booking availability.
        date_str: ISO-like date string for the cached day bucket.
    """
    async_to_sync(self.ainvalidate_master_date)(master_id, date_str)

Functions

get_booking_cache_manager()

Return a booking cache manager configured from Django settings.

Returns:

Type Description
BookingCacheManager

A ready-to-use :class:BookingCacheManager instance.

Source code in src/codex_django/core/redis/managers/booking.py
121
122
123
124
125
126
127
def get_booking_cache_manager() -> BookingCacheManager:
    """Return a booking cache manager configured from Django settings.

    Returns:
        A ready-to-use :class:`BookingCacheManager` instance.
    """
    return BookingCacheManager()