Skip to content

Commit 4fc4041

Browse files
committed
Rename to djangocms_text and fix tests
1 parent 77341f5 commit 4fc4041

File tree

593 files changed

+878
-2903
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

593 files changed

+878
-2903
lines changed

.github/workflows/test.yml

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -8,52 +8,17 @@ jobs:
88
strategy:
99
fail-fast: false
1010
matrix:
11-
python-version: [ 3.7, 3.8, 3.9, "3.10", "3.11"]
11+
python-version: [ "3.10", "3.11"]
1212
requirements-file: [
13-
dj22_cms37.txt,
14-
dj22_cms38.txt,
15-
dj22_cms40.txt,
16-
dj31_cms38.txt,
17-
dj32_cms39.txt,
18-
dj32_cms310.txt,
1913
dj32_cms311.txt,
2014
dj32_cms41.txt,
21-
dj40_cms311.txt,
22-
dj40_cms41.txt,
23-
dj41_cms311.txt,
24-
dj41_cms41.txt,
2515
dj42_cms311.txt,
2616
dj42_cms41.txt,
2717
dj50_cms41.txt
2818
]
2919
os: [
3020
ubuntu-20.04,
3121
]
32-
exclude:
33-
- python-version: 3.7
34-
requirements-file: dj32_cms41.txt
35-
- python-version: 3.7
36-
requirements-file: dj40_cms311.txt
37-
- python-version: 3.7
38-
requirements-file: dj40_cms41.txt
39-
- python-version: 3.7
40-
requirements-file: dj41_cms311.txt
41-
- python-version: 3.7
42-
requirements-file: dj41_cms41.txt
43-
- python-version: 3.7
44-
requirements-file: dj42_cms311.txt
45-
- python-version: 3.7
46-
requirements-file: dj42_cms41.txt
47-
- python-version: 3.7
48-
requirements-file: dj50_cms41.txt
49-
- python-version: 3.8
50-
requirements-file: dj50_cms41.txt
51-
- python-version: 3.9
52-
requirements-file: dj50_cms41.txt
53-
- python-version: "3.10"
54-
requirements-file: dj22_cms40.txt
55-
- python-version: "3.11"
56-
requirements-file: dj22_cms40.txt
5722
steps:
5823
- uses: actions/checkout@v1
5924
- name: Set up Python ${{ matrix.python-version }}
@@ -67,7 +32,7 @@ jobs:
6732
python setup.py install
6833
6934
- name: Run coverage
70-
run: coverage run setup.py test
35+
run: coverage run runtests.py
7136

7237
- name: Upload Coverage to Codecov
7338
uses: codecov/codecov-action@v1

djangocms_text/__init__.py

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

djangocms_text/apps.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from django.apps import AppConfig
2+
3+
4+
class TextConfig(AppConfig):
5+
name = "djangocms_text"
6+
verbose_name = "django CMS Rich Text"
7+
default_auto_field = "django.db.models.AutoField"

djangocms_text_ckeditor/attribute_parsers.py renamed to djangocms_text/attribute_parsers.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,5 @@
22

33

44
class DataAttributeParser(AllowTokenParser):
5-
65
def parse(self, attribute, val):
7-
return attribute.startswith('data-')
6+
return attribute.startswith("data-")

djangocms_text_ckeditor/cms_plugins.py renamed to djangocms_text/cms_plugins.py

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ class TextPlugin(CMSPluginBase):
165165
render_template = "cms/plugins/text.html"
166166
inline_editing_template = "cms/plugins/inline.html"
167167
change_form_template = "cms/plugins/text_plugin_change_form.html"
168-
ckeditor_configuration = settings.TEXT_CKEDITOR_CONFIGURATION
168+
ckeditor_configuration = settings.TEXT_CONFIGURATION
169169
disable_child_plugins = True
170170
fieldsets = ((None, {"fields": ("body",)}),)
171171

@@ -176,10 +176,6 @@ class TextPlugin(CMSPluginBase):
176176
"pre_change_plugin": pre_change_plugin,
177177
}
178178

