Skip to content
Draft
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
Empty file.
44 changes: 44 additions & 0 deletions openedx_learning/apps/assessment_criteria/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from django.contrib import admin

from .models import (
AssessmentCriteria,
AssessmentCriteriaGroup,
StudentAssessmentCriteriaStatus,
StudentCompetencyStatus,
)


@admin.register(AssessmentCriteriaGroup)
class AssessmentCriteriaGroupAdmin(admin.ModelAdmin):
list_display = ("id", "name", "course_id", "parent", "ordering", "logic_operator", "competency_tag")
list_filter = ("logic_operator",)
search_fields = ("name",)


@admin.register(AssessmentCriteria)
class AssessmentCriteriaAdmin(admin.ModelAdmin):
list_display = (
"id",
"group",
"course_id",
"rule_type",
"rule_payload",
"retake_rule",
"competency_tag",
"object_tag",
)
list_filter = ("rule_type", "retake_rule")


@admin.register(StudentAssessmentCriteriaStatus)
class StudentAssessmentCriteriaStatusAdmin(admin.ModelAdmin):
list_display = ("id", "assessment_criteria", "user", "status", "timestamp")
list_filter = ("status",)
search_fields = ("user__username", "user__email")


@admin.register(StudentCompetencyStatus)
class StudentCompetencyStatusAdmin(admin.ModelAdmin):
list_display = ("id", "competency_tag", "user", "status", "timestamp")
list_filter = ("status",)
search_fields = ("user__username", "user__email")
248 changes: 248 additions & 0 deletions openedx_learning/apps/assessment_criteria/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
"""
Assessment criteria API.

Use these helpers instead of manipulating models directly, so future logic can
stay centralized here.
"""
from __future__ import annotations

from django.db import models

from .models import (
AssessmentCriteria,
AssessmentCriteriaGroup,
StudentAssessmentCriteriaStatus,
StudentCompetencyStatus,
)
from .models.student_status import StudentStatus


AssessmentCriteriaGroupDoesNotExist = AssessmentCriteriaGroup.DoesNotExist
AssessmentCriteriaDoesNotExist = AssessmentCriteria.DoesNotExist
StudentAssessmentCriteriaStatusDoesNotExist = StudentAssessmentCriteriaStatus.DoesNotExist
StudentCompetencyStatusDoesNotExist = StudentCompetencyStatus.DoesNotExist


def create_assessment_criteria_group(
*,
parent: AssessmentCriteriaGroup | None,
competency_tag,
name: str,
ordering: int,
logic_operator: str | None = None,
) -> AssessmentCriteriaGroup:
"""
Create and return an AssessmentCriteriaGroup.
"""
group = AssessmentCriteriaGroup(
parent=parent,
competency_tag=competency_tag,
name=name,
ordering=ordering,
logic_operator=logic_operator,
)
group.full_clean()
group.save()
return group


def get_assessment_criteria_group(group_id: int) -> AssessmentCriteriaGroup | None:
"""
Return a group by id, or None if not found.
"""
return AssessmentCriteriaGroup.objects.filter(id=group_id).first()


def list_assessment_criteria_groups(
*,
parent: AssessmentCriteriaGroup | None = None,
) -> models.QuerySet[AssessmentCriteriaGroup]:
"""
Return groups, optionally filtered by parent.
"""
qs = AssessmentCriteriaGroup.objects.all()
if parent is not None:
qs = qs.filter(parent=parent)
return qs.order_by("ordering", "id")


def update_assessment_criteria_group(
group: AssessmentCriteriaGroup,
*,
parent: AssessmentCriteriaGroup | None | models.NOT_PROVIDED = models.NOT_PROVIDED,
competency_tag=models.NOT_PROVIDED,
name: str | models.NOT_PROVIDED = models.NOT_PROVIDED,
ordering: int | models.NOT_PROVIDED = models.NOT_PROVIDED,
logic_operator: str | None | models.NOT_PROVIDED = models.NOT_PROVIDED,
) -> AssessmentCriteriaGroup:
"""
Update and return an AssessmentCriteriaGroup.
"""
if parent is not models.NOT_PROVIDED:
group.parent = parent
if competency_tag is not models.NOT_PROVIDED:
group.competency_tag = competency_tag
if name is not models.NOT_PROVIDED:
group.name = name
if ordering is not models.NOT_PROVIDED:
group.ordering = ordering
if logic_operator is not models.NOT_PROVIDED:
group.logic_operator = logic_operator
group.full_clean()
group.save()
return group


def delete_assessment_criteria_group(group: AssessmentCriteriaGroup) -> None:
"""
Delete the provided AssessmentCriteriaGroup.
"""
group.delete()


