diff --git a/.github/workflows/pylint-checks.yml b/.github/workflows/pylint-checks.yml index 28e08fb16c..e9703d8014 100644 --- a/.github/workflows/pylint-checks.yml +++ b/.github/workflows/pylint-checks.yml @@ -20,7 +20,7 @@ jobs: - module-name: openedx-1 path: "--django-settings-module=lms.envs.test 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/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/course_live/" - module-name: openedx-2 - path: "--django-settings-module=lms.envs.test 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/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/ openedx/core/djangoapps/learner_pathway/" + path: "--django-settings-module=lms.envs.test 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/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/ openedx/core/djangoapps/learner_pathway/ openedx/core/djangoapps/notifications/" - module-name: common path: "--django-settings-module=lms.envs.test common" - module-name: cms diff --git a/cms/envs/common.py b/cms/envs/common.py index 7cfebe63de..4e15b835bc 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -1634,6 +1634,9 @@ INSTALLED_APPS = [ # Discussion 'openedx.core.djangoapps.django_comment_common', + # Notifications + 'openedx.core.djangoapps.notifications', + # for course creator table 'django.contrib.admin', diff --git a/lms/envs/common.py b/lms/envs/common.py index e546df7b44..eb69ac6b01 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -3265,6 +3265,9 @@ INSTALLED_APPS = [ # MFE API 'lms.djangoapps.mfe_config_api', + + # Notifications + 'openedx.core.djangoapps.notifications', ] ######################### CSRF ######################################### diff --git a/openedx/core/djangoapps/notifications/__init__.py b/openedx/core/djangoapps/notifications/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openedx/core/djangoapps/notifications/admin.py b/openedx/core/djangoapps/notifications/admin.py new file mode 100644 index 0000000000..9c778c09f2 --- /dev/null +++ b/openedx/core/djangoapps/notifications/admin.py @@ -0,0 +1,13 @@ +""" +Django Admin for Notifications +""" + +from django.contrib import admin + +from .models import Notification + + +class NotificationAdmin(admin.ModelAdmin): + pass + +admin.site.register(Notification, NotificationAdmin) diff --git a/openedx/core/djangoapps/notifications/migrations/0001_initial.py b/openedx/core/djangoapps/notifications/migrations/0001_initial.py new file mode 100644 index 0000000000..510e4b877b --- /dev/null +++ b/openedx/core/djangoapps/notifications/migrations/0001_initial.py @@ -0,0 +1,38 @@ +# Generated by Django 3.2.18 on 2023-04-12 23: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 + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Notification', + 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')), + ('app_name', models.CharField(choices=[('DISCUSSION', 'Discussion')], max_length=64)), + ('notification_type', models.CharField(choices=[('NEW_CONTRIBUTION', 'New Contribution')], max_length=64)), + ('content', models.CharField(max_length=1024)), + ('content_context', models.JSONField(default={})), + ('content_url', models.URLField(blank=True, null=True)), + ('last_read', models.DateTimeField(blank=True, null=True)), + ('last_seen', models.DateTimeField(blank=True, null=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/openedx/core/djangoapps/notifications/migrations/__init__.py b/openedx/core/djangoapps/notifications/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openedx/core/djangoapps/notifications/models.py b/openedx/core/djangoapps/notifications/models.py new file mode 100644 index 0000000000..dea1873ca2 --- /dev/null +++ b/openedx/core/djangoapps/notifications/models.py @@ -0,0 +1,64 @@ +""" +Models for notifications +""" +from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user +from django.db import models +from model_utils.models import TimeStampedModel + + +class NotificationApplication(models.TextChoices): + """ + Application choices where notifications are generated from + """ + DISCUSSION = 'DISCUSSION' + + +class NotificationType(models.TextChoices): + """ + Notification type choices + """ + NEW_CONTRIBUTION = 'NEW_CONTRIBUTION' + + +class NotificationTypeContent: + """ + Notification type content + """ + NEW_CONTRIBUTION_NOTIFICATION_CONTENT = 'There is a new contribution. {new_contribution}' + + +class Notification(TimeStampedModel): + """ + Model to store notifications for users + + .. no_pii: + """ + user = models.ForeignKey(User, related_name="notifications", on_delete=models.CASCADE) + app_name = models.CharField(max_length=64, choices=NotificationApplication.choices) + notification_type = models.CharField(max_length=64, choices=NotificationType.choices) + content = models.CharField(max_length=1024) + content_context = models.JSONField(default={}) + content_url = models.URLField(null=True, blank=True) + last_read = models.DateTimeField(null=True, blank=True) + last_seen = models.DateTimeField(null=True, blank=True) + + def __str__(self): + return f'{self.user.username} - {self.app_name} - {self.notification_type} - {self.content}' + + def get_content(self): + return self.content + + def get_content_url(self): + return self.content_url + + def get_notification_type(self): + return self.notification_type + + def get_app_name(self): + return self.app_name + + def get_content_context(self): + return self.content_context + + def get_user(self): + return self.user