Add first skeleton of a duplicate method

Add other base ideas for the copy mechanism for content block attached
entities like assignments and surveys

Relates to MS-651

Add test for duplicating entries, also update implementation
This commit is contained in:
Ramon Wenger 2023-03-07 23:53:53 +01:00
parent d6221e8cd5
commit 71dbfeb1f4
6 changed files with 416 additions and 162 deletions

View File

@ -9,12 +9,38 @@ from wagtail.models import Page, Site
from wagtail.rich_text import RichText
from assignments.models import Assignment
from basicknowledge.models import BasicKnowledge, INTERDISCIPLINARY, INTERDISCIPLINARY_LABEL, InstrumentCategory, \
InstrumentType, \
LANGUAGE_COMMUNICATION, LANGUAGE_COMMUNICATION_LABEL, SOCIETY, SOCIETY_LABEL
from books.blocks import AssignmentBlock, BasicKnowledgeBlock, ImageUrlBlock, LinkBlock, VideoBlock
from basicknowledge.models import (
BasicKnowledge,
INTERDISCIPLINARY,
INTERDISCIPLINARY_LABEL,
InstrumentCategory,
InstrumentType,
LANGUAGE_COMMUNICATION,
LANGUAGE_COMMUNICATION_LABEL,
SOCIETY,
SOCIETY_LABEL,
)
from books.blocks import (
AssignmentBlock,
BasicKnowledgeBlock,
ImageUrlBlock,
LinkBlock,
SurveyBlock,
VideoBlock,
)
from books.models import Book, Chapter, ContentBlock, Module, TextBlock, Topic
from core.factories import BasePageFactory, DummyImageFactory, fake, fake_paragraph, fake_title
from core.factories import (
BasePageFactory,
DummyImageFactory,
fake,
fake_paragraph,
fake_title,
)
from core.logger import get_logger
from surveys.factories import SurveyFactory
from surveys.models import Survey
logger = get_logger(__name__)
class BookFactory(BasePageFactory):
@ -24,18 +50,25 @@ class BookFactory(BasePageFactory):
@staticmethod
def create_default_structure():
site = wagtail_factories.SiteFactory.create(is_default_site=True)
Page.objects.get(title='Root').delete()
Page.objects.get(title="Root").delete()
book = BookFactory.create(parent=site.root_page, title='A book')
topic = TopicFactory.create(parent=book, order=1, title='A topic')
module = ModuleFactory.create(parent=topic,
title="A module",
meta_title="Modul 1",
teaser="Whatever",
intro="<p>Hello</p>")
book = BookFactory.create(parent=site.root_page, title="A book")
topic = TopicFactory.create(parent=book, order=1, title="A topic")
module = ModuleFactory.create(
parent=topic,
title="A module",
meta_title="Modul 1",
teaser="Whatever",
intro="<p>Hello</p>",
)
chapter = ChapterFactory.create(parent=module, title="A chapter")
content_block = ContentBlockFactory.create(parent=chapter, module=module, title="A content block", type="task",
contents=[])
content_block = ContentBlockFactory.create(
parent=chapter,
module=module,
title="A content block",
type="task",
contents=[],
)
return book, topic, module, chapter, content_block
@ -45,7 +78,9 @@ class TopicFactory(BasePageFactory):
model = Topic
order = 0
teaser = factory.LazyAttribute(lambda x: fake.sentence(nb_words=random.randint(8, 12)))
teaser = factory.LazyAttribute(
lambda x: fake.sentence(nb_words=random.randint(8, 12))
)
description = factory.LazyAttribute(lambda x: fake.text(max_nb_chars=200))
@ -54,7 +89,9 @@ class ModuleFactory(BasePageFactory):
model = Module
meta_title = factory.LazyAttribute(lambda x: fake.text(max_nb_chars=20))
teaser = factory.LazyAttribute(lambda x: fake.sentence(nb_words=random.randint(8, 12)))
teaser = factory.LazyAttribute(
lambda x: fake.sentence(nb_words=random.randint(8, 12))
)
intro = factory.LazyAttribute(lambda x: fake.text(max_nb_chars=200))
hero_image = factory.SubFactory(DummyImageFactory)
@ -75,11 +112,14 @@ class TextBlockFactory(wagtail_factories.StructBlockFactory):
class InstrumentCategoryFactory(factory.DjangoModelFactory):
class Meta:
model = InstrumentCategory
django_get_or_create = ('name',)
django_get_or_create = ("name",)
name = factory.Iterator(
[LANGUAGE_COMMUNICATION_LABEL, SOCIETY_LABEL, INTERDISCIPLINARY_LABEL]
)
foreground = factory.Iterator(["FF0000", "FFFFFF", "000000"])
background = factory.Iterator(["FF0000", "FFFFFF", "000000"])
name = factory.Iterator([LANGUAGE_COMMUNICATION_LABEL, SOCIETY_LABEL, INTERDISCIPLINARY_LABEL])
foreground = factory.Iterator(['FF0000', 'FFFFFF', '000000'])
background = factory.Iterator(['FF0000', 'FFFFFF', '000000'])
class InstrumentTypeFactory(factory.DjangoModelFactory):
class Meta:
@ -99,7 +139,7 @@ class InstrumentFactory(BasePageFactory):
@classmethod
def _create(cls, model_class, *args, **kwargs):
kwargs['parent'] = Site.objects.get(is_default_site=True).root_page
kwargs["parent"] = Site.objects.get(is_default_site=True).root_page
return super()._create(model_class, *args, **kwargs)
@ -113,7 +153,7 @@ class BasicKnowledgeBlockFactory(wagtail_factories.StructBlockFactory):
class ImageUrlBlockFactory(wagtail_factories.StructBlockFactory):
title = fake_title()
url = factory.LazyAttribute(lambda x: 'https://picsum.photos/600/400/?random')
url = factory.LazyAttribute(lambda x: "https://picsum.photos/600/400/?random")
class Meta:
model = ImageUrlBlock
@ -121,127 +161,202 @@ class ImageUrlBlockFactory(wagtail_factories.StructBlockFactory):
class LinkBlockFactory(wagtail_factories.StructBlockFactory):
text = fake_title()
url = factory.LazyAttribute(lambda x: 'https://picsum.photos/600/400/?random')
url = factory.LazyAttribute(lambda x: "https://picsum.photos/600/400/?random")
class Meta:
model = LinkBlock
class AssignmentBlockFactory(wagtail_factories.StructBlockFactory):
class Meta:
model = AssignmentBlock
class EntityBlockFactory(wagtail_factories.StructBlockFactory):
@classmethod
def _build(cls, model_class, *args, **kwargs):
block = model_class()
return blocks.StructValue(
block,
# todo: build in a more generic fashion
[
(name, kwargs['assignment']) for name, child_block in block.child_blocks.items()
],
)
logger.debug(cls.id_key)
logger.debug(cls.entity_key)
logger.debug(kwargs)
value = block.to_python({cls.id_key: kwargs.get(cls.entity_key).id})
clean_value = block.clean(value)
return clean_value
class AssignmentBlockFactory(EntityBlockFactory):
class Meta:
model = AssignmentBlock
id_key = "assignment_id"
entity_key = "assignment"
class SurveyBlockFactory(EntityBlockFactory):
class Meta:
model = SurveyBlock
id_key = "survey_id"
entity_key = "survey"
class VideoBlockFactory(wagtail_factories.StructBlockFactory):
url = factory.LazyAttribute(lambda x: 'https://www.youtube.com/watch?v=lO9d-AJai8Q')
url = factory.LazyAttribute(lambda x: "https://www.youtube.com/watch?v=lO9d-AJai8Q")
class Meta:
model = VideoBlock
block_types = ['text_block', 'basic_knowledge', 'student_entry', 'image_url_block', 'solution']
block_types = [
"text_block",
"basic_knowledge",
"student_entry",
"image_url_block",
"solution",
]
class ContentBlockFactory(BasePageFactory):
class Meta:
model = ContentBlock
type = factory.LazyAttribute(lambda x: random.choice(['normal', 'instrument', 'task',]))
type = factory.LazyAttribute(
lambda x: random.choice(
[
"normal",
"instrument",
"task",
]
)
)
contents = wagtail_factories.StreamFieldFactory({
'text_block': TextBlockFactory,
'basic_knowledge': BasicKnowledgeBlockFactory,
'assignment': AssignmentBlockFactory,
'image_block': wagtail_factories.ImageChooserBlockFactory,
'image_url_block': ImageUrlBlockFactory,
'link_block': LinkBlockFactory,
'video_block': VideoBlockFactory,
'solution': TextBlockFactory
})
contents = wagtail_factories.StreamFieldFactory(
{
"text_block": TextBlockFactory,
"basic_knowledge": BasicKnowledgeBlockFactory,
"assignment": AssignmentBlockFactory,
"image_block": wagtail_factories.ImageChooserBlockFactory,
"image_url_block": ImageUrlBlockFactory,
"link_block": LinkBlockFactory,
"video_block": VideoBlockFactory,
"solution": TextBlockFactory,
"survey": SurveyBlockFactory,
}
)
@classmethod
def stream_field_magic(cls, module, kwargs, stream_field_name):
if stream_field_name in kwargs:
"""
"""
stream_field_name is most likely 'contents'
this means: if there is a property named contents, use the defined ones in this block.
otherwise, go into the other block and randomize the contents
"""
for idx, resource in enumerate(kwargs[stream_field_name]):
value = resource['value']
block_type = resource['type']
value = resource["value"]
block_type = resource["type"]
if block_type == 'assignment':
if block_type == "assignment":
user = get_user_model().objects.first()
assignment = Assignment.objects.create(
title=value['title'],
assignment=value['assignment'],
title=value["title"],
assignment=value["assignment"],
owner=user,
module=module
module=module,
)
kwargs['{}__{}__{}__{}'.format(stream_field_name, idx, block_type, 'assignment')] = assignment
kwargs[
"{}__{}__{}__{}".format(
stream_field_name, idx, block_type, "assignment"
)
] = assignment
elif block_type == "survey":
survey = Survey.objects.create(
title=value["title"], data=value["data"], module=module
)
kwargs[
"{}__{}__{}__{}".format(
stream_field_name, idx, block_type, "survey"
)
] = survey
else:
for jdx, field in enumerate(value):
if block_type == 'text_block':
kwargs['{}__{}__{}__{}'.format(stream_field_name, idx, block_type, field)] = RichText(
value[field])
elif block_type == 'solution':
kwargs['{}__{}__{}__{}'.format(stream_field_name, idx, block_type, field)] = RichText(
value[field])
elif block_type == 'basic_knowledge':
if field == 'description':
if block_type == "text_block":
kwargs[
"{}__{}__{}__{}".format(
stream_field_name, idx, block_type, field
)
] = RichText(value[field])
elif block_type == "solution":
kwargs[
"{}__{}__{}__{}".format(
stream_field_name, idx, block_type, field
)
] = RichText(value[field])
elif block_type == "basic_knowledge":
if field == "description":
kwargs[
'{}__{}__{}__{}'.format(stream_field_name, idx, block_type, field)] = RichText(
value[field])
"{}__{}__{}__{}".format(
stream_field_name, idx, block_type, field
)
] = RichText(value[field])
else:
kwargs[
'{}__{}__{}__{}'.format(stream_field_name, idx, block_type,
field)] = 'https://google.ch'
elif block_type == 'image_url_block':
"{}__{}__{}__{}".format(
stream_field_name, idx, block_type, field
)
] = "https://google.ch"
elif block_type == "image_url_block":
kwargs[
'{}__{}__{}__{}'.format(stream_field_name, idx, block_type, field)] = value[field]
"{}__{}__{}__{}".format(
stream_field_name, idx, block_type, field
)
] = value[field]
else:
kwargs[
'{}__{}__{}__{}'.format(stream_field_name, idx, block_type, field)] = value[field]
"{}__{}__{}__{}".format(
stream_field_name, idx, block_type, field
)
] = value[field]
del kwargs[stream_field_name]
else: # random contents from generator
for i in range(0, random.randint(3, 7)):
block_type = random.choice(block_types)
if block_type == 'text_block':
kwargs['{}__{}__{}__{}'.format(stream_field_name, i, 'text_block', 'text')] = RichText(
fake_paragraph())
elif block_type == 'basic_knowledge':
kwargs['{}__{}__{}__{}'.format(stream_field_name, i, 'basic_knowledge', 'description')] = RichText(
fake_paragraph())
elif block_type == 'assignment':
kwargs['{}__{}__{}__{}'.format(stream_field_name, i, 'assignment', 'task_text')] = RichText(
fake_paragraph())
elif block_type == 'image_url_block':
if block_type == "text_block":
kwargs[
'{}__{}__{}__{}'.format(stream_field_name, i, 'image_url_block', 'title')] = fake_paragraph()
"{}__{}__{}__{}".format(
stream_field_name, i, "text_block", "text"
)
] = RichText(fake_paragraph())
elif block_type == "basic_knowledge":
kwargs[
'{}__{}__{}__{}'.format(stream_field_name, i, 'image_url_block',
'url')] = 'https://picsum.photos/400/?random={}'.format(
''.join(random.choice('abcdefghiklmn') for _ in range(6)))
elif block_type == 'solution':
kwargs['{}__{}__{}__{}'.format(stream_field_name, i, 'solution', 'text')] = RichText(
fake_paragraph())
"{}__{}__{}__{}".format(
stream_field_name, i, "basic_knowledge", "description"
)
] = RichText(fake_paragraph())
elif block_type == "assignment":
kwargs[
"{}__{}__{}__{}".format(
stream_field_name, i, "assignment", "task_text"
)
] = RichText(fake_paragraph())
elif block_type == "image_url_block":
kwargs[
"{}__{}__{}__{}".format(
stream_field_name, i, "image_url_block", "title"
)
] = fake_paragraph()
kwargs[
"{}__{}__{}__{}".format(
stream_field_name, i, "image_url_block", "url"
)
] = "https://picsum.photos/400/?random={}".format(
"".join(random.choice("abcdefghiklmn") for _ in range(6))
)
elif block_type == "solution":
kwargs[
"{}__{}__{}__{}".format(
stream_field_name, i, "solution", "text"
)
] = RichText(fake_paragraph())
@classmethod
def create(cls, module, **kwargs):
cls.stream_field_magic(module, kwargs, 'contents')
cls.stream_field_magic(module, kwargs, "contents")
return cls._generate(CREATE_STRATEGY, kwargs)

View File

@ -2,6 +2,7 @@ import logging
from django.db import models
from wagtail.admin.panels import FieldPanel, TabbedInterface, ObjectList
from books.models.contentblock import ContentBlock
from core.wagtail_utils import StrictHierarchyPage, get_default_settings
from users.models import SchoolClass
@ -38,6 +39,9 @@ class Chapter(StrictHierarchyPage, GraphqlNodeMixin):
SchoolClass, related_name="hidden_chapter_descriptions"
)
def get_content_blocks(self):
return ContentBlock.objects.all().descendants_of(self)
def sync_title_visibility(self, school_class_template, school_class_to_sync):
if (
self.title_hidden_for.filter(id=school_class_template.id).exists()
@ -53,8 +57,7 @@ class Chapter(StrictHierarchyPage, GraphqlNodeMixin):
def sync_description_visibility(self, school_class_template, school_class_to_sync):
if (
self.description_hidden_for.filter(
id=school_class_template.id).exists()
self.description_hidden_for.filter(id=school_class_template.id).exists()
and not self.description_hidden_for.filter(
id=school_class_to_sync.id
).exists()
@ -62,8 +65,7 @@ class Chapter(StrictHierarchyPage, GraphqlNodeMixin):
self.description_hidden_for.add(school_class_to_sync)
if (
self.description_hidden_for.filter(
id=school_class_to_sync.id).exists()
self.description_hidden_for.filter(id=school_class_to_sync.id).exists()
and not self.description_hidden_for.filter(
id=school_class_template.id
).exists()

View File

@ -1,6 +1,5 @@
import logging
from django.db import models
from assignments.models import Assignment
from wagtail.admin.panels import (
FieldPanel,
TabbedInterface,
@ -11,6 +10,7 @@ from wagtail.fields import StreamField
from wagtail.images.blocks import ImageChooserBlock
from books.managers import ContentBlockManager
from core.logger import get_logger
from core.wagtail_utils import get_default_settings
from books.blocks import (
CMSDocumentBlock,
@ -30,14 +30,53 @@ from books.blocks import (
ThinglinkBlock,
InstructionBlock,
)
from books.utils import get_type_and_value
from core.wagtail_utils import StrictHierarchyPage
from notes.models import ContentBlockBookmark
from surveys.models import Survey
from users.models import SchoolClass, User
from core.mixins import GraphqlNodeMixin
logger = logging.getLogger(__name__)
logger = get_logger(__name__)
def duplicate_entities_generator(new_module):
def duplicate_entities(block):
content_type = block.block_type
value = block.value
logger.debug(block)
if content_type == "assignment":
assignment = value.get("assignment_id")
assignment.pk = None
assignment.title = f"{assignment.title} (Kopie)"
assignment.module = new_module
logger.debug(f"setting new module {new_module}, {assignment.module}")
assignment.save()
block = AssignmentBlock()
data = {"assignment_id": assignment.pk}
value = block.to_python(data)
cleaned_value = block.clean(value)
new_block = ("assignment", cleaned_value)
logger.debug(new_block)
return new_block
if content_type == "survey":
logger.debug(value)
survey = value.get("survey_id")
survey.pk = None
survey.title = f"{survey.title} (Kopie)"
survey.module = new_module
logger.debug(f"setting new module {new_module}, {survey.module}")
survey.save()
block = SurveyBlock()
data = {"survey_id": survey.pk}
value = block.to_python(data)
cleaned_value = block.clean(value)
new_block = ("survey", cleaned_value)
# logger.debug(new_block)
logger.debug(new_block)
return new_block
return block
return duplicate_entities
class ContentBlock(StrictHierarchyPage, GraphqlNodeMixin):
@ -102,8 +141,7 @@ class ContentBlock(StrictHierarchyPage, GraphqlNodeMixin):
use_json_field=True,
)
type = models.CharField(
max_length=100, choices=TYPE_CHOICES, default=NORMAL)
type = models.CharField(max_length=100, choices=TYPE_CHOICES, default=NORMAL)
content_panels = [
FieldPanel("title", classname="full title"),
@ -127,30 +165,56 @@ class ContentBlock(StrictHierarchyPage, GraphqlNodeMixin):
def module(self):
return self.get_parent().get_parent().specific
# duplicate all attached Surveys and Assignments
def duplicate_attached_entities(self):
logger.debug("starting to duplicate inside the content block")
duplicate_entities = duplicate_entities_generator(self.module)
logger.debug(f"new module: {self.module}")
iterator = map(duplicate_entities, self.contents)
logger.debug("here is the iterator")
new_contents = list(iterator)
logger.debug(new_contents)
# we can't just insert a list here, we need a StreamValue data type
# so we need to clear the list, then add each element in turn
self.contents.clear()
# like this, the internal methods of the SteamValue data type can work on the single elements
for content in new_contents:
logger.debug(content)
self.contents.append(content)
# as an illustration
# block = SolutionBlock()
# data = {'text': 'This is me'}
# value = block.to_python(data)
# clean_value = block.clean(value)
# self.contents.append(('solution', clean_value))
logger.debug("self.contents")
logger.debug(self.contents)
self.save()
def is_hidden_for_class(self, school_class):
return (
not self.user_created
and self.hidden_for.filter(id=school_class.id).exists()
not self.user_creted and self.hidden_for.filter(id=school_class.id).exists()
) or (
self.user_created
and not self.visible_for.filter(id=school_class.id).exists()
)
def save(self, *args, **kwargs):
# todo: move this to the after_create_page and after_edit_page hooks, and remove from here.
for data in self.contents.raw_data:
block_type, value = get_type_and_value(data)
# def save(self, *args, **kwargs):
# todo: move this to the after_create_page and after_edit_page hooks, and remove from here.
# for data in self.contents.raw_data:
# block_type, value = get_type_and_value(data)
# todo: also do the same for assignments
if block_type == "survey":
module = self.module
survey = value["survey_id"]
if isinstance(survey, int):
survey = Survey.objects.get(pk=survey)
if survey.module != module:
survey.module = module
survey.save()
super().save(*args, **kwargs)
# todo: also do the same for assignments
# if block_type == "survey":
# module = self.module
# survey = value["survey_id"]
# if isinstance(survey, int):
# survey = Survey.objects.get(pk=survey)
# if survey.module != module:
# survey.module = module
# survey.save()
# super().save(*args, **kwargs)
class ContentBlockSnapshot(ContentBlock):

View File

@ -0,0 +1,53 @@
from django.test import TestCase
from assignments.factories import AssignmentFactory
from assignments.models import Assignment
from books.factories import BookFactory, ContentBlockFactory
from books.models.contentblock import ContentBlock
from core.logger import get_logger
from surveys.factories import SurveyFactory
from surveys.models import Survey
from users.services import create_users
logger = get_logger(__name__)
class DuplicateContentsTestCase(TestCase):
def setUp(self) -> None:
create_users()
_, _, self.module, chapter, _ = BookFactory.create_default_structure()
text = {"type": "text_block", "value": {"text": "Hallo"}}
assignment = {
"type": "assignment",
"value": {"title": "Hello", "assignment": "Assignment"},
}
survey = {"type": "survey", "value": {"title": "Survey Title", "data": "null"}}
self.content_block = ContentBlockFactory.create(
parent=chapter,
module=self.module,
title="Another content block",
type="task",
contents=[text, assignment, survey],
)
self.assignment = Assignment.objects.first()
def test_duplicate_entities(self):
self.assertEqual(
self.content_block.contents[1].value["assignment_id"], self.assignment
)
self.assertEqual(
self.content_block.contents[1].value["assignment_id"].title, "Hello"
)
self.assertEqual(Assignment.objects.count(), 1)
self.assertEqual(Survey.objects.count(), 1)
self.content_block.duplicate_attached_entities()
self.assertEqual(Assignment.objects.count(), 2)
self.assertEqual(Survey.objects.count(), 2)
new_assignment = Assignment.objects.get(id=2)
new_survey = Survey.objects.get(id=2)
content_block = ContentBlock.objects.get(id=self.content_block.id)
self.assertEqual(len(content_block.contents), 3)
self.assertEqual(
content_block.contents[1].value["assignment_id"], new_assignment
)
self.assertEqual(content_block.contents[2].value["survey_id"], new_survey)

View File

@ -1,107 +1,125 @@
import wagtail.admin.rich_text.editors.draftail.features as draftail_features
from wagtail.admin.rich_text.converters.html_to_contentstate import InlineStyleElementHandler
from wagtail.admin.rich_text.converters.html_to_contentstate import (
InlineStyleElementHandler,
)
from wagtail import hooks
from basicknowledge.models import BasicKnowledge
from books.models import ContentBlockSnapshot
from books.models.contentblock import ContentBlock
from core.logger import get_logger
logger = get_logger(__name__)
# 1. Use the register_rich_text_features hook.
@hooks.register('register_rich_text_features')
@hooks.register("register_rich_text_features")
def register_brand_feature(features):
"""
Registering the feature, which uses the `BRAND` Draft.js inline style type,
and is stored as HTML with a `<span class="brand">` tag.
"""
feature_name = 'brand'
type_ = 'BRAND'
feature_name = "brand"
type_ = "BRAND"
# 2. Configure how Draftail handles the feature in its toolbar.
control = {
'type': type_,
'label': 'Grün',
'description': 'Grün',
'style': {
'color': '#17A887',
'font-weight': '600'
},
"type": type_,
"label": "Grün",
"description": "Grün",
"style": {"color": "#17A887", "font-weight": "600"},
}
# 3. Call register_editor_plugin to register the configuration for Draftail.
features.register_editor_plugin(
'draftail', feature_name, draftail_features.InlineStyleFeature(control)
"draftail", feature_name, draftail_features.InlineStyleFeature(control)
)
# 4.configure the content transform from the DB to the editor and back.
db_conversion = {
'from_database_format': {'span[class="brand"]': InlineStyleElementHandler(type_)},
'to_database_format': {'style_map': {type_: 'span class="brand""'}},
"from_database_format": {
'span[class="brand"]': InlineStyleElementHandler(type_)
},
"to_database_format": {"style_map": {type_: 'span class="brand""'}},
}
# 5. Call register_converter_rule to register the content transformation conversion.
features.register_converter_rule('contentstate', feature_name, db_conversion)
features.register_converter_rule("contentstate", feature_name, db_conversion)
# 6. (optional) Add the feature to the default features list to make it available
# on rich text fields that do not specify an explicit 'features' list
features.default_features.append(feature_name)
@hooks.register('register_rich_text_features')
@hooks.register("register_rich_text_features")
def register_secondary_feature(features):
"""
Registering the feature, which uses the `SECONDARY` Draft.js inline style type,
and is stored as HTML with a `<span class="secondary">` tag.
"""
feature_name = 'secondary'
type_ = 'SECONDARY'
feature_name = "secondary"
type_ = "SECONDARY"
# 2. Configure how Draftail handles the feature in its toolbar.
control = {
'type': type_,
'label': 'Blau',
'description': 'Blau',
'style': {
'color': '#078CC6',
'font-weight': '600'
},
"type": type_,
"label": "Blau",
"description": "Blau",
"style": {"color": "#078CC6", "font-weight": "600"},
}
# 3. Call register_editor_plugin to register the configuration for Draftail.
features.register_editor_plugin(
'draftail', feature_name, draftail_features.InlineStyleFeature(control)
"draftail", feature_name, draftail_features.InlineStyleFeature(control)
)
# 4.configure the content transform from the DB to the editor and back.
db_conversion = {
'from_database_format': {'span[class="secondary"]': InlineStyleElementHandler(type_)},
'to_database_format': {'style_map': {type_: 'span class="secondary"'}},
"from_database_format": {
'span[class="secondary"]': InlineStyleElementHandler(type_)
},
"to_database_format": {"style_map": {type_: 'span class="secondary"'}},
}
# 5. Call register_converter_rule to register the content transformation conversion.
features.register_converter_rule('contentstate', feature_name, db_conversion)
features.register_converter_rule("contentstate", feature_name, db_conversion)
# 6. (optional) Add the feature to the default features list to make it available
# on rich text fields that do not specify an explicit 'features' list
features.default_features.append(feature_name)
@hooks.register('construct_explorer_page_queryset')
@hooks.register("construct_explorer_page_queryset")
def remove_page_types_from_menu(parent_page, pages, request):
return pages.not_type(ContentBlockSnapshot).not_type(BasicKnowledge).exclude(contentblock__user_created=True)
return (
pages.not_type(ContentBlockSnapshot)
.not_type(BasicKnowledge)
.exclude(contentblock__user_created=True)
)
@hooks.register('after_copy_page')
@hooks.register("after_copy_page")
def after_copy_hook(request, page, new_page):
# todo: find every ContentBlock further down in the tree, see if there are any Surveys or Assignments and copy them and reassign them
logger.debug(f'After copy page {page.title}, {new_page.title}')
if type(page.specific) == ContentBlock:
logger.debug("It's a content block")
content_block: ContentBlock = new_page.specific
logger.debug(f"duplicatin {content_block.title, content_block.pk}")
content_block.duplicate_attached_entities()
@hooks.register('after_edit_page')
else:
logger.debug(f"It's something else {type(page.specific)}, {ContentBlock}")
logger.debug(
f"After copy page old: {page.title} {page.pk}, {new_page.title} {new_page.pk}"
)
@hooks.register("after_edit_page")
def after_edit_hook(request, page):
logger.debug(f'After edit page {page.title}, {type(page).__name__}')
logger.debug(f"After edit page {page.title}, {type(page).__name__}")
@hooks.register('after_create_page')
@hooks.register("after_create_page")
def after_create_hook(request, page):
logger.debug(f'After create page {page.title}')
logger.debug(f"After create page {page.title}")

View File

@ -9,20 +9,25 @@ class StrictHierarchyPage(Page):
abstract = True
def get_child_ids(self):
return self.get_children().values_list('id', flat=True)
return self.get_children().values_list("id", flat=True)
@classmethod
def get_by_parent(cls, parent):
return cls.objects.filter(id__in=parent.get_child_ids()).live()
def get_content_blocks(self):
raise NotImplementedError()
def wagtail_parent_filter(parent_cls, child_cls):
class ParentValueFilter(admin.SimpleListFilter):
title = 'parent'
parameter_name = 'parent'
title = "parent"
parameter_name = "parent"
def lookups(self, request, model_admin):
return list((parent.slug, parent.title) for parent in parent_cls.objects.all())
return list(
(parent.slug, parent.title) for parent in parent_cls.objects.all()
)
def queryset(self, request, queryset):
filter_value = self.value()
@ -34,7 +39,4 @@ def wagtail_parent_filter(parent_cls, child_cls):
def get_default_settings():
return ObjectList([
FieldPanel('slug'),
CommentPanel()
], heading='Settings')
return ObjectList([FieldPanel("slug"), CommentPanel()], heading="Settings")