Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test (and fix) for Django main (future 4.0) #23

Merged
merged 8 commits into from Apr 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions .editorconfig
@@ -0,0 +1,12 @@
root = true

[*]
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8
end_of_line = lf

[*.py]
max_line_length = 119
2 changes: 2 additions & 0 deletions .flake8
@@ -0,0 +1,2 @@
[flake8]
max-line-length = 119
9 changes: 8 additions & 1 deletion .travis.yml
Expand Up @@ -8,7 +8,14 @@ env:
- DJANGO_VERSION='Django~=2.2.0'
- DJANGO_VERSION='Django~=3.0.0'
- DJANGO_VERSION='Django~=3.1.0'
- DJANGO_VERSION='Django>=3.2a1,<4.0'
- DJANGO_VERSION='Django~=3.2.0'
- DJANGO_VERSION='https://github.com/django/django/archive/refs/heads/main.zip'
jobs:
exclude:
- python: 3.6
env: DJANGO_VERSION='https://github.com/django/django/archive/refs/heads/main.zip'
- python: 3.7
env: DJANGO_VERSION='https://github.com/django/django/archive/refs/heads/main.zip'
install:
- pip install --upgrade pip
- pip install --upgrade $DJANGO_VERSION
Expand Down
3 changes: 3 additions & 0 deletions HISTORY.rst
Expand Up @@ -5,6 +5,9 @@ History
^^^^^^^^^^^^^^^^^^

- Support Django 3.2 (no changes necessary)
- Test with Django main (4.0)
- Use force_str instead of force_text,
the latter is deprecated and removed in Django 4.0


1.6 (2020-05-20)
Expand Down
10 changes: 6 additions & 4 deletions djrichtextfield/models.py
Expand Up @@ -11,14 +11,16 @@ def __init__(self, *args, field_settings=None, **kwargs):

def formfield(self, **kwargs):
kwargs['widget'] = RichTextWidget(
field_settings=self.field_settings, sanitizer=self.sanitizer)
field_settings=self.field_settings, sanitizer=self.sanitizer
)
return super(RichTextField, self).formfield(**kwargs)

def clean(self, value, model_instance):
"""
Convert the value's type, sanitize it, and run validation. Validation
errors from to_python() and validate() are propagated. Return the
correct value if no error is raised.
Convert the value's type, sanitize it, and run validation.
Validation errors from to_python() and validate() are propagated.

Return the correct value if no error is raised.
"""
value = self.to_python(value)
if value is not None:
Expand Down
5 changes: 2 additions & 3 deletions djrichtextfield/sanitizer.py
Expand Up @@ -9,8 +9,7 @@ def noop(value):

class SanitizerMixin:
"""
Get the field sanitizer from the provided kwargs during init,
or from the settings.
Get the field sanitizer from the provided kwargs during init, or from the settings.
"""

SANITIZER_KEY = 'sanitizer'
Expand All @@ -25,11 +24,11 @@ def get_sanitizer(self):
Get the field sanitizer.

The priority is the first defined in the following order:

- A sanitizer provided to the widget.
- Profile (field settings) specific sanitizer, if defined in settings.
- Global sanitizer defined in settings.
- Simple no-op sanitizer which just returns the provided value.

