diff --git a/config.py b/config.py index d0e7d14..c4a3ac1 100644 --- a/config.py +++ b/config.py @@ -1,6 +1,7 @@ api_endpoint = { 'contrihub_api_1': 'https://api.github.com/repos/ContriHUB/', 'contrihub_api_2': 'https://api.github.com/repos/contrihub/', + 'contrihub_org_repos': 'https://api.github.com/orgs/ContriHUB/repos', } html_endpoint = { diff --git a/contrihub/settings.py b/contrihub/settings.py index 840fcd9..51f6d40 100644 --- a/contrihub/settings.py +++ b/contrihub/settings.py @@ -2,6 +2,7 @@ import dj_database_url from pathlib import Path import os +from config import api_endpoint # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -184,8 +185,14 @@ EMAIL_USE_TLS = config('EMAIL_USE_TLS', default=True, cast=bool) EMAIL_USE_SSL = config('EMAIL_USE_SSL', default=False, cast=bool) -AVAILABLE_PROJECTS = config('AVAILABLE_PROJECTS', default="ContriHUB-24", - cast=lambda v: [s.strip() for s in v.split(',')]) +AVAILABLE_PROJECTS = config( + 'AVAILABLE_PROJECTS', + default='ContriHUB-24', + cast=lambda v: [s.strip() for s in v.split(',')], +) + +CONTRIHUB_ORG_REPOS_ENDPOINT = config('CONTRIHUB_ORG_REPOS_ENDPOINT', default=api_endpoint['contrihub_org_repos']) + LABEL_MENTOR = config('LABEL_MENTOR', default="mentor") LABEL_LEVEL = config('LABEL_LEVEL', default="level") LABEL_POINTS = config('LABEL_POINTS', default="points") diff --git a/home/views.py b/home/views.py index f7a0047..33f0326 100644 --- a/home/views.py +++ b/home/views.py @@ -20,6 +20,11 @@ from user_profile.models import UserProfile from .forms import ContactForm +# Legacy dashboard filters: initialise globals before any view runs +issues_qs = Issue.objects.none() +domain = 'All' +subdomain = 'All' + def home(request): return render(request, 'home/index.html') @@ -67,7 +72,7 @@ def filter_by_domain(request, domain_pk): @complete_profile_required def filter_by_subdomain(request, subdomain_pk): - global issues_qs, domain, subdomain + global issues_qs, subdomain subdomain = SubDomain.objects.get(pk=subdomain_pk) project_qs = Project.objects.all() if domain != 'All': diff --git a/project/migrations/0043_project_archived_project_description_and_more.py b/project/migrations/0043_project_archived_project_description_and_more.py new file mode 100644 index 0000000..c4e79dc --- /dev/null +++ b/project/migrations/0043_project_archived_project_description_and_more.py @@ -0,0 +1,43 @@ +# Generated by Django 4.1 on 2025-10-02 03:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0042_alter_issue_levelcolor'), + ] + + operations = [ + migrations.AddField( + model_name='project', + name='archived', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='project', + name='description', + field=models.URLField(blank=True, verbose_name='Description'), + ), + migrations.AddField( + model_name='project', + name='homepage', + field=models.URLField(blank=True, verbose_name='Homepage'), + ), + migrations.AddField( + model_name='project', + name='is_current', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='project', + name='pushed_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Last Pushed At'), + ), + migrations.AddField( + model_name='project', + name='topics_raw', + field=models.TextField(blank=True, verbose_name='Topics'), + ), + ] diff --git a/project/models.py b/project/models.py index 9c6a934..056a98b 100644 --- a/project/models.py +++ b/project/models.py @@ -30,6 +30,12 @@ class Project(models.Model): api_url = models.URLField(verbose_name="API URL") html_url = models.URLField(verbose_name="HTML URL") + description = models.URLField(verbose_name="Description", blank=True) + homepage = models.URLField(verbose_name="Homepage", blank=True) + topics_raw = models.TextField(verbose_name="Topics", blank=True) + pushed_at = models.DateTimeField(verbose_name="Last Pushed At", null=True, blank=True) + archived = models.BooleanField(default=False) + is_current = models.BooleanField(default=False) domain = models.ForeignKey(Domain, on_delete=models.DO_NOTHING, null=True, default=None) subdomain = models.ForeignKey(SubDomain, on_delete=models.DO_NOTHING, blank=True, default=None, null=True) diff --git a/project/templates/index.html b/project/templates/index.html index 45951da..80c3941 100644 --- a/project/templates/index.html +++ b/project/templates/index.html @@ -142,309 +142,41 @@
-

Projects in 2024

+

Projects

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + {% if projects %} + {% for project in projects %} +
+
+
+
+ {{ project.name }} +
+

+ {{ project.description|default:"No description provided." }} +

+ {% if project.homepage %} + + {% endif %} + {% if project.topics_raw %} +
Topics: {{ project.topics_raw }}
+ {% endif %} + {% if project.pushed_at %} +
Last updated: {{ project.pushed_at|date:"M d, Y" }}
+ {% endif %} +
+
+
+ {% endfor %} + {% else %} +
+

No projects available right now. Please check back soon.

+
+ {% endif %}
@@ -506,4 +238,4 @@
Neuda-App
- \ No newline at end of file + diff --git a/project/views.py b/project/views.py index 4a993cb..12e40c8 100644 --- a/project/views.py +++ b/project/views.py @@ -1,5 +1,9 @@ +from datetime import datetime, timedelta + from django.shortcuts import HttpResponseRedirect, reverse, render -from contrihub.settings import AVAILABLE_PROJECTS, LABEL_MENTOR, LABEL_LEVEL, LABEL_POINTS, DEPENDABOT_LOGIN, \ +from contrihub.settings import CONTRIHUB_ORG_REPOS_ENDPOINT, AVAILABLE_PROJECTS, \ + LABEL_MENTOR, LABEL_LEVEL, \ + LABEL_POINTS, DEPENDABOT_LOGIN, \ LABEL_RESTRICTED, DEFAULT_FREE_POINTS, DEFAULT_VERY_EASY_POINTS, DEFAULT_EASY_POINTS, DEFAULT_MEDIUM_POINTS, \ DEFAULT_HARD_POINTS from django.contrib.auth.decorators import user_passes_test @@ -7,12 +11,14 @@ from .models import Project, Issue from helper import safe_hit_url, SUCCESS, complete_profile_required from config import api_endpoint, html_endpoint +from django.utils import timezone User = get_user_model() def home(request): - return render(request, 'index.html') + projects = Project.objects.filter(is_current=True).order_by('-pushed_at', 'name') + return render(request, 'index.html', {'projects': projects}) @user_passes_test(lambda u: u.userprofile.role == u.userprofile.ADMIN) @@ -24,18 +30,78 @@ def populate_projects(request): :param request: :return: """ - api_uri = api_endpoint['contrihub_api_1'] - html_uri = html_endpoint['contrihub_html'] - # print(AVAILABLE_PROJECTS) - for project_name in AVAILABLE_PROJECTS: - project_qs = Project.objects.filter(name=project_name) - if not project_qs: - api_url = f"{api_uri}{project_name}" - html_url = f"{html_uri}{project_name}" - Project.objects.create( + response = safe_hit_url(CONTRIHUB_ORG_REPOS_ENDPOINT) + if response['status'] != SUCCESS: + return HttpResponseRedirect(reverse('home')) + + repos = response['data'] + active_names = set() + now = timezone.now() + year_tokens = { + str(now.year), + str(now.year - 1), + str(now.year)[-2:], + str(now.year - 1)[-2:], + } + + has_current = False + + for repo in repos: + if repo.get('archived') or repo.get('fork'): + continue + + name = repo['name'] + pushed_at_raw = repo.get('pushed_at') + pushed_at = None + is_current = False + + if pushed_at_raw: + try: + pushed_at = datetime.fromisoformat(pushed_at_raw.replace('Z', '+00:00')) + except ValueError: + pushed_at = None + + if pushed_at and pushed_at >= now - timedelta(days=365): + is_current = True + + if not is_current and any(token in name for token in year_tokens): + is_current = True + + api_url = repo['url'] + html_url = repo['html_url'] + description = repo.get('description') or '' + homepage = repo.get('homepage') or '' + topics = repo.get('topics') or [] + + project, _ = Project.objects.update_or_create( + name=name, + defaults={ + 'api_url': api_url, + 'html_url': html_url, + 'description': description, + 'homepage': homepage, + 'topics_raw': ', '.join(topics), + 'pushed_at': pushed_at, + 'archived': repo.get('archived', False), + 'is_current': is_current, + }, + ) + active_names.add(project.name) + if is_current: + has_current = True + + # fallback to AVAILABLE_PROJECTS when GitHub returns nothing + if not active_names or not has_current: + api_uri = api_endpoint['contrihub_api_1'] + html_uri = html_endpoint['contrihub_html'] + for project_name in AVAILABLE_PROJECTS: + Project.objects.update_or_create( name=project_name, - api_url=api_url, - html_url=html_url + defaults={ + 'api_url': f"{api_uri}{project_name}", + 'html_url': f"{html_uri}{project_name}", + 'is_current': True, + }, ) return HttpResponseRedirect(reverse('home'))