Skip to content

Commit 396f36a

Browse files
committed
Update project URLs to use 'project_view' and adjust related templates
1 parent c41eb3f commit 396f36a

File tree

5 files changed

+206
-27
lines changed

5 files changed

+206
-27
lines changed

blt/urls.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@
177177
)
178178
from website.views.project import (
179179
ProjectBadgeView,
180+
ProjectDetailView,
181+
ProjectListView,
180182
ProjectsDetailView,
181183
ProjectView,
182184
RepoDetailView,
@@ -537,7 +539,8 @@
537539
TemplateView.as_view(template_name="coming_soon.html"),
538540
name="googleplayapp",
539541
),
540-
re_path(r"^projects/$", ProjectView.as_view(), name="project_view"),
542+
re_path(r"^projects/$", ProjectListView.as_view(), name="project_list"),
543+
re_path(r"^allprojects/$", ProjectView.as_view(), name="project_view"),
541544
re_path(r"^apps/$", TemplateView.as_view(template_name="apps.html"), name="apps"),
542545
re_path(
543546
r"^deletions/$",
@@ -587,6 +590,7 @@
587590
re_path(r"^api/v1/createwallet/$", create_wallet, name="create_wallet"),
588591
re_path(r"^api/v1/count/$", issue_count, name="api_count"),
589592
re_path(r"^api/v1/contributors/$", contributors, name="api_contributor"),
593+
path("project/<slug:slug>/", ProjectDetailView.as_view(), name="project_view"),
590594
path("projects/<slug:slug>/badge/", ProjectBadgeView.as_view(), name="project-badge"),
591595
path("repository/<slug:slug>/", RepoDetailView.as_view(), name="repo_detail"),
592596
re_path(r"^report-ip/$", ReportIpView.as_view(), name="report_ip"),
@@ -798,7 +802,7 @@
798802
name="similarity_scan",
799803
),
800804
path("projects/create/", create_project, name="create_project"),
801-
path("project/<slug:slug>/", ProjectsDetailView.as_view(), name="projects_detail"),
805+
path("projects/<slug:slug>/", ProjectsDetailView.as_view(), name="projects_detail"),
802806
]
803807

804808
if settings.DEBUG:

website/templates/home.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ <h2 class="text-3xl font-bold mb-6">Get Involved</h2>
7777
class="bg-green-500 text-white font-semibold py-3 px-6 rounded-full">
7878
<i class="fas fa-users"></i> Join the Community
7979
</a>
80-
<a href="{% url 'project_view' %}"
80+
<a href="{% url 'project_list' %}"
8181
class="bg-blue-500 text-white font-semibold py-3 px-6 rounded-full">
8282
<i class="fas fa-project-diagram"></i> Explore Projects
8383
</a>

website/templates/includes/sidenav.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@
138138
<div class="my-4 border-t border-gray-300"></div>
139139
<!-- Projects Header Link -->
140140
<li class="mb-2 {% if request.path == '/projects/' %}bg-gray-200{% endif %}">
141-
<a href="{% url 'project_view' %}"
141+
<a href="{% url 'project_list' %}"
142142
class="flex items-center w-full no-underline p-2 {% if '/projects/' in request.path %} text-black {% else %} hover:text-red-500 text-black {% endif %}">
143143
<div class="icon text-orange-500">
144144
<i class="fas fa-box fa-lg"></i>

website/templates/sitemap.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ <h2 class="text-6xl text-black font-bold">Sitemap</h2>
102102
</li>
103103
<li class="flex items-center space-x-3 text-black hover:text-red-600 hover:translate-x-1 transition duration-200 cursor-pointer">
104104
<i class="fas fa-folder w-5 h-5 mr-1 align-middle"></i>
105-
<a href="{% url 'project_view' %}">Projects</a>
105+
<a href="{% url 'project_list' %}">Projects</a>
106106
</li>
107107
<li class="flex items-center space-x-3 text-black hover:text-red-600 hover:translate-x-1 transition duration-200 cursor-pointer">
108108
<i class="fas fa-mobile-alt w-5 h-5 mr-1 align-middle"></i>

website/views/project.py

Lines changed: 197 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import re
55
import socket
66
import time
7+
from datetime import datetime, timedelta
78
from io import BytesIO
89
from pathlib import Path
910
from urllib.parse import urlparse
@@ -23,13 +24,14 @@
2324
from django.shortcuts import get_object_or_404, redirect, render
2425
from django.utils.dateparse import parse_datetime
2526
from django.utils.text import slugify
26-
from django.utils.timezone import localtime
27+
from django.utils.timezone import localtime, now
2728
from django.views.decorators.http import require_http_methods
28-
from django.views.generic import DetailView
29+
from django.views.generic import DetailView, ListView
2930
from django_filters.views import FilterView
3031
from rest_framework.views import APIView
3132

