django-content-studio 1.0.0b8__py3-none-any.whl → 1.0.0b10__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.
- content_studio/__init__.py +1 -1
- content_studio/admin.py +29 -1
- content_studio/apps.py +12 -2
- content_studio/dashboard/__init__.py +34 -5
- content_studio/dashboard/activity_log.py +6 -2
- content_studio/dashboard/statistic.py +27 -0
- content_studio/models.py +4 -1
- content_studio/settings.py +4 -0
- content_studio/static/content_studio/assets/index.css +1 -1
- content_studio/static/content_studio/assets/index.js +90 -90
- content_studio/static/content_studio/index.html +22 -0
- content_studio/static/content_studio/locales/nl/translation.json +10 -0
- content_studio/templates/content_studio/index.html +2 -2
- content_studio/utils.py +27 -0
- content_studio/views.py +43 -11
- content_studio/viewsets.py +9 -1
- content_studio/widgets.py +4 -0
- {django_content_studio-1.0.0b8.dist-info → django_content_studio-1.0.0b10.dist-info}/METADATA +1 -1
- {django_content_studio-1.0.0b8.dist-info → django_content_studio-1.0.0b10.dist-info}/RECORD +21 -19
- {django_content_studio-1.0.0b8.dist-info → django_content_studio-1.0.0b10.dist-info}/LICENSE +0 -0
- {django_content_studio-1.0.0b8.dist-info → django_content_studio-1.0.0b10.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Django Content Studio</title>
|
|
8
|
+
<script>
|
|
9
|
+
window.DCS_STATIC_PREFIX = "/";
|
|
10
|
+
window.DCS_BASENAME = "http://localhost:8000/admin";
|
|
11
|
+
</script>
|
|
12
|
+
<link rel="stylesheet" href="/icons/pi/style.css" />
|
|
13
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
14
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
15
|
+
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,100..700;1,100..700&display=swap" rel="stylesheet">
|
|
16
|
+
<script type="module" crossorigin src="/assets/index.js"></script>
|
|
17
|
+
<link rel="stylesheet" crossorigin href="/assets/index.css">
|
|
18
|
+
</head>
|
|
19
|
+
<body>
|
|
20
|
+
<div id="root"></div>
|
|
21
|
+
</body>
|
|
22
|
+
</html>
|
|
@@ -89,5 +89,15 @@
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
|
+
},
|
|
93
|
+
"tenant": {
|
|
94
|
+
"selector": {
|
|
95
|
+
"add": "Voeg nieuwe toe"
|
|
96
|
+
},
|
|
97
|
+
"setup": {
|
|
98
|
+
"title": "Eerste {{tenant}} aanmaken",
|
|
99
|
+
"model_not_found": "Geen admin model gevonden voor {{tenant}}. Voeg een admin model toe of maak handmatig een {{tenant}} aan."
|
|
100
|
+
},
|
|
101
|
+
"shared_message": "{{content_type}} worden gedeeld tussen alle {{tenant}}."
|
|
92
102
|
}
|
|
93
103
|
}
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
<link rel="stylesheet" crossorigin href="{% static 'content_studio/icons/pi/style.css' %}">
|
|
16
16
|
|
|
17
17
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
19
|
+
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,100..700;1,100..700&display=swap" rel="stylesheet">
|
|
20
20
|
</head>
|
|
21
21
|
<body>
|
|
22
22
|
<div id="root"></div>
|
content_studio/utils.py
CHANGED
|
@@ -3,6 +3,8 @@ import sys
|
|
|
3
3
|
|
|
4
4
|
from rich.console import Console
|
|
5
5
|
|
|
6
|
+
from content_studio.settings import cs_settings
|
|
7
|
+
|
|
6
8
|
console = Console()
|
|
7
9
|
|
|
8
10
|
|
|
@@ -60,3 +62,28 @@ def get_related_field_name(inline, parent_model):
|
|
|
60
62
|
)
|
|
61
63
|
else:
|
|
62
64
|
raise ValueError(f"Multiple foreign keys found. Specify fk_name on the inline.")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def get_tenant_field_name(model):
|
|
68
|
+
tenant_model = cs_settings.TENANT_MODEL
|
|
69
|
+
|
|
70
|
+
if not tenant_model:
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
opts = model._meta
|
|
74
|
+
|
|
75
|
+
# Find all foreign keys pointing to the tenant model
|
|
76
|
+
fks = [
|
|
77
|
+
f
|
|
78
|
+
for f in opts.get_fields()
|
|
79
|
+
if f.many_to_one and f.remote_field.model == tenant_model
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
if len(fks) == 1:
|
|
83
|
+
return fks[0].name
|
|
84
|
+
elif len(fks) == 0:
|
|
85
|
+
return None
|
|
86
|
+
else:
|
|
87
|
+
raise ValueError(
|
|
88
|
+
f"Multiple fields found pointing to {tenant_model}. Only one field can point to a tenant model."
|
|
89
|
+
)
|
content_studio/views.py
CHANGED
|
@@ -3,7 +3,9 @@ from django.contrib import admin
|
|
|
3
3
|
from django.urls import reverse, NoReverseMatch
|
|
4
4
|
from django.utils.translation import gettext_lazy as _
|
|
5
5
|
from django.views.generic import TemplateView
|
|
6
|
+
from rest_framework import serializers
|
|
6
7
|
from rest_framework.decorators import action
|
|
8
|
+
from rest_framework.exceptions import MethodNotAllowed
|
|
7
9
|
from rest_framework.permissions import IsAdminUser, AllowAny
|
|
8
10
|
from rest_framework.renderers import JSONRenderer
|
|
9
11
|
from rest_framework.response import Response
|
|
@@ -31,6 +33,7 @@ class AdminApiViewSet(ViewSet):
|
|
|
31
33
|
|
|
32
34
|
permission_classes = [IsAdminUser]
|
|
33
35
|
renderer_classes = [JSONRenderer]
|
|
36
|
+
admin_site = cs_settings.ADMIN_SITE
|
|
34
37
|
|
|
35
38
|
@action(
|
|
36
39
|
methods=["get"],
|
|
@@ -42,23 +45,22 @@ class AdminApiViewSet(ViewSet):
|
|
|
42
45
|
"""
|
|
43
46
|
Returns public information about the Content Studio admin.
|
|
44
47
|
"""
|
|
45
|
-
admin_site = cs_settings.ADMIN_SITE
|
|
46
48
|
|
|
47
49
|
data = {
|
|
48
50
|
"version": __version__,
|
|
49
|
-
"site_header": admin_site.site_header,
|
|
50
|
-
"site_title": admin_site.site_title,
|
|
51
|
-
"index_title": admin_site.index_title,
|
|
52
|
-
"site_url": admin_site.site_url,
|
|
51
|
+
"site_header": self.admin_site.site_header,
|
|
52
|
+
"site_title": self.admin_site.site_title,
|
|
53
|
+
"index_title": self.admin_site.index_title,
|
|
54
|
+
"site_url": self.admin_site.site_url,
|
|
53
55
|
"health_check": get_health_check_path(),
|
|
54
56
|
"login_backends": [
|
|
55
57
|
backend.get_info()
|
|
56
|
-
for backend in admin_site.login_backend.active_backends
|
|
58
|
+
for backend in self.admin_site.login_backend.active_backends
|
|
57
59
|
],
|
|
58
|
-
"token_backend": admin_site.token_backend.active_backend.get_info(),
|
|
60
|
+
"token_backend": self.admin_site.token_backend.active_backend.get_info(),
|
|
59
61
|
"formats": {
|
|
60
62
|
model_class.__name__: frmt.serialize()
|
|
61
|
-
for model_class, frmt in admin_site.default_format_mapping.items()
|
|
63
|
+
for model_class, frmt in self.admin_site.default_format_mapping.items()
|
|
62
64
|
},
|
|
63
65
|
"widgets": get_widgets(),
|
|
64
66
|
"settings": {
|
|
@@ -83,7 +85,6 @@ class AdminApiViewSet(ViewSet):
|
|
|
83
85
|
"""
|
|
84
86
|
Returns information about the Django app (models, admin models, admin site, settings, etc.).
|
|
85
87
|
"""
|
|
86
|
-
admin_site = cs_settings.ADMIN_SITE
|
|
87
88
|
data = {
|
|
88
89
|
"models": get_models(request),
|
|
89
90
|
"model_groups": get_model_groups(),
|
|
@@ -102,11 +103,20 @@ class AdminApiViewSet(ViewSet):
|
|
|
102
103
|
},
|
|
103
104
|
}
|
|
104
105
|
|
|
105
|
-
if admin_site.dashboard:
|
|
106
|
-
data["dashboard"] = admin_site.dashboard.serialize()
|
|
106
|
+
if self.admin_site.dashboard:
|
|
107
|
+
data["dashboard"] = self.admin_site.dashboard.serialize()
|
|
107
108
|
else:
|
|
108
109
|
data["dashboard"] = {"widgets": []}
|
|
109
110
|
|
|
111
|
+
multitenancy = cs_settings.TENANT_MODEL is not None
|
|
112
|
+
|
|
113
|
+
data["multitenancy"] = {
|
|
114
|
+
"enabled": multitenancy,
|
|
115
|
+
"tenant_model": (
|
|
116
|
+
cs_settings.TENANT_MODEL._meta.label_lower if multitenancy else None
|
|
117
|
+
),
|
|
118
|
+
}
|
|
119
|
+
|
|
110
120
|
return Response(data=data)
|
|
111
121
|
|
|
112
122
|
@action(methods=["get"], detail=False, url_path="me")
|
|
@@ -116,6 +126,28 @@ class AdminApiViewSet(ViewSet):
|
|
|
116
126
|
"""
|
|
117
127
|
return Response(SessionUserSerializer(request.user).data)
|
|
118
128
|
|
|
129
|
+
@action(
|
|
130
|
+
methods=["get"],
|
|
131
|
+
detail=False,
|
|
132
|
+
url_path="tenants",
|
|
133
|
+
)
|
|
134
|
+
def list_tenants(self, request):
|
|
135
|
+
tenant_model = cs_settings.TENANT_MODEL
|
|
136
|
+
|
|
137
|
+
if not tenant_model:
|
|
138
|
+
raise MethodNotAllowed("GET", "Tenant model not defined.")
|
|
139
|
+
|
|
140
|
+
class TenantSerializer(serializers.ModelSerializer):
|
|
141
|
+
class Meta:
|
|
142
|
+
model = tenant_model
|
|
143
|
+
fields = ["id", "__str__"]
|
|
144
|
+
|
|
145
|
+
tenants = self.admin_site.get_tenants(
|
|
146
|
+
tenant_model=tenant_model, request=request
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
return Response(TenantSerializer(tenants, many=True).data)
|
|
150
|
+
|
|
119
151
|
|
|
120
152
|
def get_models(request):
|
|
121
153
|
models = []
|
content_studio/viewsets.py
CHANGED
|
@@ -18,6 +18,7 @@ from rest_framework.viewsets import ModelViewSet
|
|
|
18
18
|
from .filters import LookupFilter
|
|
19
19
|
from .serializers import RelatedItemSerializer
|
|
20
20
|
from .settings import cs_settings
|
|
21
|
+
from .utils import get_tenant_field_name
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
class BaseModelViewSet(ModelViewSet):
|
|
@@ -48,10 +49,15 @@ class BaseModelViewSet(ModelViewSet):
|
|
|
48
49
|
|
|
49
50
|
def perform_create(self, serializer):
|
|
50
51
|
instance = serializer.save()
|
|
52
|
+
tenant_id = self.request.headers.get("x-dcs-tenant", None)
|
|
53
|
+
tenant_model = cs_settings.TENANT_MODEL
|
|
54
|
+
tenant_field_name = get_tenant_field_name(instance)
|
|
55
|
+
|
|
56
|
+
if tenant_model and tenant_id and tenant_field_name:
|
|
57
|
+
setattr(instance, f"{tenant_field_name}_id", tenant_id)
|
|
51
58
|
|
|
52
59
|
if hasattr(instance, cs_settings.CREATED_BY_ATTR):
|
|
53
60
|
setattr(instance, cs_settings.CREATED_BY_ATTR, self.request.user)
|
|
54
|
-
instance.save()
|
|
55
61
|
|
|
56
62
|
content_type = ContentType.objects.get_for_model(instance)
|
|
57
63
|
LogEntry.objects.create(
|
|
@@ -63,6 +69,8 @@ class BaseModelViewSet(ModelViewSet):
|
|
|
63
69
|
change_message="",
|
|
64
70
|
)
|
|
65
71
|
|
|
72
|
+
instance.save()
|
|
73
|
+
|
|
66
74
|
def perform_update(self, serializer):
|
|
67
75
|
instance = serializer.save()
|
|
68
76
|
|
content_studio/widgets.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
content_studio/__init__.py,sha256=
|
|
2
|
-
content_studio/admin.py,sha256=
|
|
3
|
-
content_studio/apps.py,sha256=
|
|
4
|
-
content_studio/dashboard/__init__.py,sha256=
|
|
5
|
-
content_studio/dashboard/activity_log.py,sha256=
|
|
1
|
+
content_studio/__init__.py,sha256=EhSgseurRaoQIflicvxGVAbsaOcj-0ip6wNs6lTzi54,162
|
|
2
|
+
content_studio/admin.py,sha256=bsGQvHndTHmlFUgsBgWiDk9YOEz5i7duHwJk73CSdyk,11180
|
|
3
|
+
content_studio/apps.py,sha256=iGZYxsGerYYC8EmV9Wu9phuT9R8UHUkIQ6XAm94EUgA,3961
|
|
4
|
+
content_studio/dashboard/__init__.py,sha256=G0fsYbegeZScWRb3vbCwd0sEm8fVc-tT_r-zCSPktgw,2377
|
|
5
|
+
content_studio/dashboard/activity_log.py,sha256=0hK96lXziD663a6YuKO7zRTVcVmbRitERqfNGc2NrtE,939
|
|
6
|
+
content_studio/dashboard/statistic.py,sha256=aoHJMM88zehapYWOmq_lqdSArkagYLCRWEAxXoNMcRg,912
|
|
6
7
|
content_studio/filters.py,sha256=GyglR2E_wswomW7EnfShEXr14zxuRlLAuxrHVO3QQYg,4335
|
|
7
8
|
content_studio/form.py,sha256=XvUbBVR3QlvyM1l73QWV5TnreQTM8cWNJqgxkACj9dQ,5041
|
|
8
9
|
content_studio/formats.py,sha256=JzOWrDRCVSnjJI0oX5fyFQU8LC634_jeAe84Widy43U,786
|
|
@@ -10,32 +11,33 @@ content_studio/login_backends/__init__.py,sha256=hgPJh9hFobubImfhynaeZ_dku3sjqNQ
|
|
|
10
11
|
content_studio/login_backends/username_password.py,sha256=JJbF3oWnhOwLs-WaqclCCH6nAnrhlKbyBUzAJKVtq7A,2561
|
|
11
12
|
content_studio/media_library/serializers.py,sha256=1MPFqbZdE0iTYqZrzzYoT_y4q0ItGTbQ-I7omyojAZo,660
|
|
12
13
|
content_studio/media_library/viewsets.py,sha256=XsQuCrcfZ52xvvsEbHZ2WTI99wf-cheOKfaMJyfAJbo,4318
|
|
13
|
-
content_studio/models.py,sha256=
|
|
14
|
+
content_studio/models.py,sha256=9F9C-K7KkVT6i5TZGm0SEPyRxaIMlVblrZwzGET199E,2191
|
|
14
15
|
content_studio/paginators.py,sha256=XrA6ECP2pO75SSj0Mi0Jqj7n_YPuIin-V8_6EOaQj64,589
|
|
15
16
|
content_studio/router.py,sha256=7Up_sipGaUDoY6ElJNRf85ADaYfJCWV4To523L4LGuw,393
|
|
16
17
|
content_studio/serializers.py,sha256=tWgL7J2z-Qc5BQjMc3PXpvLZa_6J6MfVUqRDOK_X0Xs,3867
|
|
17
|
-
content_studio/settings.py,sha256=
|
|
18
|
+
content_studio/settings.py,sha256=uu5Pnb502ZQE328J9-3Q4UtLGvD6tTAOOgv652dVUig,4598
|
|
18
19
|
content_studio/static/content_studio/assets/browser-ponyfill-TyWUZ1Oq.js,sha256=sKLpD8vGwLfnJVLaSGMMi8krArcm2Qj3-igVwDvLMek,10287
|
|
19
|
-
content_studio/static/content_studio/assets/index.css,sha256=
|
|
20
|
-
content_studio/static/content_studio/assets/index.js,sha256=
|
|
20
|
+
content_studio/static/content_studio/assets/index.css,sha256=5ppY6CCbxK-0cxchbw3j6JVwZgSKUPorB_8ynR87OTY,86894
|
|
21
|
+
content_studio/static/content_studio/assets/index.js,sha256=W48he-x3ApBAspmg_38FYGRQPHL7Td4oSJ-S2BUqzsE,1419730
|
|
21
22
|
content_studio/static/content_studio/icons/pi/Phosphor-Bold.svg,sha256=4kdzmyaGcNVhK6qRIA8PdhaZ7raHU0xoU26ht52VG_c,2967217
|
|
22
23
|
content_studio/static/content_studio/icons/pi/Phosphor-Bold.ttf,sha256=EKChy0-BVqQg-fhM80xOmHHljtLd6h9qgHmtByQ6f7I,495308
|
|
23
24
|
content_studio/static/content_studio/icons/pi/Phosphor-Bold.woff,sha256=3k3MnRjPMzY8GdLJJMnZ1R4hCXi9Pm7FCK7HFnCz2wY,495388
|
|
24
25
|
content_studio/static/content_studio/icons/pi/Phosphor-Bold.woff2,sha256=IVO1LOqeBvwMyElgSL5IWzgaD4vxMK3qWpdW_I8QC4c,150052
|
|
25
26
|
content_studio/static/content_studio/icons/pi/style.css,sha256=yKMt9n-L1X9wxjceFewjLfJd3ro-uQYNeqpoEBps4kA,85821
|
|
26
27
|
content_studio/static/content_studio/img/media_placeholder.svg,sha256=ZLrfeqvaC5YwuHAg_7YJXLhYzLz2azVcKqCLEGOVTTs,3253
|
|
28
|
+
content_studio/static/content_studio/index.html,sha256=IuVbv9-sX3WUUr7mDDEAVG9Q1tKmsHoZMpZwaWJ9SNM,896
|
|
27
29
|
content_studio/static/content_studio/locales/en/translation.json,sha256=aYJThaN5db_1Eo5YeeDpqxhf5gohtOL12yG4FF4CaRI,2524
|
|
28
|
-
content_studio/static/content_studio/locales/nl/translation.json,sha256=
|
|
30
|
+
content_studio/static/content_studio/locales/nl/translation.json,sha256=GUuxX_rLKQll_gBe6dSpNb1etF7p7EuF-gJNJzqd4v8,3131
|
|
29
31
|
content_studio/static/content_studio/vite.svg,sha256=SnSK_UQ5GLsWWRyDTEAdrjPoeGGrXbrQgRw6O0qSFPs,1497
|
|
30
|
-
content_studio/templates/content_studio/index.html,sha256=
|
|
32
|
+
content_studio/templates/content_studio/index.html,sha256=As2C1ahUKDvVy4kTFU0gHTy9mIL82817NUSSf93GC_Q,1032
|
|
31
33
|
content_studio/token_backends/__init__.py,sha256=dO3aWIHXX8He399ZEvlS4fNgyL84OY9TEtkva9b5N5Q,1183
|
|
32
34
|
content_studio/token_backends/jwt.py,sha256=niGCpRqaUVhhS9haXfH1uFlg2v8NLB5IpsJ4N79Q0Gg,1565
|
|
33
35
|
content_studio/urls.py,sha256=EY7lbzC0Q5vLfvqE3rK_4hmDrXhTuxKzYC55cc5tEEo,701
|
|
34
|
-
content_studio/utils.py,sha256=
|
|
35
|
-
content_studio/views.py,sha256=
|
|
36
|
-
content_studio/viewsets.py,sha256=
|
|
37
|
-
content_studio/widgets.py,sha256=
|
|
38
|
-
django_content_studio-1.0.
|
|
39
|
-
django_content_studio-1.0.
|
|
40
|
-
django_content_studio-1.0.
|
|
41
|
-
django_content_studio-1.0.
|
|
36
|
+
content_studio/utils.py,sha256=dGiYCixg-qcPGbF4hV6fts1Vv4ED8gRi8syLuSS4xzw,2123
|
|
37
|
+
content_studio/views.py,sha256=D5-o5pcj3RHuGyGB7S8fXAXoccRqdFpQEZZQ6sn7TvE,6361
|
|
38
|
+
content_studio/viewsets.py,sha256=iLhV6A_dl64-Ui4_DDT1Gje9ygvu0dYtF4OAO90MHSQ,5776
|
|
39
|
+
content_studio/widgets.py,sha256=kROwtBrM3s-NKSo9riVX_oDW5crlCx1TiPBt0HKX6Zc,1111
|
|
40
|
+
django_content_studio-1.0.0b10.dist-info/LICENSE,sha256=Wnx2EJhtSNnXE5Qs80i1HTBNFZTi8acEtC5TYqtFlnQ,1075
|
|
41
|
+
django_content_studio-1.0.0b10.dist-info/METADATA,sha256=ag5nbI1zmmBTVT-Dl2bzDCFJ3JG0Al7CBEqySVoxGkk,2513
|
|
42
|
+
django_content_studio-1.0.0b10.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
43
|
+
django_content_studio-1.0.0b10.dist-info/RECORD,,
|
{django_content_studio-1.0.0b8.dist-info → django_content_studio-1.0.0b10.dist-info}/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|