wbwiki 2.2.1__tar.gz

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.
Files changed (33) hide show
  1. wbwiki-2.2.1/.gitignore +181 -0
  2. wbwiki-2.2.1/PKG-INFO +5 -0
  3. wbwiki-2.2.1/pyproject.toml +29 -0
  4. wbwiki-2.2.1/wbwiki/__init__.py +1 -0
  5. wbwiki-2.2.1/wbwiki/admin.py +18 -0
  6. wbwiki-2.2.1/wbwiki/apps.py +5 -0
  7. wbwiki-2.2.1/wbwiki/dynamic_preferences_registry.py +16 -0
  8. wbwiki-2.2.1/wbwiki/factories.py +30 -0
  9. wbwiki-2.2.1/wbwiki/migrations/0001_initial_squashed_0004_alter_wikiarticle_tags.py +33 -0
  10. wbwiki-2.2.1/wbwiki/migrations/0005_wikiarticlerelationship_and_more.py +52 -0
  11. wbwiki-2.2.1/wbwiki/migrations/0006_auto_20240103_0954.py +43 -0
  12. wbwiki-2.2.1/wbwiki/migrations/__init__.py +0 -0
  13. wbwiki-2.2.1/wbwiki/models.py +76 -0
  14. wbwiki-2.2.1/wbwiki/preferences.py +10 -0
  15. wbwiki-2.2.1/wbwiki/serializers.py +91 -0
  16. wbwiki-2.2.1/wbwiki/tests/__init__.py +0 -0
  17. wbwiki-2.2.1/wbwiki/tests/conftest.py +13 -0
  18. wbwiki-2.2.1/wbwiki/tests/signals.py +13 -0
  19. wbwiki-2.2.1/wbwiki/tests/test_initial.py +2 -0
  20. wbwiki-2.2.1/wbwiki/tests/tests.py +17 -0
  21. wbwiki-2.2.1/wbwiki/urls.py +26 -0
  22. wbwiki-2.2.1/wbwiki/viewsets/__init__.py +5 -0
  23. wbwiki-2.2.1/wbwiki/viewsets/buttons/__init__.py +4 -0
  24. wbwiki-2.2.1/wbwiki/viewsets/buttons/wikis.py +51 -0
  25. wbwiki-2.2.1/wbwiki/viewsets/display/__init__.py +1 -0
  26. wbwiki-2.2.1/wbwiki/viewsets/display/wikis.py +75 -0
  27. wbwiki-2.2.1/wbwiki/viewsets/endpoints/__init__.py +4 -0
  28. wbwiki-2.2.1/wbwiki/viewsets/endpoints/wikis.py +20 -0
  29. wbwiki-2.2.1/wbwiki/viewsets/menu/__init__.py +1 -0
  30. wbwiki-2.2.1/wbwiki/viewsets/menu/wikis.py +21 -0
  31. wbwiki-2.2.1/wbwiki/viewsets/preview/__init__.py +0 -0
  32. wbwiki-2.2.1/wbwiki/viewsets/titles/init.py +0 -0
  33. wbwiki-2.2.1/wbwiki/viewsets/wikis.py +107 -0
