Skip to content

fix: filter expired and future posts from public views - #85

Open
dnlzrgz wants to merge 17 commits into
django-cms:mainfrom
dnlzrgz:fix_filter_by_date_published_end
Open

dnlzrgz wants to merge 17 commits into
django-cms:mainfrom
dnlzrgz:fix_filter_by_date_published_end

Conversation

@dnlzrgz

@dnlzrgz dnlzrgz commented Mar 18, 2026 •

Copy link
Copy Markdown

Description

This PR fixes the issue about expired or future posts being visible to public users. I also updated how test data is generated because the previous "random" dates were causing inconsistently fails after the fix was applied.

Since both issues are related, I decided to solve the date fallback problem when sorting and I realized that I had introduced a subtle bug that caused posts without a post__date_published to be filtered out. Both the filtering and the ordering logic should be correct now.

Related resources

Checklist

  • I have opened this pull request against master
  • I have added or modified the tests when changing logic
  • I have followed the conventional commits guidelines to add meaningful information into the changelog
  • I have read the contribution guidelines and I have joined #pr-review on
    Discord to find a “pr review buddy” who is going to review my pull request.

Summary by Sourcery

Ensure public post listings only include currently published stories and add coverage for publication window filtering.

Bug Fixes:

  • Hide posts that are scheduled for the future or already expired from public post list views.

Tests:

  • Add a view test verifying that the latest posts list only includes posts within the publication date window.
  • Adjust post factories to use deterministic, non-expiring default publication dates to avoid flaky tests.

Summary by Sourcery

Ensure public post views expose only currently available posts while retaining correct fallback ordering and compatible admin fields.

Bug Fixes:

  • Filter public post listings to show only posts currently within their publication window.
  • Preserve posts without a publication date by falling back to their creation date for ordering.

Enhancements:

  • Hide publication date fields in the admin when the timed publishing extension is installed.

Tests:

  • Add coverage for publication-window filtering, ordering fallback, and conditional admin date fields.
  • Use deterministic, non-expiring default publication dates in test factories.

@sourcery-ai

sourcery-ai Bot commented Mar 18, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Ensures public post listings only show currently published posts by filtering on publication date range, and stabilizes test data by using deterministic publication dates and adding coverage for the new filtering behavior.

Sequence diagram for public post listing with publication date filtering

sequenceDiagram
  actor PublicUser
  participant Browser
  participant PostListView
  participant PostContent_manager
  participant Database

  PublicUser->>Browser: Request_stories_listing
  Browser->>PostListView: HTTP_GET /stories/
  PostListView->>PostListView: get_queryset()
  PostListView->>PostContent_manager: model.objects.all()
  PostContent_manager->>Database: SELECT * FROM postcontent
  Database-->>PostContent_manager: All_PostContent_rows
  PostListView->>PostContent_manager: filter(date_published_lte_now, date_published_end_null_or_gt_now)
  PostContent_manager->>Database: SELECT ... WHERE post.date_published <= now AND (post.date_published_end IS NULL OR post.date_published_end > now)
  Database-->>PostContent_manager: Currently_published_PostContent
  PostListView->>PostContent_manager: filter(language, namespace)
  PostContent_manager->>Database: SELECT ... WHERE language = request_language AND app_config.namespace = view_namespace
  Database-->>PostContent_manager: Localized_namespace_posts
  PostListView-->>Browser: Rendered_list_template
  Browser-->>PublicUser: Page_with_only_currently_published_posts
Loading

Updated class diagram for post content listing and publication dates

