diff --git a/openedx_tagging/core/tagging/admin.py b/openedx_tagging/core/tagging/admin.py index 5be444cfc..4016df6ef 100644 --- a/openedx_tagging/core/tagging/admin.py +++ b/openedx_tagging/core/tagging/admin.py @@ -1,10 +1,44 @@ """ Tagging app admin """ +from __future__ import annotations + from django.contrib import admin from .models import ObjectTag, Tag, Taxonomy admin.site.register(Taxonomy) -admin.site.register(Tag) -admin.site.register(ObjectTag) + + +@admin.register(Tag) +class TagAdmin(admin.ModelAdmin): + """ + Admin definition for Tag model + """ + autocomplete_fields = ["parent"] + search_fields = ["value", "external_id"] + list_display = ["__str__", "taxonomy", "external_id"] + list_filter = ["taxonomy"] + + def has_add_permission(self, request): + """ + Don't create Tags using the django admin. Use the API or UI. + """ + return False + + +@admin.register(ObjectTag) +class ObjectTagAdmin(admin.ModelAdmin): + """ + Admin definition for ObjectTag model + """ + fields = ["object_id", "taxonomy", "tag", "_value"] + autocomplete_fields = ["tag"] + list_display = ["object_id", "name", "value"] + readonly_fields = ["object_id"] + + def has_add_permission(self, request): + """ + Don't create ObjectTags using the django admin. Use the API or UI. + """ + return False diff --git a/openedx_tagging/core/tagging/migrations/0014_minor_fixes.py b/openedx_tagging/core/tagging/migrations/0014_minor_fixes.py new file mode 100644 index 000000000..e09177899 --- /dev/null +++ b/openedx_tagging/core/tagging/migrations/0014_minor_fixes.py @@ -0,0 +1,36 @@ +# Generated by Django 3.2.22 on 2023-11-08 22:37 + +from django.db import migrations, models + +import openedx_learning.lib.fields + + +def fix_language_taxonomy(apps, schema_editor): + """ + Ensure that the language taxonomy has the correct taxonomy_class. Some + previous versions of this app's migration history allowed it to be created + without the right taxonomy_class. + """ + Taxonomy = apps.get_model("oel_tagging", "Taxonomy") + correct_class = "openedx_tagging.core.tagging.models.system_defined.LanguageTaxonomy" + lang_taxonomy = Taxonomy.objects.get(pk=-1) + if lang_taxonomy._taxonomy_class != correct_class: + lang_taxonomy._taxonomy_class = correct_class + lang_taxonomy.save(update_fields=["_taxonomy_class"]) + + +class Migration(migrations.Migration): + + dependencies = [ + ('oel_tagging', '0013_tag_parent_blank'), + ] + + operations = [ + # Allow taxonomy_class to be blank: + migrations.AlterField( + model_name='taxonomy', + name='_taxonomy_class', + field=models.CharField(blank=True, help_text='Taxonomy subclass used to instantiate this instance; must be a fully-qualified module and class name. If the module/class cannot be imported, an error is logged and the base Taxonomy class is used instead.', max_length=255, null=True), + ), + migrations.RunPython(fix_language_taxonomy, None), + ] diff --git a/openedx_tagging/core/tagging/models/base.py b/openedx_tagging/core/tagging/models/base.py index 86f948770..f79bb2522 100644 --- a/openedx_tagging/core/tagging/models/base.py +++ b/openedx_tagging/core/tagging/models/base.py @@ -227,6 +227,7 @@ class Taxonomy(models.Model): "Taxonomy subclass used to instantiate this instance; must be a fully-qualified module and class name." " If the module/class cannot be imported, an error is logged and the base Taxonomy class is used instead." ), + blank=True, ) class Meta: diff --git a/openedx_tagging/core/tagging/rest_api/v1/views.py b/openedx_tagging/core/tagging/rest_api/v1/views.py index d2566ca2c..e2145200f 100644 --- a/openedx_tagging/core/tagging/rest_api/v1/views.py +++ b/openedx_tagging/core/tagging/rest_api/v1/views.py @@ -170,7 +170,8 @@ class TaxonomyView(ModelViewSet): """ - lookup_value_regex = r"\d+" + # System taxonomies use negative numbers for their primary keys + lookup_value_regex = r'-?\d+' serializer_class = TaxonomySerializer permission_classes = [TaxonomyObjectPermissions] diff --git a/tests/openedx_tagging/core/tagging/test_views.py b/tests/openedx_tagging/core/tagging/test_views.py index bdb214aed..1a1c69ada 100644 --- a/tests/openedx_tagging/core/tagging/test_views.py +++ b/tests/openedx_tagging/core/tagging/test_views.py @@ -169,6 +169,24 @@ def test_list_invalid_page(self) -> None: assert response.status_code == status.HTTP_404_NOT_FOUND + def test_language_taxonomy(self): + """ + Test the "Language" taxonomy that's included. + """ + self.client.force_authenticate(user=self.user) + response = self.client.get(TAXONOMY_LIST_URL) + assert response.status_code == status.HTTP_200_OK + taxonomy_list = response.data["results"] + assert len(taxonomy_list) == 1 + check_taxonomy( + taxonomy_list[0], + taxonomy_id=LANGUAGE_TAXONOMY_ID, + name="Languages", + description="Languages that are enabled on this system.", + allow_multiple=False, # We may change this in the future to allow multiple language tags + system_defined=True, + ) + @ddt.data( (None, {"enabled": True}, status.HTTP_401_UNAUTHORIZED), (None, {"enabled": False}, status.HTTP_401_UNAUTHORIZED), @@ -193,6 +211,13 @@ def test_detail_taxonomy(self, user_attr: str | None, taxonomy_data: dict[str, b if status.is_success(expected_status): check_taxonomy(response.data, taxonomy.pk, **create_data) + def test_detail_system_taxonomy(self): + url = TAXONOMY_DETAIL_URL.format(pk=LANGUAGE_TAXONOMY_ID) + self.client.force_authenticate(user=self.user) + + response = self.client.get(url) + assert response.status_code == status.HTTP_200_OK + def test_detail_taxonomy_404(self) -> None: url = TAXONOMY_DETAIL_URL.format(pk=123123)