179-
# On django CMS 3.5 this attribute is set automatically
180-
# when do_post_copy is defined in the plugin class.
181-
_has_do_post_copy = True
182-
183179
@classmethod
184180
def do_post_copy(cls, instance, source_map):
185181
ids = plugin_tags_to_id_list(instance.body)
@@ -220,10 +216,10 @@ def get_editor_widget(self, request, plugins, plugin):
220216
the text area
221217
"""
222218
cancel_url_name = self.get_admin_url_name("delete_on_cancel")
223-
cancel_url = reverse("admin:%s" % cancel_url_name)
219+
cancel_url = reverse(f"admin:{cancel_url_name}")
224220

225221
render_plugin_url_name = self.get_admin_url_name("render_plugin")
226-
render_plugin_url = reverse("admin:%s" % render_plugin_url_name)
222+
render_plugin_url = reverse(f"admin:{render_plugin_url_name}")
227223

228224
action_token = self.get_action_token(request, plugin)
229225

@@ -260,7 +256,6 @@ def _get_body_css_classes_from_parent_plugins(
260256
"""
261257
parent_current = plugin_instance.parent
262258
if parent_current:
263-
264259
for plugin_name, plugin_class in plugin_pool.plugins.items():
265260
is_current_parent_found = plugin_name == parent_current.plugin_type
266261
if is_current_parent_found:
@@ -462,7 +457,7 @@ def delete_on_cancel(self, request):
462457
except ValidationError as error:
463458
return HttpResponseBadRequest(error.message)
464459

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

515-
modules = get_placeholder_conf("plugin_modules", plugin.placeholder.slot, template, default={})
516-
names = get_placeholder_conf("plugin_labels", plugin.placeholder.slot, template, default={})
510+
modules = get_placeholder_conf(
511+
"plugin_modules", plugin.placeholder.slot, template, default={}
512+
)
513+
names = get_placeholder_conf(
514+
"plugin_labels", plugin.placeholder.slot, template, default={}
515+
)
517516
main_list = []
518517

519518
# plugin.value points to the class name of the plugin
520519
# It's added on registration. TIL.
521520
for plugin in child_plugins:
522-
main_list.append({'value': plugin.value,
523-
'name': names.get(plugin.value, plugin.name),
524-
'icon': getattr(plugin, "text_icon", None),
525-
'module': modules.get(plugin.value, plugin.module)})
521+
main_list.append(
522+
{
523+
"value": plugin.value,
524+
"name": names.get(plugin.value, plugin.name),
525+
"icon": getattr(plugin, "text_icon", None),
526+
"module": modules.get(plugin.value, plugin.module),
527+
}
528+
)
526529
return sorted(main_list, key=operator.itemgetter("module"))
527530

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

539542
def get_render_template(self, context, instance, placeholder):
540-
if hasattr(context["request"], "toolbar") and context["request"].toolbar.edit_mode_active:
543+
if (
544+
hasattr(context["request"], "toolbar")
545+
and context["request"].toolbar.edit_mode_active
546+
):
541547
return self.inline_editing_template
542548
else:
543549
return self.render_template

djangocms_text_ckeditor/cms_toolbars.py renamed to djangocms_text/cms_toolbars.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,45 +20,57 @@ class IconButton(Button):
2020

2121
class InlineEditingItem(BaseItem):
2222
"""Make ckeditor base path available for inline editing"""
23+
2324
def render(self):
2425
return mark_safe(
2526
f'<script class="ckeditorconfig" '
26-
f'data-ckeditor-basepath="{settings.TEXT_CKEDITOR_BASE_PATH}"></script>')
27+
f'data-ckeditor-basepath="{settings.TEXT_CKEDITOR_BASE_PATH}"></script>'
28+
)
2729

2830

2931
class InlineEditingToolbar(CMSToolbar):
3032
@property
3133
def media(self):
3234
if self.toolbar.edit_mode_active and self.inline_editing:
3335
return forms.Media(
34-
css={'screen': ('djangocms_text_ckeditor/css/cms.inline-ckeditor.css',)},
36+
css={
37+
"screen": ("djangocms_text_ckeditor/css/cms.inline-ckeditor.css",)
38+
},
3539
js=(PATH_TO_JS,),
3640
)
3741
return forms.Media()
3842

