From 3cc1f9ec0839f8bb8edfe1b5c1b8a43da95d9eba Mon Sep 17 00:00:00 2001 From: Jaap Joris Vens Date: Sat, 21 Mar 2020 18:49:41 +0100 Subject: [PATCH] New version! Modelled after Django's admin application, simplecms now allows you to define everything related to it in the file 'cms.py'. No more inherited proxy models and no more migrations every time you add/remove a section type! In fact, the dependency on django-polymorphic has completely been removed! The example project has been updated and should get you started. Documentation will be coming soon! --- cms/__init__.py | 3 + cms/cms.py | 25 ++ cms/decorators.py | 35 ++- cms/forms.py | 24 +- .../0003_remove_section_polymorphic_ctype.py | 17 ++ cms/models.py | 28 +- cms/static/cms/admin.scss | 18 ++ cms/static/cms/admin.scss.css | 14 + cms/static/cms/admin.scss.css.map | 2 +- cms/static/cms/cms.scss | 246 ---------------- cms/static/cms/cms.scss.css | 173 ----------- cms/static/cms/cms.scss.css.map | 2 +- cms/templates/cms/base.html | 7 +- cms/templates/cms/page.html | 6 +- cms/templatetags/cms.py | 18 +- cms/templatetags/eval.py | 20 ++ cms/templatetags/markdown.py | 15 - cms/views.py | 112 ++------ example/app/__init__.py | 1 - example/app/apps.py | 4 - example/app/cms.py | 25 ++ example/app/forms.py | 0 example/app/migrations/0001_initial.py | 14 +- example/app/migrations/0002_sectionimage.py | 26 ++ example/app/models.py | 42 +-- example/app/static/app/main1.scss | 271 ++++++++++++++++-- example/app/static/app/main1.scss.css | 187 +++++++++++- example/app/static/app/main1.scss.css.map | 2 +- .../app/templates/app/sections/button.html | 7 - .../app/templates/app/sections/contact.html | 4 + example/app/templates/app/sections/image.html | 11 - .../app/templates/app/sections/images.html | 15 + example/app/templates/app/sections/text.html | 8 +- example/app/templates/app/sections/video.html | 20 +- example/app/templates/base.html | 3 +- example/app/views.py | 27 -- example/project/settings.py | 5 +- 37 files changed, 734 insertions(+), 703 deletions(-) create mode 100644 cms/cms.py create mode 100644 cms/migrations/0003_remove_section_polymorphic_ctype.py create mode 100644 cms/templatetags/eval.py delete mode 100644 cms/templatetags/markdown.py delete mode 100644 example/app/apps.py create mode 100644 example/app/cms.py delete mode 100644 example/app/forms.py create mode 100644 example/app/migrations/0002_sectionimage.py delete mode 100644 example/app/templates/app/sections/button.html delete mode 100644 example/app/templates/app/sections/image.html create mode 100644 example/app/templates/app/sections/images.html delete mode 100644 example/app/views.py diff --git a/cms/__init__.py b/cms/__init__.py index fbe6e55..3268ce3 100644 --- a/cms/__init__.py +++ b/cms/__init__.py @@ -1 +1,4 @@ +from .decorators import register +from .cms import SectionView, SectionFormView + default_app_config = 'cms.apps.CmsConfig' diff --git a/cms/cms.py b/cms/cms.py new file mode 100644 index 0000000..30e8ba4 --- /dev/null +++ b/cms/cms.py @@ -0,0 +1,25 @@ +from django.views.generic import edit +from django.http import HttpResponseRedirect + +class SectionView: + '''Generic section view''' + template_name = 'cms/sections/section.html' + + def __init__(self, request): + '''Initialize request attribute''' + self.request = request + + def get_context_data(self, **kwargs): + '''Override this to customize a section's context''' + return kwargs + +class SectionFormView(edit.FormMixin, SectionView): + '''Generic section with associated form''' + + def post(self, request): + '''Process form''' + form = self.get_form() + if form.is_valid(): + form.save(request) + return HttpResponseRedirect(self.get_success_url()) + return form diff --git a/cms/decorators.py b/cms/decorators.py index 1b6c836..26ae877 100644 --- a/cms/decorators.py +++ b/cms/decorators.py @@ -1,14 +1,25 @@ -def register_model(verbose_name): - '''Decorator to register a section subclass''' - def wrapper(model): - parent_model = model.__bases__[-1] - parent_model.TYPES.append((model.__name__.lower(), verbose_name)) - return model +def register(verbose_name): + import swapper + Section = swapper.load_model('cms', 'Section') + + '''Decorator to register a specific section type''' + def wrapper(view): + Section._cms_views[view.__name__.lower()] = view + Section.TYPES.append((view.__name__.lower(), verbose_name)) + return view return wrapper -def register_view(section_class): - '''Decorator to connect a section model to a view class''' - def wrapper(model): - section_class.view_class = model - return model - return wrapper +# def register_model(verbose_name): +# '''Decorator to register a section subclass''' +# def wrapper(model): +# parent_model = model.__bases__[-1] +# parent_model.TYPES.append((model.__name__.lower(), verbose_name)) +# return model +# return wrapper + +# def register_view(section_class): +# '''Decorator to connect a section model to a view class''' +# def wrapper(model): +# section_class.view_class = model +# return model +# return wrapper diff --git a/cms/forms.py b/cms/forms.py index 5134b27..d27616c 100644 --- a/cms/forms.py +++ b/cms/forms.py @@ -1,6 +1,7 @@ import swapper from django import forms from django.conf import settings +from django.db.models import Prefetch from django.contrib.contenttypes.models import ContentType from django.core.mail import EmailMessage from django.utils.translation import gettext_lazy as _ @@ -13,7 +14,6 @@ class PageForm(forms.ModelForm): super().__init__(*args, **kwargs) self.label_suffix = '' extra = 1 if self.instance.pk else 2 - self.formsets = [forms.inlineformset_factory( parent_model = Page, model = Section, @@ -37,7 +37,7 @@ class PageForm(forms.ModelForm): def clean(self): super().clean() if not self.formsets[0].is_valid(): - self.add_error(None, _('There’s a problem saving one of the sections')) + self.add_error(None, repr(self.formsets[0].errors)) def save(self, *args, **kwargs): page = super().save() @@ -60,21 +60,21 @@ class SectionForm(forms.ModelForm): super().__init__(*args, **kwargs) self.label_suffix = '' self.fields['DELETE'] = forms.BooleanField(label=_('Delete'), required=False) + extra = 1 if self.instance.pk else 2 # Repopulate the 'choices' attribute of the type field from # the child model. self.fields['type'].choices = self._meta.model.TYPES self.fields['type'].initial = self._meta.model.TYPES[0][0] - # Populate the 'formsets' attribute if the Section was # extendend with one_to_many fields self.formsets = [] for field in self.instance._meta.get_fields(): if field.one_to_many: - extra = 1 if getattr(self.instance, field.name).exists() else 2 formset = forms.inlineformset_factory( - Section, field.related_model, + parent_model=Section, + model=field.related_model, fields='__all__', extra=extra, )( @@ -107,19 +107,9 @@ class SectionForm(forms.ModelForm): if section.page.slug and not section.page.sections.exists(): section.page.delete() return - - # Explanation: get the content type of the model that the user - # supplied when filling in this form, and save it's id to the - # 'polymorphic_ctype_id' field. The next time the object is - # requested from the database, django-polymorphic will convert - # it to the correct subclass. - section.polymorphic_ctype = ContentType.objects.get( - app_label=section._meta.app_label, - model=section.type.lower(), - ) - - if commit: + elif commit: section.save() + for formset in self.formsets: formset.save(commit=commit) diff --git a/cms/migrations/0003_remove_section_polymorphic_ctype.py b/cms/migrations/0003_remove_section_polymorphic_ctype.py new file mode 100644 index 0000000..45b239e --- /dev/null +++ b/cms/migrations/0003_remove_section_polymorphic_ctype.py @@ -0,0 +1,17 @@ +# Generated by Django 3.0.3 on 2020-03-21 10:23 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('cms', '0002_initial_homepage'), + ] + + operations = [ + migrations.RemoveField( + model_name='section', + name='polymorphic_ctype', + ), + ] diff --git a/cms/models.py b/cms/models.py index 96889f5..96f6def 100644 --- a/cms/models.py +++ b/cms/models.py @@ -2,11 +2,12 @@ import swapper from django.db import models from django.urls import reverse -from django.forms import TextInput, Select from django.utils.text import slugify +from django.forms import TextInput, Select from django.utils.translation import gettext_lazy as _ +from django.core.exceptions import ImproperlyConfigured + from embed_video.fields import EmbedVideoField -from polymorphic.models import PolymorphicModel class VarCharField(models.TextField): '''Variable width CharField''' @@ -86,9 +87,13 @@ class BasePage(Numbered, models.Model): verbose_name_plural = _('Pages') ordering = ['number'] -class BaseSection(Numbered, PolymorphicModel): +class BaseSection(Numbered, models.Model): '''Abstract base model for sections''' - TYPES = [] # Will be populated by @register_model() + + # These will be populated by @register + TYPES = [] + _cms_views = {} + page = models.ForeignKey(swapper.get_model_name('cms', 'Page'), verbose_name=_('page'), related_name='sections', on_delete=models.PROTECT) title = VarCharField(_('section')) type = VarCharField(_('type')) @@ -113,6 +118,21 @@ class BaseSection(Numbered, PolymorphicModel): else: return self.title + def get_view(self, request): + '''Try to instantiate the registered view for this section''' + try: + return self.__class__._cms_views[self.type](request) + except: + raise ImproperlyConfigured( + f'No view registered for sections of type {self.type}!') + + @classmethod + def get_fields_per_type(cls): + fields_per_type = {} + for name, view in cls._cms_views.items(): + fields_per_type[name] = ['type', 'number'] + view.fields + return fields_per_type + class Meta: abstract = True verbose_name = _('section') diff --git a/cms/static/cms/admin.scss b/cms/static/cms/admin.scss index 4d3b563..1fb1c45 100644 --- a/cms/static/cms/admin.scss +++ b/cms/static/cms/admin.scss @@ -7,6 +7,24 @@ html, body { padding: 0; } +div.edit { + position: fixed; + right: 1em; + bottom: 1em; + z-index: 1000; + + button { + padding: 0; + outline: none; + background: none; + border: none; + } + img { + width: 75px !important; + height: auto; + } +} + /* Form elements */ form.cms { diff --git a/cms/static/cms/admin.scss.css b/cms/static/cms/admin.scss.css index 9774b05..035cf20 100644 --- a/cms/static/cms/admin.scss.css +++ b/cms/static/cms/admin.scss.css @@ -4,6 +4,20 @@ html, body { margin: 0; padding: 0; } +div.edit { + position: fixed; + right: 1em; + bottom: 1em; + z-index: 1000; } + div.edit button { + padding: 0; + outline: none; + background: none; + border: none; } + div.edit img { + width: 75px !important; + height: auto; } + /* Form elements */ form.cms { background: #bfb; diff --git a/cms/static/cms/admin.scss.css.map b/cms/static/cms/admin.scss.css.map index ef8a961..f252a3d 100644 --- a/cms/static/cms/admin.scss.css.map +++ b/cms/static/cms/admin.scss.css.map @@ -5,5 +5,5 @@ "admin.scss" ], "names": [], - "mappings": "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" + "mappings": "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" } \ No newline at end of file diff --git a/cms/static/cms/cms.scss b/cms/static/cms/cms.scss index 5ecbfd7..e69de29 100644 --- a/cms/static/cms/cms.scss +++ b/cms/static/cms/cms.scss @@ -1,246 +0,0 @@ -$font: sans-serif; -$small: 500px; -$medium: 800px; -$blue: #3573a8; - -html, body { - font-family: $font; - line-height: 1.33; - margin: 0; - padding: 0; -} - -a { - color: $blue; - text-decoration: none; - &:hover { - text-decoration: underline; - } -} - -table { - border-collapse: collapse; - th, td { - padding: 1em; - } - th { - border-bottom: 2px solid black; - } -} - -div.wrapper { - box-sizing: border-box; - max-width: 700px; - margin: auto; - padding: 0 1rem; -} - -div.spacer { - height: 1rem; - clear: both; -} - -nav { - padding: 0; - - button#hamburger { - position: absolute; - z-index: 1; - top: 0; - right: 0; - - .hamburger-inner, .hamburger-inner:before, .hamburger-inner:after { - background: $blue; - } - &.is-active { - position: fixed; - } - &:hover { - opacity: 1 !important; - } - &:focus { - outline: none !important; - } - } - - ul { - border-top: 2px solid $blue; - border-bottom: 2px solid $blue; - list-style: none; - margin: 0; - padding: 0; - text-align: center; - overflow: hidden; - display: flex; - flex-wrap: wrap; - justify-content: center; - align-items: center; - align-content: start; - - li { - margin: 0; - padding: 0; - display: inline-block; - - a { - font-size: 1.25rem; - padding: 5px .5em; - transition: .1s ease; - display: inline-block; - font-weight: bold; - } - } - } - - @media(min-width: $small) { - a:hover:not(.edit), a.current { - text-decoration: none; - color: white; - background: $blue; - } - - button#hamburger { - display: none; - } - } - - @media(max-width: $small) { - padding: 0; - - button#hamburger { - display: block; - } - ul#menu { - position: fixed; - overflow-y: auto; - z-index: 1; - margin: 0; - padding: 0; - padding-top: 2em; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: white; - list-style: none; - - li { - width: 100%; - box-sizing: border-box; - padding: 1em; - margin: 0 1em; - border-bottom: 1px solid #ddd; - line-height: 1.5; - text-align: center; - - a { - text-decoration: none; - } - } - - transition: .5s ease; - transform: translatex(100%); - &.visible { - transform: translatex(0); - } - } - } -} - -div.edit { - text-align: center; - &.page { - position: fixed; - right: 1em; - bottom: 1em; - z-index: 1000; - button { - padding: 0; - outline: none; - } - img { - width: 75px; - height: auto; - } - a:before, button:before, a:after, button:after { - display: none; - } - } -} - -div.edit a, div.edit button, a.edit{ - font-family: inherit; - font-size: 1rem !important; - font-weight: normal !important; - color: red !important; - text-transform: none !important; - text-decoration: none; - color: inherit; - border: none; - display: inline; - background: none; - cursor: pointer; - - &:before { - content: '[ '; - } - &:after { - content: ' ]'; - } -} - -section { - clear: both; - - div.image { - img { - width: 100%; - } - } - - div.title { - text-align: center; - } - - div.content { - } - - div.video { - div.iframe { - width: 100%; - padding-bottom: 56%; - position: relative; - - iframe { - position: absolute; - width: 100%; - height: 100%; - left: 0; - top: 0; - } - } - } - - div.button { - text-align: center; - padding: 1em 0; - } -} - -section.contact { - div.message { - display: none; - } - div.formfield { - padding: 0.5em 0; - } - form input, form textarea { - box-sizing: border-box; - font-family: inherit; - font-size: inherit; - display: block; - width: 100%; - padding: 0.5em; - margin: 0; - } -} diff --git a/cms/static/cms/cms.scss.css b/cms/static/cms/cms.scss.css index 36f648e..41d5f46 100644 --- a/cms/static/cms/cms.scss.css +++ b/cms/static/cms/cms.scss.css @@ -1,175 +1,2 @@ -html, body { - font-family: sans-serif; - line-height: 1.33; - margin: 0; - padding: 0; } - -a { - color: #3573a8; - text-decoration: none; } - a:hover { - text-decoration: underline; } - -table { - border-collapse: collapse; } - table th, table td { - padding: 1em; } - table th { - border-bottom: 2px solid black; } - -div.wrapper { - box-sizing: border-box; - max-width: 700px; - margin: auto; - padding: 0 1rem; } - -div.spacer { - height: 1rem; - clear: both; } - -nav { - padding: 0; } - nav button#hamburger { - position: absolute; - z-index: 1; - top: 0; - right: 0; } - nav button#hamburger .hamburger-inner, nav button#hamburger .hamburger-inner:before, nav button#hamburger .hamburger-inner:after { - background: #3573a8; } - nav button#hamburger.is-active { - position: fixed; } - nav button#hamburger:hover { - opacity: 1 !important; } - nav button#hamburger:focus { - outline: none !important; } - nav ul { - border-top: 2px solid #3573a8; - border-bottom: 2px solid #3573a8; - list-style: none; - margin: 0; - padding: 0; - text-align: center; - overflow: hidden; - display: flex; - flex-wrap: wrap; - justify-content: center; - align-items: center; - align-content: start; } - nav ul li { - margin: 0; - padding: 0; - display: inline-block; } - nav ul li a { - font-size: 1.25rem; - padding: 5px .5em; - transition: .1s ease; - display: inline-block; - font-weight: bold; } - @media (min-width: 500px) { - nav a:hover:not(.edit), nav a.current { - text-decoration: none; - color: white; - background: #3573a8; } - nav button#hamburger { - display: none; } } - @media (max-width: 500px) { - nav { - padding: 0; } - nav button#hamburger { - display: block; } - nav ul#menu { - position: fixed; - overflow-y: auto; - z-index: 1; - margin: 0; - padding: 0; - padding-top: 2em; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: white; - list-style: none; - transition: .5s ease; - transform: translatex(100%); } - nav ul#menu li { - width: 100%; - box-sizing: border-box; - padding: 1em; - margin: 0 1em; - border-bottom: 1px solid #ddd; - line-height: 1.5; - text-align: center; } - nav ul#menu li a { - text-decoration: none; } - nav ul#menu.visible { - transform: translatex(0); } } -div.edit { - text-align: center; } - div.edit.page { - position: fixed; - right: 1em; - bottom: 1em; - z-index: 1000; } - div.edit.page button { - padding: 0; - outline: none; } - div.edit.page img { - width: 75px; - height: auto; } - div.edit.page a:before, div.edit.page button:before, div.edit.page a:after, div.edit.page button:after { - display: none; } - -div.edit a, div.edit button, a.edit { - font-family: inherit; - font-size: 1rem !important; - font-weight: normal !important; - color: red !important; - text-transform: none !important; - text-decoration: none; - color: inherit; - border: none; - display: inline; - background: none; - cursor: pointer; } - div.edit a:before, div.edit button:before, a.edit:before { - content: '[ '; } - div.edit a:after, div.edit button:after, a.edit:after { - content: ' ]'; } - -section { - clear: both; } - section div.image img { - width: 100%; } - section div.title { - text-align: center; } - section div.video div.iframe { - width: 100%; - padding-bottom: 56%; - position: relative; } - section div.video div.iframe iframe { - position: absolute; - width: 100%; - height: 100%; - left: 0; - top: 0; } - section div.button { - text-align: center; - padding: 1em 0; } - -section.contact div.message { - display: none; } - -section.contact div.formfield { - padding: 0.5em 0; } - -section.contact form input, section.contact form textarea { - box-sizing: border-box; - font-family: inherit; - font-size: inherit; - display: block; - width: 100%; - padding: 0.5em; - margin: 0; } /*# sourceMappingURL=cms.scss.css.map */ \ No newline at end of file diff --git a/cms/static/cms/cms.scss.css.map b/cms/static/cms/cms.scss.css.map index 57d0dc9..b5590bb 100644 --- a/cms/static/cms/cms.scss.css.map +++ b/cms/static/cms/cms.scss.css.map @@ -5,5 +5,5 @@ "cms.scss" ], "names": [], - "mappings": "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" + "mappings": "" } \ No newline at end of file diff --git a/cms/templates/cms/base.html b/cms/templates/cms/base.html index fbd3e5d..aeed746 100644 --- a/cms/templates/cms/base.html +++ b/cms/templates/cms/base.html @@ -1,8 +1,8 @@ -{% load static %} -{% load i18n %} +{% load static i18n %} +{% get_current_language as lang%} - + {% block title %}{% endblock %} @@ -49,7 +49,6 @@ diff --git a/cms/templates/cms/page.html b/cms/templates/cms/page.html index 365f2a7..e701bd7 100644 --- a/cms/templates/cms/page.html +++ b/cms/templates/cms/page.html @@ -1,17 +1,15 @@ {% extends 'base.html' %} -{% load static %} -{% load cms %} +{% load static cms %} {% block title %}{{block.super}} - {{page.title}}{% endblock %} {% block content %} {% for section in sections %} -
{% include_section section %} {% endfor %} {% if perms.cms_page.change %} -
+
{% endif %} diff --git a/cms/templatetags/cms.py b/cms/templatetags/cms.py index 6be1d41..fc6d1c3 100644 --- a/cms/templatetags/cms.py +++ b/cms/templatetags/cms.py @@ -14,17 +14,21 @@ class IncludeSectionNode(template.Node): def __init__(self, section): self.section = template.Variable(section) self.csrf_token = template.Variable('csrf_token') + self.request = template.Variable('request') self.perms = template.Variable('perms') super().__init__() def render(self, context): section = self.section.resolve(context) - template_name = section.view.template_name csrf_token = self.csrf_token.resolve(context) + request = self.request.resolve(context) perms = self.perms.resolve(context) - section.context.update({ - 'csrf_token': csrf_token, - 'perms': perms, - }) - t = context.template.engine.get_template(template_name) - return t.render(template.Context(section.context)) + view = section.get_view(request) + section_context = view.get_context_data( + csrf_token=csrf_token, + section=section, + request=request, + perms=perms, + ) + t = context.template.engine.get_template(view.template_name) + return t.render(template.Context(section_context)) diff --git a/cms/templatetags/eval.py b/cms/templatetags/eval.py new file mode 100644 index 0000000..9790a0a --- /dev/null +++ b/cms/templatetags/eval.py @@ -0,0 +1,20 @@ +from markdown import markdown as md +from django import template +from django.utils.safestring import mark_safe + +MARKDOWN_EXTENSIONS = ['extra', 'smarty'] +register = template.Library() + +@register.simple_tag(takes_context=True) +def eval(context, expr): + '''USE WITH CAUTION!!! + + This template tag runs its argument through Django's templating + system using the current context, placing all power into the + hands of the content editors. + + Also, it applies Markdown. + + ''' + result = template.Template(expr).render(context) + return mark_safe(md(result, extensions=MARKDOWN_EXTENSIONS)) diff --git a/cms/templatetags/markdown.py b/cms/templatetags/markdown.py deleted file mode 100644 index 63a2437..0000000 --- a/cms/templatetags/markdown.py +++ /dev/null @@ -1,15 +0,0 @@ -from markdown import markdown as md -from django import template -from django.conf import settings -from django.utils.safestring import mark_safe - -EXTENSIONS = getattr(settings, 'MARKDOWN_EXTENSIONS', []) - -register = template.Library() - -@register.filter(is_safe=True) -def markdown(value): - '''Runs Markdown over a given value - - ''' - return mark_safe(md(value, extensions=EXTENSIONS)) diff --git a/cms/views.py b/cms/views.py index 16a41f0..a0f9fe2 100644 --- a/cms/views.py +++ b/cms/views.py @@ -3,45 +3,16 @@ import swapper from django.shortcuts import redirect from django.views.generic import base, detail, edit -from django.http import Http404, HttpResponse, HttpResponseRedirect -from django.utils.translation import gettext_lazy as _ -from django.core.exceptions import ImproperlyConfigured -from django.contrib.contenttypes.models import ContentType from django.contrib.auth.mixins import UserPassesTestMixin +from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseBadRequest -from .decorators import register_view from .forms import PageForm, SectionForm Page = swapper.load_model('cms', 'Page') Section = swapper.load_model('cms', 'Section') -@register_view(Section) -class SectionView: - '''Generic section view''' - template_name = 'cms/sections/section.html' - - def setup(self, request, section): - '''Initialize request and section attributes''' - self.request = request - self.section = section - - def get_context_data(self, **kwargs): - '''Override this to customize a section's context''' - return kwargs - -class SectionFormView(edit.FormMixin, SectionView): - '''Generic section with associated form''' - - def post(self, request): - '''Process form''' - form = self.get_form() - if form.is_valid(): - form.save(request) - return HttpResponseRedirect(self.get_success_url()) - return form - class PageView(detail.DetailView): - '''View of a page with heterogeneous (polymorphic) sections''' + '''View of a page with heterogeneous sections''' model = Page template_name = 'cms/page.html' @@ -49,27 +20,16 @@ class PageView(detail.DetailView): '''Supply a default argument for slug''' super().setup(*args, slug=slug, **kwargs) - def initialize_section(self, section): - section.view = section.__class__.view_class() - section.view.setup(self.request, section) - section.context = section.view.get_context_data( - request = self.request, - section = section, - ) - def get(self, request, *args, **kwargs): - '''Initialize sections and render final response''' + '''Instantiate section views and render final response''' try: page = self.object = self.get_object() except Http404: if self.request.user.has_perm('cms_page_create'): return redirect('cms:updatepage', self.kwargs['slug']) - else: - raise + raise context = self.get_context_data(**kwargs) sections = page.sections.all() - for section in sections: - self.initialize_section(section) context.update({ 'page': page, 'sections': sections, @@ -77,8 +37,7 @@ class PageView(detail.DetailView): return self.render_to_response(context) def post(self, request, **kwargs): - '''Initialize sections and call the post() function of the correct - section view''' + '''Call the post() method of the correct section view''' try: pk = int(self.request.POST.get('section')) except: @@ -88,11 +47,9 @@ class PageView(detail.DetailView): context = self.get_context_data(**kwargs) sections = page.sections.all() for section in sections: - self.initialize_section(section) if section.pk == pk: - result = section.view.post(request) - if isinstance(result, HttpResponseRedirect): - return result + view = section.get_view(request) + result = view.post(request) if isinstance(result, HttpResponse): return result section.context['form'] = result @@ -112,33 +69,26 @@ class PageView(detail.DetailView): return context class EditPage(UserPassesTestMixin, edit.ModelFormMixin, base.TemplateResponseMixin, base.View): + '''Base view with nested forms for editing the page and all its sections''' model = Page form_class = PageForm template_name = 'cms/edit.html' def test_func(self): - app, model = swapper.get_model_name('cms', 'page').lower().split('.') - return self.request.user.has_perm('f{app}_{model}_change') + '''Only allow users with the correct permissions''' + return self.request.user.has_perm('cms_page_change') def get_form_kwargs(self): + '''Set the default slug to the current URL for new pages''' kwargs = super().get_form_kwargs() if 'slug' in self.kwargs: kwargs.update({'initial': {'slug': self.kwargs['slug']}}) return kwargs def get_context_data(self, **kwargs): + '''Populate the fields_per_type dict for use in javascript''' context = super().get_context_data(**kwargs) - fields_per_type = {} - for model, _ in Section.TYPES: - ctype = ContentType.objects.get( - app_label = Section._meta.app_label, - model = model.lower(), - ) - fields_per_type[ctype.model] = ['type', 'number'] + ctype.model_class().fields - - context.update({ - 'fields_per_type': json.dumps(fields_per_type), - }) + context['fields_per_type'] = json.dumps(Section.get_fields_per_type()) return context def get_object(self): @@ -148,11 +98,13 @@ class EditPage(UserPassesTestMixin, edit.ModelFormMixin, base.TemplateResponseMi except Http404: return None - def get(self, request, *args, **kwargs): + def get(self, *args, **kwargs): + '''Handle GET requests''' self.object = self.get_object() - return self.render_to_response(self.get_context_data()) + return self.render_to_response(self.get_context_data(**kwargs)) - def post(self, request, *args, **kwargs): + def post(self, *args, **kwargs): + '''Handle POST requests''' self.object = self.get_object() form = self.get_form() @@ -161,14 +113,15 @@ class EditPage(UserPassesTestMixin, edit.ModelFormMixin, base.TemplateResponseMi if page: return HttpResponseRedirect(page.get_absolute_url()) return HttpResponseRedirect('/') - return self.render_to_response(self.get_context_data(form=form)) + return self.render_to_response(self.get_context_data(form=form, **kwargs)) class CreatePage(EditPage): + '''View for creating new pages''' def get_object(self): return Page() class UpdatePage(EditPage): - pass + '''View for editing existing pages''' class EditSection(UserPassesTestMixin, edit.ModelFormMixin, base.TemplateResponseMixin, base.View): model = Section @@ -176,8 +129,7 @@ class EditSection(UserPassesTestMixin, edit.ModelFormMixin, base.TemplateRespons template_name = 'cms/edit.html' def test_func(self): - app, model = swapper.get_model_name('cms', 'section').lower().split('.') - return self.request.user.has_perm('f{app}_{model}_change') + return self.request.user.has_perm('cms_section_change') def get_form_kwargs(self): kwargs = super().get_form_kwargs() @@ -188,17 +140,7 @@ class EditSection(UserPassesTestMixin, edit.ModelFormMixin, base.TemplateRespons def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - fields_per_type = {} - for model, _ in Section.TYPES: - ctype = ContentType.objects.get( - app_label = Section._meta.app_label, - model = model.lower(), - ) - fields_per_type[ctype.model] = ['type', 'number'] + ctype.model_class().fields - - context.update({ - 'fields_per_type': json.dumps(fields_per_type), - }) + context['fields_per_type'] = json.dumps(Section.get_fields_per_type()) return context def get_object(self, queryset=None): @@ -215,11 +157,11 @@ class EditSection(UserPassesTestMixin, edit.ModelFormMixin, base.TemplateRespons raise Http404() return section - def get(self, request, *args, **kwargs): + def get(self, *args, **kwargs): self.object = self.get_object() - return self.render_to_response(self.get_context_data()) + return self.render_to_response(self.get_context_data(**kwargs)) - def post(self, request, *args, **kwargs): + def post(self, *args, **kwargs): self.object = self.get_object() form = self.get_form() @@ -231,7 +173,7 @@ class EditSection(UserPassesTestMixin, edit.ModelFormMixin, base.TemplateRespons return HttpResponseRedirect(self.page.get_absolute_url()) else: return HttpResponseRedirect('/') - return self.render_to_response(self.get_context_data(form=form)) + return self.render_to_response(self.get_context_data(form=form, **kwargs)) class CreateSection(EditSection): def get_section(self): diff --git a/example/app/__init__.py b/example/app/__init__.py index 58bdc56..e69de29 100644 --- a/example/app/__init__.py +++ b/example/app/__init__.py @@ -1 +0,0 @@ -default_app_config = 'app.apps.Config' diff --git a/example/app/apps.py b/example/app/apps.py deleted file mode 100644 index 13cc753..0000000 --- a/example/app/apps.py +++ /dev/null @@ -1,4 +0,0 @@ -from django.apps import AppConfig -class Config(AppConfig): - name = 'app' - verbose_name = 'app' diff --git a/example/app/cms.py b/example/app/cms.py new file mode 100644 index 0000000..afb5f26 --- /dev/null +++ b/example/app/cms.py @@ -0,0 +1,25 @@ +import cms +from cms.forms import ContactForm +from django.utils.translation import gettext_lazy as _ + +@cms.register(_('Text')) +class Text(cms.SectionView): + fields = ['title', 'content'] + template_name = 'app/sections/text.html' + +@cms.register(_('Images')) +class Images(cms.SectionView): + fields = ['title', 'images'] + template_name = 'app/sections/images.html' + +@cms.register(_('Video')) +class Video(cms.SectionView): + fields = ['title', 'video'] + template_name = 'app/sections/video.html' + +@cms.register(_('Contact')) +class Contact(cms.SectionFormView): + fields = ['title'] + form_class = ContactForm + success_url = '/thanks/' + template_name = 'app/sections/contact.html' diff --git a/example/app/forms.py b/example/app/forms.py deleted file mode 100644 index e69de29..0000000 diff --git a/example/app/migrations/0001_initial.py b/example/app/migrations/0001_initial.py index c1d6ca3..abfbb72 100644 --- a/example/app/migrations/0001_initial.py +++ b/example/app/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.0.2 on 2020-02-16 14:27 +# Generated by Django 3.0.2 on 2020-03-21 16:44 import cms.models from django.conf import settings @@ -12,7 +12,6 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('contenttypes', '0002_remove_content_type_name'), ] operations = [ @@ -20,9 +19,9 @@ class Migration(migrations.Migration): name='Page', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('number', models.PositiveIntegerField(blank=True, verbose_name='number')), - ('title', cms.models.VarCharField(verbose_name='title')), + ('title', cms.models.VarCharField(verbose_name='page')), ('slug', models.SlugField(blank=True, unique=True, verbose_name='slug')), + ('number', models.PositiveIntegerField(blank=True, verbose_name='number')), ('menu', models.BooleanField(default=True, verbose_name='visible in menu')), ], options={ @@ -37,15 +36,14 @@ class Migration(migrations.Migration): name='Section', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('type', cms.models.VarCharField(blank=True, verbose_name='type')), + ('title', cms.models.VarCharField(verbose_name='section')), + ('type', cms.models.VarCharField(verbose_name='type')), ('number', models.PositiveIntegerField(blank=True, verbose_name='number')), - ('title', cms.models.VarCharField(blank=True, verbose_name='title')), ('content', models.TextField(blank=True, verbose_name='content')), ('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')), - ('href', cms.models.VarCharField(blank=True, verbose_name='button link')), + ('href', cms.models.VarCharField(blank=True, verbose_name='link')), ('page', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='sections', to=settings.CMS_PAGE_MODEL, verbose_name='page')), - ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_app.section_set+', to='contenttypes.ContentType')), ], options={ 'verbose_name': 'section', diff --git a/example/app/migrations/0002_sectionimage.py b/example/app/migrations/0002_sectionimage.py new file mode 100644 index 0000000..8360eda --- /dev/null +++ b/example/app/migrations/0002_sectionimage.py @@ -0,0 +1,26 @@ +# Generated by Django 3.0.2 on 2020-03-21 16:58 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='SectionImage', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('image', models.ImageField(upload_to='', verbose_name='Image')), + ('section', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='images', to=settings.CMS_SECTION_MODEL)), + ], + options={ + 'ordering': ['pk'], + }, + ), + ] diff --git a/example/app/models.py b/example/app/models.py index 485a68d..6024746 100644 --- a/example/app/models.py +++ b/example/app/models.py @@ -1,46 +1,22 @@ from django.db import models -from django.conf import settings from cms.models import BasePage, BaseSection -from cms.decorators import register_model +from django.utils.translation import gettext_lazy as _ class Page(BasePage): - '''Add custom fields here. Already existing fields: position, title, - slug, menu + '''Add custom fields here. Already existing fields: title, slug, + number, menu ''' class Section(BaseSection): - '''Add custom fields here. Already existing fields: type, position, - title, content, image, video, href + '''Add custom fields here. Already existing fields: title, type, + number, content, image, video, href ''' -@register_model('Tekst') -class TextSection(Section): - fields = ['title', 'content'] - class Meta: - proxy = True +class SectionImage(models.Model): + section = models.ForeignKey(Section, related_name='images', on_delete=models.CASCADE) + image = models.ImageField(_('Image')) -@register_model('Button') -class ButtonSection(Section): - fields = ['title', 'href'] class Meta: - proxy = True - -@register_model('Afbeelding') -class ImageSection(Section): - fields = ['title', 'image'] - class Meta: - proxy = True - -@register_model('Video') -class VideoSection(Section): - fields = ['title', 'video'] - class Meta: - proxy = True - -@register_model('Contact') -class ContactSection(Section): - fields = ['title'] - class Meta: - proxy = True + ordering = ['pk'] diff --git a/example/app/static/app/main1.scss b/example/app/static/app/main1.scss index fa1d4c8..570235d 100644 --- a/example/app/static/app/main1.scss +++ b/example/app/static/app/main1.scss @@ -1,10 +1,43 @@ -$small: 500px; -$medium: 800px; $font: sans-serif; $titlefont: sans-serif; +$small: 500px; +$medium: 800px; +$max-width: 700px; +$color1: #3573a8; -body { +html, body { font-family: $font; + line-height: 1.5; + margin: 0; + padding: 0; +} + +a { + color: $color1; + text-decoration: none; + &:hover { + text-decoration: underline; + } +} + +a.button, button.button { + cursor: pointer; + font-family: $titlefont + font-size: 1.5em; + line-height: 1.25; + border-radius: 5px; + display: inline-block; + text-decoration: none; + border: none; + padding: 5px 20px; + background: $color1; + color: white; + box-sizing: border-box; + &:hover { + color: $color1; + background: white; + box-shadow: inset 0 0 0 2px $color1; + } } h1, h2, h3, h4, h5, h6 { @@ -16,32 +49,232 @@ h2 { font-size: 1.5em } h3 { font-size: 1.25em } h4, h5, h6 { font-size: 1em } -a { - &:hover { +a.edit { + color: red; + text-decoration: none; + font-size: 1rem; + font-weight: normal; + &:before { content: '[ ' } + &:after { content: ' ]' } +} +section a.edit { + position: absolute; + bottom: 0; + right: 1em; +} + +table { + border-collapse: collapse; + th, td { + padding: 1em; + } + th { + border-bottom: 2px solid $color1; } } -a.button { - &:hover { - } +div.wrapper { + box-sizing: border-box; + max-width: $max-width; + margin: auto; + padding: 0 1rem; +} + +div.spacer { + height: 1rem; + clear: both; } header { - h1 { - text-align: center; - } - img { - display: block; - width: 100%; - max-width: 400px; - margin: auto; - } + text-align: center; } nav { + button#hamburger { + position: absolute; + z-index: 1; + top: 0; + right: 0; + + .hamburger-inner, .hamburger-inner:before, .hamburger-inner:after { + background: $color1; + } + &.is-active { + position: fixed; + } + &:hover { + opacity: 1 !important; + } + &:focus { + outline: none !important; + } + } + + ul { + border-top: 2px solid $color1; + border-bottom: 2px solid $color1; + list-style: none; + margin: 0; + padding: 0; + text-align: center; + overflow: hidden; + display: flex; + flex-wrap: wrap; + justify-content: center; + align-items: center; + align-content: start; + + li { + margin: 0; + padding: 0; + display: inline-block; + + a { + font-size: 1.25rem; + padding: 5px .5em; + transition: .1s ease; + display: inline-block; + font-weight: bold; + } + } + } + + @media(min-width: $small) { + a:hover:not(.edit), a.current { + text-decoration: none; + color: white; + background: $color1; + } + + button#hamburger { + display: none; + } + } + + @media(max-width: $small) { + padding: 0; + + button#hamburger { + display: block; + } + ul#menu { + position: fixed; + overflow-y: auto; + z-index: 1; + margin: 0; + padding: 0; + padding-top: 2em; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: white; + list-style: none; + + li { + width: 100%; + box-sizing: border-box; + padding: 1em; + margin: 0 1em; + border-bottom: 1px solid #ddd; + line-height: 1.5; + text-align: center; + + a { + text-decoration: none; + } + } + + transition: .5s ease; + transform: translatex(100%); + &.visible { + transform: translatex(0); + } + } + } +} + +div.edit { + position: fixed; + right: 1em; + bottom: 1em; + z-index: 1000; + + button { + padding: 0; + outline: none; + background: none; + border: none; + } + img { + width: 75px; + height: auto; + } +} + +section { + clear: both; + position: relative; + + div.title { + text-align: center; + } + + div.video { + div.iframe { + width: 100%; + padding-bottom: 56%; + position: relative; + + iframe { + position: absolute; + width: 100%; + height: 100%; + left: 0; + top: 0; + } + } + } +} + +section.images { + div.images { + display: flex; + flex-wrap: wrap; + margin: 0.25em; + justify-content: center; + + div.image { + flex: 1 1 100px; + max-width: $max-width; + margin: 0.5em; + + img { + display: block; + width: 100%; + } + } + } +} + +section.contact { + div.message { + display: none; + } + div.formfield { + padding: 0.5em 0; + } + form input, form textarea { + box-sizing: border-box; + font-family: inherit; + font-size: inherit; + display: block; + width: 100%; + padding: 0.5em; + margin: 0; + } } footer { - margin-top: 4em; - min-height: 400px; + min-height: 10em; } diff --git a/example/app/static/app/main1.scss.css b/example/app/static/app/main1.scss.css index 1398449..fe44c41 100644 --- a/example/app/static/app/main1.scss.css +++ b/example/app/static/app/main1.scss.css @@ -1,5 +1,14 @@ -body { - font-family: sans-serif; } +html, body { + font-family: sans-serif; + line-height: 1.5; + margin: 0; + padding: 0; } + +a { + color: #3573a8; + text-decoration: none; } + a:hover { + text-decoration: underline; } h1, h2, h3, h4, h5, h6 { margin: .5em 0; @@ -17,17 +26,179 @@ h3 { h4, h5, h6 { font-size: 1em; } -header h1 { +a.edit { + color: red; + text-decoration: none; + font-size: 1rem; + font-weight: normal; } + a.edit:before { + content: '[ '; } + a.edit:after { + content: ' ]'; } + +section a.edit { + position: absolute; + bottom: 0; + right: 1em; } + +table { + border-collapse: collapse; } + table th, table td { + padding: 1em; } + table th { + border-bottom: 2px solid #3573a8; } + +div.wrapper { + box-sizing: border-box; + max-width: 700px; + margin: auto; + padding: 0 1rem; } + +div.spacer { + height: 1rem; + clear: both; } + +header { text-align: center; } -header img { +nav button#hamburger { + position: absolute; + z-index: 1; + top: 0; + right: 0; } + nav button#hamburger .hamburger-inner, nav button#hamburger .hamburger-inner:before, nav button#hamburger .hamburger-inner:after { + background: #3573a8; } + nav button#hamburger.is-active { + position: fixed; } + nav button#hamburger:hover { + opacity: 1 !important; } + nav button#hamburger:focus { + outline: none !important; } + +nav ul { + border-top: 2px solid #3573a8; + border-bottom: 2px solid #3573a8; + list-style: none; + margin: 0; + padding: 0; + text-align: center; + overflow: hidden; + display: flex; + flex-wrap: wrap; + justify-content: center; + align-items: center; + align-content: start; } + nav ul li { + margin: 0; + padding: 0; + display: inline-block; } + nav ul li a { + font-size: 1.25rem; + padding: 5px .5em; + transition: .1s ease; + display: inline-block; + font-weight: bold; } + +@media (min-width: 500px) { + nav a:hover:not(.edit), nav a.current { + text-decoration: none; + color: white; + background: #3573a8; } + nav button#hamburger { + display: none; } } + +@media (max-width: 500px) { + nav { + padding: 0; } + nav button#hamburger { + display: block; } + nav ul#menu { + position: fixed; + overflow-y: auto; + z-index: 1; + margin: 0; + padding: 0; + padding-top: 2em; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: white; + list-style: none; + transition: .5s ease; + transform: translatex(100%); } + nav ul#menu li { + width: 100%; + box-sizing: border-box; + padding: 1em; + margin: 0 1em; + border-bottom: 1px solid #ddd; + line-height: 1.5; + text-align: center; } + nav ul#menu li a { + text-decoration: none; } + nav ul#menu.visible { + transform: translatex(0); } } + +div.edit { + position: fixed; + right: 1em; + bottom: 1em; + z-index: 1000; } + div.edit button { + padding: 0; + outline: none; + background: none; + border: none; } + div.edit img { + width: 75px; + height: auto; } + +section { + clear: both; + position: relative; } + section div.title { + text-align: center; } + section div.video div.iframe { + width: 100%; + padding-bottom: 56%; + position: relative; } + section div.video div.iframe iframe { + position: absolute; + width: 100%; + height: 100%; + left: 0; + top: 0; } + +section.images div.images { + display: flex; + flex-wrap: wrap; + margin: 0.25em; + justify-content: center; } + section.images div.images div.image { + flex: 1 1 100px; + max-width: 700px; + margin: 0.5em; } + section.images div.images div.image img { + display: block; + width: 100%; } + +section.contact div.message { + display: none; } + +section.contact div.formfield { + padding: 0.5em 0; } + +section.contact form input, section.contact form textarea { + box-sizing: border-box; + font-family: inherit; + font-size: inherit; display: block; width: 100%; - max-width: 400px; - margin: auto; } + padding: 0.5em; + margin: 0; } footer { - margin-top: 4em; - min-height: 400px; } + min-height: 10em; } /*# sourceMappingURL=main1.scss.css.map */ \ No newline at end of file diff --git a/example/app/static/app/main1.scss.css.map b/example/app/static/app/main1.scss.css.map index 33544de..fe213a4 100644 --- a/example/app/static/app/main1.scss.css.map +++ b/example/app/static/app/main1.scss.css.map @@ -5,5 +5,5 @@ "main1.scss" ], "names": [], - "mappings": "AAKA,AAAA,IAAI,CAAC;EACH,WAAW,EAJN,UAAU,GAKhB;;AAED,AAAA,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;EACrB,MAAM,EAAE,MAAM;EACd,WAAW,EARD,UAAU,GASrB;;AACD,AAAA,EAAE,CAAC;EAAE,SAAS,EAAE,GAAI,GAAE;;AACtB,AAAA,EAAE,CAAC;EAAE,SAAS,EAAE,KAAM,GAAE;;AACxB,AAAA,EAAE,CAAC;EAAE,SAAS,EAAE,MAAO,GAAE;;AACzB,AAAA,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;EAAE,SAAS,EAAE,GAAI,GAAE;;AAY9B,AACE,MADI,CACJ,EAAE,CAAC;EACD,UAAU,EAAE,MAAM,GACnB;;AAHH,AAIE,MAJI,CAIJ,GAAG,CAAC;EACF,OAAO,EAAE,KAAK;EACd,KAAK,EAAE,IAAI;EACX,SAAS,EAAE,KAAK;EAChB,MAAM,EAAE,IAAI,GACb;;AAMH,AAAA,MAAM,CAAC;EACL,UAAU,EAAE,GAAG;EACf,UAAU,EAAE,KAAK,GAClB" + "mappings": "AAOA,AAAA,IAAI,EAAE,IAAI,CAAC;EACT,WAAW,EARN,UAAU;EASf,WAAW,EAAE,GAAG;EAChB,MAAM,EAAE,CAAC;EACT,OAAO,EAAE,CAAC,GACX;;AAED,AAAA,CAAC,CAAC;EACA,KAAK,EAVE,OAAO;EAWd,eAAe,EAAE,IAAI,GAItB;EAND,AAGE,CAHD,CAGG,KAAK,CAAC;IACN,eAAe,EAAE,SAAS,GAC3B;;AAGH,AAAA,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;EACrB,MAAM,EAAE,MAAM;EACd,WAAW,EAvBD,UAAU,GAwBrB;;AACD,AAAA,EAAE,CAAC;EAAE,SAAS,EAAE,GAAI,GAAE;;AACtB,AAAA,EAAE,CAAC;EAAE,SAAS,EAAE,KAAM,GAAE;;AACxB,AAAA,EAAE,CAAC;EAAE,SAAS,EAAE,MAAO,GAAE;;AACzB,AAAA,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;EAAE,SAAS,EAAE,GAAI,GAAE;;AAE9B,AAAA,CAAC,AAAA,KAAK,CAAC;EACL,KAAK,EAAE,GAAG;EACV,eAAe,EAAE,IAAI;EACrB,SAAS,EAAE,IAAI;EACf,WAAW,EAAE,MAAM,GAGpB;EAPD,AAKE,CALD,AAAA,KAAK,CAKF,MAAM,CAAC;IAAE,OAAO,EAAE,IAAK,GAAE;EAL7B,AAME,CAND,AAAA,KAAK,CAMF,KAAK,CAAC;IAAE,OAAO,EAAE,IAAK,GAAE;;AAE5B,AAAA,OAAO,CAAC,CAAC,AAAA,KAAK,CAAC;EACb,QAAQ,EAAE,QAAQ;EAClB,MAAM,EAAE,CAAC;EACT,KAAK,EAAE,GAAG,GACX;;AAED,AAAA,KAAK,CAAC;EACJ,eAAe,EAAE,QAAQ,GAO1B;EARD,AAEE,KAFG,CAEH,EAAE,EAFJ,KAAK,CAEC,EAAE,CAAC;IACL,OAAO,EAAE,GAAG,GACb;EAJH,AAKE,KALG,CAKH,EAAE,CAAC;IACD,aAAa,EAAE,GAAG,CAAC,KAAK,CA9CnB,OAAO,GA+Cb;;AAGH,AAAA,GAAG,AAAA,QAAQ,CAAC;EACV,UAAU,EAAE,UAAU;EACtB,SAAS,EArDC,KAAK;EAsDf,MAAM,EAAE,IAAI;EACZ,OAAO,EAAE,MAAM,GAChB;;AAED,AAAA,GAAG,AAAA,OAAO,CAAC;EACT,MAAM,EAAE,IAAI;EACZ,KAAK,EAAE,IAAI,GACZ;;AAED,AAAA,MAAM,CAAC;EACL,UAAU,EAAE,MAAM,GACnB;;AAED,AACE,GADC,CACD,MAAM,AAAA,UAAU,CAAC;EACf,QAAQ,EAAE,QAAQ;EAClB,OAAO,EAAE,CAAC;EACV,GAAG,EAAE,CAAC;EACN,KAAK,EAAE,CAAC,GAcT;EAnBH,AAOI,GAPD,CACD,MAAM,AAAA,UAAU,CAMd,gBAAgB,EAPpB,GAAG,CACD,MAAM,AAAA,UAAU,CAMI,gBAAgB,CAAC,MAAM,EAP7C,GAAG,CACD,MAAM,AAAA,UAAU,CAM6B,gBAAgB,CAAC,KAAK,CAAC;IAChE,UAAU,EA1EP,OAAO,GA2EX;EATL,AAUI,GAVD,CACD,MAAM,AAAA,UAAU,AASb,UAAU,CAAC;IACV,QAAQ,EAAE,KAAK,GAChB;EAZL,AAaI,GAbD,CACD,MAAM,AAAA,UAAU,CAYZ,KAAK,CAAC;IACN,OAAO,EAAE,YAAY,GACtB;EAfL,AAgBI,GAhBD,CACD,MAAM,AAAA,UAAU,CAeZ,KAAK,CAAC;IACN,OAAO,EAAE,eAAe,GACzB;;AAlBL,AAqBE,GArBC,CAqBD,EAAE,CAAC;EACD,UAAU,EAAE,GAAG,CAAC,KAAK,CAxFhB,OAAO;EAyFZ,aAAa,EAAE,GAAG,CAAC,KAAK,CAzFnB,OAAO;EA0FZ,UAAU,EAAE,IAAI;EAChB,MAAM,EAAE,CAAC;EACT,OAAO,EAAE,CAAC;EACV,UAAU,EAAE,MAAM;EAClB,QAAQ,EAAE,MAAM;EAChB,OAAO,EAAE,IAAI;EACb,SAAS,EAAE,IAAI;EACf,eAAe,EAAE,MAAM;EACvB,WAAW,EAAE,MAAM;EACnB,aAAa,EAAE,KAAK,GAerB;EAhDH,AAmCI,GAnCD,CAqBD,EAAE,CAcA,EAAE,CAAC;IACD,MAAM,EAAE,CAAC;IACT,OAAO,EAAE,CAAC;IACV,OAAO,EAAE,YAAY,GAStB;IA/CL,AAwCM,GAxCH,CAqBD,EAAE,CAcA,EAAE,CAKA,CAAC,CAAC;MACA,SAAS,EAAE,OAAO;MAClB,OAAO,EAAE,QAAQ;MACjB,UAAU,EAAE,QAAQ;MACpB,OAAO,EAAE,YAAY;MACrB,WAAW,EAAE,IAAI,GAClB;;AAIL,MAAM,mBACJ;EAnDJ,AAmDI,GAnDD,CAmDC,CAAC,CAAC,KAAK,CAAA,GAAK,CAAA,KAAK,GAnDrB,GAAG,CAmDqB,CAAC,AAAA,QAAQ,CAAC;IAC5B,eAAe,EAAE,IAAI;IACrB,KAAK,EAAE,KAAK;IACZ,UAAU,EAxHP,OAAO,GAyHX;EAvDL,AAyDI,GAzDD,CAyDC,MAAM,AAAA,UAAU,CAAC;IACf,OAAO,EAAE,IAAI,GACd,EAJA;;AAOH,MAAM,mBA9DR;EAAA,AAAA,GAAG,CAAC;IA+DA,OAAO,EAAE,CAAC,GAwCb;IAvGD,AAiEI,GAjED,CAiEC,MAAM,AAAA,UAAU,CAAC;MACf,OAAO,EAAE,KAAK,GACf;IAnEL,AAoEI,GApED,CAoEC,EAAE,AAAA,KAAK,CAAC;MACN,QAAQ,EAAE,KAAK;MACf,UAAU,EAAE,IAAI;MAChB,OAAO,EAAE,CAAC;MACV,MAAM,EAAE,CAAC;MACT,OAAO,EAAE,CAAC;MACV,WAAW,EAAE,GAAG;MAChB,GAAG,EAAE,CAAC;MACN,IAAI,EAAE,CAAC;MACP,KAAK,EAAE,CAAC;MACR,MAAM,EAAE,CAAC;MACT,UAAU,EAAE,KAAK;MACjB,UAAU,EAAE,IAAI;MAgBhB,UAAU,EAAE,QAAQ;MACpB,SAAS,EAAE,gBAAgB,GAI5B;MArGL,AAkFM,GAlFH,CAoEC,EAAE,AAAA,KAAK,CAcL,EAAE,CAAC;QACD,KAAK,EAAE,IAAI;QACX,UAAU,EAAE,UAAU;QACtB,OAAO,EAAE,GAAG;QACZ,MAAM,EAAE,KAAK;QACb,aAAa,EAAE,cAAc;QAC7B,WAAW,EAAE,GAAG;QAChB,UAAU,EAAE,MAAM,GAKnB;QA9FP,AA2FQ,GA3FL,CAoEC,EAAE,AAAA,KAAK,CAcL,EAAE,CASA,CAAC,CAAC;UACA,eAAe,EAAE,IAAI,GACtB;MA7FT,AAkGM,GAlGH,CAoEC,EAAE,AAAA,KAAK,AA8BJ,QAAQ,CAAC;QACR,SAAS,EAAE,aAAa,GACzB,EAGN;;AAED,AAAA,GAAG,AAAA,KAAK,CAAC;EACP,QAAQ,EAAE,KAAK;EACf,KAAK,EAAE,GAAG;EACV,MAAM,EAAE,GAAG;EACX,OAAO,EAAE,IAAI,GAYd;EAhBD,AAME,GANC,AAAA,KAAK,CAMN,MAAM,CAAC;IACL,OAAO,EAAE,CAAC;IACV,OAAO,EAAE,IAAI;IACb,UAAU,EAAE,IAAI;IAChB,MAAM,EAAE,IAAI,GACb;EAXH,AAYE,GAZC,AAAA,KAAK,CAYN,GAAG,CAAC;IACF,KAAK,EAAE,IAAI;IACX,MAAM,EAAE,IAAI,GACb;;AAGH,AAAA,OAAO,CAAC;EACN,KAAK,EAAE,IAAI;EACX,QAAQ,EAAE,QAAQ,GAqBnB;EAvBD,AAIE,OAJK,CAIL,GAAG,AAAA,MAAM,CAAC;IACR,UAAU,EAAE,MAAM,GACnB;EANH,AASI,OATG,CAQL,GAAG,AAAA,MAAM,CACP,GAAG,AAAA,OAAO,CAAC;IACT,KAAK,EAAE,IAAI;IACX,cAAc,EAAE,GAAG;IACnB,QAAQ,EAAE,QAAQ,GASnB;IArBL,AAcM,OAdC,CAQL,GAAG,AAAA,MAAM,CACP,GAAG,AAAA,OAAO,CAKR,MAAM,CAAC;MACL,QAAQ,EAAE,QAAQ;MAClB,KAAK,EAAE,IAAI;MACX,MAAM,EAAE,IAAI;MACZ,IAAI,EAAE,CAAC;MACP,GAAG,EAAE,CAAC,GACP;;AAKP,AACE,OADK,AAAA,OAAO,CACZ,GAAG,AAAA,OAAO,CAAC;EACT,OAAO,EAAE,IAAI;EACb,SAAS,EAAE,IAAI;EACf,MAAM,EAAE,MAAM;EACd,eAAe,EAAE,MAAM,GAYxB;EAjBH,AAOI,OAPG,AAAA,OAAO,CACZ,GAAG,AAAA,OAAO,CAMR,GAAG,AAAA,MAAM,CAAC;IACR,IAAI,EAAE,SAAS;IACf,SAAS,EAhOH,KAAK;IAiOX,MAAM,EAAE,KAAK,GAMd;IAhBL,AAYM,OAZC,AAAA,OAAO,CACZ,GAAG,AAAA,OAAO,CAMR,GAAG,AAAA,MAAM,CAKP,GAAG,CAAC;MACF,OAAO,EAAE,KAAK;MACd,KAAK,EAAE,IAAI,GACZ;;AAKP,AACE,OADK,AAAA,QAAQ,CACb,GAAG,AAAA,QAAQ,CAAC;EACV,OAAO,EAAE,IAAI,GACd;;AAHH,AAIE,OAJK,AAAA,QAAQ,CAIb,GAAG,AAAA,UAAU,CAAC;EACZ,OAAO,EAAE,OAAO,GACjB;;AANH,AAOE,OAPK,AAAA,QAAQ,CAOb,IAAI,CAAC,KAAK,EAPZ,OAAO,AAAA,QAAQ,CAOD,IAAI,CAAC,QAAQ,CAAC;EACxB,UAAU,EAAE,UAAU;EACtB,WAAW,EAAE,OAAO;EACpB,SAAS,EAAE,OAAO;EAClB,OAAO,EAAE,KAAK;EACd,KAAK,EAAE,IAAI;EACX,OAAO,EAAE,KAAK;EACd,MAAM,EAAE,CAAC,GACV;;AAGH,AAAA,MAAM,CAAC;EACL,UAAU,EAAE,IAAI,GACjB" } \ No newline at end of file diff --git a/example/app/templates/app/sections/button.html b/example/app/templates/app/sections/button.html deleted file mode 100644 index 58a8925..0000000 --- a/example/app/templates/app/sections/button.html +++ /dev/null @@ -1,7 +0,0 @@ -
- -
diff --git a/example/app/templates/app/sections/contact.html b/example/app/templates/app/sections/contact.html index 3213987..18e5774 100644 --- a/example/app/templates/app/sections/contact.html +++ b/example/app/templates/app/sections/contact.html @@ -15,4 +15,8 @@
+ + {% if perms.cms_section_change %} + {% trans 'edit' %}
+ {% endif %} diff --git a/example/app/templates/app/sections/image.html b/example/app/templates/app/sections/image.html deleted file mode 100644 index bb9748c..0000000 --- a/example/app/templates/app/sections/image.html +++ /dev/null @@ -1,11 +0,0 @@ -{% load thumbnail %} - -
-
- {% if section.image %} -
- {{section.title}} -
- {% endif %} -
-
diff --git a/example/app/templates/app/sections/images.html b/example/app/templates/app/sections/images.html new file mode 100644 index 0000000..517d3e6 --- /dev/null +++ b/example/app/templates/app/sections/images.html @@ -0,0 +1,15 @@ +{% load thumbnail i18n %} + +
+
+ {% for image in section.images.all %} +
+ +
+ {% endfor %} +
+ + {% if perms.cms_section_change %} + {% trans 'edit' %}
+ {% endif %} +
diff --git a/example/app/templates/app/sections/text.html b/example/app/templates/app/sections/text.html index 3681988..4747ed4 100644 --- a/example/app/templates/app/sections/text.html +++ b/example/app/templates/app/sections/text.html @@ -1,4 +1,4 @@ -{% load markdown %} +{% load eval i18n %}
@@ -9,7 +9,11 @@
- {{section.content|markdown}} + {% eval section.content %}
+ + {% if perms.cms_section_change %} + {% trans 'edit' %}
+ {% endif %}
diff --git a/example/app/templates/app/sections/video.html b/example/app/templates/app/sections/video.html index 152831b..851e9ef 100644 --- a/example/app/templates/app/sections/video.html +++ b/example/app/templates/app/sections/video.html @@ -1,13 +1,15 @@ -{% load embed_video_tags %} +{% load embed_video_tags i18n %}
-
- {% if section.video %} -
-
- {% video section.video '800x600' %} -
+ {% if section.video %} +
+
+ {% video section.video '800x600' %}
- {% endif %} -
+
+ {% endif %} + + {% if perms.cms_section_change %} + {% trans 'edit' %}
+ {% endif %}
diff --git a/example/app/templates/base.html b/example/app/templates/base.html index f8067aa..109ceda 100644 --- a/example/app/templates/base.html +++ b/example/app/templates/base.html @@ -1,6 +1,7 @@ {% extends 'cms/base.html' %} {% load static %} -{% block title %}app{% endblock %} + +{% block title %}Awesome Website{% endblock %} {% block extrahead %} diff --git a/example/app/views.py b/example/app/views.py deleted file mode 100644 index 6c38436..0000000 --- a/example/app/views.py +++ /dev/null @@ -1,27 +0,0 @@ -from cms.forms import ContactForm -from cms.views import SectionView, SectionFormView -from cms.decorators import register_view - -from .models import * - -@register_view(TextSection) -class TextView(SectionView): - template_name = 'app/sections/text.html' - -@register_view(ButtonSection) -class ButtonView(SectionView): - template_name = 'app/sections/button.html' - -@register_view(ImageSection) -class ImageView(SectionView): - template_name = 'app/sections/image.html' - -@register_view(VideoSection) -class VideoView(SectionView): - template_name = 'app/sections/video.html' - -@register_view(ContactSection) -class ContactFormView(SectionFormView): - form_class = ContactForm - success_url = '/thanks/' - template_name = 'app/sections/contact.html' diff --git a/example/project/settings.py b/example/project/settings.py index e9e2f85..625fcb0 100644 --- a/example/project/settings.py +++ b/example/project/settings.py @@ -9,6 +9,7 @@ except ImportError: PROJECT_NAME = 'example' KEYFILE = f'/tmp/{PROJECT_NAME}.secret' ADMINS = [('JJ Vens', 'jj@rtts.eu')] +DEFAULT_FROM_EMAIL = 'noreply@rtts.eu' DEFAULT_TO_EMAIL = 'jj@rtts.eu' ALLOWED_HOSTS = ['*'] ROOT_URLCONF = 'project.urls' @@ -24,8 +25,7 @@ MEDIA_URL = '/media/' MEDIA_ROOT = '/srv/' + PROJECT_NAME + '/media' LOGIN_REDIRECT_URL = '/' CMS_SECTION_MODEL = 'app.Section' -CMS_PAGE_MODEL = 'app.Page' # https://github.com/wq/django-swappable-models/issues/18#issuecomment-514039164 -MARKDOWN_EXTENSIONS = ['extra', 'smarty'] +CMS_PAGE_MODEL = 'app.Page' def read(file): with open(file) as f: @@ -48,7 +48,6 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', 'cms', - 'polymorphic', 'embed_video', 'easy_thumbnails', 'django_extensions',