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
content_studio/__init__.py
CHANGED
content_studio/admin.py
CHANGED
|
@@ -13,6 +13,7 @@ from .token_backends import TokenBackendManager
|
|
|
13
13
|
from .utils import get_related_field_name, flatten
|
|
14
14
|
|
|
15
15
|
register = admin.register
|
|
16
|
+
display = admin.display
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
class StackedInline(admin.StackedInline):
|
|
@@ -102,6 +103,12 @@ class AdminSite(admin.AdminSite):
|
|
|
102
103
|
"""
|
|
103
104
|
return obj.file.url
|
|
104
105
|
|
|
106
|
+
def get_tenants(self, tenant_model: Type[models.Model], **kwargs):
|
|
107
|
+
"""
|
|
108
|
+
Method for getting the list of available tenants.
|
|
109
|
+
"""
|
|
110
|
+
return tenant_model.objects.all()
|
|
111
|
+
|
|
105
112
|
|
|
106
113
|
admin_site = AdminSite()
|
|
107
114
|
|
|
@@ -217,7 +224,7 @@ class AdminSerializer:
|
|
|
217
224
|
"list": {
|
|
218
225
|
"per_page": admin_class.list_per_page,
|
|
219
226
|
"description": getattr(admin_class, "list_description", ""),
|
|
220
|
-
"display":
|
|
227
|
+
"display": self.get_list_display(),
|
|
221
228
|
"search": len(admin_class.search_fields) > 0,
|
|
222
229
|
"filter": admin_class.list_filter,
|
|
223
230
|
"sortable_by": admin_class.sortable_by,
|
|
@@ -254,6 +261,27 @@ class AdminSerializer:
|
|
|
254
261
|
for i in self.get_edit_sidebar(getattr(admin_class, "edit_sidebar", None))
|
|
255
262
|
]
|
|
256
263
|
|
|
264
|
+
def get_list_display(self):
|
|
265
|
+
admin_class = self.admin_class
|
|
266
|
+
fields = []
|
|
267
|
+
|
|
268
|
+
for field in admin_class.list_display:
|
|
269
|
+
if hasattr(admin_class, field):
|
|
270
|
+
method = getattr(admin_class, field)
|
|
271
|
+
description = getattr(method, "short_description", None)
|
|
272
|
+
empty_value = getattr(method, "empty_value", None)
|
|
273
|
+
fields.append(
|
|
274
|
+
{
|
|
275
|
+
"name": field,
|
|
276
|
+
"description": description,
|
|
277
|
+
"empty_value": empty_value,
|
|
278
|
+
}
|
|
279
|
+
)
|
|
280
|
+
else:
|
|
281
|
+
fields.append({"name": field})
|
|
282
|
+
|
|
283
|
+
return fields
|
|
284
|
+
|
|
257
285
|
def get_edit_main(self, edit_main):
|
|
258
286
|
"""
|
|
259
287
|
Returns a normalized list of form set groups.
|
content_studio/apps.py
CHANGED
|
@@ -4,7 +4,7 @@ from django.contrib import admin
|
|
|
4
4
|
from . import VERSION
|
|
5
5
|
from .paginators import ContentPagination
|
|
6
6
|
from .settings import cs_settings
|
|
7
|
-
from .utils import is_runserver
|
|
7
|
+
from .utils import is_runserver, get_tenant_field_name
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class DjangoContentStudioConfig(AppConfig):
|
|
@@ -68,7 +68,7 @@ class DjangoContentStudioConfig(AppConfig):
|
|
|
68
68
|
_admin_model = admin_model
|
|
69
69
|
is_singleton = getattr(admin_model, "is_singleton", False)
|
|
70
70
|
pagination_class = Pagination
|
|
71
|
-
queryset = _model.objects.
|
|
71
|
+
queryset = _model.objects.none()
|
|
72
72
|
search_fields = list(getattr(_admin_model, "search_fields", []))
|
|
73
73
|
|
|
74
74
|
def get_serializer_class(self):
|
|
@@ -90,6 +90,16 @@ class DjangoContentStudioConfig(AppConfig):
|
|
|
90
90
|
|
|
91
91
|
return Serializer
|
|
92
92
|
|
|
93
|
+
def get_queryset(self):
|
|
94
|
+
tenant_model = cs_settings.TENANT_MODEL
|
|
95
|
+
tenant_id = self.request.headers.get("x-dcs-tenant", None)
|
|
96
|
+
field_name = get_tenant_field_name(self._model)
|
|
97
|
+
|
|
98
|
+
if tenant_model and tenant_id and field_name:
|
|
99
|
+
return self._model.objects.filter(**{f"{field_name}_id": tenant_id})
|
|
100
|
+
|
|
101
|
+
return self._model.objects.all()
|
|
102
|
+
|
|
93
103
|
if parent:
|
|
94
104
|
prefix = f"api/inlines/{parent._meta.label_lower}/{model._meta.label_lower}"
|
|
95
105
|
basename = f"content_studio_api-{parent._meta.label_lower}-{model._meta.label_lower}"
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
|
|
3
|
+
from rest_framework import serializers
|
|
1
4
|
from rest_framework.decorators import action
|
|
2
5
|
from rest_framework.exceptions import NotFound
|
|
3
6
|
from rest_framework.parsers import JSONParser
|
|
@@ -31,7 +34,11 @@ class Dashboard:
|
|
|
31
34
|
def serialize(self):
|
|
32
35
|
return {
|
|
33
36
|
"widgets": [
|
|
34
|
-
{
|
|
37
|
+
{
|
|
38
|
+
"name": w.name,
|
|
39
|
+
"widget_id": w.widget_id,
|
|
40
|
+
"col_span": w.col_span,
|
|
41
|
+
}
|
|
35
42
|
for w in self.widgets
|
|
36
43
|
]
|
|
37
44
|
}
|
|
@@ -50,15 +57,37 @@ class DashboardViewSet(ViewSet):
|
|
|
50
57
|
admin_site.token_backend.active_backend.authentication_class
|
|
51
58
|
]
|
|
52
59
|
|
|
53
|
-
@action(detail=False, url_path="(?P<
|
|
54
|
-
def get(self, request,
|
|
60
|
+
@action(detail=False, url_path="widgets/(?P<widget_id>[^/.]+)")
|
|
61
|
+
def get(self, request, widget_id=None):
|
|
55
62
|
widget = None
|
|
56
63
|
|
|
57
64
|
for w in self.dashboard.widgets:
|
|
58
|
-
if
|
|
65
|
+
if widget_id == str(w.widget_id):
|
|
59
66
|
widget = w
|
|
60
67
|
|
|
61
68
|
if not widget:
|
|
62
69
|
raise NotFound()
|
|
63
70
|
|
|
64
|
-
|
|
71
|
+
data = widget.get_data(request)
|
|
72
|
+
|
|
73
|
+
if isinstance(data, serializers.Serializer):
|
|
74
|
+
data.is_valid(raise_exception=True)
|
|
75
|
+
data = data.data
|
|
76
|
+
|
|
77
|
+
return Response(data=data)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class BaseWidget:
|
|
81
|
+
col_span = 1
|
|
82
|
+
widget_id = None
|
|
83
|
+
|
|
84
|
+
def __init__(self):
|
|
85
|
+
self.widget_id = self.widget_id or uuid.uuid4()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class SpacingWidget(BaseWidget):
|
|
89
|
+
name = "SpacingWidget"
|
|
90
|
+
|
|
91
|
+
def __init__(self, col_span=1):
|
|
92
|
+
self.col_span = col_span
|
|
93
|
+
super().__init__()
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
from content_studio.serializers import ContentSerializer
|
|
2
1
|
from django.contrib.admin.models import LogEntry
|
|
3
2
|
from rest_framework import serializers
|
|
4
3
|
|
|
4
|
+
from content_studio.serializers import ContentSerializer
|
|
5
|
+
from ..dashboard import BaseWidget
|
|
6
|
+
|
|
5
7
|
|
|
6
8
|
class LogEntrySerializer(ContentSerializer):
|
|
7
9
|
object_model = serializers.SerializerMethodField()
|
|
@@ -22,11 +24,13 @@ class LogEntrySerializer(ContentSerializer):
|
|
|
22
24
|
return f"{obj.content_type.app_label}.{obj.content_type.model}"
|
|
23
25
|
|
|
24
26
|
|
|
25
|
-
class ActivityLogWidget:
|
|
27
|
+
class ActivityLogWidget(BaseWidget):
|
|
26
28
|
"""
|
|
27
29
|
Widget for showing activity logs.
|
|
28
30
|
"""
|
|
29
31
|
|
|
32
|
+
name = "ActivityLogWidget"
|
|
33
|
+
|
|
30
34
|
col_span = 2
|
|
31
35
|
|
|
32
36
|
def get_data(self, request):
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from rest_framework import serializers
|
|
2
|
+
|
|
3
|
+
from ..dashboard import BaseWidget
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class StatisticWidgetSerializer(serializers.Serializer):
|
|
7
|
+
value = serializers.IntegerField()
|
|
8
|
+
prefix = serializers.CharField(default="", allow_blank=True, required=False)
|
|
9
|
+
suffix = serializers.CharField(default="", allow_blank=True, required=False)
|
|
10
|
+
title = serializers.CharField(default="", allow_blank=True, required=False)
|
|
11
|
+
trend = serializers.DecimalField(
|
|
12
|
+
max_digits=5, decimal_places=1, allow_null=True, required=False
|
|
13
|
+
)
|
|
14
|
+
trend_sentiment = serializers.ChoiceField(
|
|
15
|
+
default="default", required=False, choices=["default", "positive", "negative"]
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class StatisticWidget(BaseWidget):
|
|
20
|
+
"""
|
|
21
|
+
Widget for showing some statistic.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
name = "StatisticWidget"
|
|
25
|
+
|
|
26
|
+
def get_data(self, request):
|
|
27
|
+
raise NotImplementedError("You need to implement get_data for your widget.")
|
content_studio/models.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from django.db import models
|
|
2
2
|
|
|
3
|
-
from .utils import is_jsonable
|
|
3
|
+
from .utils import is_jsonable, get_tenant_field_name
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class ModelSerializer:
|
|
@@ -10,11 +10,14 @@ class ModelSerializer:
|
|
|
10
10
|
def serialize(self):
|
|
11
11
|
model = self.model
|
|
12
12
|
|
|
13
|
+
tenant_field = get_tenant_field_name(self.model)
|
|
14
|
+
|
|
13
15
|
return {
|
|
14
16
|
"label": model._meta.label_lower,
|
|
15
17
|
"verbose_name": model._meta.verbose_name,
|
|
16
18
|
"verbose_name_plural": model._meta.verbose_name_plural,
|
|
17
19
|
"fields": self.get_fields(),
|
|
20
|
+
"tenant_field": tenant_field,
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
def get_fields(self):
|
content_studio/settings.py
CHANGED
|
@@ -31,6 +31,7 @@ DEFAULTS = {
|
|
|
31
31
|
"CREATED_AT_ATTR": "created_at",
|
|
32
32
|
"MEDIA_LIBRARY_MODEL": None,
|
|
33
33
|
"MEDIA_LIBRARY_FOLDER_MODEL": None,
|
|
34
|
+
"TENANT_MODEL": None,
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
|
|
@@ -40,6 +41,7 @@ IMPORT_STRINGS = [
|
|
|
40
41
|
"LOGIN_BACKENDS",
|
|
41
42
|
"MEDIA_LIBRARY_MODEL",
|
|
42
43
|
"MEDIA_LIBRARY_FOLDER_MODEL",
|
|
44
|
+
"TENANT_MODEL",
|
|
43
45
|
]
|
|
44
46
|
|
|
45
47
|
|
|
@@ -150,3 +152,5 @@ def reload_settings(*args, **kwargs):
|
|
|
150
152
|
|
|
151
153
|
|
|
152
154
|
setting_changed.connect(reload_settings)
|
|
155
|
+
|
|
156
|
+
allowed_cors_headers = ("x-dcs-tenant",)
|