def test_access_api_can_manage_moderators_update_student_no_session(self):
        """Users with no session can't update user"""
        update_user = UserFactory()
        api_user = UserFactory(lti_consumer=update_user.lti_consumer)

        lti_context = LTIContextFactory(lti_consumer=update_user.lti_consumer)
        forum = ForumFactory()
        forum.lti_contexts.add(lti_context)

        # Assign student group to user
        lti_context.sync_user_groups(update_user, ["student"])
        # Assign the permission
        assign_perm("can_manage_moderator", api_user, forum, True)
        #
        response = self.client.patch(
            f"/api/v1.0/users/{update_user.id}/add_group_moderator/",
            content_type="application/json",
        )
        self.assertEqual(response.status_code, 403)
        content = json.loads(response.content)
        self.assertEqual(
            content,
            {"detail": "Authentication credentials were not provided."})
        # Create the session and it should work
        self.client.force_login(api_user, "ashley.auth.backend.LTIBackend")
        session = self.client.session
        session[SESSION_LTI_CONTEXT_ID] = lti_context.id
        session.save()
        response = self.client.patch(
            f"/api/v1.0/users/{update_user.id}/add_group_moderator/",
            content_type="application/json",
        )
        self.assertEqual(response.status_code, 200)
    def test_access_api_can_manage_moderators_update_student_no_group_moderator(
            self):
        """If moderator group doesn't exist user can be updated and group created
        Case for forum created before this feature"""
        update_user = UserFactory()
        api_user = UserFactory(lti_consumer=update_user.lti_consumer)

        lti_context = LTIContextFactory(lti_consumer=update_user.lti_consumer)
        forum = ForumFactory()
        forum.lti_contexts.add(lti_context)
        # Add group student
        lti_context.sync_user_groups(update_user, ["student"])
        # Assign the permission
        assign_perm("can_manage_moderator", api_user, forum, True)
        # Creates the session
        self.client.force_login(api_user, "ashley.auth.backend.LTIBackend")
        session = self.client.session
        session[SESSION_LTI_CONTEXT_ID] = lti_context.id
        session.save()

        # Data to promote user to moderator
        response = self.client.patch(
            f"/api/v1.0/users/{update_user.id}/add_group_moderator/",
            content_type="application/json",
        )
        self.assertEqual(response.status_code, 200)
    def test_access_api_update_instructor_patch(self):
        """
        Standard instructor call to patch user is allowed but doesn't change anything,
        as serializer's attributes are read-only.
        """
        # Creates a forum
        user = UserFactory(id=1, public_username="******")
        lti_context = LTIContextFactory(lti_consumer=user.lti_consumer)
        forum = ForumFactory()
        forum.lti_contexts.add(lti_context)
        lti_context.sync_user_groups(user, ["instructor"])
        assign_perm("can_manage_moderator", user, forum, True)
        # Creates the session
        self.client.force_login(user, "ashley.auth.backend.LTIBackend")
        session = self.client.session
        session[SESSION_LTI_CONTEXT_ID] = lti_context.id
        session.save()
        data = {"id": 10, "public_username": "******"}
        response = self.client.patch(
            f"/api/v1.0/users/{user.id}/",
            json.dumps(data),
            content_type="application/json",
        )

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.json(), {"id": 1, "public_username": "******"})
 def test_access_api_update_instructor_post(self):
     """Post to update user is not allowed even for an instructor."""
     # Creates a forum
     user = UserFactory()
     lti_context = LTIContextFactory(lti_consumer=user.lti_consumer)
     forum = ForumFactory()
     forum.lti_contexts.add(lti_context)
     lti_context.sync_user_groups(user, ["instructor"])
     assign_perm("can_manage_moderator", user, forum, True)
     # Creates the session
     self.client.force_login(user, "ashley.auth.backend.LTIBackend")
     session = self.client.session
     session[SESSION_LTI_CONTEXT_ID] = lti_context.id
     session.save()
     response = self.client.post(
         f"/api/v1.0/users/{user.id}/",
         content_type="application/json",
     )
     self.assertEqual(response.status_code, 405)
     self.assertEqual(response.json(),
                      {"detail": 'Method "POST" not allowed.'})
Example #5
0
    def test_get_user_roles_other_context(self):
        """get_user_roles should return the list of the roles names without the specific
        patterns cg:{context.id}:role: only the label of the roles and only for the
        current context"""
        lti_consumer = LTIConsumerFactory()
        context = LTIContextFactory(lti_consumer=lti_consumer)
        context2 = LTIContextFactory(lti_consumer=lti_consumer)
        # Create user
        user = UserFactory(lti_consumer=lti_consumer)

        # Sync user groups in context with multiple roles
        context.sync_user_groups(user, ["role1", "role2"])
        # Sync user groups in context2 with other roles)
        context2.sync_user_groups(user, ["newgroup"])

        # check that the list of roles is returned
        self.assertCountEqual(
            ["role1", "role2"],
            context.get_user_roles(user),
        )
        self.assertCountEqual(
            ["newgroup"],
            context2.get_user_roles(user),
        )
Example #6
0
    def test_get_user_roles(self):
        """get_user_roles should return the list of the roles names
        without the specific patterns cg:{context.id}:role: only the label
        of the roles
        """
        lti_consumer = LTIConsumerFactory()
        context = LTIContextFactory(lti_consumer=lti_consumer)

        # Create two users
        user = UserFactory(lti_consumer=lti_consumer)
        user2 = UserFactory(lti_consumer=lti_consumer)

        # Sync user groups in context with multiple roles
        context.sync_user_groups(user, ["role1", "role2"])
        # check that the list of roles is returned
        self.assertCountEqual(
            ["role1", "role2"],
            context.get_user_roles(user),
        )
        # add an extra group
        instructor_group = Group.objects.create(
            name=f"{context.base_group_name}:role:instructor")
        user.groups.add(instructor_group)
        # check that the list of roles is returned
        self.assertCountEqual(
            ["role1", "role2", "instructor"],
            context.get_user_roles(user),
        )
        # check that group that are not roles get ignored
        other_group = Group.objects.create(
            name=f"{context.base_group_name}:other)")
        user2.groups.add(other_group)
        self.assertCountEqual(
            [],
            context.get_user_roles(user2),
        )
