From 0e9cf90ae3616cc4210de2f17af0dc2e21685a46 Mon Sep 17 00:00:00 2001 From: DonnieBLT <128622481+DonnieBLT@users.noreply.github.com> Date: Sun, 10 Nov 2024 00:47:09 -0500 Subject: [PATCH] Refactor project and contribution models: remove and re-add github_username field, update project fields, and clean up templates --- website/admin.py | 9 +- website/class_views.py | 127 ++++++---- .../commands/fetch_contributor_stats.py | 167 +++++++------- .../management/commands/update_projects.py | 79 +++++-- .../0144_delete_contributorstats_and_more.py | 72 ++++++ ...github_username_alter_contribution_user.py | 30 +++ ...146_remove_contribution_github_username.py | 16 ++ .../0147_contribution_github_username.py | 17 ++ ...st_commit_date_project_license_and_more.py | 37 +++ ...osed_issues_project_created_at_and_more.py | 47 ++++ website/models.py | 71 ++++-- website/templates/contributor_stats.html | 12 +- website/templates/projects.html | 1 - website/templates/website/project_detail.html | 218 +++++++++++++----- website/templates/website/project_list.html | 34 +-- 15 files changed, 687 insertions(+), 250 deletions(-) create mode 100644 website/migrations/0144_delete_contributorstats_and_more.py create mode 100644 website/migrations/0145_contribution_github_username_alter_contribution_user.py create mode 100644 website/migrations/0146_remove_contribution_github_username.py create mode 100644 website/migrations/0147_contribution_github_username.py create mode 100644 website/migrations/0148_project_last_commit_date_project_license_and_more.py create mode 100644 website/migrations/0149_project_closed_issues_project_created_at_and_more.py delete mode 100644 website/templates/projects.html diff --git a/website/admin.py b/website/admin.py index 04a35fabc..4c3814cac 100644 --- a/website/admin.py +++ b/website/admin.py @@ -13,7 +13,6 @@ Company, CompanyAdmin, Contribution, - ContributorStats, Domain, Hunt, HuntPrize, @@ -408,16 +407,14 @@ class TimeLogAdmin(admin.ModelAdmin): ) -class ContributionAdmin(admin.ModelAdmin): # Added class +class ContributionAdmin(admin.ModelAdmin): list_display = ("user", "title", "description", "status", "created", "txid") list_filter = ["status", "user"] search_fields = ["title", "description", "user__username"] date_hierarchy = "created" -# Register all models with their respective admin classes admin.site.register(Project, ProjectAdmin) -admin.site.register(ContributorStats) admin.site.register(Bid, BidAdmin) admin.site.register(UserProfile, UserProfileAdmin) admin.site.register(User, UserAdmin) @@ -438,9 +435,7 @@ class ContributionAdmin(admin.ModelAdmin): # Added class admin.site.register(Suggestion, SuggestionAdmin) admin.site.register(SuggestionVotes, SuggestionVotesAdmin) admin.site.register(TimeLog, TimeLogAdmin) -admin.site.register(Contribution, ContributionAdmin) # Added registration - -# Register missing models +admin.site.register(Contribution, ContributionAdmin) admin.site.register(InviteFriend) admin.site.register(IP, IPAdmin) admin.site.register(Transaction) diff --git a/website/class_views.py b/website/class_views.py index d026345f6..f01eb502e 100644 --- a/website/class_views.py +++ b/website/class_views.py @@ -29,7 +29,7 @@ from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator # import Min -from django.db.models import Count, Max, Q, Sum +from django.db.models import Count, Q, Sum from django.db.models.functions import ExtractMonth from django.db.transaction import atomic from django.http import ( @@ -65,7 +65,6 @@ CompanyAdmin, Contribution, Contributor, - ContributorStats, Domain, Hunt, HuntPrize, @@ -99,16 +98,21 @@ class ProjectDetailView(DetailView): model = Project def post(self, request, *args, **kwargs): - if "update_project" in request.POST: - from django.core.management import call_command + from django.core.management import call_command + + project = self.get_object() - project = self.get_object() # Use get_object() to retrieve the current object + if "refresh_stats" in request.POST: call_command("update_projects", "--project_id", project.pk) - # extract only the org and repo + messages.success(request, f"Refreshing stats for {project.name}") + + elif "refresh_contributors" in request.POST: owner_repo = project.github_url.rstrip("/").split("/")[-2:] - call_command("fetch_contributor_stats", "--repo", owner_repo[0] + "/" + owner_repo[1]) - messages.success(request, "Requested refresh to the " + project.name + " project") - return redirect("project_view", slug=project.slug) + repo = f"{owner_repo[0]}/{owner_repo[1]}" + call_command("fetch_contributor_stats", "--repo", repo) + messages.success(request, f"Refreshing contributors for {project.name}") + + return redirect("project_view", slug=project.slug) def get(self, request, *args, **kwargs): project = self.get_object() @@ -176,12 +180,22 @@ def get_context_data(self, **kwargs): return context def post(self, request, *args, **kwargs): - if "update_projects" in request.POST: - print("Updating projects") - from django.core.management import call_command # Add this import + if "refresh_stats" in request.POST: + from django.core.management import call_command call_command("update_projects") - messages.success(request, "Requested refresh to projects") + messages.success(request, "Refreshing project statistics...") + return redirect("project_list") + + if "refresh_contributors" in request.POST: + from django.core.management import call_command + + projects = Project.objects.all() + for project in projects: + owner_repo = project.github_url.rstrip("/").split("/")[-2:] + repo = f"{owner_repo[0]}/{owner_repo[1]}" + call_command("fetch_contributor_stats", "--repo", repo) + messages.success(request, "Refreshing contributor data...") return redirect("project_list") form = GitHubURLForm(request.POST) @@ -548,49 +562,70 @@ def post(self, request, *args, **kwargs): return JsonResponse({"status": "There was some error"}) +from datetime import datetime, timedelta + +from django.utils import timezone + + class ContributorStatsView(TemplateView): template_name = "contributor_stats.html" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - self.period = self.request.GET.get("period", "day") - - end_date = datetime.now().date() - if self.period == "day": - start_date = end_date - timedelta(days=1) - elif self.period == "week": - start_date = end_date - timedelta(days=7) - elif self.period == "year": - start_date = end_date - timedelta(days=365) - else: - start_date = end_date - timedelta(days=1) - stats = ContributorStats.objects.filter(github_date__range=[start_date, end_date]) + # Get date range using Django's timezone + end_date = timezone.now() + display_end_date = end_date.date() - latest_date = stats.aggregate(Max("github_date"))["github_date__max"] - display_end_date = latest_date if latest_date and latest_date < end_date else end_date + # Calculate start date + self.period = self.request.GET.get("period", "30") + days = int(self.period) + start_date = end_date - timedelta(days=days) + start_date = start_date.date() + + # Query contributions + contributions = Contribution.objects.filter( + created__date__gte=start_date, created__date__lte=display_end_date + ) + # Aggregate stats by GitHub username user_stats = {} - for stat in stats: + + for contribution in contributions: + username = contribution.github_username + if username not in user_stats: + user_stats[username] = { + "commits": 0, + "issues_opened": 0, + "issues_closed": 0, + "prs": 0, + "comments": 0, + "total": 0, + } + + # Add stats + if contribution.contribution_type == "commit": + user_stats[username]["commits"] += 1 + elif contribution.contribution_type == "issue_opened": + user_stats[username]["issues_opened"] += 1 + elif contribution.contribution_type == "issue_closed": + user_stats[username]["issues_closed"] += 1 + elif contribution.contribution_type == "pull_request": + user_stats[username]["prs"] += 1 + elif contribution.contribution_type == "comment": + user_stats[username]["comments"] += 1 + + # Calculate weighted total total = ( - stat.commits - + stat.issues_opened - + stat.issues_closed - + stat.prs - + stat.comments - + stat.assigned_issues + user_stats[username]["commits"] * 5 + + user_stats[username]["prs"] * 3 + + user_stats[username]["issues_opened"] * 2 + + user_stats[username]["issues_closed"] * 2 + + user_stats[username]["comments"] ) - user_stats[stat.username] = { - "commits": stat.commits, - "issues_opened": stat.issues_opened, - "issues_closed": stat.issues_closed, - "assigned_issues": stat.assigned_issues, - "prs": stat.prs, - "comments": stat.comments, - "total": total, - } + user_stats[username]["total"] = total - # Sort by total descending + # Sort by total contributions user_stats = dict(sorted(user_stats.items(), key=lambda x: x[1]["total"], reverse=True)) context.update( @@ -601,6 +636,7 @@ def get_context_data(self, **kwargs): "end_date": display_end_date.strftime("%Y-%m-%d"), } ) + return context @@ -1694,7 +1730,6 @@ def get_cumulative_data(queryset, date_field="created"): get_cumulative_data(CompanyAdmin.objects), get_cumulative_data(Transaction.objects), get_cumulative_data(Payment.objects), - get_cumulative_data(ContributorStats.objects), get_cumulative_data(Monitor.objects), get_cumulative_data(Bid.objects), get_cumulative_data(ChatBotLog.objects), @@ -2232,7 +2267,7 @@ def get_context_data(self, **kwargs): if self.request.user.is_authenticated: context["wallet"] = Wallet.objects.get(user=self.request.user) - context["issue_count"] = Issue.objects.filter(url__contains=self.object.domain_name).count() + context["issue_count"] = Issue.objects.filter(url__contains=self.object.domain_name.count()) context["all_comment"] = self.object.comments.all context["all_users"] = User.objects.all() context["likes"] = UserProfile.objects.filter(issue_upvoted=self.object).count() diff --git a/website/management/commands/fetch_contributor_stats.py b/website/management/commands/fetch_contributor_stats.py index fc2a3c3d3..a38fdcd9c 100644 --- a/website/management/commands/fetch_contributor_stats.py +++ b/website/management/commands/fetch_contributor_stats.py @@ -1,11 +1,10 @@ -from collections import defaultdict -from datetime import datetime, timedelta +from datetime import datetime import requests from django.conf import settings from django.core.management.base import BaseCommand -from website.models import ContributorStats # Adjust this to your actual model path +from website.models import Contribution, Project class Command(BaseCommand): @@ -21,12 +20,7 @@ def add_arguments(self, parser): def handle(self, *args, **options): # Clear existing records - ContributorStats.objects.all().delete() - - # Prepare the time range - end_date = datetime.now().date() - start_date = end_date - timedelta(days=7) - since = start_date.isoformat() + Contribution.objects.all().delete() # GitHub repository details repo = options["repo"] @@ -34,107 +28,122 @@ def handle(self, *args, **options): # Authentication headers headers = {"Authorization": f"token {settings.GITHUB_TOKEN}"} - # Initialize data structure - user_stats = defaultdict( - lambda: { - "commits": 0, - "issues_opened": 0, - "issues_closed": 0, - "prs": 0, - "comments": 0, - "assigned_issues": 0, - "github_date": None, # Add this field - } - ) # Fetch and process data - self.fetch_and_update_data("pulls", user_stats, headers, owner, repo, since, start_date) - self.fetch_and_update_data( - "issuesopen", user_stats, headers, owner, repo, since, start_date - ) - self.fetch_and_update_data( - "issuesclosed", user_stats, headers, owner, repo, since, start_date - ) - self.fetch_and_update_data("commits", user_stats, headers, owner, repo, since, start_date) - self.fetch_and_update_data("comments", user_stats, headers, owner, repo, since, start_date) - - # Save the updated data to the database - for username, stats in user_stats.items(): - ContributorStats.objects.create( - username=username, - commits=stats["commits"], - issues_opened=stats["issues_opened"], - issues_closed=stats["issues_closed"], - prs=stats["prs"], - comments=stats["comments"], - assigned_issues=stats["assigned_issues"], - github_date=stats.get("github_date") - or datetime.now().date(), # Set the GitHub date - ) + self.fetch_and_update_data("pulls", headers, owner, repo) + self.fetch_and_update_data("issuesopen", headers, owner, repo) + self.fetch_and_update_data("issuesclosed", headers, owner, repo) + self.fetch_and_update_data("commits", headers, owner, repo) + self.fetch_and_update_data("comments", headers, owner, repo) self.stdout.write(self.style.SUCCESS("Successfully updated contributor stats")) - def fetch_and_update_data(self, data_type, user_stats, headers, owner, repo, since, start_date): + def fetch_and_update_data(self, data_type, headers, owner, repo): # Define URL based on data_type if data_type == "pulls": - url = f"https://api.github.com/repos/{owner}/{repo}/pulls?state=all&since={since}&per_page=500" + url = f"https://api.github.com/repos/{owner}/{repo}/pulls" elif data_type == "issuesopen": - url = f"https://api.github.com/repos/{owner}/{repo}/issues?state=open&since={since}&per_page=500" + url = f"https://api.github.com/repos/{owner}/{repo}/issues?state=open" elif data_type == "issuesclosed": - url = f"https://api.github.com/repos/{owner}/{repo}/issues?state=closed&since={since}&per_page=500" + url = f"https://api.github.com/repos/{owner}/{repo}/issues?state=closed" elif data_type == "commits": - url = f"https://api.github.com/repos/{owner}/{repo}/commits?since={since}&per_page=500" + url = f"https://api.github.com/repos/{owner}/{repo}/commits" elif data_type == "comments": - url = f"https://api.github.com/repos/{owner}/{repo}/issues/comments?since={since}&per_page=200" + url = f"https://api.github.com/repos/{owner}/{repo}/issues/comments" response = requests.get(url, headers=headers) items = response.json() - # Check for errors in response - if isinstance(items, dict) and items.get("message"): + if not isinstance(items, list): raise ValueError(f"Error fetching data from GitHub: {items['message']}") - # Process each item based on its type + # Get project object + project = Project.objects.get(github_url__contains=f"{owner}/{repo}") + for item in items: if data_type == "pulls": user = item["user"]["login"] - created_at = datetime.strptime(item["created_at"], "%Y-%m-%dT%H:%M:%SZ").date() - if created_at >= start_date: - user_stats[user]["prs"] += 1 - user_stats[user]["github_date"] = created_at + Contribution.objects.create( + github_username=user, + title=item["title"], + description=item["body"] or "", + contribution_type="pull_request", + github_id=str(item["id"]), + github_url=item["html_url"], + created=datetime.strptime(item["created_at"], "%Y-%m-%dT%H:%M:%SZ"), + status=item["state"], + repository=project, + ) elif data_type == "issuesopen": - user = item["user"]["login"] - created_at = datetime.strptime(item["created_at"], "%Y-%m-%dT%H:%M:%SZ").date() if "pull_request" in item: continue + user = item["user"]["login"] if item["state"] == "open": - user_stats[user]["issues_opened"] += 1 - user_stats[user]["github_date"] = created_at + Contribution.objects.create( + github_username=user, + title=item["title"], + description=item["body"] or "", + contribution_type="issue_opened", + github_id=str(item["id"]), + github_url=item["html_url"], + created=datetime.strptime(item["created_at"], "%Y-%m-%dT%H:%M:%SZ"), + status="open", + repository=project, + ) if item.get("assignee"): user = item["assignee"]["login"] - user_stats[user]["assigned_issues"] += 1 - user_stats[user]["github_date"] = created_at + Contribution.objects.create( + github_username=user, + title=f"Assigned to {item['title']}", + description=f"Assigned to issue: {item['html_url']}", + contribution_type="issue_assigned", + github_id=f"{item['id']}_assigned", + github_url=item["html_url"], + created=datetime.strptime(item["created_at"], "%Y-%m-%dT%H:%M:%SZ"), + status="open", + repository=project, + ) elif data_type == "issuesclosed": - user = item["user"]["login"] - created_at = datetime.strptime(item["created_at"], "%Y-%m-%dT%H:%M:%SZ").date() if "pull_request" in item: continue + user = item["user"]["login"] if item["state"] == "closed": - user_stats[user]["issues_closed"] += 1 - user_stats[user]["github_date"] = created_at - if item.get("assignee"): - user = item["assignee"]["login"] - user_stats[user]["assigned_issues"] += 1 - user_stats[user]["github_date"] = created_at + Contribution.objects.create( + github_username=user, + title=item["title"], + description=item["body"] or "", + contribution_type="issue_closed", + github_id=str(item["id"]), + github_url=item["html_url"], + created=datetime.strptime( + item["closed_at"] or item["created_at"], "%Y-%m-%dT%H:%M:%SZ" + ), + status="closed", + repository=project, + ) elif data_type == "commits": user = item["author"]["login"] - created_at = datetime.strptime( - item["commit"]["author"]["date"], "%Y-%m-%dT%H:%M:%SZ" - ).date() - user_stats[user]["commits"] += 1 - user_stats[user]["github_date"] = created_at + Contribution.objects.create( + title=item["commit"]["message"], + description=item["commit"]["message"], + github_username=user, + contribution_type="commits", + github_id=user, + github_url=item["html_url"], + created=datetime.strptime( + item["commit"]["author"]["date"], "%Y-%m-%dT%H:%M:%SZ" + ), + repository=project, + ) elif data_type == "comments": user = item["user"]["login"] - created_at = datetime.strptime(item["created_at"], "%Y-%m-%dT%H:%M:%SZ").date() - user_stats[user]["comments"] += 1 - user_stats[user]["github_date"] = created_at + Contribution.objects.create( + title=item["body"], + description=item["body"], + github_username=user, + contribution_type="comments", + github_id=user, + github_url=item["html_url"], + created=datetime.strptime(item["created_at"], "%Y-%m-%dT%H:%M:%SZ"), + repository=project, + ) diff --git a/website/management/commands/update_projects.py b/website/management/commands/update_projects.py index 52aab4e73..f6080899f 100644 --- a/website/management/commands/update_projects.py +++ b/website/management/commands/update_projects.py @@ -1,5 +1,7 @@ +import time + import requests -from django.core.exceptions import MultipleObjectsReturned +from django.conf import settings from django.core.management.base import BaseCommand from django.utils.dateparse import parse_datetime @@ -62,27 +64,76 @@ def handle(self, *args, **kwargs): # Fetch stars, forks, and issues count url = f"https://api.github.com/repos/{repo_name}" - response = requests.get(url, headers={"Content-Type": "application/json"}) + headers = {"Authorization": f"token {settings.GITHUB_TOKEN}"} + response = requests.get(url, headers=headers) + if response.status_code == 200: repo_data = response.json() project.stars = repo_data.get("stargazers_count", 0) project.forks = repo_data.get("forks_count", 0) - project.total_issues = repo_data.get( - "open_issues_count", 0 - ) # Directly use open_issues_count + project.watchers = repo_data.get("subscribers_count", 0) + project.network_count = repo_data.get("network_count", 0) + project.subscribers_count = repo_data.get("subscribers_count", 0) + project.primary_language = repo_data.get("language") + project.license = repo_data.get("license", {}).get("name") + project.created_at = parse_datetime(repo_data.get("created_at")) + project.updated_at = parse_datetime(repo_data.get("updated_at")) + project.size = repo_data.get("size", 0) + + # Get closed issues count with proper pagination + closed_issues_count = 0 + page = 1 + while True: + closed_issues_url = ( + f"https://api.github.com/repos/{repo_name}/issues" + f"?state=closed&per_page=100&page={page}" + ) + closed_response = requests.get(closed_issues_url, headers=headers) + + if closed_response.status_code != 200: + self.stdout.write( + self.style.WARNING( + f"Failed to fetch page {page} of closed issues: {closed_response.status_code}" + ) + ) + break - # Fetch last commit date - commits_url = f"https://api.github.com/repos/{repo_name}/commits" - commits_response = requests.get( - commits_url, headers={"Content-Type": "application/json"} + issues = closed_response.json() + if not issues: + break + + # Filter out pull requests + actual_issues = [issue for issue in issues if "pull_request" not in issue] + closed_issues_count += len(actual_issues) + + # Check if we've reached the last page + if "next" not in closed_response.links: + break + + page += 1 + # Respect GitHub's rate limits + time.sleep(1) + + project.closed_issues = closed_issues_count + project.open_issues = ( + repo_data.get("open_issues_count", 0) - project.open_pull_requests ) + + # Get open PRs count + pr_url = f"https://api.github.com/repos/{repo_name}/pulls?state=open&per_page=100" + pr_response = requests.get(pr_url, headers=headers) + if pr_response.status_code == 200: + project.open_pull_requests = len(pr_response.json()) + + # Get last commit + commits_url = f"https://api.github.com/repos/{repo_name}/commits" + commits_response = requests.get(commits_url, headers=headers) if commits_response.status_code == 200: - commits_data = commits_response.json() - if commits_data: - last_commit_date = ( - commits_data[0].get("commit", {}).get("committer", {}).get("date") + commits = commits_response.json() + if commits: + project.last_commit_date = parse_datetime( + commits[0].get("commit", {}).get("committer", {}).get("date") ) - project.last_updated = parse_datetime(last_commit_date) # Fetch latest release url = f"https://api.github.com/repos/{repo_name}/releases/latest" diff --git a/website/migrations/0144_delete_contributorstats_and_more.py b/website/migrations/0144_delete_contributorstats_and_more.py new file mode 100644 index 000000000..9d664749a --- /dev/null +++ b/website/migrations/0144_delete_contributorstats_and_more.py @@ -0,0 +1,72 @@ +# Generated by Django 5.1.1 on 2024-11-10 04:05 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("website", "0143_contributorstats_last_updated_and_more"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.DeleteModel( + name="ContributorStats", + ), + migrations.AddField( + model_name="contribution", + name="contribution_type", + field=models.CharField( + choices=[ + ("commit", "Commit"), + ("issue_opened", "Issue Opened"), + ("issue_closed", "Issue Closed"), + ("issue_assigned", "Issue Assigned"), + ("pull_request", "Pull Request"), + ("comment", "Comment"), + ], + default="commit", + max_length=20, + ), + ), + migrations.AddField( + model_name="contribution", + name="github_id", + field=models.CharField(blank=True, max_length=100, null=True), + ), + migrations.AddField( + model_name="contribution", + name="github_url", + field=models.URLField(blank=True, null=True), + ), + migrations.AddField( + model_name="contribution", + name="repository", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="website.project", + ), + ), + migrations.AlterField( + model_name="contribution", + name="created", + field=models.DateTimeField(), + ), + migrations.AddIndex( + model_name="contribution", + index=models.Index(fields=["github_id"], name="website_con_github__0ac410_idx"), + ), + migrations.AddIndex( + model_name="contribution", + index=models.Index(fields=["user", "created"], name="website_con_user_id_0a46de_idx"), + ), + migrations.AddIndex( + model_name="contribution", + index=models.Index( + fields=["repository", "created"], name="website_con_reposit_9a7e49_idx" + ), + ), + ] diff --git a/website/migrations/0145_contribution_github_username_alter_contribution_user.py b/website/migrations/0145_contribution_github_username_alter_contribution_user.py new file mode 100644 index 000000000..c25929d9a --- /dev/null +++ b/website/migrations/0145_contribution_github_username_alter_contribution_user.py @@ -0,0 +1,30 @@ +# Generated by Django 5.1.1 on 2024-11-10 04:32 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("website", "0144_delete_contributorstats_and_more"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name="contribution", + name="github_username", + field=models.CharField(default="", max_length=255), + ), + migrations.AlterField( + model_name="contribution", + name="user", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ] diff --git a/website/migrations/0146_remove_contribution_github_username.py b/website/migrations/0146_remove_contribution_github_username.py new file mode 100644 index 000000000..a4714812a --- /dev/null +++ b/website/migrations/0146_remove_contribution_github_username.py @@ -0,0 +1,16 @@ +# Generated by Django 5.1.1 on 2024-11-10 04:34 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("website", "0145_contribution_github_username_alter_contribution_user"), + ] + + operations = [ + migrations.RemoveField( + model_name="contribution", + name="github_username", + ), + ] diff --git a/website/migrations/0147_contribution_github_username.py b/website/migrations/0147_contribution_github_username.py new file mode 100644 index 000000000..588db8251 --- /dev/null +++ b/website/migrations/0147_contribution_github_username.py @@ -0,0 +1,17 @@ +# Generated by Django 5.1.1 on 2024-11-10 04:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("website", "0146_remove_contribution_github_username"), + ] + + operations = [ + migrations.AddField( + model_name="contribution", + name="github_username", + field=models.CharField(default="", max_length=255), + ), + ] diff --git a/website/migrations/0148_project_last_commit_date_project_license_and_more.py b/website/migrations/0148_project_last_commit_date_project_license_and_more.py new file mode 100644 index 000000000..d219d37cc --- /dev/null +++ b/website/migrations/0148_project_last_commit_date_project_license_and_more.py @@ -0,0 +1,37 @@ +# Generated by Django 5.1.1 on 2024-11-10 05:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("website", "0147_contribution_github_username"), + ] + + operations = [ + migrations.AddField( + model_name="project", + name="last_commit_date", + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name="project", + name="license", + field=models.CharField(blank=True, max_length=100, null=True), + ), + migrations.AddField( + model_name="project", + name="open_pull_requests", + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name="project", + name="primary_language", + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AddField( + model_name="project", + name="watchers", + field=models.IntegerField(default=0), + ), + ] diff --git a/website/migrations/0149_project_closed_issues_project_created_at_and_more.py b/website/migrations/0149_project_closed_issues_project_created_at_and_more.py new file mode 100644 index 000000000..9295996db --- /dev/null +++ b/website/migrations/0149_project_closed_issues_project_created_at_and_more.py @@ -0,0 +1,47 @@ +# Generated by Django 5.1.1 on 2024-11-10 05:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("website", "0148_project_last_commit_date_project_license_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="project", + name="closed_issues", + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name="project", + name="created_at", + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name="project", + name="network_count", + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name="project", + name="open_issues", + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name="project", + name="size", + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name="project", + name="subscribers_count", + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name="project", + name="updated_at", + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/website/models.py b/website/models.py index 2f980c6db..5ba797971 100644 --- a/website/models.py +++ b/website/models.py @@ -625,22 +625,6 @@ class Payment(models.Model): created = models.DateTimeField(auto_now_add=True) -class ContributorStats(models.Model): - username = models.CharField(max_length=255, unique=True) - commits = models.IntegerField(default=0) - issues_opened = models.IntegerField(default=0) - issues_closed = models.IntegerField(default=0) - prs = models.IntegerField(default=0) - comments = models.IntegerField(default=0) - assigned_issues = models.IntegerField(default=0) - last_updated = models.DateTimeField(auto_now=True) - created = models.DateTimeField(auto_now_add=True) - github_date = models.DateField() # Add this field - - def __str__(self): - return self.username - - class Monitor(models.Model): url = models.URLField() keyword = models.CharField(max_length=255) @@ -757,6 +741,18 @@ class Project(models.Model): total_issues = models.IntegerField(default=0) repo_visit_count = models.IntegerField(default=0) project_visit_count = models.IntegerField(default=0) + watchers = models.IntegerField(default=0) + open_pull_requests = models.IntegerField(default=0) + primary_language = models.CharField(max_length=50, null=True, blank=True) + license = models.CharField(max_length=100, null=True, blank=True) + last_commit_date = models.DateTimeField(null=True, blank=True) + updated_at = models.DateTimeField(null=True, blank=True) + created_at = models.DateTimeField(null=True, blank=True) + network_count = models.IntegerField(default=0) + subscribers_count = models.IntegerField(default=0) + open_issues = models.IntegerField(default=0) + closed_issues = models.IntegerField(default=0) + size = models.IntegerField(default=0) # Repository size in KB def __str__(self): return self.name @@ -765,18 +761,47 @@ def get_top_contributors(self, limit=30): return self.contributors.order_by("-contributions")[:limit] +# class ContributorStats(models.Model): +# username = models.CharField(max_length=255, unique=True) +# commits = models.IntegerField(default=0) +# issues_opened = models.IntegerField(default=0) +# issues_closed = models.IntegerField(default=0) +# prs = models.IntegerField(default=0) +# comments = models.IntegerField(default=0) +# assigned_issues = models.IntegerField(default=0) +# created = models.DateTimeField(auto_now_add=True) + + class Contribution(models.Model): - user = models.ForeignKey(User, on_delete=models.CASCADE) + CONTRIBUTION_TYPES = [ + ("commit", "Commit"), + ("issue_opened", "Issue Opened"), + ("issue_closed", "Issue Closed"), + ("issue_assigned", "Issue Assigned"), + ("pull_request", "Pull Request"), + ("comment", "Comment"), + ] + + user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True) title = models.CharField(max_length=255) description = models.TextField() - created = models.DateTimeField(auto_now_add=True) + repository = models.ForeignKey(Project, on_delete=models.CASCADE, null=True) + contribution_type = models.CharField( + max_length=20, choices=CONTRIBUTION_TYPES, default="commit" + ) + github_username = models.CharField(max_length=255, default="") + github_id = models.CharField(max_length=100, null=True, blank=True) + github_url = models.URLField(null=True, blank=True) + created = models.DateTimeField() status = models.CharField(max_length=50, choices=[("open", "Open"), ("closed", "Closed")]) - txid = models.CharField( - max_length=64, blank=True, null=True - ) # Transaction ID on the Bitcoin blockchain + txid = models.CharField(max_length=64, blank=True, null=True) - def __str__(self): - return self.title + class Meta: + indexes = [ + models.Index(fields=["github_id"]), + models.Index(fields=["user", "created"]), + models.Index(fields=["repository", "created"]), + ] class BaconToken(models.Model): diff --git a/website/templates/contributor_stats.html b/website/templates/contributor_stats.html index 799b27188..858a8742f 100644 --- a/website/templates/contributor_stats.html +++ b/website/templates/contributor_stats.html @@ -9,16 +9,16 @@
- + Daily - + Weekly - + Yearly
diff --git a/website/templates/projects.html b/website/templates/projects.html deleted file mode 100644 index f39468b39..000000000 --- a/website/templates/projects.html +++ /dev/null @@ -1 +0,0 @@ -{% extends "coming_soon.html" %} diff --git a/website/templates/website/project_detail.html b/website/templates/website/project_detail.html index e2aa82eb3..0e137b2ea 100644 --- a/website/templates/website/project_detail.html +++ b/website/templates/website/project_detail.html @@ -37,66 +37,139 @@

