kopia lustrzana https://github.com/rtts/django-simplecms
Simplify CRUD logic
Whe needs CRUD? If a page exists, you edit it. If it doesn't, the same form creates it. No more sections on a page? It gets automatically deleted. The only thing the user has to remember is: nothing. Brilliant, right?main
rodzic
b783424ff5
commit
c0464a5ab6
|
@ -60,9 +60,9 @@ class Numbered:
|
|||
|
||||
class BasePage(Numbered, models.Model):
|
||||
'''Abstract base model for pages'''
|
||||
number = models.PositiveIntegerField(_('number'), blank=True)
|
||||
title = VarCharField(_('title'))
|
||||
title = VarCharField(_('page'))
|
||||
slug = models.SlugField(_('slug'), blank=True, unique=True)
|
||||
number = models.PositiveIntegerField(_('number'), blank=True)
|
||||
menu = models.BooleanField(_('visible in menu'), default=True)
|
||||
|
||||
def __str__(self):
|
||||
|
@ -85,10 +85,10 @@ class BaseSection(Numbered, PolymorphicModel):
|
|||
'''Abstract base model for sections'''
|
||||
TYPES = [] # Will be populated by @register_model()
|
||||
page = models.ForeignKey(swapper.get_model_name('cms', 'Page'), verbose_name=_('page'), related_name='sections', on_delete=models.PROTECT)
|
||||
type = VarCharField(_('type'), blank=True)
|
||||
title = VarCharField(_('section'))
|
||||
type = VarCharField(_('type'))
|
||||
number = models.PositiveIntegerField(_('number'), blank=True)
|
||||
|
||||
title = VarCharField(_('title'), blank=True)
|
||||
content = models.TextField(_('content'), blank=True)
|
||||
image = models.ImageField(_('image'), blank=True)
|
||||
video = EmbedVideoField(_('video'), blank=True, help_text=_('Paste a YouTube, Vimeo, or SoundCloud link'))
|
||||
|
|
|
@ -66,10 +66,6 @@ div.spacer {
|
|||
clear: both;
|
||||
}
|
||||
|
||||
header, nav, section, footer {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
nav {
|
||||
padding: 0;
|
||||
|
||||
|
@ -181,9 +177,16 @@ div.edit {
|
|||
text-align: center;
|
||||
&.page {
|
||||
position: fixed;
|
||||
left: 1em;
|
||||
top: 1em;
|
||||
right: 1em;
|
||||
bottom: 1em;
|
||||
z-index: 1000;
|
||||
img {
|
||||
width: 75px;
|
||||
height: auto;
|
||||
}
|
||||
a:before, button:before, a:after, button:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -210,7 +213,6 @@ div.edit a, div.edit button, a.edit{
|
|||
|
||||
section {
|
||||
clear: both;
|
||||
border-bottom: 2px solid $blue;
|
||||
|
||||
div.image {
|
||||
img {
|
||||
|
@ -259,8 +261,8 @@ section.contactsection {
|
|||
/* Form elements */
|
||||
|
||||
form.cms {
|
||||
div.wrapper {
|
||||
overflow: hidden;
|
||||
section {
|
||||
margin-top: 3em;
|
||||
}
|
||||
|
||||
div.global_error {
|
||||
|
@ -288,8 +290,8 @@ form.cms {
|
|||
clear: both;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.type, &.number {
|
||||
width: 75%;
|
||||
&.type, &.number, &.slug {
|
||||
width: 77%;
|
||||
clear: none;
|
||||
float: left;
|
||||
}
|
||||
|
@ -308,15 +310,16 @@ form.cms {
|
|||
div.label {
|
||||
font-weight: 700;
|
||||
}
|
||||
input, select, .textarea, div.textarea {
|
||||
border: 1px solid black;
|
||||
input {
|
||||
//font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
div.label, label {
|
||||
font-size: 0.8rem;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 400;
|
||||
text-align: left;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
div.input {
|
||||
|
@ -336,39 +339,32 @@ form.cms {
|
|||
input, select, textarea {
|
||||
background: white;
|
||||
color: black;
|
||||
border: 1px solid #aaa;
|
||||
border: 0.5px solid #ccc;
|
||||
border-radius: 3px;
|
||||
font-size: 1rem;
|
||||
display: block;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 5px;
|
||||
padding: 5px 8px;
|
||||
font-family: inherit;
|
||||
}
|
||||
input[type=checkbox] {
|
||||
width: auto;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
input[name$=title] {
|
||||
font-weight: bold;
|
||||
}
|
||||
textarea {
|
||||
font-size: 1rem;
|
||||
padding: 5px 5px;
|
||||
height: 15em;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
input[type=checkbox] {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
select {
|
||||
background: white;
|
||||
}
|
||||
|
||||
div.filefield {
|
||||
border: 1px solid #aaa;
|
||||
background: white;
|
||||
padding: 4px;
|
||||
|
||||
input { border: none }
|
||||
padding-left: 3px;
|
||||
}
|
||||
|
||||
ul.errorlist {
|
||||
|
|
|
@ -49,9 +49,6 @@ div.spacer {
|
|||
height: 1rem;
|
||||
clear: both; }
|
||||
|
||||
header, nav, section, footer {
|
||||
padding: 1rem; }
|
||||
|
||||
nav {
|
||||
padding: 0; }
|
||||
nav button#hamburger {
|
||||
|
@ -133,9 +130,14 @@ div.edit {
|
|||
text-align: center; }
|
||||
div.edit.page {
|
||||
position: fixed;
|
||||
left: 1em;
|
||||
top: 1em;
|
||||
right: 1em;
|
||||
bottom: 1em;
|
||||
z-index: 1000; }
|
||||
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;
|
||||
|
@ -155,8 +157,7 @@ div.edit a, div.edit button, a.edit {
|
|||
content: ' ]'; }
|
||||
|
||||
section {
|
||||
clear: both;
|
||||
border-bottom: 2px solid #3573a8; }
|
||||
clear: both; }
|
||||
section div.image img {
|
||||
width: 100%; }
|
||||
section div.title {
|
||||
|
@ -182,8 +183,8 @@ section.contactsection textarea, section.contactsection input {
|
|||
font-family: inherit; }
|
||||
|
||||
/* Form elements */
|
||||
form.cms div.wrapper {
|
||||
overflow: hidden; }
|
||||
form.cms section {
|
||||
margin-top: 3em; }
|
||||
|
||||
form.cms div.global_error {
|
||||
border: 2px dotted red;
|
||||
|
@ -205,8 +206,8 @@ form.cms div.formfield {
|
|||
margin-bottom: 10px;
|
||||
clear: both;
|
||||
box-sizing: border-box; }
|
||||
form.cms div.formfield.type, form.cms div.formfield.number {
|
||||
width: 75%;
|
||||
form.cms div.formfield.type, form.cms div.formfield.number, form.cms div.formfield.slug {
|
||||
width: 77%;
|
||||
clear: none;
|
||||
float: left; }
|
||||
form.cms div.formfield.number {
|
||||
|
@ -222,13 +223,11 @@ form.cms div.formfield.error {
|
|||
form.cms div.formfield.required div.label {
|
||||
font-weight: 700; }
|
||||
|
||||
form.cms div.formfield.required input, form.cms div.formfield.required select, form.cms div.formfield.required .textarea, form.cms div.formfield.required div.textarea {
|
||||
border: 1px solid black; }
|
||||
|
||||
form.cms div.label, form.cms label {
|
||||
font-size: 0.8rem;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 400;
|
||||
text-align: left; }
|
||||
text-align: left;
|
||||
margin-bottom: 2px; }
|
||||
|
||||
form.cms div.input {
|
||||
overflow: hidden; }
|
||||
|
@ -244,36 +243,32 @@ form.cms span.required {
|
|||
form.cms input, form.cms select, form.cms textarea {
|
||||
background: white;
|
||||
color: black;
|
||||
border: 1px solid #aaa;
|
||||
border: 0.5px solid #ccc;
|
||||
border-radius: 3px;
|
||||
font-size: 1rem;
|
||||
display: block;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 5px;
|
||||
padding: 5px 8px;
|
||||
font-family: inherit; }
|
||||
|
||||
form.cms input[type=checkbox] {
|
||||
width: auto;
|
||||
display: inline-block;
|
||||
vertical-align: middle; }
|
||||
|
||||
form.cms input[name$=title] {
|
||||
font-weight: bold; }
|
||||
|
||||
form.cms textarea {
|
||||
font-size: 1rem;
|
||||
padding: 5px 5px;
|
||||
height: 15em; }
|
||||
|
||||
form.cms input[type=checkbox] {
|
||||
width: auto; }
|
||||
height: 15em;
|
||||
line-height: 1.5; }
|
||||
|
||||
form.cms select {
|
||||
background: white; }
|
||||
|
||||
form.cms div.filefield {
|
||||
border: 1px solid #aaa;
|
||||
background: white;
|
||||
padding: 4px; }
|
||||
form.cms div.filefield input {
|
||||
border: none; }
|
||||
padding-left: 3px; }
|
||||
|
||||
form.cms ul.errorlist {
|
||||
margin: 0;
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,75 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1"
|
||||
viewBox="0 0 48 48"
|
||||
enable-background="new 0 0 48 48"
|
||||
id="svg6"
|
||||
sodipodi:docname="edit.svg"
|
||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
|
||||
<metadata
|
||||
id="metadata12">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs10" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1075"
|
||||
id="namedview8"
|
||||
showgrid="true"
|
||||
inkscape:zoom="13.906433"
|
||||
inkscape:cx="58.141489"
|
||||
inkscape:cy="18.442256"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg6">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid887" />
|
||||
</sodipodi:namedview>
|
||||
<circle
|
||||
fill="#4CAF50"
|
||||
cx="24"
|
||||
cy="24"
|
||||
r="21"
|
||||
id="circle2" />
|
||||
<g
|
||||
id="g895"
|
||||
transform="matrix(0.49467333,-0.05078122,-0.05078122,0.49467333,-9.7836852,14.587579)"
|
||||
style="fill:#ffffff;fill-opacity:1">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path889"
|
||||
d="M 50,48 H 60 L 85,23 75,13 50,38 Z"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path891"
|
||||
d="M 78,10 88,20 94,14 84,4 Z"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
</svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 2.3 KiB |
|
@ -0,0 +1,60 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1"
|
||||
viewBox="0 0 48 48"
|
||||
enable-background="new 0 0 48 48"
|
||||
id="svg828"
|
||||
sodipodi:docname="ok.svg"
|
||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
|
||||
<metadata
|
||||
id="metadata834">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs832" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1075"
|
||||
id="namedview830"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.9166667"
|
||||
inkscape:cx="-11.389831"
|
||||
inkscape:cy="24"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg828" />
|
||||
<circle
|
||||
fill="#4CAF50"
|
||||
cx="24"
|
||||
cy="24"
|
||||
r="21"
|
||||
id="circle824" />
|
||||
<polygon
|
||||
fill="#CCFF90"
|
||||
points="34.6,14.6 21,28.2 15.4,22.6 12.6,25.4 21,33.8 37.4,17.4"
|
||||
id="polygon826"
|
||||
style="fill:#ffffff;fill-opacity:1" />
|
||||
</svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 1.7 KiB |
|
@ -0,0 +1 @@
|
|||
{% extends 'cms/admin.html' %}
|
|
@ -0,0 +1,19 @@
|
|||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="{% get_current_language as lang%}{{lang}}">
|
||||
<head>
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="{% static 'cms/cms.scss.css' %}">
|
||||
{% block extrahead %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extrabody %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
|
@ -1,41 +1,31 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% extends 'admin.html' %}
|
||||
{% load static i18n %}
|
||||
|
||||
{% block title %}{% trans 'Edit' %} {{form.instance}}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="POST" enctype="multipart/form-data" class="cms">
|
||||
{% csrf_token %}
|
||||
{{form.media}}
|
||||
<div class="edit page">
|
||||
<button>{% trans 'save' %}</button>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<div class="wrapper">
|
||||
<div class="title">
|
||||
<h1>{{form.instance}}</h1>
|
||||
</div>
|
||||
{% if form.errors or formset.errors %}
|
||||
<div class="global_error">
|
||||
{% trans 'Please correct the error(s) below and save again' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% for field in form %}
|
||||
{% if field.field.widget.input_type == 'checkbox' %}
|
||||
{% include 'cms/formfield_checkbox.html' with field=field %}
|
||||
{% else %}
|
||||
{% include 'cms/formfield.html' with field=field %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if form.instance.pk %}
|
||||
<div class="formfield">
|
||||
<div class="input">
|
||||
<input type="checkbox" name="delete" id="id_delete"> <label for="id_delete">{% trans 'Delete' %}</label>
|
||||
{% if form %}
|
||||
<section>
|
||||
<div class="wrapper">
|
||||
{% if form.errors or formset.errors %}
|
||||
<div class="global_error">
|
||||
{% trans 'Please correct the error(s) below and save again' %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
{% for field in form %}
|
||||
{% if field.field.widget.input_type == 'checkbox' %}
|
||||
{% include 'cms/formfield_checkbox.html' with field=field %}
|
||||
{% else %}
|
||||
{% include 'cms/formfield.html' with field=field %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
{% if formset %}
|
||||
{{formset.management_form}}
|
||||
|
@ -46,7 +36,6 @@
|
|||
{% endfor %}
|
||||
<section>
|
||||
<div class="wrapper">
|
||||
<h1>{{form.instance}}</h1>
|
||||
<div class="subform">
|
||||
{% for field in form.visible_fields %}
|
||||
{% if field.name == 'DELETE' and not form.instance.pk %}
|
||||
|
@ -62,91 +51,98 @@
|
|||
</section>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<div class="edit page">
|
||||
<button><img src="{% static 'cms/ok.svg' %}"></button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block extrabody %}
|
||||
<script type="text/javascript" src="/static/admin/js/urlify.js"></script>
|
||||
<script>
|
||||
// https://stackoverflow.com/a/25621277
|
||||
var tx = document.getElementsByTagName('textarea');
|
||||
for (var i = 0; i < tx.length; i++) {
|
||||
tx[i].setAttribute('style', 'height:0;overflow-y:hidden;');
|
||||
tx[i].style.height = (tx[i].scrollHeight) + 'px';
|
||||
tx[i].addEventListener("input", OnInput, false);
|
||||
}
|
||||
function OnInput() {
|
||||
this.style.height = '0';
|
||||
this.style.height = (this.scrollHeight) + 'px';
|
||||
}
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
|
||||
var slugfield = document.getElementById('id_slug');
|
||||
var titlefield = document.getElementById('id_title');
|
||||
// Auto-resize <textarea's>
|
||||
|
||||
/* My own implementation of Django's prepopulate.js */
|
||||
var tx = document.getElementsByTagName('textarea');
|
||||
for (var i = 0; i < tx.length; i++) {
|
||||
tx[i].setAttribute('style', 'height:0;overflow-y:hidden;');
|
||||
tx[i].style.height = (tx[i].scrollHeight) + 'px';
|
||||
tx[i].addEventListener("input", OnInput, false);
|
||||
}
|
||||
function OnInput() {
|
||||
this.style.height = '0';
|
||||
this.style.height = (this.scrollHeight) + 'px';
|
||||
}
|
||||
|
||||
if (slugfield && titlefield) {
|
||||
var virgin = slugfield.value === '';
|
||||
// My own implementation of Django's prepopulate.js
|
||||
|
||||
if (virgin) {
|
||||
titlefield.addEventListener('input', function(event) {
|
||||
if (virgin) {
|
||||
slugfield.value = URLify(titlefield.value);
|
||||
var slugfield = document.getElementById('id_slug');
|
||||
var titlefield = document.getElementById('id_title');
|
||||
if (slugfield && titlefield) {
|
||||
var virgin = slugfield.value === '';
|
||||
if (virgin) {
|
||||
titlefield.addEventListener('input', function(event) {
|
||||
if (virgin) {
|
||||
slugfield.value = URLify(titlefield.value);
|
||||
}
|
||||
});
|
||||
slugfield.addEventListener('input', function(event) {
|
||||
virgin = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-hide non-relevant fields, based on type field
|
||||
|
||||
{% if fields_per_type %}
|
||||
var typefield = document.getElementById('id_type');
|
||||
fields_per_type = {{fields_per_type|safe}};
|
||||
|
||||
if (typefield) {
|
||||
function show_relevant_fields(type) {
|
||||
for (el of document.querySelectorAll('div.formfield')) {
|
||||
el.style.display = 'none';
|
||||
}
|
||||
});
|
||||
slugfield.addEventListener('input', function(event) {
|
||||
virgin = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
{% if fields_per_type %}
|
||||
|
||||
var typefield = document.getElementById('id_type');
|
||||
fields_per_type = {{fields_per_type|safe}};
|
||||
|
||||
if (typefield) {
|
||||
function show_relevant_fields(type) {
|
||||
for (el of document.querySelectorAll('div.formfield')) {
|
||||
el.style.display = 'none';
|
||||
for (field of fields_per_type[type]) {
|
||||
el = document.getElementById(field);
|
||||
el.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
for (field of fields_per_type[type]) {
|
||||
el = document.getElementById(field);
|
||||
el.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
typefield.addEventListener('input', function(event) {
|
||||
typefield.addEventListener('input', function(event) {
|
||||
show_relevant_fields(typefield.value.toLowerCase());
|
||||
});
|
||||
show_relevant_fields(typefield.value.toLowerCase());
|
||||
});
|
||||
show_relevant_fields(typefield.value.toLowerCase());
|
||||
}
|
||||
|
||||
subforms = document.querySelectorAll('div.subform');
|
||||
for (let i = 0; i < subforms.length; ++i) {
|
||||
let typefield = document.getElementById('id_sections-' + i + '-type');
|
||||
|
||||
function show_relevant_fields(type) {
|
||||
for (field of subforms[i].querySelectorAll('div.formfield')) {
|
||||
field.style.display = 'none';
|
||||
}
|
||||
|
||||
delete_checkbox = document.getElementById('DELETE' + i);
|
||||
if (delete_checkbox) {
|
||||
delete_checkbox.style.display = 'block';
|
||||
}
|
||||
for (field of fields_per_type[type]) {
|
||||
el = document.getElementById(field + i);
|
||||
el.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
typefield.addEventListener('input', function(event) {
|
||||
(show_relevant_fields(typefield.value.toLowerCase()));
|
||||
});
|
||||
show_relevant_fields(typefield.value.toLowerCase());
|
||||
}
|
||||
{% endif %}
|
||||
subforms = document.querySelectorAll('div.subform');
|
||||
for (let i = 0; i < subforms.length; ++i) {
|
||||
let typefield = document.getElementById('id_sections-' + i + '-type');
|
||||
|
||||
function show_relevant_fields(type) {
|
||||
for (field of subforms[i].querySelectorAll('div.formfield')) {
|
||||
field.style.display = 'none';
|
||||
}
|
||||
|
||||
delete_checkbox = document.getElementById('DELETE' + i);
|
||||
if (delete_checkbox) {
|
||||
delete_checkbox.style.display = 'block';
|
||||
}
|
||||
for (field of fields_per_type[type]) {
|
||||
el = document.getElementById(field + i);
|
||||
el.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
typefield.addEventListener('input', function(event) {
|
||||
(show_relevant_fields(typefield.value.toLowerCase()));
|
||||
});
|
||||
show_relevant_fields(typefield.value.toLowerCase());
|
||||
}
|
||||
{% endif %}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
{% extends 'cms/edit.html' %}
|
||||
|
||||
{% block formset %}{% endblock %}
|
|
@ -1,5 +1,5 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load cms %}
|
||||
|
||||
{% block title %}{{block.super}} - {{page.title}}{% endblock %}
|
||||
|
@ -11,7 +11,7 @@
|
|||
|
||||
{% if user.is_staff %}
|
||||
<div class="edit page">
|
||||
<a href="{% if page.slug %}{% url 'cms:updatepage' page.pk %}{% else %}{% url 'cms:updatehomepage' %}{% endif %}">{% trans 'edit this page' %}</a>
|
||||
<a href="{% if page.slug %}{% url 'cms:editpage' page.slug %}{% else %}{% url 'cms:editpage' %}{% endif %}"><img src="{% static 'cms/edit.svg' %}"></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
from django.urls import path
|
||||
from .views import PageView, UpdatePage, CreatePage, UpdateSection, CreateSection
|
||||
from .views import PageView, EditPage, CreatePage
|
||||
|
||||
app_name = 'cms'
|
||||
|
||||
urlpatterns = [
|
||||
path('updatepage/', UpdatePage.as_view(), {'slug': ''}, name='updatehomepage'),
|
||||
path('updatepage/<int:pk>/', UpdatePage.as_view(), name='updatepage'),
|
||||
path('updatesection/<int:pk>/', UpdateSection.as_view(), name='updatesection'),
|
||||
path('edit/', EditPage.as_view(), name='editpage'),
|
||||
path('<slug:slug>/edit/', EditPage.as_view(), name='editpage'),
|
||||
path('createpage/', CreatePage.as_view(), name='createpage'),
|
||||
path('createsection/<int:pk>', CreateSection.as_view(), name='createsection'),
|
||||
path('', PageView.as_view(), name='page'),
|
||||
path('<slug:slug>/', PageView.as_view(), name='page'),
|
||||
]
|
||||
|
|
125
cms/views.py
125
cms/views.py
|
@ -2,8 +2,10 @@ import json
|
|||
import swapper
|
||||
|
||||
from django.views import generic
|
||||
from django.http import Http404
|
||||
from django.shortcuts import redirect
|
||||
from django.views.generic.edit import FormMixin
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
from django.contrib.admin.utils import NestedObjects
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
@ -63,24 +65,7 @@ class SectionFormSetView(SectionView):
|
|||
kwargs['formset'] = self.get_formset()
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
class MenuMixin:
|
||||
'''Add pages to template context'''
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
pages = Page.objects.filter(menu=True)
|
||||
context.update({
|
||||
'pages': pages,
|
||||
})
|
||||
return context
|
||||
|
||||
class MemoryMixin:
|
||||
'''Remember the previous page in session'''
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if request.user.is_authenticated:
|
||||
request.session['previous_url'] = request.path
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
class PageView(MenuMixin, MemoryMixin, generic.DetailView):
|
||||
class PageView(generic.DetailView):
|
||||
'''View of a page with heterogeneous (polymorphic) sections'''
|
||||
model = Page
|
||||
template_name = 'cms/page.html'
|
||||
|
@ -99,7 +84,13 @@ class PageView(MenuMixin, MemoryMixin, generic.DetailView):
|
|||
|
||||
def get(self, request, *args, **kwargs):
|
||||
'''Initialize sections and render final response'''
|
||||
page = self.object = self.get_object()
|
||||
try:
|
||||
page = self.object = self.get_object()
|
||||
except Http404:
|
||||
if self.request.user.has_perm('cms_page_create'):
|
||||
return redirect('cms:editpage', self.kwargs['slug'])
|
||||
else:
|
||||
raise
|
||||
context = self.get_context_data(**kwargs)
|
||||
sections = page.sections.all()
|
||||
for section in sections:
|
||||
|
@ -135,15 +126,46 @@ class PageView(MenuMixin, MemoryMixin, generic.DetailView):
|
|||
})
|
||||
return self.render_to_response(context)
|
||||
|
||||
# The following views require a logged-in staff member
|
||||
|
||||
class StaffRequiredMixin(UserPassesTestMixin):
|
||||
def test_func(self):
|
||||
return self.request.user.is_staff
|
||||
|
||||
class TypeMixin(MenuMixin):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
pages = Page.objects.filter(menu=True)
|
||||
context.update({
|
||||
'pages': pages,
|
||||
})
|
||||
return context
|
||||
|
||||
class EditPage(UserPassesTestMixin, generic.UpdateView):
|
||||
model = Page
|
||||
form_class = PageForm
|
||||
template_name = 'cms/edit.html'
|
||||
|
||||
def setup(self, *args, slug='', **kwargs):
|
||||
'''Supply a default argument for slug'''
|
||||
super().setup(*args, slug=slug, **kwargs)
|
||||
|
||||
def test_func(self):
|
||||
return self.request.user.has_perm('cms_page_change')
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs.update({'label_suffix': ''})
|
||||
return kwargs
|
||||
|
||||
def form_valid(self, form):
|
||||
object = form.save()
|
||||
return redirect(object.get_absolute_url())
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
try:
|
||||
page = self.object = self.get_object()
|
||||
except Http404:
|
||||
return CreatePage.as_view()(request, slug=self.kwargs['slug'])
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
if 'formset' not in context:
|
||||
context['formset'] = SectionFormSet(instance=self.object, form_kwargs={'label_suffix': ''})
|
||||
fields_per_type = {}
|
||||
for model, _ in Section.TYPES:
|
||||
ctype = ContentType.objects.get(
|
||||
|
@ -157,38 +179,6 @@ class TypeMixin(MenuMixin):
|
|||
})
|
||||
return context
|
||||
|
||||
class BaseUpdateView(generic.UpdateView):
|
||||
template_name = 'cms/edit.html'
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs.update({'label_suffix': ''})
|
||||
return kwargs
|
||||
|
||||
def form_valid(self, form):
|
||||
if 'delete' in self.request.POST:
|
||||
collector = NestedObjects(using='default')
|
||||
collector.collect([self.object])
|
||||
self.template_name = 'cms/confirm.html'
|
||||
return self.render_to_response(self.get_context_data(
|
||||
deleted = collector.nested(),
|
||||
protected = collector.protected,
|
||||
object = self.object,
|
||||
))
|
||||
else:
|
||||
form.save()
|
||||
return redirect(self.request.session.get('previous_url'))
|
||||
|
||||
class UpdatePage(StaffRequiredMixin, TypeMixin, BaseUpdateView):
|
||||
model = Page
|
||||
form_class = PageForm
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
if 'formset' not in context:
|
||||
context['formset'] = SectionFormSet(instance=self.object, form_kwargs={'label_suffix': ''})
|
||||
return context
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
form = self.get_form()
|
||||
|
@ -202,21 +192,10 @@ class UpdatePage(StaffRequiredMixin, TypeMixin, BaseUpdateView):
|
|||
def form_invalid(self, form, formset):
|
||||
return self.render_to_response(self.get_context_data(form=form, formset=formset))
|
||||
|
||||
class UpdateSection(StaffRequiredMixin, TypeMixin, BaseUpdateView):
|
||||
model = Section
|
||||
form_class = SectionForm
|
||||
|
||||
class CreatePage(StaffRequiredMixin, MenuMixin, generic.CreateView):
|
||||
class CreatePage(generic.CreateView):
|
||||
model = Page
|
||||
form_class = PageForm
|
||||
template_name = 'cms/new.html'
|
||||
template_name = 'cms/edit.html'
|
||||
|
||||
class CreateSection(StaffRequiredMixin, TypeMixin, generic.CreateView):
|
||||
model = Section
|
||||
form_class = SectionForm
|
||||
template_name = 'cms/new.html'
|
||||
|
||||
def form_valid(self, form):
|
||||
form.instance.page = Page.objects.get(pk=self.kwargs.get('pk'))
|
||||
form.save()
|
||||
return redirect(self.request.session.get('previous_url'))
|
||||
def get_form_kwargs(self, *args, **kwargs):
|
||||
return {'initial': {'slug': self.kwargs['slug']}}
|
||||
|
|
|
@ -23,7 +23,7 @@ class TextSection(Section):
|
|||
|
||||
@register_model('Button')
|
||||
class ButtonSection(Section):
|
||||
fields = ['button_text', 'button_link']
|
||||
fields = ['title', 'href']
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue