diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 568737f..60ecc4f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,14 @@ Change Log Unreleased ********** +0.9.4 - 2024-05-16 +****************** + +Fixes +===== + +* Allow to serialize dates as strings in JSON. + 0.9.3 - 2024-05-15 ****************** diff --git a/platform_plugin_aspects/__init__.py b/platform_plugin_aspects/__init__.py index 6ae703c..4391ab8 100644 --- a/platform_plugin_aspects/__init__.py +++ b/platform_plugin_aspects/__init__.py @@ -5,6 +5,6 @@ import os from pathlib import Path -__version__ = "0.9.3" +__version__ = "0.9.4" ROOT_DIRECTORY = Path(os.path.dirname(os.path.abspath(__file__))) diff --git a/platform_plugin_aspects/extensions/filters.py b/platform_plugin_aspects/extensions/filters.py index a3215c2..6fe3f39 100644 --- a/platform_plugin_aspects/extensions/filters.py +++ b/platform_plugin_aspects/extensions/filters.py @@ -71,7 +71,6 @@ def run_filter( "show_dashboard_link": show_dashboard_link, } - print(section_data) context["sections"].append(section_data) return { "context": context, diff --git a/platform_plugin_aspects/management/commands/load_test_tracking_events.py b/platform_plugin_aspects/management/commands/load_test_tracking_events.py index d6a58ad..3c1a518 100644 --- a/platform_plugin_aspects/management/commands/load_test_tracking_events.py +++ b/platform_plugin_aspects/management/commands/load_test_tracking_events.py @@ -158,7 +158,6 @@ def record_to_clickhouse(self, event_type, extra) -> None: output = io.StringIO() writer = csv.writer(output) writer.writerow((self.run_id, event_type, json.dumps(extra))) - print(output.getvalue().encode("utf-8")) response = requests.post( url=self.ch_url, diff --git a/platform_plugin_aspects/management/commands/monitor_load_test_tracking.py b/platform_plugin_aspects/management/commands/monitor_load_test_tracking.py index d1f902c..c7e89bc 100644 --- a/platform_plugin_aspects/management/commands/monitor_load_test_tracking.py +++ b/platform_plugin_aspects/management/commands/monitor_load_test_tracking.py @@ -145,6 +145,7 @@ def run(self) -> None: lag = None current_stats = {"clickhouse": self.get_clickhouse_stats()} + lag = -1 if collect_redis_bus: current_stats["redis_bus"] = self.get_redis_bus_stats() lag = current_stats["redis_bus"]["lag"] diff --git a/platform_plugin_aspects/sinks/serializers.py b/platform_plugin_aspects/sinks/serializers.py index a477780..2106ec9 100644 --- a/platform_plugin_aspects/sinks/serializers.py +++ b/platform_plugin_aspects/sinks/serializers.py @@ -2,13 +2,39 @@ import json import uuid +from datetime import date, datetime from django.utils import timezone +from pytz import UTC from rest_framework import serializers from platform_plugin_aspects.utils import get_model, get_tags_for_block +class DateTimeJSONEncoder(json.JSONEncoder): + """JSON encoder aware of datetime.datetime and datetime.date objects""" + + def default(self, obj): # pylint: disable=arguments-renamed + """ + Serialize datetime and date objects of iso format. + + datetime objects are converted to UTC. + """ + + if isinstance(obj, datetime): + if obj.tzinfo is None: + # Localize to UTC naive datetime objects + obj = UTC.localize(obj) # pylint: disable=no-value-for-parameter + else: + # Convert to UTC datetime objects from other timezones + obj = obj.astimezone(UTC) + return obj.isoformat() + elif isinstance(obj, date): + return obj.isoformat() + + return super().default(obj) + + class BaseSinkSerializer(serializers.Serializer): # pylint: disable=abstract-method """Base sink serializer for ClickHouse.""" @@ -148,7 +174,7 @@ def get_course_data_json(self, overview): "language": getattr(overview, "language", ""), "tags": get_tags_for_block(overview.id), } - return json.dumps(json_fields) + return json.dumps(json_fields, cls=DateTimeJSONEncoder) def get_course_key(self, overview): """Return the course key as a string.""" diff --git a/platform_plugin_aspects/sinks/tests/test_serializers.py b/platform_plugin_aspects/sinks/tests/test_serializers.py index 127451e..8e281e8 100644 --- a/platform_plugin_aspects/sinks/tests/test_serializers.py +++ b/platform_plugin_aspects/sinks/tests/test_serializers.py @@ -1,4 +1,5 @@ import json +from datetime import datetime from unittest.mock import Mock, patch from django.test import TestCase @@ -6,6 +7,7 @@ from platform_plugin_aspects.sinks.serializers import ( BaseSinkSerializer, CourseOverviewSerializer, + DateTimeJSONEncoder, ) from test_utils.helpers import course_key_factory @@ -60,7 +62,7 @@ def test_get_course_data_json(self, mock_get_tags): """ expected_tags = ["TAX1=tag1", "TAX2=tag2"] json_fields = { - "advertised_start": "2018-01-01T00:00:00Z", + "advertised_start": datetime.now(), "announcement": "announcement", "lowest_passing_grade": 0.0, "invitation_only": "invitation_only", @@ -80,7 +82,8 @@ def test_get_course_data_json(self, mock_get_tags): mock_get_tags.return_value = expected_tags self.assertEqual( - self.serializer.get_course_data_json(mock_overview), json.dumps(json_fields) + self.serializer.get_course_data_json(mock_overview), + json.dumps(json_fields, cls=DateTimeJSONEncoder), ) mock_get_tags.assert_called_once_with(mock_overview.id) diff --git a/platform_plugin_aspects/tests/test_xblock.py b/platform_plugin_aspects/tests/test_xblock.py index d890afc..2b4217f 100644 --- a/platform_plugin_aspects/tests/test_xblock.py +++ b/platform_plugin_aspects/tests/test_xblock.py @@ -103,8 +103,10 @@ def test_render_translations(self, mock_get_language, mock_resource_exists): for resource in student_view.resources: if resource.kind == "url": url_resource = resource - self.assertIsNotNone(url_resource, "No 'url' resource found in fragment") - self.assertIn("eo/text.js", url_resource.data) + self.assertIsNotNone( + url_resource, "No 'url' resource found in fragment" + ) + self.assertIn("eo/text.js", url_resource.data) mock_get_language.assert_called_once() mock_resource_exists.assert_called_once()