Skip to content

Commit

Permalink
ROLES-3 Model Setup (#33229)
Browse files Browse the repository at this point in the history
* feat: add CourseRolesService model

* feat: add CourseRolesRole model

* feat: add CourseRolesPermission model

* feat: add CourseRolesRolePermissions model

* feat: add CourseRolesUserRole model

* feat: remove wrong unique_together in CourseRolesUserRole

* feat: add coures_roles app to cms and lms common envs

* feat: add migrations

* feat: delete wrong migration

* feat: change many to many relationships to foreign keys

* feat: add many to many relationships between role-user and role-permission

* feat: add initial migration

* docs: add readme file

* docs: update readme file

* chore: add course_roles in quality matrix CI

* feat: remove fields related to custom roles

* docs: update docstrings

* feat: add minimal string representation change

* feat: remove descriptions from role and permission tables

* feat: swich the role to service relationship into a many to many relationship

* docs: update role docstring

* feat: update initial migration

* feat: change on_delete for org and coures on UserRole model to cascade

* feat: update initial migration

* docs: update CourseRolesPermission model docstring
  • Loading branch information
julianpalmerio authored Sep 13, 2023
1 parent edc80c2 commit 11f0f79
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .github/workflows/pylint-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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/content_staging/ 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/ openedx/core/djangoapps/notifications/ openedx/core/djangoapps/staticfiles/"
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/ openedx/core/djangoapps/staticfiles/ openedx/core/djangoapps/course_roles/"
- module-name: common
path: "--django-settings-module=lms.envs.test common"
- module-name: cms
Expand Down
3 changes: 3 additions & 0 deletions cms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -1788,6 +1788,9 @@

# Blockstore
'blockstore.apps.bundles',

# Course Roles
'openedx.core.djangoapps.course_roles',
]


Expand Down
3 changes: 3 additions & 0 deletions lms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -3295,6 +3295,9 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring

# Notifications
'openedx.core.djangoapps.notifications',

# Course Roles
'openedx.core.djangoapps.course_roles',
]

######################### CSRF #########################################
Expand Down
15 changes: 15 additions & 0 deletions openedx/core/djangoapps/course_roles/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Course Roles
#############################

Status: Development

Purpose
*******

The intention of this app is to to make course level roles for users within LMS/CMS more flexible.

Responsibilities
***************

Add models to manage the course roles and their permissions.
Provide a helper to check if a user has a specific permission in a course.
84 changes: 84 additions & 0 deletions openedx/core/djangoapps/course_roles/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Generated by Django 3.2.20 on 2023-09-12 22:03

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

initial = True

dependencies = [
('organizations', '0004_auto_20230727_2054'),
('course_overviews', '0029_alter_historicalcourseoverview_options'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='CourseRolesPermission',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True)),
],
),
migrations.CreateModel(
name='CourseRolesRole',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
],
),
migrations.CreateModel(
name='CourseRolesService',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True)),
],
),
migrations.CreateModel(
name='CourseRolesUserRole',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('course', models.ForeignKey(db_constraint=False, null=True, on_delete=django.db.models.deletion.CASCADE, to='course_overviews.courseoverview')),
('org', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='organizations.organization')),
('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='course_roles.courserolesrole')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'unique_together': {('user', 'role', 'course')},
},
),
migrations.CreateModel(
name='CourseRolesRoleService',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='course_roles.courserolesrole')),
('service', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='course_roles.courserolesservice')),
],
),
migrations.CreateModel(
name='CourseRolesRolePermissions',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='course_roles.courserolespermission')),
('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='course_roles.courserolesrole')),
],
),
migrations.AddField(
model_name='courserolesrole',
name='permissions',
field=models.ManyToManyField(through='course_roles.CourseRolesRolePermissions', to='course_roles.CourseRolesPermission'),
),
migrations.AddField(
model_name='courserolesrole',
name='services',
field=models.ManyToManyField(through='course_roles.CourseRolesRoleService', to='course_roles.CourseRolesService'),
),
migrations.AddField(
model_name='courserolesrole',
name='users',
field=models.ManyToManyField(through='course_roles.CourseRolesUserRole', to=settings.AUTH_USER_MODEL),
),
]
Empty file.
103 changes: 103 additions & 0 deletions openedx/core/djangoapps/course_roles/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
"""
Models for course roles schema
"""
from django.contrib.auth import get_user_model
from django.db import models

from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from organizations.models import Organization

User = get_user_model()


class CourseRolesRole(models.Model):
"""
Model for a course roles role.
A role is a collection of permissions that can be assigned to a user.
The services field defines for which service UI the role is intended, such as CMS and/or LMS.
"""
name = models.CharField(max_length=255)
services = models.ManyToManyField('CourseRolesService', through='CourseRolesRoleService')
permissions = models.ManyToManyField('CourseRolesPermission', through='CourseRolesRolePermissions')
users = models.ManyToManyField(User, through='CourseRolesUserRole')

def __str__(self):
return self.name


class CourseRolesPermission(models.Model):
"""
Model for a course roles permission.
A permission represents what a user can do.
"""
name = models.CharField(max_length=255, unique=True)

def __str__(self):
return self.name


class CourseRolesRolePermissions(models.Model):
"""
Model for a course roles role permission.
A role permission is a mapping between a role and a permission.
"""
role = models.ForeignKey('CourseRolesRole', on_delete=models.CASCADE)
permission = models.ForeignKey('CourseRolesPermission', on_delete=models.CASCADE)

def __str__(self):
return f"{self.role} - {self.permission}"


class CourseRolesUserRole(models.Model):
"""
Model for a course roles user role.
A user role is a mapping between a user, a role, a course and an organization.
If the course is null, the user role is for the organization.
If the course and the organization are null the role is for the instance.
"""
user = models.ForeignKey(User, on_delete=models.CASCADE)
role = models.ForeignKey('CourseRolesRole', on_delete=models.CASCADE)
course = models.ForeignKey(
CourseOverview,
db_constraint=False,
on_delete=models.CASCADE,
null=True,
)
org = models.ForeignKey(Organization, on_delete=models.CASCADE, null=False)

class Meta:
unique_together = ('user', 'role', 'course')

def __str__(self):
return f"{self.user} - {self.course} - {self.role} - {self.org}"


class CourseRolesService(models.Model):
"""
Model for a course roles service.
A service is a UI that can be used to assign roles to users.
Such as CMS or LMS.
"""
name = models.CharField(max_length=255, unique=True)

def __str__(self):
return self.name


class CourseRolesRoleService(models.Model):
"""
Model for a course roles role service.
A role service is a mapping between a role and a service.
"""
role = models.ForeignKey('CourseRolesRole', on_delete=models.CASCADE)
service = models.ForeignKey('CourseRolesService', on_delete=models.CASCADE)

def __str__(self):
return f"{self.role} - {self.service}"

0 comments on commit 11f0f79

Please sign in to comment.