mirror of
https://github.com/boostorg/website-v2.git
synced 2026-01-19 04:42:17 +00:00
display user icons on the homepage library spotlight refs #1658
Re-use the code written for the library detail page for displaying authors and maintainers on the homepage. To avoid duplicating code, moved all the necessary pieces to a mixin to be used by HomepageView and LibraryDetail, and adjusted it to work for both.
This commit is contained in:
@@ -4,7 +4,7 @@ import pytest
|
||||
from django.test.utils import override_settings
|
||||
|
||||
|
||||
def test_homepage(library, version, tp):
|
||||
def test_homepage(library, library_version, version, tp):
|
||||
"""Ensure we can hit the homepage"""
|
||||
# Use any page that is named 'home' otherwise use /
|
||||
url = tp.reverse("home")
|
||||
|
||||
24
ak/views.py
24
ak/views.py
@@ -9,18 +9,16 @@ from django.views.generic import TemplateView
|
||||
|
||||
from core.calendar import extract_calendar_events, events_by_month, get_calendar
|
||||
from libraries.constants import LATEST_RELEASE_URL_PATH_STR
|
||||
from libraries.models import Library
|
||||
from libraries.mixins import ContributorMixin
|
||||
from news.models import Entry
|
||||
from versions.models import Version
|
||||
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
class HomepageView(TemplateView):
|
||||
class HomepageView(ContributorMixin, TemplateView):
|
||||
"""
|
||||
Our default homepage for temp-site. We expect you to not use this view
|
||||
after you start working on your project.
|
||||
Define all the pieces that will be displayed on the home page
|
||||
"""
|
||||
|
||||
template_name = "homepage.html"
|
||||
@@ -28,9 +26,6 @@ class HomepageView(TemplateView):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["entries"] = Entry.objects.published().order_by("-publish_at")[:3]
|
||||
latest_version = Version.objects.most_recent()
|
||||
context["latest_version"] = latest_version
|
||||
context["featured_library"] = self.get_featured_library(latest_version)
|
||||
context["events"] = self.get_events()
|
||||
if context["events"]:
|
||||
context["num_months"] = len(context["events"])
|
||||
@@ -64,19 +59,6 @@ class HomepageView(TemplateView):
|
||||
|
||||
return dict(sorted_events)
|
||||
|
||||
def get_featured_library(self, latest_version):
|
||||
library = Library.objects.filter(featured=True).first()
|
||||
|
||||
# If we don't have a featured library, return a random library
|
||||
if not library:
|
||||
library = (
|
||||
Library.objects.filter(library_version__version=latest_version)
|
||||
.order_by("?")
|
||||
.first()
|
||||
)
|
||||
|
||||
return library
|
||||
|
||||
|
||||
class ForbiddenView(View):
|
||||
"""
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import structlog
|
||||
from types import SimpleNamespace
|
||||
|
||||
from django.db.models import Count, Exists, OuterRef
|
||||
from django.db.models.functions import Lower
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.urls import reverse
|
||||
|
||||
@@ -7,7 +11,13 @@ from libraries.constants import (
|
||||
MASTER_RELEASE_URL_PATH_STR,
|
||||
DEVELOP_RELEASE_URL_PATH_STR,
|
||||
)
|
||||
from libraries.models import Library
|
||||
from libraries.models import (
|
||||
Commit,
|
||||
CommitAuthor,
|
||||
CommitAuthorEmail,
|
||||
Library,
|
||||
LibraryVersion,
|
||||
)
|
||||
from versions.models import Version
|
||||
|
||||
logger = structlog.get_logger()
|
||||
@@ -70,3 +80,171 @@ class BoostVersionMixin:
|
||||
)
|
||||
# here we hack extra_context into the request so we can access for cookie checks
|
||||
request.extra_context = self.extra_context
|
||||
|
||||
|
||||
class ContributorMixin:
|
||||
"""Mixin to gather a list of all authors, maintainers, and
|
||||
contributors without duplicates.
|
||||
Uses the current Library if on the Library detail view,
|
||||
otherwise grabs a featured library
|
||||
"""
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["latest_version"] = Version.objects.most_recent()
|
||||
|
||||
if hasattr(self, "object") and isinstance(self.object, Library):
|
||||
library = self.object
|
||||
try:
|
||||
library_version = LibraryVersion.objects.get(
|
||||
library=library, version=context["selected_version"]
|
||||
)
|
||||
except LibraryVersion.DoesNotExist:
|
||||
return context
|
||||
else:
|
||||
library_version = self.get_featured_library()
|
||||
context["featured_library"] = library_version
|
||||
|
||||
context["authors"] = self.get_related(library_version, "authors")
|
||||
context["maintainers"] = self.get_related(
|
||||
library_version,
|
||||
"maintainers",
|
||||
exclude_ids=[x.id for x in context["authors"]],
|
||||
)
|
||||
context["author_tag"] = self.get_author_tag(library_version)
|
||||
exclude_maintainer_ids = [
|
||||
x.commitauthor.id
|
||||
for x in context["maintainers"]
|
||||
if getattr(x.commitauthor, "id", None)
|
||||
]
|
||||
exclude_author_ids = [
|
||||
x.commitauthor.id
|
||||
for x in context["authors"]
|
||||
if getattr(x.commitauthor, "id", None)
|
||||
]
|
||||
top_contributors_release = self.get_top_contributors(
|
||||
library_version=library_version,
|
||||
exclude=exclude_maintainer_ids + exclude_author_ids,
|
||||
)
|
||||
context["top_contributors_release_new"] = [
|
||||
x for x in top_contributors_release if x.is_new
|
||||
]
|
||||
context["top_contributors_release_old"] = [
|
||||
x for x in top_contributors_release if not x.is_new
|
||||
]
|
||||
exclude_top_contributor_ids = [x.id for x in top_contributors_release]
|
||||
context["previous_contributors"] = self.get_previous_contributors(
|
||||
library_version,
|
||||
exclude=exclude_maintainer_ids
|
||||
+ exclude_top_contributor_ids
|
||||
+ exclude_author_ids,
|
||||
)
|
||||
return context
|
||||
|
||||
def get_featured_library(self):
|
||||
"""Returns latest LibraryVersion associated with the featured Library"""
|
||||
# If multiple are featured, pick one at random
|
||||
latest_version = Version.objects.most_recent()
|
||||
library = Library.objects.filter(featured=True).order_by("?").first()
|
||||
|
||||
# If we don't have a featured library, return a random library
|
||||
if not library:
|
||||
library = (
|
||||
Library.objects.filter(library_version__version=latest_version)
|
||||
.order_by("?")
|
||||
.first()
|
||||
)
|
||||
if not library:
|
||||
return None
|
||||
libversion = LibraryVersion.objects.filter(
|
||||
library_id=library.id, version=latest_version
|
||||
).first()
|
||||
|
||||
return libversion
|
||||
|
||||
def get_related(self, library_version, relation="maintainers", exclude_ids=None):
|
||||
"""Get the maintainers|authors for the current LibraryVersion.
|
||||
|
||||
Also patches the CommitAuthor onto the user, if a matching email exists.
|
||||
"""
|
||||
if relation == "maintainers":
|
||||
qs = library_version.maintainers.all()
|
||||
elif relation == "authors":
|
||||
qs = library_version.authors.all()
|
||||
else:
|
||||
raise ValueError("relation must be maintainers or authors.")
|
||||
if exclude_ids:
|
||||
qs = qs.exclude(id__in=exclude_ids)
|
||||
qs = list(qs)
|
||||
commit_authors = {
|
||||
author_email.email: author_email
|
||||
for author_email in CommitAuthorEmail.objects.annotate(
|
||||
email_lower=Lower("email")
|
||||
)
|
||||
.filter(email_lower__in=[x.email.lower() for x in qs])
|
||||
.select_related("author")
|
||||
}
|
||||
for user in qs:
|
||||
if author_email := commit_authors.get(user.email.lower(), None):
|
||||
user.commitauthor = author_email.author
|
||||
else:
|
||||
user.commitauthor = SimpleNamespace(
|
||||
github_profile_url="",
|
||||
avatar_url="",
|
||||
display_name=f"{user.display_name}",
|
||||
)
|
||||
return qs
|
||||
|
||||
def get_author_tag(self, library_version):
|
||||
"""Format the authors for the author meta tag in the template."""
|
||||
author_names = list(
|
||||
library_version.library.authors.values_list("display_name", flat=True)
|
||||
)
|
||||
if len(author_names) > 1:
|
||||
final_output = ", ".join(author_names[:-1]) + " and " + author_names[-1]
|
||||
else:
|
||||
final_output = author_names[0] if author_names else ""
|
||||
|
||||
return final_output
|
||||
|
||||
def get_top_contributors(self, library_version=None, exclude=None):
|
||||
if library_version:
|
||||
prev_versions = Version.objects.minor_versions().filter(
|
||||
version_array__lt=library_version.version.cleaned_version_parts_int
|
||||
)
|
||||
qs = CommitAuthor.objects.filter(
|
||||
commit__library_version=library_version
|
||||
).annotate(
|
||||
is_new=~Exists(
|
||||
Commit.objects.filter(
|
||||
author_id=OuterRef("id"),
|
||||
library_version__in=LibraryVersion.objects.filter(
|
||||
version__in=prev_versions, library=library_version.library
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
qs = CommitAuthor.objects.filter(
|
||||
commit__library_version__library=self.object
|
||||
)
|
||||
if exclude:
|
||||
qs = qs.exclude(id__in=exclude)
|
||||
qs = qs.annotate(count=Count("commit")).order_by("-count")
|
||||
return qs
|
||||
|
||||
def get_previous_contributors(self, library_version, exclude=None):
|
||||
library_versions = LibraryVersion.objects.filter(
|
||||
library=library_version.library,
|
||||
version__in=Version.objects.minor_versions().filter(
|
||||
version_array__lt=library_version.version.cleaned_version_parts_int
|
||||
),
|
||||
)
|
||||
qs = (
|
||||
CommitAuthor.objects.filter(commit__library_version__in=library_versions)
|
||||
.annotate(count=Count("commit"))
|
||||
.order_by("-count")
|
||||
)
|
||||
if exclude:
|
||||
qs = qs.exclude(id__in=exclude)
|
||||
return qs
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import datetime
|
||||
from types import SimpleNamespace
|
||||
|
||||
from django.contrib import messages
|
||||
from django.db.models import F, Count, Exists, OuterRef, Prefetch
|
||||
from django.db.models.functions import Lower
|
||||
from django.db.models import F, Count, Prefetch
|
||||
from django.http import Http404
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.utils.decorators import method_decorator
|
||||
@@ -15,12 +13,9 @@ from core.githubhelper import GithubAPIClient
|
||||
from versions.models import Version
|
||||
|
||||
from .constants import README_MISSING
|
||||
from .mixins import VersionAlertMixin, BoostVersionMixin
|
||||
from .mixins import VersionAlertMixin, BoostVersionMixin, ContributorMixin
|
||||
from .models import (
|
||||
Category,
|
||||
Commit,
|
||||
CommitAuthor,
|
||||
CommitAuthorEmail,
|
||||
Library,
|
||||
LibraryVersion,
|
||||
)
|
||||
@@ -208,7 +203,7 @@ class LibraryCategorized(LibraryListBase):
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name="dispatch")
|
||||
class LibraryDetail(VersionAlertMixin, BoostVersionMixin, DetailView):
|
||||
class LibraryDetail(VersionAlertMixin, BoostVersionMixin, ContributorMixin, DetailView):
|
||||
"""Display a single Library in insolation"""
|
||||
|
||||
model = Library
|
||||
@@ -240,40 +235,7 @@ class LibraryDetail(VersionAlertMixin, BoostVersionMixin, DetailView):
|
||||
if library_version
|
||||
else self.object.github_url
|
||||
)
|
||||
context["authors"] = self.get_related(library_version, "authors")
|
||||
context["maintainers"] = self.get_related(
|
||||
library_version,
|
||||
"maintainers",
|
||||
exclude_ids=[x.id for x in context["authors"]],
|
||||
)
|
||||
context["author_tag"] = self.get_author_tag()
|
||||
exclude_maintainer_ids = [
|
||||
x.commitauthor.id
|
||||
for x in context["maintainers"]
|
||||
if getattr(x.commitauthor, "id", None)
|
||||
]
|
||||
exclude_author_ids = [
|
||||
x.commitauthor.id
|
||||
for x in context["authors"]
|
||||
if getattr(x.commitauthor, "id", None)
|
||||
]
|
||||
top_contributors_release = self.get_top_contributors(
|
||||
version=context["selected_version"],
|
||||
exclude=exclude_maintainer_ids + exclude_author_ids,
|
||||
)
|
||||
context["top_contributors_release_new"] = [
|
||||
x for x in top_contributors_release if x.is_new
|
||||
]
|
||||
context["top_contributors_release_old"] = [
|
||||
x for x in top_contributors_release if not x.is_new
|
||||
]
|
||||
exclude_top_contributor_ids = [x.id for x in top_contributors_release]
|
||||
context["previous_contributors"] = self.get_previous_contributors(
|
||||
context["selected_version"],
|
||||
exclude=exclude_maintainer_ids
|
||||
+ exclude_top_contributor_ids
|
||||
+ exclude_author_ids,
|
||||
)
|
||||
|
||||
# Populate the commit graphs
|
||||
context["commit_data_by_release"] = self.get_commit_data_by_release()
|
||||
context["dependency_diff"] = self.get_dependency_diff(library_version)
|
||||
@@ -309,17 +271,6 @@ class LibraryDetail(VersionAlertMixin, BoostVersionMixin, DetailView):
|
||||
for x in reversed(list(qs))
|
||||
]
|
||||
|
||||
def get_author_tag(self):
|
||||
"""Format the authors for the author meta tag in the template."""
|
||||
authors = self.object.authors.all()
|
||||
author_names = [author.display_name for author in authors]
|
||||
if len(author_names) > 1:
|
||||
final_output = ", ".join(author_names[:-1]) + " and " + author_names[-1]
|
||||
else:
|
||||
final_output = author_names[0] if author_names else ""
|
||||
|
||||
return final_output
|
||||
|
||||
def _prepare_commit_data(self, commit_data, data_type):
|
||||
commit_data_list = []
|
||||
for data in commit_data:
|
||||
@@ -345,84 +296,6 @@ class LibraryDetail(VersionAlertMixin, BoostVersionMixin, DetailView):
|
||||
# This should never happen because it should be caught in get_object
|
||||
return self.object.github_url
|
||||
|
||||
def get_related(self, library_version, relation="maintainers", exclude_ids=None):
|
||||
"""Get the maintainers|authors for the current LibraryVersion.
|
||||
|
||||
Also patches the CommitAuthor onto the user, if a matching email exists.
|
||||
"""
|
||||
if relation == "maintainers":
|
||||
qs = library_version.maintainers.all()
|
||||
elif relation == "authors":
|
||||
qs = library_version.authors.all()
|
||||
else:
|
||||
raise ValueError("relation must be maintainers or authors.")
|
||||
if exclude_ids:
|
||||
qs = qs.exclude(id__in=exclude_ids)
|
||||
qs = list(qs)
|
||||
commit_authors = {
|
||||
author_email.email: author_email
|
||||
for author_email in CommitAuthorEmail.objects.annotate(
|
||||
email_lower=Lower("email")
|
||||
)
|
||||
.filter(email_lower__in=[x.email.lower() for x in qs])
|
||||
.select_related("author")
|
||||
}
|
||||
for user in qs:
|
||||
if author_email := commit_authors.get(user.email.lower(), None):
|
||||
user.commitauthor = author_email.author
|
||||
else:
|
||||
user.commitauthor = SimpleNamespace(
|
||||
github_profile_url="",
|
||||
avatar_url="",
|
||||
display_name=f"{user.display_name}",
|
||||
)
|
||||
return qs
|
||||
|
||||
def get_top_contributors(self, version=None, exclude=None):
|
||||
if version:
|
||||
library_version = LibraryVersion.objects.get(
|
||||
library=self.object, version=version
|
||||
)
|
||||
prev_versions = Version.objects.minor_versions().filter(
|
||||
version_array__lt=version.cleaned_version_parts_int
|
||||
)
|
||||
qs = CommitAuthor.objects.filter(
|
||||
commit__library_version=library_version
|
||||
).annotate(
|
||||
is_new=~Exists(
|
||||
Commit.objects.filter(
|
||||
author_id=OuterRef("id"),
|
||||
library_version__in=LibraryVersion.objects.filter(
|
||||
version__in=prev_versions, library=self.object
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
qs = CommitAuthor.objects.filter(
|
||||
commit__library_version__library=self.object
|
||||
)
|
||||
if exclude:
|
||||
qs = qs.exclude(id__in=exclude)
|
||||
qs = qs.annotate(count=Count("commit")).order_by("-count")
|
||||
return qs
|
||||
|
||||
def get_previous_contributors(self, version, exclude=None):
|
||||
library_versions = LibraryVersion.objects.filter(
|
||||
library=self.object,
|
||||
version__in=Version.objects.minor_versions().filter(
|
||||
version_array__lt=version.cleaned_version_parts_int
|
||||
),
|
||||
)
|
||||
qs = (
|
||||
CommitAuthor.objects.filter(commit__library_version__in=library_versions)
|
||||
.annotate(count=Count("commit"))
|
||||
.order_by("-count")
|
||||
)
|
||||
if exclude:
|
||||
qs = qs.exclude(id__in=exclude)
|
||||
return qs
|
||||
|
||||
def get_version(self):
|
||||
"""Get the version of Boost for the library we're currently looking at."""
|
||||
version_slug = self.kwargs.get("version_slug")
|
||||
|
||||
@@ -133,29 +133,22 @@
|
||||
All Libraries <i class="fas fa-chevron-right text-sky-600 dark:text-sky-300 group-hover:text-orange dark:group-hover:text-orange"></i>
|
||||
</a>
|
||||
</div>
|
||||
<h3 class="pb-2 mb-4 text-lg md:text-2xl capitalize border-b border-gray-400 text-orange dark:border-slate"><a href="{% url 'library-detail' library_slug=featured_library.slug version_slug=LATEST_RELEASE_URL_PATH_STR %}" class="link-header">{{ featured_library.name }}</a></h3>
|
||||
<h3 class="pb-2 mb-4 text-lg md:text-2xl capitalize border-b border-gray-400 text-orange dark:border-slate"><a href="{% url 'library-detail' library_slug=featured_library.library.slug version_slug=LATEST_RELEASE_URL_PATH_STR %}" class="link-header">{{ featured_library.library.name }}</a></h3>
|
||||
<span class="pb-1 mx-auto w-full text-sm md:text-base align-left">
|
||||
{{ featured_library.description }}
|
||||
</span>
|
||||
<p class="pb-1 mx-auto w-full text-xs md:text-sm align-left">{% if featured_library.first_boost_version %}Added in {{ featured_library.first_boost_version.display_name }}{% endif %}</p>
|
||||
<div class="py-4">
|
||||
{% if featured_library.authors %}
|
||||
{% if authors or maintainers %}
|
||||
<div class="flex flex-wrap justify-start">
|
||||
{% for author in featured_library.authors.all %}
|
||||
<div class="p-1 md:p-2 w-min text-center flex flex-col items-center justify-center">
|
||||
<div class="bg-gray-300 dark:bg-slate rounded-lg w-[36px] h-[36px]">
|
||||
{% if author.image %}
|
||||
<img src="{{ author.image.url }}"
|
||||
title="{{ author.display_name }}"
|
||||
alt="{{ author.display_name }}"
|
||||
class="rounded-lg w-[36px] h-[36px]" />
|
||||
{% else %}
|
||||
<i class="text-3xl fas fa-user text-white dark:text-white/60" title="{{ author.display_name }}"></i>
|
||||
{% endif %}
|
||||
</div>
|
||||
<span class="text-xs">{{ author.display_name }}</span>
|
||||
</div>
|
||||
<div class="grid md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-y-3 gap-x-2">
|
||||
{% for user in authors %}
|
||||
{% avatar user=user commitauthor=user.commitauthor avatar_type="wide" contributor_label="Author" %}
|
||||
{% endfor %}
|
||||
{% for user in maintainers %}
|
||||
{% avatar user=user commitauthor=user.commitauthor avatar_type="wide" contributor_label="Maintainer" %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user