Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 0 additions & 14 deletions cms/djangoapps/contentstore/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@

# from cms.djangoapps.contentstore.views.preview import _load_preview_block
from cms.djangoapps.models.settings.course_grading import CourseGradingModel
from common.djangoapps.student import auth
from common.djangoapps.student.roles import CourseCreatorRole, OrgContentCreatorRole

try:
# Technically this is a django app plugin, so we should not error if it's not installed:
Expand Down Expand Up @@ -272,15 +270,3 @@ def is_item_in_course_tree(item):
ancestor = ancestor.get_parent()

return ancestor is not None


def is_content_creator(user, org):
"""
Check if the user has the role to create content.

This function checks if the User has role to create content
or if the org is supplied, it checks for Org level course content
creator.
"""
return (auth.user_has_role(user, CourseCreatorRole()) or
auth.user_has_role(user, OrgContentCreatorRole(org=org)))
4 changes: 2 additions & 2 deletions cms/djangoapps/contentstore/views/course.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@
has_course_author_access,
has_studio_read_access,
has_studio_write_access,
has_studio_advanced_settings_access
has_studio_advanced_settings_access,
is_content_creator,
)
from common.djangoapps.student.roles import (
CourseInstructorRole,
Expand Down Expand Up @@ -102,7 +103,6 @@
update_course_details,
)
from .component import ADVANCED_COMPONENT_TYPES
from ..helpers import is_content_creator
from cms.djangoapps.contentstore.xblock_services.xblock_service import (
create_xblock_info,
)
Expand Down
4 changes: 2 additions & 2 deletions cms/djangoapps/contentstore/views/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
STUDIO_VIEW_USERS,
get_user_permissions,
has_studio_read_access,
has_studio_write_access
has_studio_write_access,
is_content_creator,
)
from common.djangoapps.student.roles import (
CourseInstructorRole,
Expand All @@ -42,7 +43,6 @@
from ..config.waffle import REDIRECT_TO_LIBRARY_AUTHORING_MICROFRONTEND
from ..utils import add_instructor, reverse_library_url
from .component import CONTAINER_TEMPLATES, get_component_templates
from ..helpers import is_content_creator
from cms.djangoapps.contentstore.xblock_services.xblock_service import create_xblock_info
from .user import user_with_role

Expand Down
4 changes: 4 additions & 0 deletions cms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -1756,10 +1756,14 @@
# API Documentation
'drf_yasg',

# Tagging
'openedx_tagging.core.tagging.apps.TaggingConfig',

'openedx.features.course_duration_limits',
'openedx.features.content_type_gating',
'openedx.features.discounts',
'openedx.features.effort_estimation',
'openedx.features.content_tagging',
'lms.djangoapps.experiments',

'openedx.core.djangoapps.external_user_ids',
Expand Down
12 changes: 12 additions & 0 deletions common/djangoapps/student/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,18 @@ def has_studio_read_access(user, course_key):
return bool(STUDIO_VIEW_CONTENT & get_user_permissions(user, course_key))


def is_content_creator(user, org):
"""
Check if the user has the role to create content.

This function checks if the User has role to create content
or if the org is supplied, it checks for Org level course content
creator.
"""
return (user_has_role(user, CourseCreatorRole()) or
user_has_role(user, OrgContentCreatorRole(org=org)))


def add_users(caller, role, *users):
"""
The caller requests adding the given users to the role. Checks that the caller
Expand Down
4 changes: 4 additions & 0 deletions lms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -3216,6 +3216,9 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring
# Course Goals
'lms.djangoapps.course_goals.apps.CourseGoalsConfig',

# Tagging
Comment thread
pomegranited marked this conversation as resolved.
Resolved
'openedx_tagging.core.tagging.apps.TaggingConfig',

# Features
'openedx.features.calendar_sync',
'openedx.features.course_bookmarks',
Expand All @@ -3227,6 +3230,7 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring
'openedx.features.discounts',
'openedx.features.effort_estimation',
'openedx.features.name_affirmation_api.apps.NameAffirmationApiConfig',
'openedx.features.content_tagging',

'lms.djangoapps.experiments',

Expand Down
Empty file.
19 changes: 19 additions & 0 deletions openedx/features/content_tagging/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
""" Tagging app admin """
from django.contrib import admin

from .models import ContentTaxonomy


class ContentTaxonomyOrgAdmin(admin.TabularInline):
model = ContentTaxonomy.org_owners.through


class ContentTaxonomyAdmin(admin.ModelAdmin):
"""
Admin form for the content taxonomy table.
"""

inlines = (ContentTaxonomyOrgAdmin,)


admin.site.register(ContentTaxonomy, ContentTaxonomyAdmin)
106 changes: 106 additions & 0 deletions openedx/features/content_tagging/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"""
Content Tagging APIs
"""
from typing import List

import openedx_tagging.core.tagging.api as oel_tagging
from django.db.models import QuerySet
from openedx_tagging.core.tagging.models import ObjectTag, Tag, Taxonomy
from organizations.models import Organization

from .models import ContentTaxonomy


def create_taxonomy(
name,
org_owners: List[Organization] = None,
description=None,
enabled=True,
required=False,
allow_multiple=False,
allow_free_text=False,
) -> ContentTaxonomy:
"""
Creates, saves, and returns a new ContentTaxonomy with the given attributes.

If `org_owners` is empty/None, then the returned taxonomy is enabled for all organizations.
"""
taxonomy = ContentTaxonomy.objects.create(
name=name,
description=description,
enabled=enabled,
required=required,
allow_multiple=allow_multiple,
allow_free_text=allow_free_text,
)
if org_owners:
set_taxonomy_org_owners(taxonomy, org_owners)
return taxonomy


def set_taxonomy_org_owners(
taxonomy: ContentTaxonomy,
org_owners: List[Organization] = None,
) -> ContentTaxonomy:
"""
Updates the list of org_owners on the given taxonomy.

If `org_owners` is empty/None, then the returned taxonomy is enabled for all organizations.
"""
if org_owners:
taxonomy.org_owners.set(org_owners)
else:
taxonomy.org_owners.clear()
return taxonomy


def get_taxonomies(org_owner: Organization = None, enabled=True) -> QuerySet:
"""
Returns a queryset containing the enabled taxonomies owned by the given org, sorted by name.

If you want the disabled taxonomies, pass enabled=False.
If you want all taxonomies (both enabled and disabled), pass enabled=None.
"""
return (
ContentTaxonomy.objects.filter_enabled(
org_owner,
enabled,
)
.order_by("name", "id")
.select_subclasses()
)


# Expose the oel_tagging APIs
Comment thread
pomegranited marked this conversation as resolved.
Resolved


def get_tags(taxonomy: Taxonomy) -> List[Tag]:
"""
Exposes the oel_tagging.get_tags API method.
"""
return oel_tagging.get_tags(taxonomy)


def resync_object_tags(object_tags: QuerySet = None) -> int:
"""
Exposes the oel_tagging.resync_object_tags API method.
"""
return oel_tagging.resync_object_tags(object_tags)


def get_object_tags(
taxonomy: Taxonomy, object_id: str, object_type: str, valid_only=True
) -> List[ObjectTag]:
"""
Exposes the oel_tagging.get_object_tags API method.
"""
return oel_tagging.get_object_tags(taxonomy, object_id, object_type, valid_only)


def tag_object(
taxonomy: Taxonomy, tags: List, object_id: str, object_type: str
) -> List[ObjectTag]:
"""
Exposes the oel_tagging.tag_object API method.
"""
return oel_tagging.tag_object(taxonomy, tags, object_id, object_type)
12 changes: 12 additions & 0 deletions openedx/features/content_tagging/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""
Define the content tagging Django App.
"""

from django.apps import AppConfig


class ContentTaggingConfig(AppConfig):
"""App config for the content tagging feature"""

default_auto_field = "django.db.models.BigAutoField"
name = "openedx.features.content_tagging"
72 changes: 72 additions & 0 deletions openedx/features/content_tagging/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Generated by Django 3.2.19 on 2023-06-19 22:44

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


class Migration(migrations.Migration):
initial = True

dependencies = [
("organizations", "0003_historicalorganizationcourse"),
("oel_tagging", "0001_initial"),
]

operations = [
migrations.CreateModel(
name="ContentTaxonomy",
fields=[
(
"taxonomy_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="oel_tagging.taxonomy",
),
),
],
options={
"verbose_name_plural": "ContentTaxonomies",
},
bases=("oel_tagging.taxonomy",),
),
migrations.CreateModel(
name="ContentTaxonomyOrg",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"org",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="organizations.organization",
),
),
(
"taxonomy",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="content_tagging.contenttaxonomy",
),
),
],
),
migrations.AddField(
model_name="contenttaxonomy",
name="org_owners",
field=models.ManyToManyField(
through="content_tagging.ContentTaxonomyOrg",
to="organizations.Organization",
),
),
]
Empty file.
Loading