3233
from website.bitcoin_utils import create_bacon_token
34+
from website.forms import GitHubURLForm
3335
from website.models import IP, BaconToken, Contribution, Organization, Project, Repo
3436
from website.utils import admin_required
3537

@@ -83,6 +85,105 @@ def distribute_bacon(request, contribution_id):
8385
return render(request, "select_contribution.html", {"contributions": contributions})
8486

8587

88+
class ProjectDetailView(DetailView):
89+
model = Project
90+
period = None
91+
selected_year = None
92+
93+
def post(self, request, *args, **kwargs):
94+
from django.core.management import call_command
95+
96+
project = self.get_object()
97+
98+
if "refresh_stats" in request.POST:
99+
call_command("update_projects", "--project_id", project.pk)
100+
messages.success(request, f"Refreshing stats for {project.name}")
101+
102+
elif "refresh_contributor_stats" in request.POST:
103+
owner_repo = project.github_url.rstrip("/").split("/")[-2:]
104+
repo = f"{owner_repo[0]}/{owner_repo[1]}"
105+
call_command("fetch_contributor_stats", "--repo", repo)
106+
messages.success(request, f"Refreshing contributor stats for {project.name}")
107+
108+
elif "refresh_contributors" in request.POST:
109+
call_command("fetch_contributors", "--project_id", project.pk)
110+
return redirect("project_view", slug=project.slug)
111+
112+
def get(self, request, *args, **kwargs):
113+
project = self.get_object()
114+
project.project_visit_count += 1
115+
project.save()
116+
return super().get(request, *args, **kwargs)
117+
118+
def get_context_data(self, **kwargs):
119+
context = super().get_context_data(**kwargs)
120+
end_date = now()
121+
display_end_date = end_date.date()
122+
selected_year = self.request.GET.get("year", None)
123+
if selected_year:
124+
start_date = datetime(int(selected_year), 1, 1)
125+
display_end_date = datetime(int(selected_year), 12, 31)
126+
else:
127+
self.period = self.request.GET.get("period", "30")
128+
days = int(self.period)
129+
start_date = end_date - timedelta(days=days)
130+
start_date = start_date.date()
131+
132+
contributions = Contribution.objects.filter(
133+
created__date__gte=start_date,
134+
created__date__lte=display_end_date,
135+
repository=self.get_object(),
136+
)
137+
138+
user_stats = {}
139+
for contribution in contributions:
140+
username = contribution.github_username
141+
if username not in user_stats:
142+
user_stats[username] = {
143+
"commits": 0,
144+
"issues_opened": 0,
145+
"issues_closed": 0,
146+
"prs": 0,
147+
"comments": 0,
148+
"total": 0,
149+
}
150+
if contribution.contribution_type == "commit":
151+
user_stats[username]["commits"] += 1
152+
elif contribution.contribution_type == "issue_opened":
153+
user_stats[username]["issues_opened"] += 1
154+
elif contribution.contribution_type == "issue_closed":
155+
user_stats[username]["issues_closed"] += 1
156+
elif contribution.contribution_type == "pull_request":
157+
user_stats[username]["prs"] += 1
158+
elif contribution.contribution_type == "comment":
159+
user_stats[username]["comments"] += 1
160+
total = (
161+
user_stats[username]["commits"] * 5
162+
+ user_stats[username]["prs"] * 3
163+
+ user_stats[username]["issues_opened"] * 2
164+
+ user_stats[username]["issues_closed"] * 2
165+
+ user_stats[username]["comments"]
166+
)
167+
user_stats[username]["total"] = total
168+
169+
user_stats = dict(sorted(user_stats.items(), key=lambda x: x[1]["total"], reverse=True))
170+
171+
current_year = now().year
172+
year_list = list(range(current_year, current_year - 10, -1))
173+
174+
context.update(
175+
{
176+
"user_stats": user_stats,
177+
"period": self.period,
178+
"start_date": start_date.strftime("%Y-%m-%d"),
179+
"end_date": display_end_date.strftime("%Y-%m-%d"),
180+
"year_list": year_list,
181+
"selected_year": selected_year,
182+
}
183+
)
184+
return context
185+
186+
86187
class ProjectBadgeView(APIView):
87188
def get(self, request, slug):
88189
# Retrieve the project or return 404
@@ -138,6 +239,94 @@ def get(self, request, slug):
138239
return response
139240

140241

