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
4 changes: 3 additions & 1 deletion config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
"testimonials",
"patches",
"asciidoctor_sandbox",
"contributions",
]

AUTH_USER_MODEL = "users.User"
Expand Down Expand Up @@ -386,7 +387,8 @@
)

# GitHub settings

GITHUB_API_REST_URL = "https://api.github.com/repos"
GITHUB_API_GRAPHQL_URL = "https://api.github.com/graphql"
GITHUB_TOKEN = env("GITHUB_TOKEN", default=None)
JDOODLE_API_CLIENT_ID = env("JDOODLE_API_CLIENT_ID", "")
JDOODLE_API_CLIENT_SECRET = env("JDOODLE_API_CLIENT_SECRET", "")
Expand Down
8 changes: 8 additions & 0 deletions config/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ def __contains__(self, item):
def __getitem__(self, item):
return None

def setdefault(self, key, default=None):
return None


# Disable our logging
logging.disable(logging.CRITICAL)
Expand All @@ -18,6 +21,11 @@ def __getitem__(self, item):

DEBUG = False

# Disable debug toolbar in tests
DEBUG_TOOLBAR_CONFIG = {
"SHOW_TOOLBAR_CALLBACK": lambda request: False,
}

OAUTH2_PROVIDER_APPLICATION_MODEL = "oauth2_provider.Application"
OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL = "oauth2_provider.AccessToken"
OAUTH2_PROVIDER_ID_TOKEN_MODEL = "oauth2_provider.IDToken"
Expand Down
Empty file added contributions/__init__.py
Empty file.
205 changes: 205 additions & 0 deletions contributions/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
from django.contrib import admin
from django.db.models import Count

from .models import (
Email,
GitContribution,
GitProfile,
GithubContribution,
GithubProfile,
Identity,
Wg21Contribution,
Wg21Profile,
)


class ReadOnlyInlineMixin:
"""Base mixin for read-only inline admin classes."""

extra = 0
readonly_fields = ["created", "modified"]


class ReadOnlyAdminMixin:
"""Base mixin for read-only admin classes."""

readonly_fields = ["created", "modified"]

def has_add_permission(self, request):
return False


class GitProfileInline(ReadOnlyInlineMixin, admin.TabularInline):
model = GitProfile
fields = ["name", "email", "identity"]
autocomplete_fields = ["email", "identity"]


class GithubProfileInline(ReadOnlyInlineMixin, admin.TabularInline):
model = GithubProfile
fields = ["name", "github_user_id", "identity"]
autocomplete_fields = ["identity"]


class Wg21ProfileInline(ReadOnlyInlineMixin, admin.TabularInline):
model = Wg21Profile
fields = ["name", "identity"]
autocomplete_fields = ["identity"]


class GithubContributionInline(ReadOnlyInlineMixin, admin.TabularInline):
model = GithubContribution
fields = ["type", "contributed_at", "repo", "info"]


class Wg21ContributionInline(ReadOnlyInlineMixin, admin.TabularInline):
model = Wg21Contribution
fields = ["info", "comment", "contributed_at"]


class GitContributionInline(ReadOnlyInlineMixin, admin.TabularInline):
model = GitContribution
fields = ["contributed_at", "repo", "info", "comment"]


@admin.register(Email)
class EmailAdmin(ReadOnlyAdminMixin, admin.ModelAdmin):
list_display = ["email", "git_profile_count", "created"]
search_fields = ["email"]
ordering = ["-created"]
inlines = [GitProfileInline]

def get_queryset(self, request):
return (
super()
.get_queryset(request)
.annotate(_git_profile_count=Count("git_profiles", distinct=True))
)

@admin.display(
description="Git Profiles",
ordering="_git_profile_count",
)
def git_profile_count(self, obj):
return obj._git_profile_count


class ProfileTypeFilter(admin.SimpleListFilter):
"""Filter identities by profile type."""

title = "profile type"
parameter_name = "profile_type"

PROFILE_FILTERS = {
"git": "gitprofile_profiles__isnull",
"github": "githubprofile_profiles__isnull",
"wg21": "wg21profile_profiles__isnull",
}

def lookups(self, request, model_admin):
return (
("git", "Has Git Profile"),
("github", "Has GitHub Profile"),
("wg21", "Has WG21 Profile"),
)