def create_assessment_criteria(
*,
group: AssessmentCriteriaGroup,
object_tag,
competency_tag,
rule_type: str,
rule_payload: dict,
retake_rule: str,
) -> AssessmentCriteria:
"""
Create and return an AssessmentCriteria.
"""
criteria = AssessmentCriteria(
group=group,
object_tag=object_tag,
competency_tag=competency_tag,
rule_type=rule_type,
rule_payload=rule_payload,
retake_rule=retake_rule,
)
criteria.full_clean()
criteria.save()
return criteria


def get_assessment_criteria(criteria_id: int) -> AssessmentCriteria | None:
"""
Return assessment criteria by id, or None if not found.
"""
return AssessmentCriteria.objects.filter(id=criteria_id).first()


def list_assessment_criteria(
*,
group: AssessmentCriteriaGroup | None = None,
) -> models.QuerySet[AssessmentCriteria]:
"""
Return criteria, optionally filtered by group.
"""
qs = AssessmentCriteria.objects.all()
if group is not None:
qs = qs.filter(group=group)
return qs.order_by("id")


def update_assessment_criteria(
criteria: AssessmentCriteria,
*,
group: AssessmentCriteriaGroup | models.NOT_PROVIDED = models.NOT_PROVIDED,
object_tag=models.NOT_PROVIDED,
competency_tag=models.NOT_PROVIDED,
rule_type: str | models.NOT_PROVIDED = models.NOT_PROVIDED,
rule_payload: dict | models.NOT_PROVIDED = models.NOT_PROVIDED,
retake_rule: str | models.NOT_PROVIDED = models.NOT_PROVIDED,
) -> AssessmentCriteria:
"""
Update and return AssessmentCriteria.
"""
if group is not models.NOT_PROVIDED:
criteria.group = group
if object_tag is not models.NOT_PROVIDED:
criteria.object_tag = object_tag
if competency_tag is not models.NOT_PROVIDED:
criteria.competency_tag = competency_tag
if rule_type is not models.NOT_PROVIDED:
criteria.rule_type = rule_type
if rule_payload is not models.NOT_PROVIDED:
criteria.rule_payload = rule_payload
if retake_rule is not models.NOT_PROVIDED:
criteria.retake_rule = retake_rule
criteria.full_clean()
criteria.save()
return criteria


def delete_assessment_criteria(criteria: AssessmentCriteria) -> None:
"""
Delete the provided AssessmentCriteria.
"""
criteria.delete()


def set_student_assessment_criteria_status(
*,
assessment_criteria: AssessmentCriteria,
user,
status: StudentStatus,
) -> StudentAssessmentCriteriaStatus:
"""
Create or update student assessment criteria status.
"""
entry, _created = StudentAssessmentCriteriaStatus.objects.update_or_create(
assessment_criteria=assessment_criteria,
user=user,
defaults={"status": status},
)
return entry


def set_student_competency_status(
*,
competency_tag,
user,
status: StudentStatus,
) -> StudentCompetencyStatus:
"""
Create or update student competency status.
"""
entry, _created = StudentCompetencyStatus.objects.update_or_create(
competency_tag=competency_tag,
user=user,
defaults={"status": status},
)
return entry


def list_student_assessment_criteria_statuses(
*,
assessment_criteria: AssessmentCriteria | None = None,
user=None,
) -> models.QuerySet[StudentAssessmentCriteriaStatus]:
"""
Return student assessment criteria statuses with optional filters.
"""
qs = StudentAssessmentCriteriaStatus.objects.all()
if assessment_criteria is not None:
qs = qs.filter(assessment_criteria=assessment_criteria)
if user is not None:
qs = qs.filter(user=user)
return qs.order_by("-timestamp", "id")


def list_student_competency_statuses(
*,
competency_tag=None,
user=None,
) -> models.QuerySet[StudentCompetencyStatus]:
"""
Return student competency statuses with optional filters.
"""
qs = StudentCompetencyStatus.objects.all()
if competency_tag is not None:
qs = qs.filter(competency_tag=competency_tag)
if user is not None:
qs = qs.filter(user=user)
return qs.order_by("-timestamp", "id")
18 changes: 18 additions & 0 deletions openedx_learning/apps/assessment_criteria/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""
Assessment criteria Django application initialization.
"""
from django.apps import AppConfig


class AssessmentCriteriaConfig(AppConfig):
"""
Configuration for the assessment criteria Django application.
"""
name = "openedx_learning.apps.assessment_criteria"
verbose_name = "Learning Core > Assessment Criteria"
default_auto_field = "django.db.models.BigAutoField"
label = "oel_assessment_criteria"

def ready(self):
# Register signal handlers.
from . import events # pylint: disable=unused-import
Loading
Loading