242+
class ProjectListView(ListView):
243+
model = Project
244+
context_object_name = "projects"
245+
246+
def get_context_data(self, **kwargs):
247+
context = super().get_context_data(**kwargs)
248+
context["form"] = GitHubURLForm()
249+
context["sort_by"] = self.request.GET.get("sort_by", "-created")
250+
context["order"] = self.request.GET.get("order", "desc")
251+
return context
252+
253+
def post(self, request, *args, **kwargs):
254+
if "refresh_stats" in request.POST:
255+
from django.core.management import call_command
256+
257+
call_command("update_projects")
258+
messages.success(request, "Refreshing project statistics...")
259+
return redirect("project_list")
260+
261+
if "refresh_contributors" in request.POST:
262+
from django.core.management import call_command
263+
264+
projects = Project.objects.all()
265+
for project in projects:
266+
owner_repo = project.github_url.rstrip("/").split("/")[-2:]
267+
repo = f"{owner_repo[0]}/{owner_repo[1]}"
268+
call_command("fetch_contributor_stats", "--repo", repo)
269+
messages.success(request, "Refreshing contributor data...")
270+
return redirect("project_list")
271+
272+
form = GitHubURLForm(request.POST)
273+
if form.is_valid():
274+
github_url = form.cleaned_data["github_url"]
275+
# Extract the repository part of the URL
276+
match = re.match(r"https://github.com/([^/]+/[^/]+)", github_url)
277+
if match:
278+
repo_path = match.group(1)
279+
api_url = f"https://api.github.com/repos/{repo_path}"
280+
response = requests.get(api_url)
281+
if response.status_code == 200:
282+
data = response.json()
283+
# if the description is empty, use the name as the description
284+
if not data["description"]:
285+
data["description"] = data["name"]
286+
287+
# Check if a project with the same slug already exists
288+
slug = data["name"].lower()
289+
if Project.objects.filter(slug=slug).exists():
290+
messages.error(request, "A project with this slug already exists.")
291+
return redirect("project_list")
292+
293+
project, created = Project.objects.get_or_create(
294+
github_url=github_url,
295+
defaults={
296+
"name": data["name"],
297+
"slug": slug,
298+
"description": data["description"],
299+
"wiki_url": data["html_url"],
300+
"homepage_url": data.get("homepage", ""),
301+
"logo_url": data["owner"]["avatar_url"],
302+
},
303+
)
304+
if created:
305+
messages.success(request, "Project added successfully.")
306+
else:
307+
messages.info(request, "Project already exists.")
308+
else:
309+
messages.error(request, "Failed to fetch project from GitHub.")
310+
else:
311+
messages.error(request, "Invalid GitHub URL.")
312+
return redirect("project_list")
313+
context = self.get_context_data()
314+
context["form"] = form
315+
return self.render_to_response(context)
316+
317+
def get_queryset(self):
318+
queryset = super().get_queryset()
319+
sort_by = self.request.GET.get("sort_by", "-created")
320+
order = self.request.GET.get("order", "desc")
321+
322+
if order == "asc" and sort_by.startswith("-"):
323+
sort_by = sort_by[1:]
324+
elif order == "desc" and not sort_by.startswith("-"):
325+
sort_by = f"-{sort_by}"
326+
327+
return queryset.order_by(sort_by)
328+
329+
141330
class ProjectRepoFilter(django_filters.FilterSet):
142331
search = django_filters.CharFilter(method="filter_search", label="Search")
143332
repo_type = django_filters.ChoiceFilter(
@@ -810,18 +999,11 @@ def get_issue_count(full_name, query, headers):
810999
)
8111000

8121001
except requests.RequestException as e:
813-
return JsonResponse(
814-
{"status": "error", "message": f"Network error: {str(e)}"}, status=503
815-
)
1002+
return JsonResponse({"status": "error", "message": f"Network error: {str(e)}"}, status=503)
8161003
except requests.HTTPError as e:
817-
return JsonResponse(
818-
{"status": "error", "message": f"GitHub API error: {str(e)}"},
819-
status=e.response.status_code,
820-
)
1004+
return JsonResponse({"status": "error", "message": f"GitHub API error: {str(e)}"}, status=e.response.status_code)
8211005
except ValueError as e:
822-
return JsonResponse(
823-
{"status": "error", "message": f"Data parsing error: {str(e)}"}, status=400
824-
)
1006+
return JsonResponse({"status": "error", "message": f"Data parsing error: {str(e)}"}, status=400)
8251007

8261008
elif section == "metrics":
8271009
try:
@@ -929,17 +1111,10 @@ def get_issue_count(full_name, query, headers):
9291111
)
9301112

9311113
except requests.RequestException as e:
932-
return JsonResponse(
933-
{"status": "error", "message": f"Network error: {str(e)}"}, status=503
934-
)
1114+
return JsonResponse({"status": "error", "message": f"Network error: {str(e)}"}, status=503)
9351115
except requests.HTTPError as e:
936-
return JsonResponse(
937-
{"status": "error", "message": f"GitHub API error: {str(e)}"},
938-
status=e.response.status_code,
939-
)
1116+
return JsonResponse({"status": "error", "message": f"GitHub API error: {str(e)}"}, status=e.response.status_code)
9401117
except ValueError as e:
941-
return JsonResponse(
942-
{"status": "error", "message": f"Data parsing error: {str(e)}"}, status=400
943-
)
1118+
return JsonResponse({"status": "error", "message": f"Data parsing error: {str(e)}"}, status=400)
9441119

9451120
return super().post(request, *args, **kwargs)

0 commit comments

Comments
 (0)