Skip to content

Commit c6beab1

Browse files
authored
listed top contributors for gsoc2025 with github issue model (OWASP-BLT#3884)
1 parent 271b579 commit c6beab1

File tree

4 files changed

+124
-1
lines changed

4 files changed

+124
-1
lines changed

blt/urls.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@
144144
GitHubIssueDetailView,
145145
GitHubIssuesView,
146146
GithubIssueView,
147+
GsocView,
147148
IssueCreate,
148149
IssueEdit,
149150
IssueView,
@@ -678,7 +679,7 @@
678679
update_lectures_order,
679680
name="update_lectures_order",
680681
),
681-
re_path(r"^gsoc/$", TemplateView.as_view(template_name="gsoc.html"), name="gsoc"),
682+
path("gsoc/", GsocView.as_view(), name="gsoc"),
682683
re_path(
683684
r"^privacypolicy/$",
684685
TemplateView.as_view(template_name="privacy.html"),

website/templates/gsoc.html

+34
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,40 @@ <h3 class="text-xl font-semibold text-red-700 mb-2">OWASP BLT Project Ideas</h3>
207207
</a>
208208
</div>
209209
</div>
210+
<!-- Top Contributors Leaderboard -->
211+
<div class="bg-white rounded-xl shadow-lg hover:shadow-xl transition-all duration-300 p-8 mb-12 border-l-4 border-red-500">
212+
<h2 class="text-3xl font-bold text-gray-900 mb-6">Top Contributors for GSoC 2025 Projects</h2>
213+
<p class="text-gray-700 mb-4">
214+
For transparency, we are implementing a Top contributors List for students this year. This demonstrates our commitment to working students who are contunually contributing while also signaling our intent to other projects and organizations.
215+
</p>
216+
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
217+
{% for project, data in projects.items %}
218+
<div class="bg-gray-50 rounded-lg p-6">
219+
<a href="{{ data.repo_url }}"
220+
target="_blank"
221+
class="text-xl font-semibold text-gray-900 mb-4">
222+
{{ project }} - Total PRs:
223+
<span class="text-red-600 font-bold">{{ data.total_prs }}</span>
224+
</a>
225+
<div class="space-y-3">
226+
{% for contributor in data.contributors %}
227+
<div class="flex items-center justify-between p-2 hover:bg-gray-100 rounded">
228+
<a href="{{ contributor.url }}"
229+
target="_blank"
230+
class="text-gray-700 hover:text-red-600">{{ contributor.username }}</a>
231+
<span class="bg-red-100 text-red-800 text-sm font-medium px-2.5 py-0.5 rounded">
232+
{{ contributor.prs }} PR
233+
{% if contributor.prs != 1 %}s{% endif %}
234+
</span>
235+
</div>
236+
{% empty %}
237+
<p class="text-gray-500 italic">No contributors in this period</p>
238+
{% endfor %}
239+
</div>
240+
</div>
241+
{% endfor %}
242+
</div>
243+
</div>
210244
<!-- Past Events -->
211245
<div class="bg-white rounded-xl shadow-lg hover:shadow-xl transition-all duration-300 p-8 mb-12 border-l-4 border-red-500">
212246
<h2 class="text-3xl font-bold text-gray-900 mb-6">Past GSOC Events</h2>

website/views/constants.py

+16
Original file line numberDiff line numberDiff line change
@@ -568,3 +568,19 @@
568568
"gradle": "gradle",
569569
"ant": "ant",
570570
}
571+
572+
GSOC25_PROJECTS = {
573+
"Bug Logging Tool(BLT)": [
574+
"OWASP-BLT/BLT",
575+
"OWASP-BLT/BLT-Flutter",
576+
"OWASP-BLT/BLT-Bacon",
577+
"OWASP-BLT/BLT-Action",
578+
"OWASP-BLT/BLT-Extension",
579+
],
580+
"NEST": ["OWASP/Nest"],
581+
"NETTACKER": ["OWASP/Nettacker"],
582+
"JUICE-SHOP": ["juice-shop/juice-shop"],
583+
"DevSecOps Maturity Model(DSMM)": ["devsecopsmaturitymodel/DevSecOps-MaturityModel"],
584+
"PYGOAT": ["adeyosemanputra/PyGoat"],
585+
"OpenCRE": ["OWASP/OpenCRE"],
586+
}

