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
6 changes: 6 additions & 0 deletions .annotation_safe_list.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ openedx_content.Unit:
".. no_pii:": "This model has no PII"
openedx_content.UnitVersion:
".. no_pii:": "This model has no PII"
openedx_learning.HistoricalCompetencyCriteriaGroup:
".. no_pii:": "This model has no PII"
openedx_learning.HistoricalCompetencyCriterion:
".. no_pii:": "This model has no PII"
openedx_learning.HistoricalCompetencyRuleProfile:
".. no_pii:": "This model has no PII"
social_django.Association:
".. no_pii:": "This model has no PII"
social_django.Code:
Expand Down
6 changes: 6 additions & 0 deletions .importlinter
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
root_packages =
openedx_learning
openedx_content
openedx_catalog
openedx_tagging
openedx_django_lib
openedx_core
Expand All @@ -26,6 +27,11 @@ layers =
# Content: authoring-side models and APIs.
openedx_content

# Catalog: CatalogCourse/CourseRun. CompetencyCriteriaGroup and CompetencyRuleProfile
# (openedx_learning) scope to a CourseRun, so this must sit below openedx_learning; it doesn't
# depend on tagging or content, so it can sit above openedx_tagging.
openedx_catalog

# Tagging is very simple & fundamental. Should probably not depend on any other Django apps.
openedx_tagging

Expand Down
3 changes: 3 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,8 @@ files =
[mypy-organizations.*]
follow_untyped_imports = True

[mypy-simple_history.*]
follow_untyped_imports = True

[mypy.plugins.django-stubs]
django_settings_module = "projects.dev"
2 changes: 2 additions & 0 deletions requirements/base.in
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ rules<4.0 # Django extension for rules-based authorization check
tomlkit # Parses and writes TOML configuration files

edx-organizations # Implemented the "Organization" model that CatalogCourse/CourseRun are keyed to

django-simple-history # History tracking for CBE criteria definitions, per ADR-0003
4 changes: 3 additions & 1 deletion requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ django-crum==0.7.9
django-model-utils==5.0.0
# via edx-organizations
django-simple-history==3.13.0
# via edx-organizations
# via
# -r requirements/base.in
# edx-organizations
django-waffle==5.0.0
# via
# edx-django-utils
Expand Down
64 changes: 63 additions & 1 deletion src/openedx_learning/applets/cbe/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,22 @@
"""
from django.contrib import admin

from .models import CompetencyTaxonomy
from openedx_django_lib.admin_utils import ReadOnlyModelAdmin

from .models import (
CompetencyMasteryStatus,
CompetencyTaxonomy,
StudentCompetencyCriteriaGroupStatus,
StudentCompetencyCriteriaStatus,
StudentCompetencyStatus,
)

__all__ = [
"CompetencyTaxonomyAdmin",
"CompetencyMasteryStatusAdmin",
"StudentCompetencyCriteriaStatusAdmin",
"StudentCompetencyCriteriaGroupStatusAdmin",
"StudentCompetencyStatusAdmin",
]


Expand All @@ -18,4 +30,54 @@ class CompetencyTaxonomyAdmin(admin.ModelAdmin):
list_filter = ["enabled"]


class CompetencyMasteryStatusAdmin(ReadOnlyModelAdmin):
"""
The CompetencyMasteryStatus model admin.
"""
list_display = ["id", "status"]


class StudentCompetencyCriteriaStatusAdmin(ReadOnlyModelAdmin):
"""
The StudentCompetencyCriteriaStatus model admin.

Deliberately read-only: an editable page would be the staff-correction
path, which ADR-0004 Decision 6 requires to take a row lock and recompute
every ancestor status, and none of that machinery exists yet.
"""
list_display = ["user", "criterion", "status", "created", "modified"]
list_filter = ["status"]
list_select_related = ["user", "criterion", "status"]


class StudentCompetencyCriteriaGroupStatusAdmin(ReadOnlyModelAdmin):
"""
The StudentCompetencyCriteriaGroupStatus model admin.

Deliberately read-only: an editable page would be the staff-correction
path, which ADR-0004 Decision 6 requires to take a row lock and recompute
every ancestor status, and none of that machinery exists yet.
"""
list_display = ["user", "group", "status", "created", "modified"]
list_filter = ["status"]
list_select_related = ["user", "group", "status"]


class StudentCompetencyStatusAdmin(ReadOnlyModelAdmin):
"""
The StudentCompetencyStatus model admin.

Deliberately read-only: an editable page would be the staff-correction
path, which ADR-0004 Decision 6 requires to take a row lock and recompute
every ancestor status, and none of that machinery exists yet.
"""
list_display = ["user", "tag", "status", "created", "modified"]
list_filter = ["status"]
list_select_related = ["user", "tag", "status"]


admin.site.register(CompetencyTaxonomy, CompetencyTaxonomyAdmin)
admin.site.register(CompetencyMasteryStatus, CompetencyMasteryStatusAdmin)
admin.site.register(StudentCompetencyCriteriaStatus, StudentCompetencyCriteriaStatusAdmin)
admin.site.register(StudentCompetencyCriteriaGroupStatus, StudentCompetencyCriteriaGroupStatusAdmin)
admin.site.register(StudentCompetencyStatus, StudentCompetencyStatusAdmin)
35 changes: 35 additions & 0 deletions src/openedx_learning/applets/cbe/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""
Models for Competency-Based Education (CBE).
"""

from .competency_taxonomy import CompetencyTaxonomy
from .criteria import (
CompetencyCriteriaGroup,
CompetencyCriterion,
CompetencyRuleProfile,
LogicOperator,
RuleType,
validate_rule_payload,
)
from .learner_status import (
CompetencyMasteryStatus,
MasteryStatus,
StudentCompetencyCriteriaGroupStatus,
StudentCompetencyCriteriaStatus,
StudentCompetencyStatus,
)

__all__ = [
"CompetencyTaxonomy",
"CompetencyCriteriaGroup",
"CompetencyCriterion",
"CompetencyRuleProfile",
"LogicOperator",
"RuleType",
"validate_rule_payload",
"MasteryStatus",
"CompetencyMasteryStatus",
"StudentCompetencyCriteriaStatus",
"StudentCompetencyCriteriaGroupStatus",
"StudentCompetencyStatus",
]
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
"""
Models for Competency-Based Education (CBE).
The CompetencyTaxonomy model.
"""
from django.db import models
from django.utils.translation import gettext_lazy as _

from openedx_tagging.models import Taxonomy

__all__ = [
Expand Down Expand Up @@ -35,6 +38,19 @@ class CompetencyTaxonomy(Taxonomy):
.. no_pii:
"""

taxonomy_overrides_org = models.BooleanField(
default=False,
help_text=_(
"Resolves a tie when assigning a CompetencyRuleProfile to a CompetencyCriterion (ADR-0002 "
"Decision 4): if both an organization-scoped profile and a taxonomy-scoped profile from this "
"taxonomy apply to the same criterion, False (the default) assigns the organization-scoped "
"profile, and True assigns this taxonomy's own profile instead, so it cannot be locally "
"weakened by an organization. Nothing reads this field yet: organization-scoped "
"CompetencyRuleProfile rows do not exist in this phase, so the tie it resolves cannot arise "
"until they do."
),
)

class Meta:
verbose_name = "Competency Taxonomy"
verbose_name_plural = "Competency Taxonomies"
Loading