Skip to content

Add item authors to RSS feeds #7012

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 42 additions & 2 deletions tests/unit/rss/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,14 @@ def test_rss_updates(db_request):
release1.created = datetime.date(2011, 1, 1)
release2 = ReleaseFactory.create(project=project2)
release2.created = datetime.date(2012, 1, 1)
release2.author_email = "[email protected]"
release3 = ReleaseFactory.create(project=project1)
release3.created = datetime.date(2013, 1, 1)

assert rss.rss_updates(db_request) == {
"latest_releases": [release3, release2, release1]
"latest_releases": tuple(
zip((release3, release2, release1), (None, "[email protected]", None))
)
}
assert db_request.response.content_type == "text/xml"

Expand All @@ -64,5 +67,42 @@ def test_rss_packages(db_request):
project3.created = datetime.date(2013, 1, 1)
ReleaseFactory.create(project=project3)

assert rss.rss_packages(db_request) == {"newest_projects": [project3, project1]}
assert rss.rss_packages(db_request) == {
"newest_projects": tuple(zip((project3, project1), (None, None)))
}
assert db_request.response.content_type == "text/xml"


def test_format_author(db_request):
db_request.find_service = pretend.call_recorder(
lambda *args, **kwargs: pretend.stub(
enabled=False, csp_policy=pretend.stub(), merge=lambda _: None
)
)

db_request.session = pretend.stub()

project = ProjectFactory.create()
release = ReleaseFactory.create(project=project)

release.author_email = "[email protected]"
assert rss._format_author(release) == release.author_email

release.author_email = "No Reply <[email protected]>"
assert rss._format_author(release) == "[email protected]"

for invalid in (None, "", "UNKNOWN", "[email protected], UNKNOWN"):
release.author_email = invalid
assert rss._format_author(release) is None

release.author_email = (
# simple, no spaces
"[email protected],"
# space after
"[email protected] ,"
# space before, incl realname
" No Reply <[email protected]>,"
# two spaces before, angle brackets
" <[email protected]>"
)
assert rss._format_author(release) == ", ".join(["[email protected]"] * 4)
36 changes: 34 additions & 2 deletions warehouse/rss/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from email.utils import getaddresses

from pyramid.view import view_config
from sqlalchemy.orm import joinedload

Expand All @@ -18,6 +20,32 @@
from warehouse.xml import XML_CSP


def _format_author(release):
"""
Format release author suitably for inclusion in an RSS feed.

Release author names and emails are hard to match robustly, mainly
because there may be multiple in both, and especially the names may
contain pretty much anything. So stick with just the emails with some
rudimentary sanity checks.

Even though the spec says "the email address" and thus assumes a single
author, we let multiple pass, comma separated.

http://www.rssboard.org/rss-specification#ltauthorgtSubelementOfLtitemgt
"""
author_emails = []
for _, author_email in getaddresses([release.author_email or ""]):
if "@" not in author_email:
# Require all valid looking
return None
author_emails.append(author_email)

if not author_emails:
return None
return ", ".join(author_emails)


@view_config(
route_name="rss.updates",
renderer="rss/updates.xml",
Expand All @@ -42,8 +70,9 @@ def rss_updates(request):
.limit(40)
.all()
)
release_authors = [_format_author(release) for release in latest_releases]

return {"latest_releases": latest_releases}
return {"latest_releases": tuple(zip(latest_releases, release_authors))}


@view_config(
Expand All @@ -70,5 +99,8 @@ def rss_packages(request):
.limit(40)
.all()
)
project_authors = [
_format_author(project.releases[0]) for project in newest_projects
]

return {"newest_projects": newest_projects}
return {"newest_projects": tuple(zip(newest_projects, project_authors))}
3 changes: 2 additions & 1 deletion warehouse/templates/rss/packages.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
{% block title %}PyPI newest packages{% endblock %}
{% block description %}Newest packages registered at the Python Package Index{% endblock %}
{% block items -%}
{% for project in newest_projects %}
{% for project, author in newest_projects %}
<item>
<title>{{ project.name }} added to PyPI</title>
<link>{{ request.route_url('packaging.project', name=project.normalized_name) }}</link>
<guid>{{ request.route_url('packaging.project', name=project.normalized_name) }}</guid>
<description>{{ project.releases[0].summary }}</description>
{% if author %}<author>{{ author }}</author>{% endif %}
<pubDate>{{ project.created|format_rfc822_datetime() }}</pubDate>
</item>
{%- endfor %}
Expand Down
3 changes: 2 additions & 1 deletion warehouse/templates/rss/updates.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
{% block title %}PyPI recent updates{% endblock %}
{% block description %}Recent updates to the Python Package Index{% endblock %}
{% block items -%}
{% for release in latest_releases %}
{% for release, author in latest_releases %}
<item>
<title>{{ release.project.name }} {{ release.version }}</title>
<link>{{ request.route_url('packaging.release', name=release.project.normalized_name, version=release.version) }}</link>
<description>{{ release.summary }}</description>
{% if author %}<author>{{ author }}</author>{% endif %}
<pubDate>{{ release.created|format_rfc822_datetime() }}</pubDate>
</item>
{%- endfor %}
Expand Down