Skip to content

Commit b74c1c9

Browse files
authored
Merge pull request #1716 from cdw9/1705_library_author
Display user icons on the homepage library spotlight
2 parents 4860978 + a31970c commit b74c1c9

File tree

5 files changed

+196
-170
lines changed

5 files changed

+196
-170
lines changed

ak/tests/test_default_pages.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from django.test.utils import override_settings
55

66

7-
def test_homepage(library, version, tp):
7+
def test_homepage(library, library_version, version, tp):
88
"""Ensure we can hit the homepage"""
99
# Use any page that is named 'home' otherwise use /
1010
url = tp.reverse("home")

ak/views.py

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,23 @@
99

1010
from core.calendar import extract_calendar_events, events_by_month, get_calendar
1111
from libraries.constants import LATEST_RELEASE_URL_PATH_STR
12-
from libraries.models import Library
12+
from libraries.mixins import ContributorMixin
1313
from news.models import Entry
14-
from versions.models import Version
1514

1615

1716
logger = structlog.get_logger()
1817

1918

20-
class HomepageView(TemplateView):
19+
class HomepageView(ContributorMixin, TemplateView):
2120
"""
22-
Our default homepage for temp-site. We expect you to not use this view
23-
after you start working on your project.
21+
Define all the pieces that will be displayed on the home page
2422
"""
2523

2624
template_name = "homepage.html"
2725

2826
def get_context_data(self, **kwargs):
2927
context = super().get_context_data(**kwargs)
3028
context["entries"] = Entry.objects.published().order_by("-publish_at")[:3]
31-
latest_version = Version.objects.most_recent()
32-
context["latest_version"] = latest_version
33-
context["featured_library"] = self.get_featured_library(latest_version)
3429
context["events"] = self.get_events()
3530
if context["events"]:
3631
context["num_months"] = len(context["events"])
@@ -64,19 +59,6 @@ def get_events(self):
6459

6560
return dict(sorted_events)
6661

67-
def get_featured_library(self, latest_version):
68-
library = Library.objects.filter(featured=True).first()
69-
70-
# If we don't have a featured library, return a random library
71-
if not library:
72-
library = (
73-
Library.objects.filter(library_version__version=latest_version)
74-
.order_by("?")
75-
.first()
76-
)
77-
78-
return library
79-
8062