{{ project.name }}

{% endif %} {% for link in project.external_links %}{{ link.name }}{% endfor %}
-
- {% csrf_token %} - -
+
+
+ {% csrf_token %} + +
+
+ {% csrf_token %} + +
+

Project Statistics

-
- - {{ project.stars }} -

Stars

-
-
- - {{ project.forks }} -

Forks

-
-
- - {{ project.watchers }} -

Watchers

-
-
- - {{ project.total_issues }} -

Open Issues

-
-
- - {{ project.open_pull_requests }} -

Open PRs

-
-
- - {{ project.primary_language }} -

Language

-
-
- - {{ project.license }} -

License

-
-
- - {{ project.last_commit_date }} -

Last Commit

-
-
- - {{ project.project_visit_count }} -

Project Visit Count

-
-
- - {{ project.repo_visit_count }} -

Repo Visit Count

-
+ + +
+ + {{ project.created_at|date:"M d, Y" }} +

Created

+
+
+ +
+ + {{ project.updated_at|date:"M d, Y" }} +

Last Updated

+
+
+ +
+ + {{ project.last_commit_date|date:"M d, Y"|default:"No commits" }} +

Last Commit

+
+
+ + +
+ + {{ project.primary_language|default:"Not specified" }} +

Language

+
+
+ +
+ + {{ project.license|default:"Not specified" }} +

