def _get_url_list_topic_with_three_topics(self): """Creates a forum with three topics and a user to access the forum that has the permission to access it. It's a shortcut used in all the tests below.""" forum = ForumFactory() # create 3 topics with distinct subject, views_count and date PostFactory( topic=TopicFactory(forum=forum, views_count=9), subject="TOPIC B the eldest with 9 views_count", ) PostFactory( topic=TopicFactory(forum=forum, views_count=6), subject="TOPIC A created second with 6 views_count", ) PostFactory( topic=TopicFactory(forum=forum, views_count=12), subject="TOPIC C the newest one with 12 views_count", ) user = UserFactory() assign_perm("can_read_forum", user, forum) self.client.force_login(user) # Setup url_list_topic = f"/forum/forum/{forum.slug}-{forum.pk}/" return forum, url_list_topic
def test_testing_topic_announce(self): """Controls topics that are of type announcement don't have sorted options""" # Creates posts for announcement topics forum = ForumFactory() PostFactory(topic=TopicFactory(forum=forum, type=Topic.TOPIC_ANNOUNCE)) PostFactory(topic=TopicFactory(forum=forum, type=Topic.TOPIC_ANNOUNCE)) user = UserFactory() assign_perm("can_read_forum", user, forum) self.client.force_login(user) response = self.client.get(f"/forum/forum/{forum.slug}-{forum.pk}/") html = lxml.html.fromstring(response.content) # Select the header block of the announcement block, the first block announce_block = str( etree.tostring(html.cssselect(".topiclist .card-header")[0])) # Controls that announce_block is about announcements and not topics self.assertIn("Announcements", announce_block) self.assertNotIn("Topics", announce_block) self.assertIn("Replies", announce_block) self.assertIn("Views", announce_block) self.assertIn("Last post", announce_block) # There's no sortable informations self.assertNotIn("sortable sorted", announce_block) # There's no column that has a sorting link on self.assertNotIn("<a href=", announce_block) # There's no toggle sorting self.assertNotIn("Toggle sorting", announce_block)
def test_forum_search_with_unautorized_forum_from_other_lti_context(self): """ Try to search in a forum that is not part of our LTIContext by submitting in the search form a forum from another LTIContext. """ user = UserFactory() lti_context = LTIContextFactory(lti_consumer=user.lti_consumer) lti_context2 = LTIContextFactory(lti_consumer=user.lti_consumer) forum = ForumFactory() forum2 = ForumFactory() forum.lti_contexts.add(lti_context) forum2.lti_contexts.add(lti_context2) PostFactory( topic=TopicFactory(forum=forum), text="Good morning world", ) PostFactory( topic=TopicFactory(forum=forum2), text="Hello world", ) # Index posts in Elasticsearch call_command("rebuild_index", interactive=False) # Connects and gets acces to the forum self.client.force_login(user, "ashley.auth.backend.LTIBackend") assign_perm("can_read_forum", user, forum) assign_perm("can_read_forum", user, forum2) session = self.client.session session[SESSION_LTI_CONTEXT_ID] = lti_context.id session.save() form = SearchForm(user=user, lti_context=lti_context) # Checks that only the forum that is allowed is proposed as choice self.assertEqual( form.fields["search_forums"].choices, [(forum.id, "{} {}".format("-" * forum.margin_level, forum.name))], ) # Despite that, we force the request on the forum that is not allowed response = self.client.get( f"/forum/search/?q=world&search_forums={forum2.id}") self.assertEqual(response.status_code, 200) # Controls that we get an error and the search is not executed self.assertContains( response, f"Select a valid choice. {forum2.id} is not one of the available choices.", html=True, ) # Valid request, we search on the forum that is allowed, we get only one result # as forum2 is ignored response = self.client.get( f"/forum/search/?q=world&search_forums={forum.id}") self.assertEqual(response.status_code, 200) self.assertContains(response, "Your search has returned <b>1</b> result", html=True)
def test_testing_topic_announce_dont_get_ordered(self): """ Controls topics that are of type announcement don't get ordered if sorting is submitted in url. Orders are only applied to Topic posts. """ forum = ForumFactory() user = UserFactory() assign_perm("can_read_forum", user, forum) self.client.force_login(user) # Creates posts for announcement topics topicAnnounce1 = TopicFactory(forum=forum, type=Topic.TOPIC_ANNOUNCE, views_count=100) topicAnnounce2 = TopicFactory(forum=forum, type=Topic.TOPIC_ANNOUNCE, views_count=200) PostFactory( topic=topicAnnounce1, subject="TOPIC A TYPE ANNOUNCED", ) PostFactory( topic=topicAnnounce2, subject="TOPIC B TYPE ANNOUNCED", ) # Post of topicAnnounce2 has been created last, it should be the first one on the list self.assertLess(topicAnnounce1.last_post_on, topicAnnounce2.last_post_on) # Orders on column view_post response = self.client.get( f"/forum/forum/{forum.slug}-{forum.pk}/?o=2") # Orders is respected on default creation order self.assertContentBefore(response, "TOPIC B TYPE ANNOUNCED", "TOPIC A TYPE ANNOUNCED") # Reverses order response = self.client.get( f"/forum/forum/{forum.slug}-{forum.pk}/?o=-2") # Orders of announcement topics stays the same self.assertContentBefore(response, "TOPIC B TYPE ANNOUNCED", "TOPIC A TYPE ANNOUNCED") # Orders on replies column response = self.client.get( f"/forum/forum/{forum.slug}-{forum.pk}/?o=-1") # Shows order is respected on default creation order self.assertContentBefore(response, "TOPIC B TYPE ANNOUNCED", "TOPIC A TYPE ANNOUNCED") # Reverses order response = self.client.get( f"/forum/forum/{forum.slug}-{forum.pk}/?o=1") # Orders of announcement topics stays the same self.assertContentBefore(response, "TOPIC B TYPE ANNOUNCED", "TOPIC A TYPE ANNOUNCED")
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 _get_url_list_forum_with_forums(self): """Creates four forums and a user to access the forum that has the permission to access it. It's a shortcut used in all the tests below. """ user = UserFactory() lti_context = LTIContextFactory(lti_consumer=user.lti_consumer) forum = ForumFactory(name="Z Letter") forum.lti_contexts.add(lti_context) # create 3 topics, and one topic with 10 posts topic = TopicFactory(forum=forum) for i in range(10): PostFactory(topic=topic) PostFactory(topic=TopicFactory(forum=forum)) PostFactory(topic=TopicFactory(forum=forum)) # create a forum with 2 topics forum2 = ForumFactory(name="A Letter") forum2.lti_contexts.add(lti_context) PostFactory(topic=TopicFactory(forum=forum2)) PostFactory(topic=TopicFactory(forum=forum2)) # create a forum with 1 topic and 5 messages forum3 = ForumFactory(name="Q Letter") forum3.lti_contexts.add(lti_context) topic3 = TopicFactory(forum=forum3) PostFactory(topic=topic3) PostFactory(topic=topic3) PostFactory(topic=topic3) PostFactory(topic=topic3) PostFactory(topic=topic3) PostFactory(topic=TopicFactory(forum=forum3)) PostFactory(topic=TopicFactory(forum=forum3)) PostFactory(topic=TopicFactory(forum=forum3)) # create a forum with no topic forum4 = ForumFactory(name="M Letter") forum4.lti_contexts.add(lti_context) # Grant access assign_perm("can_see_forum", user, forum, True) assign_perm("can_read_forum", user, forum, True) assign_perm("can_see_forum", user, forum2, True) assign_perm("can_read_forum", user, forum2, True) assign_perm("can_see_forum", user, forum3, True) assign_perm("can_read_forum", user, forum3, True) assign_perm("can_see_forum", user, forum4, True) assign_perm("can_read_forum", user, forum4, True) self.client.force_login(user, "ashley.auth.backend.LTIBackend") return forum, forum2, forum3, forum4
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"
def test_course_info_course_student_cant_locked(self): """ User is a student, he shouldn't see the CTA to lock the course and he can't lock a course using the url. As a student he can create new topic or new post. """ forum = self._connects("student") # A LTIContext and a Forum should have been created post = PostFactory(topic=TopicFactory(forum=forum)) url = f"/forum/forum/{forum.slug}-{forum.pk}/" response = self.client.get(url) url_topic_create = f"{url}topic/create/" # user is a student and he can create a new topic self.assertContains( response, f'<a href="{url_topic_create}" class="btn btn-primary btn-sm">' '<i class="fa fa-comments fa-lg"></i> New topic</a>', html=True, ) response = self.client.get(url_topic_create, follow=True) self.assertEqual(200, response.status_code) # user has no button to lock the course self.assertNotContains(response, "Lock forums") # user can answer a post url_topic = f"{url}topic/{post.topic.slug}-{post.topic.pk}/" response = self.client.get(url_topic, follow=True) url_topic_reply = f"{url_topic}post/create/" self.assertContains( response, f'<a href="{url_topic_reply}" class="btn btn-primary btn-sm">' '<i class="fa fa-comment fa-lg"></i> Post reply</a>', html=True, ) response = self.client.get(url_topic_reply, follow=True) self.assertEqual(200, response.status_code) # user can't access the view to lock the course response = self.client.get(f"/forum/admin/lock-course/{forum.id}/") self.assertEqual(403, response.status_code) # user can't post to activate the lock on the course response = self.client.post(f"/forum/admin/lock-course/{forum.id}/") self.assertEqual(403, response.status_code)
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_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_forum_search_empty_public_username(self): """ A topic posted by a user that has not defined a public_username should be indexed with no error. """ user = UserFactory(public_username="") forum = ForumFactory() # Create a topic and a post topic = TopicFactory(subject="yahl2vooPh", poster=user, forum=forum) post = PostFactory(subject="dooV7ei3ju", topic=topic, poster=user) # Index the post in Elasticsearch call_command("rebuild_index", interactive=False) user2 = UserFactory() assign_perm("can_read_forum", user2, post.topic.forum) self.client.force_login(user2) response = self.client.get( f"/forum/search/?q=dooV7ei3ju&search_poster_name={post.poster.get_public_username()}\ &search_forums={post.topic.forum.pk}") self.assertContains(response, "Your search has returned <b>1</b> result", html=True) # Ensure that the default display name is present user_profile_url = reverse("forum_member:profile", kwargs={"pk": user.id}) self.assertContains(response, f'<a href="{user_profile_url}">Anonymous</a>', html=True) # Ensure that the default display name is not indexed response = self.client.get( f"/forum/search/?q=dooV7ei3ju&search_poster_name={post.poster.get_public_username()}\ &search_forums={post.topic.forum.pk}&search_poster_name=Anonymous" ) self.assertContains(response, "Your search has returned <b>0</b> results", html=True)
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
def test_testing_topic_is_sticky_stay_sticky_other_column_than_default( self): """ Request page with a sticky topic and check that the sticky topic is always the first result even if it's sorted on column other than default one. The order will be done on view counts column. """ forum, url_list_topic = self._get_url_list_topic_with_three_topics() # Creates a post for the sticky topic PostFactory( topic=TopicFactory(forum=forum, type=Topic.TOPIC_STICKY, views_count=7), subject="TOPIC STICKY ONE", ) topic12 = forum.topics.get(subject__contains="12 views_count") topic9 = forum.topics.get(subject__contains="9 views_count") topic6 = forum.topics.get(subject__contains="6 views_count") topicSticky = forum.topics.get(subject__startswith="TOPIC STICKY ONE") # Confirms that the sticky topic has neither max or lowest view_count self.assertGreater(topic12.views_count, topic9.views_count) self.assertGreater(topic9.views_count, topicSticky.views_count) self.assertGreater(topicSticky.views_count, topic6.views_count) # Orders on column view_post response = self.client.get(f"{url_list_topic}?o=2") # Sticky should stay first, we compare it with the max and the min views_count self.assertContentBefore(response, "TOPIC STICKY ONE", "12 views_count") self.assertContentBefore(response, "TOPIC STICKY ONE", "6 views_count") # Reverses the order and confirms sticky topic stays on top response = self.client.get(f"{url_list_topic}?o=-2") # Sticky should stay first, we compare it with the max and the min views_count self.assertContentBefore(response, "TOPIC STICKY ONE", "6 views_count") self.assertContentBefore(response, "TOPIC STICKY ONE", "12 views_count")
def test_testing_topic_is_sticky_stay_sticky_default_order(self): """ Request page with a sticky topic and check that the sticky topic is always the first result on default order """ forum, url_list_topic = self._get_url_list_topic_with_three_topics() # Creates a post for sticky topic PostFactory( topic=TopicFactory(forum=forum, type=Topic.TOPIC_STICKY), subject="TOPIC STICKY ONE", ) # Calls page with default order response = self.client.get(url_list_topic) topicC = forum.topics.get(subject__startswith="TOPIC C the newest") topicB = forum.topics.get(subject__startswith="TOPIC B the eldest") topicA = forum.topics.get(subject__startswith="TOPIC A created second") topicSticky = forum.topics.get(subject__startswith="TOPIC STICKY ONE") # Controls topics have been created in the order assumed in their titles self.assertGreater(topicSticky.last_post_on, topicC.last_post_on) self.assertGreater(topicC.last_post_on, topicA.last_post_on) self.assertGreater(topicA.last_post_on, topicB.last_post_on) # Should be ordered by date by default with the sticky topic shown first self.assertContentBefore(response, "TOPIC STICKY ONE", "TOPIC C the newest") self.assertContentBefore(response, "TOPIC C the newest", "TOPIC A created second") self.assertContentBefore(response, "TOPIC A created second", "TOPIC B the eldest") # Reverses the order, the sticky topic should remain first response = self.client.get(f"{url_list_topic}?o=-0") self.assertContentBefore(response, "TOPIC STICKY ONE", "TOPIC B") self.assertContentBefore(response, "TOPIC B", "TOPIC A ") self.assertContentBefore(response, "TOPIC C", "TOPIC A")
def test_course_connection_no_lti(self): """ LTI session is not set then page can still be viewed, the notice about the course being locked won't appear """ forum = self._connects("instructor") post = PostFactory(topic=TopicFactory(forum=forum)) # instructor locks the forum response = self.client.get(f"/forum/admin/lock-course/{forum.id}/") self.assertEqual(200, response.status_code) # user connected without LTI Session user = UserFactory() assign_perm("can_read_forum", user, forum) self.client.force_login(user) # info is not present even if the course is locked # - in forums view but page is available response = self.client.get("/forum/") self.assertNotContains( response, "This course has locked forums, non admin users can only read the history.", ) # - in the forum view response = self.client.get(f"/forum/forum/{forum.slug}-{forum.pk}/") self.assertNotContains( response, "This course has locked forums, non admin users can only read the history.", ) # - in topic view response = self.client.get( f"/forum/forum/{forum.slug}-{forum.pk}/topic/{post.topic.slug}-{post.topic.pk}/" ) self.assertNotContains( response, "This course has locked forums, non admin users can only read the history.", )
def test_forum_search_post_topic_partial(self): """ The topic subject is not directly indexed but Django Machina forces it with the post subject when creating the first post of a topic: # see: apps/forum_conversation/abstract_models.py#L314 So in the end, searching for a topic subject will return the first post as result. """ # First, let's secure Django Machina's overriding mechanism topic = TopicFactory(subject="5f3gh8ka1") forum = topic.forum self.assertEqual(topic.subject, "5f3gh8ka1") # When we create the first post for this topic... post = PostFactory(subject="497jk1sav", topic=topic) # The topic subject should be overriden topic.refresh_from_db() self.assertEqual(topic.subject, "497jk1sav") PostFactory(topic=topic) # Creating a second post does not change the topic subject again topic.refresh_from_db() self.assertEqual(topic.subject, "497jk1sav") # A third post in the same forum with a different topic PostFactory(topic__forum=forum) user = UserFactory() assign_perm("can_read_forum", user, forum) # Index the post in Elasticsearch call_command("rebuild_index", interactive=False) self.client.force_login(user) response = self.client.get("/forum/search/?q=497jk1sav") self.assertContains(response, "Your search has returned <b>1</b> result", html=True) self.assertContains( response, (f'<a href="/forum/forum/{forum.slug:s}-{forum.id:d}/topic/{topic.slug:s}-' f'{topic.id:d}/?post={ post.pk }#{ post.pk }" ' f'class="topic-name-link">{topic.subject:s}</a>'), html=True, )
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_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_course_student_course_locked_admin_connects_before(self): """ Instructor connects and lock the course, then the student connects himself. We check that when the user wasn't connected before the locking, he can see the course info as locked and he can't create new topic or reply to post. """ forum = self._connects("instructor") # create a post in the forum post = PostFactory(topic=TopicFactory(forum=forum)) # context object has been created context = LTIContext.objects.get(lti_id=self.context_id) # course is locked url_lock = f"/forum/admin/lock-course/{forum.id}/" response = self.client.post(url_lock, follow=True) self.assertEqual(200, response.status_code) # course is now marked as locked context.refresh_from_db() self.assertEqual(context.is_marked_locked, True) # connects the student self._connects("student") # info is present in forums view response = self.client.get("/forum/") self.assertContains( response, "This course has locked forums, non admin users can only read the history.", ) # info is present in the forum view url_forum = f"/forum/forum/{forum.slug}-{forum.pk}/" response = self.client.get(url_forum) self.assertContains( response, "This course has locked forums, non admin users can only read the history.", ) url_topic_create = f"{url_forum}topic/create/" # user is student but he can't create a new topic anymore self.assertNotContains( response, f'<a href="{url_topic_create}" class="btn btn-primary btn-sm">' '<i class="fa fa-comments fa-lg"></i> New topic</a>', html=True, ) # info is present in topic view response = self.client.get( f"{url_forum}topic/{post.topic.slug}-{post.topic.pk}/") self.assertContains( response, "This course has locked forums, non admin users can only read the history.", ) response = self.client.get(url_topic_create, follow=True) self.assertEqual(403, response.status_code) # user can't answer a post anymore url_topic = f"{url_forum}topic/{post.topic.slug}-{post.topic.pk}/" response = self.client.get(url_topic, follow=True) url_topic_reply = f"{url_topic}post/create/" self.assertNotContains( response, f'<a href="{url_topic_reply}" class="btn btn-primary btn-sm">' '<i class="fa fa-comment fa-lg"></i> Post reply</a>', html=True, ) response = self.client.get(url_topic_reply, follow=True) self.assertEqual(403, response.status_code)
def test_forum_search_several_forums_lti_context_search(self): """ Creates forum in different lti_context, make sure user can't search into it, even if he has access to this forum. """ user = UserFactory() lti_context = LTIContextFactory(lti_consumer=user.lti_consumer) lti_context2 = LTIContextFactory(lti_consumer=user.lti_consumer) forum = ForumFactory() forum2 = ForumFactory() forum3 = ForumFactory() forum.lti_contexts.add(lti_context) forum2.lti_contexts.add(lti_context) forum3.lti_contexts.add(lti_context2) post1 = PostFactory( topic=TopicFactory(forum=forum), subject="Forum same lti_context", text="Hello world", ) post2 = PostFactory( topic=TopicFactory(forum=forum2), subject="Forum2 same lti_context", text="Good morning world", ) post3 = PostFactory( topic=TopicFactory(forum=forum3), subject="Forum3 different lti_context", text="Welcome world", ) # 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() assign_perm("can_read_forum", user, forum) assign_perm("can_read_forum", user, forum2) assign_perm("can_read_forum", user, forum3) # Index the post in Elasticsearch call_command("rebuild_index", interactive=False) response = self.client.get("/forum/search/?q=world") self.assertContains(response, "Your search has returned <b>2</b> results", html=True) self.assertContains(response, post1.subject) self.assertContains(response, post2.subject) self.assertNotContains(response, post3.subject) # 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() response = self.client.get("/forum/search/?q=world") # We should only have one result self.assertContains(response, "Your search has returned <b>1</b> result", html=True) self.assertNotContains(response, post1.subject) self.assertNotContains(response, post2.subject) self.assertContains(response, post3.subject)
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, )
def test_xapi_logger(self): """ When a post is updated, the test_track_post receiver should emit an XAPI event on the logger configured for the corresponding LTIConsumer. """ # Create a user with access to this forum user = UserFactory() lti_context = LTIContextFactory( lti_consumer=user.lti_consumer, lti_id="course-v1:myschool+mathematics101+session01", ) topic = TopicFactory(poster=user) post = PostFactory(poster=user, topic=topic) forum = topic.forum forum.lti_contexts.add(lti_context) assign_perm("can_read_forum", user, forum, True) assign_perm("can_reply_to_topics", user, forum, True) assign_perm("can_edit_own_posts", user, forum, True) post_update_url = reverse( "forum_conversation:post_update", kwargs={ "forum_slug": forum.slug, "forum_pk": forum.pk, "topic_slug": topic.slug, "topic_pk": topic.pk, "pk": post.pk, }, ) logger_name = f"xapi.{user.lti_consumer.slug}" self.client.force_login(user, "ashley.auth.backend.LTIBackend") session = self.client.session session[SESSION_LTI_CONTEXT_ID] = lti_context.id session.save() with self.assertLogs(logger=logger_name, level="INFO") as cm: response = self.client.post( post_update_url, data={ "subject": topic.subject, "content": "foo text", }, follow=True, ) self.assertEqual(response.status_code, 200) # One line of debug should have been written self.assertEqual(len(cm.output), 2) # Extract XAPI statement from log output log_prefix_len = len(f"{logger_name}:INFO:") raw_xapi_event = cm.output[0][log_prefix_len:] xapi_event = json.loads(raw_xapi_event) # The XAPI event should have an ID self.assertIn("id", xapi_event) # Validate the actor part of the XAPI event self.assertEqual("Agent", xapi_event["actor"]["objectType"]) self.assertEqual(user.lti_consumer.url, xapi_event["actor"]["account"]["homePage"]) self.assertEqual(user.public_username, xapi_event["actor"]["account"]["name"]) # Validate the verb self.assertEqual("https://w3id.org/xapi/dod-isd/verbs/updated", xapi_event["verb"]["id"]) # Validate the activity self.assertEqual(f"id://ashley/post/{post.pk}", xapi_event["object"]["id"]) self.assertEqual("Activity", xapi_event["object"]["objectType"]) self.assertEqual( "https://w3id.org/xapi/acrossx/activities/message", xapi_event["object"]["definition"]["type"], ) # Validate the context self.assertEqual( f"uuid://{forum.lti_id}", xapi_event["context"]["contextActivities"]["parent"][0]["id"], ) self.assertEqual( "Activity", xapi_event["context"]["contextActivities"]["parent"][0] ["objectType"], ) self.assertEqual( "http://id.tincanapi.com/activitytype/discussion", xapi_event["context"]["contextActivities"]["parent"][0] ["definition"]["type"], ) self.assertEqual( "course-v1:myschool+mathematics101+session01", xapi_event["context"]["contextActivities"]["parent"][1]["id"], ) self.assertEqual( "Activity", xapi_event["context"]["contextActivities"]["parent"][1] ["objectType"], ) self.assertEqual( "http://adlnet.gov/expapi/activities/course", xapi_event["context"]["contextActivities"]["parent"][1] ["definition"]["type"], )
def test_course_create_forum_after_lock(self): """ Instructor locks the course, then a student connects. After the course is locked, the instructor reconnects and then creates a new forum whereas the course is supposed to be locked. We check that the student still can't reply or add topic in this new forum created after the locked. """ forum = self._connects("instructor") response = self.client.post(f"/forum/admin/lock-course/{forum.id}/", follow=True) self.assertEqual(200, response.status_code) # course is now marked as locked context = LTIContext.objects.get(lti_id=self.context_id) self.assertEqual(context.is_marked_locked, True) # student connection self._connects("student", uuid="NewOne", lis_person_sourcedid="enzo") # a new Forum is created after the locking forum2 = ForumFactory() forum2 = self._connects("instructor", forum_uuid=forum2.lti_id) # create a post in forum3 post = PostFactory(topic=TopicFactory(forum=forum2)) # student reconnects self._connects("student", uuid="NewOne", lis_person_sourcedid="enzo") # user can only read the forum self.assertEqual( ["can_see_forum", "can_read_forum"], list( GroupForumPermission.objects.filter( forum=forum2, group=context.get_base_group(), has_perm=True).values_list("permission__codename", flat=True)), ) # info is present in forums view response = self.client.get("/forum/") self.assertContains( response, "This course has locked forums, non admin users can only read the history.", ) # info is present in the forum4 view url_forum2 = f"/forum/forum/{forum2.slug}-{forum2.pk}/" response = self.client.get(url_forum2) self.assertContains( response, "This course has locked forums, non admin users can only read the history.", ) url_topic_create = f"{url_forum2}topic/create/" # user is student but he can't create a new topic anymore self.assertNotContains( response, f'<a href="{url_topic_create}" class="btn btn-primary btn-sm">' '<i class="fa fa-comments fa-lg"></i> New topic</a>', html=True, ) # info is present in topic view response = self.client.get( f"{url_forum2}topic/{post.topic.slug}-{post.topic.pk}/") self.assertContains( response, "This course has locked forums, non admin users can only read the history.", ) # still can't create a topic event if the forum was created after response = self.client.get(url_topic_create, follow=True) self.assertEqual(403, response.status_code) # user can't answer a post anymore url_topic = f"{url_forum2}topic/{post.topic.slug}-{post.topic.pk}/" response = self.client.get(url_topic, follow=True) url_topic_reply = f"{url_topic}post/create/" self.assertNotContains( response, f'<a href="{url_topic_reply}" class="btn btn-primary btn-sm">' '<i class="fa fa-comment fa-lg"></i> Post reply</a>', html=True, ) response = self.client.get(url_topic_reply, follow=True) self.assertEqual(403, response.status_code)
def test_course_create_multiple_forums(self): """ Instructor locks the course that has multiple forums. Checks the student can't write in a forum other than the one from which it has been locked. """ self._connects("instructor") forum2 = ForumFactory() forum3 = ForumFactory() # connects user and add forums to the lti_context forum2 = self._connects("instructor", forum_uuid=forum2.lti_id) forum3 = self._connects("instructor", forum_uuid=forum3.lti_id) # create a post in forum3 post = PostFactory(topic=TopicFactory(forum=forum3)) response = self.client.post(f"/forum/admin/lock-course/{forum2.id}/", follow=True) self.assertEqual(200, response.status_code) # course is now marked as locked context = LTIContext.objects.get(lti_id=self.context_id) self.assertEqual(context.is_marked_locked, True) # student connection self._connects("student") # info is present in forums view response = self.client.get("/forum/") self.assertContains( response, "This course has locked forums, non admin users can only read the history.", ) # info is present in the forum3 view url_forum3 = f"/forum/forum/{forum3.slug}-{forum3.pk}/" response = self.client.get(url_forum3) self.assertContains( response, "This course has locked forums, non admin users can only read the history.", ) url_topic_create = f"{url_forum3}topic/create/" # user is student but he can't create a new topic anymore self.assertNotContains( response, f'<a href="{url_topic_create}" class="btn btn-primary btn-sm">' '<i class="fa fa-comments fa-lg"></i> New topic</a>', html=True, ) # info is present in topic view response = self.client.get( f"{url_forum3}topic/{post.topic.slug}-{post.topic.pk}/") self.assertContains( response, "This course has locked forums, non admin users can only read the history.", ) # can't create a topic response = self.client.get(url_topic_create, follow=True) self.assertEqual(403, response.status_code) # user can't answer a post anymore url_topic = f"{url_forum3}topic/{post.topic.slug}-{post.topic.pk}/" response = self.client.get(url_topic, follow=True) url_topic_reply = f"{url_topic}post/create/" self.assertNotContains( response, f'<a href="{url_topic_reply}" class="btn btn-primary btn-sm">' '<i class="fa fa-comment fa-lg"></i> Post reply</a>', html=True, ) response = self.client.get(url_topic_reply, follow=True) self.assertEqual(403, response.status_code)
def test_forum_search_archived_form_search(self): """ Create different forums in the same lti_context, make sure user can't select archived forums in the advanced search. """ user = UserFactory() lti_context = LTIContextFactory(lti_consumer=user.lti_consumer) forum = ForumFactory() forum2 = ForumFactory() forum3 = ForumFactory() forum.lti_contexts.add(lti_context) forum2.lti_contexts.add(lti_context) forum3.lti_contexts.add(lti_context) PostFactory(topic=TopicFactory(forum=forum), ) PostFactory(topic=TopicFactory(forum=forum2), ) PostFactory(topic=TopicFactory(forum=forum3), ) # 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() assign_perm("can_read_forum", user, forum) assign_perm("can_read_forum", user, forum2) assign_perm("can_read_forum", user, forum3) # Load the advanced search form form = SearchForm(user=user, lti_context=lti_context) # Check that only forums that are allowed are proposed as choice self.assertEqual( form.fields["search_forums"].choices, [ (forum.id, "{} {}".format("-" * forum.margin_level, forum.name)), (forum2.id, "{} {}".format("-" * forum.margin_level, forum2.name)), (forum3.id, "{} {}".format("-" * forum.margin_level, forum3.name)), ], ) # Archive the forum2 forum2.archived = True forum2.save() form = SearchForm(user=user, lti_context=lti_context) # Check that forum2 is not proposed as choice anymore as it has been archived self.assertEqual( form.fields["search_forums"].choices, [ (forum.id, "{} {}".format("-" * forum.margin_level, forum.name)), (forum3.id, "{} {}".format("-" * forum.margin_level, forum3.name)), ], ) # Despite that, we force the request on the forum that is not allowed response = self.client.get( f"/forum/search/?q=world&search_forums={forum2.id}") self.assertEqual(response.status_code, 200) # Control that we get an error and the search is not executed self.assertContains( response, f"Select a valid choice. {forum2.id} is not one of the available choices.", html=True, )
def test_xapi_logger(self): """ When a topic is viewed, the test_track_topic receiver should emit an XAPI event on the logger configured for the corresponding LTIConsumer. """ # Create a topic in a new forum topic = TopicFactory() for _ in range(42): PostFactory(topic=topic) # Create a user with access to this forum user = UserFactory() lti_context = LTIContextFactory( lti_consumer=user.lti_consumer, lti_id="course-v1:myschool+mathematics101+session01", ) forum = topic.forum forum.lti_contexts.add(lti_context) assign_perm("can_read_forum", user, forum, True) topic_url = reverse( "forum_conversation:topic", kwargs={ "forum_slug": topic.forum.slug, "forum_pk": topic.forum.pk, "slug": topic.slug, "pk": topic.pk, }, ) logger_name = f"xapi.{user.lti_consumer.slug}" self.client.force_login(user, "ashley.auth.backend.LTIBackend") session = self.client.session session[SESSION_LTI_CONTEXT_ID] = lti_context.id session.save() with self.assertLogs(logger=logger_name, level="INFO") as cm: response = self.client.get(topic_url, data={"page": 2}) self.assertEqual(response.status_code, 200) # One line of debug should have been written self.assertEqual(len(cm.output), 1) # Extract XAPI statement from log output log_prefix_len = len(f"{logger_name}:INFO:") raw_xapi_event = cm.output[0][log_prefix_len:] xapi_event = json.loads(raw_xapi_event) # The XAPI event should have an ID self.assertIn("id", xapi_event) # Validate the actor part of the XAPI event self.assertEqual("Agent", xapi_event["actor"]["objectType"]) self.assertEqual(user.lti_consumer.url, xapi_event["actor"]["account"]["homePage"]) self.assertEqual(user.public_username, xapi_event["actor"]["account"]["name"]) # Validate the verb self.assertEqual("http://id.tincanapi.com/verb/viewed", xapi_event["verb"]["id"]) # Validate the activity self.assertEqual(f"id://ashley/topic/{topic.pk}", xapi_event["object"]["id"]) self.assertEqual("Activity", xapi_event["object"]["objectType"]) self.assertEqual( "http://id.tincanapi.com/activitytype/discussion", xapi_event["object"]["definition"]["type"], ) # validate the activity definition extensions expected_extensions = { "https://w3id.org/xapi/acrossx/extensions/total-items": 42, "https://w3id.org/xapi/acrossx/extensions/total-pages": 3, } self.assertEqual(xapi_event["object"]["definition"]["extensions"], expected_extensions) # Validate the context self.assertEqual( f"uuid://{topic.forum.lti_id}", xapi_event["context"]["contextActivities"]["parent"][0]["id"], ) self.assertEqual( "Activity", xapi_event["context"]["contextActivities"]["parent"][0] ["objectType"], ) self.assertEqual( xapi_event["context"]["extensions"], {"http://www.risc-inc.com/annotator/extensions/page": 2}, ) self.assertEqual( "http://id.tincanapi.com/activitytype/community-site", xapi_event["context"]["contextActivities"]["parent"][0] ["definition"]["type"], ) self.assertEqual( "course-v1:myschool+mathematics101+session01", xapi_event["context"]["contextActivities"]["parent"][1]["id"], ) self.assertEqual( "Activity", xapi_event["context"]["contextActivities"]["parent"][1] ["objectType"], ) self.assertEqual( "http://adlnet.gov/expapi/activities/course", xapi_event["context"]["contextActivities"]["parent"][1] ["definition"]["type"], )
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, )
def test_course_info_course_instructor_can_locked(self): """ User is an instructor or an administrator, it should see the CTA to lock the course and he can lock the course. Once the course is locked, permissions of created groups have changed and information of the course being locked is displayed. """ forum = self._connects("instructor") # A LTIContext and a Forum should have been created context = LTIContext.objects.get(lti_id=self.context_id) self.assertEqual(context.is_marked_locked, False) # create a post in the forum post = PostFactory(topic=TopicFactory(forum=forum)) # focus on permission of groups created base_group = context.get_base_group() instructor_group = context.get_role_group("instructor") count_perm_instructor = GroupForumPermission.objects.filter( forum=forum, group=instructor_group, has_perm=True).count() count_perm_base = GroupForumPermission.objects.filter( forum=forum, group=base_group, has_perm=True).count() self.assertEqual( [ "can_see_forum", "can_read_forum", "can_start_new_topics", "can_reply_to_topics", "can_edit_own_posts", "can_post_without_approval", "can_vote_in_polls", ], list( GroupForumPermission.objects.filter(forum=forum, group=base_group, has_perm=True).values_list( "permission__codename", flat=True)), ) url_forum = f"/forum/forum/{forum.slug}-{forum.pk}/" url_lock = f"/forum/admin/lock-course/{forum.id}/" response = self.client.get(url_forum) self.assertContains( response, f'<a href="{url_lock}" title="Lock forums" class="dropdown-item">Lock forums</a>', html=True, ) # user can access the view to lock a course response = self.client.get(url_lock) self.assertEqual(200, response.status_code) # user can't post to activate the lock on the course response = self.client.post(url_lock, follow=True) self.assertEqual(200, response.status_code) # course is now marked as locked context.refresh_from_db() self.assertEqual(context.is_marked_locked, True) # info is present in forums view response = self.client.get("/forum/") self.assertContains( response, "This course has locked forums, non admin users can only read the history.", ) # info is present in the forum view url_forum = f"/forum/forum/{forum.slug}-{forum.pk}/" response = self.client.get(url_forum) self.assertContains( response, "This course has locked forums, non admin users can only read the history.", ) # the cta to lock is no more present as forum is already locked self.assertNotContains( response, f'<a href="{url_lock}" title="Lock forums" class="dropdown-item">Lock forums</a>', html=True, ) # info is present in topic view response = self.client.get( f"{url_forum}topic/{post.topic.slug}-{post.topic.pk}/") self.assertContains( response, "This course has locked forums, non admin users can only read the history.", ) # permissions of instructor haven't changed self.assertEqual( count_perm_instructor, GroupForumPermission.objects.filter(forum=forum, group=instructor_group, has_perm=True).count(), ) # for base group, five permissions should have been deleted self.assertEqual( count_perm_base - 5, GroupForumPermission.objects.filter(forum=forum, group=base_group, has_perm=True).count(), ) self.assertEqual( ["can_see_forum", "can_read_forum"], list( GroupForumPermission.objects.filter(forum=forum, group=base_group, has_perm=True).values_list( "permission__codename", flat=True)), )
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)
def test_course_student_course_locked_student_connects_before(self): """ We create a student before and then an instructor. The instructor locks the course. The students reconnects after, the course should be locked. """ user_count = 0 forum = self._connects("student") # one user has been created self.assertEqual(user_count + 1, User.objects.count()) user_count += 1 user = User.objects.last() # create a post in the forum post = PostFactory(topic=TopicFactory(forum=forum, poster=user), poster=user) # context object has been created context = LTIContext.objects.get(lti_id=self.context_id) student_group = context.get_role_group("student") base_group = context.get_base_group() # student group contains no permission, all permissions are in the # base group self.assertEqual( [], list( GroupForumPermission.objects.filter(forum=forum, group=student_group, has_perm=True).values_list( "permission__codename", flat=True)), ) # check base group permission count_perm_base = GroupForumPermission.objects.filter( forum=forum, group=base_group, has_perm=True).count() self.assertEqual( [ "can_see_forum", "can_read_forum", "can_start_new_topics", "can_reply_to_topics", "can_edit_own_posts", "can_post_without_approval", "can_vote_in_polls", ], list( GroupForumPermission.objects.filter(forum=forum, group=base_group, has_perm=True).values_list( "permission__codename", flat=True)), ) self._connects("instructor", uuid="NewOne", lis_person_sourcedid="enzo") # admin user has been created self.assertEqual(user_count + 1, User.objects.count()) user_count += 1 # course is locked url_lock = f"/forum/admin/lock-course/{forum.id}/" response = self.client.post(url_lock, follow=True) self.assertEqual(200, response.status_code) # course is now marked as locked context.refresh_from_db() self.assertEqual(context.is_marked_locked, True) # reconnect the student user self._connects("student") # no new user has been created self.assertEqual(user_count, User.objects.count()) # info is present in forums view response = self.client.get("/forum/") self.assertContains( response, "This course has locked forums, non admin users can only read the history.", ) # info is present in the forum view url_forum = f"/forum/forum/{forum.slug}-{forum.pk}/" response = self.client.get(url_forum) self.assertContains( response, "This course has locked forums, non admin users can only read the history.", ) url_topic_create = f"{url_forum}topic/create/" # user is a student but he can't create a new topic anymore self.assertNotContains( response, f'<a href="{url_topic_create}" class="btn btn-primary btn-sm">' '<i class="fa fa-comments fa-lg"></i> New topic</a>', html=True, ) # info is present in topic view response = self.client.get( f"{url_forum}topic/{post.topic.slug}-{post.topic.pk}/") self.assertContains( response, "This course has locked forums, non admin users can only read the history.", ) # permissions of the base group have been deleted self.assertEqual( count_perm_base - 5, GroupForumPermission.objects.filter(forum=forum, group=base_group, has_perm=True).count(), ) self.assertEqual( ["can_see_forum", "can_read_forum"], list( GroupForumPermission.objects.filter(forum=forum, group=base_group, has_perm=True).values_list( "permission__codename", flat=True)), ) response = self.client.get(url_topic_create, follow=True) self.assertEqual(403, response.status_code) # user can't answer a post anymore url_topic = f"{url_forum}topic/{post.topic.slug}-{post.topic.pk}/" response = self.client.get(url_topic, follow=True) url_topic_reply = f"{url_topic}post/create/" self.assertNotContains( response, f'<a href="{url_topic_reply}" class="btn btn-primary btn-sm">' '<i class="fa fa-comment fa-lg"></i> Post reply</a>', html=True, ) response = self.client.get(url_topic_reply, follow=True) self.assertEqual(403, response.status_code)