Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions backend/apps/github/management/commands/github_update_users.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
"""A command to update GitHub users."""

import logging

from django.core.management.base import BaseCommand
from django.db.models import Q, Sum

from apps.common.models import BATCH_SIZE
from apps.github.models.repository_contributor import RepositoryContributor
from apps.github.models.user import User
Expand Down Expand Up @@ -46,14 +44,16 @@ def handle(self, *args, **options):
.annotate(total_contributions=Sum("contributions_count"))
}
users = []
for idx, user in enumerate(active_users[offset:]):
for idx, user in enumerate( active_users[offset:] ):
prefix = f"{idx + offset + 1} of {active_users_count - offset}"
self.stdout.write(f"{prefix:<10} {user.title}\n")

user.contributions_count = user_contributions.get(user.id, 0)
users.append(user)

if not len(users) % BATCH_SIZE:
User.bulk_save(users, fields=("contributions_count",))

User.bulk_save(users, fields=("contributions_count",))
self.stdout.write( f"{prefix:<10} {user.title}\n" )
# update contributions
user.contributions_count = user_contributions.get( user.id, 0 )
# RECALCULATE SCORE
user.calculated_score = user.calculate_score()
# add to list
users.append( user )
if not len( users ) % BATCH_SIZE:
User.bulk_save( users, fields=("contributions_count", "calculated_score") )

User.bulk_save( users, fields=("contributions_count", "calculated_score") )
15 changes: 15 additions & 0 deletions backend/apps/github/migrations/0045_add_calculated_score.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("github", "0044_user_indexes"),
]
operations = [
migrations.AddField(
model_name="user",
name="calculated_score",
field=models.FloatField(
default=0,
help_text="Computed score based on contribution signals",
),
),
]
37 changes: 36 additions & 1 deletion backend/apps/github/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,33 @@

class User(NodeModel, GenericUserModel, TimestampedModel, UserIndexMixin):
"""User model."""
calculated_score = models.FloatField( default=0 )

def calculate_score(self):
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
return (
self._contribution_score()
+ self._consistency_score()
+ self._recency_score()
)

def _contribution_score(self):
return (self.contributions_count or 0) * 1.0
def _consistency_score(self):
base = self.contributions_count or 0
if base > 50:
return 25
elif base > 10:
return 10
return 0
def _recency_score(self):
return 5 if (self.contributions_count or 0) > 0 else 0
Comment thread
coderabbitai[bot] marked this conversation as resolved.

def _leadership_score(self):
score = 0
# Simple leadership signal
if self.is_owasp_staff:
score += 30
return score

class Meta:
"""Model options."""
Expand Down Expand Up @@ -55,7 +82,13 @@ class Meta:
)

contributions_count = models.PositiveIntegerField(
verbose_name="Contributions count", default=0
verbose_name="Contributions count",
default=0
)

calculated_score = models.FloatField(
default=0,
help_text="Computed score based on contribution signals"
)
Comment on lines +89 to 92
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search the GitHub app for model and migration references to `calculated_score`.
rg -n -C2 "calculated_score|AddField\(|AlterField\(" backend/apps/github

Repository: OWASP/Nest

Length of output: 39202


Create a migration for the calculated_score field.

The calculated_score field is defined in the model but lacks a corresponding migration. When the ORM attempts to persist data to this field, the column won't exist in the database, causing failures. Add a migration using python manage.py makemigrations to add this column.

🧰 Tools
🪛 Ruff (0.15.6)

[warning] 82-85: Class field calculated_score is defined multiple times

Remove duplicate field definition for calculated_score

(PIE794)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/apps/github/models/user.py` around lines 82 - 85, The model field
calculated_score (models.FloatField) was added to the User model but no
migration exists; run Django's migration flow to create and apply the schema
change: run python manage.py makemigrations to generate a migration that adds
the calculated_score column (keeping the default=0 and help_text), inspect and
commit the produced migration file, then run python manage.py migrate to apply
it to the database so the column exists at runtime.


contribution_data = models.JSONField(
Expand Down Expand Up @@ -170,6 +203,8 @@ def from_github(self, gh_user) -> None:
setattr(self, model_field, value)

self.is_bot = gh_user.type == "Bot"
self.calculated_score = self.calculate_score()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Score becomes stale when contributions_count is updated via management command.

The github_update_users.py management command (lines 27-59) updates contributions_count via bulk_save() without calling from_github(). This means calculated_score will not be recalculated when contributions are aggregated through that path, causing the two fields to be out of sync.

Since the management command appears to be the primary mechanism for aggregating contribution counts from RepositoryContributor, this will leave calculated_score stale for most users.

Consider one of these approaches:

  1. Update the management command to also recalculate scores:
# In github_update_users.py, after setting contributions_count:
user.contributions_count = user_contributions.get(user.id, 0)
user.calculated_score = user.calculate_score()
users.append(user)

# And update bulk_save to include the new field:
User.bulk_save(users, fields=("contributions_count", "calculated_score"))
  1. Use a database-level computed/generated field if using PostgreSQL and Django 5+, so the score is always derived from stored values.

  2. Override the model's save() method to always recalculate the score when contributions_count changes (though this won't help with bulk_update).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/apps/github/models/user.py` at line 185, The calculated_score is set
only in the model via self.calculated_score = self.calculate_score(), so updates
to contributions_count performed by the github_update_users.py management
command using bulk_save() leave calculated_score stale; fix by updating the
management command to recalculate scores before bulk_save (for each User set
user.contributions_count = ... and user.calculated_score =
user.calculate_score(), then call User.bulk_save(users,
fields=("contributions_count","calculated_score"))), or alternatively implement
a DB computed/generated column or override User.save() to recompute
calculated_score when contributions_count changes (note override won’t affect
bulk_update paths).



def get_absolute_url(self):
"""Get absolute URL for the user."""
Expand Down