+
diff --git a/web/templates/web/contributor_detail.html b/web/templates/web/contributor_detail.html
deleted file mode 100644
index a24a42997..000000000
--- a/web/templates/web/contributor_detail.html
+++ /dev/null
@@ -1,414 +0,0 @@
-{% extends "base.html" %}
-
-{% block content %}
-
-
-
- Contributor Details: {{ user.login }}
-
-
-
-
-
-

-
-
-
-
-
-
-
-
Reactions
-
{{ user.reactions_received|default:"0" }}
-
-
⭐
-
-
-
-
Followers
-
{{ user.followers|default:"0" }}
-
-
👥
-
-
-
-
Mentorship
-
{{ user.mentorship_score|default:"0" }}
-
-
🤝
-
-
-
-
Collaboration
-
{{ user.collaboration_score|default:"0" }}
-
-
🔗
-
-
-
-
-
-
Contribution Analytics
-
-
-
-
-
-
-
-
-
-
-
Code Contributions
-
-
-
-
-
-
-
-
-
Recent Activity
-
-
-
- -
-
-
-
-
-
- First contribution on
- {{ first_contribution_date|default:"N/A" }}
-
-
-
-
-
-
- -
-
-
-
-
-
- Completed
- {{ user.issue_assignments|default:"0" }}
- issue assignments
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-{% endblock content %}
diff --git a/web/templates/web/contributors_list.html b/web/templates/web/contributors_list.html
deleted file mode 100644
index 8962da2f9..000000000
--- a/web/templates/web/contributors_list.html
+++ /dev/null
@@ -1,251 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}
- GitHub Contributors - Alpha One Education
-{% endblock title %}
-{% block content %}
-
-
-
-
-
GitHub Contributors
-
- Celebrating the amazing people who contribute to our open source projects.
-
-
-
-
- View on GitHub
-
-
-
-
-
- Smart Contributor Ranking
-
-
- Our contributor ranking system weighs multiple factors to provide a fair assessment of contribution value:
-
-
-
-
- Merged PRs
-
-
- Merged pull requests have the highest weight as they represent accepted contributions.
-
-
-
-
- Balanced Contributions
-
-
- We value contributors who maintain a healthy ratio of merged PRs to total PRs.
-
-
-
-
- Quality Factors
-
-
- Too many closed (non-merged) or open PRs may indicate quality issues or unfinished work.
-
-
-
-
-
-
-
-
{{ contributors|length }}
-
Contributors
-
-
-
- {% if contributors %}
- {% with total=0 %}
- {% for contributor in contributors %}
- {% with total=total|add:contributor.merged_pr_count %}{% endwith %}
- {% endfor %}
- {{ total }}
- {% endwith %}
- {% else %}
- 0
- {% endif %}
-
-
Merged PRs
-
-
-
- {% if contributors %}
- {% with total=0 %}
- {% for contributor in contributors %}
- {% with total=total|add:contributor.open_pr_count %}{% endwith %}
- {% endfor %}
- {{ total }}
- {% endwith %}
- {% else %}
- 0
- {% endif %}
-
-
Open PRs
-
-
-
- {% if contributors %}
- {% with total=0 %}
- {% for contributor in contributors %}
- {% with total=total|add:contributor.closed_pr_count %}{% endwith %}
- {% endfor %}
- {{ total }}
- {% endwith %}
- {% else %}
- 0
- {% endif %}
-
-
Closed PRs
-
-
-
-
-
-
All Contributors
-
Sorted by contribution quality score
-
-
- {% for contributor in contributors %}
-
-
-
-
-
-
-
-
-
-
-
- Score: {{ contributor.smart_score|floatformat:1 }}
-
-
-
- Ratio: {{ contributor.contribution_ratio|floatformat:2 }}
-
-
-
- Details
-
-
-
-
-
-
-
-
- {% empty %}
-
-
-
No contributors found
-
We couldn't retrieve the contributor list at this time.
-
-
-
- Refresh
-
-
-
- {% endfor %}
-
-
-
-
-
-
Want to contribute?
-
- Join our community of contributors and help us build the future of education. No contribution is too small!
-
-
-
- Browse Open Issues
-
-
-
-
-{% endblock content %}
diff --git a/web/urls.py b/web/urls.py
index 3fb4e298a..3d511d255 100644
--- a/web/urls.py
+++ b/web/urls.py
@@ -482,9 +482,6 @@
path("features/", features_page, name="features"),
path("features/vote/", feature_vote, name="feature_vote"),
path("features/vote-count/", feature_vote_count, name="feature_vote_count"),
- # Contributors
- path("contributors/", views.contributors_list_view, name="contributors_list_view"),
- path("contributors/
/", views.contributor_detail_view, name="contributor_detail"),
# Membership URLs
path("membership/checkout//", views.membership_checkout, name="membership_checkout"),
path(
diff --git a/web/views.py b/web/views.py
index b4d485749..6afa45093 100644
--- a/web/views.py
+++ b/web/views.py
@@ -7523,9 +7523,6 @@ def map_data_api(request):
return JsonResponse({"sessions": map_data})
-GITHUB_REPO = "alphaonelabs/alphaonelabs-education-website"
-GITHUB_API_BASE = "https://api.github.com"
-
logger = logging.getLogger(__name__)
@@ -7546,230 +7543,6 @@ def github_api_request(endpoint, params=None, headers=None):
return {}
-def get_user_contribution_metrics(username, token):
- """
- Use the GitHub GraphQL API to fetch all of the user's merged PRs (across all repos),
- then filter by the target repo, implementing pagination to ensure complete data.
- """
- graphql_endpoint = "https://api.github.com/graphql"
- headers = {"Authorization": f"Bearer {token}"}
-
- query = """
- query($username: String!, $after: String) {
- user(login: $username) {
- pullRequests(
- first: 100
- after: $after
- states: MERGED
- orderBy: { field: CREATED_AT, direction: DESC }
- ) {
- totalCount
- pageInfo {
- endCursor
- hasNextPage
- }
- nodes {
- additions
- deletions
- createdAt
- repository {
- nameWithOwner
- }
- }
- }
- }
- }
- """
-
- all_filtered_prs = []
- after_cursor = None
-
- while True:
- variables = {"username": username, "after": after_cursor}
- try:
- response = requests.post(
- graphql_endpoint, json={"query": query, "variables": variables}, headers=headers, timeout=15
- )
- if response.status_code == 200:
- data = response.json()
- if data.get("data") and data["data"].get("user"):
- pr_data = data["data"]["user"]["pullRequests"]
- prs = pr_data["nodes"]
-
- # Filter PRs to the target repository (case insensitive)
- target_repo = GITHUB_REPO.lower()
- filtered_prs = [pr for pr in prs if pr["repository"]["nameWithOwner"].lower() == target_repo]
- all_filtered_prs.extend(filtered_prs)
-
- # Check if there are more pages
- if pr_data["pageInfo"]["hasNextPage"]:
- after_cursor = pr_data["pageInfo"]["endCursor"]
- else:
- break
- else:
- logger.error("No user or PR data found in GraphQL response.")
- break
- else:
- logger.error(f"GraphQL error: {response.status_code} - {response.text}")
- break
- except Exception as e:
- logger.error(f"GraphQL request error: {e}")
- break
-
- # Prepare final result structure
- final_result = {
- "data": {"user": {"pullRequests": {"totalCount": len(all_filtered_prs), "nodes": all_filtered_prs}}}
- }
- return final_result
-
-
-def contributor_detail_view(request, username):
- """
- View to display detailed information about a specific GitHub contributor.
- Only accessible to staff members.
- """
- cache_key = f"github_contributor_{username}"
- cached_data = cache.get(cache_key)
- if cached_data:
- return render(request, "web/contributor_detail.html", cached_data)
-
- token = os.environ.get("GITHUB_TOKEN")
- headers = {"Authorization": f"token {token}"} if token else {}
-
- # Initialize variables to store contributor data
- user_data = {}
- prs_created = 0
- prs_merged = 0
- issues_created = 0
- pr_reviews = 0
- pr_comments = 0
- issue_comments = 0
- lines_added = 0
- lines_deleted = 0
- first_contribution_date = "N/A"
- issue_assignments = 0
-
- user_endpoint = f"{GITHUB_API_BASE}/users/{username}"
- user_data = github_api_request(user_endpoint, headers=headers)
- if not user_data:
- logger.error("User profile data could not be retrieved.")
-
- # Pull requests created
- prs_created_json = github_api_request(
- f"{GITHUB_API_BASE}/search/issues",
- params={"q": f"author:{username} type:pr repo:{GITHUB_REPO}"},
- headers=headers,
- )
- prs_created = prs_created_json.get("total_count", 0)
-
- # Pull requests merged
- prs_merged_json = github_api_request(
- f"{GITHUB_API_BASE}/search/issues",
- params={"q": f"author:{username} type:pr repo:{GITHUB_REPO} is:merged"},
- headers=headers,
- )
- prs_merged = prs_merged_json.get("total_count", 0)
-
- # Issues created
- issues_json = github_api_request(
- f"{GITHUB_API_BASE}/search/issues",
- params={"q": f"author:{username} type:issue repo:{GITHUB_REPO}"},
- headers=headers,
- )
- issues_created = issues_json.get("total_count", 0)
-
- # Pull request reviews
- reviews_json = github_api_request(
- f"{GITHUB_API_BASE}/search/issues",
- params={"q": f"reviewer:{username} type:pr repo:{GITHUB_REPO}"},
- headers=headers,
- )
- pr_reviews = reviews_json.get("total_count", 0)
-
- # Pull requests with comments
- pr_comments_json = github_api_request(
- f"{GITHUB_API_BASE}/search/issues",
- params={"q": f"commenter:{username} type:pr repo:{GITHUB_REPO}"},
- headers=headers,
- )
- pr_comments = pr_comments_json.get("total_count", 0)
-
- # Issues with comments
- issue_comments_json = github_api_request(
- f"{GITHUB_API_BASE}/search/issues",
- params={"q": f"commenter:{username} type:issue repo:{GITHUB_REPO}"},
- headers=headers,
- )
- issue_comments = issue_comments_json.get("total_count", 0)
-
- # Oldest PR creation date
- prs_oldest = github_api_request(
- f"{GITHUB_API_BASE}/search/issues",
- params={
- "q": f"author:{username} type:pr repo:{GITHUB_REPO}",
- "sort": "created",
- "order": "asc",
- "per_page": 1,
- },
- headers=headers,
- )
- if prs_oldest.get("total_count", 0) > 0:
- first_contribution_date = prs_oldest["items"][0].get("created_at", "N/A")
-
- # Issue assignments
- issue_assignments_json = github_api_request(
- f"{GITHUB_API_BASE}/search/issues",
- params={"q": f"assignee:{username} type:issue repo:{GITHUB_REPO}"},
- headers=headers,
- )
- issue_assignments = issue_assignments_json.get("total_count", 0)
- metrics = get_user_contribution_metrics(username, token)
- pr_data = metrics.get("data", {}).get("user", {}).get("pullRequests", {}).get("nodes", [])
- for pr in pr_data:
- lines_added += pr.get("additions", 0)
- lines_deleted += pr.get("deletions", 0)
-
- # Update user_data with additional metrics
- user_data.update(
- {
- "reactions_received": user_data.get("reactions_received", 0),
- "mentorship_score": user_data.get("mentorship_score", 0),
- "collaboration_score": user_data.get("collaboration_score", 0),
- "issue_assignments": issue_assignments,
- }
- )
-
- # Prepare context for the template
- context = {
- "user": user_data,
- "prs_created": prs_created,
- "prs_merged": prs_merged,
- "pr_reviews": pr_reviews,
- "issues_created": issues_created,
- "issue_comments": issue_comments,
- "pr_comments": pr_comments,
- "lines_added": lines_added,
- "lines_deleted": lines_deleted,
- "first_contribution_date": first_contribution_date,
- "chart_data": {
- "prs_created": prs_created,
- "prs_merged": prs_merged,
- "pr_reviews": pr_reviews,
- "issues_created": issues_created,
- "issue_assignments": issue_assignments,
- "pr_comments": pr_comments,
- "issue_comments": issue_comments,
- "lines_added": lines_added,
- "lines_deleted": lines_deleted,
- "first_contribution_date": (first_contribution_date if first_contribution_date != "N/A" else "N/A"),
- },
- }
-
- # Cache for 1 hour
- cache.set(cache_key, context, 3600)
- return render(request, "web/contributor_detail.html", context)
-
-
@login_required
def all_study_groups(request):
"""Display all study groups across courses."""
@@ -8354,142 +8127,6 @@ def topic_detail(request, pk):
return render(request, "web/forum/topic.html", context)
-def contributors_list_view(request):
- # Check if cached data is available
- cached_context = cache.get("contributors_context")
- if cached_context:
- return render(request, "web/contributors_list.html", cached_context)
-
- # Initialize a dictionary to track contributor stats
- contributor_stats = {}
-
- # Function to add a contributor to our stats dictionary
- def add_contributor(username, avatar_url, profile_url):
- if username not in contributor_stats:
- contributor_stats[username] = {
- "username": username,
- "avatar_url": avatar_url,
- "profile_url": profile_url,
- "merged_pr_count": 0,
- "closed_pr_count": 0,
- "open_pr_count": 0,
- "total_pr_count": 0,
- "prs_url": f"https://github.com/AlphaOneLabs/education-website/pulls?q=is:pr+author:{username}",
- }
-
- try:
- # Fetch closed PRs first (includes both merged and non-merged closed PRs)
- closed_prs = []
- for page in range(1, 11): # Limit to 10 pages to prevent hitting API rate limits
- response = github_api_request(
- f"{GITHUB_API_BASE}/repos/AlphaOneLabs/education-website/pulls",
- params={"state": "closed", "per_page": 100, "page": page},
- )
- if not response or len(response) == 0:
- break
-
- closed_prs.extend(response)
- time.sleep(0.5) # Add delay to avoid hitting rate limits
-
- # Process closed PRs
- for pr in closed_prs:
- username = pr["user"]["login"]
-
- # Skip bots and specific users
- if "[bot]" in username or "dependabot" in username or username == "A1L13N":
- continue
-
- avatar_url = pr["user"]["avatar_url"]
- profile_url = pr["user"]["html_url"]
-
- # Add to our tracking
- add_contributor(username, avatar_url, profile_url)
-
- # Update the appropriate count based on whether it was merged
- if pr["merged_at"]:
- contributor_stats[username]["merged_pr_count"] += 1
- else:
- contributor_stats[username]["closed_pr_count"] += 1
-
- # Now fetch open PRs
- open_prs = []
- for page in range(1, 6): # Limit to 5 pages for open PRs
- response = github_api_request(
- f"{GITHUB_API_BASE}/repos/AlphaOneLabs/education-website/pulls",
- params={"state": "open", "per_page": 100, "page": page},
- )
- if not response or len(response) == 0:
- break
-
- open_prs.extend(response)
- time.sleep(0.5) # Add delay to avoid hitting rate limits
-
- # Process open PRs
- for pr in open_prs:
- username = pr["user"]["login"]
-
- # Skip bots and specific users
- if "[bot]" in username or "dependabot" in username or username == "A1L13N":
- continue
-
- avatar_url = pr["user"]["avatar_url"]
- profile_url = pr["user"]["html_url"]
-
- # Add to our tracking
- add_contributor(username, avatar_url, profile_url)
-
- # Update open PR count
- contributor_stats[username]["open_pr_count"] += 1
-
- # Calculate total PR count and filter out users with no merged PRs
- contributors = []
- for username, stats in contributor_stats.items():
- # Skip contributors with no merged PRs
- if stats["merged_pr_count"] == 0:
- continue
-
- # Calculate total PR count
- stats["total_pr_count"] = stats["merged_pr_count"] + stats["closed_pr_count"] + stats["open_pr_count"]
-
- # Calculate a smart score that prioritizes merged PRs but penalizes imbalances
- # Formula: (merged_pr_count * 10) - penalties for imbalanced contributions
- smart_score = stats["merged_pr_count"] * 10
-
- # Penalize if closed PRs are more than half of merged PRs (could indicate issues with code quality)
- if stats["closed_pr_count"] > (stats["merged_pr_count"] / 2):
- smart_score -= (stats["closed_pr_count"] - (stats["merged_pr_count"] / 2)) * 2
-
- # Penalize if open PRs are more than merged PRs (could indicate abandonment issues)
- if stats["open_pr_count"] > stats["merged_pr_count"]:
- smart_score -= stats["open_pr_count"] - stats["merged_pr_count"]
-
- # Calculate a contribution ratio: merged/(total) - higher is better
- if stats["total_pr_count"] > 0:
- stats["contribution_ratio"] = stats["merged_pr_count"] / stats["total_pr_count"]
- else:
- stats["contribution_ratio"] = 0
-
- # Store the smart score
- stats["smart_score"] = smart_score
-
- contributors.append(stats)
-
- # Sort by smart score (primary) and then by merged PR count (secondary)
- contributors.sort(key=lambda x: (x["smart_score"], x["merged_pr_count"]), reverse=True)
-
- # Store the context in cache for 12 hours
- context = {"contributors": contributors}
- cache.set("contributors_context", context, 12 * 60 * 60)
-
- return render(request, "web/contributors_list.html", context)
-
- except Exception as e:
- # Log the error
- print(f"Error fetching contributors: {e}")
- # Return an empty list in case of error
- return render(request, "web/contributors_list.html", {"contributors": []})
-
-
@login_required
def video_request_list(request):
"""View for listing video requests with optional category filtering."""