Skip to content

Refactor for common news endpoint#4195

Open
MizukiTemma wants to merge 5 commits intodevelopfrom
refactoring/common_news_endpoint
Open

Refactor for common news endpoint#4195
MizukiTemma wants to merge 5 commits intodevelopfrom
refactoring/common_news_endpoint

Conversation

@MizukiTemma
Copy link
Copy Markdown
Member

@MizukiTemma MizukiTemma commented Mar 16, 2026

Short description

This PR implements a common endpoint for news (push notifcation) and Tü News.

Proposed changes

  • Add a new endpoint all-news/ that delivers both PNs and posts of Tü News
  • Add source flag to distinguish PNs form Tü News posts
  • Keep the old endpoint fcm/,sent_push_notifications) not to break the app
  • Add a celery task to collect and save Tü news posts as cache
  • Add Tü News related models and management command to integrate functionalities of our Tü News repository
  • The current approach is based on that we will store Tü News posts in our Integreat CMS database and the memory cache solution proposed here comes later

Side effects

Faithfulness to issue description and design

There are no intended deviations from the issue and design.

  • Currently Tü News Bridge is supporting only German, English, Farsi and Arabic. This PR checkes Tü News posts for all languages that are available in the CMS system.

  • We probably need a new section in CMS where Service Team can decide for which language we fetch posts from Tü News (not implemented yet). Currently German, English, Farsi and Arabic are handled in our Tü News

How to test

  1. Use the command import_tuenews
  2. Check "Enable external news" in a region
  3. Try the new endpoint

❓ Does anyone have an idea how to test the social media header endpoints (news/<slug:news_type>/<slug:slug>/) ?

Resolved issues

Fixes: #4174


Pull Request Review Guidelines

@steffenkleinle
Copy link
Copy Markdown
Member

German verbose names should probably be changed to English 🤔 Is there any harm @svenseeberg @steffenkleinle ?

What does that mean, can you give me some more info here please? What names are we talking about?

@MizukiTemma
Copy link
Copy Markdown
Member Author

German verbose names should probably be changed to English 🤔 Is there any harm @svenseeberg @steffenkleinle ?

What does that mean, can you give me some more info here please? What names are we talking about?

"Title", "Inhalt", "E-News Nummer", "Datum", "WP Post ID" for example

https://github.com/digitalfabrik/tunews/blob/66dcab37b28b0f4d9cbdbd2d91abdc7189ffa216/src/news/models/newsitem.py#L9-L19

I guess these aren't used in the app but want to be sure 😅 Maybe rather a question to @svenseeberg if these are being used somewhere.

@MizukiTemma MizukiTemma changed the title [WIP] Refactor for common news endpoint Refactor for common news endpoint Mar 16, 2026
@MizukiTemma MizukiTemma marked this pull request as ready for review March 16, 2026 16:58
@MizukiTemma MizukiTemma added deadline Needs to be fixed in the given time needs-reviewer labels Mar 16, 2026
Copy link
Copy Markdown
Contributor

@jonbulz jonbulz left a comment

Choose a reason for hiding this comment

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

This PR doesn't sit right with me. I don't think this is a good approach. I think there are 3 ways to handle this, all with their pros and cons:

  1. Store News in cache, not database: Have some kind of service fetch news form different sources and store them as JSON in our redis cache. Then have our API simply fetch the cached news from external sources in addition to our internal PushNotifications
  2. Properly store the external news in our database by transforming them to PushNotifications (with some kind of source flag for clarity)
  3. If the deadline is urgent, copy-paste the code from tunews as a Django app into our repository and dump the content from the existing database. IMO, this only makes sense as an indermediate step, i.e. the code should be removed at some point once we implemented one of the above solutions. this is much easier if we keep it as a separate app.

Introducing a new NewsLanguage model in our cms app feels like a bad idea to me. This is how you get a messy database. No one wants a messy database

title=post["title"],
language_code=language.bcp47_tag,
excerpt=post["content"],
url=post["link"],
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@steffenkleinle

We don't have get_absolute_url() for Tü News posts as they are not part of our contents. Is it acceptable to substitute url with link flag for Tü News posts? (examples of Tü News)

@MizukiTemma MizukiTemma requested a review from jonbulz April 2, 2026 20:34
@MizukiTemma
Copy link
Copy Markdown
Member Author

@jonbulz
Thank you for detailed suggestions and explanation 😍 I hope it's now implemented better 💪

Copy link
Copy Markdown
Contributor

@jonbulz jonbulz left a comment

Choose a reason for hiding this comment

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

Thanks for working on this topic, this is a solid start! I do have some concerns, though:

Architecture / Extensibility

We already plan to add a third news source (Amal news) in the near future. There may be more to come after that. With this pattern, we have growing if/elif chains in several places. Also, the code for external news lives in push_notifications.py, which is a bit confusing. I think even though we decided against a database model, this deserves its own module and class. I don't want to be too specific with the proposed solution, but if you want, we can discuss this in a meeting.

Resilience

There are several possible unhandled exceptions in the code, see my additional comments. I think we need to be a bit more defensive when dealing with content from external sources. A malformed response should not crash our process.

Other Bugs

There are some other bugs I noticed, e.g. the duplicate URL name, see my comments. Additionally, I think we are still lacking pagination, and the results are not sorted again after tuenews have been added. I'd also prefer if we stick to one naming convention tuenews or tue_news :)

news.append(self.transform_post(post))

cache.delete(f"tuenews:{language.slug}")
cache.set(f"tuenews:{language.slug}", news, timeout=60 * 60 * 24)
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.

maybe TTL of 24h is a bit short if we run this command once a day. actually, I guess we could remove the timeout entirely

request=request,
title=post["title"],
language_code=language.bcp47_tag,
excerpt=post["content"],
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.

I think the posts don't have a content attribute after transform_post, so post["content"] will raise a KeyError

if not region.external_news_enabled:
raise Http404("Tü News is not enabled in this region.")
posts = cache.get(f"tuenews:{language_slug}", [])
post = next((post for post in posts if post["id"] == slug), None)
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.

slug here is a str, are you sure post["id"] is as well? if it's an integer, the condition will always be false. Maybe safer to use str(post["id"]), or vice versa?

path(
"news/all-news/",
region_social_media_headers,
name="social_region_reserved_local_news",
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.

shadows "news/local/" on urls.py:191

:param language_slug: language slug
:return: list of Tü news posts
"""
if not Region.objects.get(slug=region_slug).external_news_enabled:
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.

I think it's safer to pass request.region instead of region_slug. If you want to keep it like this, you should catch and handle RegionDoesNotExist

)

if response.status_code != 200:
continue
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.

silently failing the import could give future us a headache. I'd log at least a warning here

"""

for language in Language.objects.all():
response = requests.get(
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.

it's probably safer to wrap all of this in a try/except block to handle RequestExceptions (ConnectionError, Timeout, DecodeError)

news = []

for post in posts:
if not post["acf"]["integreat"]:
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.

a malformed post will raise an unhandled KeyError. safer to wrap in try/except and catch KeyError and TypeError


for language in Language.objects.all():
response = requests.get(
f"http://tuenews.de/wp-json/wp/v2/posts/?lang={language.slug}",
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.

no https?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deadline Needs to be fixed in the given time

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Provide common news endpoint / Refactor TüNews

4 participants