From 36283f1a8b311d73886ef1d3db80ab4528648d66 Mon Sep 17 00:00:00 2001
From: AsadAzam <asadazam93@gmail.com>
Date: Thu, 2 Sep 2021 13:25:53 +0500
Subject: [PATCH] feat! Modify DiscussionsConfiguration to allow programs
 (#28541)

* feat! Modify DiscussionsConfiguration to allow programs

* feat: add model for program discussions configuration
---
 openedx/core/djangoapps/discussions/admin.py  | 19 ++++++-
 ...uration_programdiscussionsconfiguration.py | 56 +++++++++++++++++++
 openedx/core/djangoapps/discussions/models.py | 50 ++++++++++++++++-
 3 files changed, 122 insertions(+), 3 deletions(-)
 create mode 100644 openedx/core/djangoapps/discussions/migrations/0004_historicalprogramdiscussionsconfiguration_programdiscussionsconfiguration.py

diff --git a/openedx/core/djangoapps/discussions/admin.py b/openedx/core/djangoapps/discussions/admin.py
index 33866b3f164..870940a9394 100644
--- a/openedx/core/djangoapps/discussions/admin.py
+++ b/openedx/core/djangoapps/discussions/admin.py
@@ -7,7 +7,7 @@ from simple_history.admin import SimpleHistoryAdmin
 
 from openedx.core.djangoapps.config_model_utils.admin import StackedConfigModelAdmin
 
-from .models import DiscussionsConfiguration
+from .models import DiscussionsConfiguration, ProgramDiscussionsConfiguration
 from .models import ProviderFilter
 
 
@@ -27,6 +27,22 @@ class DiscussionsConfigurationAdmin(SimpleHistoryAdmin):
     )
 
 
+class ProgramDiscussionsConfigurationAdmin(SimpleHistoryAdmin):
+    """
+    Customize the admin interface for the program discussions configuration
+    """
+
+    search_fields = (
+        'program_uuid',
+        'enabled',
+        'provider_type',
+    )
+    list_filter = (
+        'enabled',
+        'provider_type',
+    )
+
+
 class AllowListFilter(SimpleListFilter):
     """
     Customize the admin interface for the AllowList
@@ -88,4 +104,5 @@ class ProviderFilterAdmin(StackedConfigModelAdmin):
 
 
 admin.site.register(DiscussionsConfiguration, DiscussionsConfigurationAdmin)
+admin.site.register(ProgramDiscussionsConfiguration, ProgramDiscussionsConfigurationAdmin)
 admin.site.register(ProviderFilter, ProviderFilterAdmin)
diff --git a/openedx/core/djangoapps/discussions/migrations/0004_historicalprogramdiscussionsconfiguration_programdiscussionsconfiguration.py b/openedx/core/djangoapps/discussions/migrations/0004_historicalprogramdiscussionsconfiguration_programdiscussionsconfiguration.py
new file mode 100644
index 00000000000..872d2887a7e
--- /dev/null
+++ b/openedx/core/djangoapps/discussions/migrations/0004_historicalprogramdiscussionsconfiguration_programdiscussionsconfiguration.py
@@ -0,0 +1,56 @@
+# Generated by Django 2.2.24 on 2021-08-31 11:18
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+import model_utils.fields
+import simple_history.models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('lti_consumer', '0012_rename_courseeditltifieldsenabledflag_model'),
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('discussions', '0003_alter_provider_filter_list'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='ProgramDiscussionsConfiguration',
+            fields=[
+                ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
+                ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
+                ('program_uuid', models.CharField(db_index=True, max_length=50, primary_key=True, serialize=False, verbose_name='Program UUID')),
+                ('enabled', models.BooleanField(default=True, help_text='If disabled, the discussions in the associated program will be disabled.')),
+                ('provider_type', models.CharField(help_text="The discussion provider's id", max_length=50, verbose_name='Discussion provider')),
+                ('lti_configuration', models.ForeignKey(blank=True, help_text='The LTI configuration data for this program/provider.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='lti_consumer.LtiConfiguration')),
+            ],
+            options={
+                'abstract': False,
+            },
+        ),
+        migrations.CreateModel(
+            name='HistoricalProgramDiscussionsConfiguration',
+            fields=[
+                ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
+                ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
+                ('program_uuid', models.CharField(db_index=True, max_length=50, verbose_name='Program UUID')),
+                ('enabled', models.BooleanField(default=True, help_text='If disabled, the discussions in the associated program will be disabled.')),
+                ('provider_type', models.CharField(help_text="The discussion provider's id", max_length=50, verbose_name='Discussion provider')),
+                ('history_id', models.AutoField(primary_key=True, serialize=False)),
+                ('history_date', models.DateTimeField()),
+                ('history_change_reason', models.CharField(max_length=100, null=True)),
+                ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
+                ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
+                ('lti_configuration', models.ForeignKey(blank=True, db_constraint=False, help_text='The LTI configuration data for this program/provider.', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='lti_consumer.LtiConfiguration')),
+            ],
+            options={
+                'verbose_name': 'historical program discussions configuration',
+                'ordering': ('-history_date', '-history_id'),
+                'get_latest_by': 'history_date',
+            },
+            bases=(simple_history.models.HistoricalChanges, models.Model),
+        ),
+    ]
diff --git a/openedx/core/djangoapps/discussions/models.py b/openedx/core/djangoapps/discussions/models.py
index 9164e478e89..f7d131af6c8 100644
--- a/openedx/core/djangoapps/discussions/models.py
+++ b/openedx/core/djangoapps/discussions/models.py
@@ -360,8 +360,7 @@ class DiscussionsConfiguration(TimeStampedModel):
 
     def clean(self):
         """
-        Validate the model
-
+        Validate the model.
         Currently, this only support courses, this can be extended
         whenever discussions are available in other contexts
         """
@@ -418,3 +417,50 @@ class DiscussionsConfiguration(TimeStampedModel):
     @classmethod
     def get_available_providers(cls, context_key: CourseKey) -> list[str]:
         return ProviderFilter.current(course_key=context_key).available_providers
+
+
+class ProgramDiscussionsConfiguration(TimeStampedModel):
+    """
+    Associates a program with a discussion provider and configuration
+    """
+
+    program_uuid = models.CharField(
+        primary_key=True,
+        db_index=True,
+        max_length=50,
+        verbose_name=_("Program UUID"),
+    )
+    enabled = models.BooleanField(
+        default=True,
+        help_text=_("If disabled, the discussions in the associated program will be disabled.")
+    )
+    lti_configuration = models.ForeignKey(
+        LtiConfiguration,
+        on_delete=models.SET_NULL,
+        blank=True,
+        null=True,
+        help_text=_("The LTI configuration data for this program/provider."),
+    )
+    provider_type = models.CharField(
+        blank=False,
+        max_length=50,
+        verbose_name=_("Discussion provider"),
+        help_text=_("The discussion provider's id"),
+    )
+    history = HistoricalRecords()
+
+    def __str__(self):
+        return f"ProgramDiscussionConfiguration(uuid='{self.uuid}', provider='{self.provider}', enabled={self.enabled})"
+
+    @classmethod
+    def is_enabled(cls, program_uuid) -> bool:
+        """
+        Check if there is an active configuration for a given program uuid
+
+        Default to False, if no configuration exists
+        """
+        try:
+            configuration = cls.objects.get(program_uuid=program_uuid)
+            return configuration.enabled
+        except cls.DoesNotExist:
+            return False
-- 
GitLab