-
Notifications
You must be signed in to change notification settings - Fork 57
Updated Organization feature and allowed guests to view the atcoder leaderboard #201
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
0fca9f9
3de1b3d
a4fd782
f98d19b
21b29a6
1449d44
2875cd4
61b4238
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| # Generated by Django 5.1.5 on 2026-03-21 09:59 | ||
|
|
||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ("leaderboard", "0002_alter_codeforcesuser_last_activity_and_more"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AddField( | ||
| model_name="codechefuser", | ||
| name="calendar_data", | ||
| field=models.TextField(blank=True, default="[]"), | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| from django.conf import settings | ||
| from django.db import migrations, models | ||
| import django.db.models.deletion | ||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ("leaderboard", "0019_codechefuser_calendar_data"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.CreateModel( | ||
| name="Organization", | ||
| fields=[ | ||
| ( | ||
| "id", | ||
| models.BigAutoField( | ||
| auto_created=True, | ||
| primary_key=True, | ||
| serialize=False, | ||
| verbose_name="ID", | ||
| ), | ||
| ), | ||
| ("name", models.CharField(max_length=128)), | ||
| ("description", models.TextField(blank=True, null=True)), | ||
| ("is_private", models.BooleanField(default=True)), | ||
| ( | ||
| "join_code", | ||
| models.CharField(blank=True, max_length=10, null=True, unique=True), | ||
| ), | ||
| ("created_at", models.DateTimeField(auto_now_add=True)), | ||
| ( | ||
| "admin", | ||
| models.ForeignKey( | ||
| on_delete=django.db.models.deletion.CASCADE, | ||
| related_name="administered_organizations", | ||
| to=settings.AUTH_USER_MODEL, | ||
| ), | ||
| ), | ||
| ], | ||
| ), | ||
| migrations.CreateModel( | ||
| name="OrganizationMember", | ||
| fields=[ | ||
| ( | ||
| "id", | ||
| models.BigAutoField( | ||
| auto_created=True, | ||
| primary_key=True, | ||
| serialize=False, | ||
| verbose_name="ID", | ||
| ), | ||
| ), | ||
| ("joined_at", models.DateTimeField(auto_now_add=True)), | ||
| ( | ||
| "organization", | ||
| models.ForeignKey( | ||
| on_delete=django.db.models.deletion.CASCADE, | ||
| related_name="memberships", | ||
| to="leaderboard.organization", | ||
| ), | ||
| ), | ||
| ( | ||
| "user", | ||
| models.ForeignKey( | ||
| on_delete=django.db.models.deletion.CASCADE, | ||
| related_name="organization_memberships", | ||
| to=settings.AUTH_USER_MODEL, | ||
| ), | ||
| ), | ||
| ], | ||
| options={ | ||
| "unique_together": {("organization", "user")}, | ||
| }, | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| # Generated by Django 5.1.5 on 2026-03-24 08:58 | ||
|
|
||
| import django.db.models.deletion | ||
| import leaderboard.models | ||
| from django.conf import settings | ||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ("leaderboard", "0020_organization_organizationmember"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AddField( | ||
| model_name="leetcodeuser", | ||
| name="calendar_data", | ||
| field=models.TextField(blank=True, default="{}"), | ||
| ), | ||
| migrations.AddField( | ||
| model_name="usernames", | ||
| name="ac_uname", | ||
| field=models.CharField(default="", max_length=64), | ||
| ), | ||
| migrations.AddField( | ||
| model_name="usernames", | ||
| name="bio", | ||
| field=models.TextField(blank=True, default="", max_length=500), | ||
| ), | ||
| migrations.AddField( | ||
| model_name="usernames", | ||
| name="location", | ||
| field=models.CharField(blank=True, default="", max_length=128), | ||
| ), | ||
| migrations.AddField( | ||
| model_name="usernames", | ||
| name="occupation", | ||
| field=models.CharField(blank=True, default="", max_length=128), | ||
| ), | ||
| migrations.AddField( | ||
| model_name="usernames", | ||
| name="organization", | ||
| field=models.CharField(blank=True, default="", max_length=128), | ||
| ), | ||
| migrations.AlterField( | ||
| model_name="codeforcesuser", | ||
| name="last_activity", | ||
| field=models.BigIntegerField( | ||
| default=leaderboard.models.get_default_cf_last_activity | ||
| ), | ||
| ), | ||
| migrations.CreateModel( | ||
| name="Achievement", | ||
| fields=[ | ||
| ( | ||
| "id", | ||
| models.BigAutoField( | ||
| auto_created=True, | ||
| primary_key=True, | ||
| serialize=False, | ||
| verbose_name="ID", | ||
| ), | ||
| ), | ||
| ("slug", models.CharField(max_length=64)), | ||
| ("tier", models.CharField(max_length=64)), | ||
| ("unlocked_at", models.DateTimeField(auto_now_add=True)), | ||
| ( | ||
| "user", | ||
| models.ForeignKey( | ||
| on_delete=django.db.models.deletion.CASCADE, | ||
| to=settings.AUTH_USER_MODEL, | ||
| ), | ||
| ), | ||
| ], | ||
| options={ | ||
| "unique_together": {("user", "slug", "tier")}, | ||
| }, | ||
| ), | ||
| migrations.CreateModel( | ||
| name="PostVote", | ||
| fields=[ | ||
| ( | ||
| "id", | ||
| models.BigAutoField( | ||
| auto_created=True, | ||
| primary_key=True, | ||
| serialize=False, | ||
| verbose_name="ID", | ||
| ), | ||
| ), | ||
| ( | ||
| "vote_type", | ||
| models.CharField( | ||
| choices=[("like", "Like"), ("dislike", "Dislike")], max_length=7 | ||
| ), | ||
| ), | ||
| ( | ||
| "post", | ||
| models.ForeignKey( | ||
| on_delete=django.db.models.deletion.CASCADE, | ||
| related_name="votes", | ||
| to="leaderboard.discussionpost", | ||
| ), | ||
| ), | ||
| ( | ||
| "user", | ||
| models.ForeignKey( | ||
| on_delete=django.db.models.deletion.CASCADE, | ||
| to=settings.AUTH_USER_MODEL, | ||
| ), | ||
| ), | ||
| ], | ||
| options={ | ||
| "unique_together": {("user", "post")}, | ||
| }, | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| # Generated by Django 5.1.5 on 2026-03-28 11:42 | ||
|
|
||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ("leaderboard", "0021_leetcodeuser_calendar_data_usernames_ac_uname_and_more"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AlterField( | ||
| model_name="organization", | ||
| name="join_code", | ||
| field=models.CharField(blank=True, max_length=10, null=True, unique=True), | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| # Generated by Django 5.1.5 on 2026-03-29 11:09 | ||
|
|
||
| from django.db import migrations | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ("leaderboard", "0019_codechefuser_calendar_data"), | ||
| ("leaderboard", "0022_alter_organization_join_code"), | ||
| ] | ||
|
|
||
| operations = [] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| # Generated by Django 5.1.5 on 2026-03-29 11:10 | ||
|
|
||
| import leaderboard.models | ||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ("leaderboard", "0023_merge_20260329_1109"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AlterField( | ||
| model_name="codeforcesuser", | ||
| name="last_activity", | ||
| field=models.BigIntegerField( | ||
| default=leaderboard.models.get_current_timestamp | ||
| ), | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| # Generated by Django 5.1.5 on 2026-03-29 12:30 | ||
|
|
||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ("leaderboard", "0024_alter_codeforcesuser_last_activity"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AlterUniqueTogether( | ||
| name="unifiedscorehistory", | ||
| unique_together=set(), | ||
| ), | ||
| migrations.AddField( | ||
| model_name="unifiedscorehistory", | ||
| name="user_id", | ||
| field=models.IntegerField(db_index=True, default=0), | ||
| ), | ||
| migrations.AlterUniqueTogether( | ||
| name="unifiedscorehistory", | ||
| unique_together={("user_id", "date")}, | ||
| ), | ||
| ] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -275,6 +275,7 @@ class UnifiedScoreHistory(models.Model): | |
| per-platform breakdown. Written by a Celery beat task every day. | ||
| Used by the Unified trend analysis heatmap and line chart. | ||
| """ | ||
| user_id = models.IntegerField(db_index=True, default=0) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check for existing UnifiedScoreHistory rows and potential conflicts
ast-grep --pattern 'class UnifiedScoreHistory($$$):
$$$
'
# Look for any existing data migration that backfills user_id
rg -n 'UnifiedScoreHistory' --glob '**/migrations/*.py' -A 5Repository: OpenLake/Leaderboard-Pro Length of output: 2658 🏁 Script executed: # Get the full migration file to see if UnifiedScoreHistory is being created or altered
cat -n api/leaderboard/migrations/0002_alter_codeforcesuser_last_activity_and_more.py | head -100Repository: OpenLake/Leaderboard-Pro Length of output: 1963 🏁 Script executed: # Check if there's a data migration that backfills user_id
rg -l 'RunPython|RunSQL' api/leaderboard/migrations/ | xargs -I {} grep -l 'user_id\|UnifiedScoreHistory' {} 2>/dev/nullRepository: OpenLake/Leaderboard-Pro Length of output: 50 🏁 Script executed: # Check trends_views.py for how it queries UnifiedScoreHistory
rg -n 'UnifiedScoreHistory' api/ -A 3 -B 1 | grep -E '(trends_views|\.filter|\.objects)'Repository: OpenLake/Leaderboard-Pro Length of output: 1872 🏁 Script executed: # List all migrations in order
ls -la api/leaderboard/migrations/*.py | head -20Repository: OpenLake/Leaderboard-Pro Length of output: 1222 🏁 Script executed: # Find migrations that modify UnifiedScoreHistory after 0002
rg -l 'UnifiedScoreHistory' api/leaderboard/migrations/*.py | sortRepository: OpenLake/Leaderboard-Pro Length of output: 146 🏁 Script executed: # Check the celery task that updates UnifiedScoreHistory
rg -n 'update_or_create' api/leaderboard/celery.py -B 5 -A 10 | grep -A 10 'UnifiedScoreHistory'Repository: OpenLake/Leaderboard-Pro Length of output: 658 🏁 Script executed: # Read migration 0025 that alters UnifiedScoreHistory
cat -n api/leaderboard/migrations/0025_alter_unifiedscorehistory_unique_together_and_more.pyRepository: OpenLake/Leaderboard-Pro Length of output: 949 🏁 Script executed: # Check if there are any migrations between 0002 and 0025 that might touch UnifiedScoreHistory
rg -n 'UnifiedScoreHistory' api/leaderboard/migrations/000*.pyRepository: OpenLake/Leaderboard-Pro Length of output: 189 🏁 Script executed: # Search for any data migrations (numbered migrations with RunPython) that might backfill user_id
find api/leaderboard/migrations -name "*.py" -type f -exec grep -l "RunPython\|raw_sql" {} \; | xargs grep -l "user_id\|UnifiedScoreHistory" 2>/dev/nullRepository: OpenLake/Leaderboard-Pro Length of output: 52 🏁 Script executed: # Check if there are any migrations after 0025 that might fix the user_id values
ls api/leaderboard/migrations/002*.pyRepository: OpenLake/Leaderboard-Pro Length of output: 499 🏁 Script executed: # Look at the celery task more broadly to understand the context
rg -n 'def.*unified' api/leaderboard/celery.py -i -A 20 | head -50Repository: OpenLake/Leaderboard-Pro Length of output: 912 Migration 0025 adds Migration 0002 created Any existing records in the table will have A data migration must backfill Additionally, 🤖 Prompt for AI Agents |
||
| username = models.CharField(max_length=64, db_index=True) | ||
| date = models.DateField(db_index=True) | ||
|
|
||
|
|
@@ -291,7 +292,7 @@ class UnifiedScoreHistory(models.Model): | |
|
|
||
| class Meta: | ||
| # one record per user per day | ||
| unique_together = ("username", "date") | ||
| unique_together = ("user_id", "date") | ||
| ordering = ["date"] | ||
|
Comment on lines
293
to
296
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check how trends_views.py queries UnifiedScoreHistory
rg -n 'UnifiedScoreHistory' --type py -B 2 -A 10 | head -100Repository: OpenLake/Leaderboard-Pro Length of output: 7785 🏁 Script executed: # See the complete query and score_map logic in trends_views.py around the UnifiedScoreHistory filter
sed -n '330,420p' api/leaderboard/trends_views.pyRepository: OpenLake/Leaderboard-Pro Length of output: 3617 🏁 Script executed: # Check if username can be duplicate for different user_ids in the unified ranking
grep -n 'username' api/leaderboard/analytics.py | head -30Repository: OpenLake/Leaderboard-Pro Length of output: 2036 🏁 Script executed: # Search for score_map usage in trends_views.py
rg -n 'score_map' api/leaderboard/trends_views.py -B 2 -A 2Repository: OpenLake/Leaderboard-Pro Length of output: 514 🏁 Script executed: # Check the exact iteration logic in build_unified_ranking to confirm one row per User
sed -n '160,195p' api/leaderboard/analytics.pyRepository: OpenLake/Leaderboard-Pro Length of output: 1712 🏁 Script executed: # Check if username field constraint exists or if it's truly only unique on (user_id, date)
grep -A 5 'unique_together' api/leaderboard/models.py | grep -A 5 'UnifiedScoreHistory'Repository: OpenLake/Leaderboard-Pro Length of output: 210 🏁 Script executed: # Verify the User model to understand if user.username is unique
rg -A 10 'class User' api/leaderboard/models.pyRepository: OpenLake/Leaderboard-Pro Length of output: 1795 Data loss risk: In records = UnifiedScoreHistory.objects.filter(username=username, date__gte=cutoff)
score_map = {r.date.isoformat(): r.total_score for r in records}The constraint This occurs because Add
🧰 Tools🪛 Ruff (0.15.7)[warning] 296-296: Mutable default value for class attribute (RUF012) 🤖 Prompt for AI Agents |
||
|
|
||
| def __str__(self): | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Migration will fail on existing data: missing data migration step.
The migration sequence:
(username, date)constraintuser_idfield withdefault=0(user_id, date)constraintIf any existing
UnifiedScoreHistoryrows share the samedate, step 3 will fail with a unique constraint violation because they all haveuser_id=0.Insert a
RunPythondata migration between steps 2 and 3 to backfilluser_idfrom theusernamefield by looking up the correspondingUserNames.user_id.🛠️ Suggested migration structure
🧰 Tools
🪛 Ruff (0.15.7)
[warning] 12-26: Mutable default value for class attribute
(RUF012)
🤖 Prompt for AI Agents