kopia lustrzana https://github.com/rtts/django-simplecms
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!main
rodzic
09f03b6866
commit
3cc1f9ec08
|
@ -1 +1,4 @@
|
|||
from .decorators import register
|
||||
from .cms import SectionView, SectionFormView
|
||||
|
||||
default_app_config = 'cms.apps.CmsConfig'
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
24
cms/forms.py
24
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)
|
||||
|
||||
|
|
|
@ -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',
|
||||
),
|
||||
]
|
|
@ -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')
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -5,5 +5,5 @@
|
|||
"admin.scss"
|
||||
],
|
||||
"names": [],
|
||||
"mappings": "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"
|
||||
"mappings": "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"
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 */
|
File diff suppressed because one or more lines are too long
|
@ -1,8 +1,8 @@
|
|||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load static i18n %}
|
||||
{% get_current_language as lang%}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="{% get_current_language as lang%}{{lang}}">
|
||||
<html lang="{{lang}}">
|
||||
<head>
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
<meta charset="utf-8"/>
|
||||
|
@ -49,7 +49,6 @@
|
|||
|
||||
<footer>
|
||||
{% block footer %}
|
||||
{{footer|safe}}
|
||||
{% endblock %}
|
||||
</footer>
|
||||
|
||||
|
|
|
@ -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 %}
|
||||
<div id="{{section.title|slugify}}"></div>
|
||||
{% include_section section %}
|
||||
{% endfor %}
|
||||
|
||||
{% if perms.cms_page.change %}
|
||||
<div class="edit page">
|
||||
<div class="edit">
|
||||
<a href="{% if page.slug %}{% url 'cms:updatepage' page.slug %}{% else %}{% url 'cms:updatepage' %}{% endif %}"><img src="{% static 'cms/edit.png' %}"></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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))
|
|
@ -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))
|
112
cms/views.py
112
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):
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
default_app_config = 'app.apps.Config'
|
|
@ -1,4 +0,0 @@
|
|||
from django.apps import AppConfig
|
||||
class Config(AppConfig):
|
||||
name = 'app'
|
||||
verbose_name = 'app'
|
|
@ -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'
|
|
@ -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',
|
||||
|
|
|
@ -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'],
|
||||
},
|
||||
),
|
||||
]
|
|
@ -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']
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 */
|
File diff suppressed because one or more lines are too long
|
@ -1,7 +0,0 @@
|
|||
<section class="button">
|
||||
<div class="wrapper">
|
||||
<div class="button">
|
||||
<a class="button" href="{{section.href}}">{{section.title}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
|
@ -15,4 +15,8 @@
|
|||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if perms.cms_section_change %}
|
||||
<a class="edit" href="{% url 'cms:updatesection' section.number %}">{% trans 'edit' %}</a><br>
|
||||
{% endif %}
|
||||
</section>
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
{% load thumbnail %}
|
||||
|
||||
<section class="image">
|
||||
<div class="wrapper">
|
||||
{% if section.image %}
|
||||
<div class="image">
|
||||
<img alt="{{section.title}}" src="{% thumbnail section.image 800x800 %}">
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
|
@ -0,0 +1,15 @@
|
|||
{% load thumbnail i18n %}
|
||||
|
||||
<section class="images">
|
||||
<div class="images">
|
||||
{% for image in section.images.all %}
|
||||
<div class="image">
|
||||
<img src="{% thumbnail image.image 700x700 %}">
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if perms.cms_section_change %}
|
||||
<a class="edit" href="{% url 'cms:updatesection' section.number %}">{% trans 'edit' %}</a><br>
|
||||
{% endif %}
|
||||
</section>
|
|
@ -1,4 +1,4 @@
|
|||
{% load markdown %}
|
||||
{% load eval i18n %}
|
||||
|
||||
<section class="text">
|
||||
<div class="wrapper">
|
||||
|
@ -9,7 +9,11 @@
|
|||
</div>
|
||||
|
||||
<div class="content">
|
||||
{{section.content|markdown}}
|
||||
{% eval section.content %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if perms.cms_section_change %}
|
||||
<a class="edit" href="{% url 'cms:updatesection' section.number %}">{% trans 'edit' %}</a><br>
|
||||
{% endif %}
|
||||
</section>
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
{% load embed_video_tags %}
|
||||
{% load embed_video_tags i18n %}
|
||||
|
||||
<section class="video">
|
||||
<div class="wrapper">
|
||||
{% if section.video %}
|
||||
<div class="video">
|
||||
<div class="iframe">
|
||||
{% video section.video '800x600' %}
|
||||
</div>
|
||||
{% if section.video %}
|
||||
<div class="video">
|
||||
<div class="iframe">
|
||||
{% video section.video '800x600' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if perms.cms_section_change %}
|
||||
<a class="edit" href="{% url 'cms:updatesection' section.number %}">{% trans 'edit' %}</a><br>
|
||||
{% endif %}
|
||||
</section>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{% extends 'cms/base.html' %}
|
||||
{% load static %}
|
||||
{% block title %}app{% endblock %}
|
||||
|
||||
{% block title %}Awesome Website{% endblock %}
|
||||
|
||||
{% block extrahead %}
|
||||
<link rel="stylesheet" href="{% static 'app/main1.scss.css' %}">
|
||||
|
|
|
@ -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'
|
|
@ -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',
|
||||
|
|
Ładowanie…
Reference in New Issue