diff --git a/metrics_app/__init__.py b/metrics_app/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/metrics_app/admin.py b/metrics_app/admin.py new file mode 100644 index 000000000..05f0da18f --- /dev/null +++ b/metrics_app/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin + +from metrics_app import models + +# Register your models here. +admin.site.register(models.Metric) diff --git a/metrics_app/apps.py b/metrics_app/apps.py new file mode 100644 index 000000000..a8b5b9002 --- /dev/null +++ b/metrics_app/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class MetricsAppConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'metrics_app' diff --git a/metrics_app/migrations/0001_initial.py b/metrics_app/migrations/0001_initial.py new file mode 100644 index 000000000..fccae0e62 --- /dev/null +++ b/metrics_app/migrations/0001_initial.py @@ -0,0 +1,32 @@ +# Generated by Django 4.2.17 on 2025-05-27 16:29 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Metric', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('modified', models.DateTimeField(auto_now=True, help_text='The date/time this resource was created.')), + ('created', models.DateTimeField(auto_now_add=True, help_text='The date/time this resource was created.')), + ('service', models.CharField(max_length=32)), + ('payload', models.JSONField(default=dict)), + ('created_by', models.ForeignKey(default=None, editable=False, help_text='The user who created this resource.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(app_label)s_%(class)s_created+', to=settings.AUTH_USER_MODEL)), + ('modified_by', models.ForeignKey(default=None, editable=False, help_text='The user who last modified this resource.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(app_label)s_%(class)s_modified+', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/metrics_app/migrations/__init__.py b/metrics_app/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/metrics_app/models.py b/metrics_app/models.py new file mode 100644 index 000000000..fc1af01c9 --- /dev/null +++ b/metrics_app/models.py @@ -0,0 +1,7 @@ +from ansible_base.lib.abstract_models import CommonModel +from django.db import models + +# Create your models here. +class Metric(CommonModel): + service = models.CharField(max_length=32) + payload = models.JSONField(default=dict) diff --git a/metrics_app/serializers.py b/metrics_app/serializers.py new file mode 100644 index 000000000..88f26f0f5 --- /dev/null +++ b/metrics_app/serializers.py @@ -0,0 +1,11 @@ +from rest_framework.serializers import ModelSerializer + +from ansible_base.lib.serializers.common import NamedCommonModelSerializer +from ansible_base.rbac.api.related import RelatedAccessMixin +from metrics_app import models + + +class MetricsSerializer(RelatedAccessMixin, NamedCommonModelSerializer): + class Meta: + model = models.Metric + fields = '__all__' \ No newline at end of file diff --git a/metrics_app/tests.py b/metrics_app/tests.py new file mode 100644 index 000000000..7ce503c2d --- /dev/null +++ b/metrics_app/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/metrics_app/tests/test_metrics.py b/metrics_app/tests/test_metrics.py new file mode 100644 index 000000000..761726561 --- /dev/null +++ b/metrics_app/tests/test_metrics.py @@ -0,0 +1,13 @@ +import pytest +from metrics_app.models import Metric + +@pytest.mark.django_db +def test_create_metric_default_payload(): + result = Metric.objects.create() + result.save() + assert result.payload == dict() + +@pytest.mark.django_db +def test_modifying_metric_updates_modified_time(): + # TODO: Implement this test + raise NotImplementedError() \ No newline at end of file diff --git a/metrics_app/views.py b/metrics_app/views.py new file mode 100644 index 000000000..6ebec485d --- /dev/null +++ b/metrics_app/views.py @@ -0,0 +1,27 @@ +import logging +from rest_framework.viewsets import ModelViewSet + +from ansible_base.lib.utils.response import get_fully_qualified_url +from ansible_base.lib.utils.views.ansible_base import AnsibleBaseView +from ansible_base.rbac import permission_registry +from ansible_base.rbac.policies import visible_users +from metrics_app import models, serializers + +logger = logging.getLogger(__name__) + + +class MetricsAppViewSet(ModelViewSet, AnsibleBaseView): + prefetch_related = () + select_related = () + + def apply_optimizations(self, qs): + if self.prefetch_related: + qs = qs.prefetch_related(*self.prefetch_related) + if self.select_related: + qs = qs.select_related(*self.select_related) + return qs + +class MetricsViewSet(MetricsAppViewSet): + serializer_class = serializers.MetricsSerializer + prefetch_related = ('created_by', 'modified_by', 'resource', 'resource__content_type') + queryset = models.Metric.objects.all() diff --git a/test_app/defaults.py b/test_app/defaults.py index ba8be1ee9..dd1d5219e 100644 --- a/test_app/defaults.py +++ b/test_app/defaults.py @@ -66,6 +66,7 @@ 'ansible_base.activitystream', 'ansible_base.help_text_check', 'ansible_base.feature_flags', + 'metrics_app', ] MIDDLEWARE = [ diff --git a/test_app/router.py b/test_app/router.py index 189bdc4fe..2eaad945a 100644 --- a/test_app/router.py +++ b/test_app/router.py @@ -2,10 +2,12 @@ from ansible_base.oauth2_provider import views as oauth2_provider_views from ansible_base.rbac.api import views as rbac_views from test_app import views +from metrics_app import views as metrics_app_views router = AssociationResourceRouter() # using an intentionally unpredictable basename router.register(r'encrypted_models', views.EncryptionModelViewSet, basename='encryption_test_model') +router.register(r'metrics', metrics_app_views.MetricsViewSet, basename='metrics') # intentionally not registering ResourceMigrationTestModel to test lack of URLs diff --git a/test_app/urls.py b/test_app/urls.py index ba7bf4b3b..447c30aca 100644 --- a/test_app/urls.py +++ b/test_app/urls.py @@ -7,6 +7,7 @@ from ansible_base.lib.dynamic_config.dynamic_urls import api_urls, api_version_urls, root_urls from ansible_base.resource_registry.urls import urlpatterns as resource_api_urls from test_app import views +from metrics_app import views as metrics_app_views from test_app.router import router as test_app_router urlpatterns = [ @@ -22,6 +23,7 @@ path('api/v1/', include(resource_api_urls)), path('api/v1/', views.api_root), path('api/v1/timeout_view/', views.timeout_view, name='test-timeout-view'), + path('api/v1/metrics/', metrics_app_views.MetricsViewSet.as_view({'get': 'list'}), name='metrics'), path('login/', include('rest_framework.urls')), path("__debug__/", include("debug_toolbar.urls")), ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)