Skip to content

Commit 0a754c8

Browse files
committed
feat: add daily stats model and GitHub username field; update daily tasks and templates
1 parent 5512550 commit 0a754c8

18 files changed

+407
-190
lines changed

website/admin.py

+10
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
Contributor,
2020
ContributorStats,
2121
Course,
22+
DailyStats,
2223
Domain,
2324
Enrollment,
2425
ForumCategory,
@@ -598,6 +599,14 @@ class RoomAdmin(admin.ModelAdmin):
598599
date_hierarchy = "created_at"
599600

600601

602+
class DailyStatsAdmin(admin.ModelAdmin):
603+
list_display = ("name", "value", "created", "modified")
604+
search_fields = ["name", "value"]
605+
list_filter = ["created", "modified"]
606+
readonly_fields = ["created", "modified"]
607+
ordering = ["-modified"]
608+
609+
601610
admin.site.register(Project, ProjectAdmin)
602611
admin.site.register(Repo, RepoAdmin)
603612
admin.site.register(Contributor, ContributorAdmin)
@@ -649,3 +658,4 @@ class RoomAdmin(admin.ModelAdmin):
649658
admin.site.register(Message, MessageAdmin)
650659
admin.site.register(SlackBotActivity, SlackBotActivityAdmin)
651660
admin.site.register(Room, RoomAdmin)
661+
admin.site.register(DailyStats, DailyStatsAdmin)

website/management/base.py

+32-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from django.core.management.base import BaseCommand
22
from django.utils import timezone
33

4-
from website.models import ManagementCommandLog
4+
from website.models import DailyStats, ManagementCommandLog
55

66

77
class LoggedBaseCommand(BaseCommand):
@@ -28,6 +28,22 @@ def execute(self, *args, **options):
2828
command_name=command_name, success=True, run_count=1, last_run=timezone.now()
2929
)
3030

31+
# Update DailyStats for this command
32+
today = timezone.now().date()
33+
try:
34+
# Try to find an existing entry for today
35+
daily_stat = DailyStats.objects.get(name=command_name, created__date=today)
36+
# Increment the value
37+
try:
38+
current_value = int(daily_stat.value)
39+
daily_stat.value = str(current_value + 1)
40+
except (ValueError, TypeError):
41+
daily_stat.value = "1"
42+
daily_stat.save()
43+
except DailyStats.DoesNotExist:
44+
# Create a new entry
45+
DailyStats.objects.create(name=command_name, value="1")
46+
3147
return result
3248
except Exception as e:
3349
# Handle error case
@@ -46,4 +62,19 @@ def execute(self, *args, **options):
4662
command_name=command_name, success=False, error_message=str(e), run_count=1, last_run=timezone.now()
4763
)
4864

65+
# Still update DailyStats even if there was an error
66+
today = timezone.now().date()
67+
try:
68+
daily_stat = DailyStats.objects.get(name=command_name, created__date=today)
69+
# Increment the value
70+
try:
71+
current_value = int(daily_stat.value)
72+
daily_stat.value = str(current_value + 1)
73+
except (ValueError, TypeError):
74+
daily_stat.value = "1"
75+
daily_stat.save()
76+
except DailyStats.DoesNotExist:
77+
# Create a new entry
78+
DailyStats.objects.create(name=command_name, value="1")
79+
4980
raise

website/management/commands/fetch_gsoc_orgs.py

+3
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ def assign_tags(self, org, tags):
100100
tag_slug = slugify(tag_name)
101101
tag, _ = Tag.objects.get_or_create(slug=tag_slug, defaults={"name": tag_name})
102102
org.tags.add(tag)
103+
# also add the tag gsoc25 to all orgs that run this
104+
tag, _ = Tag.objects.get_or_create(slug="gsoc25", defaults={"name": "GSoC 2025"})
105+
org.tags.add(tag)
103106

104107
def assign_contacts(self, org, contacts):
105108
for contact in contacts:

website/management/commands/initsuperuser.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,8 @@ def handle(self, *args, **options):
1111
EMAIL = user[1]
1212
PASSWORD = user[2]
1313
print("Creating superuser for %s (%s)" % (USERNAME, EMAIL))
14-
superuser = User.objects.create_superuser(username=USERNAME, email=EMAIL, password=PASSWORD)
15-
superuser.save()
14+
if settings.DEBUG:
15+
superuser = User.objects.create_superuser(username=USERNAME, email=EMAIL, password=PASSWORD)
16+
superuser.save()
17+
else:
18+
print("Skipping superuser creation in non-debug mode")

