Skip to content

Commit 22c60e9

Browse files
committed
feat: add new group icon for various author cases in user profile
1 parent 709e9d6 commit 22c60e9

10 files changed

Lines changed: 121 additions & 10 deletions

File tree

core/views.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2008,6 +2008,16 @@ def get_context_data(self, **kwargs):
20082008
"badge": BadgeToken.BOOST_DAY,
20092009
"bio": "Boost Day contributor.",
20102010
},
2011+
{
2012+
# Collective author: group icon, no link or role (as produced by
2013+
# build_library_intro_context for e.g. "Various").
2014+
"name": "Various Authors",
2015+
"profile_url": None,
2016+
"role": None,
2017+
"avatar_url": "",
2018+
"badge": "",
2019+
"bio": "",
2020+
},
20112021
]
20122022

20132023
context["contributor_data"] = 10 * [

libraries/utils.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,25 @@ def patch_commit_authors(users):
470470
return users
471471

472472

473+
def apply_collective_author_overrides(author_dicts):
474+
"""Normalize collective authors (e.g. "Various Authors") in V3 profile dicts.
475+
476+
A collective author renders as a single labelled group icon with no profile
477+
link or role. Mutates each dict in place and returns the same list.
478+
"""
479+
from users.templatetags.avatar_tags import (
480+
collective_author_label,
481+
is_collective_author,
482+
)
483+
484+
for author in author_dicts:
485+
if is_collective_author(author["name"]):
486+
author["name"] = collective_author_label(author["name"])
487+
author["profile_url"] = None
488+
author["role"] = None
489+
return author_dicts
490+
491+
473492
def build_library_intro_context(
474493
library_version, *, max_authors=None, include_contributors=False
475494
):
@@ -520,6 +539,8 @@ def build_library_intro_context(
520539
for contributor in top_contributors
521540
)
522541

542+
apply_collective_author_overrides(author_dicts)
543+
523544
return {
524545
"library_name": library.display_name,
525546
"description": library_version.description or library.description or "",

libraries/views.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
Tier,
3535
)
3636
from .utils import (
37+
apply_collective_author_overrides,
3738
get_view_from_cookie,
3839
set_view_in_cookie,
3940
get_prioritized_library_view,
@@ -572,15 +573,17 @@ def get_v3_context_data(self, **kwargs):
572573
]
573574
)
574575
context["this_release_contributors"] = (
575-
this_release or SharedResources.library_release_contributors
576+
apply_collective_author_overrides(this_release)
577+
or SharedResources.library_release_contributors
576578
)
577579

578580
all_time = [
579581
a.to_v3_profile_dict("Contributor")
580582
for a in base_context.get("previous_contributors", [])
581583
]
582584
context["all_time_contributors"] = (
583-
all_time or SharedResources.library_all_contributors
585+
apply_collective_author_overrides(all_time)
586+
or SharedResources.library_all_contributors
584587
)
585588

586589
context["is_flagship_lib"] = self.object.tier == Tier.FLAGSHIP

static/css/v3/avatar.css

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,19 @@
106106
background: var(--color-accent-weak-teal);
107107
}
108108