"""
sanitizer = self.sanitizer

Expand Down
2 changes: 1 addition & 1 deletion djrichtextfield/settings.py
Expand Up @@ -6,7 +6,7 @@
'js': [],
'css': {},
'init_template': None,
'settings': {}
'settings': {},
}
CONFIG = DEFAULT_CONFIG.copy()
CONFIG.update(getattr(settings, 'DJRICHTEXTFIELD_CONFIG', {}))
Expand Down
2 changes: 1 addition & 1 deletion djrichtextfield/urls.py
Expand Up @@ -3,5 +3,5 @@
from djrichtextfield.views import InitView

urlpatterns = [
path('init.js', InitView.as_view(), name='djrichtextfield_init')
path('init.js', InitView.as_view(), name='djrichtextfield_init'),
]
5 changes: 2 additions & 3 deletions djrichtextfield/views.py
@@ -1,6 +1,6 @@
import json

from django.utils.encoding import force_text
from django.utils.encoding import force_str
from django.utils.safestring import mark_safe
from django.views.generic import TemplateView

Expand All @@ -12,8 +12,7 @@ class InitView(TemplateView):
content_type = 'application/javascript'

def get_settings_json(self):
return mark_safe(json.dumps(settings.CONFIG['settings'],
default=force_text))
return mark_safe(json.dumps(settings.CONFIG['settings'], default=force_str))

def get_context_data(self, **kwargs):
context_data = super(InitView, self).get_context_data(**kwargs)
Expand Down
24 changes: 11 additions & 13 deletions djrichtextfield/widgets.py
@@ -1,9 +1,8 @@
import json

from django.conf import settings as django_settings
from django.forms.widgets import Media, Textarea
from django.urls import reverse
from django.utils.encoding import force_text
from django.utils.encoding import force_str
from django.utils.html import format_html

from djrichtextfield import settings
Expand All @@ -21,7 +20,10 @@ def __init__(self, attrs=None, field_settings=None, sanitizer=None):
defaults = {'class': self.CSS_CLASS}
if attrs:
if 'class' in attrs:
attrs['class'] = ' '.join([attrs['class'], defaults['class']])
attrs['class'] = ' '.join([
attrs['class'],
defaults['class'],
])
defaults.update(attrs)
self.field_settings = field_settings or {}
super(RichTextWidget, self).__init__(defaults, sanitizer=sanitizer)
Expand All @@ -35,8 +37,8 @@ def media(self):

def get_field_settings(self):
"""
Get the field settings, if the configured setting is a string try
to get a 'profile' from the global config.
Get the field settings, if the configured setting is a string
try to get a 'profile' from the global config.
"""
field_settings = None
if self.field_settings:
Expand All @@ -51,19 +53,15 @@ def render(self, name, value, attrs=None, renderer=None):
attrs = attrs or {}
field_settings = self.get_field_settings()
if field_settings:
attrs[self.SETTINGS_ATTR] = json.dumps(field_settings,
default=force_text)
textarea = super(RichTextWidget, self).render(name, value, attrs=attrs,
renderer=renderer)
return format_html(
'<div class="{0}">{1}</div>', self.CONTAINER_CLASS, textarea)
attrs[self.SETTINGS_ATTR] = json.dumps(field_settings, default=force_str)
textarea = super(RichTextWidget, self).render(name, value, attrs=attrs, renderer=renderer)
return format_html('<div class="{0}">{1}</div>', self.CONTAINER_CLASS, textarea)

def value_from_datadict(self, *args, **kwargs):
"""
Pass the submitted value through the sanitizer before returning it.
"""
value = super(RichTextWidget, self).value_from_datadict(
*args, **kwargs)
value = super(RichTextWidget, self).value_from_datadict(*args, **kwargs)
if value is not None:
value = self.get_sanitizer()(value)
return value
8 changes: 2 additions & 6 deletions setup.py
@@ -1,10 +1,7 @@
import os
import sys

try:
from setuptools import setup
except ImportError:
from distutils.core import setup
from setuptools import setup

version = '1.6.1.dev0'

Expand All @@ -21,8 +18,7 @@
setup(
name='django-richtextfield',
version=version,
description='A Django model field and widget that renders a'
' customizable WYSIWYG/rich text editor',
description='A Django model field and widget that renders a customizable WYSIWYG/rich text editor',
long_description=readme + '\n\n' + history,
author='Jaap Roes',
author_email='jaap.roes@gmail.com',
Expand Down
17 changes: 8 additions & 9 deletions testproject/settings.py
Expand Up @@ -64,16 +64,15 @@
'settings': {
'menubar': False,
'plugins': 'link image table code',
'toolbar': 'formatselect | bold italic | removeformat |'
' link unlink image table | code',
'toolbar': 'formatselect | bold italic | removeformat |' ' link unlink image table | code',
'block_formats': 'Paragraph=p;Header 1=h1;Header 2=h2;Header 3=h3',
'width': 700
'width': 700,
},
'profiles': {
'mini': {
'toolbar': 'bold italic | removeformat'
}
}
'toolbar': 'bold italic | removeformat',
},
},
}

CKEDITOR_CONFIG = {
Expand All @@ -83,7 +82,7 @@
'toolbar': [
{'items': ['Format', '-', 'Bold', 'Italic', '-', 'RemoveFormat']},
{'items': ['Link', 'Unlink', 'Image', 'Table']},
{'items': ['Source']}
{'items': ['Source']},
],
'format_tags': 'p;h1;h2;h3',
'width': 700,
Expand All @@ -97,8 +96,8 @@
},
'sanitizer': lambda value: 'foo' + value,
'sanitizer_profiles': {
'baz': lambda value: value + 'baz'
}
'baz': lambda value: value + 'baz',
},
}

DJRICHTEXTFIELD_CONFIG = CKEDITOR_CONFIG
8 changes: 5 additions & 3 deletions testproject/tests/test_models.py
Expand Up @@ -14,14 +14,16 @@ def test_formfield_widget(self):
"""
Model field has RichTextWidget
"""
self.assertIsInstance(
RichTextField().formfield().widget, RichTextWidget)
self.assertIsInstance(RichTextField().formfield().widget, RichTextWidget)

def test_formfield_widget_passes_settings(self):
"""
Model field passes setting to widget
"""
settings = {'foo': True, 'bar': [1, 2, 3]}
settings = {
'foo': True,
'bar': [1, 2, 3],
}
widget = RichTextField(field_settings=settings).formfield().widget
self.assertEqual(widget.field_settings, settings)

Expand Down
4 changes: 1 addition & 3 deletions testproject/tests/test_sanitizer.py
Expand Up @@ -20,9 +20,7 @@ def test_get_sanitizer_uses_profile_sanitizer(self):
"""
mixin = SanitizerMixin()
mixin.field_settings = 'baz'
self.assertEqual(
settings.CONFIG['sanitizer_profiles']['baz'],
mixin.get_sanitizer())
self.assertEqual(settings.CONFIG['sanitizer_profiles']['baz'], mixin.get_sanitizer())

def test_clean_uses_global_sanitizer_with_no_sanitizer_profiles(self):
"""
Expand Down
16 changes: 7 additions & 9 deletions testproject/tests/test_views.py
Expand Up @@ -3,21 +3,19 @@
from django.urls import reverse


@override_settings(DJRICHTEXTFIELD_CONFIG={
'init_template': 'djrichtextfield/init/tinymce.js'
})
@override_settings(DJRICHTEXTFIELD_CONFIG={'init_template': 'djrichtextfield/init/tinymce.js'})
class TestInitView(TestCase):
def setUp(self):
self.response = self.client.get(reverse('djrichtextfield_init'))

def test_template(self):
self.assertEqual(
self.response.template_name, ['djrichtextfield/init.js'])
self.assertEqual(self.response.template_name, ['djrichtextfield/init.js'])

def test_content_type(self):
self.assertEqual(
self.response['content-type'], 'application/javascript')
self.assertEqual(self.response['content-type'], 'application/javascript')

def test_init_template_in_context(self):
self.assertEqual('djrichtextfield/init/tinymce.js',
self.response.context_data['init_template'])
self.assertEqual(
'djrichtextfield/init/tinymce.js',
self.response.context_data['init_template'],
)