website/management/commands/run_daily.py

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ def handle(self, *args, **options):
1818
call_command("check_keywords")
1919
call_command("check_owasp_projects")
2020
call_command("check_trademarks")
21+
call_command("update_repo_stars")
2122
except Exception as e:
2223
logger.error(f"Error in daily tasks: {str(e)}")
2324
raise
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import logging
2+
import time
3+
4+
import requests
5+
from django.core.management.base import BaseCommand
6+
from django.utils import timezone
7+
8+
from website.models import ManagementCommandLog, Repo
9+
10+
logger = logging.getLogger(__name__)
11+
12+
13+
class Command(BaseCommand):
14+
help = "Update repository star counts from GitHub API"
15+
16+
def handle(self, *args, **options):
17+
command_name = "update_repo_stars"
18+
log_entry, created = ManagementCommandLog.objects.get_or_create(command_name=command_name)
19+
log_entry.run_count += 1
20+
log_entry.save()
21+
22+
try:
23+
self.stdout.write(self.style.SUCCESS("Starting repository star count update..."))
24+
25+
# Get all repositories
26+
repos = Repo.objects.all()
27+
self.stdout.write(f"Found {repos.count()} repositories to update")
28+
29+
updated_count = 0
30+
error_count = 0
31+
32+
for repo in repos:
33+
try:
34+
# Extract owner and repo name from repo_url
35+
if "github.com" in repo.repo_url:
36+
parts = repo.repo_url.split("github.com/")
37+
if len(parts) > 1:
38+
repo_path = parts[1].strip("/")
39+
40+
# Make API request to GitHub
41+
api_url = f"https://api.github.com/repos/{repo_path}"
42+
headers = {"Accept": "application/vnd.github.v3+json"}
43+
44+
# Add GitHub token if available
45+
github_token = None
46+
if github_token:
47+
headers["Authorization"] = f"token {github_token}"
48+
49+
response = requests.get(api_url, headers=headers)
50+
51+
if response.status_code == 200:
52+
data = response.json()
53+
54+
# Update repository information
55+
repo.stars = data.get("stargazers_count", 0)
56+
repo.forks = data.get("forks_count", 0)
57+
repo.watchers = data.get("watchers_count", 0)
58+
repo.open_issues = data.get("open_issues_count", 0)
59+
repo.last_updated = timezone.now()
60+
61+
# Save changes
62+
repo.save()
63+
updated_count += 1
64+
self.stdout.write(f"Updated {repo.name}: {repo.stars} stars")
65+
else:
66+
self.stdout.write(
67+
self.style.WARNING(f"Failed to fetch data for {repo.name}: {response.status_code}")
68+
)
69+
error_count += 1
70+
71+
# Sleep to avoid rate limiting
72+
time.sleep(1)
73+
else:
74+
self.stdout.write(self.style.WARNING(f"Skipping {repo.name}: Not a GitHub repository"))
75+
except Exception as e:
76+
self.stdout.write(self.style.ERROR(f"Error updating {repo.name}: {str(e)}"))
77+
error_count += 1
78+
79+
self.stdout.write(
80+
self.style.SUCCESS(f"Repository update completed. Updated: {updated_count}, Errors: {error_count}")
81+
)
82+
83+
log_entry.success = True
84+
log_entry.error_message = None
85+
log_entry.save()
86+
87+
except Exception as e:
88+
self.stdout.write(self.style.ERROR(f"Command failed: {str(e)}"))
89+
log_entry.success = False
90+
log_entry.error_message = str(e)
91+
log_entry.save()

