diff --git a/.github/workflows/pylint-checks.yml b/.github/workflows/pylint-checks.yml index 799c0262f9..c6ddd9a238 100644 --- a/.github/workflows/pylint-checks.yml +++ b/.github/workflows/pylint-checks.yml @@ -19,7 +19,7 @@ jobs: - module-name: lms-2 path: "lms/djangoapps/gating/ lms/djangoapps/grades/ lms/djangoapps/instructor/ lms/djangoapps/instructor_analytics/ lms/djangoapps/discussion/ lms/djangoapps/edxnotes/ lms/djangoapps/email_marketing/ lms/djangoapps/experiments/ lms/djangoapps/instructor_task/ lms/djangoapps/learner_dashboard/ lms/djangoapps/lms_initialization/ lms/djangoapps/lms_xblock/ lms/djangoapps/lti_provider/ lms/djangoapps/mailing/ lms/djangoapps/mobile_api/ lms/djangoapps/monitoring/ lms/djangoapps/ora_staff_grader/ lms/djangoapps/program_enrollments/ lms/djangoapps/rss_proxy lms/djangoapps/static_template_view/ lms/djangoapps/staticbook/ lms/djangoapps/support/ lms/djangoapps/survey/ lms/djangoapps/teams/ lms/djangoapps/tests/ lms/djangoapps/user_tours/ lms/djangoapps/verify_student/ lms/envs/ lms/lib/ lms/tests.py" - module-name: openedx-1 - path: "openedx/core/types/ openedx/core/djangoapps/ace_common/ openedx/core/djangoapps/agreements/ openedx/core/djangoapps/api_admin/ openedx/core/djangoapps/auth_exchange/ openedx/core/djangoapps/bookmarks/ openedx/core/djangoapps/cache_toolbox/ openedx/core/djangoapps/catalog/ openedx/core/djangoapps/ccxcon/ openedx/core/djangoapps/commerce/ openedx/core/djangoapps/common_initialization/ openedx/core/djangoapps/common_views/ openedx/core/djangoapps/config_model_utils/ openedx/core/djangoapps/content/ openedx/core/djangoapps/content_libraries/ openedx/core/djangoapps/contentserver/ openedx/core/djangoapps/cookie_metadata/ openedx/core/djangoapps/cors_csrf/ openedx/core/djangoapps/course_apps/ openedx/core/djangoapps/course_date_signals/ openedx/core/djangoapps/course_groups/ openedx/core/djangoapps/coursegraph/ openedx/core/djangoapps/courseware_api/ openedx/core/djangoapps/crawlers/ openedx/core/djangoapps/credentials/ openedx/core/djangoapps/credit/ openedx/core/djangoapps/dark_lang/ openedx/core/djangoapps/debug/ openedx/core/djangoapps/demographics/ openedx/core/djangoapps/discussions/ openedx/core/djangoapps/django_comment_common/ openedx/core/djangoapps/embargo/ openedx/core/djangoapps/enrollments/ openedx/core/djangoapps/external_user_ids/ openedx/core/djangoapps/zendesk_proxy/ openedx/core/djangolib/ openedx/core/lib/ openedx/core/tests/ openedx/core/djangoapps/kafka_consumer/" + path: "openedx/core/types/ openedx/core/djangoapps/ace_common/ openedx/core/djangoapps/agreements/ openedx/core/djangoapps/api_admin/ openedx/core/djangoapps/auth_exchange/ openedx/core/djangoapps/bookmarks/ openedx/core/djangoapps/cache_toolbox/ openedx/core/djangoapps/catalog/ openedx/core/djangoapps/ccxcon/ openedx/core/djangoapps/commerce/ openedx/core/djangoapps/common_initialization/ openedx/core/djangoapps/common_views/ openedx/core/djangoapps/config_model_utils/ openedx/core/djangoapps/content/ openedx/core/djangoapps/content_libraries/ openedx/core/djangoapps/contentserver/ openedx/core/djangoapps/cookie_metadata/ openedx/core/djangoapps/cors_csrf/ openedx/core/djangoapps/course_apps/ openedx/core/djangoapps/course_date_signals/ openedx/core/djangoapps/course_groups/ openedx/core/djangoapps/coursegraph/ openedx/core/djangoapps/courseware_api/ openedx/core/djangoapps/crawlers/ openedx/core/djangoapps/credentials/ openedx/core/djangoapps/credit/ openedx/core/djangoapps/dark_lang/ openedx/core/djangoapps/debug/ openedx/core/djangoapps/demographics/ openedx/core/djangoapps/discussions/ openedx/core/djangoapps/django_comment_common/ openedx/core/djangoapps/embargo/ openedx/core/djangoapps/enrollments/ openedx/core/djangoapps/external_user_ids/ openedx/core/djangoapps/zendesk_proxy/ openedx/core/djangolib/ openedx/core/lib/ openedx/core/tests/ openedx/core/djangoapps/kafka_consumer/ openedx/core/djangoapps/course_live/" - module-name: openedx-2 path: "openedx/core/djangoapps/geoinfo/ openedx/core/djangoapps/header_control/ openedx/core/djangoapps/heartbeat/ openedx/core/djangoapps/lang_pref/ openedx/core/djangoapps/models/ openedx/core/djangoapps/monkey_patch/ openedx/core/djangoapps/oauth_dispatch/ openedx/core/djangoapps/olx_rest_api/ openedx/core/djangoapps/password_policy/ openedx/core/djangoapps/plugin_api/ openedx/core/djangoapps/plugins/ openedx/core/djangoapps/profile_images/ openedx/core/djangoapps/programs/ openedx/core/djangoapps/safe_sessions/ openedx/core/djangoapps/schedules/ openedx/core/djangoapps/self_paced/ openedx/core/djangoapps/service_status/ openedx/core/djangoapps/session_inactivity_timeout/ openedx/core/djangoapps/signals/ openedx/core/djangoapps/site_configuration/ openedx/core/djangoapps/system_wide_roles/ openedx/core/djangoapps/theming/ openedx/core/djangoapps/user_api/ openedx/core/djangoapps/user_authn/ openedx/core/djangoapps/util/ openedx/core/djangoapps/verified_track_content/ openedx/core/djangoapps/video_config/ openedx/core/djangoapps/video_pipeline/ openedx/core/djangoapps/waffle_utils/ openedx/core/djangoapps/xblock/ openedx/core/djangoapps/xmodule_django/ openedx/core/tests/ openedx/features/ openedx/testing/ openedx/tests/" - module-name: common diff --git a/lms/envs/common.py b/lms/envs/common.py index 3463e92e51..44d22a5965 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -3215,6 +3215,9 @@ INSTALLED_APPS = [ # TODO (EventBus): Make Kafka/event-bus optional 'openedx.core.djangoapps.kafka_consumer', + + # Course Live App + 'openedx.core.djangoapps.course_live', ] ######################### CSRF ######################################### diff --git a/openedx/core/djangoapps/course_live/__init__.py b/openedx/core/djangoapps/course_live/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openedx/core/djangoapps/course_live/admin.py b/openedx/core/djangoapps/course_live/admin.py new file mode 100644 index 0000000000..896a47c7e8 --- /dev/null +++ b/openedx/core/djangoapps/course_live/admin.py @@ -0,0 +1,25 @@ +""" +Admin interface for course live configuration +""" +from django.contrib import admin +from simple_history.admin import SimpleHistoryAdmin + +from openedx.core.djangoapps.course_live.models import CourseLiveConfiguration + + +class CourseLiveConfigurationAdmin(SimpleHistoryAdmin): + """ + Admin interface for the course live configuration + """ + search_fields = ( + 'course_key', + 'enabled', + 'provider_type', + ) + list_filter = ( + 'enabled', + 'provider_type', + ) + + +admin.site.register(CourseLiveConfiguration, CourseLiveConfigurationAdmin) diff --git a/openedx/core/djangoapps/course_live/migrations/0001_initial.py b/openedx/core/djangoapps/course_live/migrations/0001_initial.py new file mode 100644 index 0000000000..7deb99f9f4 --- /dev/null +++ b/openedx/core/djangoapps/course_live/migrations/0001_initial.py @@ -0,0 +1,60 @@ +# Generated by Django 3.2.12 on 2022-02-23 08:07 + +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 opaque_keys.edx.django.models +import simple_history.models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('lti_consumer', '0013_auto_20210712_1352'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='HistoricalCourseLiveConfiguration', + fields=[ + ('id', models.IntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), + ('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')), + ('course_key', opaque_keys.edx.django.models.CourseKeyField(db_index=True, max_length=255)), + ('enabled', models.BooleanField(default=True, help_text='If disabled, the LTI in the associated course will be disabled.')), + ('provider_type', models.CharField(help_text="The LTI provider's id", max_length=50, verbose_name='LTI 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 course/provider.', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='lti_consumer.lticonfiguration')), + ], + options={ + 'verbose_name': 'historical course live configuration', + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': 'history_date', + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + migrations.CreateModel( + name='CourseLiveConfiguration', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('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')), + ('course_key', opaque_keys.edx.django.models.CourseKeyField(db_index=True, max_length=255)), + ('enabled', models.BooleanField(default=True, help_text='If disabled, the LTI in the associated course will be disabled.')), + ('provider_type', models.CharField(help_text="The LTI provider's id", max_length=50, verbose_name='LTI provider')), + ('lti_configuration', models.ForeignKey(blank=True, help_text='The LTI configuration data for this course/provider.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='lti_consumer.lticonfiguration')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/openedx/core/djangoapps/course_live/migrations/__init__.py b/openedx/core/djangoapps/course_live/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openedx/core/djangoapps/course_live/models.py b/openedx/core/djangoapps/course_live/models.py new file mode 100644 index 0000000000..ef18eb56ae --- /dev/null +++ b/openedx/core/djangoapps/course_live/models.py @@ -0,0 +1,46 @@ +""" +Models course live integrations. +""" +from django.db import models +from django.utils.translation import gettext_lazy as _ +from simple_history.models import HistoricalRecords +from lti_consumer.models import LtiConfiguration +from model_utils.models import TimeStampedModel +from opaque_keys.edx.django.models import CourseKeyField + + +class CourseLiveConfiguration(TimeStampedModel): + """ + Associates a Course with a LTI provider and configuration + """ + course_key = CourseKeyField(max_length=255, db_index=True, null=False) + enabled = models.BooleanField( + default=True, + help_text=_("If disabled, the LTI in the associated course 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 course/provider."), + ) + provider_type = models.CharField( + blank=False, + max_length=50, + verbose_name=_("LTI provider"), + help_text=_("The LTI provider's id"), + ) + history = HistoricalRecords() + + def __str__(self): + return f"Configuration(course_key='{self.course_key}', provider='{self.provider_type}', enabled={self.enabled})" + + @classmethod + def get(cls, course_key): + """ + Lookup a course live configuration by course uuid. + """ + return cls.objects.filter( + course_key=course_key + ).first()