Example #7
0
    def test_can_tell_if_the_user_is_instructor_of_this_forum():
        """
        Given two users. User1 is student of the forum, User2 is instructor.
        We control that is_user_instructor can detect if a user is an instructor.
        Then a forum can be part of multiple contexts. If a user is instructor in one context,
        he is considered intructor of this forum in all contexts. We add a new context to the forum
        where user is instructor and test that user is now considered as instructor
        """

        # load template
        def get_rendered(topic, user):
            template = Template(
                "{% load custom_tags %}" +
                "{% if topic|is_user_instructor:user %}YES{% else %}NO{% endif %}"
            )
            context = Context({"topic": topic, "user": user})
            rendered = template.render(context)

            return rendered

        lti_consumer = LTIConsumerFactory()
        # Create two LTI Context
        context1 = LTIContextFactory(lti_consumer=lti_consumer)
        context2 = LTIContextFactory(lti_consumer=lti_consumer)
        # Create two users
        user1 = UserFactory(lti_consumer=lti_consumer)
        user2 = UserFactory(lti_consumer=lti_consumer)

        # Sync user1 groups in context1 with role "student"
        context1.sync_user_groups(user1, ["student"])
        # Sync user1 groups in context2 with role "instructor"
        context2.sync_user_groups(user1, ["instructor"])
        # Sync user2 groups in context1 with role "instructor"
        context1.sync_user_groups(user2, ["instructor"])

        # Create forum and add context1
        forum = ForumFactory(name="Initial forum name")
        forum.lti_contexts.add(context1)

        # Set up topic
        topic = TopicFactory(forum=forum,
                             poster=user1,
                             subject="topic création")

        # Chek that user1 is not instructor
        assert get_rendered(topic, user1) == "NO"
        # Chek that user2 is instructor
        assert get_rendered(topic, user2) == "YES"

        # Add forum to context2 where user1 has role "instructor"
        forum.lti_contexts.add(context2)
        # Check that user1 is now instructor as well
        assert get_rendered(topic, user1) == "YES"
Example #8
0
    def test_forum_moderation_list_forums_view(self):
        """
        Create forum in different LTIContext, make sure user can't move topics into it,
        even if he has access to this forum.
        """
        user = UserFactory()

        lti_context1 = LTIContextFactory(lti_consumer=user.lti_consumer)
        lti_context2 = LTIContextFactory(lti_consumer=user.lti_consumer)
        forum1Lti1 = ForumFactory(name="Forum1")
        forum2Lti1 = ForumFactory(name="Forum2")
        forum3Lti2 = ForumFactory(name="Forum3")
        forum4Lti2 = ForumFactory(name="Forum4")

        # User is instructor in both forums
        lti_context1.sync_user_groups(user, ["instructor"])
        lti_context2.sync_user_groups(user, ["instructor"])

        # Assign permission to the group for this forum
        self._init_forum(forum1Lti1, lti_context1)
        self._init_forum(forum2Lti1, lti_context1)
        self._init_forum(forum3Lti2, lti_context2)
        self._init_forum(forum4Lti2, lti_context2)

        # Create a post for a topic part of lti_context1
        topicForumLti1 = TopicFactory(forum=forum1Lti1)
        PostFactory(topic=topicForumLti1, )

        # Create a post for a topic part of lti_context2
        topicForumLti2 = TopicFactory(forum=forum3Lti2)
        PostFactory(topic=topicForumLti2, )

        # Create the session and log in lti_context1
        self.client.force_login(user, "ashley.auth.backend.LTIBackend")
        session = self.client.session
        session[SESSION_LTI_CONTEXT_ID] = lti_context1.id
        session.save()

        # Requests url to move the topic
        response = self.client.get(
            f"/forum/moderation/topic/{topicForumLti1.slug}-{topicForumLti1.id}/move/"
        )
        # Checks we are on the right page
        self.assertContains(response, "Move topic", html=True)

        # Forum2 part of lti_context1 should be proposed not the others
        self.assertContains(response, forum2Lti1.name)
        self.assertNotContains(response, forum3Lti2.name)
        self.assertNotContains(response, forum4Lti2.name)

        # Change the session to connect user to lti_context2
        self.client.force_login(user, "ashley.auth.backend.LTIBackend")
        session = self.client.session
        session[SESSION_LTI_CONTEXT_ID] = lti_context2.id
        session.save()

        # Forum4 part of lti_context2 should be proposed not the others
        response = self.client.get(
            f"/forum/moderation/topic/{topicForumLti2.slug}-{topicForumLti2.id}/move/"
        )
        self.assertContains(response, forum4Lti2.name)
        self.assertNotContains(response, forum2Lti1.name)
        self.assertNotContains(response, forum1Lti1.name)