website/migrations/0214_dailystats.py

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Generated by Django 5.1.6 on 2025-03-02 17:48
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
dependencies = [
8+
("website", "0213_organization_categories_and_more"),
9+
]
10+
11+
operations = [
12+
migrations.CreateModel(
13+
name="DailyStats",
14+
fields=[
15+
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
16+
("name", models.CharField(max_length=100, unique=True)),
17+
("value", models.CharField(max_length=255)),
18+
("created", models.DateTimeField(auto_now_add=True)),
19+
("modified", models.DateTimeField(auto_now=True)),
20+
],
21+
options={
22+
"verbose_name": "Daily Statistic",
23+
"verbose_name_plural": "Daily Statistics",
24+
"ordering": ["-modified"],
25+
},
26+
),
27+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Generated by Django 5.1.6 on 2025-03-02 18:02
2+
3+
import django.db.models.deletion
4+
from django.conf import settings
5+
from django.db import migrations, models
6+
7+
8+
class Migration(migrations.Migration):
9+
dependencies = [
10+
("website", "0214_dailystats"),
11+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12+
]
13+
14+
operations = [
15+
migrations.AddField(
16+
model_name="bid",
17+
name="github_username",
18+
field=models.CharField(blank=True, max_length=100, null=True),
19+
),
20+
migrations.AlterField(
21+
model_name="bid",
22+
name="user",
23+
field=models.ForeignKey(
24+
blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
25+
),
26+
),
27+
]

website/models.py

+17-1
Original file line numberDiff line numberDiff line change
@@ -914,7 +914,8 @@ def __str__(self):
914914

915915

916916
class Bid(models.Model):
917-
user = models.ForeignKey(User, on_delete=models.CASCADE)
917+
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
918+
github_username = models.CharField(max_length=100, blank=True, null=True)
918919
# link this to our issue model
919920
issue_url = models.URLField()
920921
created = models.DateTimeField(default=timezone.now)
@@ -1969,3 +1970,18 @@ class BaconSubmission(models.Model):
19691970

19701971
def __str__(self):
19711972
return f"{self.user.username} - {self.status}"
1973+
1974+
1975+
class DailyStats(models.Model):
1976+
name = models.CharField(max_length=100, unique=True)
1977+
value = models.CharField(max_length=255)
1978+
created = models.DateTimeField(auto_now_add=True)
1979+
modified = models.DateTimeField(auto_now=True)
1980+
1981+
class Meta:
1982+
verbose_name = "Daily Statistic"
1983+
verbose_name_plural = "Daily Statistics"
1984+
ordering = ["-modified"]
1985+
1986+
def __str__(self):
1987+
return f"{self.name}: {self.value}"

website/templates/bidding.html

+18
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,24 @@ <h2 class="text-xl font-semibold mb-3 text-gray-900 flex items-center gap-2">
6666
</button>
6767
</div>
6868
</div>
69+
<!-- GitHub Username Input -->
70+
<div>
71+
<label class="block text-sm font-semibold text-gray-700 mb-2">
72+
<i class="fab fa-github mr-2"></i>GitHub Username
73+
</label>
74+
<div class="relative">
75+
<input type="text"
76+
id="githubUsername"
77+
name="user"
78+
class="block w-full rounded-lg border-2 border-gray-200 shadow-sm focus:border-[#e74c3c] focus:ring focus:ring-[#e74c3c] focus:ring-opacity-50 pl-10 pr-4 py-3 transition-all duration-200"
79+
placeholder="Enter GitHub username"
80+
value="{{ request.user.username }}">
81+
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
82+
<i class="fab fa-github text-gray-400"></i>
83+
</div>
84+
</div>
85+
<p class="mt-2 text-sm text-gray-500">Enter your GitHub username if different from your BLT username</p>
86+
</div>
6987
<!-- Current Bid Display -->
7088
<div id="BidDisplay" class="text-lg font-medium text-gray-900"></div>
7189
<!-- Quick Bid Buttons -->

website/templates/home.html

+10-42
Original file line numberDiff line numberDiff line change
@@ -803,53 +803,26 @@ <h2 class="text-3xl font-bold mb-6">Get Involved</h2>
803803
'action': 'OWASP-BLT/BLT-Action'
804804
};
805805

806-
// Cache star counts in localStorage with expiration
807-
function getStoredStarCount(repo) {
808-
const stored = localStorage.getItem(`star_count_${repo}`);
809-
if (stored) {
810-
try {
811-
const { count, timestamp } = JSON.parse(stored);
812-
// Cache for 1 hour
813-
if (Date.now() - timestamp < 3600000) {
814-
return count;
815-
}
816-
} catch (e) {
817-
return null;
818-
}
806+
// Display star counts from the database
807+
{% for repo in repo_stars %}
808+
const countElement = document.getElementById(`{{ repo.key }}-star-count`);
809+
if (countElement) {
810+
countElement.textContent = {{ repo.stars }};
819811
}
812+
{% endfor %}
820813

821-
function storeStarCount(repo, count) {
822-
localStorage.setItem(`star_count_${repo}`, JSON.stringify({
823-
count: count,
824-
timestamp: Date.now()
825-
}));
826-
}
827-
814+
// Fallback to GitHub API if no data in database
828815
Object.entries(repos).forEach(([key, repo]) => {
829816
const countElement = document.getElementById(`${key}-star-count`);
830-
if (!countElement) return;
831-
832-
// First try to get from cache
833-
const cachedCount = getStoredStarCount(repo);
834-
if (cachedCount !== null) {
835-
countElement.textContent = cachedCount;
836-
}
817+
if (!countElement || countElement.textContent.trim() !== '') return;
837818

838-
// Then fetch fresh data
819+
// If we don't have data from the database, fetch from GitHub API
839820
fetch(`https://api.github.com/repos/${repo}`, {
840821
headers: {
841822
'Accept': 'application/vnd.github.v3+json'
842823
}
843824
})
844825
.then(response => {
845-
if (response.status === 403) {
846-
// Rate limited - use cached value or keep showing loading
847-
const cached = getStoredStarCount(repo);
848-
if (cached !== null) {
849-
countElement.textContent = cached;
850-
}
851-
throw new Error('Rate limited');
852-
}
853826
if (!response.ok) {
854827
throw new Error(`HTTP error! status: ${response.status}`);
855828
}
@@ -858,16 +831,11 @@ <h2 class="text-3xl font-bold mb-6">Get Involved</h2>
858831
.then(data => {
859832
if (data && typeof data.stargazers_count === 'number') {
860833
countElement.textContent = data.stargazers_count;
861-
storeStarCount(repo, data.stargazers_count);
862834
}
863835
})
864836
.catch(error => {
865837
console.error(`Error fetching star count for ${repo}:`, error);
866-
// Only show 0 if we don't have a cached value
867-
const cached = getStoredStarCount(repo);
868-
if (cached === null) {
869-
countElement.textContent = '...';
870-
}
838+
countElement.textContent = '...';
871839
});
872840
});
873841
</script>

0 commit comments

Comments
 (0)