109+
/* Collective author (e.g. "Various Authors") */
110+
.avatar--collective {
111+
box-sizing: border-box;
112+
padding: var(--space-medium);
113+
background: var(--color-accent-weak-yellow);
114+
}
115+
116+
.avatar--collective .avatar__icon {
117+
width: 100%;
118+
height: 100%;
119+
color: var(--color-icon-primary);
120+
}
121+
109122
/* Placeholder (e.g. Header when no image and no name) */
110123
.avatar-placeholder {
111124
width: var(--avatar-size-md);

static/css/v3/contributors-list.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
.contributors-list .card__column {
66
gap: var(--space-medium);
7+
overflow-y: auto;
78
}
89

910
.contributors-list.all-contributors {

templates/includes/icon.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@
123123
<path d="M10 5.99996V4.66663H11.3333V5.99996H10ZM11.3333 9.99996V8.66663H8.66667V7.33329H11.3333V5.99996H12.6667V7.33329H14V8.66663H12.6667V9.99996H11.3333ZM11.3333 9.99996V11.3333H10V9.99996H11.3333ZM7.33333 7.33329V8.66663H4.66667V9.99996H3.33333V8.66663H2V7.33329H3.33333V5.99996H4.66667V7.33329H7.33333ZM4.66667 9.99996H6V11.3333H4.66667V9.99996ZM6 4.66663V5.99996H4.66667V4.66663H6Z" />
124124
{% elif icon_name == "users" %}
125125
<path d="M7.33333 0H3.33333V1.33333H2V5.33333H3.33333V6.66667H7.33333V5.33333H3.33333V1.33333H7.33333V0ZM7.33333 1.33333H8.66667V5.33333H7.33333V1.33333ZM0 9.33333H1.33333V12H9.33333V13.3333H0V9.33333ZM1.33333 9.33333H9.33333V8H1.33333V9.33333ZM10.6667 9.33333H9.33333V13.3333H10.6667V9.33333ZM10 0H12.6667V1.33333H10V0ZM12.6667 5.33333H10V6.66667H12.6667V5.33333ZM12.6667 1.33333H14V5.33333H12.6667V1.33333ZM16 9.33333H14.6667V12H12V13.3333H16V9.33333ZM12 8H14.6667V9.33333H12V8Z" />
126+
{% elif icon_name == "people-group" %}
127+
<path d="M11 2H5V4H3V10H5V12H11V10H5V4H11V2ZM11 4H13V10H11V4ZM0 16H2V20H14V22H0V16ZM2 16H14V14H2V16ZM16 16H14V22H16V16ZM15 2H19V4H15V2ZM19 10H15V12H19V10ZM19 4H21V10H19V4ZM24 16H22V20H18V22H24V16ZM18 14H22V16H18V14Z" />
126128
{% elif icon_name == "building-community" %}
127129
<path d="M13.3334 1.33337H14.6667V14.6667H1.33337V9.33337H2.66671V13.3334H5.33337V10.6667H6.66671V13.3334H9.33337V9.33337H10.6667V13.3334H13.3334V2.66671H6.66671V4.00004H5.33337V1.33337H13.3334ZM8.00004 8.00004H9.33337V9.33337H8.00004V8.00004ZM6.66671 6.66671H8.00004V8.00004H6.66671V6.66671ZM5.33337 6.66671V5.33337H6.66671V6.66671H5.33337ZM4.00004 8.00004V6.66671H5.33337V8.00004H4.00004ZM4.00004 8.00004H2.66671V9.33337H4.00004V8.00004ZM10.6667 4.00004H12V5.33337H10.6667V4.00004ZM9.33337 4.00004H8.00004V5.33337H9.33337V4.00004ZM10.6667 6.66671H12V8.00004H10.6667V6.66671Z" />
128130
{% elif icon_name == "zap" %}

templates/v3/includes/_avatar_v3.html

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@
99
- size (optional) Avatar size: sm | md | lg | xl | xxxl (default: md)
1010

1111
Rendering priority:
12-
1. Image — when `src` is provided
13-
2. Initials — when `name` is provided (falls back to "?" if initials can't be derived)
14-
3. Placeholder — generic "?" icon when neither `src` nor `name` is given
12+
1. Group icon — when `name` is a collective label (e.g. "Various Authors")
13+
2. Image — when `src` is provided
14+
3. Initials — when `name` is provided (falls back to "?" if initials can't be derived)
15+
4. Placeholder — generic "?" icon when none of the above are provided
1516

1617
Accessibility:
1718
- When `name` is provided, the avatar is announced to screen readers via aria-label.
@@ -26,7 +27,11 @@
2627
{% endcomment %}
2728
{% load avatar_tags %}
2829
{% with size=size|default:"md" variant=variant|default:"yellow" %}
29-
{% if src %}
30+
{% if name|is_collective_author %}
31+
<span class="avatar avatar--{{ size }} avatar--collective" role="img" aria-label="{{ name }}">
32+
{% include "includes/icon.html" with icon_name="people-group" icon_class="avatar__icon" %}
33+
</span>
34+
{% elif src %}
3035
<span {% if id %}id="{{id}}"{% endif %} class="avatar avatar--{{ size }}"{% if name %} role="img" aria-label="{{ name }}"{% else %} aria-hidden="true"{% endif %}>
3136
<img src="{{ src }}" class="avatar avatar--{{ size }} avatar__img" alt="" loading="lazy" decoding="async">
3237
</span>

users/templatetags/avatar_tags.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,25 @@
1+
import re
12
from typing import Literal
23
from django import template
34
from django.template.loader import render_to_string
45

56
register = template.Library()
67

8+
_COLLECTIVE_AUTHOR_RE = re.compile(r"^\s*various\b", re.IGNORECASE)
9+
COLLECTIVE_AUTHOR_LABEL = "Various Authors"
10+
11+
12+
@register.filter
13+
def is_collective_author(value):
14+
"""True when `value` is a collective author label (e.g. "Various Authors")."""
15+
return isinstance(value, str) and bool(_COLLECTIVE_AUTHOR_RE.match(value))
16+
17+
18+
@register.filter
19+
def collective_author_label(value):
20+
"""Canonical display label for a collective author, else `value` unchanged."""
21+
return COLLECTIVE_AUTHOR_LABEL if is_collective_author(value) else value
22+
723

824
@register.filter(is_safe=True)
925
def avatar_initials(value):

users/tests/test_avatar_tags.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import pytest
2+
3+
from users.templatetags.avatar_tags import (
4+
collective_author_label,
5+
is_collective_author,
6+
)
7+
8+
9+
@pytest.mark.parametrize(
10+
"value,expected",
11+
[
12+
("Various Authors", True),
13+
("Various", True),
14+
("various authors", True),
15+
(" Various Authors", True),
16+
("John Various", False),
17+
("Variant", False),
18+
("", False),
19+
(None, False),
20+
(123, False),
21+
],
22+
)
23+
def test_is_collective_author(value, expected):
24+
assert is_collective_author(value) is expected
25+
26+
27+
@pytest.mark.parametrize(
28+
"value,expected",
29+
[
30+
("Various", "Various Authors"),
31+
("various", "Various Authors"),
32+
("Various Authors", "Various Authors"),
33+
("Jane Doe", "Jane Doe"),
34+
],
35+
)
36+
def test_collective_author_label(value, expected):
37+
assert collective_author_label(value) == expected

versions/views.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from libraries.models import Commit, CommitAuthor, ReleaseReport
2929
from libraries.tasks import generate_release_report
3030
from libraries.utils import (
31+
apply_collective_author_overrides,
3132
set_selected_boost_version,
3233
determine_selected_boost_version,
3334
library_doc_latest_transform,
@@ -181,10 +182,12 @@ def get_version_heading(self, obj, is_current_release, v3=False):
181182

182183
def get_v3_contributors(self, version):
183184
"""Shape the release's top contributors for the v3 contributors card."""
184-
return [
185-
author.to_v3_profile_dict("Contributor")
186-
for author in self.get_top_contributors_release(version)
187-
]
185+
return apply_collective_author_overrides(
186+
[
187+
author.to_v3_profile_dict("Contributor")
188+
for author in self.get_top_contributors_release(version)
189+
]
190+
)
188191

189192
def get_v3_context_data(self, **kwargs):
190193
obj = self.object

0 commit comments

Comments
 (0)