Skip to content

Commit

Permalink
Rename to djangocms_text and fix tests
Browse files Browse the repository at this point in the history
  • Loading branch information
fsbraun committed Jan 13, 2024
1 parent 77341f5 commit 4fc4041
Show file tree
Hide file tree
Showing 593 changed files with 878 additions and 2,903 deletions.
39 changes: 2 additions & 37 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,52 +8,17 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [ 3.7, 3.8, 3.9, "3.10", "3.11"]
python-version: [ "3.10", "3.11"]
requirements-file: [
dj22_cms37.txt,
dj22_cms38.txt,
dj22_cms40.txt,
dj31_cms38.txt,
dj32_cms39.txt,
dj32_cms310.txt,
dj32_cms311.txt,
dj32_cms41.txt,
dj40_cms311.txt,
dj40_cms41.txt,
dj41_cms311.txt,
dj41_cms41.txt,
dj42_cms311.txt,
dj42_cms41.txt,
dj50_cms41.txt
]
os: [
ubuntu-20.04,
]
exclude:
- python-version: 3.7
requirements-file: dj32_cms41.txt
- python-version: 3.7
requirements-file: dj40_cms311.txt
- python-version: 3.7
requirements-file: dj40_cms41.txt
- python-version: 3.7
requirements-file: dj41_cms311.txt
- python-version: 3.7
requirements-file: dj41_cms41.txt
- python-version: 3.7
requirements-file: dj42_cms311.txt
- python-version: 3.7
requirements-file: dj42_cms41.txt
- python-version: 3.7
requirements-file: dj50_cms41.txt
- python-version: 3.8
requirements-file: dj50_cms41.txt
- python-version: 3.9
requirements-file: dj50_cms41.txt
- python-version: "3.10"
requirements-file: dj22_cms40.txt
- python-version: "3.11"
requirements-file: dj22_cms40.txt
steps:
- uses: actions/checkout@v1
- name: Set up Python ${{ matrix.python-version }}
Expand All @@ -67,7 +32,7 @@ jobs:
python setup.py install
- name: Run coverage
run: coverage run setup.py test
run: coverage run runtests.py