License

+
+
+ +
+ + {{ project.size|filesizeformat }} +

Size

+
+
+ + +
+ + {{ project.open_issues|default:"0" }} +

Open Issues

+
+
+ +
+ + {{ project.closed_issues|default:"0" }} +

Closed Issues

+
+
+ +
+ + {{ project.open_pull_requests|default:"0" }} +

Open PRs

+
+
+ + +
+ + {{ project.stars|default:"0" }} +

Stars

+
+
+ +
+ + {{ project.forks|default:"0" }} +

Forks

+
+
+ +
+ + {{ project.subscribers_count|default:"0" }} +

Subscribers

+
+
+ +
+ + {{ project.network_count|default:"0" }} +

Network Size

+
+
@@ -207,29 +280,41 @@

Related Projects

} .stats-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 20px; margin-top: 20px; } .stat-item { - background-color: #f8f9fa; - padding: 20px; - text-align: center; + background: #f8f9fa; border-radius: 8px; + padding: 15px; + text-align: center; + transition: transform 0.2s; + } + + .stat-item:hover { + transform: translateY(-2px); + box-shadow: 0 2px 8px rgba(0,0,0,0.1); } + .stat-item i { font-size: 24px; color: #007bff; margin-bottom: 10px; } + .stat-item span { display: block; - font-size: 24px; + font-size: 18px; font-weight: bold; + margin: 5px 0; + color: #343a40; } + .stat-item p { - margin-top: 5px; - color: #555; + margin: 0; + color: #6c757d; + font-size: 14px; } .contributors-section { margin-top: 50px; @@ -306,5 +391,16 @@

Related Projects

font-size: 14px; color: #333; } + .refresh-buttons { + display: flex; + gap: 10px; + margin-top: 15px; + } + + .refresh-buttons button { + display: flex; + align-items: center; + gap: 5px; + } {% endblock content %} diff --git a/website/templates/website/project_list.html b/website/templates/website/project_list.html index ca8ea467e..f7e699638 100644 --- a/website/templates/website/project_list.html +++ b/website/templates/website/project_list.html @@ -10,23 +10,31 @@

Projects: {{ projects.count }}

{% csrf_token %} -
-
- -
- + +
+
+ {% csrf_token %} +
{% csrf_token %} -