diff --git a/api/products/admin.py b/api/products/admin.py index 5c1da1c..f5bd385 100644 --- a/api/products/admin.py +++ b/api/products/admin.py @@ -20,3 +20,6 @@ class AuthTokenAdmin(admin.ModelAdmin): admin.site.register(AuthToken, AuthTokenAdmin) admin.site.register(GlobalConfig) + +admin.site.register(ABTest) +admin.site.register(ABTestSample) diff --git a/api/products/models.py b/api/products/models.py index 24ad2e8..7d96b06 100644 --- a/api/products/models.py +++ b/api/products/models.py @@ -414,3 +414,24 @@ class FrameLabel(models.Model): def __str__(self): return self.oss_path + +class ABTest(models.Model): + name = models.CharField(max_length=128, verbose_name="名称", db_index=True, unique=True) + description = models.TextField(verbose_name="描述") + test_object = models.CharField(max_length=128, verbose_name="实验对象") + create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建日期") + update_time = models.DateTimeField(auto_now=True, verbose_name="更新日期") + enabled = models.BooleanField(default=True, verbose_name="是否启用") + spec = models.TextField(verbose_name="实验配置") + + def __str__(self): + return f"{self.name} {self.category}" + +class ABTestSample(models.Model): + abtest = models.ForeignKey(ABTest, related_name="samples", on_delete=models.CASCADE) + data = models.TextField(verbose_name="实验数据") + create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建日期") + update_time = models.DateTimeField(auto_now=True, verbose_name="更新日期") + + def __str__(self): + return f"{self.pk} {self.abtest.name} {self.create_time}" diff --git a/api/products/views.py b/api/products/views.py index c692bcc..2408144 100644 --- a/api/products/views.py +++ b/api/products/views.py @@ -467,6 +467,24 @@ class CameraRuleResource(BaseResource): schema['fields']['notes']['longtext'] = True return schema +class ABTestResource(BaseResource): + class Meta: + queryset = ABTest.objects.all().order_by('-pk') + resource_name = 'ab-test' + authorization = BaseAuthorization() + serializer = BaseSerializer() + + auth_role = 'admin' + +class ABTestSampleResource(BaseResource): + class Meta: + queryset = ABTestSample.objects.all().order_by('-pk') + resource_name = 'ab-test-sample' + authorization = BaseAuthorization() + serializer = BaseSerializer() + + auth_role = 'admin' + for cls in BaseResource.__subclasses__(): v1_api.register(cls()) @@ -1886,4 +1904,38 @@ class LabelFrameView(BaseView): return JsonResponse({ 'ok': True, 'id': fo.id, - }) \ No newline at end of file + }) + +class ABTestView(BaseView): + name = 'ab-test' + auth_check = None + + def get(self, request): + test_object = request.GET.get('test_object', None) + abtest = ABTest.objects.filter(enabled=True) + if test_object: + abtest = abtest.filter(test_object=test_object) + return JsonResponse({ + 'items': [ + { + 'name': abtest.name, + 'test_object': abtest.test_object, + 'spec': abtest.spec, + } + for abtest in abtest + ] + }) + +class ABTestReportView(BaseView): + name = 'ab-test-report' + auth_check = None + + def post(self, request): + name = request.data.get('name') + abtest = ABTest.objects.filter(name=name).first() + if not abtest: + return http404() + sample = ABTestSample.objects.create(abtest=abtest, data=json.dumps(request.data.get('data'))) + return JsonResponse({ + 'ok': True, + }) diff --git a/api/tests/test_api.py b/api/tests/test_api.py index 5efa8cb..879230e 100755 --- a/api/tests/test_api.py +++ b/api/tests/test_api.py @@ -158,3 +158,43 @@ class APITest(TestCase): ) self.assertEqual(r.status_code, 200) self.assertIn('status', r.json()) + + def test_abtest(self): + r = self.get("/api/v1/ab-test/", admin=True) + self.assertEqual(r.status_code, 200) + self.assertIn('items', r.json()) + self.assertEqual(len(r.json()['items']), 0) + + + spec = """ + +""" + ABTest.objects.create(name="test", test_object="dummy", spec=spec) + r = self.get("/api/v1/ab-test/", admin=True) + self.assertEqual(r.status_code, 200) + self.assertIn('items', r.json()) + self.assertEqual(len(r.json()['items']), 1) + self.assertEqual(r.json()['items'][0]['name'], "test") + self.assertEqual(r.json()['items'][0]['test_object'], "dummy") + self.assertEqual(r.json()['items'][0]['spec'], spec) + + r = self.get("/api/v1/ab-test/?test_object=dummy") + self.assertEqual(r.status_code, 200) + self.assertEqual(len(r.json()['items']), 1) + + r = self.post("/api/v1/ab-test-report/", data={ + 'name': "test", + 'data': { + 'foo': "bar", + }, + }) + self.assertEqual(r.status_code, 200) + self.assertIn('ok', r.json()) + self.assertTrue(r.json()['ok']) + + abts = ABTestSample.objects.filter(abtest__name="test") + self.assertEqual(len(abts), 1) + self.assertEqual(abts[0].abtest.name, "test") + self.assertEqual(abts[0].data, json.dumps({ + 'foo': "bar", + })) \ No newline at end of file