diff --git a/lms/models/grouping.py b/lms/models/grouping.py index 2ed72a66e8..9cd95d15be 100644 --- a/lms/models/grouping.py +++ b/lms/models/grouping.py @@ -171,6 +171,11 @@ class MoodleGroup(Grouping): class Course(Grouping): __mapper_args__ = {"polymorphic_identity": Grouping.Type.COURSE} + assignments = sa.orm.relationship( + "Assignment", secondary="assignment_grouping", viewonly=True + ) + """Assignments that belong to this course.""" + def set_group_sets(self, group_sets: list[dict]): """ Store this course's available group sets. diff --git a/lms/routes.py b/lms/routes.py index ccb7b595bf..ba09f24e46 100644 --- a/lms/routes.py +++ b/lms/routes.py @@ -209,6 +209,8 @@ def includeme(config): # pylint:disable=too-many-statements config.add_route("admin.courses", "/admin/courses") config.add_route("admin.course", "/admin/course/{id_}") + config.add_route("admin.assignment", "/admin/assignment/{id_}") + config.add_route("admin.email", "/admin/email") config.add_route( "admin.email.preview.instructor_email_digest", diff --git a/lms/services/assignment.py b/lms/services/assignment.py index 01644509d8..0c99a60a6b 100644 --- a/lms/services/assignment.py +++ b/lms/services/assignment.py @@ -188,6 +188,9 @@ def upsert_assignment_groupings( ) ) + def get_by_id(self, id_: int) -> Assignment | None: + return self._db.query(Assignment).filter_by(id=id_).one_or_none() + def factory(_context, request): return AssignmentService( diff --git a/lms/templates/admin/assignment/show.html.jinja2 b/lms/templates/admin/assignment/show.html.jinja2 new file mode 100644 index 0000000000..2293e65e9b --- /dev/null +++ b/lms/templates/admin/assignment/show.html.jinja2 @@ -0,0 +1,14 @@ +{% import "lms:templates/admin/macros.html.jinja2" as macros %} +{% extends "lms:templates/admin/base.html.jinja2" %} +{% block header %}Assignment {{ assignment.id }}{% endblock %} +{% block content %} +
+
+ Assignment + {{ macros.disabled_text_field("ID", assignment.id) }} + {{ macros.disabled_text_field("Title", assignment.title) }} + {{ macros.disabled_text_field("Document", assignment.document_url) }} + {{ macros.created_updated_fields(assignment) }} +
+
+{% endblock %} diff --git a/lms/templates/admin/course/show.html.jinja2 b/lms/templates/admin/course/show.html.jinja2 index 98d6d34b54..e2a3883d47 100644 --- a/lms/templates/admin/course/show.html.jinja2 +++ b/lms/templates/admin/course/show.html.jinja2 @@ -10,6 +10,12 @@ {{ macros.disabled_text_field("H ID", course.authority_provided_id) }} {{ macros.created_updated_fields(course) }} + +
+ Assignments + {{ macros.assignments_table(request, course.assignments) }} +
+
Application Instance {{ macros.instance_preview(request, course.application_instance) }} diff --git a/lms/templates/admin/macros.html.jinja2 b/lms/templates/admin/macros.html.jinja2 index 7cdbf18bf5..0d85ee1bbe 100644 --- a/lms/templates/admin/macros.html.jinja2 +++ b/lms/templates/admin/macros.html.jinja2 @@ -240,3 +240,10 @@ }); {% endmacro %} +{% macro assignments_table(request, assignments) %} + {{ object_list_table(request, 'admin.assignment', assignments, + fields=[ + {"label": "Title", "name": "title"}, + {"label": "Resource link id", "name": "resource_link_id"}, + ]) }} +{% endmacro %} diff --git a/lms/views/admin/assignment.py b/lms/views/admin/assignment.py new file mode 100644 index 0000000000..2754f1fe52 --- /dev/null +++ b/lms/views/admin/assignment.py @@ -0,0 +1,32 @@ +from pyramid.httpexceptions import HTTPNotFound +from pyramid.view import view_config, view_defaults + +from lms.models import Assignment +from lms.security import Permissions + + +@view_defaults(request_method="GET", permission=Permissions.ADMIN) +class AdminAssignmentViews: + def __init__(self, request) -> None: + self.request = request + self.assignment_service = request.find_service(name="assignment") + + @view_config( + route_name="admin.assignment", + request_method="GET", + renderer="lms:templates/admin/assignment/show.html.jinja2", + permission=Permissions.STAFF, + ) + def show(self): + assignment_id = self.request.matchdict["id_"] + assignment = self._get_or_404(assignment_id) + + return { + "assignment": assignment, + } + + def _get_or_404(self, id_) -> Assignment: + if assignment := self.assignment_service.get_by_id(id_=id_): + return assignment + + raise HTTPNotFound() diff --git a/tests/unit/lms/services/assignment_test.py b/tests/unit/lms/services/assignment_test.py index 614e7a3b83..e6a359058a 100644 --- a/tests/unit/lms/services/assignment_test.py +++ b/tests/unit/lms/services/assignment_test.py @@ -205,6 +205,12 @@ def test_upsert_assignment_grouping(self, svc, assignment, db_session): ] ) + def test_get_by_id(self, svc, db_session): + assignment = factories.Assignment() + db_session.flush() + + assert assignment == svc.get_by_id(assignment.id) + @pytest.fixture def svc(self, db_session, misc_plugin, grouping_plugin): return AssignmentService(db_session, misc_plugin, grouping_plugin) diff --git a/tests/unit/lms/views/admin/assignment_test.py b/tests/unit/lms/views/admin/assignment_test.py new file mode 100644 index 0000000000..d2a60b3908 --- /dev/null +++ b/tests/unit/lms/views/admin/assignment_test.py @@ -0,0 +1,30 @@ +from unittest.mock import sentinel + +import pytest +from pyramid.httpexceptions import HTTPNotFound + +from lms.views.admin.assignment import AdminAssignmentViews + + +class TestAdminAssignmentViews: + def test_show(self, pyramid_request, assignment_service, views): + pyramid_request.matchdict["id_"] = sentinel.id_ + + response = views.show() + + assignment_service.get_by_id.assert_called_once_with(id_=sentinel.id_) + + assert response == { + "assignment": assignment_service.get_by_id.return_value, + } + + def test_show_not_found(self, pyramid_request, assignment_service, views): + pyramid_request.matchdict["id_"] = sentinel.id_ + assignment_service.get_by_id.return_value = None + + with pytest.raises(HTTPNotFound): + views.show() + + @pytest.fixture + def views(self, pyramid_request): + return AdminAssignmentViews(pyramid_request) diff --git a/tests/unit/lms/views/admin/course_test.py b/tests/unit/lms/views/admin/course_test.py index fc540ced39..195bdbf2b8 100644 --- a/tests/unit/lms/views/admin/course_test.py +++ b/tests/unit/lms/views/admin/course_test.py @@ -8,7 +8,7 @@ @pytest.mark.usefixtures("course_service", "organization_service") -class TestAdminCouseViews: +class TestAdminCourseViews: def test_show(self, pyramid_request, course_service, views): pyramid_request.matchdict["id_"] = sentinel.id_