Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
41 changes: 41 additions & 0 deletions backend/apps/mentorship/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from apps.mentorship.models.mentor import Mentor
from apps.mentorship.models.module import Module
from apps.mentorship.models.program import Program
from apps.mentorship.models.task import Task
from apps.mentorship.models.task_level import TaskLevel


class MenteeAdmin(admin.ModelAdmin):
Expand Down Expand Up @@ -86,8 +88,47 @@ class ProgramAdmin(admin.ModelAdmin):
filter_horizontal = ("admins",)


class TaskAdmin(admin.ModelAdmin):
"""Admin view for Task model."""

list_display = (
"issue",
"assignee",
"module",
"status",
"assigned_at",
"deadline_at",
)

search_fields = (
"issue__title",
"assignee__github_user__login",
"module__name",
)

list_filter = ("status", "module", "assignee")


class TaskLevelAdmin(admin.ModelAdmin):
"""Admin view for TaskLevel model."""

list_display = (
"description",
"labels",
"module",
"name",
)

search_fields = (
"name",
"module__name",
)


admin.site.register(MenteeProgram, MenteeProgramAdmin)
admin.site.register(Mentee, MenteeAdmin)
admin.site.register(Mentor, MentorAdmin)
admin.site.register(Module, ModuleAdmin)
admin.site.register(Program, ProgramAdmin)
admin.site.register(Task, TaskAdmin)
admin.site.register(TaskLevel, TaskLevelAdmin)
102 changes: 102 additions & 0 deletions backend/apps/mentorship/migrations/0002_module_labels_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Generated by Django 5.2.4 on 2025-07-23 19:42

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("github", "0032_user_github_user_created_at_desc_and_more"),
("mentorship", "0001_initial"),
]
operations = [
migrations.AddField(
model_name="module",
name="labels",
field=models.JSONField(blank=True, default=list, verbose_name="Labels"),
),
migrations.CreateModel(
name="Task",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("nest_created_at", models.DateTimeField(auto_now_add=True)),
("nest_updated_at", models.DateTimeField(auto_now=True)),
(
"assigned_at",
models.DateTimeField(
auto_now_add=True,
help_text="Timestamp when the task was assigned to the mentee.",
),
),
(
"deadline_at",
models.DateTimeField(
blank=True,
help_text="Optional deadline for the task.",
null=True,
),
),
(
"metadata",
models.JSONField(blank=True, default=dict, help_text="Optional data"),
),
(
"status",
models.CharField(
choices=[
("TODO", "To Do"),
("IN_PROGRESS", "In Progress"),
("IN_REVIEW", "In Review"),
("COMPLETED", "Completed"),
],
default="TODO",
max_length=20,
verbose_name="Task status",
),
),
(
"assignee",
models.ForeignKey(
blank=True,
help_text="The mentee assigned to this task.",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="tasks",
to="github.user",
),
),
(
"issue",
models.ForeignKey(
help_text="The GitHub issue this task corresponds to.",
on_delete=django.db.models.deletion.PROTECT,
related_name="mentorship_tasks",
to="github.issue",
),
),
(
"module",
models.ForeignKey(
help_text="The module this task is part of.",
on_delete=django.db.models.deletion.CASCADE,
related_name="tasks",
to="mentorship.module",
),
),
],
options={
"verbose_name_plural": "Tasks",
"db_table": "mentorship_tasks",
"ordering": ["deadline_at"],
"unique_together": {("issue", "assignee")},
},
),
]
65 changes: 65 additions & 0 deletions backend/apps/mentorship/migrations/0003_tasklevel_task_level.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Generated by Django 5.2.4 on 2025-07-23 19:51

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("mentorship", "0002_module_labels_task"),
]