Example #9
0
    def test_forum_moderation_list_forums_with_deleted_form(self):
        """
        Create three forums in the same LTIContext and archive one. Load the form to choose
        to which forum the user wants to move the topic. Control that the forum deleted is
        not part of the choice.
        """
        user = UserFactory()

        lti_context = LTIContextFactory(lti_consumer=user.lti_consumer)
        forum1 = ForumFactory(name="Forum1")
        forum2 = ForumFactory(name="Forum2")
        forum3 = ForumFactory(name="Forum3")

        lti_context.sync_user_groups(user, ["instructor"])

        # Assign permission to the group for this forum
        self._init_forum(forum1, lti_context)
        self._init_forum(forum2, lti_context)
        self._init_forum(forum3, lti_context)
        # Create a post for a topic part of lti_context
        topicForum1 = TopicFactory(forum=forum1)
        PostFactory(topic=topicForum1, )
        topicForum2 = TopicFactory(forum=forum2)
        PostFactory(topic=topicForum2, )
        topicForum3 = TopicFactory(forum=forum3)
        PostFactory(topic=topicForum3, )

        # Create the session and logged in lti_context
        self.client.force_login(user, "ashley.auth.backend.LTIBackend")
        session = self.client.session
        session[SESSION_LTI_CONTEXT_ID] = lti_context.id
        session.save()

        form = TopicMoveForm(user=user,
                             lti_context=lti_context,
                             topic=topicForum1)

        # Check that only the forum that is allowed is proposed as choice
        self.assertEqual(
            form.fields["forum"].choices,
            [
                (forum1.id, {
                    "label": " Forum1",
                    "disabled": True
                }),
                (
                    forum2.id,
                    "{} {}".format("-" * forum2.margin_level, forum2.name),
                ),
                (
                    forum3.id,
                    "{} {}".format("-" * forum3.margin_level, forum3.name),
                ),
            ],
        )

        # Archive the forum2
        forum2.archived = True
        forum2.save()

        form = TopicMoveForm(user=user,
                             lti_context=lti_context,
                             topic=topicForum1)

        # Check that forum2 is not proposed as choice anymore as it has been archived
        self.assertEqual(
            form.fields["forum"].choices,
            [
                (forum1.id, {
                    "label": " Forum1",
                    "disabled": True
                }),
                (
                    forum3.id,
                    "{} {}".format("-" * forum3.margin_level, forum3.name),
                ),
            ],
        )

        # We try to move topic from forum1 to forum2 even if forum2 has been archived
        response = self.client.post(
            f"/forum/moderation/topic/{topicForum1.slug}-{topicForum1.id}/move/",
            data={"forum": {forum2.id}},
            follow=True,
        )

        self.assertEqual(response.status_code, 200)

        # Control that we get an error and the move is not executed
        self.assertContains(
            response,
            f"Select a valid choice. {forum2.id} is not one of the available choices.",
            html=True,
        )
Example #10
0
    def test_forum_moderation_list_forums_form(self):
        """
        Create forum in different LTIContext. Load the form to choose to which forum
        user wants to move the topic. Try to move it to a forum that is not allowed and
        control that it's not possible.
        """
        user = UserFactory()

        lti_context1 = LTIContextFactory(lti_consumer=user.lti_consumer)
        lti_context2 = LTIContextFactory(lti_consumer=user.lti_consumer)
        forum1Lti1 = ForumFactory(name="Forum1")
        forum2Lti1 = ForumFactory(name="Forum2")
        forum3Lti2 = ForumFactory(name="Forum3")

        # User is instructor in both forums
        lti_context1.sync_user_groups(user, ["instructor"])
        lti_context2.sync_user_groups(user, ["instructor"])

        # Assign permission to the group for this forum
        self._init_forum(forum1Lti1, lti_context1)
        self._init_forum(forum2Lti1, lti_context1)
        self._init_forum(forum3Lti2, lti_context2)

        # Create a post for a topic part of lti_context1
        topicForumLti1 = TopicFactory(forum=forum1Lti1)
        PostFactory(topic=topicForumLti1, )

        # Create the session and logged in lti_context1
        self.client.force_login(user, "ashley.auth.backend.LTIBackend")
        session = self.client.session
        session[SESSION_LTI_CONTEXT_ID] = lti_context1.id
        session.save()

        form = TopicMoveForm(user=user,
                             lti_context=lti_context1,
                             topic=topicForumLti1)

        # Check that only the forum that is allowed is proposed as choice
        self.assertEqual(
            form.fields["forum"].choices,
            [
                (forum1Lti1.id, {
                    "label": " Forum1",
                    "disabled": True
                }),
                (
                    forum2Lti1.id,
                    "{} {}".format("-" * forum2Lti1.margin_level,
                                   forum2Lti1.name),
                ),
            ],
        )

        # We move the forum to topic2 part of the same lti_context1
        response = self.client.post(
            f"/forum/moderation/topic/{topicForumLti1.slug}-{topicForumLti1.id}/move/",
            data={"forum": {forum2Lti1.id}},
            follow=True,
        )
        self.assertEqual(response.status_code, 200)
        self.assertContains(response,
                            "This topic has been moved successfully.")

        # We force the request on the forum that is not allowed
        response = self.client.post(
            f"/forum/moderation/topic/{topicForumLti1.slug}-{topicForumLti1.id}/move/",
            data={"forum": {forum3Lti2.id}},
            follow=True,
        )
        self.assertEqual(response.status_code, 200)

        # Controls that we get an error and the move is not executed
        self.assertContains(
            response,
            f"Select a valid choice. {forum3Lti2.id} is not one of the available choices.",
            html=True,
        )
