diff --git a/contentcuration/automation/migrations/0001_initial.py b/contentcuration/automation/migrations/0001_initial.py index 6152c0f4b2..fd5d3ad213 100644 --- a/contentcuration/automation/migrations/0001_initial.py +++ b/contentcuration/automation/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.24 on 2024-08-05 21:23 +# Generated by Django 3.2.24 on 2025-03-26 11:12 import uuid import django.db.models.deletion @@ -7,26 +7,36 @@ class Migration(migrations.Migration): + initial = True dependencies = [ - ('kolibri_public', '0003_alter_file_preset'), + ('kolibri_public', '0005_alter_localfile_extension'), + ('contentcuration', '0151_embeddings_embeddingscontentnode'), ] operations = [ migrations.CreateModel( name='RecommendationsCache', fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, - serialize=False)), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ('request_hash', models.CharField(max_length=32, null=True)), - ('rank', models.FloatField(default=0.0, null=True)), + ('topic_id', models.UUIDField(blank=True, null=True)), + ('rank', models.IntegerField(default=0, null=True)), ('override_threshold', models.BooleanField(default=False)), ('timestamp', models.DateTimeField(auto_now_add=True)), - ('contentnode', models.ForeignKey(blank=True, null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name='recommendations', - to='kolibri_public.contentnode')), + ('channel', models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name='channel_recommendations', + to='contentcuration.channel')), + ('contentnode', models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name='contentnode_recommendations', + to='kolibri_public.contentnode')), ], ), migrations.AddIndex( diff --git a/contentcuration/automation/models.py b/contentcuration/automation/models.py index 7000ddf420..0d92dfd977 100644 --- a/contentcuration/automation/models.py +++ b/contentcuration/automation/models.py @@ -3,6 +3,8 @@ from django.db import models from kolibri_public.models import ContentNode +from contentcuration.models import Channel + REQUEST_HASH_INDEX_NAME = "request_hash_idx" CONTENTNODE_INDEX_NAME = "contentnode_idx" @@ -11,14 +13,22 @@ class RecommendationsCache(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) request_hash = models.CharField(max_length=32, null=True) + topic_id = models.UUIDField(null=True, blank=True) contentnode = models.ForeignKey( ContentNode, null=True, blank=True, - related_name='recommendations', + related_name='contentnode_recommendations', + on_delete=models.CASCADE, + ) + channel = models.ForeignKey( + Channel, + null=True, + blank=True, + related_name='channel_recommendations', on_delete=models.CASCADE, ) - rank = models.FloatField(default=0.0, null=True) + rank = models.IntegerField(default=0, null=True) override_threshold = models.BooleanField(default=False) timestamp = models.DateTimeField(auto_now_add=True) diff --git a/contentcuration/automation/tests/test_recommendations_cache_model.py b/contentcuration/automation/tests/test_recommendations_cache_model.py index 9cb2e96c9a..01024c3ee0 100644 --- a/contentcuration/automation/tests/test_recommendations_cache_model.py +++ b/contentcuration/automation/tests/test_recommendations_cache_model.py @@ -4,30 +4,41 @@ from django.db import IntegrityError from kolibri_public.models import ContentNode +from contentcuration.models import Channel from contentcuration.tests.base import StudioTestCase class TestRecommendationsCache(StudioTestCase): def setUp(self): + self.topic_id = uuid.uuid4() self.content_node = ContentNode.objects.create( id=uuid.uuid4(), title='Test Content Node', content_id=uuid.uuid4(), channel_id=uuid.uuid4(), ) + self.channel = Channel.objects.create( + id=uuid.uuid4(), + name='Test Channel', + actor_id=1, + ) self.cache = RecommendationsCache.objects.create( request_hash='test_hash', + topic_id=self.topic_id, contentnode=self.content_node, - rank=1.0, + channel=self.channel, + rank=1, override_threshold=False ) def test_cache_creation(self): self.assertIsInstance(self.cache, RecommendationsCache) self.assertEqual(self.cache.request_hash, 'test_hash') + self.assertEqual(self.cache.topic_id, self.topic_id) self.assertEqual(self.cache.contentnode, self.content_node) - self.assertEqual(self.cache.rank, 1.0) + self.assertEqual(self.cache.channel, self.channel) + self.assertEqual(self.cache.rank, 1) self.assertFalse(self.cache.override_threshold) def test_cache_retrieval(self): @@ -38,8 +49,10 @@ def test_cache_uniqueness(self): with self.assertRaises(IntegrityError): RecommendationsCache.objects.create( request_hash='test_hash', + topic_id=self.topic_id, contentnode=self.content_node, - rank=2.0, + channel=self.channel, + rank=2, override_threshold=True ) diff --git a/contentcuration/automation/utils/appnexus/base.py b/contentcuration/automation/utils/appnexus/base.py index 626bca1d58..616c057c93 100644 --- a/contentcuration/automation/utils/appnexus/base.py +++ b/contentcuration/automation/utils/appnexus/base.py @@ -5,7 +5,7 @@ import requests from requests.adapters import HTTPAdapter -from requests.packages.urllib3.util.retry import Retry +from urllib3 import Retry from . import errors @@ -77,7 +77,7 @@ def __new__(cls, *args, **kwargs): def __init__( self, - url_prefix="", + url_prefix="stable", ): self.url_prefix = url_prefix if not self.session: @@ -161,17 +161,19 @@ def connect(self, **kwargs): """ Establishes a connection to the backend service. """ try: request = BackendRequest(method="GET", path=self.connect_endpoint, **kwargs) - self._make_request(request) - return True + api_response = self._make_request(request) + response_data = api_response.json() + status = response_data.get("status", None) + return status == "OK" except Exception: return False def make_request(self, request): """ Make a request to the backend service. """ try: - response = self._make_request(request) - response_body = dict(data=response.json()) - return BackendResponse(**response_body) + api_response = self._make_request(request) + response_data = api_response.json() + return BackendResponse(data=response_data) except ValueError as e: logging.exception(e) raise errors.InvalidResponse("Invalid response from backend") diff --git a/contentcuration/contentcuration/dev_settings.py b/contentcuration/contentcuration/dev_settings.py index 439bdef8af..d81d23a993 100644 --- a/contentcuration/contentcuration/dev_settings.py +++ b/contentcuration/contentcuration/dev_settings.py @@ -5,4 +5,4 @@ ROOT_URLCONF = "contentcuration.dev_urls" -INSTALLED_APPS += ("drf_yasg", "automation") +INSTALLED_APPS += ("drf_yasg",) diff --git a/contentcuration/contentcuration/frontend/RecommendedResourceCard/components/RecommendedResourceCard.vue b/contentcuration/contentcuration/frontend/RecommendedResourceCard/components/RecommendedResourceCard.vue index fc69859fc5..719b0e9bed 100644 --- a/contentcuration/contentcuration/frontend/RecommendedResourceCard/components/RecommendedResourceCard.vue +++ b/contentcuration/contentcuration/frontend/RecommendedResourceCard/components/RecommendedResourceCard.vue @@ -1,40 +1,46 @@