diff --git a/.github/workflows/unit-test-shards.json b/.github/workflows/unit-test-shards.json
index 827366365fa8..cb9beeb3c6bf 100644
--- a/.github/workflows/unit-test-shards.json
+++ b/.github/workflows/unit-test-shards.json
@@ -239,7 +239,6 @@
"cms/djangoapps/course_creators/",
"cms/djangoapps/export_course_metadata/",
"cms/djangoapps/modulestore_migrator/",
- "cms/djangoapps/maintenance/",
"cms/djangoapps/models/",
"cms/djangoapps/pipeline_js/",
"cms/djangoapps/xblock_config/",
diff --git a/cms/djangoapps/maintenance/__init__.py b/cms/djangoapps/maintenance/__init__.py
deleted file mode 100644
index e69de29bb2d1..000000000000
diff --git a/cms/djangoapps/maintenance/tests.py b/cms/djangoapps/maintenance/tests.py
deleted file mode 100644
index 51f776b00afa..000000000000
--- a/cms/djangoapps/maintenance/tests.py
+++ /dev/null
@@ -1,194 +0,0 @@
-"""
-Tests for the maintenance app views.
-"""
-
-
-import ddt
-from django.conf import settings
-from django.urls import reverse
-
-from common.djangoapps.student.tests.factories import AdminFactory, UserFactory
-from openedx.features.announcements.models import Announcement
-from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
-
-from .views import MAINTENANCE_VIEWS
-
-# This list contains URLs of all maintenance app views.
-MAINTENANCE_URLS = [reverse(view['url']) for view in MAINTENANCE_VIEWS.values()]
-
-
-class TestMaintenanceIndex(ModuleStoreTestCase):
- """
- Tests for maintenance index view.
- """
-
- def setUp(self):
- super().setUp()
- self.user = AdminFactory()
- login_success = self.client.login(username=self.user.username, password=self.TEST_PASSWORD)
- self.assertTrue(login_success)
- self.view_url = reverse('maintenance:maintenance_index')
-
- def test_maintenance_index(self):
- """
- Test that maintenance index view lists all the maintenance app views.
- """
- response = self.client.get(self.view_url)
- self.assertContains(response, 'Maintenance', status_code=200)
-
- # Check that all the expected links appear on the index page.
- for url in MAINTENANCE_URLS:
- self.assertContains(response, url, status_code=200)
-
-
-@ddt.ddt
-class MaintenanceViewTestCase(ModuleStoreTestCase):
- """
- Base class for maintenance view tests.
- """
- view_url = ''
-
- def setUp(self):
- super().setUp()
- self.user = AdminFactory()
- login_success = self.client.login(username=self.user.username, password=self.TEST_PASSWORD)
- self.assertTrue(login_success)
-
- def verify_error_message(self, data, error_message):
- """
- Verify the response contains error message.
- """
- response = self.client.post(self.view_url, data=data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
- self.assertContains(response, error_message, status_code=200)
-
- def tearDown(self):
- """
- Reverse the setup.
- """
- self.client.logout()
- super().tearDown()
-
-
-@ddt.ddt
-class MaintenanceViewAccessTests(MaintenanceViewTestCase):
- """
- Tests for access control of maintenance views.
- """
- @ddt.data(*MAINTENANCE_URLS)
- def test_require_login(self, url):
- """
- Test that maintenance app requires user login.
- """
- # Log out then try to retrieve the page
- self.client.logout()
- response = self.client.get(url)
-
- # Expect a redirect to the login page
- redirect_url = '{login_url}?next={original_url}'.format(
- login_url=settings.LOGIN_URL,
- original_url=url,
- )
-
- # Studio login redirects to LMS login
- self.assertRedirects(response, redirect_url, target_status_code=302)
-
- @ddt.data(*MAINTENANCE_URLS)
- def test_global_staff_access(self, url):
- """
- Test that all maintenance app views are accessible to global staff user.
- """
- response = self.client.get(url)
- self.assertEqual(response.status_code, 200)
-
- @ddt.data(*MAINTENANCE_URLS)
- def test_non_global_staff_access(self, url):
- """
- Test that all maintenance app views are not accessible to non-global-staff user.
- """
- user = UserFactory(username='test', email='test@example.com', password=self.TEST_PASSWORD)
- login_success = self.client.login(username=user.username, password=self.TEST_PASSWORD)
- self.assertTrue(login_success)
-
- response = self.client.get(url)
- self.assertContains(
- response,
- f'Must be {settings.PLATFORM_NAME} staff to perform this action.',
- status_code=403
- )
-
-
-@ddt.ddt
-class TestAnnouncementsViews(MaintenanceViewTestCase):
- """
- Tests for the announcements edit view.
- """
-
- def setUp(self):
- super().setUp()
- self.admin = AdminFactory.create(
- email='staff@edx.org',
- username='admin',
- password=self.TEST_PASSWORD
- )
- self.client.login(username=self.admin.username, password=self.TEST_PASSWORD)
- self.non_staff_user = UserFactory.create(
- email='test@edx.org',
- username='test',
- password=self.TEST_PASSWORD
- )
-
- def test_index(self):
- """
- Test create announcement view
- """
- url = reverse("maintenance:announcement_index")
- response = self.client.get(url)
- self.assertContains(response, '
')
-
- def test_create(self):
- """
- Test create announcement view
- """
- url = reverse("maintenance:announcement_create")
- self.client.post(url, {"content": "Test Create Announcement", "active": True})
- result = Announcement.objects.filter(content="Test Create Announcement").exists()
- self.assertTrue(result)
-
- def test_edit(self):
- """
- Test edit announcement view
- """
- announcement = Announcement.objects.create(content="test")
- announcement.save()
- url = reverse("maintenance:announcement_edit", kwargs={"pk": announcement.pk})
- response = self.client.get(url)
- self.assertContains(response, '
diff --git a/openedx/features/announcements/__init__.py b/openedx/features/announcements/__init__.py
deleted file mode 100644
index e69de29bb2d1..000000000000
diff --git a/openedx/features/announcements/apps.py b/openedx/features/announcements/apps.py
deleted file mode 100644
index 4bf964cae51b..000000000000
--- a/openedx/features/announcements/apps.py
+++ /dev/null
@@ -1,32 +0,0 @@
-"""
-Announcements Application Configuration
-"""
-
-
-from django.apps import AppConfig
-from edx_django_utils.plugins import PluginURLs, PluginSettings
-
-from openedx.core.djangoapps.plugins.constants import ProjectType, SettingsType
-
-
-class AnnouncementsConfig(AppConfig):
- """
- Application Configuration for Announcements
- """
- name = 'openedx.features.announcements'
-
- plugin_app = {
- PluginURLs.CONFIG: {
- ProjectType.LMS: {
- PluginURLs.NAMESPACE: 'announcements',
- PluginURLs.REGEX: '^announcements/',
- PluginURLs.RELATIVE_PATH: 'urls',
- }
- },
- PluginSettings.CONFIG: {
- ProjectType.LMS: {
- SettingsType.COMMON: {PluginSettings.RELATIVE_PATH: 'settings.common'},
- SettingsType.TEST: {PluginSettings.RELATIVE_PATH: 'settings.test'},
- }
- }
- }
diff --git a/openedx/features/announcements/forms.py b/openedx/features/announcements/forms.py
deleted file mode 100644
index 879101ca37d0..000000000000
--- a/openedx/features/announcements/forms.py
+++ /dev/null
@@ -1,20 +0,0 @@
-"""
-Forms for the Announcement Editor
-"""
-
-
-from django import forms
-
-from .models import Announcement
-
-
-class AnnouncementForm(forms.ModelForm):
- """
- Form for editing Announcements
- """
- content = forms.CharField(widget=forms.Textarea, label='', required=False)
- active = forms.BooleanField(initial=True, required=False)
-
- class Meta:
- model = Announcement
- fields = ['content', 'active']
diff --git a/openedx/features/announcements/migrations/0001_initial.py b/openedx/features/announcements/migrations/0001_initial.py
deleted file mode 100644
index c959b634905e..000000000000
--- a/openedx/features/announcements/migrations/0001_initial.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ]
-
- operations = [
- migrations.CreateModel(
- name='Announcement',
- fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
- ('content', models.CharField(default='lorem ipsum', max_length=1000)),
- ('active', models.BooleanField(default=True)),
- ],
- ),
- ]
diff --git a/openedx/features/announcements/migrations/__init__.py b/openedx/features/announcements/migrations/__init__.py
deleted file mode 100644
index e69de29bb2d1..000000000000
diff --git a/openedx/features/announcements/models.py b/openedx/features/announcements/models.py
deleted file mode 100644
index f58f61165db6..000000000000
--- a/openedx/features/announcements/models.py
+++ /dev/null
@@ -1,22 +0,0 @@
-"""
-Models for Announcements
-"""
-
-
-from django.db import models
-
-
-class Announcement(models.Model):
- """
- Site-wide announcements to be displayed on the dashboard
-
- .. no_pii:
- """
- class Meta:
- app_label = 'announcements'
-
- content = models.CharField(max_length=1000, null=False, default="lorem ipsum")
- active = models.BooleanField(default=True)
-
- def __str__(self):
- return self.content
diff --git a/openedx/features/announcements/settings/__init__.py b/openedx/features/announcements/settings/__init__.py
deleted file mode 100644
index e69de29bb2d1..000000000000
diff --git a/openedx/features/announcements/settings/common.py b/openedx/features/announcements/settings/common.py
deleted file mode 100644
index 4de3740d2d27..000000000000
--- a/openedx/features/announcements/settings/common.py
+++ /dev/null
@@ -1,21 +0,0 @@
-"""Common settings for Announcements"""
-
-
-def plugin_settings(settings):
- """
- Common settings for Announcements
- .. toggle_name: FEATURES['ENABLE_ANNOUNCEMENTS']
- .. toggle_implementation: SettingDictToggle
- .. toggle_default: False
- .. toggle_description: This feature can be enabled to show system wide announcements
- on the sidebar of the learner dashboard. Announcements can be created by Global Staff
- users on maintenance dashboard of studio. Maintenance dashboard can accessed at
- https://{studio.domain}/maintenance
- .. toggle_warning: TinyMCE is needed to show an editor in the studio.
- .. toggle_use_cases: open_edx
- .. toggle_creation_date: 2017-11-08
- .. toggle_tickets: https://github.com/openedx/edx-platform/pull/16496
- """
- settings.ENABLE_ANNOUNCEMENTS = False
- # Configure number of announcements to show per page
- settings.ANNOUNCEMENTS_PER_PAGE = 5
diff --git a/openedx/features/announcements/settings/test.py b/openedx/features/announcements/settings/test.py
deleted file mode 100644
index 8c8406d23f4b..000000000000
--- a/openedx/features/announcements/settings/test.py
+++ /dev/null
@@ -1,8 +0,0 @@
-"""Test settings for Announcements"""
-
-
-def plugin_settings(settings):
- """
- Test settings for Announcements
- """
- settings.ENABLE_ANNOUNCEMENTS = True
diff --git a/openedx/features/announcements/static/announcements/jsx/Announcements.jsx b/openedx/features/announcements/static/announcements/jsx/Announcements.jsx
deleted file mode 100644
index 9d370883352c..000000000000
--- a/openedx/features/announcements/static/announcements/jsx/Announcements.jsx
+++ /dev/null
@@ -1,141 +0,0 @@
-// eslint-disable-next-line max-classes-per-file
-import React from 'react';
-import ReactDOM from 'react-dom';
-import PropTypes from 'prop-types';
-import {Button} from '@edx/paragon';
-import $ from 'jquery';
-
-class AnnouncementSkipLink extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- count: 0
- };
- $.get('/announcements/page/1')
- .then(data => {
- this.setState({
- count: data.count
- });
- });
- }
-
- render() {
- return ({'Skip to list of ' + this.state.count + ' announcements'}
);
- }
-}
-
-// eslint-disable-next-line react/prefer-stateless-function
-class Announcement extends React.Component {
- render() {
- return (
-
- );
- }
-}
-
-Announcement.propTypes = {
- content: PropTypes.string.isRequired,
-};
-
-class AnnouncementList extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- page: 1,
- announcements: [],
- // eslint-disable-next-line react/no-unused-state
- num_pages: 0,
- has_prev: false,
- has_next: false,
- start_index: 0,
- end_index: 0,
- };
- }
-
- retrievePage(page) {
- $.get('/announcements/page/' + page)
- .then(data => {
- this.setState({
- announcements: data.announcements,
- has_next: data.next,
- has_prev: data.prev,
- // eslint-disable-next-line react/no-unused-state
- num_pages: data.num_pages,
- count: data.count,
- start_index: data.start_index,
- end_index: data.end_index,
- page: page
- });
- });
- }
-
- renderPrevPage() {
- this.retrievePage(this.state.page - 1);
- }
-
- renderNextPage() {
- this.retrievePage(this.state.page + 1);
- }
-
- // eslint-disable-next-line react/no-deprecated, react/sort-comp
- componentWillMount() {
- this.retrievePage(this.state.page);
- }
-
- render() {
- var children = this.state.announcements.map(
- // eslint-disable-next-line react/no-array-index-key
- (announcement, index) =>
- );
- if (this.state.has_prev) {
- var prev_button = (
-
-
- );
- }
- if (this.state.has_next) {
- var next_button = (
-
-
- );
- }
- return (
-
- {children}
- {prev_button}
- {next_button}
-
- );
- }
-}
-
-export default class AnnouncementsView {
- constructor() {
- ReactDOM.render(
- ,
- document.getElementById('announcements'),
- );
- ReactDOM.render(
- ,
- document.getElementById('announcements-skip'),
- );
- }
-}
-
-export {AnnouncementsView, AnnouncementList, AnnouncementSkipLink};
diff --git a/openedx/features/announcements/static/announcements/jsx/Announcements.test.jsx b/openedx/features/announcements/static/announcements/jsx/Announcements.test.jsx
deleted file mode 100644
index 3ec55f392889..000000000000
--- a/openedx/features/announcements/static/announcements/jsx/Announcements.test.jsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import React from 'react';
-import renderer from 'react-test-renderer';
-import testAnnouncements from './test-announcements.json';
-
-import {AnnouncementSkipLink, AnnouncementList} from './Announcements';
-
-describe('Announcements component', () => {
- test('render skip link', () => {
- const component = renderer.create(
- ,
- );
- component.root.instance.setState({count: 10});
- const tree = component.toJSON();
- expect(tree).toMatchSnapshot();
- });
-
- test('render test announcements', () => {
- const component = renderer.create(
- ,
- );
- component.root.instance.setState(testAnnouncements);
- const tree = component.toJSON();
- expect(tree).toMatchSnapshot();
- });
-});
diff --git a/openedx/features/announcements/static/announcements/jsx/__snapshots__/Announcements.test.jsx.snap b/openedx/features/announcements/static/announcements/jsx/__snapshots__/Announcements.test.jsx.snap
deleted file mode 100644
index bbf9bfaaaa69..000000000000
--- a/openedx/features/announcements/static/announcements/jsx/__snapshots__/Announcements.test.jsx.snap
+++ /dev/null
@@ -1,78 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Announcements component render skip link 1`] = `
-
- Skip to list of 10 announcements
-
-`;
-
-exports[`Announcements component render test announcements 1`] = `
-
-
-
Announcement 2",
- }
- }
- />
-
-
-
-
-
-
-
- 1 - 5) of 6
-
-
-
-`;
diff --git a/openedx/features/announcements/static/announcements/jsx/test-announcements.json b/openedx/features/announcements/static/announcements/jsx/test-announcements.json
deleted file mode 100644
index d23d39303020..000000000000
--- a/openedx/features/announcements/static/announcements/jsx/test-announcements.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "announcements": [
- {"content": "Test Announcement 1"},
- {"content": "Bold
Announcement 2"},
- {"content": "Test Announcement 3"},
- {"content": "Test Announcement 4"},
- {"content": "Test Announcement 5"},
- {"content": "Test Announcement 6"}
- ],
- "has_next": true,
- "has_prev": false,
- "num_pages": 2,
- "count": 6,
- "start_index": 1,
- "end_index": 5,
- "page": 1
-}
diff --git a/openedx/features/announcements/tests/__init__.py b/openedx/features/announcements/tests/__init__.py
deleted file mode 100644
index e69de29bb2d1..000000000000
diff --git a/openedx/features/announcements/tests/test_announcements.py b/openedx/features/announcements/tests/test_announcements.py
deleted file mode 100644
index 10c608b4a6cd..000000000000
--- a/openedx/features/announcements/tests/test_announcements.py
+++ /dev/null
@@ -1,95 +0,0 @@
-"""
-Unit tests for the announcements feature.
-"""
-
-import json
-from unittest.mock import patch
-
-from django.conf import settings
-from django.test import TestCase
-from django.test.client import Client
-from django.urls import reverse
-
-from common.djangoapps.student.tests.factories import AdminFactory
-from openedx.core.djangolib.testing.utils import skip_unless_lms
-from openedx.features.announcements.models import Announcement
-
-TEST_ANNOUNCEMENTS = [
- ("Active Announcement", True),
- ("Inactive Announcement", False),
- ("Another Test Announcement", True),
- ("
Formatted Announcement", True),
- ("
Other Formatted Announcement", True),
-]
-
-
-@skip_unless_lms
-class TestGlobalAnnouncements(TestCase):
- """
- Test Announcements in LMS
- """
-
- @classmethod
- def setUpTestData(cls):
- super().setUpTestData()
- Announcement.objects.bulk_create([
- Announcement(content=content, active=active)
- for content, active in TEST_ANNOUNCEMENTS
- ])
-
- def setUp(self):
- super().setUp()
- self.client = Client()
- self.admin = AdminFactory.create(
- email='staff@edx.org',
- username='admin',
- password='pass'
- )
- self.client.login(username=self.admin.username, password='pass')
-
- @patch.dict(settings.FEATURES, {'ENABLE_ANNOUNCEMENTS': False})
- def test_feature_flag_disabled(self):
- """Ensures that the default settings effectively disables the feature"""
- response = self.client.get('/dashboard')
- self.assertNotContains(response, 'AnnouncementsView')
- self.assertNotContains(response, '
Formatted Announcement")
diff --git a/openedx/features/announcements/urls.py b/openedx/features/announcements/urls.py
deleted file mode 100644
index 0f0ad3a33960..000000000000
--- a/openedx/features/announcements/urls.py
+++ /dev/null
@@ -1,13 +0,0 @@
-"""
-Defines URLs for announcements in the LMS.
-"""
-from django.contrib.auth.decorators import login_required
-from django.urls import path
-
-from .views import AnnouncementsJSONView
-
-urlpatterns = [
- path('page/', login_required(AnnouncementsJSONView.as_view()),
- name='page',
- ),
-]
diff --git a/openedx/features/announcements/views.py b/openedx/features/announcements/views.py
deleted file mode 100644
index b6657c29cc12..000000000000
--- a/openedx/features/announcements/views.py
+++ /dev/null
@@ -1,37 +0,0 @@
-"""
-Views to show announcements.
-"""
-
-
-from django.conf import settings
-from django.http import JsonResponse
-from django.views.generic.list import ListView
-
-from .models import Announcement
-
-
-class AnnouncementsJSONView(ListView):
- """
- View returning a page of announcements for the dashboard
- """
- model = Announcement
- object_list = Announcement.objects.filter(active=True)
- paginate_by = settings.FEATURES.get('ANNOUNCEMENTS_PER_PAGE', 5)
-
- def get(self, request, *args, **kwargs):
- """
- Return active announcements as json
- """
- context = self.get_context_data()
-
- announcements = [{"content": announcement.content} for announcement in context['object_list']]
- result = {
- "announcements": announcements,
- "next": context['page_obj'].has_next(),
- "prev": context['page_obj'].has_previous(),
- "start_index": context['page_obj'].start_index(),
- "end_index": context['page_obj'].end_index(),
- "count": context['paginator'].count,
- "num_pages": context['paginator'].num_pages,
- }
- return JsonResponse(result)
diff --git a/setup.py b/setup.py
index 5b9f020ac3c5..eeb7b79f534d 100644
--- a/setup.py
+++ b/setup.py
@@ -139,7 +139,6 @@
],
"lms.djangoapp": [
"ace_common = openedx.core.djangoapps.ace_common.apps:AceCommonConfig",
- "announcements = openedx.features.announcements.apps:AnnouncementsConfig",
"content_libraries = openedx.core.djangoapps.content_libraries.apps:ContentLibrariesConfig",
"course_apps = openedx.core.djangoapps.course_apps.apps:CourseAppsConfig",
"course_live = openedx.core.djangoapps.course_live.apps:CourseLiveConfig",
@@ -158,7 +157,6 @@
"program_enrollments = lms.djangoapps.program_enrollments.apps:ProgramEnrollmentsConfig",
],
"cms.djangoapp": [
- "announcements = openedx.features.announcements.apps:AnnouncementsConfig",
"ace_common = openedx.core.djangoapps.ace_common.apps:AceCommonConfig",
"bookmarks = openedx.core.djangoapps.bookmarks.apps:BookmarksConfig",
"course_live = openedx.core.djangoapps.course_live.apps:CourseLiveConfig",
diff --git a/webpack.common.config.js b/webpack.common.config.js
index 36ac75708c23..4c8e201051d4 100644
--- a/webpack.common.config.js
+++ b/webpack.common.config.js
@@ -134,7 +134,6 @@ module.exports = Merge.merge({
// Features
Currency: './openedx/features/course_experience/static/course_experience/js/currency.js',
- AnnouncementsView: './openedx/features/announcements/static/announcements/jsx/Announcements.jsx',
CookiePolicyBanner: './common/static/js/src/CookiePolicyBanner.jsx',
// Common