operations = [
migrations.CreateModel(
name="TaskLevel",
fields=[
(
"id",
models.BigAutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
(
"description",
models.TextField(blank=True, default="", verbose_name="Description"),
),
("labels", models.JSONField(blank=True, default=list, verbose_name="Labels")),
("name", models.CharField(max_length=200, verbose_name="Name")),
(
"module",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="task_levels",
to="mentorship.module",
verbose_name="Module",
),
),
(
"needs",
models.ManyToManyField(
blank=True,
related_name="required_by",
to="mentorship.tasklevel",
verbose_name="Prerequisite Levels",
),
),
],
options={
"verbose_name_plural": "Task Levels",
"db_table": "mentorship_task_levels",
"ordering": ["name"],
},
),
migrations.AddField(
model_name="task",
name="level",
field=models.ForeignKey(
blank=True,
help_text="The difficulty level of this task.",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="tasks",
to="mentorship.tasklevel",
),
),
]
2 changes: 2 additions & 0 deletions backend/apps/mentorship/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@
from .mentor_module import MentorModule
from .module import Module
from .program import Program
from .task import Task
from .task_level import TaskLevel
25 changes: 25 additions & 0 deletions backend/apps/mentorship/models/common/status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""Mentorship app start/end range."""

from django.db import models


class Status(models.Model):
"""Defines the possible states of a task."""

class Meta:
abstract = True

class StatusChoices(models.TextChoices):
"""Status choices."""

TODO = "TODO", "To Do"
IN_PROGRESS = "IN_PROGRESS", "In Progress"
IN_REVIEW = "IN_REVIEW", "In Review"
COMPLETED = "COMPLETED", "Completed"

status = models.CharField(
max_length=20,
choices=StatusChoices.choices,
default=StatusChoices.TODO,
verbose_name="Task status",
)
12 changes: 11 additions & 1 deletion backend/apps/mentorship/models/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
from django.db import models

from apps.common.models import TimestampedModel
from apps.mentorship.models.common import ExperienceLevel, MatchingAttributes, StartEndRange
from apps.mentorship.models.common import (
ExperienceLevel,
MatchingAttributes,
StartEndRange,
)


class Module(ExperienceLevel, MatchingAttributes, StartEndRange, TimestampedModel):
Expand Down Expand Up @@ -42,6 +46,12 @@ class Meta:
verbose_name="Project",
)

labels = models.JSONField(
blank=True,
default=list,
verbose_name="Labels",
)

# M2Ms.
mentors = models.ManyToManyField(
"mentorship.Mentor",
Expand Down
88 changes: 88 additions & 0 deletions backend/apps/mentorship/models/task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""Task model for the Mentorship app."""

from __future__ import annotations

from django.db import models

from apps.common.models import TimestampedModel


class Task(TimestampedModel):
"""Connects a Module, a GitHub Issue, and a Mentee to track work."""

class Meta:
db_table = "mentorship_tasks"
verbose_name_plural = "Tasks"
unique_together = ("issue", "assignee")
ordering = ["deadline_at"]

class StatusChoices(models.TextChoices):
"""Status choices."""

TODO = "TODO", "To Do"
IN_PROGRESS = "IN_PROGRESS", "In Progress"
IN_REVIEW = "IN_REVIEW", "In Review"
COMPLETED = "COMPLETED", "Completed"

# FKs.
assignee = models.ForeignKey(
"github.User",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="tasks",
help_text="The mentee assigned to this task.",
)

issue = models.ForeignKey(
"github.Issue",
on_delete=models.PROTECT,
related_name="mentorship_tasks",
help_text="The GitHub issue this task corresponds to.",
)

level = models.ForeignKey(
"mentorship.TaskLevel",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="tasks",
help_text="The difficulty level of this task.",
)

module = models.ForeignKey(
"mentorship.Module",
on_delete=models.CASCADE,
related_name="tasks",
help_text="The module this task is part of.",
)

assigned_at = models.DateTimeField(
auto_now_add=True,
help_text="Timestamp when the task was assigned to the mentee.",
)

deadline_at = models.DateTimeField(
null=True, blank=True, help_text="Optional deadline for the task."
)

metadata = models.JSONField(
default=dict,
blank=True,
help_text="Optional data",
)

status = models.CharField(
max_length=20,
choices=StatusChoices.choices,
default=StatusChoices.TODO,
verbose_name="Task status",
)

def __str__(self) -> str:
"""Return a human-readable representation of the task."""
return (
f"Task for '{self.issue.title}' assigned to {self.assignee.login}"
if self.assignee
else f"Task: {self.issue.title} (Unassigned)"
)
Loading