Skip to main content

Overview

The GenLayer Points platform uses a relational database schema built with Django ORM. The data model is organized around users, contributions, leaderboards, and category-specific profiles.

Core Models

Base Model

All models inherit from BaseModel which provides automatic timestamps:
# utils/models.py
from django.db import models

class BaseModel(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        abstract = True

User Management

User Model

Location: users/models.py Custom user model with email-based authentication and Ethereum wallet integration:
class User(AbstractUser, BaseModel):
    # Authentication
    email = EmailField(unique=True)  # Primary identifier
    username = CharField(max_length=150, blank=True)
    
    # Basic Profile
    name = CharField(max_length=255, blank=True)
    address = CharField(max_length=42, blank=True, null=True)  # Ethereum address
    visible = BooleanField(default=True)  # API visibility
    
    # Extended Profile
    description = TextField(max_length=500, blank=True)
    banner_image_url = URLField(max_length=500, blank=True)
    profile_image_url = URLField(max_length=500, blank=True)
    website = URLField(blank=True)
    
    # Social Links
    contact_email = EmailField(blank=True)
    twitter_handle = CharField(max_length=50, blank=True)
    discord_handle = CharField(max_length=50, blank=True)
    telegram_handle = CharField(max_length=50, blank=True)
    linkedin_handle = CharField(max_length=100, blank=True)
    
    # GitHub OAuth
    github_username = CharField(max_length=100, blank=True)
    github_user_id = CharField(max_length=50, blank=True)
    github_access_token = TextField(blank=True)  # Encrypted
    github_linked_at = DateTimeField(null=True, blank=True)
    
    # Referral System
    referral_code = CharField(max_length=8, unique=True, null=True)
    referred_by = ForeignKey('self', null=True, on_delete=SET_NULL)
    
    USERNAME_FIELD = 'email'
Key Features:
  • Email as primary identifier (not username)
  • Unique Ethereum address constraint
  • Referral system for viral growth
  • GitHub OAuth for developer features
  • Visibility flag for hiding test accounts
Relationships:
  • One-to-One with Validator, Builder, or Steward profiles
  • One-to-Many with Contribution (user’s contributions)
  • Self-referential for referrals

Contribution System

Category Model

Location: contributions/models.py Defines user categories (Validator, Builder, Steward):
class Category(BaseModel):
    name = CharField(max_length=100, unique=True)  # "Validator"
    slug = SlugField(unique=True)  # "validator"
    description = TextField(blank=True)
    profile_model = CharField(max_length=100)  # "validators.Validator"

ContributionType Model

Location: contributions/models.py Defines types of contributions users can make:
class ContributionType(BaseModel):
    name = CharField(max_length=100)  # "Node Runner"
    slug = SlugField(max_length=100, unique=True)  # "node-runner"
    description = TextField(blank=True)
    category = ForeignKey(Category, on_delete=CASCADE)
    
    # Point Configuration
    min_points = PositiveIntegerField(default=0)
    max_points = PositiveIntegerField(default=100)
    
    # Behavior Flags
    is_default = BooleanField(default=False)  # Auto-create for validators
    is_submittable = BooleanField(default=True)  # Can users submit?
    
    # Help Text
    examples = JSONField(default=list)  # Example submissions
Example ContributionTypes:
  • validator-waitlist - Joining the waitlist
  • validator-node-upgrade - Upgrading node version
  • builder-welcome - Completing builder onboarding
  • builder-project - Submitting a project
  • steward-review - Reviewing submissions

Contribution Model

Location: contributions/models.py Actual contribution records with frozen point values:
class Contribution(BaseModel):
    user = ForeignKey(User, on_delete=CASCADE)
    contribution_type = ForeignKey(ContributionType, on_delete=CASCADE)
    mission = ForeignKey('Mission', null=True, on_delete=SET_NULL)
    
    # Points Calculation (FROZEN - never changes)
    points = PositiveIntegerField(default=0)  # Base points
    multiplier_at_creation = DecimalField(max_digits=5, decimal_places=2)
    frozen_global_points = PositiveIntegerField(default=0)  # points × multiplier
    
    # Metadata
    contribution_date = DateTimeField(null=True, blank=True)
    notes = TextField(blank=True)
Key Concept - Frozen Points:
Points are calculated once at contribution creation and never change, even if multipliers are updated later. This ensures fairness and prevents retroactive changes.
# Example
contribution = Contribution.objects.create(
    user=user,
    contribution_type=type,
    points=100,  # Base points
    multiplier_at_creation=1.5,  # Active multiplier at creation time
    frozen_global_points=150  # 100 × 1.5 = 150 (LOCKED)
)

SubmittedContribution Model

Location: contributions/models.py User submissions pending staff review:
class SubmittedContribution(BaseModel):
    id = UUIDField(primary_key=True, default=uuid4)  # Public-facing UUID
    
    user = ForeignKey(User, on_delete=CASCADE)
    contribution_type = ForeignKey(ContributionType, on_delete=CASCADE)
    mission = ForeignKey('Mission', null=True, on_delete=SET_NULL)
    contribution_date = DateTimeField()
    notes = TextField(blank=True)
    
    # State Management
    STATE_CHOICES = [
        ('pending', 'Pending Review'),
        ('accepted', 'Accepted'),
        ('rejected', 'Rejected'),
        ('more_info_needed', 'More Information Needed')
    ]
    state = CharField(max_length=20, choices=STATE_CHOICES, default='pending')
    
    # Review Fields
    proposed_points = PositiveIntegerField(null=True)  # Auto-calculated
    staff_reply = TextField(blank=True)
    reviewed_by = ForeignKey(User, null=True, on_delete=SET_NULL)
    reviewed_at = DateTimeField(null=True)
    
    # Steward Assignment
    assigned_to = ForeignKey(User, null=True, on_delete=SET_NULL)
    
    # Proposal System (for stewards)
    proposed_action = CharField(max_length=20, null=True)
    proposed_contribution_type = ForeignKey(ContributionType, null=True)
    proposed_user = ForeignKey(User, null=True)
    proposed_staff_reply = TextField(blank=True)
    
    # Link to approved contribution
    converted_contribution = ForeignKey(Contribution, null=True)
Workflow:
  1. User submits via /api/v1/contributions/ (with reCAPTCHA)
  2. Creates SubmittedContribution with state='pending'
  3. Steward reviews and updates state
  4. If accepted, creates actual Contribution record
  5. Links via converted_contribution

Evidence Model

Location: contributions/models.py Supporting evidence for contributions:
class Evidence(BaseModel):
    # Parent relationship (one of these)
    contribution = ForeignKey(Contribution, null=True, on_delete=CASCADE)
    submitted_contribution = ForeignKey(SubmittedContribution, null=True, on_delete=CASCADE)
    
    # Evidence data
    description = TextField(blank=True)
    url = URLField(blank=True)
    file = FileField(blank=True, null=True)  # DEPRECATED - no longer used
File uploads are disabled (issue #212). Users can only provide URLs and text descriptions.

Leaderboard System

GlobalLeaderboardMultiplier Model

Location: leaderboard/models.py Time-based multipliers for contribution types:
class GlobalLeaderboardMultiplier(BaseModel):
    contribution_type = ForeignKey(ContributionType, on_delete=CASCADE)
    multiplier_value = DecimalField(max_digits=10, decimal_places=2, default=1.0)
    valid_from = DateTimeField()  # When this multiplier becomes active
    description = CharField(max_length=255, blank=True)
    notes = TextField(blank=True)
Example:
# Before launch: 1.5x multiplier
GlobalLeaderboardMultiplier.objects.create(
    contribution_type=node_runner_type,
    multiplier_value=1.5,
    valid_from=datetime(2024, 1, 1)
)

# After launch: reduce to 1.0x
GlobalLeaderboardMultiplier.objects.create(
    contribution_type=node_runner_type,
    multiplier_value=1.0,
    valid_from=datetime(2024, 6, 1)
)
Contributions created before June 1st will have multiplier_at_creation=1.5, while those after will have 1.0.

LeaderboardEntry Model

Location: leaderboard/models.py User rankings per leaderboard type:
class LeaderboardEntry(BaseModel):
    user = ForeignKey(User, on_delete=CASCADE)
    type = CharField(max_length=50, choices=LEADERBOARD_TYPES)
    total_points = PositiveIntegerField(default=0)
    rank = PositiveIntegerField(null=True, blank=True)
    graduation_date = DateTimeField(null=True)  # For graduation leaderboard
    
    class Meta:
        unique_together = ['user', 'type']
Leaderboard Types:
  • validator - Active validators (has Validator profile)
  • builder - Active builders (has Builder profile)
  • validator-waitlist - Waitlist participants (not graduated)
  • validator-waitlist-graduation - Graduated validators (frozen points)
Point Calculation:
Sum of all validator category contributions:
points = Contribution.objects.filter(
    user=user,
    contribution_type__category__slug='validator'
).aggregate(Sum('frozen_global_points'))['frozen_global_points__sum']

ReferralPoints Model

Location: leaderboard/models.py Tracks 10% bonus from referred users:
class ReferralPoints(BaseModel):
    user = OneToOneField(User, on_delete=CASCADE)
    builder_points = PositiveIntegerField(default=0)  # 10% of referred builders
    validator_points = PositiveIntegerField(default=0)  # 10% of referred validators
Calculation:
# Get all users referred by this user
referred_users = User.objects.filter(referred_by=referrer)

# Calculate 10% of their builder contributions
builder_points = int(Contribution.objects.filter(
    user__in=referred_users,
    contribution_type__category__slug='builder'
).aggregate(Sum('frozen_global_points'))['frozen_global_points__sum'] * 0.1)

# Calculate 10% of their validator contributions
validator_points = int(Contribution.objects.filter(
    user__in=referred_users,
    contribution_type__category__slug='validator'
).aggregate(Sum('frozen_global_points'))['frozen_global_points__sum'] * 0.1)

Category Profiles

Validator Model

Location: validators/models.py
class Validator(BaseModel):
    user = OneToOneField(User, on_delete=CASCADE)
    node_version = CharField(max_length=50, blank=True)
    # Additional validator-specific fields

Builder Model

Location: builders/models.py
class Builder(BaseModel):
    user = OneToOneField(User, on_delete=CASCADE)
    # Builder journey tracking
    # Project submissions

Steward Model

Location: stewards/models.py
class Steward(BaseModel):
    user = OneToOneField(User, on_delete=CASCADE)
    permissions = JSONField(default=dict)  # Role-based permissions
    # Steward-specific fields

Additional Models

Mission Model

Location: contributions/models.py Time-limited featured missions:
class Mission(BaseModel):
    name = CharField(max_length=200)
    description = TextField()  # Supports Markdown
    start_date = DateTimeField(null=True)
    end_date = DateTimeField(null=True)
    contribution_type = ForeignKey(ContributionType, on_delete=CASCADE)

ContributionHighlight Model

Location: contributions/models.py Featured contributions for the homepage:
class ContributionHighlight(BaseModel):
    contribution = ForeignKey(Contribution, on_delete=CASCADE)
    title = CharField(max_length=200)
    description = TextField()

Database Relationships

Indexing Strategy

Migration Management

Creating Migrations

# After model changes
python manage.py makemigrations

# Preview SQL
python manage.py sqlmigrate contributions 0001

# Apply migrations
python manage.py migrate

Common Migration Patterns

# Add a new field with default
operations = [
    migrations.AddField(
        model_name='user',
        name='new_field',
        field=models.CharField(max_length=100, default=''),
    ),
]

Next Steps

Authentication Flow

SIWE authentication implementation

Backend Setup

Django development environment

Architecture

System architecture overview

Environment Variables

Configuration reference

Build docs developers (and LLMs) love