Example #11
0
    def test_sync_user_groups(self):
        """Test group synchronization"""

        # Create a LTI Consumer
        lti_consumer = LTIConsumerFactory()

        # Create 2 LTI Contexts for lti_consumer
        context1 = LTIContextFactory(lti_consumer=lti_consumer)
        context2 = LTIContextFactory(lti_consumer=lti_consumer)

        # Create an unrelated django group
        unrelated_group = Group.objects.create(name="unrelated_django_group")

        # Initialize a user with no group
        user = UserFactory(lti_consumer=lti_consumer)
        self.assertEqual(0, user.groups.count())

        # Sync user groups in context1 with role "student"
        context1.sync_user_groups(user, ["student"])
        self.assertCountEqual(
            [
                context1.base_group_name,
                f"{context1.base_group_name}:role:student"
            ],
            list(user.groups.values_list("name", flat=True)),
        )

        # Add the user to an unrelated django group
        user.groups.add(unrelated_group)

        # Sync user groups in context2 with multiple roles
        context2.sync_user_groups(user, ["role1", "role2"])
        self.assertCountEqual(
            [
                unrelated_group.name,
                context1.base_group_name,
                f"{context1.base_group_name}:role:student",
                context2.base_group_name,
                f"{context2.base_group_name}:role:role1",
                f"{context2.base_group_name}:role:role2",
            ],
            list(user.groups.values_list("name", flat=True)),
        )

        # Sync user groups in context 2 with another role
        context2.sync_user_groups(user, ["instructor"])
        self.assertCountEqual(
            [
                unrelated_group.name,
                context1.base_group_name,
                f"{context1.base_group_name}:role:student",
                context2.base_group_name,
                f"{context2.base_group_name}:role:instructor",
            ],
            list(user.groups.values_list("name", flat=True)),
        )

        # Sync user groups in context 1 with no role
        context1.sync_user_groups(user, [])
        self.assertCountEqual(
            [
                unrelated_group.name,
                context1.base_group_name,
                context2.base_group_name,
                f"{context2.base_group_name}:role:instructor",
            ],
            list(user.groups.values_list("name", flat=True)),
        )

        # Create another LTIConsumer
        lti_consumer2 = LTIConsumerFactory()

        # Create a LTI Context for lti_consumer2
        context3 = LTIContextFactory(lti_consumer=lti_consumer2)

        # Create a user for this LTI Context
        user2 = UserFactory(lti_consumer=lti_consumer2)

        # Check the PermissionDenied gets called as the user in not part of this LTIContext
        with self.assertRaises(PermissionDenied):
            context3.sync_user_groups(user, ["instructor"])

        # Check the PermissionDenied gets called as the user in not part of this LTIContext
        with self.assertRaises(PermissionDenied):
            context2.sync_user_groups(user2, [])
Example #12
0
    def test_sync_user_with_internal_moderators_groups(self):
        """
        Test that internal group moderator doesn't get removed using sync_user_groups
        """
        lti_consumer = LTIConsumerFactory()
        context = LTIContextFactory(lti_consumer=lti_consumer)
        # Initialize a user with no group
        user = UserFactory(lti_consumer=lti_consumer)
        self.assertEqual(0, user.groups.count())

        # Sync user groups in context with role "student"
        context.sync_user_groups(user, ["student"])
        self.assertCountEqual(
            [
                context.base_group_name,
                f"{context.base_group_name}:role:student"
            ],
            list(user.groups.values_list("name", flat=True)),
        )
        # Add the user to moderator group
        group_moderator_name = context.get_group_role_name(
            _FORUM_ROLE_MODERATOR)
        group_moderator = Group.objects.create(name=group_moderator_name)
        user.groups.add(group_moderator)
        user.save()
        # confirm group has been added
        self.assertCountEqual(
            [
                context.base_group_name,
                f"{context.base_group_name}:role:student",
                f"{context.base_group_name}:role:moderator",
            ],
            list(user.groups.values_list("name", flat=True)),
        )

        # Sync user groups in context with funnyrole group
        context.sync_user_groups(user, ["funnyrole"])
        self.assertCountEqual(
            ["funnyrole", "moderator"],
            context.get_user_roles(user),
        )
        # User should still have the moderator group
        self.assertCountEqual(
            [
                context.base_group_name,
                f"{context.base_group_name}:role:funnyrole",
                f"{context.base_group_name}:role:moderator",
            ],
            list(user.groups.values_list("name", flat=True)),
        )

        # creates a new context
        context2 = LTIContextFactory(lti_consumer=lti_consumer)
        context2.sync_user_groups(user, ["instructor"])
        # moderator group should not exist in this context
        self.assertCountEqual(
            [
                context.base_group_name,
                f"{context.base_group_name}:role:funnyrole",
                f"{context.base_group_name}:role:moderator",
                context2.base_group_name,
                f"{context2.base_group_name}:role:instructor",
            ],
            list(user.groups.values_list("name", flat=True)),
        )
