kopia lustrzana https://github.com/rtts/django-simplecms
First draft of version 2.0.0
Oh boy! This is a big one. Two new dependencies: swapper and django-polymorphic will now allow any project that uses cms to elegantly extend the default Section model with custom fields and custom subclasses. This is still a work in progress.readwriteweb
rodzic
4004643dea
commit
d166e10b05
22
cms/admin.py
22
cms/admin.py
|
@ -2,36 +2,18 @@ from django.contrib import admin
|
||||||
from django.utils.text import Truncator
|
from django.utils.text import Truncator
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from .models import Page, Section, SubSection, Config
|
from .models import Page, Config
|
||||||
|
|
||||||
class InlineSectionAdmin(admin.StackedInline):
|
|
||||||
model = Section
|
|
||||||
extra = 0
|
|
||||||
|
|
||||||
class InlineSubSectionAdmin(admin.StackedInline):
|
|
||||||
model = SubSection
|
|
||||||
extra = 0
|
|
||||||
|
|
||||||
@admin.register(Page)
|
@admin.register(Page)
|
||||||
class PageAdmin(admin.ModelAdmin):
|
class PageAdmin(admin.ModelAdmin):
|
||||||
prepopulated_fields = {'slug': ('title',)}
|
prepopulated_fields = {'slug': ('title',)}
|
||||||
inlines = [InlineSectionAdmin]
|
|
||||||
|
|
||||||
@admin.register(Section)
|
class BaseSectionAdmin(admin.ModelAdmin):
|
||||||
class SectionAdmin(admin.ModelAdmin):
|
|
||||||
inlines = [InlineSubSectionAdmin]
|
|
||||||
list_filter = [
|
list_filter = [
|
||||||
('page', admin.RelatedOnlyFieldListFilter),
|
('page', admin.RelatedOnlyFieldListFilter),
|
||||||
]
|
]
|
||||||
list_display = ['__str__', 'get_type_display']
|
list_display = ['__str__', 'get_type_display']
|
||||||
|
|
||||||
@admin.register(SubSection)
|
|
||||||
class SubSectionAdmin(admin.ModelAdmin):
|
|
||||||
list_filter = [
|
|
||||||
('section', admin.RelatedOnlyFieldListFilter),
|
|
||||||
('section__page', admin.RelatedOnlyFieldListFilter),
|
|
||||||
]
|
|
||||||
|
|
||||||
@admin.register(Config)
|
@admin.register(Config)
|
||||||
class ConfigAdmin(admin.ModelAdmin):
|
class ConfigAdmin(admin.ModelAdmin):
|
||||||
list_display = ['__str__', 'get_content']
|
list_display = ['__str__', 'get_content']
|
||||||
|
|
33
cms/forms.py
33
cms/forms.py
|
@ -1,5 +1,9 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from .models import Page, Section, SubSection
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from .models import Page
|
||||||
|
|
||||||
|
import swapper
|
||||||
|
Section = swapper.load_model('cms', 'Section')
|
||||||
|
|
||||||
class PageForm(forms.ModelForm):
|
class PageForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -7,14 +11,25 @@ class PageForm(forms.ModelForm):
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
class SectionForm(forms.ModelForm):
|
class SectionForm(forms.ModelForm):
|
||||||
|
def save(self):
|
||||||
|
section = super().save()
|
||||||
|
app_label = section._meta.app_label
|
||||||
|
model = section.type
|
||||||
|
|
||||||
|
# Explanation: we'll 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. This way, the next
|
||||||
|
# time the object is requested from the database,
|
||||||
|
# django-polymorphic will automatically convert it to the
|
||||||
|
# correct subclass. Brilliant!
|
||||||
|
section.polymorphic_ctype = ContentType.objects.get(
|
||||||
|
app_label=section._meta.app_label,
|
||||||
|
model=section.type.lower(),
|
||||||
|
)
|
||||||
|
|
||||||
|
section.save()
|
||||||
|
return section
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Section
|
model = Section
|
||||||
exclude = ['page']
|
exclude = ['page']
|
||||||
|
|
||||||
class SubSectionForm(forms.ModelForm):
|
|
||||||
class Meta:
|
|
||||||
model = SubSection
|
|
||||||
exclude = ['section']
|
|
||||||
|
|
||||||
SectionFormSet = forms.inlineformset_factory(Page, Section, exclude='__all__', extra=0)
|
|
||||||
SubSectionFormSet = forms.inlineformset_factory(Section, SubSection, exclude='__all__', extra=0)
|
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
|
# Generated by Django 3.0.1 on 2019-12-31 11:15
|
||||||
|
|
||||||
import ckeditor.fields
|
import ckeditor.fields
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -16,7 +20,7 @@ class Migration(migrations.Migration):
|
||||||
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')),
|
||||||
('parameter', models.PositiveIntegerField(choices=[(10, 'Footer')], unique=True)),
|
('parameter', models.PositiveIntegerField(choices=[(10, 'Footer')], unique=True)),
|
||||||
('content', ckeditor.fields.RichTextField(blank=True, verbose_name='Inhoud')),
|
('content', ckeditor.fields.RichTextField(blank=True, verbose_name='content')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'configuration parameter',
|
'verbose_name': 'configuration parameter',
|
||||||
|
@ -44,20 +48,19 @@ class Migration(migrations.Migration):
|
||||||
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')),
|
('position', models.PositiveIntegerField(blank=True, verbose_name='position')),
|
||||||
('title', models.CharField(max_length=255, verbose_name='title')),
|
('title', models.CharField(blank=True, max_length=255, verbose_name='title')),
|
||||||
('type', models.CharField(choices=[('normal', 'Normaal')], default='normal', max_length=16, verbose_name='section type')),
|
('type', models.CharField(choices=[('normal', 'Normaal')], default='normal', max_length=16, verbose_name='section type')),
|
||||||
('color', models.PositiveIntegerField(choices=[(1, 'Licht'), (2, 'Donker')], default=1, verbose_name='color')),
|
('color', models.PositiveIntegerField(choices=[(1, 'Wit')], default=1, verbose_name='color')),
|
||||||
('content', ckeditor.fields.RichTextField(blank=True, verbose_name='content')),
|
('content', ckeditor.fields.RichTextField(blank=True, verbose_name='content')),
|
||||||
('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', models.CharField(blank=True, max_length=255, verbose_name='button text')),
|
('button_text', models.CharField(blank=True, max_length=255, verbose_name='button text')),
|
||||||
('button_link', models.CharField(blank=True, max_length=255, verbose_name='button link')),
|
('button_link', models.CharField(blank=True, max_length=255, verbose_name='button link')),
|
||||||
('page', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='sections', to='cms.Page', verbose_name='page')),
|
('page', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='sections', to='cms.Page', verbose_name='page')),
|
||||||
|
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_cms.section_set+', to='contenttypes.ContentType')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'section',
|
'swappable': 'CMS_SECTION_MODEL',
|
||||||
'verbose_name_plural': 'sections',
|
|
||||||
'ordering': ['position'],
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -8,7 +8,7 @@ def add_homepage(apps, schema_editor):
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('cms', '0003_subsection'),
|
('cms', '0001_initial'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
|
@ -1,15 +0,0 @@
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('cms', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='section',
|
|
||||||
name='title',
|
|
||||||
field=models.CharField(blank=True, max_length=255, verbose_name='title'),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,32 +0,0 @@
|
||||||
import ckeditor.fields
|
|
||||||
import cms.models
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('cms', '0002_title'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='SubSection',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('position', models.PositiveIntegerField(blank=True, verbose_name='position')),
|
|
||||||
('title', models.CharField(blank=True, max_length=255, verbose_name='title')),
|
|
||||||
('color', models.PositiveIntegerField(choices=[(1, 'Licht'), (2, 'Donker')], default=1, verbose_name='color')),
|
|
||||||
('content', ckeditor.fields.RichTextField(blank=True, verbose_name='content')),
|
|
||||||
('image', models.ImageField(blank=True, upload_to='', verbose_name='image')),
|
|
||||||
('button_text', cms.models.VarCharField(blank=True, verbose_name='button text')),
|
|
||||||
('button_link', cms.models.VarCharField(blank=True, verbose_name='button link')),
|
|
||||||
('section', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='subsections', to='cms.Section', verbose_name='section')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'subsection',
|
|
||||||
'verbose_name_plural': 'subsections',
|
|
||||||
'ordering': ['position'],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
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.conf import settings
|
||||||
|
@ -5,6 +6,7 @@ from django.forms import TextInput
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from ckeditor.fields import RichTextField
|
from ckeditor.fields import RichTextField
|
||||||
from embed_video.fields import EmbedVideoField
|
from embed_video.fields import EmbedVideoField
|
||||||
|
from polymorphic.models import PolymorphicModel
|
||||||
|
|
||||||
from numberedmodel.models import NumberedModel
|
from numberedmodel.models import NumberedModel
|
||||||
|
|
||||||
|
@ -36,11 +38,12 @@ class Page(NumberedModel):
|
||||||
verbose_name_plural = _('Pages')
|
verbose_name_plural = _('Pages')
|
||||||
ordering = ['position']
|
ordering = ['position']
|
||||||
|
|
||||||
class Section(NumberedModel):
|
choices = settings.SECTION_TYPES
|
||||||
|
class BaseSection(NumberedModel, PolymorphicModel):
|
||||||
page = models.ForeignKey(Page, verbose_name=_('page'), related_name='sections', on_delete=models.PROTECT)
|
page = models.ForeignKey(Page, verbose_name=_('page'), related_name='sections', on_delete=models.PROTECT)
|
||||||
position = models.PositiveIntegerField(_('position'), blank=True)
|
position = models.PositiveIntegerField(_('position'), blank=True)
|
||||||
title = models.CharField(_('title'), max_length=255, blank=True)
|
title = models.CharField(_('title'), max_length=255, blank=True)
|
||||||
type = models.CharField(_('section type'), max_length=16, default=settings.SECTION_TYPES[0][0], choices=settings.SECTION_TYPES)
|
type = models.CharField(_('section type'), max_length=16, default=choices[0][0], choices=choices)
|
||||||
color = models.PositiveIntegerField(_('color'), default=1, choices=settings.SECTION_COLORS)
|
color = models.PositiveIntegerField(_('color'), default=1, choices=settings.SECTION_COLORS)
|
||||||
|
|
||||||
content = RichTextField(_('content'), blank=True)
|
content = RichTextField(_('content'), blank=True)
|
||||||
|
@ -61,36 +64,15 @@ class Section(NumberedModel):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
abstract = True
|
||||||
verbose_name = _('section')
|
verbose_name = _('section')
|
||||||
verbose_name_plural = _('sections')
|
verbose_name_plural = _('sections')
|
||||||
ordering = ['position']
|
ordering = ['position']
|
||||||
|
#app_label = 'cms'
|
||||||
|
|
||||||
class SubSection(NumberedModel):
|
class Section(BaseSection):
|
||||||
section = models.ForeignKey(Section, verbose_name=_('section'), related_name='subsections', on_delete=models.CASCADE)
|
|
||||||
position = models.PositiveIntegerField(_('position'), blank=True)
|
|
||||||
title = models.CharField(_('title'), max_length=255, blank=True)
|
|
||||||
color = models.PositiveIntegerField(_('color'), default=1, choices=settings.SECTION_COLORS)
|
|
||||||
|
|
||||||
content = RichTextField(_('content'), blank=True)
|
|
||||||
image = models.ImageField(_('image'), blank=True)
|
|
||||||
button_text = VarCharField(_('button text'), blank=True)
|
|
||||||
button_link = VarCharField(_('button link'), blank=True)
|
|
||||||
|
|
||||||
def number_with_respect_to(self):
|
|
||||||
return self.section.subsections.all()
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
if not self.pk:
|
|
||||||
return str(_('New subsection'))
|
|
||||||
elif not self.title:
|
|
||||||
return str(_('Untitled'))
|
|
||||||
else:
|
|
||||||
return self.title
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('subsection')
|
swappable = swapper.swappable_setting('cms', 'Section')
|
||||||
verbose_name_plural = _('subsections')
|
|
||||||
ordering = ['position']
|
|
||||||
|
|
||||||
class Config(models.Model):
|
class Config(models.Model):
|
||||||
TYPES = [
|
TYPES = [
|
||||||
|
|
|
@ -188,8 +188,6 @@ div.edit a, div.edit button, a.edit{
|
||||||
display: inline;
|
display: inline;
|
||||||
background: none;
|
background: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
content: '[ ';
|
content: '[ ';
|
||||||
|
|
|
@ -140,9 +140,7 @@ div.edit a, div.edit button, a.edit {
|
||||||
border: none;
|
border: none;
|
||||||
display: inline;
|
display: inline;
|
||||||
background: none;
|
background: none;
|
||||||
cursor: pointer;
|
cursor: pointer; }
|
||||||
margin: 0;
|
|
||||||
padding: 0; }
|
|
||||||
div.edit a:before, div.edit button:before, a.edit:before {
|
div.edit a:before, div.edit button:before, a.edit:before {
|
||||||
content: '[ '; }
|
content: '[ '; }
|
||||||
div.edit a:after, div.edit button:after, a.edit:after {
|
div.edit a:after, div.edit button:after, a.edit:after {
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -27,7 +27,7 @@
|
||||||
{% if pages %}
|
{% if pages %}
|
||||||
<ul id="menu">
|
<ul id="menu">
|
||||||
{% for p in pages %}
|
{% for p in pages %}
|
||||||
<li><a href="{% if p.slug %}{% url page_url_pattern p.slug %}{% else %}{% url page_url_pattern %}{% endif %}" {% if p.pk == object.pk %}class="current"{% endif %}>{{p.title}}</a></li>
|
<li><a href="{% if p.slug %}{% url page_url_pattern p.slug %}{% else %}{% url page_url_pattern %}{% endif %}" {% if p.pk == page.pk %}class="current"{% endif %}>{{p.title}}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if user.is_staff %}
|
{% if user.is_staff %}
|
||||||
<li><a class="edit" href="{% url 'cms:createpage' %}">+ {% trans 'new page' %}</a></li>
|
<li><a class="edit" href="{% url 'cms:createpage' %}">+ {% trans 'new page' %}</a></li>
|
||||||
|
|
|
@ -6,7 +6,8 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% for section in sections %}
|
{% for section in sections %}
|
||||||
<section class="{{section.type}} color{{section.color}}">
|
<section class="{{section.type}} color{{section.color}}">
|
||||||
{% include 'cms/sections/'|add:section.type|add:'.html' %}
|
DIT IS EEN SECTIE MET FIELDS: {{section.fields}}
|
||||||
|
{% include 'cms/sections/'|add:section.type|lower|add:'.html' %}
|
||||||
</section>
|
</section>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
{% extends 'cms/sections/base.html' %}
|
|
@ -1,5 +1,5 @@
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from .views import PageView, UpdatePage, CreatePage, UpdateSection, CreateSection, CreateSubSection
|
from .views import PageView, UpdatePage, CreatePage, UpdateSection, CreateSection
|
||||||
|
|
||||||
app_name = 'cms'
|
app_name = 'cms'
|
||||||
|
|
||||||
|
@ -9,7 +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'),
|
||||||
path('createsubsection/<int:pk>/', CreateSubSection.as_view(), name='createsubsection'),
|
|
||||||
|
|
||||||
# Feel free to copy the following into your root URL conf!
|
# Feel free to copy the following into your root URL conf!
|
||||||
path('', PageView.as_view(), name='page'),
|
path('', PageView.as_view(), name='page'),
|
||||||
|
|
51
cms/views.py
51
cms/views.py
|
@ -4,10 +4,13 @@ from django.shortcuts import redirect
|
||||||
from django.contrib.auth.mixins import UserPassesTestMixin
|
from django.contrib.auth.mixins import UserPassesTestMixin
|
||||||
from django.views.generic import DetailView, UpdateView, CreateView
|
from django.views.generic import DetailView, UpdateView, CreateView
|
||||||
|
|
||||||
from .models import Page, Section, SubSection
|
from .models import Page
|
||||||
from .forms import PageForm, SectionFormSet, SectionForm, SubSectionFormSet, SubSectionForm
|
from .forms import PageForm, SectionForm
|
||||||
from .utils import get_config
|
from .utils import get_config
|
||||||
|
|
||||||
|
import swapper
|
||||||
|
Section = swapper.load_model('cms', 'Section')
|
||||||
|
|
||||||
class StaffRequiredMixin(UserPassesTestMixin):
|
class StaffRequiredMixin(UserPassesTestMixin):
|
||||||
def test_func(self):
|
def test_func(self):
|
||||||
return self.request.user.is_staff
|
return self.request.user.is_staff
|
||||||
|
@ -68,59 +71,17 @@ class CreateSection(StaffRequiredMixin, MenuMixin, CreateView):
|
||||||
form.save()
|
form.save()
|
||||||
return redirect(self.request.session.get('previous_url'))
|
return redirect(self.request.session.get('previous_url'))
|
||||||
|
|
||||||
class CreateSubSection(StaffRequiredMixin, MenuMixin, CreateView):
|
|
||||||
model = SubSection
|
|
||||||
form_class = SubSectionForm
|
|
||||||
template_name = 'cms/new.html'
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
|
||||||
form.instance.section = Section.objects.get(pk=self.kwargs.get('pk'))
|
|
||||||
form.save()
|
|
||||||
return redirect(self.request.session.get('previous_url'))
|
|
||||||
|
|
||||||
class BaseUpdateView(StaffRequiredMixin, MenuMixin, UpdateView):
|
class BaseUpdateView(StaffRequiredMixin, MenuMixin, UpdateView):
|
||||||
template_name = 'cms/edit.html'
|
template_name = 'cms/edit.html'
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def form_valid(self, form):
|
||||||
self.object = self.get_object()
|
|
||||||
form = self.get_form()
|
|
||||||
formset = self.formset_class(request.POST, request.FILES, instance=self.object)
|
|
||||||
if form.is_valid() and formset.is_valid():
|
|
||||||
return self.form_valid(form, formset)
|
|
||||||
else:
|
|
||||||
return self.form_invalid(form, formset)
|
|
||||||
|
|
||||||
def form_valid(self, form, formset):
|
|
||||||
form.save()
|
form.save()
|
||||||
formset.save()
|
|
||||||
return redirect(self.request.session.get('previous_url'))
|
return redirect(self.request.session.get('previous_url'))
|
||||||
|
|
||||||
def form_invalid(self, form, formset):
|
|
||||||
return self.render_to_response(self.get_context_data(form=form, formset=formset))
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super().get_context_data(**kwargs)
|
|
||||||
if 'formset' not in context:
|
|
||||||
formset = self.formset_class(instance=self.object)
|
|
||||||
context.update({
|
|
||||||
'formset': formset,
|
|
||||||
'formset_form_url': self.get_formset_form_url(self.object),
|
|
||||||
'formset_description': self.formset_class.model._meta.verbose_name,
|
|
||||||
})
|
|
||||||
return context
|
|
||||||
|
|
||||||
class UpdatePage(BaseUpdateView):
|
class UpdatePage(BaseUpdateView):
|
||||||
model = Page
|
model = Page
|
||||||
form_class = PageForm
|
form_class = PageForm
|
||||||
formset_class = SectionFormSet
|
|
||||||
|
|
||||||
def get_formset_form_url(self, page):
|
|
||||||
return reverse('cms:createsection', args=[page.pk])
|
|
||||||
|
|
||||||
class UpdateSection(BaseUpdateView):
|
class UpdateSection(BaseUpdateView):
|
||||||
model = Section
|
model = Section
|
||||||
form_class = SectionForm
|
form_class = SectionForm
|
||||||
formset_class = SubSectionFormSet
|
|
||||||
|
|
||||||
def get_formset_form_url(self, page):
|
|
||||||
return reverse('cms:createsubsection', args=[page.pk])
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
default_app_config = 'app.apps.Config'
|
|
@ -0,0 +1,7 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
from cms.admin import BaseSectionAdmin
|
||||||
|
from .models import Section
|
||||||
|
|
||||||
|
@admin.register(Section)
|
||||||
|
class SectionAdmin(BaseSectionAdmin):
|
||||||
|
pass
|
|
@ -0,0 +1,4 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
class Config(AppConfig):
|
||||||
|
name = 'app'
|
||||||
|
verbose_name = 'app'
|
|
@ -0,0 +1,64 @@
|
||||||
|
# Generated by Django 3.0.1 on 2019-12-31 11:16
|
||||||
|
|
||||||
|
import ckeditor.fields
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import embed_video.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
|
('cms', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Section',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('position', models.PositiveIntegerField(blank=True, verbose_name='position')),
|
||||||
|
('title', models.CharField(blank=True, max_length=255, verbose_name='title')),
|
||||||
|
('type', models.CharField(choices=[('normal', 'Normaal')], default='normal', max_length=16, verbose_name='section type')),
|
||||||
|
('color', models.PositiveIntegerField(choices=[(1, 'Wit')], default=1, verbose_name='color')),
|
||||||
|
('content', ckeditor.fields.RichTextField(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')),
|
||||||
|
('button_text', models.CharField(blank=True, max_length=255, verbose_name='button text')),
|
||||||
|
('button_link', models.CharField(blank=True, max_length=255, verbose_name='button link')),
|
||||||
|
('page', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='sections', to='cms.Page', 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',
|
||||||
|
'verbose_name_plural': 'sections',
|
||||||
|
'ordering': ['position'],
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ImageSection',
|
||||||
|
fields=[
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'proxy': True,
|
||||||
|
'indexes': [],
|
||||||
|
'constraints': [],
|
||||||
|
},
|
||||||
|
bases=('app.section',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='TextSection',
|
||||||
|
fields=[
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'proxy': True,
|
||||||
|
'indexes': [],
|
||||||
|
'constraints': [],
|
||||||
|
},
|
||||||
|
bases=('app.section',),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
from cms.models import BaseSection
|
||||||
|
|
||||||
|
class Section(BaseSection):
|
||||||
|
'''Add custom fields here. Already existing fields: title, color,
|
||||||
|
content, image, video, button_text, button_link
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
class TextSection(Section):
|
||||||
|
fields = ['title', 'content']
|
||||||
|
class Meta:
|
||||||
|
proxy = True
|
||||||
|
|
||||||
|
class ImageSection(Section):
|
||||||
|
fields = ['title', 'image']
|
||||||
|
class Meta:
|
||||||
|
proxy = True
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
$small: 500px;
|
||||||
|
$medium: 800px;
|
||||||
|
$font: sans-serif;
|
||||||
|
$titlefont: sans-serif;
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: $font;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
margin: .5em 0;
|
||||||
|
font-family: $titlefont;
|
||||||
|
}
|
||||||
|
h1 { font-size: 2em }
|
||||||
|
h2 { font-size: 1.5em }
|
||||||
|
h3 { font-size: 1.25em }
|
||||||
|
h4, h5, h6 { font-size: 1em }
|
||||||
|
|
||||||
|
a {
|
||||||
|
&:hover {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a.button {
|
||||||
|
&:hover {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
margin-top: 4em;
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
body {
|
||||||
|
font-family: sans-serif; }
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
margin: .5em 0;
|
||||||
|
font-family: sans-serif; }
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2em; }
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.5em; }
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1.25em; }
|
||||||
|
|
||||||
|
h4, h5, h6 {
|
||||||
|
font-size: 1em; }
|
||||||
|
|
||||||
|
header h1 {
|
||||||
|
text-align: center; }
|
||||||
|
|
||||||
|
header img {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
margin: auto; }
|
||||||
|
|
||||||
|
footer {
|
||||||
|
margin-top: 4em;
|
||||||
|
min-height: 400px; }
|
||||||
|
|
||||||
|
/*# sourceMappingURL=main1.scss.css.map */
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"file": "main1.css",
|
||||||
|
"sources": [
|
||||||
|
"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"
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{% extends 'cms/base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
{% block title %}app{% endblock %}
|
||||||
|
|
||||||
|
{% block extrahead %}
|
||||||
|
<link rel="stylesheet" href="{% static 'app/main1.scss.css' %}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<div class="wrapper">
|
||||||
|
<h1><a href="/">Awesome Website</a></h1>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -5,7 +5,7 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
PROJECT_NAME = INSERT_PROJECT_NAME_HERE
|
PROJECT_NAME = 'example'
|
||||||
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 = ['*']
|
||||||
|
@ -22,6 +22,7 @@ MEDIA_URL = '/media/'
|
||||||
MEDIA_ROOT = '/srv/' + PROJECT_NAME + '/media'
|
MEDIA_ROOT = '/srv/' + PROJECT_NAME + '/media'
|
||||||
LOGIN_REDIRECT_URL = '/'
|
LOGIN_REDIRECT_URL = '/'
|
||||||
PAGE_URL_PATTERN = 'cms:page'
|
PAGE_URL_PATTERN = 'cms:page'
|
||||||
|
CMS_SECTION_MODEL = 'app.Section'
|
||||||
|
|
||||||
def read(file):
|
def read(file):
|
||||||
with open(file) as f:
|
with open(file) as f:
|
||||||
|
@ -36,7 +37,8 @@ except IOError:
|
||||||
write(KEYFILE, SECRET_KEY)
|
write(KEYFILE, SECRET_KEY)
|
||||||
|
|
||||||
SECTION_TYPES = [
|
SECTION_TYPES = [
|
||||||
('normal', 'Normaal'),
|
('TextSection', 'Tekst'),
|
||||||
|
('ImageSection', 'Afbeelding'),
|
||||||
]
|
]
|
||||||
|
|
||||||
SECTION_COLORS = [
|
SECTION_COLORS = [
|
||||||
|
@ -63,8 +65,7 @@ CKEDITOR_CONFIGS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
'simplesass',
|
'app',
|
||||||
PROJECT_NAME,
|
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
|
@ -72,7 +73,9 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'cms',
|
'cms',
|
||||||
|
'simplesass',
|
||||||
'ckeditor',
|
'ckeditor',
|
||||||
|
'polymorphic',
|
||||||
'embed_video',
|
'embed_video',
|
||||||
'easy_thumbnails',
|
'easy_thumbnails',
|
||||||
'django_extensions',
|
'django_extensions',
|
||||||
|
|
|
@ -3,6 +3,8 @@ Django
|
||||||
django-ckeditor
|
django-ckeditor
|
||||||
django-extensions
|
django-extensions
|
||||||
django-embed-video
|
django-embed-video
|
||||||
|
django-polymorphic
|
||||||
easy-thumbnails
|
easy-thumbnails
|
||||||
psycopg2
|
psycopg2
|
||||||
libsass
|
libsass
|
||||||
|
swapper
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -3,7 +3,7 @@ from setuptools import setup, find_packages
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name = 'django-simplecms',
|
name = 'django-simplecms',
|
||||||
version = '1.0.2',
|
version = '2.0.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',
|
||||||
|
|
Ładowanie…
Reference in New Issue