@@ -0,0 +1,181 @@
1
+ ~
2
+
3
+ # Docker volumes
4
+ volumes/
5
+ # Poetry auth file
6
+ auth.toml
7
+
8
+ media/*
9
+ media/
10
+ mediafiles/
11
+ mediafiles/*
12
+ test/*
13
+ staticfiles/*
14
+ staticfiles/
15
+ #
16
+ # Byte-compiled / optimized / DLL files
17
+ __pycache__/
18
+ *.py[cod]
19
+ *$py.class
20
+
21
+ # C extensions
22
+ *.so
23
+
24
+ # Distribution / packaging
25
+ .Python
26
+ build/
27
+ develop-eggs/
28
+ dist/
29
+ info/
30
+ downloads/
31
+ eggs/
32
+ .eggs/
33
+ lib/
34
+ lib64/
35
+ parts/
36
+ sdist/
37
+ var/
38
+ wheels/
39
+ share/python-wheels/
40
+ *.egg-info/
41
+ .installed.cfg
42
+ *.egg
43
+ MANIFEST
44
+
45
+ # PyInstaller
46
+ # Usually these files are written by a python script from a template
47
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
48
+ *.manifest
49
+ *.spec
50
+
51
+ # Installer logs
52
+ pip-log.txt
53
+ pip-delete-this-directory.txt
54
+
55
+ # Unit test / coverage reports
56
+ htmlcov/
57
+ .tox/
58
+ .nox/
59
+ .coverage
60
+ .coverage.*
61
+ .cache
62
+ .dccache
63
+ nosetests.xml
64
+ coverage.xml
65
+ *.cover
66
+ *.py,cover
67
+ .hypothesis/
68
+ .pytest_cache/
69
+ cover/
70
+ report.xml
71
+ */report.xml
72
+
73
+ # Translations
74
+ *.mo
75
+ *.pot
76
+
77
+ # Django stuff:
78
+ *.log
79
+ local_settings.py
80
+ *.sqlite3
81
+ db.sqlite3-journal
82
+
83
+ # Flask stuff:
84
+ instance/
85
+ .webassets-cache
86
+
87
+ # Scrapy stuff:
88
+ .scrapy
89
+
90
+ # Sphinx documentation
91
+ docs/_build/
92
+
93
+ # PyBuilder
94
+ .pybuilder/
95
+ target/
96
+
97
+ # Jupyter Notebook
98
+ .ipynb_checkpoints
99
+ *.ipynb
100
+ # IPython
101
+ profile_default/
102
+ ipython_config.py
103
+
104
+ # pyenv
105
+ # For a library or package, you might want to ignore these files since the code is
106
+ # intended to run in multiple environments; otherwise, check them in:
107
+ # .python-version
108
+
109
+ # pipenv
110
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
111
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
112
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
113
+ # install all needed dependencies.
114
+ #Pipfile.lock
115
+
116
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
117
+ __pypackages__/
118
+
119
+ # Celery stuff
120
+ celerybeat-schedule
121
+ celerybeat.pid
122
+
123
+ # SageMath parsed files
124
+ *.sage.py
125
+
126
+ # Environments
127
+ .env
128
+ .envrc
129
+ .venv
130
+ env/
131
+ venv/
132
+ ENV/
133
+ env.bak/
134
+ venv.bak/
135
+ .vscode/
136
+ .idea/
137
+ .idea.bkp/
138
+
139
+ # Spyder project settings
140
+ .spyderproject
141
+ .spyproject
142
+
143
+ # Rope project settings
144
+ .ropeproject
145
+
146
+ # mkdocs documentation
147
+ /site
148
+
149
+ # mypy
150
+ .mypy_cache/
151
+ .dmypy.json
152
+ dmypy.json
153
+
154
+ # Pyre type checker
155
+ .pyre/
156
+
157
+ # pytype static type analyzer
158
+ .pytype/
159
+ crm/
160
+ # Cython debug symbols
161
+ cython_debug/
162
+
163
+ # Gitlab Runner
164
+ builds
165
+ builds/
166
+
167
+ # Integrator Office 365 : reverse proxy tunnel for outlook365
168
+ ngrok
169
+ */ngrok
170
+ /modules/**/system/
171
+
172
+ /modules/wbmailing/files/*
173
+ /modules/wbmailing/mailing/*
174
+
175
+ /projects/*/requirements.txt
176
+ public
177
+
178
+ # Ignore archive localization generated folder
179
+ backend/modules/**/archive/*
180
+ **/**/requirements.txt
181
+ CHANGELOG-*
wbwiki-2.2.1/PKG-INFO ADDED
@@ -0,0 +1,5 @@
1
+ Metadata-Version: 2.3
2
+ Name: wbwiki
3
+ Version: 2.2.1
4
+ Author-email: Christopher Wittlinger <c.wittlinger@stainly.com>
5
+ Requires-Dist: wbcore
@@ -0,0 +1,29 @@
1
+ [project]
2
+ name = "wbwiki"
3
+ description = ""
4
+ authors = [{ name = "Christopher Wittlinger", email = "c.wittlinger@stainly.com"}]
5
+ dynamic = ["version"]
6
+
7
+ dependencies = [
8
+ "wbcore",
9
+ ]
10
+
11
+ [tool.uv.sources]
12
+ wbcore = { workspace = true }
13
+
14
+ [tool.uv]
15
+ package = true
16
+
17
+ [tool.hatch.version]
18
+ path = "../../pyproject.toml"
19
+
20
+ [tool.hatch.build.targets.sdist]
21
+ include = ["wbwiki/*"]
22
+
23
+ [tool.hatch.build.targets.wheel]
24
+ packages = ["wbwiki"]
25
+ only-packages = true
26
+
27
+ [build-system]
28
+ requires = ["hatchling"]
29
+ build-backend = "hatchling.build"
@@ -0,0 +1 @@
1
+ __version__ = "1.0.0"
@@ -0,0 +1,18 @@
1
+ from django.contrib import admin
2
+ from wbwiki.models import WikiArticle, WikiArticleRelationship
3
+
4
+
5
+ class WikiArticleRelationshipInline(admin.TabularInline):
6
+ model = WikiArticleRelationship
7
+ extra = 0
8
+ raw_id_fields = ["content_type", "wiki"]
9
+
10
+
11
+ @admin.register(WikiArticleRelationship)
12
+ class WikiArticleRelationshipAdmin(admin.ModelAdmin):
13
+ pass
14
+
15
+
16
+ @admin.register(WikiArticle)
17
+ class WikiArticleModelAdmin(admin.ModelAdmin):
18
+ inlines = [WikiArticleRelationshipInline]
@@ -0,0 +1,5 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class WbwikiConfig(AppConfig):
5
+ name = "wbwiki"
@@ -0,0 +1,16 @@
1
+ from django.contrib.contenttypes.models import ContentType
2
+ from django.utils.translation import gettext as _
3
+ from dynamic_preferences.preferences import Section
4
+ from dynamic_preferences.registries import global_preferences_registry
5
+ from dynamic_preferences.types import ModelMultipleChoicePreference
6
+
7
+ mailing_section = Section("wbwiki")
8
+
9
+
10
+ @global_preferences_registry.register
11
+ class AllowedTypeWikiRelationshipPreference(ModelMultipleChoicePreference):
12
+ section = mailing_section
13
+ name = "allowed_type_wiki_relationship"
14
+ queryset = ContentType.objects.all()
15
+ default = []
16
+ verbose_name = _("Allowed Type Wiki Relationship")
@@ -0,0 +1,30 @@
1
+ import factory
2
+ from django.contrib.contenttypes.models import ContentType
3
+ from wbwiki.models import WikiArticle, WikiArticleRelationship
4
+
5
+
6
+ class WikiArticleFactory(factory.django.DjangoModelFactory):
7
+ class Meta:
8
+ model = WikiArticle
9
+
10
+ title = factory.Faker("pystr")
11
+ summary = factory.Faker("pystr")
12
+ content = factory.Faker("pystr")
13
+
14
+
15
+ class AbstractPublicationFactory(factory.django.DjangoModelFactory):
16
+ object_id = factory.SelfAttribute("content_object.id")
17
+ content_type = factory.LazyAttribute(lambda o: ContentType.objects.get_for_model(o.content_object))
18
+
19
+ class Meta:
20
+ exclude = ["content_object"]
21
+ abstract = True
22
+
23
+
24
+ class WikiArticleRelationshipFactory(AbstractPublicationFactory):
25
+ class Meta:
26
+ model = WikiArticleRelationship
27
+
28
+ wiki = factory.SubFactory(WikiArticleFactory)
29
+ content_object = factory.SubFactory("wbcore.contrib.authentication.factories.AuthenticatedPersonFactory")
30
+ # content_object = factory.SubFactory(WikiArticleFactory)
@@ -0,0 +1,33 @@
1
+ # Generated by Django 4.1.8 on 2023-04-19 05:27
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ initial = True
8
+
9
+ dependencies = [
10
+ ("tags", "0001_initial"),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.CreateModel(
15
+ name="WikiArticle",
16
+ fields=[
17
+ ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
18
+ ("tag_detail_endpoint", models.CharField(blank=True, max_length=255, null=True)),
19
+ ("tag_representation", models.CharField(blank=True, max_length=512, null=True)),
20
+ ("title", models.CharField(max_length=1024)),
21
+ ("content", models.TextField(default="")),
22
+ ("summary", models.TextField(default="")),
23
+ (
24
+ "tags",
25
+ models.ManyToManyField(blank=True, related_name="%(app_label)s_%(class)s_items", to="tags.tag"),
26
+ ),
27
+ ],
28
+ options={
29
+ "verbose_name": "Wiki Article",
30
+ "verbose_name_plural": "Wiki Articles",
31
+ },
32
+ ),
33
+ ]
@@ -0,0 +1,52 @@
1
+ # Generated by Django 4.1.9 on 2023-07-11 10:27
2
+
3
+ import django.db.models.deletion
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+ dependencies = [
9
+ ("contenttypes", "0002_remove_content_type_name"),
10
+ ("wbwiki", "0001_initial_squashed_0004_alter_wikiarticle_tags"),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.CreateModel(
15
+ name="WikiArticleRelationship",
16
+ fields=[
17
+ ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
18
+ ("computed_str", models.CharField(blank=True, max_length=512, null=True, verbose_name="Name")),
19
+ ("object_id", models.PositiveIntegerField(verbose_name="Related Item")),
20
+ (
21
+ "content_type",
22
+ models.ForeignKey(
23
+ on_delete=django.db.models.deletion.CASCADE,
24
+ to="contenttypes.contenttype",
25
+ verbose_name="Item Type",
26
+ ),
27
+ ),
28
+ (
29
+ "wiki",
30
+ models.ForeignKey(
31
+ on_delete=django.db.models.deletion.CASCADE,
32
+ related_name="relationships",
33
+ to="wbwiki.wikiarticle",
34
+ ),
35
+ ),
36
+ ],
37
+ options={
38
+ "verbose_name": "Wiki Relationship",
39
+ "verbose_name_plural": "Wiki Relationships",
40
+ },
41
+ ),
42
+ migrations.AddIndex(
43
+ model_name="wikiarticlerelationship",
44
+ index=models.Index(fields=["content_type", "object_id"], name="wbwiki_wiki_content_9ba127_idx"),
45
+ ),
46
+ migrations.AddConstraint(
47
+ model_name="wikiarticlerelationship",
48
+ constraint=models.UniqueConstraint(
49
+ fields=("wiki", "content_type", "object_id"), name="unique_wiki_article_relationship"
50
+ ),
51
+ ),
52
+ ]
@@ -0,0 +1,43 @@
1
+ # Generated by Django 4.2.8 on 2024-01-03 08:54
2
+
3
+ from contextlib import suppress
4
+
5
+ from django.apps import apps
6
+ from django.contrib.contenttypes.models import ContentType
7
+ from django.db import migrations
8
+
9
+
10
+ def migrate_content_type(apps, schema_editor):
11
+ WikiArticleRelationship = apps.get_model("wbwiki", "WikiArticleRelationship")
12
+
13
+ for model_name in [
14
+ "Classification",
15
+ "Instrument",
16
+ "ClassificationGroup",
17
+ "Deal",
18
+ "Exchange",
19
+ "InstrumentClassificationRelatedInstrument",
20
+ "InstrumentClassificationThroughModel",
21
+ "InstrumentFavoriteGroup",
22
+ "InstrumentList",
23
+ "InstrumentListThroughModel",
24
+ "InstrumentRequest",
25
+ "RelatedInstrumentThroughModel",
26
+ "InstrumentPrice",
27
+ ]:
28
+ with suppress(ContentType.DoesNotExist):
29
+ old_ct = ContentType.objects.get(app_label="wbportfolio", model=model_name.lower())
30
+ new_ct = ContentType.objects.get(app_label="wbfdm", model=model_name.lower())
31
+ WikiArticleRelationship.objects.filter(content_type_id=old_ct.id).update(content_type_id=new_ct.id)
32
+
33
+
34
+ class Migration(migrations.Migration):
35
+ dependencies = (
36
+ [
37
+ ("wbwiki", "0005_wikiarticlerelationship_and_more"),
38
+ ("wbfdm", "0012_instrumentprice_created_instrumentprice_modified"),
39
+ ]
40
+ if apps.is_installed("wbfdm")
41
+ else [("wbwiki", "0005_wikiarticlerelationship_and_more")]
42
+ )
43
+ operations = [migrations.RunPython(migrate_content_type)]
File without changes
@@ -0,0 +1,76 @@
1
+ import reversion
2
+ from django.contrib.contenttypes.fields import GenericForeignKey
3
+ from django.contrib.contenttypes.models import ContentType
4
+ from django.db import models
5
+ from django.utils.translation import gettext_lazy as _
6
+ from rest_framework.reverse import reverse
7
+ from wbcore.contrib.tags.models import TagModelMixin
8
+ from wbcore.models import WBModel
9
+ from wbcore.utils.models import ComplexToStringMixin
10
+
11
+
12
+ @reversion.register()
13
+ class WikiArticle(TagModelMixin, WBModel):
14
+ title = models.CharField(max_length=1024)
15
+ summary = models.TextField(default="")
16
+ content = models.TextField(default="")
17
+
18
+ def __str__(self) -> str:
19
+ return f"{self.title}"
20
+
21
+ @classmethod
22
+ def get_endpoint_basename(cls):
23
+ return "wbwiki:wikiarticle"
24
+
25
+ @classmethod
26
+ def get_representation_endpoint(cls):
27
+ return "wbwiki:wikiarticle-list"
28
+
29
+ @classmethod
30
+ def get_representation_value_key(cls):
31
+ return "id"
32
+
33
+ @classmethod
34
+ def get_representation_label_key(cls):
35
+ return "{{ title }}"
36
+
37
+ def get_tag_detail_endpoint(self):
38
+ return reverse("wbwiki:wikiarticle-detail", [self.id])
39
+
40
+ def get_tag_representation(self):
41
+ return self.title
42
+
43
+ class Meta:
44
+ verbose_name = "Wiki Article"
45
+ verbose_name_plural = "Wiki Articles"
46
+
47
+
48
+ class WikiArticleRelationship(ComplexToStringMixin, models.Model):
49
+ wiki = models.ForeignKey(to="wbwiki.WikiArticle", related_name="relationships", on_delete=models.CASCADE)
50
+ content_type = models.ForeignKey(ContentType, verbose_name=_("Item Type"), on_delete=models.CASCADE)
51
+ object_id = models.PositiveIntegerField(verbose_name=_("Related Item"))
52
+ content_object = GenericForeignKey("content_type", "object_id")
53
+
54
+ def __str__(self) -> str:
55
+ return f"{self.wiki} -> {self.content_object}"
56
+
57
+ def compute_str(self) -> str:
58
+ return f"{self.content_object}"
59
+
60
+ class Meta:
61
+ verbose_name = _("Wiki Relationship")
62
+ verbose_name_plural = _("Wiki Relationships")
63
+ constraints = [
64
+ models.UniqueConstraint(
65
+ name="unique_wiki_article_relationship", fields=["wiki", "content_type", "object_id"]
66
+ ),
67
+ ]
68
+ indexes = [models.Index(fields=["content_type", "object_id"])]
69
+
70
+ @classmethod
71
+ def get_representation_value_key(cls):
72
+ return "id"
73
+
74
+ @classmethod
75
+ def get_representation_label_key(cls):
76
+ return "{{wiki}} -> {{computed_str}}"
@@ -0,0 +1,10 @@
1
+ from django.contrib.contenttypes.models import ContentType
2
+ from django.db.utils import ProgrammingError
3
+ from dynamic_preferences.registries import global_preferences_registry
4
+
5
+
6
+ def get_allowed_type_wiki_relationship():
7
+ try:
8
+ return global_preferences_registry.manager()["wbwiki__allowed_type_wiki_relationship"]
9
+ except (RuntimeError, ProgrammingError):
10
+ return ContentType.objects.none()
@@ -0,0 +1,91 @@
1
+ from django.utils.translation import gettext as _
2
+ from rest_framework import serializers as rf_serializers
3
+ from rest_framework.reverse import reverse
4
+ from wbcore import serializers
5
+ from wbcore.content_type.serializers import (
6
+ ContentTypeRepresentationSerializer,
7
+ DynamicObjectIDRepresentationSerializer,
8
+ )
9
+ from wbcore.contrib.tags.serializers import TagSerializerMixin
10
+ from wbwiki.models import WikiArticle, WikiArticleRelationship
11
+
12
+ from .preferences import get_allowed_type_wiki_relationship
13
+
14
+
15
+ class WikiArticleRepresentationSerializer(serializers.RepresentationSerializer):
16
+ class Meta:
17
+ model = WikiArticle
18
+ fields = ("id", "title")
19
+
20
+
21
+ class WikiArticleModelSerializer(TagSerializerMixin, serializers.ModelSerializer):
22
+ @serializers.register_only_instance_resource()
23
+ def relationship_resources(self, instance, request, user, **kwargs):
24
+ resources = {"relationship": reverse("wiki:wiki-relationship-list", args=[instance.id], request=request)}
25
+ return resources
26
+
27
+ class Meta:
28
+ model = WikiArticle
29
+ fields = (
30
+ "id",
31
+ "title",
32
+ "summary",
33
+ "content",
34
+ "tags",
35
+ "_tags",
36
+ "_additional_resources",
37
+ )
38
+
39
+
40
+ class WikiArticleRelationshipModelSerializer(serializers.ModelSerializer):
41
+ _wiki = WikiArticleRepresentationSerializer(source="wiki")
42
+ _content_type = ContentTypeRepresentationSerializer(
43
+ source="content_type",
44
+ label_key="{{model_title}}",
45
+ allowed_types=get_allowed_type_wiki_relationship(),
46
+ )
47
+ _object_id = DynamicObjectIDRepresentationSerializer(
48
+ source="object_id",
49
+ optional_get_parameters={"content_type": "content_type"},
50
+ depends_on=[{"field": "content_type", "options": {}}],
51
+ )
52
+
53
+ @serializers.register_only_instance_resource()
54
+ def additional_resources(self, instance, request, user, view, **kwargs):
55
+ if instance and hasattr(instance.content_object, "get_endpoint_basename"):
56
+ return {
57
+ "object_endpoint": reverse(
58
+ f"{instance.content_object.get_endpoint_basename()}-detail",
59
+ args=[instance.object_id],
60
+ request=request,
61
+ )
62
+ }
63
+
64
+ class Meta:
65
+ model = WikiArticleRelationship
66
+ dependency_map = {"object_id": ["content_type"]}
67
+ fields = (
68
+ "id",
69
+ "content_type",
70
+ "_content_type",
71
+ "object_id",
72
+ "_object_id",
73
+ "wiki",
74
+ "_wiki",
75
+ "computed_str",
76
+ "_additional_resources",
77
+ )
78
+
79
+ def validate(self, validated_data):
80
+ content_type = validated_data.get("content_type", self.instance.content_type if self.instance else None)
81
+ wiki = validated_data.get("wiki", self.instance.wiki if self.instance else None)
82
+ object_id = validated_data.get("object_id", self.instance.object_id if self.instance else None)
83
+
84
+ if content_type and wiki and object_id:
85
+ qs = WikiArticleRelationship.objects.filter(wiki=wiki, content_type=content_type, object_id=object_id)
86
+ qs = qs.exclude(id=self.instance.id) if self.instance else qs
87
+ if qs.exists():
88
+ raise rf_serializers.ValidationError(
89
+ {"object_id": _("Relationship with wiki: {} already exists").format(wiki)}
90
+ )
91
+ return super().validate(validated_data)
File without changes
@@ -0,0 +1,13 @@
1
+ from django.apps import apps
2
+ from django.db import connection
3
+ from django.db.models.signals import pre_migrate
4
+ from pytest_factoryboy import register
5
+ from wbcore.contrib.geography.tests.signals import app_pre_migration
6
+ from wbwiki.factories import WikiArticleFactory, WikiArticleRelationshipFactory
7
+
8
+ register(WikiArticleFactory)
9
+ register(WikiArticleRelationshipFactory)
10
+
11
+ from .signals import *
12
+
13
+ pre_migrate.connect(app_pre_migration, sender=apps.get_app_config("wbwiki"))
@@ -0,0 +1,13 @@
1
+ from django.contrib.contenttypes.models import ContentType
2
+ from django.dispatch import receiver
3
+ from wbcore.test.signals import custom_update_kwargs
4
+ from wbwiki.factories import WikiArticleRelationshipFactory
5
+ from wbwiki.viewsets import ContentObjectWikiArticleModelViewSet
6
+
7
+
8
+ @receiver(custom_update_kwargs, sender=ContentObjectWikiArticleModelViewSet)
9
+ def receive_kwargs_content_object(sender, *args, **kwargs):
10
+ if wiki := kwargs.get("obj_factory"):
11
+ WikiArticleRelationshipFactory(wiki=wiki, content_object=wiki)
12
+ return {"content_type": ContentType.objects.get_for_model(wiki).id, "content_id": wiki.id}
13
+ return {}
@@ -0,0 +1,2 @@
1
+ def test_initial():
2
+ assert True
@@ -0,0 +1,17 @@
1
+ import pytest
2
+ from wbcore.test import GenerateTest, default_config
3
+
4
+ config = {}
5
+ for key, value in default_config.items():
6
+ config[key] = list(
7
+ filter(
8
+ lambda x: x.__module__.startswith("wbwiki") and x.__name__ not in ["WikiArticleRelationshipModelViewSet"],
9
+ value,
10
+ )
11
+ )
12
+
13
+
14
+ @pytest.mark.django_db
15
+ @GenerateTest(config)
16
+ class TestProject:
17
+ pass
@@ -0,0 +1,26 @@
1
+ from django.urls import include, path
2
+ from wbcore.routers import WBCoreRouter
3
+ from wbwiki import viewsets
4
+
5
+ router = WBCoreRouter()
6
+ router.register(r"wikiarticle", viewsets.WikiArticleModelViewSet, basename="wikiarticle")
7
+
8
+ wiki_router = WBCoreRouter()
9
+ wiki_router.register(
10
+ r"relationship",
11
+ viewsets.WikiArticleRelationshipModelViewSet,
12
+ basename="wiki-relationship",
13
+ )
14
+
15
+ content_object_wiki_router = WBCoreRouter()
16
+ content_object_wiki_router.register(
17
+ r"contentobjectwiki",
18
+ viewsets.ContentObjectWikiArticleModelViewSet,
19
+ basename="contentobjectwiki",
20
+ )
21
+
22
+ urlpatterns = [
23
+ path("", include(router.urls)),
24
+ path("wiki/<wiki_id>/", include(wiki_router.urls)),
25
+ path("contentobjectwiki/<int:content_type>/<int:content_id>/", include(content_object_wiki_router.urls)),
26
+ ]
@@ -0,0 +1,5 @@
1
+ from .wikis import (
2
+ ContentObjectWikiArticleModelViewSet,
3
+ WikiArticleModelViewSet,
4
+ WikiArticleRelationshipModelViewSet,
5
+ )
@@ -0,0 +1,4 @@
1
+ from .wikis import (
2
+ ContentObjectWikiArticleButtonConfig,
3
+ WikiArticleRelationshipButtonConfig,
4
+ )
@@ -0,0 +1,51 @@
1
+ from django.utils.translation import gettext as _
2
+ from rest_framework.reverse import reverse
3
+ from wbcore import serializers as wb_serializers
4
+ from wbcore.contrib.icons import WBIcon
5
+ from wbcore.enums import RequestType
6
+ from wbcore.metadata.configs import buttons as bt
7
+ from wbcore.metadata.configs.buttons.view_config import ButtonViewConfig
8
+ from wbcore.metadata.configs.display.instance_display.shortcuts import (
9
+ create_simple_display,
10
+ )
11
+ from wbwiki.models import WikiArticle
12
+ from wbwiki.serializers import WikiArticleRepresentationSerializer
13
+
14
+
15
+ class WikiArticleRelationshipButtonConfig(ButtonViewConfig):
16
+ def get_custom_instance_buttons(self):
17
+ return self.get_custom_list_instance_buttons()
18
+
19
+ def get_custom_list_instance_buttons(self):
20
+ return {
21
+ bt.WidgetButton(key="object_endpoint", label="{{computed_str}}", icon=WBIcon.CLIPBOARD.icon),
22
+ }
23
+
24
+
25
+ class ContentObjectWikiArticleButtonConfig(ButtonViewConfig):
26
+ def get_custom_buttons(self):
27
+ if not self.view.kwargs.get("pk", None):
28
+
29
+ class WikiArticleSerializer(wb_serializers.Serializer):
30
+ wiki = wb_serializers.PrimaryKeyRelatedField(
31
+ queryset=WikiArticle.objects.all(),
32
+ required=True,
33
+ label=_("Wiki"),
34
+ )
35
+ _wiki = WikiArticleRepresentationSerializer(source="wiki")
36
+
37
+ return {
38
+ bt.ActionButton(
39
+ identifiers=("wbwiki:wikiarticle",),
40
+ method=RequestType.PATCH,
41
+ endpoint=f"{reverse('wbwiki:wikiarticle-linkwiki', args=[], request=self.request)}?content_type={self.view.kwargs['content_type']}&content_id={self.view.kwargs['content_id']}",
42
+ action_label=_("Add Existing Wiki"),
43
+ title=_("Add Existing Wiki"),
44
+ label=_("Add Existing Wiki"),
45
+ icon=WBIcon.LINK.icon,
46
+ serializer=WikiArticleSerializer,
47
+ description_fields="<p>Link item to Existing Wiki</p><p>Are you sure you want to proceed?</p>",
48
+ instance_display=create_simple_display([["wiki"]]),
49
+ )
50
+ }
51
+ return {}
@@ -0,0 +1 @@
1
+ from .wikis import WikiArticleDisplayConfig, WikiArticleRelationshipDisplayConfig
@@ -0,0 +1,75 @@
1
+ from django.utils.translation import gettext as _
2
+ from wbcore.metadata.configs import display as dp
3
+ from wbcore.metadata.configs.display.instance_display import (
4
+ Display,
5
+ Inline,
6
+ Layout,
7
+ Page,
8
+ Style,
9
+ create_simple_display,
10
+ )
11
+ from wbcore.metadata.configs.display.instance_display.operators import default
12
+ from wbcore.metadata.configs.display.view_config import DisplayViewConfig
13
+
14
+
15
+ class WikiArticleRelationshipDisplayConfig(DisplayViewConfig):
16
+ def get_list_display(self) -> dp.ListDisplay:
17
+ return dp.ListDisplay(
18
+ fields=[
19
+ dp.Field(key="content_type", label="Item Type"),
20
+ dp.Field(key="computed_str", label="Related Item"),
21
+ ]
22
+ )
23
+
24
+ def get_instance_display(self) -> Display:
25
+ return create_simple_display([["content_type", "object_id"]])
26
+
27
+
28
+ class WikiArticleDisplayConfig(DisplayViewConfig):
29
+ def get_list_display(self) -> dp.ListDisplay:
30
+ return dp.ListDisplay(
31
+ fields=[
32
+ dp.Field(key="title", label="Title"),
33
+ dp.Field(key="summary", label="Summary"),
34
+ dp.Field(key="tags", label="Tags"),
35
+ ]
36
+ )
37
+
38
+ def _get_custom_instance_display(self) -> Display:
39
+ return Display(
40
+ pages=[
41
+ Page(
42
+ title=_("Main Information"),
43
+ layouts={
44
+ default(): Layout(
45
+ grid_template_areas=[["title"], ["summary"], ["tags"], ["content"]],
46
+ grid_template_columns=[
47
+ "minmax(min-content, 1fr)",
48
+ ],
49
+ ),
50
+ },
51
+ ),
52
+ Page(
53
+ title=_("Relationship"),
54
+ layouts={
55
+ default(): Layout(
56
+ grid_template_areas=[["relationship_key"]],
57
+ inlines=[Inline(key="relationship_key", endpoint="relationship")],
58
+ grid_template_columns=[
59
+ "minmax(min-content, 1fr)",
60
+ ],
61
+ grid_auto_rows=Style.MIN_CONTENT,
62
+ ),
63
+ },
64
+ ),
65
+ ]
66
+ )
67
+
68
+ def get_instance_display(self) -> Display:
69
+ display = (
70
+ self._get_custom_instance_display()
71
+ if "pk" in self.view.kwargs
72
+ else create_simple_display([["title", "summary", "tags", "content"]])
73
+ )
74
+
75
+ return display
@@ -0,0 +1,4 @@
1
+ from .wikis import (
2
+ ContentObjectWikiArticleEndpointConfig,
3
+ WikiArticleRelationshipEndpointConfig,
4
+ )
@@ -0,0 +1,20 @@
1
+ from rest_framework.reverse import reverse
2
+ from wbcore.metadata.configs.endpoints import EndpointViewConfig
3
+
4
+
5
+ class WikiArticleRelationshipEndpointConfig(EndpointViewConfig):
6
+ def get_endpoint(self, **kwargs):
7
+ return reverse(
8
+ "wiki:wiki-relationship-list",
9
+ args=[self.view.kwargs["wiki_id"]],
10
+ request=self.request,
11
+ )
12
+
13
+
14
+ class ContentObjectWikiArticleEndpointConfig(EndpointViewConfig):
15
+ def get_endpoint(self, **kwargs):
16
+ return reverse(
17
+ "wiki:contentobjectwiki-list",
18
+ args=[self.view.kwargs["content_type"], self.view.kwargs["content_id"]],
19
+ request=self.request,
20
+ )
@@ -0,0 +1 @@
1
+ from .wikis import WIKI_MENU, WIKI_MENU_ITEM
@@ -0,0 +1,21 @@
1
+ from wbcore.menus import ItemPermission, Menu, MenuItem
2
+
3
+ WIKI_MENU_ITEM = MenuItem(
4
+ label="WIKI",
5
+ endpoint="wbwiki:wikiarticle-list",
6
+ add=MenuItem(
7
+ label="Create a wiki article",
8
+ endpoint="wbwiki:wikiarticle-list",
9
+ permission=ItemPermission(
10
+ permissions=["wbwiki.create_wikiarticle"],
11
+ ),
12
+ ),
13
+ permission=ItemPermission(
14
+ permissions=["wbwiki.view_wikiarticle"],
15
+ ),
16
+ )
17
+
18
+ WIKI_MENU = Menu(
19
+ label="WIKI",
20
+ items=[WIKI_MENU_ITEM],
21
+ )
File without changes
File without changes
@@ -0,0 +1,107 @@
1
+ from django.utils.translation import gettext_lazy as _
2
+ from rest_framework import status
3
+ from rest_framework.decorators import action
4
+ from rest_framework.response import Response
5
+ from reversion.views import RevisionMixin
6
+ from wbcore import viewsets
7
+ from wbcore.contrib.authentication.authentication import JWTCookieAuthentication
8
+ from wbwiki.models import WikiArticle, WikiArticleRelationship
9
+ from wbwiki.serializers import (
10
+ WikiArticleModelSerializer,
11
+ WikiArticleRelationshipModelSerializer,
12
+ )
13
+
14
+ from .buttons import (
15
+ ContentObjectWikiArticleButtonConfig,
16
+ WikiArticleRelationshipButtonConfig,
17
+ )
18
+ from .display import WikiArticleDisplayConfig, WikiArticleRelationshipDisplayConfig
19
+ from .endpoints import (
20
+ ContentObjectWikiArticleEndpointConfig,
21
+ WikiArticleRelationshipEndpointConfig,
22
+ )
23
+
24
+
25
+ class WikiArticleModelViewSet(RevisionMixin, viewsets.ModelViewSet):
26
+ queryset = WikiArticle.objects.all()
27
+ serializer_class = WikiArticleModelSerializer
28
+ # search_fields = ("title", "summary", "content", "tags__title")
29
+ filterset_fields = {
30
+ "title": ["icontains"],
31
+ "summary": ["icontains"],
32
+ "tags": ["exact"],
33
+ }
34
+
35
+ display_config_class = WikiArticleDisplayConfig
36
+
37
+ @action(detail=False, methods=["PATCH"], authentication_classes=[JWTCookieAuthentication])
38
+ def linkwiki(self, request, pk=None):
39
+ content_type_id = request.GET.get("content_type")
40
+ object_id = request.GET.get("content_id")
41
+ wiki_id = request.POST.get("wiki")
42
+
43
+ if content_type_id and object_id:
44
+ if wiki_id:
45
+ wiki_relationship, created = WikiArticleRelationship.objects.get_or_create(
46
+ wiki_id=wiki_id,
47
+ content_type_id=content_type_id,
48
+ object_id=object_id,
49
+ )
50
+ if created:
51
+ message = _("Relationship Item has been added to wiki")
52
+ _status = status.HTTP_200_OK
53
+ else:
54
+ message = _("Wiki has already been linked to this Item")
55
+ _status = status.HTTP_400_BAD_REQUEST
56
+ else:
57
+ message = _("Wiki is mandatory")
58
+ _status = status.HTTP_400_BAD_REQUEST
59
+ else:
60
+ message = _("Relationship Item could not be linked to wiki")
61
+ _status = status.HTTP_400_BAD_REQUEST
62
+
63
+ return Response(
64
+ {"__notification": {"title": message}},
65
+ status=_status,
66
+ )
67
+
68
+
69
+ class ContentObjectWikiArticleModelViewSet(WikiArticleModelViewSet):
70
+ endpoint_config_class = ContentObjectWikiArticleEndpointConfig
71
+ button_config_class = ContentObjectWikiArticleButtonConfig
72
+
73
+ def get_queryset(self):
74
+ if (content_type_id := self.kwargs.get("content_type")) and (object_id := self.kwargs.get("content_id")):
75
+ return (
76
+ super()
77
+ .get_queryset()
78
+ .filter(relationships__content_type=content_type_id, relationships__object_id=object_id)
79
+ )
80
+ return WikiArticle.objects.none()
81
+
82
+ def create(self, request, *args, **kwargs):
83
+ response = super().create(request, *args, **kwargs)
84
+
85
+ if (content_type_id := self.kwargs.get("content_type")) and (object_id := self.kwargs.get("content_id")):
86
+ WikiArticleRelationship.objects.get_or_create(
87
+ wiki_id=response.data["instance"]["id"],
88
+ content_type_id=content_type_id,
89
+ object_id=object_id,
90
+ )
91
+
92
+ return response
93
+
94
+
95
+ class WikiArticleRelationshipModelViewSet(viewsets.ModelViewSet):
96
+ serializer_class = WikiArticleRelationshipModelSerializer
97
+ display_config_class = WikiArticleRelationshipDisplayConfig
98
+ endpoint_config_class = WikiArticleRelationshipEndpointConfig
99
+ button_config_class = WikiArticleRelationshipButtonConfig
100
+ queryset = WikiArticleRelationship.objects.all()
101
+ search_fields = ("computed_str", "content_type__model")
102
+ filterset_fields = {
103
+ "computed_str": ["icontains", "exact"],
104
+ }
105
+
106
+ def get_queryset(self):
107
+ return super().get_queryset().filter(wiki__id=self.kwargs["wiki_id"])