classDiagram
  class Post {
    DateTimeField date_published
    DateTimeField date_published_end
  }

  class PostAppConfig {
    CharField namespace
  }

  class PostContent {
    CharField language
    ForeignKey post
    ForeignKey app_config
  }

  class PostContentManager {
    QuerySet all()
  }

  class PostContentAdminManager {
    QuerySet latest_content()
  }

  class PostListView {
    get_queryset()
  }

  PostContent "*" --> "1" Post
  Post "1" --> "1" PostAppConfig
  PostContent "*" --> "1" PostAppConfig

  PostContent --> PostContentManager : objects
  PostContent --> PostContentAdminManager : admin_manager

  PostListView --> PostContent : model

  PostListView ..> Post : filters_by_date_published
  PostListView ..> Post : filters_by_date_published_end
  PostListView ..> PostContent : filters_by_language
  PostListView ..> PostAppConfig : filters_by_namespace
Loading

File-Level Changes

Change Details Files
Apply publication date window filtering for public post listings while keeping admin/toolbar behavior unchanged.
  • Introduce current_time in PostListView.get_queryset to represent 'now' when evaluating visibility.
  • For non-toolbar (public) requests, restrict queryset to posts with date_published <= now and (no end date or date_published_end > now).
  • Keep admin_manager.latest_content() path for edit/preview toolbar requests while still scoping by language, namespace, and site.
djangocms_stories/views.py
Add regression test to verify expired posts are hidden and valid posts are shown in the latest posts public view.
  • Create test_post_list_view_filters_by_publication_date_end using PostContentFactory to build visible and expired posts around current_time.
  • Publish created posts with publish_if_necessary and assert only the visible post title appears in the response HTML for the public client.
tests/test_views.py
Stabilize test factories by making publication dates deterministic and non-expiring by default.
  • Replace random date_published Faker with LazyFunction that sets date_published to now minus one day.
  • Set date_published_end to None by default so factory-generated posts never expire unless explicitly overridden.
tests/factories.py

Possibly linked issues


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

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • In views.py you remove the from cms.utils import get_current_site import but still call site_compatibility_decorator(get_current_site), which will raise a NameError; either keep the import or rename the wrapped variable to avoid shadowing and ensure the correct function is passed in.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `views.py` you remove the `from cms.utils import get_current_site` import but still call `site_compatibility_decorator(get_current_site)`, which will raise a `NameError`; either keep the import or rename the wrapped variable to avoid shadowing and ensure the correct function is passed in.

## Individual Comments

### Comment 1
<location path="djangocms_stories/views.py" line_range="125-127" />
<code_context>
             queryset = self.model.admin_manager.latest_content()
         else:
             queryset = self.model.objects.all()
+            queryset = queryset.filter(
+                Q(post__date_published__lte=current_time),
+                Q(post__date_published_end__isnull=True) | Q(post__date_published_end__gt=current_time),
+            )
+
</code_context>
<issue_to_address>
**issue:** Consider whether items with date_published_end exactly equal to `current_time` should be treated as expired.

Because the filter uses `post__date_published_end__gt=current_time`, posts ending exactly at `current_time` will not be returned. If the end timestamp is meant to be inclusive, switch to `gte`; if it’s meant to be exclusive, consider documenting that assumption to prevent future off‑by‑one timing issues.
</issue_to_address>

### Comment 2
<location path="tests/test_views.py" line_range="103-112" />
<code_context>
+    assert expired_post.title not in content
+
+
 @pytest.mark.django_db
 def test_post_archive_view(admin_client, admin_user, default_config):
     """
</code_context>
<issue_to_address>
**suggestion (testing):** Add a complementary test to verify that future-dated posts are excluded from the public list view

You can mirror this test by creating a post with `date_published = now() + timedelta(days=1)` and a valid `date_published_end`, marking it as published, then asserting its title is absent from the public `client` response (e.g. in `test_post_list_view_filters_out_future_posts`). That will ensure future-dated content can’t accidentally appear in public views.
</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 thread djangocms_stories/views.py Outdated
Comment thread tests/test_views.py
@codecov

codecov Bot commented Mar 24, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.44444% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 89.41%. Comparing base (f628aa0) to head (6046e20).

Files with missing lines Patch % Lines
djangocms_stories/views.py 87.50% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #85      +/-   ##
==========================================
- Coverage   90.94%   89.41%   -1.53%     
==========================================
  Files          24       24              
  Lines        2309     2325      +16     
  Branches      276      281       +5     
==========================================
- Hits         2100     2079      -21     
- Misses        126      160      +34     
- Partials       83       86       +3     

☔ View full report in Codecov by Harness.
📢 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 commented Mar 25, 2026

Copy link
Copy Markdown
Member

@dnlzrgz Nice PR! Thank you!

I appreciate the use of django ORM features to fix the visibility and sorting issues.

There's a package called djangocms-timed-publishing which is an extension to djangocms-verisoning and adds precisely the feature of making published content only visible after and/or before a certain date.

We might need to think on how to align those features. Would you mind having a look at djangocms-timed-publishing ?

@dnlzrgz

dnlzrgz commented Mar 25, 2026 •

Copy link
Copy Markdown
Author

Thanks for the comment @fsbraun!

I took a look at the djangocms-timed-publishing and you're right. It will definitely conflict with my current solution and make the UI "weirder" than it should.

I feel like djangocms-stories should have a way to handle this without the need of another dependency. But I can also see how having a plugin providing this functionality can make sense.

My proposal would be then to ensure that djangocms-stories works as intended with its own dates so it isn't "broken" by default and things work as the user expects and then try to update the get_queryset to check if the plugin is installed and try to avoid the conflict there.

Let me know if this approach sounds good to you!

@fsbraun

fsbraun commented Mar 27, 2026

Copy link
Copy Markdown
Member

OK, let's avoid the "being broken by default". For now, can we check for "djangocms_timed_publishing" being in INSTALLED_APPS. If so, omit the publication dates from the forms - so the user does not have two options?

@dnlzrgz

dnlzrgz commented Mar 30, 2026

Copy link
Copy Markdown
Author

To not make things messier these are the changes I will make if everything looks good to you:

  1. In the BaseConfigListViewMixin I will update the get_queryset as follows:
#...
        if hasattr(self.request, "toolbar") and (
            self.request.toolbar.edit_mode_active or self.request.toolbar.preview_mode_active
        ):
            queryset = self.model.admin_manager.latest_content()
        else:
            queryset = self.model.objects.all()
            if not apps.is_installed("djangocms_timed_publishing"):
                queryset = queryset.filter(
                    Q(post__date_published__lte=current_time) | Q(post__date_published__isnull=True),
                    Q(post__date_published_end__isnull=True) | Q(post__date_published_end__gt=current_time),
                )
#...
  1. Update the get_fieldsets of the PostAdmin class in a similar way.

This PR feels a bit "spaghetti" to me but I haven't been able to come up with a better solution.

@dnlzrgz

dnlzrgz commented Apr 22, 2026 •

Copy link
Copy Markdown
Author

There is a failling test in test_admin.py (I don't know if it was me who broke something but it shouldn't be the case) and I also still need to update the documentation to point to the potential conflict between plugins.

I got a bit lost in some parts but I think everything should be good now.

Edit: I just merged the 'main' branch so maybe the failing test I mentioned is fixed now.

@fsbraun

fsbraun commented Apr 25, 2026

Copy link
Copy Markdown
Member

@dnlzrgz Can you take another look at the failing tests? It seems to be influenced by the django CMS version.

@dnlzrgz

dnlzrgz commented May 7, 2026

Copy link
Copy Markdown
Author

Hey @fsbraun, thanks for the patience. I have been trying to understand what is failing and why but so far I haven't been able to replicate the CI results on my machine (even after purging the environment, setting a new one, and doing a fresh install). All tests pass on my end.

I checked if maybe djangocms_timed_publishing was accidentally being installed, if the test util publish_if_necessary was modifying things in a way that it shouldn't, reviewing the factories, re-reviewing the queryset filtering logic, checking that the django CMS version was correct and so on...

Any guidance on what to look at next would be greatly appreciated!

This branch has not been deployed

No deployments
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