Replace CKEditor's RichTextField with MarkdownField

Because you should not store raw HTML in database tables!
readwriteweb
Jaap Joris Vens 2020-01-02 23:37:26 +01:00
rodzic bd654f5103
commit f9ea04662b
15 zmienionych plików z 97 dodań i 139 usunięć

Wyświetl plik

@ -1,29 +0,0 @@
from django.contrib import admin
from django.utils.text import Truncator
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
from .models import Config
class BasePageAdmin(admin.ModelAdmin):
prepopulated_fields = {'slug': ('title',)}
class BaseSectionAdmin(admin.ModelAdmin):
list_filter = [
('page', admin.RelatedOnlyFieldListFilter),
]
list_display = ['__str__', 'get_type_display']
@admin.register(Config)
class ConfigAdmin(admin.ModelAdmin):
list_display = ['__str__', 'get_content']
exclude = ['parameter']
def get_content(self, obj):
return mark_safe(Truncator(obj.content).words(50, html=True))
get_content.short_description = _('content')
def has_add_permission(self, request):
return False
def has_delete_permission(self, *args, **kwargs):
return False

Wyświetl plik

@ -1,11 +1,11 @@
# Generated by Django 3.0.1 on 2020-01-02 18:31
# Generated by Django 3.0.1 on 2020-01-02 20:41
import ckeditor.fields
import cms.models
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import embed_video.fields
import markdownfield.models
class Migration(migrations.Migration):
@ -34,19 +34,6 @@ class Migration(migrations.Migration):
'swappable': 'CMS_PAGE_MODEL',
},
),
migrations.CreateModel(
name='Config',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('parameter', models.PositiveIntegerField(choices=[(10, 'Footer')], unique=True)),
('content', ckeditor.fields.RichTextField(blank=True, verbose_name='content')),
],
options={
'verbose_name': 'configuration parameter',
'verbose_name_plural': 'configuration parameters',
'ordering': ['parameter'],
},
),
migrations.CreateModel(
name='Section',
fields=[
@ -55,7 +42,8 @@ class Migration(migrations.Migration):
('position', models.PositiveIntegerField(blank=True, verbose_name='position')),
('title', cms.models.VarCharField(blank=True, verbose_name='title')),
('color', models.PositiveIntegerField(choices=[(1, 'Wit')], default=1, verbose_name='color')),
('content', ckeditor.fields.RichTextField(blank=True, verbose_name='content')),
('content', markdownfield.models.MarkdownField(blank=True, verbose_name='content')),
('content_rendered', markdownfield.models.RenderedMarkdownField(editable=False)),
('image', models.ImageField(blank=True, upload_to='', verbose_name='image')),
('video', embed_video.fields.EmbedVideoField(blank=True, help_text='Paste a YouTube, Vimeo, or SoundCloud link', verbose_name='video')),
('button_text', cms.models.VarCharField(blank=True, verbose_name='button text')),

Wyświetl plik

@ -7,9 +7,10 @@ from django.urls import reverse
from django.conf import settings
from django.forms import TextInput, Select
from django.utils.translation import gettext_lazy as _
from ckeditor.fields import RichTextField
from embed_video.fields import EmbedVideoField
from polymorphic.models import PolymorphicModel
from markdownfield.models import MarkdownField, RenderedMarkdownField
from markdownfield.validators import VALIDATOR_NULL
from numberedmodel.models import NumberedModel
@ -27,9 +28,9 @@ class VarCharChoiceField(models.TextField):
class BasePage(NumberedModel):
'''Abstract base model for pages'''
position = models.PositiveIntegerField(_('position'), blank=True)
title = VarCharField(_('title'))
slug = models.SlugField(_('slug'), help_text=_('A short identifier to use in URLs'), blank=True, unique=True)
title = VarCharField(_('title'))
position = models.PositiveIntegerField(_('position'), blank=True)
menu = models.BooleanField(_('visible in menu'), default=True)
def __str__(self):
@ -52,11 +53,12 @@ class BaseSection(NumberedModel, PolymorphicModel):
'''Abstract base model for sections'''
TYPES = []
page = models.ForeignKey(swapper.get_model_name('cms', 'Page'), verbose_name=_('page'), related_name='sections', on_delete=models.PROTECT)
type = VarCharChoiceField(_('section type'), default='', choices=TYPES)
position = models.PositiveIntegerField(_('position'), blank=True)
type = VarCharChoiceField(_('type'), default='', choices=TYPES)
title = VarCharField(_('title'), blank=True)
position = models.PositiveIntegerField(_('position'), blank=True)
color = models.PositiveIntegerField(_('color'), default=1, choices=settings.SECTION_COLORS)
content = RichTextField(_('content'), blank=True)
content = MarkdownField(_('content'), rendered_field='content_rendered', validator=VALIDATOR_NULL, use_admin_editor=False, blank=True)
content_rendered = RenderedMarkdownField()
image = models.ImageField(_('image'), blank=True)
video = EmbedVideoField(_('video'), blank=True, help_text=_('Paste a YouTube, Vimeo, or SoundCloud link'))
button_text = VarCharField(_('button text'), blank=True)
@ -88,19 +90,3 @@ class Section(BaseSection):
'''Swappable section model'''
class Meta(BaseSection.Meta):
swappable = swapper.swappable_setting('cms', 'Section')
class Config(models.Model):
TYPES = [
(10, _('Footer')),
]
parameter = models.PositiveIntegerField(choices=TYPES, unique=True)
content = RichTextField(_('content'), blank=True)
def __str__(self):
return "{}. {}".format(self.parameter, self.get_parameter_display())
class Meta:
verbose_name = _('configuration parameter')
verbose_name_plural = _('configuration parameters')
ordering = ['parameter']

Wyświetl plik

@ -5,6 +5,7 @@ $blue: #3573a8;
html, body {
font-family: $font;
line-height: 1.33;
margin: 0;
padding: 0;
}
@ -17,6 +18,16 @@ a {
}
}
table {
border-collapse: collapse;
th, td {
padding: 1em;
}
th {
border-bottom: 2px solid black;
}
}
a.button, button.button {
cursor: pointer;
font-family: sans-serif;
@ -248,6 +259,10 @@ section.contactsection {
/* Form elements */
form.cms {
div.wrapper {
overflow: hidden;
}
div.global_error {
border: 2px dotted red;
padding: 10px;
@ -263,7 +278,6 @@ form.cms {
border: 0.5px solid black;
border-radius: 3px;
legend {
font-size: 1.15em;
}
@ -272,10 +286,26 @@ form.cms {
div.formfield {
margin: 5px 0;
padding: 10px 0;
font-size: 0;
}
div.formfield > * {
font-size: 1rem;
clear: both;
box-sizing: border-box;
&#type, &#position, &#title, &#slug {
padding: 0 1em;
clear: none;
float: left;
display: inline-block;
width: 33%;
}
&#type, &#slug {
padding-left: 0;
}
&#title {
padding: 0;
}
&#position {
float: right;
padding-right: 0;
}
}
div.formfield.error {
border: 2px dotted red;
@ -323,13 +353,8 @@ form.cms {
margin: 0;
padding: 5px;
}
div.django-ckeditor-widget {
display: block !important;
}
div.cke_chrome {
box-sizing: border-box !important;
border: 1px solid #aaa !important;
textarea {
height: 25em;
}
input[type=checkbox] {

Wyświetl plik

@ -1,5 +1,6 @@
html, body {
font-family: sans-serif;
line-height: 1.33;
margin: 0;
padding: 0; }
@ -9,6 +10,13 @@ a {
a:hover {
text-decoration: underline; }
table {
border-collapse: collapse; }
table th, table td {
padding: 1em; }
table th {
border-bottom: 2px solid black; }
a.button, button.button {
cursor: pointer;
font-family: sans-serif;
@ -174,6 +182,9 @@ section.contactsection textarea {
font-family: inherit; }
/* Form elements */
form.cms div.wrapper {
overflow: hidden; }
form.cms div.global_error {
border: 2px dotted red;
padding: 10px;
@ -193,10 +204,21 @@ form.cms fieldset {
form.cms div.formfield {
margin: 5px 0;
padding: 10px 0;
font-size: 0; }
form.cms div.formfield > * {
font-size: 1rem; }
clear: both;
box-sizing: border-box; }
form.cms div.formfield#type, form.cms div.formfield#position, form.cms div.formfield#title, form.cms div.formfield#slug {
padding: 0 1em;
clear: none;
float: left;
display: inline-block;
width: 33%; }
form.cms div.formfield#type, form.cms div.formfield#slug {
padding-left: 0; }
form.cms div.formfield#title {
padding: 0; }
form.cms div.formfield#position {
float: right;
padding-right: 0; }
form.cms div.formfield.error {
border: 2px dotted red;
@ -237,12 +259,8 @@ form.cms input, form.cms select, form.cms textarea {
margin: 0;
padding: 5px; }
form.cms div.django-ckeditor-widget {
display: block !important; }
form.cms div.cke_chrome {
box-sizing: border-box !important;
border: 1px solid #aaa !important; }
form.cms textarea {
height: 25em; }
form.cms input[type=checkbox] {
width: auto; }

File diff suppressed because one or more lines are too long

Wyświetl plik

@ -22,7 +22,7 @@
</div>
</section>
{% block formset %}
{% if formset %}
{{formset.management_form}}
{% for form in formset %}
{{form.media}}
@ -48,7 +48,7 @@
</div>
</div>
</section>
{% endblock %}
{% endif %}
<div class="edit page">
<button>{% trans 'save' %}</button>
@ -74,6 +74,9 @@
slugfield.value = URLify(titlefield.value);
}
});
slugfield.addEventListener('input', function(event) {
virgin = false;
});
}
}

Wyświetl plik

@ -17,9 +17,9 @@
</div>
{% endif %}
{% if section.content %}
{% if section.content_rendered %}
<div class="content">
{{section.content|safe}}
{{section.content_rendered|safe}}
</div>
{% endif %}

Wyświetl plik

@ -1,10 +0,0 @@
from .models import Config
def get_config(parameter):
'''Gets or creates the requested parameter.
'''
if parameter not in [t[0] for t in Config.TYPES]:
raise ValueError('Invalid configuration parameter requested')
(c, created) = Config.objects.get_or_create(parameter=parameter)
return c.content

Wyświetl plik

@ -12,7 +12,6 @@ from django.http import HttpResponseRedirect, HttpResponseBadRequest
from .decorators import register_view
from .forms import PageForm, SectionForm
from .utils import get_config
Page = swapper.load_model('cms', 'Page')
Section = swapper.load_model('cms', 'Section')
@ -59,14 +58,12 @@ class SectionWithFormView(SectionView):
return section
class MenuMixin:
'''Add pages and footer to template context'''
'''Add pages to template context'''
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
pages = Page.objects.filter(menu=True)
footer = get_config(10)
context.update({
'pages': pages,
'footer': footer,
})
return context

Wyświetl plik

@ -1,11 +1,11 @@
# Generated by Django 3.0.1 on 2020-01-02 18:31
# Generated by Django 3.0.1 on 2020-01-02 20:42
import ckeditor.fields
import cms.models
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import embed_video.fields
import markdownfield.models
class Migration(migrations.Migration):
@ -13,8 +13,8 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
migrations.swappable_dependency(settings.CMS_PAGE_MODEL),
('contenttypes', '0002_remove_content_type_name'),
]
operations = [
@ -42,7 +42,8 @@ class Migration(migrations.Migration):
('position', models.PositiveIntegerField(blank=True, verbose_name='position')),
('title', cms.models.VarCharField(blank=True, verbose_name='title')),
('color', models.PositiveIntegerField(choices=[(1, 'Wit')], default=1, verbose_name='color')),
('content', ckeditor.fields.RichTextField(blank=True, verbose_name='content')),
('content', markdownfield.models.MarkdownField(blank=True, verbose_name='content')),
('content_rendered', markdownfield.models.RenderedMarkdownField(editable=False)),
('image', models.ImageField(blank=True, upload_to='', verbose_name='image')),
('video', embed_video.fields.EmbedVideoField(blank=True, help_text='Paste a YouTube, Vimeo, or SoundCloud link', verbose_name='video')),
('button_text', cms.models.VarCharField(blank=True, verbose_name='button text')),

Wyświetl plik

@ -7,6 +7,7 @@ except ImportError:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
PROJECT_NAME = 'example'
SITE_URL = 'https://example.com/'
KEYFILE = f'/tmp/{PROJECT_NAME}.secret'
ADMINS = [('JJ Vens', 'jj@rtts.eu')]
ALLOWED_HOSTS = ['*']
@ -23,6 +24,8 @@ MEDIA_URL = '/media/'
MEDIA_ROOT = '/srv/' + PROJECT_NAME + '/media'
LOGIN_REDIRECT_URL = '/'
CMS_SECTION_MODEL = 'app.Section'
MARKDOWN_EXTENSIONS = ['extra', 'smarty']
MARKDOWN_EXTENSION_CONFIGS = {'extra': {}, 'smarty': {}}
def read(file):
with open(file) as f:
@ -36,34 +39,10 @@ except IOError:
SECRET_KEY = ''.join(random.choice(string.printable) for x in range(50))
write(KEYFILE, SECRET_KEY)
SECTION_TYPES = [
('TextSection', 'Tekst'),
('ImageSection', 'Afbeelding'),
]
SECTION_COLORS = [
(1, 'Wit'),
]
CKEDITOR_CONFIGS = {
'default': {
'removePlugins': 'elementspath',
'extraPlugins': 'format',
'width': '100%',
'toolbar': 'Custom',
# 'contentsCss': STATIC_URL + 'ckeditor.css',
# 'allowedContent': True, # this allows iframes, embeds, scripts, etc...
'toolbar_Custom': [
['Format'],
['Bold', 'Italic', 'Underline', 'TextColor'],
['NumberedList', 'BulletedList', 'Blockquote'],
['JustifyLeft', 'JustifyCenter', 'JustifyRight'],
['Link', 'Unlink'],
['RemoveFormat', 'Source'],
],
}
}
INSTALLED_APPS = [
'app',
'django.contrib.admin',
@ -74,7 +53,6 @@ INSTALLED_APPS = [
'django.contrib.staticfiles',
'cms',
'simplesass',
'ckeditor',
'polymorphic',
'embed_video',
'easy_thumbnails',

Wyświetl plik

@ -3,7 +3,7 @@ from setuptools import setup, find_packages
setup(
name = 'django-simplecms',
version = '2.1.0',
version = '2.2.0',
url = 'https://github.com/rtts/django-simplecms',
author = 'Jaap Joris Vens',
author_email = 'jj@rtts.eu',
@ -13,12 +13,13 @@ setup(
include_package_data = True,
install_requires = [
'django',
'django-ckeditor',
'django-extensions',
'django-embed-video',
'django-polymorphic',
'django-markdownfield',
'easy-thumbnails',
'psycopg2',
'markdown',
'libsass',
'swapper',
],