- name: Upload Coverage to Codecov
uses: codecov/codecov-action@v1
21 changes: 21 additions & 0 deletions djangocms_text/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""
See PEP 440 (https://www.python.org/dev/peps/pep-0440/)
Release logic:
1. Increase version number (change __version__ below).
2. Ensure the static bundle is upto date with ``nvm use && gulp bundle``
3. Assure that all changes have been documented in CHANGELOG.rst.
4. In setup.py check that
- versions from all third party packages are pinned in ``REQUIREMENTS``.
- the list of ``CLASSIFIERS`` is up to date.
5. git add djangocms_text_ckeditor/__init__.py CHANGELOG.rst setup.py
6. git commit -m 'Bump to {new version}'
7. git push
8. Assure that all tests pass on https://github.com/django-cms/djangocms-text-ckeditor/actions
9. Create a new release on https://github.com/django-cms/djangocms-text-ckeditor/releases/new
10. Publish the release when ready
11. Github actions will publish the new package to pypi
"""
__version__ = "0.1.0"

default_app_config = "djangocms_text.apps.TextConfig"
7 changes: 7 additions & 0 deletions djangocms_text/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.apps import AppConfig


class TextConfig(AppConfig):
name = "djangocms_text"
verbose_name = "django CMS Rich Text"
default_auto_field = "django.db.models.AutoField"
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,5 @@


class DataAttributeParser(AllowTokenParser):

def parse(self, attribute, val):
return attribute.startswith('data-')
return attribute.startswith("data-")
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ class TextPlugin(CMSPluginBase):
render_template = "cms/plugins/text.html"
inline_editing_template = "cms/plugins/inline.html"
change_form_template = "cms/plugins/text_plugin_change_form.html"
ckeditor_configuration = settings.TEXT_CKEDITOR_CONFIGURATION
ckeditor_configuration = settings.TEXT_CONFIGURATION
disable_child_plugins = True
fieldsets = ((None, {"fields": ("body",)}),)

Expand All @@ -176,10 +176,6 @@ class TextPlugin(CMSPluginBase):
"pre_change_plugin": pre_change_plugin,
}

# On django CMS 3.5 this attribute is set automatically
# when do_post_copy is defined in the plugin class.
_has_do_post_copy = True

@classmethod
def do_post_copy(cls, instance, source_map):
ids = plugin_tags_to_id_list(instance.body)
Expand Down Expand Up @@ -220,10 +216,10 @@ def get_editor_widget(self, request, plugins, plugin):
the text area
"""
cancel_url_name = self.get_admin_url_name("delete_on_cancel")
cancel_url = reverse("admin:%s" % cancel_url_name)
cancel_url = reverse(f"admin:{cancel_url_name}")

render_plugin_url_name = self.get_admin_url_name("render_plugin")
render_plugin_url = reverse("admin:%s" % render_plugin_url_name)
render_plugin_url = reverse(f"admin:{render_plugin_url_name}")

action_token = self.get_action_token(request, plugin)

Expand Down Expand Up @@ -260,7 +256,6 @@ def _get_body_css_classes_from_parent_plugins(
"""
parent_current = plugin_instance.parent
if parent_current:

for plugin_name, plugin_class in plugin_pool.plugins.items():
is_current_parent_found = plugin_name == parent_current.plugin_type
if is_current_parent_found:
Expand Down Expand Up @@ -462,7 +457,7 @@ def delete_on_cancel(self, request):
except ValidationError as error:
return HttpResponseBadRequest(error.message)

# This form validates the the given plugin is a child
# This form validates the given plugin is a child
# of the text plugin or is a text plugin.
# If the plugin is a child then we validate that this child
# is not present in the text plugin (because then it's not a cancel).
Expand Down Expand Up @@ -512,17 +507,25 @@ def get_plugins(self, obj=None):
child_plugins = (get_plugin(name) for name in child_plugin_types)
template = getattr(self.page, "template", None)

modules = get_placeholder_conf("plugin_modules", plugin.placeholder.slot, template, default={})
names = get_placeholder_conf("plugin_labels", plugin.placeholder.slot, template, default={})
modules = get_placeholder_conf(
"plugin_modules", plugin.placeholder.slot, template, default={}
)
names = get_placeholder_conf(
"plugin_labels", plugin.placeholder.slot, template, default={}
)
main_list = []

# plugin.value points to the class name of the plugin
# It's added on registration. TIL.
for plugin in child_plugins:
main_list.append({'value': plugin.value,
'name': names.get(plugin.value, plugin.name),
'icon': getattr(plugin, "text_icon", None),
'module': modules.get(plugin.value, plugin.module)})
main_list.append(
{
"value": plugin.value,
"name": names.get(plugin.value, plugin.name),
"icon": getattr(plugin, "text_icon", None),
"module": modules.get(plugin.value, plugin.module),
}
)
return sorted(main_list, key=operator.itemgetter("module"))

def get_form(self, request, obj=None, **kwargs):
Expand All @@ -537,7 +540,10 @@ def get_form(self, request, obj=None, **kwargs):
return super().get_form(request, obj, **kwargs)

def get_render_template(self, context, instance, placeholder):
if hasattr(context["request"], "toolbar") and context["request"].toolbar.edit_mode_active:
if (
hasattr(context["request"], "toolbar")
and context["request"].toolbar.edit_mode_active
):
return self.inline_editing_template
else:
return self.render_template
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,45 +20,57 @@ class IconButton(Button):

class InlineEditingItem(BaseItem):
"""Make ckeditor base path available for inline editing"""

def render(self):
return mark_safe(
f'<script class="ckeditorconfig" '
f'data-ckeditor-basepath="{settings.TEXT_CKEDITOR_BASE_PATH}"></script>')
f'data-ckeditor-basepath="{settings.TEXT_CKEDITOR_BASE_PATH}"></script>'
)


class InlineEditingToolbar(CMSToolbar):
@property
def media(self):
if self.toolbar.edit_mode_active and self.inline_editing:
return forms.Media(
css={'screen': ('djangocms_text_ckeditor/css/cms.inline-ckeditor.css',)},
css={
"screen": ("djangocms_text_ckeditor/css/cms.inline-ckeditor.css",)
},
js=(PATH_TO_JS,),
)
return forms.Media()

@cached_property
def inline_editing(self):
inline_editing = self.request.session.get("inline_editing", True) # Activated by default
change = self.request.GET.get("inline_editing", None) # can be changed by query param
inline_editing = self.request.session.get(
"inline_editing", True
) # Activated by default
change = self.request.GET.get(
"inline_editing", None
) # can be changed by query param
if change is not None:
inline_editing = change == "1"
self.request.session["inline_editing"] = inline_editing # store in session
return inline_editing

def populate(self):
if self.toolbar.edit_mode_active:
item = ButtonList(side = self.toolbar.RIGHT)
item = ButtonList(side=self.toolbar.RIGHT)
item.add_item(
IconButton(
name=_("Toggle inline editing mode for text plugins"),
url=self.get_full_path_with_param("inline_editing", int(not self.inline_editing)),
url=self.get_full_path_with_param(
"inline_editing", int(not self.inline_editing)
),
active=self.inline_editing,
extra_classes=["cms-icon cms-icon-pencil"],
),
)
self.toolbar.add_item(item)
if self.inline_editing:
self.toolbar.add_item(InlineEditingItem(), position=None) # Loads js and css for inline editing
self.toolbar.add_item(
InlineEditingItem(), position=None
) # Loads js and css for inline editing

def get_full_path_with_param(self, key, value):
"""
Expand Down
80 changes: 80 additions & 0 deletions djangocms_text/fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from django.contrib.admin import widgets as admin_widgets
from django.db import models
from django.forms.fields import CharField
from django.utils.safestring import mark_safe

from .html import clean_html
from .widgets import TextEditorWidget


class HTMLFormField(CharField):
widget = TextEditorWidget

def __init__(self, *args, **kwargs):
conf = kwargs.pop("configuration", None)

if conf:
widget = TextEditorWidget(configuration=conf)
else:
widget = None
kwargs.setdefault("widget", widget)
super().__init__(*args, **kwargs)

def clean(self, value):
value = super().clean(value)
clean_value = clean_html(value, full=False)

# We `mark_safe` here (as well as in the correct places) because Django
# Parler cache's the value directly from the in-memory object as it
# also stores the value in the database. So the cached version is never
# processed by `from_db_value()`.
clean_value = mark_safe(clean_value)

return clean_value


class HTMLField(models.TextField):
def __init__(self, *args, **kwargs):
# This allows widget configuration customization
# from the model definition
self.configuration = kwargs.pop("configuration", None)
super().__init__(*args, **kwargs)

def from_db_value(self, value, expression, connection, context=None):
if value is None:
return value
return mark_safe(value)

def to_python(self, value):
# On Django >= 1.8 a new method
# was introduced (from_db_value) which is called
# whenever the value is loaded from the db.
# And to_python is called for serialization and cleaning.
# This means we don't need to add mark_safe on Django >= 1.8
# because it's handled by (from_db_value)
if value is None:
return value
return value

def formfield(self, **kwargs):
if self.configuration:
widget = TextEditorWidget(configuration=self.configuration)
else:
widget = TextEditorWidget

defaults = {
"form_class": HTMLFormField,
"widget": widget,
}
defaults.update(kwargs)

# override the admin widget
if defaults["widget"] == admin_widgets.AdminTextareaWidget:
defaults["widget"] = widget
return super().formfield(**defaults)

def clean(self, value, model_instance):
# This needs to be marked safe as well because the form field's
# clean method is not called on model.full_clean()
value = super().clean(value, model_instance)
return mark_safe(clean_html(value, full=False))
Loading

0 comments on commit 4fc4041

Please sign in to comment.