django-structured-metaobjects 1.0.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_structured_metaobjects-1.0.0.dist-info/METADATA +112 -0
- django_structured_metaobjects-1.0.0.dist-info/RECORD +40 -0
- django_structured_metaobjects-1.0.0.dist-info/WHEEL +6 -0
- django_structured_metaobjects-1.0.0.dist-info/licenses/LICENSE +21 -0
- django_structured_metaobjects-1.0.0.dist-info/top_level.txt +2 -0
- structured_metaobjects/__init__.py +14 -0
- structured_metaobjects/admin.py +20 -0
- structured_metaobjects/apps.py +8 -0
- structured_metaobjects/compiler.py +161 -0
- structured_metaobjects/forms.py +36 -0
- structured_metaobjects/migrations/0001_initial.py +73 -0
- structured_metaobjects/migrations/__init__.py +0 -0
- structured_metaobjects/models.py +103 -0
- structured_metaobjects/schema_builder.py +166 -0
- structured_metaobjects/serializers.py +37 -0
- structured_metaobjects/static/structured_metaobjects/admin/meta_instance.js +23 -0
- structured_metaobjects/urls.py +12 -0
- structured_metaobjects/views.py +35 -0
- tests/__init__.py +0 -0
- tests/app/__init__.py +0 -0
- tests/app/app/__init__.py +0 -0
- tests/app/app/asgi.py +5 -0
- tests/app/app/settings.py +77 -0
- tests/app/app/urls.py +14 -0
- tests/app/app/wsgi.py +5 -0
- tests/app/test_module/__init__.py +0 -0
- tests/app/test_module/admin.py +8 -0
- tests/app/test_module/apps.py +8 -0
- tests/app/test_module/migrations/0001_initial.py +22 -0
- tests/app/test_module/migrations/__init__.py +0 -0
- tests/app/test_module/models.py +12 -0
- tests/app/test_module/serializers.py +9 -0
- tests/app/test_module/views.py +9 -0
- tests/test_admin.py +25 -0
- tests/test_compiler.py +462 -0
- tests/test_forms.py +32 -0
- tests/test_models.py +213 -0
- tests/test_schema_builder.py +81 -0
- tests/test_serializers.py +79 -0
- tests/test_views.py +120 -0
tests/test_models.py
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from django.core.exceptions import ValidationError
|
|
3
|
+
from django.test.utils import override_settings
|
|
4
|
+
|
|
5
|
+
from structured_metaobjects.compiler import _cache, clear_cache
|
|
6
|
+
from structured_metaobjects.models import MetaInstance, MetaType
|
|
7
|
+
from tests.app.test_module.models import Page
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.mark.django_db
|
|
11
|
+
class TestMetaType:
|
|
12
|
+
def test_create(self):
|
|
13
|
+
mt = MetaType.objects.create(
|
|
14
|
+
name="Article",
|
|
15
|
+
schema=[{"name": "title", "kind": "string", "required": True}],
|
|
16
|
+
)
|
|
17
|
+
assert mt.pk is not None
|
|
18
|
+
assert str(mt) == "Article"
|
|
19
|
+
|
|
20
|
+
def test_save_clears_cache(self):
|
|
21
|
+
mt = MetaType.objects.create(
|
|
22
|
+
name="Cache Inv",
|
|
23
|
+
schema=[{"name": "x", "kind": "string"}],
|
|
24
|
+
)
|
|
25
|
+
mt.get_pydantic_model()
|
|
26
|
+
assert any(k[0] == mt.pk for k in _cache)
|
|
27
|
+
mt.name = "Updated"
|
|
28
|
+
mt.save()
|
|
29
|
+
assert not any(k[0] == mt.pk for k in _cache)
|
|
30
|
+
|
|
31
|
+
def test_get_pydantic_model(self):
|
|
32
|
+
mt = MetaType.objects.create(
|
|
33
|
+
name="PM",
|
|
34
|
+
schema=[{"name": "body", "kind": "string", "multiline": True}],
|
|
35
|
+
)
|
|
36
|
+
clear_cache()
|
|
37
|
+
model_cls = mt.get_pydantic_model()
|
|
38
|
+
assert hasattr(model_cls, "model_fields")
|
|
39
|
+
|
|
40
|
+
def test_get_json_schema(self):
|
|
41
|
+
mt = MetaType.objects.create(
|
|
42
|
+
name="JSON Schema",
|
|
43
|
+
schema=[{"name": "name", "kind": "string", "required": True}],
|
|
44
|
+
)
|
|
45
|
+
schema = mt.get_json_schema()
|
|
46
|
+
assert "properties" in schema
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@pytest.mark.django_db
|
|
50
|
+
class TestMetaInstance:
|
|
51
|
+
def _make_type(self):
|
|
52
|
+
return MetaType.objects.create(
|
|
53
|
+
name="Inst Type",
|
|
54
|
+
schema=[
|
|
55
|
+
{"name": "title", "kind": "string", "required": True},
|
|
56
|
+
{"name": "count", "kind": "number", "integer": True},
|
|
57
|
+
],
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def test_create(self):
|
|
61
|
+
mt = self._make_type()
|
|
62
|
+
mi = MetaInstance.objects.create(
|
|
63
|
+
meta_type=mt,
|
|
64
|
+
data={"title": "Hello", "count": 5},
|
|
65
|
+
)
|
|
66
|
+
assert mi.pk is not None
|
|
67
|
+
|
|
68
|
+
def test_str(self):
|
|
69
|
+
mt = self._make_type()
|
|
70
|
+
mi = MetaInstance.objects.create(
|
|
71
|
+
meta_type=mt, data={"title": "X"},
|
|
72
|
+
)
|
|
73
|
+
assert str(mi) == f"Inst Type #{mi.pk}"
|
|
74
|
+
|
|
75
|
+
def test_obj_property(self):
|
|
76
|
+
mt = self._make_type()
|
|
77
|
+
mi = MetaInstance.objects.create(
|
|
78
|
+
meta_type=mt,
|
|
79
|
+
data={"title": "World", "count": 10},
|
|
80
|
+
)
|
|
81
|
+
obj = mi.obj
|
|
82
|
+
assert obj.title == "World"
|
|
83
|
+
assert obj.count == 10
|
|
84
|
+
|
|
85
|
+
def test_obj_caching(self):
|
|
86
|
+
mt = self._make_type()
|
|
87
|
+
mi = MetaInstance.objects.create(
|
|
88
|
+
meta_type=mt,
|
|
89
|
+
data={"title": "Cached"},
|
|
90
|
+
)
|
|
91
|
+
obj1 = mi.obj
|
|
92
|
+
obj2 = mi.obj
|
|
93
|
+
assert obj1 is obj2
|
|
94
|
+
|
|
95
|
+
def test_obj_none_without_meta_type(self):
|
|
96
|
+
mi = MetaInstance()
|
|
97
|
+
assert mi.obj is None
|
|
98
|
+
|
|
99
|
+
def test_clean_valid_data(self):
|
|
100
|
+
mt = self._make_type()
|
|
101
|
+
mi = MetaInstance(meta_type=mt, data={"title": "Valid"})
|
|
102
|
+
mi.clean()
|
|
103
|
+
assert mi.data["title"] == "Valid"
|
|
104
|
+
|
|
105
|
+
def test_clean_invalid_data_raises(self):
|
|
106
|
+
mt = self._make_type()
|
|
107
|
+
mi = MetaInstance(meta_type=mt, data={"title": 123})
|
|
108
|
+
with pytest.raises(ValidationError):
|
|
109
|
+
mi.clean()
|
|
110
|
+
|
|
111
|
+
def test_clean_missing_required_raises(self):
|
|
112
|
+
mt = self._make_type()
|
|
113
|
+
mi = MetaInstance(meta_type=mt, data={})
|
|
114
|
+
with pytest.raises(ValidationError):
|
|
115
|
+
mi.clean()
|
|
116
|
+
|
|
117
|
+
def test_clean_normalizes_data(self):
|
|
118
|
+
mt = self._make_type()
|
|
119
|
+
mi = MetaInstance(meta_type=mt, data={"title": "Test", "count": "5"})
|
|
120
|
+
mi.clean()
|
|
121
|
+
assert mi.data["count"] == 5
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@pytest.mark.django_db
|
|
125
|
+
class TestMetaInstanceCacheEngine:
|
|
126
|
+
"""Verify that accessing ref/queryset fields on MetaInstance.obj uses
|
|
127
|
+
the structured-field CacheEngine, batch-fetching related objects
|
|
128
|
+
instead of issuing per-field queries."""
|
|
129
|
+
|
|
130
|
+
@pytest.fixture()
|
|
131
|
+
def pages(self):
|
|
132
|
+
return [
|
|
133
|
+
Page.objects.create(title="Page A", slug="page-a"),
|
|
134
|
+
Page.objects.create(title="Page B", slug="page-b"),
|
|
135
|
+
Page.objects.create(title="Page C", slug="page-c"),
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
def test_fk_field_single_query(self, django_assert_num_queries, pages):
|
|
139
|
+
mt = MetaType.objects.create(
|
|
140
|
+
name="FK Test",
|
|
141
|
+
schema=[
|
|
142
|
+
{"name": "page", "kind": "ref", "target_model": "test_module.Page"},
|
|
143
|
+
],
|
|
144
|
+
)
|
|
145
|
+
mi = MetaInstance.objects.create(
|
|
146
|
+
meta_type=mt,
|
|
147
|
+
data={"page": pages[0].pk},
|
|
148
|
+
)
|
|
149
|
+
# Building obj should batch-fetch the FK in a single query
|
|
150
|
+
# (1 query for the cache engine to fetch all referenced Page PKs)
|
|
151
|
+
with django_assert_num_queries(1):
|
|
152
|
+
obj = mi.obj
|
|
153
|
+
assert obj.page.pk == pages[0].pk
|
|
154
|
+
assert obj.page.title == "Page A"
|
|
155
|
+
|
|
156
|
+
def test_queryset_field_single_query(self, django_assert_num_queries, pages):
|
|
157
|
+
mt = MetaType.objects.create(
|
|
158
|
+
name="QS Test",
|
|
159
|
+
schema=[
|
|
160
|
+
{"name": "pages", "kind": "queryset", "target_model": "test_module.Page"},
|
|
161
|
+
],
|
|
162
|
+
)
|
|
163
|
+
pks = [p.pk for p in pages]
|
|
164
|
+
mi = MetaInstance.objects.create(
|
|
165
|
+
meta_type=mt,
|
|
166
|
+
data={"pages": pks},
|
|
167
|
+
)
|
|
168
|
+
# Building obj should batch-fetch all QS PKs in a single query
|
|
169
|
+
with django_assert_num_queries(1):
|
|
170
|
+
obj = mi.obj
|
|
171
|
+
result_pks = [p.pk for p in obj.pages]
|
|
172
|
+
assert set(result_pks) == set(pks)
|
|
173
|
+
|
|
174
|
+
def test_mixed_fk_and_qs_fields(self, django_assert_num_queries, pages):
|
|
175
|
+
mt = MetaType.objects.create(
|
|
176
|
+
name="Mixed Test",
|
|
177
|
+
schema=[
|
|
178
|
+
{"name": "main_page", "kind": "ref", "target_model": "test_module.Page"},
|
|
179
|
+
{"name": "related_pages", "kind": "queryset", "target_model": "test_module.Page"},
|
|
180
|
+
],
|
|
181
|
+
)
|
|
182
|
+
mi = MetaInstance.objects.create(
|
|
183
|
+
meta_type=mt,
|
|
184
|
+
data={
|
|
185
|
+
"main_page": pages[0].pk,
|
|
186
|
+
"related_pages": [p.pk for p in pages[1:]],
|
|
187
|
+
},
|
|
188
|
+
)
|
|
189
|
+
# Both FK and QS point to the same model so the cache engine
|
|
190
|
+
# should resolve everything in a single batched query
|
|
191
|
+
with django_assert_num_queries(1):
|
|
192
|
+
obj = mi.obj
|
|
193
|
+
assert obj.main_page.pk == pages[0].pk
|
|
194
|
+
qs_pks = [p.pk for p in obj.related_pages]
|
|
195
|
+
assert set(qs_pks) == {pages[1].pk, pages[2].pk}
|
|
196
|
+
|
|
197
|
+
def test_obj_cache_no_extra_queries(self, django_assert_num_queries, pages):
|
|
198
|
+
mt = MetaType.objects.create(
|
|
199
|
+
name="Cache Hit",
|
|
200
|
+
schema=[
|
|
201
|
+
{"name": "page", "kind": "ref", "target_model": "test_module.Page"},
|
|
202
|
+
],
|
|
203
|
+
)
|
|
204
|
+
mi = MetaInstance.objects.create(
|
|
205
|
+
meta_type=mt,
|
|
206
|
+
data={"page": pages[0].pk},
|
|
207
|
+
)
|
|
208
|
+
# First access builds the cache
|
|
209
|
+
_ = mi.obj
|
|
210
|
+
# Second access should hit the instance cache — zero queries
|
|
211
|
+
with django_assert_num_queries(0):
|
|
212
|
+
obj = mi.obj
|
|
213
|
+
assert obj.page.pk == pages[0].pk
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from structured_metaobjects.schema_builder import MetaFieldKind, MetaTypeFieldDef
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TestMetaFieldKind:
|
|
6
|
+
def test_all_kinds_present(self):
|
|
7
|
+
expected = {
|
|
8
|
+
"string", "html", "number", "boolean",
|
|
9
|
+
"date", "datetime", "select", "ref", "queryset", "group", "list",
|
|
10
|
+
}
|
|
11
|
+
assert {k.value for k in MetaFieldKind} == expected
|
|
12
|
+
|
|
13
|
+
def test_kind_is_str_enum(self):
|
|
14
|
+
assert isinstance(MetaFieldKind.string, str)
|
|
15
|
+
assert MetaFieldKind.string == "string"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestMetaTypeFieldDef:
|
|
19
|
+
def test_minimal_field(self):
|
|
20
|
+
fd = MetaTypeFieldDef(name="title")
|
|
21
|
+
assert fd.name == "title"
|
|
22
|
+
assert fd.kind == MetaFieldKind.string
|
|
23
|
+
assert fd.required is False
|
|
24
|
+
assert fd.translated is False
|
|
25
|
+
assert fd.target_model is None
|
|
26
|
+
assert fd.children == []
|
|
27
|
+
|
|
28
|
+
def test_ref_field(self):
|
|
29
|
+
fd = MetaTypeFieldDef(
|
|
30
|
+
name="page",
|
|
31
|
+
kind=MetaFieldKind.ref,
|
|
32
|
+
target_model="test_module.Page",
|
|
33
|
+
)
|
|
34
|
+
assert fd.kind == MetaFieldKind.ref
|
|
35
|
+
assert fd.target_model == "test_module.Page"
|
|
36
|
+
|
|
37
|
+
def test_group_with_children(self):
|
|
38
|
+
child = MetaTypeFieldDef(name="street", kind=MetaFieldKind.string)
|
|
39
|
+
fd = MetaTypeFieldDef(
|
|
40
|
+
name="address",
|
|
41
|
+
kind=MetaFieldKind.group,
|
|
42
|
+
children=[child],
|
|
43
|
+
)
|
|
44
|
+
assert len(fd.children) == 1
|
|
45
|
+
assert fd.children[0].name == "street"
|
|
46
|
+
|
|
47
|
+
def test_json_schema_has_conditional_logic(self):
|
|
48
|
+
schema = MetaTypeFieldDef.model_json_schema()
|
|
49
|
+
assert "if" in schema or "allOf" in schema or "$defs" in schema
|
|
50
|
+
|
|
51
|
+
def test_round_trip_dict(self):
|
|
52
|
+
data = {
|
|
53
|
+
"name": "bio",
|
|
54
|
+
"label": "Biography",
|
|
55
|
+
"kind": "string",
|
|
56
|
+
"multiline": True,
|
|
57
|
+
"required": True,
|
|
58
|
+
"translated": True,
|
|
59
|
+
}
|
|
60
|
+
fd = MetaTypeFieldDef.model_validate(data)
|
|
61
|
+
dumped = fd.model_dump(mode="json")
|
|
62
|
+
assert dumped["name"] == "bio"
|
|
63
|
+
assert dumped["kind"] == "string"
|
|
64
|
+
assert dumped["translated"] is True
|
|
65
|
+
|
|
66
|
+
def test_recursive_children(self):
|
|
67
|
+
data = {
|
|
68
|
+
"name": "section",
|
|
69
|
+
"kind": "group",
|
|
70
|
+
"children": [
|
|
71
|
+
{
|
|
72
|
+
"name": "subsection",
|
|
73
|
+
"kind": "group",
|
|
74
|
+
"children": [
|
|
75
|
+
{"name": "title", "kind": "string"},
|
|
76
|
+
],
|
|
77
|
+
}
|
|
78
|
+
],
|
|
79
|
+
}
|
|
80
|
+
fd = MetaTypeFieldDef.model_validate(data)
|
|
81
|
+
assert fd.children[0].children[0].name == "title"
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from structured_metaobjects.models import MetaInstance, MetaType
|
|
4
|
+
from structured_metaobjects.serializers import (
|
|
5
|
+
MetaInstanceSerializer,
|
|
6
|
+
MetaTypeSerializer,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.mark.django_db
|
|
11
|
+
class TestMetaTypeSerializer:
|
|
12
|
+
def test_serialize(self):
|
|
13
|
+
mt = MetaType.objects.create(
|
|
14
|
+
name="Ser",
|
|
15
|
+
schema=[{"name": "title", "kind": "string"}],
|
|
16
|
+
)
|
|
17
|
+
data = MetaTypeSerializer(mt).data
|
|
18
|
+
assert data["name"] == "Ser"
|
|
19
|
+
assert isinstance(data["schema"], list)
|
|
20
|
+
|
|
21
|
+
def test_deserialize(self):
|
|
22
|
+
payload = {
|
|
23
|
+
"name": "New Type",
|
|
24
|
+
"schema": [{"name": "body", "kind": "string", "multiline": True}],
|
|
25
|
+
}
|
|
26
|
+
s = MetaTypeSerializer(data=payload)
|
|
27
|
+
assert s.is_valid(), s.errors
|
|
28
|
+
mt = s.save()
|
|
29
|
+
assert mt.pk is not None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@pytest.mark.django_db
|
|
33
|
+
class TestMetaInstanceSerializer:
|
|
34
|
+
def _make_type(self):
|
|
35
|
+
return MetaType.objects.create(
|
|
36
|
+
name="MI Ser",
|
|
37
|
+
schema=[
|
|
38
|
+
{"name": "title", "kind": "string", "required": True},
|
|
39
|
+
],
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def test_valid_create(self):
|
|
43
|
+
mt = self._make_type()
|
|
44
|
+
payload = {
|
|
45
|
+
"meta_type": mt.pk,
|
|
46
|
+
"data": {"title": "Hello"},
|
|
47
|
+
}
|
|
48
|
+
s = MetaInstanceSerializer(data=payload)
|
|
49
|
+
assert s.is_valid(), s.errors
|
|
50
|
+
mi = s.save()
|
|
51
|
+
assert mi.data["title"] == "Hello"
|
|
52
|
+
|
|
53
|
+
def test_invalid_data_rejected(self):
|
|
54
|
+
mt = self._make_type()
|
|
55
|
+
payload = {
|
|
56
|
+
"meta_type": mt.pk,
|
|
57
|
+
"data": {},
|
|
58
|
+
}
|
|
59
|
+
s = MetaInstanceSerializer(data=payload)
|
|
60
|
+
assert not s.is_valid()
|
|
61
|
+
assert "data" in s.errors
|
|
62
|
+
|
|
63
|
+
def test_missing_meta_type_rejected(self):
|
|
64
|
+
payload = {
|
|
65
|
+
"data": {"title": "No type"},
|
|
66
|
+
}
|
|
67
|
+
s = MetaInstanceSerializer(data=payload)
|
|
68
|
+
assert not s.is_valid()
|
|
69
|
+
|
|
70
|
+
def test_update_existing(self):
|
|
71
|
+
mt = self._make_type()
|
|
72
|
+
mi = MetaInstance.objects.create(
|
|
73
|
+
meta_type=mt,
|
|
74
|
+
data={"title": "Old"},
|
|
75
|
+
)
|
|
76
|
+
s = MetaInstanceSerializer(mi, data={"data": {"title": "New"}}, partial=True)
|
|
77
|
+
assert s.is_valid(), s.errors
|
|
78
|
+
updated = s.save()
|
|
79
|
+
assert updated.data["title"] == "New"
|
tests/test_views.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from rest_framework.test import APIClient
|
|
3
|
+
|
|
4
|
+
from structured_metaobjects.models import MetaInstance, MetaType
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.fixture
|
|
8
|
+
def api_client():
|
|
9
|
+
return APIClient()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.mark.django_db
|
|
13
|
+
class TestMetaTypeViewSet:
|
|
14
|
+
def _url(self, pk=None):
|
|
15
|
+
if pk:
|
|
16
|
+
return f"/api/meta-types/{pk}/"
|
|
17
|
+
return "/api/meta-types/"
|
|
18
|
+
|
|
19
|
+
def test_list(self, api_client):
|
|
20
|
+
MetaType.objects.create(name="V1", schema=[])
|
|
21
|
+
resp = api_client.get(self._url())
|
|
22
|
+
assert resp.status_code == 200
|
|
23
|
+
assert len(resp.data) >= 1
|
|
24
|
+
|
|
25
|
+
def test_create(self, api_client):
|
|
26
|
+
resp = api_client.post(
|
|
27
|
+
self._url(),
|
|
28
|
+
{"name": "V2", "schema": [{"name": "x", "kind": "string"}]},
|
|
29
|
+
format="json",
|
|
30
|
+
)
|
|
31
|
+
assert resp.status_code == 201
|
|
32
|
+
assert resp.data["name"] == "V2"
|
|
33
|
+
|
|
34
|
+
def test_retrieve(self, api_client):
|
|
35
|
+
mt = MetaType.objects.create(name="V3", schema=[])
|
|
36
|
+
resp = api_client.get(self._url(mt.pk))
|
|
37
|
+
assert resp.status_code == 200
|
|
38
|
+
assert resp.data["name"] == "V3"
|
|
39
|
+
|
|
40
|
+
def test_update(self, api_client):
|
|
41
|
+
mt = MetaType.objects.create(name="V4", schema=[])
|
|
42
|
+
resp = api_client.patch(
|
|
43
|
+
self._url(mt.pk),
|
|
44
|
+
{"name": "V4 Updated"},
|
|
45
|
+
format="json",
|
|
46
|
+
)
|
|
47
|
+
assert resp.status_code == 200
|
|
48
|
+
assert resp.data["name"] == "V4 Updated"
|
|
49
|
+
|
|
50
|
+
def test_delete(self, api_client):
|
|
51
|
+
mt = MetaType.objects.create(name="V5", schema=[])
|
|
52
|
+
resp = api_client.delete(self._url(mt.pk))
|
|
53
|
+
assert resp.status_code == 204
|
|
54
|
+
|
|
55
|
+
def test_schema_action(self, api_client):
|
|
56
|
+
mt = MetaType.objects.create(
|
|
57
|
+
name="V6",
|
|
58
|
+
schema=[{"name": "title", "kind": "string", "required": True}],
|
|
59
|
+
)
|
|
60
|
+
resp = api_client.get(f"/api/meta-types/{mt.pk}/schema/")
|
|
61
|
+
assert resp.status_code == 200
|
|
62
|
+
assert "properties" in resp.data
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@pytest.mark.django_db
|
|
66
|
+
class TestMetaInstanceViewSet:
|
|
67
|
+
def _url(self, pk=None):
|
|
68
|
+
if pk:
|
|
69
|
+
return f"/api/meta-instances/{pk}/"
|
|
70
|
+
return "/api/meta-instances/"
|
|
71
|
+
|
|
72
|
+
def _make_type(self):
|
|
73
|
+
return MetaType.objects.create(
|
|
74
|
+
name="VI",
|
|
75
|
+
schema=[{"name": "title", "kind": "string", "required": True}],
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def test_list(self, api_client):
|
|
79
|
+
mt = self._make_type()
|
|
80
|
+
MetaInstance.objects.create(meta_type=mt, data={"title": "A"})
|
|
81
|
+
resp = api_client.get(self._url())
|
|
82
|
+
assert resp.status_code == 200
|
|
83
|
+
|
|
84
|
+
def test_create(self, api_client):
|
|
85
|
+
mt = self._make_type()
|
|
86
|
+
resp = api_client.post(
|
|
87
|
+
self._url(),
|
|
88
|
+
{"meta_type": mt.pk, "data": {"title": "New"}},
|
|
89
|
+
format="json",
|
|
90
|
+
)
|
|
91
|
+
assert resp.status_code == 201
|
|
92
|
+
|
|
93
|
+
def test_create_invalid_data(self, api_client):
|
|
94
|
+
mt = self._make_type()
|
|
95
|
+
resp = api_client.post(
|
|
96
|
+
self._url(),
|
|
97
|
+
{"meta_type": mt.pk, "data": {}},
|
|
98
|
+
format="json",
|
|
99
|
+
)
|
|
100
|
+
assert resp.status_code == 400
|
|
101
|
+
|
|
102
|
+
def test_retrieve(self, api_client):
|
|
103
|
+
mt = self._make_type()
|
|
104
|
+
mi = MetaInstance.objects.create(meta_type=mt, data={"title": "R"})
|
|
105
|
+
resp = api_client.get(self._url(mi.pk))
|
|
106
|
+
assert resp.status_code == 200
|
|
107
|
+
|
|
108
|
+
def test_schema_action(self, api_client):
|
|
109
|
+
mt = self._make_type()
|
|
110
|
+
resp = api_client.get(f"/api/meta-instances/schema/?meta_type={mt.pk}")
|
|
111
|
+
assert resp.status_code == 200
|
|
112
|
+
assert "properties" in resp.data
|
|
113
|
+
|
|
114
|
+
def test_schema_action_missing_param(self, api_client):
|
|
115
|
+
resp = api_client.get("/api/meta-instances/schema/")
|
|
116
|
+
assert resp.status_code == 400
|
|
117
|
+
|
|
118
|
+
def test_schema_action_not_found(self, api_client):
|
|
119
|
+
resp = api_client.get("/api/meta-instances/schema/?meta_type=99999")
|
|
120
|
+
assert resp.status_code == 404
|