diff --git a/blt/urls.py b/blt/urls.py index bd7306675..0e71b09a3 100644 --- a/blt/urls.py +++ b/blt/urls.py @@ -181,6 +181,7 @@ ProjectListView, ProjectsDetailView, ProjectView, + RepoDetailView, blt_tomato, create_project, distribute_bacon, @@ -591,6 +592,7 @@ re_path(r"^api/v1/contributors/$", contributors, name="api_contributor"), path("project//", ProjectDetailView.as_view(), name="project_view"), path("projects//badge/", ProjectBadgeView.as_view(), name="project-badge"), + path("repository//", RepoDetailView.as_view(), name="repo_detail"), re_path(r"^report-ip/$", ReportIpView.as_view(), name="report_ip"), re_path(r"^reported-ips/$", ReportedIpListView.as_view(), name="reported_ips_list"), re_path(r"^feed/$", feed, name="feed"), diff --git a/website/templates/projects/project_detail.html b/website/templates/projects/project_detail.html index ef40d22b4..32a038e31 100644 --- a/website/templates/projects/project_detail.html +++ b/website/templates/projects/project_detail.html @@ -1,7 +1,9 @@ {% extends "base.html" %} {% load humanize %} {% load static %} -{% block title %}{{ project.name }} - Project Details{% endblock %} +{% block title %} + {{ project.name }} - Project Details +{% endblock title %} {% block content %} {% include "includes/sidenav.html" %}
@@ -164,14 +166,15 @@

Associated Repositories

{% for repo in repositories %}
- + {% if repo.description %}

{{ repo.description }}

{% endif %}
diff --git a/website/templates/projects/project_list.html b/website/templates/projects/project_list.html index 11c588703..20707bc16 100644 --- a/website/templates/projects/project_list.html +++ b/website/templates/projects/project_list.html @@ -177,7 +177,8 @@