Example #13
0
class ForumConversationTestPostCreateView(TestCase):
    """Test post form of a forum overridden from django_machina"""

    def setUp(self):
        super().setUp()
        # Setup
        # Create consumer, context and user
        self.lti_consumer = LTIConsumerFactory()
        self.context = LTIContextFactory(lti_consumer=self.lti_consumer)

        # Create forum and add context
        self.forum = ForumFactory(name="Forum")
        self.forum.lti_contexts.add(self.context)

    def test_list_active_users_empty_topic_with_no_post(self):
        """
        The form loads the list of active users for the current topic. We control that the list
        is empty when topic has no post
        """
        # Setup
        user1 = UserFactory(
            lti_consumer=self.lti_consumer,
            public_username="******",
        )
        self.context.sync_user_groups(user1, ["student"])

        # Set up topic and initial post
        topic = TopicFactory(forum=self.forum, poster=user1)
        # Load PostForm for user1
        form = PostForm(user=user1, forum=self.forum, topic=topic)
        # No post created yet, mentions should be empty
        assert form.fields["content"].widget.attrs["mentions"] == []

    def test_list_active_users_ignore_current_user(self):
        """
        The form loads the list of active users for the current topic. We control that the list
        ignores the current user
        """
        # Setup
        user1 = UserFactory(
            lti_consumer=self.lti_consumer,
            public_username="******",
        )
        user2 = UserFactory(
            lti_consumer=self.lti_consumer,
            public_username="******",
        )
        self.context.sync_user_groups(user1, ["student"])
        self.context.sync_user_groups(user2, ["student"])

        # Set up topic and initial post
        topic = TopicFactory(forum=self.forum, poster=user1)

        # Add a Post for user1
        PostFactory(topic=topic, poster=user1)

        # User2 loads the form
        form = PostForm(user=user2, forum=self.forum, topic=topic)
        # User1 must be listed in users
        assert form.fields["content"].widget.attrs["mentions"] == [
            {
                "name": "Benoit",
                "user": user1.id,
            }
        ]
        # User1 loads the form
        form = PostForm(user=user1, forum=self.forum, topic=topic)
        # Check current user is ignored in the list
        assert form.fields["content"].widget.attrs["mentions"] == []

    def test_list_active_users_ordered_by_alphabetical_order(self):
        """
        The form loads the list of active users for the current topic. We control that the list is
        rendered in alphabetical order
        """
        # Setup
        user1 = UserFactory(
            lti_consumer=self.lti_consumer,
            public_username="******",
        )
        user2 = UserFactory(
            lti_consumer=self.lti_consumer,
            public_username="******",
        )
        user3 = UserFactory(
            lti_consumer=self.lti_consumer,
            public_username="******",
        )
        user4 = UserFactory(lti_consumer=self.lti_consumer)
        self.context.sync_user_groups(user1, ["student"])
        self.context.sync_user_groups(user2, ["student"])
        self.context.sync_user_groups(user3, ["student"])

        # Set up topic and initial post
        topic = TopicFactory(forum=self.forum, poster=user1)

        # Add post with user1
        PostFactory(topic=topic, poster=user1)

        # user2 loads the form
        form = PostForm(
            user=user2,
            forum=self.forum,
            topic=topic,
        )
        # user2 sees user1
        assert form.fields["content"].widget.attrs["mentions"] == [
            {
                "name": "Benoit",
                "user": user1.id,
            },
        ]
        # Add posts from user2
        PostFactory(topic=topic, poster=user2)

        # Load form from user 3
        form = PostForm(user=user3, forum=self.forum, topic=topic)
        # Alfred should be before Benoit
        assert form.fields["content"].widget.attrs["mentions"] == [
            {
                "name": "Alfred",
                "user": user2.id,
            },
            {
                "name": "Benoit",
                "user": user1.id,
            },
        ]
        # Add posts from user3
        PostFactory(topic=topic, poster=user3)
        form = PostForm(user=user4, forum=self.forum, topic=topic)
        # Alfred should be before Aurélien and before Benoit
        assert form.fields["content"].widget.attrs["mentions"] == [
            {
                "name": "Alfred",
                "user": user2.id,
            },
            {
                "name": "Aurélien",
                "user": user3.id,
            },
            {
                "name": "Benoit",
                "user": user1.id,
            },
        ]
        # Loads topic form loaded when editing first post
        form = TopicForm(user=user4, forum=self.forum, topic=topic)
        # we should have the list of users identical than when post form is loaded
        assert form.fields["content"].widget.attrs["mentions"] == [
            {
                "name": "Alfred",
                "user": user2.id,
            },
            {
                "name": "Aurélien",
                "user": user3.id,
            },
            {
                "name": "Benoit",
                "user": user1.id,
            },
        ]

    def test_list_active_users_has_distinct_users(self):
        """
        The form loads the list of active users for the current topic. We control that the list
        only contains distinct users
        """
        user1 = UserFactory(
            lti_consumer=self.lti_consumer,
            public_username="******",
        )
        user2 = UserFactory(
            lti_consumer=self.lti_consumer,
            public_username="******",
        )
        self.context.sync_user_groups(user1, ["student"])
        self.context.sync_user_groups(user2, ["student"])

        # Set up topic and initial post
        topic = TopicFactory(forum=self.forum, poster=user1)

        # Add three posts from user1 and two from user2
        initial_post_count = Post.objects.count()
        PostFactory(topic=topic, poster=user1)
        PostFactory(topic=topic, poster=user1)
        PostFactory(topic=topic, poster=user1)
        PostFactory(topic=topic, poster=user2)
        PostFactory(topic=topic, poster=user2)
        # Confirms Posts got created
        self.assertEqual(Post.objects.count(), initial_post_count + 5)

        # user2 loads the form
        form = PostForm(user=user2, forum=self.forum, topic=topic)
        # user2 only see one time user1
        assert form.fields["content"].widget.attrs["mentions"] == [
            {
                "name": "Benoit",
                "user": user1.id,
            }
        ]

        # user2 loads topic form loaded when editing first post
        form = TopicForm(user=user2, forum=self.forum, topic=topic)
        # we should have the list of users identical than when post form is loaded
        assert form.fields["content"].widget.attrs["mentions"] == [
            {
                "name": "Benoit",
                "user": user1.id,
            }
        ]

        # user1 loads the form
        form = PostForm(user=user1, forum=self.forum, topic=topic)
        # user1 only see one time user1
        assert form.fields["content"].widget.attrs["mentions"] == [
            {
                "name": "Alfred",
                "user": user2.id,
            },
        ]

        # user1 loads topic form loaded when editing first post
        form = TopicForm(user=user1, forum=self.forum, topic=topic)
        # we should have the list of users identical than when post form is loaded
        assert form.fields["content"].widget.attrs["mentions"] == [
            {
                "name": "Alfred",
                "user": user2.id,
            },
        ]

    def test_list_active_users_only_concerns_writer_of_current_topic(self):
        """
        The form loads the list of active users for the current topic. We control that the list
        only contains writers involved in the current topic and no other users
        """
        user1 = UserFactory(
            lti_consumer=self.lti_consumer,
            public_username="******",
        )
        user2 = UserFactory(
            lti_consumer=self.lti_consumer,
            public_username="******",
        )
        user3 = UserFactory(
            lti_consumer=self.lti_consumer,
            public_username="******",
        )
        user4 = UserFactory(
            lti_consumer=self.lti_consumer,
        )
        self.context.sync_user_groups(user1, ["student"])
        self.context.sync_user_groups(user2, ["student"])
        self.context.sync_user_groups(user3, ["student"])
        self.context.sync_user_groups(user4, ["student"])

        # Set up topic and initial post
        topic = TopicFactory(forum=self.forum, poster=user1)
        # Add two posts for topic
        PostFactory(topic=topic, poster=user1)
        PostFactory(topic=topic, poster=user2)
        # user4 loads the form
        form = PostForm(user=user4, forum=self.forum, topic=topic)
        # Two users should be listed
        assert form.fields["content"].widget.attrs["mentions"] == [
            {
                "name": "Alfred",
                "user": user2.id,
            },
            {
                "name": "Benoit",
                "user": user1.id,
            },
        ]

        # Set up new topic and initial post with user3
        topic2 = TopicFactory(forum=self.forum, poster=user3)
        PostFactory(topic=topic2, poster=user3)

        # user4 loads the form for topic2
        form = PostForm(
            user=user4,
            forum=self.forum,
            topic=topic2,
        )
        assert form.fields["content"].widget.attrs["mentions"] == [
            {
                "name": "Aurélien",
                "user": user3.id,
            }
        ]
        # user4 loads topic, nothing should have changed as user3 only posted in another topic
        form = PostForm(
            user=user4,
            forum=self.forum,
            topic=topic,
        )
        assert form.fields["content"].widget.attrs["mentions"] == [
            {
                "name": "Alfred",
                "user": user2.id,
            },
            {
                "name": "Benoit",
                "user": user1.id,
            },
        ]

        # user4 loads topic form loaded when editing first post
        form = TopicForm(user=user4, forum=self.forum, topic=topic)
        # we should have the list of users identical than when post form is loaded
        assert form.fields["content"].widget.attrs["mentions"] == [
            {
                "name": "Alfred",
                "user": user2.id,
            },
            {
                "name": "Benoit",
                "user": user1.id,
            },
        ]

    def test_list_active_users_only_concerns_users_with_approved_posts(self):
        """
        The form loads the list of active users for the current topic. We control that the list
        only concerns users that have approved posts
        """
        user1 = UserFactory(
            lti_consumer=self.lti_consumer,
            public_username="******",
        )
        user2 = UserFactory(
            lti_consumer=self.lti_consumer,
            public_username="******",
        )

        self.context.sync_user_groups(user1, ["student"])
        self.context.sync_user_groups(user2, ["student"])

        topic = TopicFactory(forum=self.forum, poster=user1)
        post = PostFactory(topic=topic, poster=user1)

        form = PostForm(user=user2, forum=self.forum, topic=topic)
        assert form.fields["content"].widget.attrs["mentions"] == [
            {
                "name": "Benoit",
                "user": user1.id,
            }
        ]
        # Post of user1 gets unapproved
        post.approved = False
        post.save()
        # Load form from user2
        form = PostForm(
            user=user2,
            forum=self.forum,
            topic=topic,
        )
        # List of active users should be empty
        assert form.fields["content"].widget.attrs["mentions"] == []

    def test_list_active_users_only_concerns_active_users(self):
        """
        The form loads the list of active users for the current topic. We control that the list
        only concerns users that have the status active
        """
        user1 = UserFactory(
            lti_consumer=self.lti_consumer,
            public_username="******",
        )
        user2 = UserFactory(
            lti_consumer=self.lti_consumer,
            public_username="******",
        )
        self.context.sync_user_groups(user1, ["student"])
        self.context.sync_user_groups(user2, ["student"])

        topic = TopicFactory(forum=self.forum, poster=user1)
        # controls form topic, the one loaded when there's no post
        form = TopicForm(user=user2, forum=self.forum, topic=topic)
        assert form.fields["content"].widget.attrs.get("mentions", None) is None

        # controls post topic
        PostFactory(topic=topic, poster=user1)
        form = PostForm(user=user2, forum=self.forum, topic=topic)
        assert form.fields["content"].widget.attrs["mentions"] == [
            {
                "name": "Benoit",
                "user": user1.id,
            }
        ]
        # user1 becomes inactive
        user1.is_active = False
        user1.save()

        # Add another post to load the list in topic form
        PostFactory(topic=topic, poster=user1)
        # user3 loads the form for topic
        form = PostForm(user=user2, forum=self.forum, topic=topic)
        # We should only see user1 in the list of active users for this topic
        assert form.fields["content"].widget.attrs["mentions"] == []

        # user3 loads the form for first post, topic form is loaded
        form = TopicForm(user=user2, forum=self.forum, topic=topic)
        # mention param exist now but it's empty
        assert form.fields["content"].widget.attrs["mentions"] == []

    def test_access_topic_reply_form(self):
        """
        The post form in a created topic is overridden from django_machina,
        we control it still loads as expected
        """
        user = UserFactory(lti_consumer=self.lti_consumer)
        assign_perm("can_read_forum", user, self.forum)
        assign_perm("can_reply_to_topics", user, self.forum)

        # Set up topic and initial post
        topic = TopicFactory(forum=self.forum, poster=user)
        PostFactory(topic=topic)

        # authenticate the user related to consumer
        self.client.force_login(user)

        url_topic_reply = (
            f"/forum/forum/{self.forum.slug}-{self.forum.pk}"
            f"/topic/{topic.slug}-{topic.pk}/post/create/"
        )

        # Run
        response = self.client.get(url_topic_reply, follow=True)
        # Check
        assert response.status_code == 200

    def test_params_form_user_current_topic(self):
        """
        The form send forum and poster information to the editor
        """
        user1 = UserFactory(lti_consumer=self.lti_consumer)
        user2 = UserFactory(lti_consumer=self.lti_consumer)
        self.context.sync_user_groups(user1, ["student"])
        self.context.sync_user_groups(user1, ["student"])

        # Set up topic and initial post
        topic = TopicFactory(forum=self.forum, poster=user1)
        PostFactory(topic=topic, poster=user1)
        PostFactory(topic=topic, poster=user2)
        # Load TopicForm
        form = TopicForm(user=user1, forum=self.forum, topic=topic)
        assert form.fields["content"].widget.attrs["forum"] == topic.forum.id

        # PostForm has the parameters as well
        form = PostForm(user=user2, forum=self.forum, topic=topic)
        assert form.fields["content"].widget.attrs["forum"] == topic.forum.id