8163
class ForbiddenView(View):
8264
"""

libraries/mixins.py

Lines changed: 179 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
import structlog
2+
from types import SimpleNamespace
3+
4+
from django.db.models import Count, Exists, OuterRef
5+
from django.db.models.functions import Lower
26
from django.shortcuts import get_object_or_404
37
from django.urls import reverse
48

@@ -7,7 +11,13 @@
711
MASTER_RELEASE_URL_PATH_STR,
812
DEVELOP_RELEASE_URL_PATH_STR,
913
)
10-
from libraries.models import Library
14+
from libraries.models import (
15+
Commit,
16+
CommitAuthor,
17+
CommitAuthorEmail,
18+
Library,
19+
LibraryVersion,
20+
)
1121
from versions.models import Version
1222

1323
logger = structlog.get_logger()
@@ -70,3 +80,171 @@ def set_extra_context(self, request):
7080
)
7181
# here we hack extra_context into the request so we can access for cookie checks
7282
request.extra_context = self.extra_context
83+
84+
85+
class ContributorMixin:
86+
"""Mixin to gather a list of all authors, maintainers, and
87+
contributors without duplicates.
88+
Uses the current Library if on the Library detail view,
89+
otherwise grabs a featured library
90+
"""
91+
92+
def get_context_data(self, **kwargs):
93+
context = super().get_context_data(**kwargs)
94+
context["latest_version"] = Version.objects.most_recent()
95+
96+
if hasattr(self, "object") and isinstance(self.object, Library):
97+
library = self.object
98+
try:
99+
library_version = LibraryVersion.objects.get(
100+
library=library, version=context["selected_version"]
101+
)
102+
except LibraryVersion.DoesNotExist:
103+
return context
104+
else:
105+
library_version = self.get_featured_library()
106+
context["featured_library"] = library_version
107+
108+
context["authors"] = self.get_related(library_version, "authors")
109+
context["maintainers"] = self.get_related(
110+
library_version,
111+
"maintainers",
112+
exclude_ids=[x.id for x in context["authors"]],
113+
)
114+
context["author_tag"] = self.get_author_tag(library_version)
115+
exclude_maintainer_ids = [
116+
x.commitauthor.id
117+
for x in context["maintainers"]
118+
if getattr(x.commitauthor, "id", None)
119+
]
120+
exclude_author_ids = [
121+
x.commitauthor.id
122+
for x in context["authors"]
123+
if getattr(x.commitauthor, "id", None)
124+
]
125+
top_contributors_release = self.get_top_contributors(
126+
library_version=library_version,
127+
exclude=exclude_maintainer_ids + exclude_author_ids,
128+
)
129+
context["top_contributors_release_new"] = [
130+
x for x in top_contributors_release if x.is_new
131+
]
132+
context["top_contributors_release_old"] = [
133+
x for x in top_contributors_release if not x.is_new
134+
]
135+
exclude_top_contributor_ids = [x.id for x in top_contributors_release]
136+
context["previous_contributors"] = self.get_previous_contributors(
137+
library_version,
138+
exclude=exclude_maintainer_ids
139+
+ exclude_top_contributor_ids
140+
+ exclude_author_ids,
141+
)
142+
return context
143+
144+
def get_featured_library(self):
145+
"""Returns latest LibraryVersion associated with the featured Library"""
146+
# If multiple are featured, pick one at random
147+
latest_version = Version.objects.most_recent()
148+
library = Library.objects.filter(featured=True).order_by("?").first()
149+
150+
# If we don't have a featured library, return a random library
151+
if not library:
152+
library = (
153+
Library.objects.filter(library_version__version=latest_version)
154+
.order_by("?")
155+
.first()
156+
)
157+
if not library:
158+
return None
159+
libversion = LibraryVersion.objects.filter(
160+
library_id=library.id, version=latest_version
161+
).first()
162+
163+
return libversion
164+
165+
def get_related(self, library_version, relation="maintainers", exclude_ids=None):
166+
"""Get the maintainers|authors for the current LibraryVersion.
167+
168+
Also patches the CommitAuthor onto the user, if a matching email exists.
169+
"""
170+
if relation == "maintainers":
171+
qs = library_version.maintainers.all()
172+
elif relation == "authors":
173+
qs = library_version.authors.all()
174+
else:
175+
raise ValueError("relation must be maintainers or authors.")
176+
if exclude_ids:
177+
qs = qs.exclude(id__in=exclude_ids)
178+
qs = list(qs)
179+
commit_authors = {
180+
author_email.email: author_email
181+
for author_email in CommitAuthorEmail.objects.annotate(
182+
email_lower=Lower("email")
183+
)
184+
.filter(email_lower__in=[x.email.lower() for x in qs])
185+
.select_related("author")
186+
}
187+
for user in qs:
188+
if author_email := commit_authors.get(user.email.lower(), None):
189+
user.commitauthor = author_email.author
190+
else:
191+
user.commitauthor = SimpleNamespace(
192+
github_profile_url="",
193+
avatar_url="",
194+
display_name=f"{user.display_name}",
195+
)
196+
return qs
197+
198+
def get_author_tag(self, library_version):
199+
"""Format the authors for the author meta tag in the template."""
200+
author_names = list(
201+
library_version.library.authors.values_list("display_name", flat=True)
202+
)
203+
if len(author_names) > 1:
204+
final_output = ", ".join(author_names[:-1]) + " and " + author_names[-1]
205+
else:
206+
final_output = author_names[0] if author_names else ""
207+
208+
return final_output
209+
210+
def get_top_contributors(self, library_version=None, exclude=None):
211+
if library_version:
212+
prev_versions = Version.objects.minor_versions().filter(
213+
version_array__lt=library_version.version.cleaned_version_parts_int
214+
)
215+
qs = CommitAuthor.objects.filter(
216+
commit__library_version=library_version
217+
).annotate(
218+
is_new=~Exists(
219+
Commit.objects.filter(
220+
author_id=OuterRef("id"),
221+
library_version__in=LibraryVersion.objects.filter(
222+
version__in=prev_versions, library=library_version.library
223+
),
224+
)
225+
)
226+
)
227+
else:
228+
qs = CommitAuthor.objects.filter(
229+
commit__library_version__library=self.object
230+
)
231+
if exclude:
232+
qs = qs.exclude(id__in=exclude)
233+
qs = qs.annotate(count=Count("commit")).order_by("-count")
234+
return qs
235+
236+
def get_previous_contributors(self, library_version, exclude=None):
237+
library_versions = LibraryVersion.objects.filter(
238+
library=library_version.library,
239+
version__in=Version.objects.minor_versions().filter(
240+
version_array__lt=library_version.version.cleaned_version_parts_int
241+
),
242+
)
243+
qs = (
244+
CommitAuthor.objects.filter(commit__library_version__in=library_versions)
245+
.annotate(count=Count("commit"))
246+
.order_by("-count")
247+
)
248+
if exclude:
249+
qs = qs.exclude(id__in=exclude)
250+
return qs

0 commit comments

Comments
 (0)