{{ project.
- +

{{ repo.description|default:"No description available."|truncatechars:150 }} diff --git a/website/templates/projects/repo_detail.html b/website/templates/projects/repo_detail.html new file mode 100644 index 000000000..36c55a5f5 --- /dev/null +++ b/website/templates/projects/repo_detail.html @@ -0,0 +1,527 @@ +{% extends "base.html" %} +{% load humanize %} +{% load static %} +{% block title %}{{ repo.name }} - Repo Details{% endblock %} +{% block content %} + {% include "includes/sidenav.html" %} + +

+
+ +
+
+
+
+ {% if repo.logo_url %} + {{ repo.name }} logo + {% endif %} +
+

{{ repo.name }}

+
+ + + + + + Back to {{ repo.project.name }} + + {% if repo.project.organization %} + | +
organization : {{ repo.project.organization.name }}
+ {% endif %} +
+
+
+

{{ repo.description|default:"No description available." }}

+
+ +
+ {% if repo.homepage_url %} + + + + + Visit Homepage + + {% endif %} + + + + + View on GitHub + +
+
+ +
+
+ {% if repo.is_main %} + + + + + Main Repository + + {% elif repo.is_wiki %} + + + + + Wiki Repository + + {% else %} + + + + + Standard Repository + + {% endif %} + {% if repo.tags.all %} +
+ Tags: + {% for tag in repo.tags.all %} + {{ tag.name }} + {% endfor %} +
+ {% endif %} +
+
+
Created {{ repo.created|date:"M d, Y" }}
+
Updated {{ repo.last_updated|naturaltime }}
+
+
+ +
+
+
+ Stars + + + +
+

{{ repo.stars|intcomma }}

+
+
+
+ Forks + + + +
+

{{ repo.forks|intcomma }}

+
+
+
+ Watchers + + + + +
+

{{ repo.watchers|intcomma }}

+
+
+
+ Network + + + +
+

{{ repo.network_count|intcomma }}

+
+
+
+ Subscribers + + + +
+

{{ repo.subscribers_count|intcomma }}

+
+
+
+ +
+ +
+ +
+
+

+ + + + Activity Metrics +

+
+ +
+
+
+
+
+

Issues

+ + + + + +
+
+
+ Open + {{ repo.open_issues|intcomma }} +
+
+ Closed + {{ repo.closed_issues|intcomma }} +
+
+
+
+ Total + {{ repo.total_issues|intcomma }} +
+
+
+
+ +
+
+
+
+
+

Pull Requests

+ + + + + +
+
+
+
{{ repo.open_pull_requests|intcomma }}
+
Open PRs
+
+
+
+
+ +
+
+
+
+
+

Commits

+ + + + + +
+
+
+
{{ repo.commit_count|intcomma }}
+
Total Commits
+
+
+ + + + Last: {{ repo.last_commit_date|date:"M d, Y" }} +
+
+
+
+
+
+
+ +
+
+

+ + + + Community +

+
+ +
+

Top Contributors

+ + {{ repo.contributor_count|intcomma }} total contributors + +
+ +
+ {% for contributor in top_contributors %} +
+ {{ contributor.name }} +
+
+ {{ contributor.name }} + {% if contributor.verified %}{% endif %} +
+
{{ contributor.contributions|intcomma }} commits
+
+ + + + + +
+ {% endfor %} +
+ + {% if repo.contributor_count > 6 %} +
+
+
+ {% for contributor in repo.contributor.all|dictsortreversed:"contributions"|slice:"6:11" %} + {{ contributor.name }} + {% endfor %} + {% if repo.contributor_count > 11 %} +
+ +{{ repo.contributor_count|add:"-11" }} +
+ {% endif %} +
+ + View all contributors + + + + +
+
+ {% endif %} +
+
+
+ +
+
+

+ + + + Technical Overview +

+
+ +
+

+ + + + Language & Stack +

+
+
+ Primary Language: + + {{ repo.primary_language|default:"Not specified" }} + +
+
+ Repository Size: + + {{ repo.size|filesizeformat }} + +
+
+ License: +
+ {{ repo.license|default:"Not specified" }} +
+
+
+
+ +
+

+ + + + Latest Release +

+ {% if repo.release_name or repo.release_datetime %} +
+ {% if repo.release_name %} +
+ Version + + {{ repo.release_name }} + +
+ {% endif %} + {% if repo.release_datetime %} +
+ Release Date + + {{ repo.release_datetime|date:"M d, Y" }} + +
+ {% endif %} +
+ Last Commit + + {{ repo.last_commit_date|date:"M d, Y" }} + +
+
+ {% else %} +
No release information available
+ {% endif %} +
+
+
+
+
+ + +
+
+{% endblock %} diff --git a/website/views/project.py b/website/views/project.py index f2bb55509..e53061582 100644 --- a/website/views/project.py +++ b/website/views/project.py @@ -830,3 +830,98 @@ def get(self, request, *args, **kwargs): response = super().get(request, *args, **kwargs) return response + + +class RepoDetailView(DetailView): + model = Repo + template_name = "projects/repo_detail.html" + context_object_name = "repo" + + def get_github_top_contributors(self, repo_url): + """Fetch top contributors directly from GitHub API""" + try: + # Extract owner/repo from GitHub URL + owner_repo = repo_url.rstrip("/").split("github.com/")[-1] + api_url = f"https://api.github.com/repos/{owner_repo}/contributors?per_page=6" + + headers = { + "Authorization": f"token {settings.GITHUB_TOKEN}", + "Accept": "application/vnd.github.v3+json", + } + + response = requests.get(api_url, headers=headers) + if response.status_code == 200: + return response.json() + return [] + except Exception as e: + print(f"Error fetching GitHub contributors: {e}") + return [] + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + repo = self.get_object() + + # Get other repos from same project + context["related_repos"] = ( + Repo.objects.filter(project=repo.project) + .exclude(id=repo.id) + .select_related("project")[:5] + ) + + # Get top contributors from GitHub + github_contributors = self.get_github_top_contributors(repo.repo_url) + + if github_contributors: + # Match by github_id instead of username + github_ids = [str(c["id"]) for c in github_contributors] + verified_contributors = repo.contributor.filter( + github_id__in=github_ids + ).select_related() + + # Create a mapping of github_id to database contributor + contributor_map = {str(c.github_id): c for c in verified_contributors} + + # Merge GitHub and database data + merged_contributors = [] + for gh_contrib in github_contributors: + gh_id = str(gh_contrib["id"]) + db_contrib = contributor_map.get(gh_id) + if db_contrib: + merged_contributors.append( + { + "name": db_contrib.name, + "github_id": db_contrib.github_id, + "avatar_url": db_contrib.avatar_url, + "contributions": gh_contrib["contributions"], + "github_url": db_contrib.github_url, + "verified": True, + } + ) + else: + merged_contributors.append( + { + "name": gh_contrib["login"], + "github_id": gh_contrib["id"], + "avatar_url": gh_contrib["avatar_url"], + "contributions": gh_contrib["contributions"], + "github_url": gh_contrib["html_url"], + "verified": False, + } + ) + + context["top_contributors"] = merged_contributors + else: + # Fallback to database contributors if GitHub API fails + context["top_contributors"] = [ + { + "name": c.name, + "github_id": c.github_id, + "avatar_url": c.avatar_url, + "contributions": c.contributions, + "github_url": c.github_url, + "verified": True, + } + for c in repo.contributor.all()[:6] + ] + + return context