3943
@cached_property
4044
def inline_editing(self):
41-
inline_editing = self.request.session.get("inline_editing", True) # Activated by default
42-
change = self.request.GET.get("inline_editing", None) # can be changed by query param
45+
inline_editing = self.request.session.get(
46+
"inline_editing", True
47+
) # Activated by default
48+
change = self.request.GET.get(
49+
"inline_editing", None
50+
) # can be changed by query param
4351
if change is not None:
4452
inline_editing = change == "1"
4553
self.request.session["inline_editing"] = inline_editing # store in session
4654
return inline_editing
4755

4856
def populate(self):
4957
if self.toolbar.edit_mode_active:
50-
item = ButtonList(side = self.toolbar.RIGHT)
58+
item = ButtonList(side=self.toolbar.RIGHT)
5159
item.add_item(
5260
IconButton(
5361
name=_("Toggle inline editing mode for text plugins"),
54-
url=self.get_full_path_with_param("inline_editing", int(not self.inline_editing)),
62+
url=self.get_full_path_with_param(
63+
"inline_editing", int(not self.inline_editing)
64+
),
5565
active=self.inline_editing,
5666
extra_classes=["cms-icon cms-icon-pencil"],
5767
),
5868
)
5969
self.toolbar.add_item(item)
6070
if self.inline_editing:
61-
self.toolbar.add_item(InlineEditingItem(), position=None) # Loads js and css for inline editing
71+
self.toolbar.add_item(
72+
InlineEditingItem(), position=None
73+
) # Loads js and css for inline editing
6274

6375
def get_full_path_with_param(self, key, value):
6476
"""

djangocms_text/fields.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
from django.contrib.admin import widgets as admin_widgets
2+
from django.db import models
3+
from django.forms.fields import CharField
4+
from django.utils.safestring import mark_safe
5+
6+
from .html import clean_html
7+
from .widgets import TextEditorWidget
8+
9+
10+
class HTMLFormField(CharField):
11+
widget = TextEditorWidget
12+
13+
def __init__(self, *args, **kwargs):
14+
conf = kwargs.pop("configuration", None)
15+
16+
if conf:
17+
widget = TextEditorWidget(configuration=conf)
18+
else:
19+
widget = None
20+
kwargs.setdefault("widget", widget)
21+
super().__init__(*args, **kwargs)
22+
23+
def clean(self, value):
24+
value = super().clean(value)
25+
clean_value = clean_html(value, full=False)
26+
27+
# We `mark_safe` here (as well as in the correct places) because Django
28+
# Parler cache's the value directly from the in-memory object as it
29+
# also stores the value in the database. So the cached version is never
30+
# processed by `from_db_value()`.
31+
clean_value = mark_safe(clean_value)
32+
33+
return clean_value
34+
35+
36+
class HTMLField(models.TextField):
37+
def __init__(self, *args, **kwargs):
38+
# This allows widget configuration customization
39+
# from the model definition
40+
self.configuration = kwargs.pop("configuration", None)
41+
super().__init__(*args, **kwargs)
42+
43+
def from_db_value(self, value, expression, connection, context=None):
44+
if value is None:
45+
return value
46+
return mark_safe(value)
47+
48+
def to_python(self, value):
49+
# On Django >= 1.8 a new method
50+
# was introduced (from_db_value) which is called
51+
# whenever the value is loaded from the db.
52+
# And to_python is called for serialization and cleaning.
53+
# This means we don't need to add mark_safe on Django >= 1.8
54+
# because it's handled by (from_db_value)
55+
if value is None:
56+
return value
57+
return value
58+
59+
def formfield(self, **kwargs):
60+
if self.configuration:
61+
widget = TextEditorWidget(configuration=self.configuration)
62+
else:
63+
widget = TextEditorWidget
64+
65+
defaults = {
66+
"form_class": HTMLFormField,
67+
"widget": widget,
68+
}
69+
defaults.update(kwargs)
70+
71+
# override the admin widget
72+
if defaults["widget"] == admin_widgets.AdminTextareaWidget:
73+
defaults["widget"] = widget
74+
return super().formfield(**defaults)
75+
76+
def clean(self, value, model_instance):
77+
# This needs to be marked safe as well because the form field's
78+
# clean method is not called on model.full_clean()
79+
value = super().clean(value, model_instance)
80+
return mark_safe(clean_html(value, full=False))

0 commit comments

Comments
 (0)