Example #14
0
    def test_render_block_is_admin_of_this_forum(self):
        """
        check that the admin icon is present on the different views
        as expected
        """
        lti_consumer = LTIConsumerFactory()
        # Create two users
        user1 = UserFactory(public_username="******",
                            lti_consumer=lti_consumer)
        user2 = UserFactory(public_username="******",
                            lti_consumer=lti_consumer)
        # Create an LTI Context
        context = LTIContextFactory(lti_consumer=lti_consumer)
        # Sync user1 groups in context with role "administrator"
        context.sync_user_groups(user1, ["administrator"])
        # Sync user1 groups in context with role "student"
        context.sync_user_groups(user2, ["student"])

        # Create forum and add context
        forum = ForumFactory(name="forum_test")
        forum.lti_contexts.add(context)

        # assign permission to the group for this forum
        self._init_forum(forum, context)

        # Set up topic
        topic1 = TopicFactory(forum=forum, poster=user1)

        # Set up post
        PostFactory.create(
            topic=topic1,
            poster=user1,
        )

        # log user1
        self.client.force_login(user1)

        # accessing forum view
        response = self.client.get(reverse("forum:index"))
        html = lxml.html.fromstring(response.content)
        forum_last_post = str(
            etree.tostring(html.cssselect(".forum-last-post")[0]))
        # control that the administrator's icon is present
        self.assertTrue((
            '<i class="icon_writer fas fa-award" aria-hidden="true" title="Administrator">'
            '<span class="sr-only">Administrator</span>'
            "</i>") in forum_last_post)
        # control that it's the right user's profile link
        self.assertTrue(
            (f'<a href="/forum/member/profile/{user1.id}/">Val&#233;ry</a>'
             ) in forum_last_post)

        # accessing forum topic listing view
        response = self.client.get(
            reverse("forum:forum", kwargs={
                "slug": forum.slug,
                "pk": forum.pk
            }))
        html = lxml.html.fromstring(response.content)
        # check that the topic creation writer contains the icon for administrator's role
        topic_created = str(etree.tostring(
            html.cssselect(".topic-created")[0]))
        # control that the administrator's icon is present
        self.assertTrue((
            '<i class="icon_writer fas fa-award" aria-hidden="true" title="Administrator">'
            '<span class="sr-only">Administrator</span>'
            "</i>") in topic_created)
        # control that it's the right user's profile link
        self.assertTrue(
            (f'<a href="/forum/member/profile/{user1.id}/">Val&#233;ry</a>'
             ) in topic_created)

        # check that the last post creation writer contains the icon for instructor's role
        html = lxml.html.fromstring(response.content)
        topic_last_post = str(
            etree.tostring(html.cssselect(".topic-last-post")[0]))
        # control that the administrator's icon is present
        self.assertTrue((
            '<i class="icon_writer fas fa-award" aria-hidden="true" title="Administrator">'
            '<span class="sr-only">Administrator</span>'
            "</i>") in topic_last_post)
        # control that it's the right user's profile link
        self.assertTrue(
            f'<a href="/forum/member/profile/{user1.id}/">Val&#233;ry</a>' in
            topic_last_post)

        # user2 add a post to the topic, last message is now from a student
        PostFactory.create(
            topic=topic1,
            poster=user2,
        )

        # reload the topic list view
        response = self.client.get(
            reverse("forum:forum", kwargs={
                "slug": forum.slug,
                "pk": forum.pk
            }))
        html = lxml.html.fromstring(response.content)
        topic_created = str(etree.tostring(
            html.cssselect(".topic-created")[0]))
        # control that the administrator's icon is present
        self.assertTrue((
            '<i class="icon_writer fas fa-award" aria-hidden="true" title="Administrator">'
            '<span class="sr-only">Administrator</span>'
            "</i>") in topic_created)
        # control that it's the right user's profile link
        self.assertTrue(
            (f'<a href="/forum/member/profile/{user1.id}/">Val&#233;ry</a>'
             ) in topic_created)

        topic_last_post = str(
            etree.tostring(html.cssselect(".topic-last-post")[0]))
        # check that there's no more icon as the post has been created by a student
        self.assertFalse("Administrator" in topic_last_post)
        # control that it's the right user's profile link
        self.assertTrue(
            (f'<a href="/forum/member/profile/{user2.id}/">Fran&#231;ois</a>'
             ) in topic_last_post)

        # accessing forum view
        response = self.client.get(reverse("forum:index"))
        # check that there's no more icon in this view as a new post has been created by a student
        self.assertNotContains(response, "icon_writer fas fa-award")

        # access list of posts from the topic
        response = self.client.get(
            reverse(
                "forum_conversation:topic",
                kwargs={
                    "forum_slug": forum.slug,
                    "forum_pk": forum.pk,
                    "slug": topic1.slug,
                    "pk": topic1.id,
                },
            ))
        # access 'post an answer' it should list the other posts of the topic
        response = self.client.get(
            f"/forum/forum/{forum.slug}-{forum.pk}/topic/{topic1.slug}-{topic1.pk}/post/create/"
        )
        html = lxml.html.fromstring(response.content)
        # check that for the posts in this topic we have the one from the instructor with the icon

        # check that for the message on first position there is no icon for the student
        html_first_post = str(etree.tostring(html.cssselect(".text-muted")[0]))
        self.assertFalse("Administrator" in html_first_post)
        self.assertTrue(
            (f'<a href="/forum/member/profile/{user2.id}/">Fran&#231;ois</a>'
             ) in html_first_post)
        # check that for the second message we have the instructor's icon
        html_second_post = str(etree.tostring(
            html.cssselect(".text-muted")[1]))
        self.assertTrue((
            '<i class="icon_writer fas fa-award" aria-hidden="true" title="Administrator">'
            '<span class="sr-only">Administrator</span>'
            "</i>") in html_second_post)
        self.assertTrue(
            (f'<a href="/forum/member/profile/{user1.id}/">Val&#233;ry</a>'
             ) in html_second_post)
    def test_access_api_can_manage_moderators_update_student_no_group_context(
            self):
        """
        We can't access endpoints targeting a user that don't have any group for this
        context. Only a user having a student group can add the moderator group.
        """
        update_user = UserFactory()
        api_user = UserFactory(lti_consumer=update_user.lti_consumer)

        lti_context = LTIContextFactory(lti_consumer=update_user.lti_consumer)
        forum = ForumFactory()
        forum.lti_contexts.add(lti_context)
        # Assign the permission
        assign_perm("can_manage_moderator", api_user, forum, True)
        # Creates the session
        self.client.force_login(api_user, "ashley.auth.backend.LTIBackend")
        session = self.client.session
        session[SESSION_LTI_CONTEXT_ID] = lti_context.id
        session.save()

        # Same for promoting/removing user to moderator
        action = random.choice(
            ["add_group_moderator", "remove_group_moderator"])
        response = self.client.patch(
            f"/api/v1.0/users/{update_user.id}/{action}/",
            content_type="application/json",
        )
        # no such user exist in this group context
        self.assertEqual(response.status_code, 404)

        # Add group and endpoints are now accessible
        lti_context.sync_user_groups(update_user, ["another_role"])
        response = self.client.patch(
            f"/api/v1.0/users/{update_user.id}/remove_group_moderator/",
            content_type="application/json",
        )
        self.assertEqual(response.status_code, 200)

        response = self.client.patch(
            f"/api/v1.0/users/{update_user.id}/add_group_moderator/",
            content_type="application/json",
        )
        self.assertEqual(response.status_code, 200)
        # Check list group of the user, moderator hasn't been added
        # as user doesn't have specificaly the student's group
        self.assertCountEqual(
            [
                lti_context.base_group_name,
                f"{lti_context.base_group_name}:role:another_role",
            ],
            list(update_user.groups.values_list("name", flat=True)),
        )

        # Set group student
        lti_context.sync_user_groups(update_user, ["student"])
        response = self.client.patch(
            f"/api/v1.0/users/{update_user.id}/add_group_moderator/",
            content_type="application/json",
        )
        self.assertEqual(response.status_code, 200)
        # now it's added
        self.assertCountEqual(
            [
                lti_context.base_group_name,
                f"{lti_context.base_group_name}:role:student",
                f"{lti_context.base_group_name}:role:moderator",
            ],
            list(update_user.groups.values_list("name", flat=True)),
        )

        response = self.client.patch(
            f"/api/v1.0/users/{update_user.id}/remove_group_moderator/",
            content_type="application/json",
        )
        self.assertEqual(response.status_code, 200)
    def test_access_api_can_manage_moderators_update_student_promote_revoke(
            self):
        """
        Promote and revoke a user with right context, permission, group
        Test to validate that update request API is working when everything
        is set properly.
        """
        update_user = UserFactory(id=1, public_username="******")
        api_user = UserFactory(lti_consumer=update_user.lti_consumer)

        lti_context = LTIContextFactory(lti_consumer=update_user.lti_consumer)
        forum = ForumFactory()
        forum.lti_contexts.add(lti_context)

        # Assign student group to user
        lti_context.sync_user_groups(update_user, ["student"])
        # Check list group of the user
        self.assertCountEqual(
            [
                lti_context.base_group_name,
                f"{lti_context.base_group_name}:role:student",
            ],
            list(update_user.groups.values_list("name", flat=True)),
        )

        # Assign the permission
        assign_perm("can_manage_moderator", api_user, forum, True)
        # Creates the session
        self.client.force_login(api_user, "ashley.auth.backend.LTIBackend")
        session = self.client.session
        session[SESSION_LTI_CONTEXT_ID] = lti_context.id
        session.save()

        # Promote user to moderator
        response = self.client.patch(
            f"/api/v1.0/users/{update_user.id}/add_group_moderator/",
            content_type="application/json",
        )
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.json(), {
            "id": 1,
            "public_username": "******"
        })

        # Check group moderator is part of group of the user
        self.assertCountEqual(
            [
                lti_context.base_group_name,
                f"{lti_context.base_group_name}:role:student",
                f"{lti_context.base_group_name}:role:moderator",
            ],
            list(update_user.groups.values_list("name", flat=True)),
        )

        # Then Revoke user to moderator
        response = self.client.patch(
            f"/api/v1.0/users/{update_user.id}/remove_group_moderator/",
            content_type="application/json",
        )
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.json(), {
            "id": 1,
            "public_username": "******"
        })

        # Check group moderator is not part of users's group
        self.assertCountEqual(
            [
                lti_context.base_group_name,
                f"{lti_context.base_group_name}:role:student",
            ],
            list(update_user.groups.values_list("name", flat=True)),
        )