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.
@@ -1,5 +1,5 @@
1
1
  __title__ = "Django Content Studio"
2
- __version__ = "1.0.0-beta.8"
2
+ __version__ = "1.0.0-beta.10"
3
3
  __author__ = "Leon van der Grient"
4
4
  __license__ = "MIT"
5
5
 
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": admin_class.list_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.all()
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
- {"name": w.__class__.__name__, "col_span": getattr(w, "col_span", 1)}
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<name>[^/.]+)")
54
- def get(self, request, name=None):
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 name == w.__class__.__name__.lower():
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
- return Response(data=widget.get_data(request))
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):
@@ -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",)