wbwriter 2.2.1__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.

Potentially problematic release.


This version of wbwriter might be problematic. Click here for more details.

Files changed (70) hide show
  1. wbwriter/__init__.py +1 -0
  2. wbwriter/admin.py +142 -0
  3. wbwriter/apps.py +5 -0
  4. wbwriter/dynamic_preferences_registry.py +15 -0
  5. wbwriter/factories/__init__.py +13 -0
  6. wbwriter/factories/article.py +181 -0
  7. wbwriter/factories/meta_information.py +29 -0
  8. wbwriter/filters/__init__.py +2 -0
  9. wbwriter/filters/article.py +47 -0
  10. wbwriter/filters/metainformationinstance.py +24 -0
  11. wbwriter/migrations/0001_initial_squashed_squashed_0008_alter_article_author_alter_article_feedback_contact_and_more.py +653 -0
  12. wbwriter/migrations/0009_dependantarticle.py +41 -0
  13. wbwriter/migrations/0010_alter_article_options.py +20 -0
  14. wbwriter/migrations/0011_auto_20240103_0953.py +39 -0
  15. wbwriter/migrations/__init__.py +0 -0
  16. wbwriter/models/__init__.py +9 -0
  17. wbwriter/models/article.py +1179 -0
  18. wbwriter/models/article_type.py +59 -0
  19. wbwriter/models/block.py +24 -0
  20. wbwriter/models/block_parameter.py +19 -0
  21. wbwriter/models/in_editor_template.py +102 -0
  22. wbwriter/models/meta_information.py +87 -0
  23. wbwriter/models/mixins.py +9 -0
  24. wbwriter/models/publication_models.py +170 -0
  25. wbwriter/models/style.py +13 -0
  26. wbwriter/models/template.py +34 -0
  27. wbwriter/pdf_generator.py +172 -0
  28. wbwriter/publication_parser.py +258 -0
  29. wbwriter/serializers/__init__.py +28 -0
  30. wbwriter/serializers/article.py +359 -0
  31. wbwriter/serializers/article_type.py +14 -0
  32. wbwriter/serializers/in_editor_template.py +37 -0
  33. wbwriter/serializers/meta_information.py +67 -0
  34. wbwriter/serializers/publication.py +82 -0
  35. wbwriter/templatetags/__init__.py +0 -0
  36. wbwriter/templatetags/writer.py +72 -0
  37. wbwriter/tests/__init__.py +0 -0
  38. wbwriter/tests/conftest.py +32 -0
  39. wbwriter/tests/signals.py +23 -0
  40. wbwriter/tests/test_filter.py +58 -0
  41. wbwriter/tests/test_model.py +591 -0
  42. wbwriter/tests/test_writer.py +38 -0
  43. wbwriter/tests/tests.py +18 -0
  44. wbwriter/typings.py +23 -0
  45. wbwriter/urls.py +83 -0
  46. wbwriter/viewsets/__init__.py +22 -0
  47. wbwriter/viewsets/article.py +270 -0
  48. wbwriter/viewsets/article_type.py +49 -0
  49. wbwriter/viewsets/buttons.py +61 -0
  50. wbwriter/viewsets/display/__init__.py +6 -0
  51. wbwriter/viewsets/display/article.py +404 -0
  52. wbwriter/viewsets/display/article_type.py +27 -0
  53. wbwriter/viewsets/display/in_editor_template.py +39 -0
  54. wbwriter/viewsets/display/meta_information.py +37 -0
  55. wbwriter/viewsets/display/meta_information_instance.py +28 -0
  56. wbwriter/viewsets/display/publication.py +55 -0
  57. wbwriter/viewsets/endpoints/__init__.py +2 -0
  58. wbwriter/viewsets/endpoints/article.py +12 -0
  59. wbwriter/viewsets/endpoints/meta_information.py +14 -0
  60. wbwriter/viewsets/in_editor_template.py +68 -0
  61. wbwriter/viewsets/menu.py +42 -0
  62. wbwriter/viewsets/meta_information.py +51 -0
  63. wbwriter/viewsets/meta_information_instance.py +48 -0
  64. wbwriter/viewsets/publication.py +117 -0
  65. wbwriter/viewsets/titles/__init__.py +2 -0
  66. wbwriter/viewsets/titles/publication_title_config.py +18 -0
  67. wbwriter/viewsets/titles/reviewer_article_title_config.py +6 -0
  68. wbwriter-2.2.1.dist-info/METADATA +8 -0
  69. wbwriter-2.2.1.dist-info/RECORD +70 -0
  70. wbwriter-2.2.1.dist-info/WHEEL +5 -0