def queryset(self, request, queryset):
if filter_field := self.PROFILE_FILTERS.get(self.value()):
return queryset.filter(**{filter_field: False}).distinct()
return queryset


@admin.register(Identity)
class IdentityAdmin(admin.ModelAdmin):
list_display = ["name", "profile_type", "needs_review", "created"]
list_filter = [ProfileTypeFilter, "needs_review", "created"]
search_fields = ["name", "description"]
readonly_fields = ["created", "modified"]
ordering = ["-created"]
inlines = [GitProfileInline, GithubProfileInline, Wg21ProfileInline]

def get_queryset(self, request):
return (
super()
.get_queryset(request)
.annotate(
_git_profile_count=Count("gitprofile_profiles", distinct=True),
_github_profile_count=Count("githubprofile_profiles", distinct=True),
_wg21_profile_count=Count("wg21profile_profiles", distinct=True),
)
)

@admin.display(description="Profile Type")
def profile_type(self, obj):
if obj._git_profile_count > 0:
return "Git"
if obj._github_profile_count > 0:
return "GitHub"
if obj._wg21_profile_count > 0:
return "WG21"
return "—"


@admin.register(GithubProfile)
class GithubProfileAdmin(ReadOnlyAdminMixin, admin.ModelAdmin):
list_display = ["name", "github_user_id", "identity", "created"]
list_filter = ["created"]
search_fields = ["name", "identity__name"]
autocomplete_fields = ["identity"]
ordering = ["-created"]
inlines = [GithubContributionInline]


@admin.register(GitProfile)
class GitProfileAdmin(ReadOnlyAdminMixin, admin.ModelAdmin):
list_display = ["name", "email", "identity", "created"]
list_filter = ["created"]
search_fields = ["name", "email__email", "identity__name"]
autocomplete_fields = ["email", "identity"]
ordering = ["-created"]
inlines = [GitContributionInline]


@admin.register(Wg21Profile)
class Wg21ProfileAdmin(ReadOnlyAdminMixin, admin.ModelAdmin):
list_display = ["name", "identity", "created"]
list_filter = ["created"]
search_fields = ["name", "identity__name"]
autocomplete_fields = ["identity"]
ordering = ["-created"]
inlines = [Wg21ContributionInline]


@admin.register(GithubContribution)
class GithubContributionAdmin(ReadOnlyAdminMixin, admin.ModelAdmin):
list_display = ["profile", "type", "repo", "contributed_at", "info", "created"]
list_filter = ["type", "contributed_at", "created"]
search_fields = ["profile__name", "repo", "comment", "info"]
autocomplete_fields = ["profile"]
ordering = ["-contributed_at"]
date_hierarchy = "contributed_at"


@admin.register(Wg21Contribution)
class Wg21ContributionAdmin(ReadOnlyAdminMixin, admin.ModelAdmin):
list_display = ["profile", "info", "comment", "contributed_at", "created"]
list_filter = ["contributed_at", "created"]
search_fields = ["profile__name", "info", "comment"]
autocomplete_fields = ["profile"]
ordering = ["-contributed_at"]


@admin.register(GitContribution)
class GitContributionAdmin(ReadOnlyAdminMixin, admin.ModelAdmin):
list_display = ["profile", "repo", "info", "contributed_at", "created"]
list_filter = ["contributed_at", "created", "repo"]
search_fields = [
"profile__name",
"profile__email__email",
"repo",
"comment",
"info",
]
autocomplete_fields = ["profile"]
ordering = ["-contributed_at"]
date_hierarchy = "contributed_at"
6 changes: 6 additions & 0 deletions contributions/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class ContributionsConfig(AppConfig):
default_auto_field = "django.db.models.AutoField"
name = "contributions"
1 change: 1 addition & 0 deletions contributions/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
BULK_CREATE_BATCH_SIZE = 4000
Empty file.
Empty file.
21 changes: 21 additions & 0 deletions contributions/management/commands/update_contributions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import djclick as click

from contributions.tasks import update_contributions


@click.command()
@click.option(
"--github-token",
help="Optional GitHub API token (defaults to system token)",
default=None,
)
def command(github_token):
"""Update the contributions list for all versions.

This command iterates through all versions and updates their contribution
data from GitHub. If no token is provided, it uses the system default token.
"""
click.secho("Starting contributions update...", fg="green")
update_contributions.delay(github_token=github_token)

click.secho("Contributions update queued!", fg="green")
Loading