website/views/issue.py

+72
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import smtplib
66
import socket
77
import uuid
8+
from collections import defaultdict
89
from datetime import datetime, timezone
910
from urllib.parse import urlparse
1011

@@ -41,6 +42,7 @@
4142
from django.utils.decorators import method_decorator
4243
from django.utils.html import escape
4344
from django.utils.timezone import now
45+
from django.views import View
4446
from django.views.decorators.csrf import csrf_exempt
4547
from django.views.decorators.http import require_POST
4648
from django.views.generic import DetailView, ListView, TemplateView
@@ -79,6 +81,8 @@
7981
safe_redirect_request,
8082
)
8183

84+
from .constants import GSOC25_PROJECTS
85+
8286

8387
@login_required(login_url="/accounts/login")
8488
def like_issue(request, issue_pk):
@@ -2008,3 +2012,71 @@ def page_vote(request):
20082012
return JsonResponse({"status": "error", "message": "An error occurred while processing your vote"})
20092013

20102014
return JsonResponse({"status": "error", "message": "Invalid request method"})
2015+
2016+
2017+
class GsocView(View):
2018+
SINCE_DATE = datetime(2024, 11, 1, tzinfo=timezone.utc)
2019+
2020+
def fetch_model_prs(self, repo_names):
2021+
contributors = defaultdict(lambda: {"count": 0, "github_url": ""})
2022+
total_pr_count = 0
2023+
2024+
# Filter repos by name
2025+
repos = Repo.objects.filter(name__in=[name.split("/")[-1] for name in repo_names])
2026+
2027+
# Fetch merged PRs
2028+
prs = GitHubIssue.objects.filter(
2029+
repo__in=repos, type="pull_request", is_merged=True, merged_at__gte=self.SINCE_DATE
2030+
).select_related("user_profile__user")
2031+
2032+
for pr in prs:
2033+
total_pr_count += 1
2034+
2035+
user_profile = pr.user_profile
2036+
if user_profile:
2037+
github_url = user_profile.github_url
2038+
if github_url and not github_url.endswith("[bot]") and "bot" not in github_url.lower():
2039+
contributors[github_url]["count"] += 1
2040+
contributors[github_url]["github_url"] = github_url
2041+
2042+
# Get top 10 contributors
2043+
top_contributors = sorted(contributors.items(), key=lambda item: item[1]["count"], reverse=True)[:10]
2044+
2045+
# Format top contributors list
2046+
formatted_contributors = [
2047+
{"url": url, "username": url.rstrip("/").split("/")[-1], "prs": data["count"]}
2048+
for url, data in top_contributors
2049+
]
2050+
2051+
return formatted_contributors, total_pr_count
2052+
2053+
def get_repo_url(self, repo_names):
2054+
if not repo_names:
2055+
return ""
2056+
2057+
repo_name = repo_names[0].split("/")[-1]
2058+
try:
2059+
repo = Repo.objects.filter(name=repo_name).first()
2060+
return repo.repo_url if repo else f"https://github.com/{repo_names[0]}"
2061+
except:
2062+
return f"https://github.com/{repo_names[0]}"
2063+
2064+
def build_project_data(self, project, repo_names):
2065+
contributors, total_prs = self.fetch_model_prs(repo_names)
2066+
2067+
return {
2068+
"contributors": contributors,
2069+
"total_prs": total_prs,
2070+
"repo_url": self.get_repo_url(repo_names),
2071+
}
2072+
2073+
def get(self, request):
2074+
project_data = {}
2075+
2076+
for project, repo_names in GSOC25_PROJECTS.items():
2077+
project_data[project] = self.build_project_data(project, repo_names)
2078+
2079+
# Sort projects by total PRs
2080+
sorted_project_data = dict(sorted(project_data.items(), key=lambda item: item[1]["total_prs"], reverse=True))
2081+
2082+
return render(request, "gsoc.html", {"projects": sorted_project_data})

0 commit comments

Comments
 (0)