|
1 | 1 | 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 |
2 | 6 | from django.shortcuts import get_object_or_404
|
3 | 7 | from django.urls import reverse
|
4 | 8 |
|
|
7 | 11 | MASTER_RELEASE_URL_PATH_STR,
|
8 | 12 | DEVELOP_RELEASE_URL_PATH_STR,
|
9 | 13 | )
|
10 |
| -from libraries.models import Library |
| 14 | +from libraries.models import ( |
| 15 | + Commit, |
| 16 | + CommitAuthor, |
| 17 | + CommitAuthorEmail, |
| 18 | + Library, |
| 19 | + LibraryVersion, |
| 20 | +) |
11 | 21 | from versions.models import Version
|
12 | 22 |
|
13 | 23 | logger = structlog.get_logger()
|
@@ -70,3 +80,171 @@ def set_extra_context(self, request):
|
70 | 80 | )
|
71 | 81 | # here we hack extra_context into the request so we can access for cookie checks
|
72 | 82 | 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