wbwriter/urls.py ADDED
@@ -0,0 +1,83 @@
1
+ from django.urls import include, path
2
+ from wbcore.routers import WBCoreRouter
3
+ from wbwriter import viewsets
4
+
5
+ router = WBCoreRouter()
6
+ router.register(r"publication", viewsets.PublicationModelViewSet, basename="publication")
7
+ router.register(
8
+ r"publicationrepresentation", viewsets.PublicationRepresentationViewSet, basename="publicationrepresentation"
9
+ )
10
+ router.register(
11
+ r"publicationparserrepresentation",
12
+ viewsets.PublicationParserRepresentationViewSet,
13
+ basename="publicationparserrepresentation",
14
+ )
15
+ router.register(
16
+ r"publicationparser",
17
+ viewsets.PublicationParserModelViewSet,
18
+ basename="publicationparser",
19
+ )
20
+
21
+ router.register(r"article", viewsets.ArticleModelViewSet, basename="article")
22
+ router.register(r"articlerepresentation", viewsets.ArticleRepresentionViewSet, basename="articlerepresentation")
23
+ # router.register(r"ddq", viewsets.DDQArticleModelViewSet, basename="ddq")
24
+ router.register(r"review-article", viewsets.ReviewerArticleModelViewSet, basename="review-article")
25
+ router.register(
26
+ r"articletyperepresentation",
27
+ viewsets.ArticleTypeRepresentationViewSet,
28
+ basename="articletyperepresentation",
29
+ )
30
+ router.register(
31
+ r"articletype",
32
+ viewsets.ArticleTypeModelViewSet,
33
+ basename="articletype",
34
+ )
35
+ router.register(
36
+ r"in-editor-template",
37
+ viewsets.InEditorTemplateModelViewSet,
38
+ basename="in-editor-template",
39
+ )
40
+ router.register(
41
+ r"in-editor-template-representation",
42
+ viewsets.InEditorTemplateRepresentationViewSet,
43
+ basename="in-editor-template-representation",
44
+ )
45
+ router.register(
46
+ r"metainformation",
47
+ viewsets.MetaInformationRepresentationViewSet,
48
+ basename="metainformation",
49
+ )
50
+ router.register(
51
+ r"metainformationinstance",
52
+ viewsets.MetaInformationInstanceModelViewSet,
53
+ basename="metainformationinstance",
54
+ )
55
+ router.register(
56
+ r"dependantarticle",
57
+ viewsets.DependantArticleModelViewSet,
58
+ basename="dependantarticle",
59
+ )
60
+ article_router = WBCoreRouter()
61
+ article_router.register(
62
+ r"metainformationinstancearticle",
63
+ viewsets.MetaInformationInstanceModelViewSet,
64
+ basename="metainformationinstancearticle",
65
+ )
66
+ article_router.register(
67
+ r"dependantarticle",
68
+ viewsets.DependantArticleModelViewSet,
69
+ basename="dependantarticle-article",
70
+ )
71
+
72
+ used_article_router = WBCoreRouter()
73
+ used_article_router.register(
74
+ r"usedarticle",
75
+ viewsets.DependantArticleModelViewSet,
76
+ basename="usedarticle-article",
77
+ )
78
+
79
+ urlpatterns = [
80
+ path("", include(router.urls)),
81
+ path("article/<int:article_id>/", include(article_router.urls)),
82
+ path("article/<int:used_article_id>/", include(used_article_router.urls)),
83
+ ]
@@ -0,0 +1,22 @@
1
+ from .article import (
2
+ ArticleModelViewSet,
3
+ ReviewerArticleModelViewSet,
4
+ DependantArticleModelViewSet,
5
+ ArticleRepresentionViewSet,
6
+ )
7
+ from .article_type import ArticleTypeModelViewSet, ArticleTypeRepresentationViewSet
8
+ from .in_editor_template import (
9
+ InEditorTemplateModelViewSet,
10
+ InEditorTemplateRepresentationViewSet,
11
+ )
12
+ from .meta_information import MetaInformationRepresentationViewSet
13
+ from .meta_information_instance import (
14
+ MetaInformationInstanceModelViewSet,
15
+ MetaInformationInstanceRepresentationViewSet,
16
+ )
17
+ from .publication import (
18
+ PublicationModelViewSet,
19
+ PublicationParserModelViewSet,
20
+ PublicationParserRepresentationViewSet,
21
+ PublicationRepresentationViewSet,
22
+ )
@@ -0,0 +1,270 @@
1
+ from django.contrib.contenttypes.models import ContentType
2
+ from django.db.models import Q, QuerySet
3
+ from django.shortcuts import get_object_or_404
4
+ from django.utils.functional import cached_property
5
+ from rest_framework import status
6
+ from rest_framework.decorators import action
7
+ from rest_framework.response import Response
8
+ from reversion.views import RevisionMixin
9
+ from wbcore.contrib.authentication.authentication import JWTCookieAuthentication
10
+ from wbcore.contrib.directory.models import Person
11
+ from wbcore.utils.views import CloneMixin
12
+ from wbcore.viewsets import ModelViewSet, RepresentationViewSet
13
+ from wbwriter.filters.article import ArticleFilter
14
+ from wbwriter.models import Article, can_access_article
15
+ from wbwriter.models.article import (
16
+ DependantArticle,
17
+ can_administrate_article,
18
+ can_edit_article_author,
19
+ can_edit_article_content,
20
+ can_edit_article_meta_data,
21
+ can_edit_article_type,
22
+ generate_pdf_as_task,
23
+ )
24
+ from wbwriter.serializers import (
25
+ ArticleFullModelSerializer,
26
+ ArticleRepresentionSerializer,
27
+ DependantArticleModelSerializer,
28
+ )
29
+
30
+ from .buttons import ArticleModelButtonConfig
31
+ from .display import ArticleDisplayConfig, DependantArticleDisplayConfig
32
+ from .endpoints import (
33
+ DependantArticleEndpointViewConfig,
34
+ ReviewerArticleModelEndpointConfig,
35
+ )
36
+ from .titles import ReviewerArticleTitleConfig
37
+
38
+
39
+ class ArticleRepresentionViewSet(RepresentationViewSet):
40
+ queryset = Article.objects.all()
41
+ serializer_class = ArticleRepresentionSerializer
42
+
43
+
44
+ class DependantArticleModelViewSet(ModelViewSet):
45
+ queryset = DependantArticle.objects.all()
46
+ serializer_class = DependantArticleModelSerializer
47
+ display_config_class = DependantArticleDisplayConfig
48
+ endpoint_config_class = DependantArticleEndpointViewConfig
49
+
50
+ def get_queryset(self) -> QuerySet[DependantArticle]:
51
+ queryset = super().get_queryset()
52
+ if article_id := self.kwargs.get("article_id", None):
53
+ queryset = queryset.filter(article_id=article_id)
54
+
55
+ if used_article_id := self.kwargs.get("used_article_id", None):
56
+ queryset = queryset.filter(dependant_article_id=used_article_id)
57
+
58
+ return queryset.select_related("article", "dependant_article")
59
+
60
+
61
+ class ArticleInstancePermissionMixin:
62
+ @cached_property
63
+ def profile(self):
64
+ user = self.request.user
65
+ return user.profile
66
+
67
+ @cached_property
68
+ def instance(self) -> Article | None:
69
+ if "pk" in self.kwargs:
70
+ return self.get_object()
71
+
72
+ @cached_property
73
+ def can_edit_article_author(self) -> bool:
74
+ if self.instance:
75
+ return can_edit_article_author(self.instance, self.request.user)
76
+ return False
77
+
78
+ @cached_property
79
+ def can_edit_article_meta_data(self) -> bool:
80
+ if self.instance:
81
+ return can_edit_article_meta_data(self.instance, self.request.user)
82
+ return False
83
+
84
+ @cached_property
85
+ def can_edit_article_type(self) -> bool:
86
+ return can_edit_article_type(self.instance, self.request.user)
87
+
88
+ @cached_property
89
+ def can_edit_article_content(self) -> bool:
90
+ if self.instance:
91
+ return can_edit_article_content(self.instance, self.request.user)
92
+ return False
93
+
94
+ @cached_property
95
+ def can_administrate_article(self) -> bool:
96
+ if self.instance:
97
+ return can_administrate_article(self.instance, self.request.user)
98
+ return False
99
+
100
+
101
+ class ArticleModelViewSet(ArticleInstancePermissionMixin, CloneMixin, RevisionMixin, ModelViewSet):
102
+ # LIST_DOCUMENTATION = "wbwriter/viewsets/documentation/article.md"
103
+
104
+ serializer_class = ArticleFullModelSerializer
105
+ queryset = Article.objects.all()
106
+
107
+ button_config_class = ArticleModelButtonConfig
108
+ display_config_class = ArticleDisplayConfig
109
+ filterset_class = ArticleFilter
110
+
111
+ search_fields = (
112
+ "tags__title",
113
+ "type__label",
114
+ "type__slug",
115
+ "content",
116
+ "title",
117
+ "name",
118
+ )
119
+ ordering = ("-modified",)
120
+ ordering_fields = (
121
+ "title",
122
+ "status",
123
+ "type",
124
+ "created",
125
+ "modified",
126
+ "id",
127
+ "author",
128
+ "reviewer",
129
+ "peer_reviewer",
130
+ "qa_reviewer",
131
+ )
132
+
133
+ @cached_property
134
+ def content_type(self):
135
+ return ContentType.objects.get(app_label="wbwriter", model="article")
136
+
137
+ def get_queryset(self):
138
+ if self.request.user.has_perm("wbwriter.administrate_article") or self.profile.is_internal:
139
+ qs = Article.objects.all()
140
+ else:
141
+ qs = Article.objects.filter(is_private=False)
142
+ return qs.select_related(
143
+ "type", "feedback_contact", "reviewer", "peer_reviewer", "qa_reviewer", "author"
144
+ ).prefetch_related("tags")
145
+
146
+ @action(detail=True, methods=["POST"], authentication_classes=[JWTCookieAuthentication])
147
+ def generate_pdf(self, request, pk):
148
+ """
149
+ NOTE: JWTCookieAuthentication only works, if the frontend is served under the same URL as the backend due to
150
+ the cookie.
151
+ """
152
+ article = get_object_or_404(Article, pk=pk)
153
+
154
+ if not can_access_article(article, request.user):
155
+ return Response({}, status=status.HTTP_403_FORBIDDEN)
156
+
157
+ generate_pdf_as_task.delay(pk, user_id=request.user.id)
158
+ return Response(
159
+ {"__notification": {"title": "PDF is going to be created"}},
160
+ status=status.HTTP_200_OK,
161
+ )
162
+
163
+ @action(detail=True, methods=["POST"], authentication_classes=[JWTCookieAuthentication])
164
+ def edit(self, request, pk):
165
+ """Sets the current user to be the author of the specified article."""
166
+ article = get_object_or_404(Article, pk=pk)
167
+ article.author = request.user.profile
168
+ article.save()
169
+ return Response(
170
+ {"__notification": {"title": f'You are now author of "{article.title}".'}},
171
+ status=status.HTTP_200_OK,
172
+ )
173
+
174
+ @action(detail=True, methods=["POST"], authentication_classes=[JWTCookieAuthentication])
175
+ def reroll_peer(self, request, pk):
176
+ """Rolls for a new peer reviewer."""
177
+ article = get_object_or_404(Article, pk=pk)
178
+ article.reroll_peer()
179
+ article.save()
180
+ return Response(
181
+ {"__notification": {"title": f'The new peer reviewer is "{article.peer_reviewer}".'}},
182
+ status=status.HTTP_200_OK,
183
+ )
184
+
185
+ @action(detail=True, methods=["POST"], authentication_classes=[JWTCookieAuthentication])
186
+ def reroll_qa(self, request, pk):
187
+ """Rolls for a new QA reviewer."""
188
+ article = get_object_or_404(Article, pk=pk)
189
+ article.reroll_qa()
190
+ article.save()
191
+ return Response(
192
+ {"__notification": {"title": f'The new QA reviewer is "{article.qa_reviewer}".'}},
193
+ status=status.HTTP_200_OK,
194
+ )
195
+
196
+ @action(detail=True, methods=["POST"], authentication_classes=[JWTCookieAuthentication])
197
+ def reroll_peer_and_qa(self, request, pk):
198
+ """Rolls for a new peer and a new QA reviewer."""
199
+ article = get_object_or_404(Article, pk=pk)
200
+ article.reroll_peer_and_qa()
201
+ article.save()
202
+ return Response(
203
+ {"__notification": {"title": f'You are now author of "{article.title}".'}},
204
+ status=status.HTTP_200_OK,
205
+ )
206
+
207
+ @action(detail=True, methods=["POST"], authentication_classes=[JWTCookieAuthentication])
208
+ def assign_new_author(self, request, pk):
209
+ """Assigns a new author."""
210
+ article = get_object_or_404(Article, pk=pk)
211
+ author_id = request.POST.get("author")
212
+ author = get_object_or_404(Person, pk=author_id)
213
+ article.author = author
214
+ article.save()
215
+ return Response(
216
+ {"__notification": {"title": f"The new author is {author}."}},
217
+ status=status.HTTP_200_OK,
218
+ )
219
+
220
+
221
+ class ReviewerArticleModelViewSet(ArticleModelViewSet):
222
+ # LIST_DOCUMENTATION = "wbwriter/viewsets/documentation/article.md"
223
+
224
+ # serializer_class = ArticleModelSerializer
225
+ queryset = Article.objects.all()
226
+ title_config_class = ReviewerArticleTitleConfig
227
+
228
+ button_config_class = ArticleModelButtonConfig
229
+ display_config_class = ArticleDisplayConfig
230
+ endpoint_config_class = ReviewerArticleModelEndpointConfig
231
+
232
+ search_fields = (
233
+ "tags__title",
234
+ "type__label",
235
+ "type__slug",
236
+ "content",
237
+ "title",
238
+ "name",
239
+ )
240
+ filter_fields = {
241
+ "title": ["icontains"],
242
+ "status": ["exact"],
243
+ "tags": ["exact"],
244
+ "type": ["exact", "icontains"],
245
+ "author": ["exact"],
246
+ "reviewer": ["exact"],
247
+ "peer_reviewer": ["exact"],
248
+ "qa_reviewer": ["exact"],
249
+ "created": ["exact", "lt", "lte", "gt", "gte"],
250
+ "modified": ["exact", "lt", "lte", "gt", "gte"],
251
+ }
252
+ ordering = ("-modified",)
253
+ ordering_fields = ("title", "status", "type", "created", "modified")
254
+
255
+ def get_queryset(self):
256
+ return Article.objects.filter(
257
+ Q(reviewer=self.profile, status=Article.Status.FEEDBACK)
258
+ | Q(peer_reviewer=self.profile, status=Article.Status.PEER_REVIEW)
259
+ | Q(qa_reviewer=self.profile, status=Article.Status.QA_REVIEW)
260
+ | Q(author=self.profile, status=Article.Status.AUTHOR_APPROVAL)
261
+ )
262
+
263
+ def get_serializer_class(self):
264
+ # TODO: Create a unique endpoint for creating instances so that we
265
+ # can assign a "new instance" to have better control over what
266
+ # serializer we want to use.
267
+ # if self.kwargs.get("pk"):
268
+ # return ArticleFullModelSerializer
269
+ # return ArticleModelSerializer
270
+ return ArticleFullModelSerializer
@@ -0,0 +1,49 @@
1
+ from wbcore.viewsets import ModelViewSet, RepresentationViewSet
2
+ from wbwriter.models import ArticleType
3
+ from wbwriter.serializers import (
4
+ ArticleTypeModelSerializer,
5
+ ArticleTypeRepresentationSerializer,
6
+ )
7
+
8
+ from .display import ArticleTypeDisplayConfig
9
+
10
+
11
+ class ArticleTypeModelViewSet(ModelViewSet):
12
+ ENDPOINT = "wbwriter:article-type-list"
13
+
14
+ serializer_class = ArticleTypeModelSerializer
15
+ # queryset = ArticleType.objects.all()
16
+
17
+ display_config_class = ArticleTypeDisplayConfig
18
+
19
+ search_fields = ("label", "slug")
20
+ filter_fields = {"label": ["icontains", "exact"], "slug": ["icontains", "exact"]}
21
+ ordering_fields = ("label",)
22
+
23
+ def get_queryset(self):
24
+ request = self.request
25
+ user = request.user
26
+ profile = user.profile
27
+
28
+ if user.is_superuser or profile.is_internal:
29
+ return ArticleType.objects.all()
30
+ # TODO: What to do with customers?
31
+
32
+
33
+ class ArticleTypeRepresentationViewSet(RepresentationViewSet):
34
+ serializer_class = ArticleTypeRepresentationSerializer
35
+ queryset = ArticleType.objects.all()
36
+
37
+ search_fields = ("label", "slug")
38
+ filter_fields = {"label": ["icontains", "exact"], "slug": ["icontains", "exact"]}
39
+ ordering_fields = ("label",)
40
+
41
+ def get_queryset(self):
42
+ request = self.request
43
+ user = request.user
44
+ profile = user.profile
45
+
46
+ if user.is_superuser or profile.is_internal:
47
+ return ArticleType.objects.all()
48
+
49
+ return ArticleType.objects.none()
@@ -0,0 +1,61 @@
1
+ from wbcore import serializers
2
+ from wbcore.contrib.directory.models import Person
3
+ from wbcore.contrib.directory.serializers import (
4
+ InternalUserProfileRepresentationSerializer,
5
+ )
6
+ from wbcore.contrib.icons import WBIcon
7
+ from wbcore.metadata.configs import buttons as bt
8
+ from wbcore.metadata.configs.buttons.view_config import ButtonViewConfig
9
+ from wbcore.metadata.configs.display.instance_display.shortcuts import (
10
+ create_simple_display,
11
+ )
12
+ from wbwriter.models.article import Article
13
+
14
+
15
+ class ArticleModelButtonConfig(ButtonViewConfig):
16
+ def get_custom_list_instance_buttons(self) -> set:
17
+ return {bt.HyperlinkButton(key="preview", label="Preview PDF", icon=WBIcon.VIEW.icon)}
18
+
19
+ def get_custom_instance_buttons(self):
20
+ class AssignAuthorSerializer(serializers.Serializer):
21
+ author = serializers.PrimaryKeyRelatedField(queryset=Person.objects.filter_only_internal())
22
+ _author = InternalUserProfileRepresentationSerializer(source="author")
23
+
24
+ custom_instance_buttons = {
25
+ bt.ActionButton(key="generate_pdf", label="Generate PDF", icon=WBIcon.DOCUMENT.icon),
26
+ list(self.get_custom_list_instance_buttons())[0],
27
+ bt.WidgetButton(key="publications", label="Publications", icon=WBIcon.NOTEBOOK.icon),
28
+ bt.ActionButton(key="reroll_peer", label="Assign new peer reviewer", icon=WBIcon.REFRESH.icon),
29
+ bt.ActionButton(key="reroll_qa", label="Assign new QA reviewer", icon=WBIcon.REFRESH.icon),
30
+ bt.ActionButton(
31
+ key="reroll_peer_and_qa", label="Assign new peer and QA reviewers", icon=WBIcon.REFRESH.icon
32
+ ),
33
+ bt.ActionButton(
34
+ key="assign_new_author",
35
+ label="Assign another author",
36
+ icon=WBIcon.PEOPLE.icon,
37
+ serializer=AssignAuthorSerializer,
38
+ instance_display=create_simple_display([["author"]]),
39
+ ),
40
+ }
41
+ # Make sure that the "Edit" button only shows up on Status "draft" and
42
+ # only if the article has no author assigned, and is of Type "DDQ".
43
+ if self.view.kwargs.get("pk", None):
44
+ instance = self.view.get_object()
45
+ if (
46
+ instance.status == Article.Status.DRAFT
47
+ and instance.type
48
+ and instance.type.slug == "ddq"
49
+ and not instance.author
50
+ ):
51
+ custom_instance_buttons.add(bt.ActionButton(key="edit", label="Edit", icon=WBIcon.EDIT.icon))
52
+
53
+ return custom_instance_buttons
54
+
55
+
56
+ class PublicationButtonConfig(ButtonViewConfig):
57
+ def get_custom_instance_buttons(self):
58
+ return {bt.HyperlinkButton(key="pdf_file", label="PDF", icon=WBIcon.NOTEBOOK.icon)}
59
+
60
+ def get_custom_list_instance_buttons(self):
61
+ return self.get_custom_instance_buttons()
@@ -0,0 +1,6 @@
1
+ from .article import ArticleDisplayConfig, DependantArticleDisplayConfig
2
+ from .article_type import ArticleTypeDisplayConfig
3
+ from .in_editor_template import InEditorTemplateDisplayConfig
4
+ from .meta_information import MetaInformationDisplayConfig
5
+ from .meta_information_instance import MetaInformationInstanceDisplayConfig
6
+ from .publication import PublicationDisplayConfig, PublicationParserDisplayConfig