kopia lustrzana https://github.com/rtts/django-simplecms
A new templatetag 'includesection' now renders a section with its own
context, as provided by the polymorphic subsection's registered view. Also, I'm trying to move all the website-related cruft from cms into the example project, so that only the Page and Section models with their own "admin" views will remain.readwriteweb
rodzic
9e1baf6ee1
commit
5f5f303187
20
cms/forms.py
20
cms/forms.py
|
@ -1,5 +1,4 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.core.mail import EmailMessage
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
|
@ -7,25 +6,6 @@ import swapper
|
||||||
Page = swapper.load_model('cms', 'Page')
|
Page = swapper.load_model('cms', 'Page')
|
||||||
Section = swapper.load_model('cms', 'Section')
|
Section = swapper.load_model('cms', 'Section')
|
||||||
|
|
||||||
class ContactForm(forms.Form):
|
|
||||||
sender = forms.EmailField(label=_('Your email address'))
|
|
||||||
spam_protection = forms.CharField(label=_('Your message'), widget=forms.Textarea())
|
|
||||||
message = forms.CharField(label=_('Your message'), widget=forms.Textarea(), initial='Hi there!')
|
|
||||||
|
|
||||||
def save(self, request):
|
|
||||||
hostname = request.get_host()
|
|
||||||
body = self.cleaned_data.get('spam_protection') # MUHAHA
|
|
||||||
if len(body.split()) < 7:
|
|
||||||
return
|
|
||||||
email = EmailMessage(
|
|
||||||
to = ['info@' + hostname],
|
|
||||||
from_email = 'noreply@' + hostname,
|
|
||||||
body = body,
|
|
||||||
subject = _('Contact form at %(hostname)s.') % {'hostname': hostname},
|
|
||||||
headers = {'Reply-To': self.cleaned_data.get('sender')},
|
|
||||||
)
|
|
||||||
email.send()
|
|
||||||
|
|
||||||
class PageForm(forms.ModelForm):
|
class PageForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Page
|
model = Page
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
# Generated by Django 3.0.1 on 2020-01-02 20:41
|
# Generated by Django 3.0.2 on 2020-01-04 22:55
|
||||||
|
|
||||||
import cms.models
|
import cms.models
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
import embed_video.fields
|
import embed_video.fields
|
||||||
import markdownfield.models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -21,9 +20,9 @@ class Migration(migrations.Migration):
|
||||||
name='Page',
|
name='Page',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('position', models.PositiveIntegerField(blank=True, verbose_name='position')),
|
|
||||||
('title', cms.models.VarCharField(verbose_name='title')),
|
|
||||||
('slug', models.SlugField(blank=True, help_text='A short identifier to use in URLs', unique=True, verbose_name='slug')),
|
('slug', models.SlugField(blank=True, help_text='A short identifier to use in URLs', unique=True, verbose_name='slug')),
|
||||||
|
('title', cms.models.VarCharField(verbose_name='title')),
|
||||||
|
('position', models.PositiveIntegerField(blank=True, verbose_name='position')),
|
||||||
('menu', models.BooleanField(default=True, verbose_name='visible in menu')),
|
('menu', models.BooleanField(default=True, verbose_name='visible in menu')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
|
@ -38,12 +37,10 @@ class Migration(migrations.Migration):
|
||||||
name='Section',
|
name='Section',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('type', cms.models.VarCharChoiceField(choices=[('textsection', 'Tekst'), ('imagesection', 'Afbeelding'), ('contactsection', 'Contact')], default='', verbose_name='section type')),
|
('type', cms.models.VarCharChoiceField(choices=[('textsection', 'Tekst'), ('imagesection', 'Afbeelding'), ('contactsection', 'Contact')], default='', verbose_name='type')),
|
||||||
('position', models.PositiveIntegerField(blank=True, verbose_name='position')),
|
|
||||||
('title', cms.models.VarCharField(blank=True, verbose_name='title')),
|
('title', cms.models.VarCharField(blank=True, verbose_name='title')),
|
||||||
('color', models.PositiveIntegerField(choices=[(1, 'Wit')], default=1, verbose_name='color')),
|
('position', models.PositiveIntegerField(blank=True, verbose_name='position')),
|
||||||
('content', markdownfield.models.MarkdownField(blank=True, verbose_name='content')),
|
('content', models.TextField(blank=True, verbose_name='content')),
|
||||||
('content_rendered', markdownfield.models.RenderedMarkdownField(editable=False)),
|
|
||||||
('image', models.ImageField(blank=True, upload_to='', verbose_name='image')),
|
('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')),
|
('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')),
|
('button_text', cms.models.VarCharField(blank=True, verbose_name='button text')),
|
||||||
|
|
|
@ -4,13 +4,10 @@ import swapper
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.conf import settings
|
|
||||||
from django.forms import TextInput, Select
|
from django.forms import TextInput, Select
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from embed_video.fields import EmbedVideoField
|
from embed_video.fields import EmbedVideoField
|
||||||
from polymorphic.models import PolymorphicModel
|
from polymorphic.models import PolymorphicModel
|
||||||
from markdownfield.models import MarkdownField, RenderedMarkdownField
|
|
||||||
from markdownfield.validators import VALIDATOR_NULL
|
|
||||||
|
|
||||||
from numberedmodel.models import NumberedModel
|
from numberedmodel.models import NumberedModel
|
||||||
|
|
||||||
|
@ -56,9 +53,7 @@ class BaseSection(NumberedModel, PolymorphicModel):
|
||||||
type = VarCharChoiceField(_('type'), default='', choices=TYPES)
|
type = VarCharChoiceField(_('type'), default='', choices=TYPES)
|
||||||
title = VarCharField(_('title'), blank=True)
|
title = VarCharField(_('title'), blank=True)
|
||||||
position = models.PositiveIntegerField(_('position'), blank=True)
|
position = models.PositiveIntegerField(_('position'), blank=True)
|
||||||
color = models.PositiveIntegerField(_('color'), default=1, choices=settings.SECTION_COLORS)
|
content = models.TextField(_('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)
|
image = models.ImageField(_('image'), blank=True)
|
||||||
video = EmbedVideoField(_('video'), blank=True, help_text=_('Paste a YouTube, Vimeo, or SoundCloud link'))
|
video = EmbedVideoField(_('video'), blank=True, help_text=_('Paste a YouTube, Vimeo, or SoundCloud link'))
|
||||||
button_text = VarCharField(_('button text'), blank=True)
|
button_text = VarCharField(_('button text'), blank=True)
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% if request.user.is_staff %}
|
||||||
|
<div class="edit">
|
||||||
|
<a href="{% url 'cms:updatesection' section.pk %}">{% trans 'edit this section' %}</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
|
@ -1,13 +1,12 @@
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load cms %}
|
||||||
|
|
||||||
{% block title %}{{block.super}} - {{page.title}}{% endblock %}
|
{% block title %}{{block.super}} - {{page.title}}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% for section in sections %}
|
{% for section in sections %}
|
||||||
<section class="{{section.type}} color{{section.color}}">
|
{% include_section section %}
|
||||||
{% include 'cms/sections/'|add:section.type|lower|add:'.html' %}
|
|
||||||
</section>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<div class="edit page">
|
<div class="edit page">
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{% load i18n thumbnail embed_video_tags %}
|
{% load i18n thumbnail embed_video_tags %}
|
||||||
|
{% load markdown %}
|
||||||
|
|
||||||
{% block section %}
|
<section class="{{section.type}} color{{section.color}}">
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
|
|
||||||
{% if section.image %}
|
{% if section.image %}
|
||||||
|
@ -17,9 +18,9 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if section.content_rendered %}
|
{% if section.content %}
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{{section.content_rendered|safe}}
|
{{section.content|markdown}}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
@ -31,32 +32,12 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if section.subsections.exists %}
|
|
||||||
<div class="subsections">
|
|
||||||
{% for sub in section.subsections.all %}
|
|
||||||
<div class="subsection color{{sub.color}}">
|
|
||||||
{% with section=sub user=None %}
|
|
||||||
{% include 'cms/sections/base.html' %}
|
|
||||||
{% endwith %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if section.button_text %}
|
{% if section.button_text %}
|
||||||
<div class="button">
|
<div class="button">
|
||||||
<a class="button" href="{{section.button_link}}">{{section.button_text}}</a>
|
<a class="button" href="{{section.button_link}}">{{section.button_text}}</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block extracontent %}{% endblock %}
|
{% include 'cms/editlink.html' %}
|
||||||
|
|
||||||
{% if user.is_staff %}
|
|
||||||
<div class="wrapper">
|
|
||||||
<div class="edit">
|
|
||||||
<a href="{% url 'cms:updatesection' section.pk %}">{% trans 'edit this section' %}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
</section>
|
|
@ -0,0 +1,29 @@
|
||||||
|
from django import template
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
@register.tag('include_section')
|
||||||
|
def do_include(parser, token):
|
||||||
|
'''Renders the section with its own context
|
||||||
|
|
||||||
|
'''
|
||||||
|
_, section = token.split_contents()
|
||||||
|
return IncludeSectionNode(section)
|
||||||
|
|
||||||
|
class IncludeSectionNode(template.Node):
|
||||||
|
def __init__(self, section):
|
||||||
|
self.section = template.Variable(section)
|
||||||
|
self.csrf_token = template.Variable('csrf_token')
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def render(self, context):
|
||||||
|
section = self.section.resolve(context)
|
||||||
|
template_name = section.view.template_name
|
||||||
|
if template_name is None:
|
||||||
|
raise ValueError(f'{section} view has no template_name attribute')
|
||||||
|
csrf_token = self.csrf_token.resolve(context)
|
||||||
|
if not hasattr(section, 'context'):
|
||||||
|
raise ValueError(dir(section))
|
||||||
|
section.context.update({'csrf_token': csrf_token})
|
||||||
|
t = context.template.engine.get_template(template_name)
|
||||||
|
return t.render(template.Context(section.context))
|
|
@ -0,0 +1,15 @@
|
||||||
|
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))
|
|
@ -9,8 +9,6 @@ urlpatterns = [
|
||||||
path('updatesection/<int:pk>/', UpdateSection.as_view(), name='updatesection'),
|
path('updatesection/<int:pk>/', UpdateSection.as_view(), name='updatesection'),
|
||||||
path('createpage/', CreatePage.as_view(), name='createpage'),
|
path('createpage/', CreatePage.as_view(), name='createpage'),
|
||||||
path('createsection/<int:pk>', CreateSection.as_view(), name='createsection'),
|
path('createsection/<int:pk>', CreateSection.as_view(), name='createsection'),
|
||||||
|
|
||||||
# Feel free to copy the following into your root URL conf!
|
|
||||||
path('', PageView.as_view(), name='page'),
|
path('', PageView.as_view(), name='page'),
|
||||||
path('<slug:slug>/', PageView.as_view(), name='page'),
|
path('<slug:slug>/', PageView.as_view(), name='page'),
|
||||||
]
|
]
|
||||||
|
|
71
cms/views.py
71
cms/views.py
|
@ -4,6 +4,7 @@ import json
|
||||||
import swapper
|
import swapper
|
||||||
|
|
||||||
from django.views import generic
|
from django.views import generic
|
||||||
|
from django.views.generic.edit import FormMixin
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
@ -19,43 +20,27 @@ Section = swapper.load_model('cms', 'Section')
|
||||||
@register_view(Section)
|
@register_view(Section)
|
||||||
class SectionView:
|
class SectionView:
|
||||||
'''Generic section view'''
|
'''Generic section view'''
|
||||||
def get(self, request, section):
|
template_name = 'cms/sections/section.html'
|
||||||
'''Override this to add custom attributes to a section'''
|
|
||||||
return section
|
|
||||||
|
|
||||||
class SectionWithFormView(SectionView):
|
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(FormMixin, SectionView):
|
||||||
'''Generic section with associated form'''
|
'''Generic section with associated form'''
|
||||||
form_class = None
|
|
||||||
success_url = None
|
|
||||||
|
|
||||||
def get_form_class(self):
|
def post(self, request):
|
||||||
'''Return the form class to use in this view.'''
|
|
||||||
if self.form_class:
|
|
||||||
return self.form_class
|
|
||||||
raise ImproperlyConfigured(
|
|
||||||
'Either specify formclass attribute or override get_form_class()')
|
|
||||||
|
|
||||||
def get_success_url(self):
|
|
||||||
'''Return the URL to redirect to after processing a valid form.'''
|
|
||||||
if self.success_url:
|
|
||||||
return self.success_url
|
|
||||||
raise ImproperlyConfigured(
|
|
||||||
'Either specify success_url attribute or override get_success_url()')
|
|
||||||
|
|
||||||
def get(self, request, section):
|
|
||||||
'''Add form to section'''
|
|
||||||
form = self.get_form_class()()
|
|
||||||
section.form = form
|
|
||||||
return section
|
|
||||||
|
|
||||||
def post(self, request, section):
|
|
||||||
'''Process form'''
|
'''Process form'''
|
||||||
form = self.get_form_class()(request.POST, request.FILES)
|
form = self.get_form()
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
form.save(request)
|
form.save(request)
|
||||||
return redirect(self.get_success_url())
|
return redirect(self.get_success_url())
|
||||||
section.form = form
|
return form
|
||||||
return section
|
|
||||||
|
|
||||||
class MenuMixin:
|
class MenuMixin:
|
||||||
'''Add pages to template context'''
|
'''Add pages to template context'''
|
||||||
|
@ -83,14 +68,21 @@ class PageView(MenuMixin, MemoryMixin, generic.DetailView):
|
||||||
'''Supply a default argument for slug'''
|
'''Supply a default argument for slug'''
|
||||||
super().setup(*args, slug=slug, **kwargs)
|
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):
|
def get(self, request, *args, **kwargs):
|
||||||
'''Call each sections's get() view before rendering final response'''
|
'''Initialize sections and render final response'''
|
||||||
page = self.object = self.get_object()
|
page = self.object = self.get_object()
|
||||||
context = self.get_context_data(**kwargs)
|
context = self.get_context_data(**kwargs)
|
||||||
sections = page.sections.all()
|
sections = page.sections.all()
|
||||||
for section in sections:
|
for section in sections:
|
||||||
view = section.__class__.view_class()
|
self.initialize_section(section)
|
||||||
view.get(request, section)
|
|
||||||
context.update({
|
context.update({
|
||||||
'page': page,
|
'page': page,
|
||||||
'sections': sections,
|
'sections': sections,
|
||||||
|
@ -98,7 +90,8 @@ class PageView(MenuMixin, MemoryMixin, generic.DetailView):
|
||||||
return self.render_to_response(context)
|
return self.render_to_response(context)
|
||||||
|
|
||||||
def post(self, request, **kwargs):
|
def post(self, request, **kwargs):
|
||||||
'''Call the post() function of the correct section view'''
|
'''Initialize sections and call the post() function of the correct
|
||||||
|
section view'''
|
||||||
try:
|
try:
|
||||||
pk = int(self.request.POST.get('section'))
|
pk = int(self.request.POST.get('section'))
|
||||||
except:
|
except:
|
||||||
|
@ -108,20 +101,20 @@ class PageView(MenuMixin, MemoryMixin, generic.DetailView):
|
||||||
context = self.get_context_data(**kwargs)
|
context = self.get_context_data(**kwargs)
|
||||||
sections = page.sections.all()
|
sections = page.sections.all()
|
||||||
for section in sections:
|
for section in sections:
|
||||||
view = section.__class__.view_class()
|
self.initialize_section(section)
|
||||||
if section.pk == pk:
|
if section.pk == pk:
|
||||||
result = view.post(request, section)
|
result = section.view.post(request)
|
||||||
if isinstance(result, HttpResponseRedirect):
|
if isinstance(result, HttpResponseRedirect):
|
||||||
return result
|
return result
|
||||||
else:
|
section.context['form'] = result
|
||||||
view.get(request, section)
|
|
||||||
context.update({
|
context.update({
|
||||||
'page': page,
|
'page': page,
|
||||||
'sections': sections,
|
'sections': sections,
|
||||||
})
|
})
|
||||||
return self.render_to_response(context)
|
return self.render_to_response(context)
|
||||||
|
|
||||||
# The following views all require a logged-in staff member
|
# The following views require a logged-in staff member
|
||||||
|
|
||||||
class StaffRequiredMixin(UserPassesTestMixin):
|
class StaffRequiredMixin(UserPassesTestMixin):
|
||||||
def test_func(self):
|
def test_func(self):
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
from django import forms
|
||||||
|
from django.core.mail import EmailMessage
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
class ContactForm(forms.Form):
|
||||||
|
sender = forms.EmailField(label=_('Your email address'))
|
||||||
|
spam_protection = forms.CharField(label=_('Your message'), widget=forms.Textarea())
|
||||||
|
message = forms.CharField(label=_('Your message'), widget=forms.Textarea(), initial='Hi there!')
|
||||||
|
|
||||||
|
def save(self, request):
|
||||||
|
hostname = request.get_host()
|
||||||
|
body = self.cleaned_data.get('spam_protection') # MUHAHA
|
||||||
|
if len(body.split()) < 7:
|
||||||
|
return
|
||||||
|
email = EmailMessage(
|
||||||
|
to = ['info@' + hostname],
|
||||||
|
from_email = 'noreply@' + hostname,
|
||||||
|
body = body,
|
||||||
|
subject = _('Contact form at %(hostname)s.') % {'hostname': hostname},
|
||||||
|
headers = {'Reply-To': self.cleaned_data.get('sender')},
|
||||||
|
)
|
||||||
|
email.send()
|
|
@ -1,11 +1,10 @@
|
||||||
# Generated by Django 3.0.1 on 2020-01-02 20:42
|
# Generated by Django 3.0.2 on 2020-01-05 02:29
|
||||||
|
|
||||||
import cms.models
|
import cms.models
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
import embed_video.fields
|
import embed_video.fields
|
||||||
import markdownfield.models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -13,8 +12,8 @@ class Migration(migrations.Migration):
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
migrations.swappable_dependency(settings.CMS_PAGE_MODEL),
|
|
||||||
('contenttypes', '0002_remove_content_type_name'),
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
|
migrations.swappable_dependency(settings.CMS_PAGE_MODEL),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -22,9 +21,9 @@ class Migration(migrations.Migration):
|
||||||
name='Page',
|
name='Page',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('position', models.PositiveIntegerField(blank=True, verbose_name='position')),
|
|
||||||
('title', cms.models.VarCharField(verbose_name='title')),
|
|
||||||
('slug', models.SlugField(blank=True, help_text='A short identifier to use in URLs', unique=True, verbose_name='slug')),
|
('slug', models.SlugField(blank=True, help_text='A short identifier to use in URLs', unique=True, verbose_name='slug')),
|
||||||
|
('title', cms.models.VarCharField(verbose_name='title')),
|
||||||
|
('position', models.PositiveIntegerField(blank=True, verbose_name='position')),
|
||||||
('menu', models.BooleanField(default=True, verbose_name='visible in menu')),
|
('menu', models.BooleanField(default=True, verbose_name='visible in menu')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
|
@ -38,16 +37,15 @@ class Migration(migrations.Migration):
|
||||||
name='Section',
|
name='Section',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('type', cms.models.VarCharChoiceField(choices=[('textsection', 'Tekst'), ('imagesection', 'Afbeelding'), ('contactsection', 'Contact')], default='', verbose_name='section type')),
|
('type', cms.models.VarCharChoiceField(choices=[('textsection', 'Tekst'), ('buttonsection', 'Button'), ('imagesection', 'Afbeelding'), ('videosection', 'Video'), ('contactsection', 'Contact')], default='', verbose_name='type')),
|
||||||
('position', models.PositiveIntegerField(blank=True, verbose_name='position')),
|
|
||||||
('title', cms.models.VarCharField(blank=True, verbose_name='title')),
|
('title', cms.models.VarCharField(blank=True, verbose_name='title')),
|
||||||
('color', models.PositiveIntegerField(choices=[(1, 'Wit')], default=1, verbose_name='color')),
|
('position', models.PositiveIntegerField(blank=True, verbose_name='position')),
|
||||||
('content', markdownfield.models.MarkdownField(blank=True, verbose_name='content')),
|
('content', models.TextField(blank=True, verbose_name='content')),
|
||||||
('content_rendered', markdownfield.models.RenderedMarkdownField(editable=False)),
|
|
||||||
('image', models.ImageField(blank=True, upload_to='', verbose_name='image')),
|
('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')),
|
('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')),
|
('button_text', cms.models.VarCharField(blank=True, verbose_name='button text')),
|
||||||
('button_link', cms.models.VarCharField(blank=True, verbose_name='button link')),
|
('button_link', cms.models.VarCharField(blank=True, verbose_name='button link')),
|
||||||
|
('color', models.PositiveIntegerField(choices=[(1, 'Wit')], default=1, verbose_name='kleur')),
|
||||||
('page', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='sections', to=settings.CMS_PAGE_MODEL, verbose_name='page')),
|
('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')),
|
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_app.section_set+', to='contenttypes.ContentType')),
|
||||||
],
|
],
|
||||||
|
@ -58,6 +56,17 @@ class Migration(migrations.Migration):
|
||||||
'abstract': False,
|
'abstract': False,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ButtonSection',
|
||||||
|
fields=[
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'proxy': True,
|
||||||
|
'indexes': [],
|
||||||
|
'constraints': [],
|
||||||
|
},
|
||||||
|
bases=('app.section',),
|
||||||
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='ContactSection',
|
name='ContactSection',
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -91,4 +100,15 @@ class Migration(migrations.Migration):
|
||||||
},
|
},
|
||||||
bases=('app.section',),
|
bases=('app.section',),
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='VideoSection',
|
||||||
|
fields=[
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'proxy': True,
|
||||||
|
'indexes': [],
|
||||||
|
'constraints': [],
|
||||||
|
},
|
||||||
|
bases=('app.section',),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from django.db import models
|
||||||
|
from django.conf import settings
|
||||||
from cms.models import BasePage, BaseSection
|
from cms.models import BasePage, BaseSection
|
||||||
from cms.decorators import register_model
|
from cms.decorators import register_model
|
||||||
|
|
||||||
|
@ -9,13 +11,20 @@ class Page(BasePage):
|
||||||
|
|
||||||
class Section(BaseSection):
|
class Section(BaseSection):
|
||||||
'''Add custom fields here. Already existing fields: type, position,
|
'''Add custom fields here. Already existing fields: type, position,
|
||||||
title, color, content, image, video, button_text, button_link
|
title, content, image, video, button_text, button_link
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
color = models.PositiveIntegerField('kleur', default=1, choices=settings.SECTION_COLORS)
|
||||||
|
|
||||||
@register_model('Tekst')
|
@register_model('Tekst')
|
||||||
class TextSection(Section):
|
class TextSection(Section):
|
||||||
fields = ['type', 'position', 'title', 'content']
|
fields = ['type', 'position', 'title', 'color', 'content']
|
||||||
|
class Meta:
|
||||||
|
proxy = True
|
||||||
|
|
||||||
|
@register_model('Button')
|
||||||
|
class ButtonSection(Section):
|
||||||
|
fields = ['type', 'position', 'title', 'button_text', 'button_link']
|
||||||
class Meta:
|
class Meta:
|
||||||
proxy = True
|
proxy = True
|
||||||
|
|
||||||
|
@ -25,6 +34,12 @@ class ImageSection(Section):
|
||||||
class Meta:
|
class Meta:
|
||||||
proxy = True
|
proxy = True
|
||||||
|
|
||||||
|
@register_model('Video')
|
||||||
|
class VideoSection(Section):
|
||||||
|
fields = ['type', 'position', 'title', 'video']
|
||||||
|
class Meta:
|
||||||
|
proxy = True
|
||||||
|
|
||||||
@register_model('Contact')
|
@register_model('Contact')
|
||||||
class ContactSection(Section):
|
class ContactSection(Section):
|
||||||
fields = ['type', 'position', 'title']
|
fields = ['type', 'position', 'title']
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
<section class="{{section.type}} color{{section.color}}">
|
||||||
|
<div class="wrapper">
|
||||||
|
{% if section.button_text %}
|
||||||
|
<div class="button">
|
||||||
|
<a class="button" href="{{section.button_link}}">{{section.button_text}}</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% include 'cms/editlink.html' %}
|
||||||
|
</div>
|
||||||
|
</section>
|
|
@ -1,7 +1,6 @@
|
||||||
{% extends 'cms/sections/base.html' %}
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block section %}
|
<section class="{{section.type}} color{{section.color}}">
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<h1>{{section.title}}</h1>
|
<h1>{{section.title}}</h1>
|
||||||
|
@ -10,12 +9,13 @@
|
||||||
<div class="form">
|
<div class="form">
|
||||||
<form method="post" class="cms">
|
<form method="post" class="cms">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% for field in section.form %}
|
{% for field in form %}
|
||||||
{% include 'cms/formfield.html' with field=field %}
|
{% include 'cms/formfield.html' with field=field %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<button class="button" name="section" value="{{section.pk}}">{% trans 'Send' %}</button>
|
<button class="button" name="section" value="{{section.pk}}">{% trans 'Send' %}</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% include 'cms/editlink.html' %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
</section>
|
|
@ -0,0 +1,14 @@
|
||||||
|
{% load thumbnail %}
|
||||||
|
|
||||||
|
<section class="{{section.type}} color{{section.color}}">
|
||||||
|
<div class="wrapper">
|
||||||
|
|
||||||
|
{% if section.image %}
|
||||||
|
<div class="image">
|
||||||
|
<img alt="{{section.title}}" src="{% thumbnail section.image 800x800 %}">
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% include 'cms/editlink.html' %}
|
||||||
|
</div>
|
||||||
|
</section>
|
|
@ -0,0 +1,21 @@
|
||||||
|
{% load markdown %}
|
||||||
|
|
||||||
|
<section class="{{section.type}} color{{section.color}}">
|
||||||
|
<div class="wrapper">
|
||||||
|
{% if section.title %}
|
||||||
|
<div class="title">
|
||||||
|
<h1>
|
||||||
|
{{section.title}}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if section.content %}
|
||||||
|
<div class="content">
|
||||||
|
{{section.content|markdown}}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% include 'cms/editlink.html' %}
|
||||||
|
</div>
|
||||||
|
</section>
|
|
@ -0,0 +1,16 @@
|
||||||
|
{% load embed_video_tags %}
|
||||||
|
|
||||||
|
<section class="{{section.type}} color{{section.color}}">
|
||||||
|
<div class="wrapper">
|
||||||
|
|
||||||
|
{% if section.video %}
|
||||||
|
<div class="video">
|
||||||
|
<div class="iframe">
|
||||||
|
{% video section.video '800x600' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% include 'cms/editlink.html' %}
|
||||||
|
</div>
|
||||||
|
</section>
|
|
@ -1 +0,0 @@
|
||||||
{% extends 'cms/sections/base.html' %}
|
|
|
@ -1 +0,0 @@
|
||||||
{% extends 'cms/sections/base.html' %}
|
|
|
@ -1,10 +1,27 @@
|
||||||
from cms.forms import ContactForm
|
from cms.views import SectionView, SectionFormView
|
||||||
from cms.views import SectionWithFormView
|
|
||||||
from cms.decorators import register_view
|
from cms.decorators import register_view
|
||||||
|
|
||||||
from .models import *
|
from .models import *
|
||||||
|
from .forms import ContactForm
|
||||||
|
|
||||||
|
@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)
|
@register_view(ContactSection)
|
||||||
class ContactFormView(SectionWithFormView):
|
class ContactFormView(SectionFormView):
|
||||||
form_class = ContactForm
|
form_class = ContactForm
|
||||||
success_url = '/thanks/'
|
success_url = '/thanks/'
|
||||||
|
template_name = 'app/sections/contact.html'
|
||||||
|
|
|
@ -7,7 +7,6 @@ except ImportError:
|
||||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||||
|
|
||||||
PROJECT_NAME = 'example'
|
PROJECT_NAME = 'example'
|
||||||
SITE_URL = 'https://example.com/'
|
|
||||||
KEYFILE = f'/tmp/{PROJECT_NAME}.secret'
|
KEYFILE = f'/tmp/{PROJECT_NAME}.secret'
|
||||||
ADMINS = [('JJ Vens', 'jj@rtts.eu')]
|
ADMINS = [('JJ Vens', 'jj@rtts.eu')]
|
||||||
ALLOWED_HOSTS = ['*']
|
ALLOWED_HOSTS = ['*']
|
||||||
|
@ -25,7 +24,6 @@ MEDIA_ROOT = '/srv/' + PROJECT_NAME + '/media'
|
||||||
LOGIN_REDIRECT_URL = '/'
|
LOGIN_REDIRECT_URL = '/'
|
||||||
CMS_SECTION_MODEL = 'app.Section'
|
CMS_SECTION_MODEL = 'app.Section'
|
||||||
MARKDOWN_EXTENSIONS = ['extra', 'smarty']
|
MARKDOWN_EXTENSIONS = ['extra', 'smarty']
|
||||||
MARKDOWN_EXTENSION_CONFIGS = {'extra': {}, 'smarty': {}}
|
|
||||||
|
|
||||||
def read(file):
|
def read(file):
|
||||||
with open(file) as f:
|
with open(file) as f:
|
||||||
|
|
4
setup.py
4
setup.py
|
@ -3,7 +3,7 @@ from setuptools import setup, find_packages
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name = 'django-simplecms',
|
name = 'django-simplecms',
|
||||||
version = '2.2.0',
|
version = '2.3.0',
|
||||||
url = 'https://github.com/rtts/django-simplecms',
|
url = 'https://github.com/rtts/django-simplecms',
|
||||||
author = 'Jaap Joris Vens',
|
author = 'Jaap Joris Vens',
|
||||||
author_email = 'jj@rtts.eu',
|
author_email = 'jj@rtts.eu',
|
||||||
|
@ -16,11 +16,9 @@ setup(
|
||||||
'django-extensions',
|
'django-extensions',
|
||||||
'django-embed-video',
|
'django-embed-video',
|
||||||
'django-polymorphic',
|
'django-polymorphic',
|
||||||
'django-markdownfield',
|
|
||||||
'easy-thumbnails',
|
'easy-thumbnails',
|
||||||
'psycopg2',
|
'psycopg2',
|
||||||
'markdown',
|
'markdown',
|
||||||
'bleach',
|
|
||||||
'libsass',
|
'libsass',
|
||||||
'swapper',
|
'swapper',
|
||||||
],
|
],
|
||||||
|
|
Ładowanie…
Reference in New Issue