Skip to content

Conversation

@fsbraun
Copy link
Member

@fsbraun fsbraun commented Dec 4, 2025

Summary by Sourcery

Optimize story content retrieval when using draft content, especially with djangocms-versioning prefetch caches.

Enhancements:

  • Leverage djangocms-versioning's prefetched current contents when available to avoid redundant database queries.
  • Simplify story content queryset construction by removing unnecessary prefetches and only applying language filtering.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Dec 4, 2025

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Optimizes how story content is retrieved for draft views by leveraging djangocms-versioning’s pre-fetched current contents and removing unnecessary prefetches, thereby reducing redundant database queries for versioned, language-specific content.

Sequence diagram for optimized draft content retrieval in get_content

sequenceDiagram
    actor View
    participant Story as StoryInstance
    participant VersioningCache as VersioningPrefetchCache
    participant PostContentQS as PostContentQueryset

    View->>Story: get_content(language, show_draft_content=True)
    alt show_draft_content is True
        opt versioning prefetch cache available
            Story->>VersioningCache: check attribute _current_contents
            alt _current_contents exists
                Story-->>View: return _current_contents[0]
            else _current_contents missing
                Story->>PostContentQS: postcontent_set(manager=admin_manager)
                PostContentQS->>PostContentQS: current_content()
                PostContentQS->>PostContentQS: filter(language)
                PostContentQS-->>Story: first()
                Story-->>View: return cached content
            end
        end
    else show_draft_content is False
        Story->>PostContentQS: postcontent_set
        PostContentQS->>PostContentQS: filter(language)
        PostContentQS-->>Story: first()
        Story-->>View: return cached content
    end
Loading

Updated class diagram for Story get_content optimization

classDiagram
    class Story {
        _content_cache
        _current_contents
        get_content(language, show_draft_content)
    }

    class PostContentQueryset {
        current_content()
        filter(language)
        first()
    }

    Story "1" --> "*" PostContentQueryset : postcontent_set

    class VersioningPrefetchCache {
        _current_contents
    }

    Story --> VersioningPrefetchCache : uses when show_draft_content
Loading

File-Level Changes

Change Details Files
Optimize get_content to use pre-fetched versioned content and reduce query overhead.
  • When show_draft_content is True, first check for the djangocms-versioning _current_contents cache and return the first item if available, avoiding additional queries.
  • Remove prefetch_related calls for placeholders and post categories from the queryset in get_content to reduce JOIN overhead.
  • Apply only a language filter on the queryset and keep the result cached in _content_cache as before.
djangocms_stories/models.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • When using the _current_contents cache, you’re bypassing both the existing _content_cache mechanism and prefetch_related, which changes behavior compared to the non-versioned path; consider still caching the result under _content_cache[key] and validating that skipping prefetch_related doesn’t introduce N+1 queries for placeholders/categories.
  • Accessing self._current_contents[0] assumes the prefetch list is non-empty; it would be safer to handle the empty case explicitly (e.g., returning None or falling back to the queryset) to avoid an IndexError when no current content exists for the requested language.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- When using the `_current_contents` cache, you’re bypassing both the existing `_content_cache` mechanism and `prefetch_related`, which changes behavior compared to the non-versioned path; consider still caching the result under `_content_cache[key]` and validating that skipping `prefetch_related` doesn’t introduce N+1 queries for placeholders/categories.
- Accessing `self._current_contents[0]` assumes the prefetch list is non-empty; it would be safer to handle the empty case explicitly (e.g., returning `None` or falling back to the queryset) to avoid an IndexError when no current content exists for the requested language.

## Individual Comments

### Comment 1
<location> `djangocms_stories/models.py:371-372` </location>
<code_context>
             if show_draft_content:
+                # Check for djangcms-versioning's prefetch cache - it's already language-filtered
+                # If present we avoid unnecessary DB queries
+                if hasattr(self, "_current_contents"):
+                    return self._current_contents[0]
                 qs = self.postcontent_set(manager="admin_manager").current_content()
             else:
</code_context>

<issue_to_address>
**issue:** Accessing `_current_contents[0]` can raise on an empty collection and bypasses the `_content_cache` path.

If `_current_contents` can ever be empty (e.g., no current content for a version), indexing with `[0]` will raise `IndexError`, whereas the previous `qs.first()` would have returned `None`. Consider either guarding against an empty list or replicating the `first()`-style behavior when empty. Also, this early return prevents `_content_cache` from being populated; if you still rely on that cache, assign the object to it before returning to avoid extra queries on subsequent calls.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 371 to 372
if hasattr(self, "_current_contents"):
return self._current_contents[0]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: Accessing _current_contents[0] can raise on an empty collection and bypasses the _content_cache path.

If _current_contents can ever be empty (e.g., no current content for a version), indexing with [0] will raise IndexError, whereas the previous qs.first() would have returned None. Consider either guarding against an empty list or replicating the first()-style behavior when empty. Also, this early return prevents _content_cache from being populated; if you still rely on that cache, assign the object to it before returning to avoid extra queries on subsequent calls.

@codecov
Copy link

codecov bot commented Dec 4, 2025

Codecov Report

❌ Patch coverage is 33.33333% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.84%. Comparing base (015dc98) to head (7cf3f5c).

Files with missing lines Patch % Lines
djangocms_stories/models.py 33.33% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #65      +/-   ##
==========================================
- Coverage   89.93%   89.84%   -0.09%     
==========================================
  Files          23       23              
  Lines        2106     2108       +2     
  Branches      239      240       +1     
==========================================
  Hits         1894     1894              
- Misses        130      131       +1     
- Partials       82       83       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@fsbraun fsbraun changed the title feat: Performance optimization for versioned changeless feat: Performance optimization for versioned changelist Dec 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants