Skip to content

feat: add news/stories pipeline which create news from article#2844

Merged
ahtesham-quraish merged 5 commits intomainfrom
ahtesham/listing-page
Jan 13, 2026
Merged

feat: add news/stories pipeline which create news from article#2844
ahtesham-quraish merged 5 commits intomainfrom
ahtesham/listing-page

Conversation

@ahtesham-quraish
Copy link
Contributor

@ahtesham-quraish ahtesham-quraish commented Jan 7, 2026

What are the relevant tickets?

https://github.com/mitodl/hq/issues/9784

Description (What does it do?)

We want to introduce an Articles Listing Page that displays all published articles in a unified feed. This page will allow readers to browse available articles and click into individual article detail pages.

Currently, MIT articles are published on Medium, and we rely on Medium’s feed pipeline to populate our news feed. To support native articles created within mit-learn, we are introducing a new pipeline that integrates our internally published articles into the same news feed system.

Screenshots (if appropriate):

Screen.Recording.2026-01-07.at.4.29.44.PM.mov

How can this be tested?

for testing you need to create new article in publish state and on home page see if it is added in stories section, even when you update the published article the published state should be changed

Additional Context

@ahtesham-quraish ahtesham-quraish changed the title WIP feat: add listing page and news/stories pipelien Jan 7, 2026
@ahtesham-quraish ahtesham-quraish force-pushed the ahtesham/listing-page branch 6 times, most recently from 1dffb25 to f5b006d Compare January 8, 2026 08:48
@ahtesham-quraish ahtesham-quraish marked this pull request as ready for review January 8, 2026 10:42
@ahtesham-quraish ahtesham-quraish changed the title feat: add listing page and news/stories pipelien feat: add news/stories pipeline which create news from article Jan 9, 2026
@mbertrand mbertrand self-assigned this Jan 9, 2026
@@ -0,0 +1,43 @@
"""Signal handlers for articles app"""

import logging
Copy link
Member

Choose a reason for hiding this comment

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

Instead of signals, mit-learn has been using pluggy. It would probably be best to do that here as well.

Here is an existing example for the authentication and profiles apps:

authentication/apps.py:

Adds several lines to initiate the pluggy hooks & plugins.

from django.apps import AppConfig
from pluggy import HookimplMarker, HookspecMarker


class AuthenticationConfig(AppConfig):
    """Authentication AppConfig"""

    name = "authentication"

    hookimpl = HookimplMarker(name)
    hookspec = HookspecMarker(name)

authentication/hooks.py:

Defines hook functions that are used to trigger plugin actions:

    @hookspec
    def user_created(self, user, user_data):
        """Trigger actions after a user is created"""

It also sets up the plugin manager for the app. There is a MITOL_AUTHENTICATION_PLUGINS setting used here, which is defined in main/settings_pluggy.py and indicates which plugins are needed for this app. In this case there is a learning_resources plugin and a profiles plugin:

MITOL_AUTHENTICATION_PLUGINS = get_string(
    "MITOL_AUTHENTICATION_PLUGINS",
    "learning_resources.plugins.FavoritesListPlugin,profiles.plugins.CreateProfilePlugin",
)

Each of these apps will have a plugins.py file. Here is the profiles plugin. The function inside the plugin class has a function with the same name and args as the hook defined above in authentication/hooks.py.

class CreateProfilePlugin:
    hookimpl = apps.get_app_config("authentication").hookimpl

    @hookimpl
    def user_created(self, user, user_data):
        """
        Perform functions on a newly created user

        Args:
            user(User): the user that was created
            user_data(dict): the user data
        """
        profile_data = user_data.get("profile", {})
        ensure_profile(user, profile_data)

In authentication/api.py there is a function user_created_actions which calls the hook that will trigger the plugin:

def user_created_actions(*, user, details, **kwargs):
    """
    Trigger plugins when a user is created
    """
    if kwargs.get("is_new"):
        pm = get_plugin_manager()
        hook = pm.hook
        hook.user_created(user=user, user_data={"profile": details})

Currently, this function is called from main/middleware/apisix.py:

user_created_actions(user=user, is_new=created, details=profile_data)

So in this case, hooks would be defined in the articles app, and the plugins w/functions would be defined in the news_events app. Something like this:

articles/hooks.py:

    @hookspec
    def sync_article_to_news_on_publish(self, article):
        """Trigger actions after an article is published""

news_events/plugins.py:

class ArticleNewsPlugin:
    hookimpl = apps.get_app_config("authentication").hookimpl

    @hookimpl
    def sync_article_to_news_on_publish(self, article):
        """
        Sync a published article to news_events

        Args:
            article(Article): the article that was published
        """
        # The code/logic in  the current sync_article_to_news_on_publish would go here.

Create a sync_article_actions function, maybe in a new articles/api.py file, and I think you'd call it from articles.views.ArticleViewSet.perform_create?

    def perform_create(self, serializer):
        clear_views_cache()
        article = serializer.save(user=self.request.user)
        sync_article_actions(article)

but you'll probably need to add an override for partial_update too and call it from within there as well.

It's more complex than the signals approach but it helps keep apps more independent of each other (imports etc) - https://github.com/mitodl/hq/discussions/1638

Copy link
Contributor Author

Choose a reason for hiding this comment

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

check now sir I have used this

Comment on lines +104 to +105
content_text = extract_text_from_content(content_json)
summary_text = content_text[:500] if content_text else ""
Copy link
Member

Choose a reason for hiding this comment

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

For both of these fields, the value contains the title and text of the article repeated twice:

"summary": "A new article yay A new article yay This is a new article - pub 2a This is a new article - pub 2a",
"content": "A new article yay A new article yay This is a new article - pub 2a This is a new article - pub 2a",

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Check now please

"image": image_data,
"detail": {
"authors": [author_name] if author_name else [],
"topics": [], # Add topics if you have them in your Article model
Copy link
Member

Choose a reason for hiding this comment

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

More of a question for Ferdi, should we include any default topics for mit-learn articles?

@github-actions
Copy link

OpenAPI Changes

Show/hide 26 changes: 6 error, 8 warning, 12 info
26 changes: 6 error, 8 warning, 12 info
error	[response-required-property-removed] at head/openapi/specs/v0.yaml	
	in API GET /api/v0/video_shorts/
		removed the required property 'results/items/video_id' from the response with the '200' status

error	[response-required-property-removed] at head/openapi/specs/v0.yaml	
	in API GET /api/v0/video_shorts/{video_id}/
		removed the required property 'video_id' from the response with the '200' status

error	[new-required-request-parameter] at head/openapi/specs/v1.yaml	
	in API POST /api/v1/webhooks/video_shorts/
		added the new required 'query' request parameter 'youtube_metadata'

error	[new-required-request-property] at head/openapi/specs/v1.yaml	
	in API POST /api/v1/webhooks/video_shorts/
		added the new required request property 'youtube_metadata'

error	[new-required-request-property] at head/openapi/specs/v1.yaml	
	in API POST /api/v1/webhooks/video_shorts/
		added the new required request property 'youtube_metadata'

error	[new-required-request-property] at head/openapi/specs/v1.yaml	
	in API POST /api/v1/webhooks/video_shorts/
		added the new required request property 'youtube_metadata'

warning	[response-optional-property-removed] at head/openapi/specs/v0.yaml	
	in API GET /api/v0/video_shorts/
		removed the optional property 'results/items/thumbnail_large_url' from the response with the '200' status

warning	[response-optional-property-removed] at head/openapi/specs/v0.yaml	
	in API GET /api/v0/video_shorts/
		removed the optional property 'results/items/thumbnail_small_url' from the response with the '200' status

warning	[response-optional-property-removed] at head/openapi/specs/v0.yaml	
	in API GET /api/v0/video_shorts/{video_id}/
		removed the optional property 'thumbnail_large_url' from the response with the '200' status

warning	[response-optional-property-removed] at head/openapi/specs/v0.yaml	
	in API GET /api/v0/video_shorts/{video_id}/
		removed the optional property 'thumbnail_small_url' from the response with the '200' status

warning	[request-parameter-removed] at head/openapi/specs/v1.yaml	
	in API POST /api/v1/webhooks/video_shorts/
		deleted the 'query' request parameter 'video_metadata'
		This is a warning because some apps may return an error when receiving a parameter that they do not expect. It is recommended to deprecate the parameter first.

warning	[request-property-removed] at head/openapi/specs/v1.yaml	
	in API POST /api/v1/webhooks/video_shorts/
		removed the request property 'video_metadata'

warning	[request-property-removed] at head/openapi/specs/v1.yaml	
	in API POST /api/v1/webhooks/video_shorts/
		removed the request property 'video_metadata'

warning	[request-property-removed] at head/openapi/specs/v1.yaml	
	in API POST /api/v1/webhooks/video_shorts/
		removed the request property 'video_metadata'

info	[response-optional-property-added] at head/openapi/specs/v0.yaml	
	in API GET /api/v0/video_shorts/
		added the optional property 'results/items/description' to the response with the '200' status

info	[response-property-became-required] at head/openapi/specs/v0.yaml	
	in API GET /api/v0/video_shorts/
		the response property 'results/items/video_url' became required for the status '200'

info	[response-required-property-added] at head/openapi/specs/v0.yaml	
	in API GET /api/v0/video_shorts/
		added the required property 'results/items/thumbnail_height' to the response with the '200' status

info	[response-required-property-added] at head/openapi/specs/v0.yaml	
	in API GET /api/v0/video_shorts/
		added the required property 'results/items/thumbnail_url' to the response with the '200' status

info	[response-required-property-added] at head/openapi/specs/v0.yaml	
	in API GET /api/v0/video_shorts/
		added the required property 'results/items/thumbnail_width' to the response with the '200' status

info	[response-required-property-added] at head/openapi/specs/v0.yaml	
	in API GET /api/v0/video_shorts/
		added the required property 'results/items/youtube_id' to the response with the '200' status

info	[response-optional-property-added] at head/openapi/specs/v0.yaml	
	in API GET /api/v0/video_shorts/{video_id}/
		added the optional property 'description' to the response with the '200' status

info	[response-property-became-required] at head/openapi/specs/v0.yaml	
	in API GET /api/v0/video_shorts/{video_id}/
		the response property 'video_url' became required for the status '200'

info	[response-required-property-added] at head/openapi/specs/v0.yaml	
	in API GET /api/v0/video_shorts/{video_id}/
		added the required property 'thumbnail_height' to the response with the '200' status

info	[response-required-property-added] at head/openapi/specs/v0.yaml	
	in API GET /api/v0/video_shorts/{video_id}/
		added the required property 'thumbnail_url' to the response with the '200' status

info	[response-required-property-added] at head/openapi/specs/v0.yaml	
	in API GET /api/v0/video_shorts/{video_id}/
		added the required property 'thumbnail_width' to the response with the '200' status

info	[response-required-property-added] at head/openapi/specs/v0.yaml	
	in API GET /api/v0/video_shorts/{video_id}/
		added the required property 'youtube_id' to the response with the '200' status


Unexpected changes? Ensure your branch is up-to-date with main (consider rebasing).

@ahtesham-quraish ahtesham-quraish added Needs Review An open Pull Request that is ready for review and removed Waiting on author labels Jan 12, 2026
Copy link
Member

@mbertrand mbertrand left a comment

Choose a reason for hiding this comment

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

Looks great, could just use a few more unit tests

@mbertrand mbertrand added Waiting on author and removed Needs Review An open Pull Request that is ready for review labels Jan 12, 2026
Copy link
Member

@mbertrand mbertrand left a comment

Choose a reason for hiding this comment

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

👍

@ahtesham-quraish ahtesham-quraish merged commit d757bba into main Jan 13, 2026
12 of 13 checks passed
@ahtesham-quraish ahtesham-quraish deleted the ahtesham/listing-page branch January 13, 2026 13:22
This was referenced Jan 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants