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):
|
class BasePage(Numbered, models.Model):
|
||||||
'''Abstract base model for pages'''
|
'''Abstract base model for pages'''
|
||||||
number = models.PositiveIntegerField(_('number'), blank=True)
|
title = VarCharField(_('page'))
|
||||||
title = VarCharField(_('title'))
|
|
||||||
slug = models.SlugField(_('slug'), blank=True, unique=True)
|
slug = models.SlugField(_('slug'), blank=True, unique=True)
|
||||||
|
number = models.PositiveIntegerField(_('number'), blank=True)
|
||||||
menu = models.BooleanField(_('visible in menu'), default=True)
|
menu = models.BooleanField(_('visible in menu'), default=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -85,10 +85,10 @@ class BaseSection(Numbered, PolymorphicModel):
|
||||||
'''Abstract base model for sections'''
|
'''Abstract base model for sections'''
|
||||||
TYPES = [] # Will be populated by @register_model()
|
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)
|
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)
|
number = models.PositiveIntegerField(_('number'), blank=True)
|
||||||
|
|
||||||
title = VarCharField(_('title'), blank=True)
|
|
||||||
content = models.TextField(_('content'), blank=True)
|
content = models.TextField(_('content'), blank=True)
|
||||||
image = models.ImageField(_('image'), blank=True)
|
image = models.ImageField(_('image'), blank=True)
|
||||||
video = EmbedVideoField(_('video'), blank=True, help_text=_('Paste a YouTube, Vimeo, or SoundCloud link'))
|
video = EmbedVideoField(_('video'), blank=True, help_text=_('Paste a YouTube, Vimeo, or SoundCloud link'))
|
||||||
|
|
|
@ -66,10 +66,6 @@ div.spacer {
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
|
|
||||||
header, nav, section, footer {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
|
@ -181,9 +177,16 @@ div.edit {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
&.page {
|
&.page {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 1em;
|
right: 1em;
|
||||||
top: 1em;
|
bottom: 1em;
|
||||||
z-index: 1000;
|
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 {
|
section {
|
||||||
clear: both;
|
clear: both;
|
||||||
border-bottom: 2px solid $blue;
|
|
||||||
|
|
||||||
div.image {
|
div.image {
|
||||||
img {
|
img {
|
||||||
|
@ -259,8 +261,8 @@ section.contactsection {
|
||||||
/* Form elements */
|
/* Form elements */
|
||||||
|
|
||||||
form.cms {
|
form.cms {
|
||||||
div.wrapper {
|
section {
|
||||||
overflow: hidden;
|
margin-top: 3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.global_error {
|
div.global_error {
|
||||||
|
@ -288,8 +290,8 @@ form.cms {
|
||||||
clear: both;
|
clear: both;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
&.type, &.number {
|
&.type, &.number, &.slug {
|
||||||
width: 75%;
|
width: 77%;
|
||||||
clear: none;
|
clear: none;
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
@ -308,15 +310,16 @@ form.cms {
|
||||||
div.label {
|
div.label {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
input, select, .textarea, div.textarea {
|
input {
|
||||||
border: 1px solid black;
|
//font-weight: bold;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
div.label, label {
|
div.label, label {
|
||||||
font-size: 0.8rem;
|
font-size: 0.7rem;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
margin-bottom: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.input {
|
div.input {
|
||||||
|
@ -336,39 +339,32 @@ form.cms {
|
||||||
input, select, textarea {
|
input, select, textarea {
|
||||||
background: white;
|
background: white;
|
||||||
color: black;
|
color: black;
|
||||||
border: 1px solid #aaa;
|
border: 0.5px solid #ccc;
|
||||||
|
border-radius: 3px;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 5px;
|
padding: 5px 8px;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
}
|
}
|
||||||
input[type=checkbox] {
|
input[type=checkbox] {
|
||||||
|
width: auto;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
input[name$=title] {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
textarea {
|
textarea {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
padding: 5px 5px;
|
|
||||||
height: 15em;
|
height: 15em;
|
||||||
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=checkbox] {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
background: white;
|
background: white;
|
||||||
}
|
padding-left: 3px;
|
||||||
|
|
||||||
div.filefield {
|
|
||||||
border: 1px solid #aaa;
|
|
||||||
background: white;
|
|
||||||
padding: 4px;
|
|
||||||
|
|
||||||
input { border: none }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.errorlist {
|
ul.errorlist {
|
||||||
|
|
|
@ -49,9 +49,6 @@ div.spacer {
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
clear: both; }
|
clear: both; }
|
||||||
|
|
||||||
header, nav, section, footer {
|
|
||||||
padding: 1rem; }
|
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
padding: 0; }
|
padding: 0; }
|
||||||
nav button#hamburger {
|
nav button#hamburger {
|
||||||
|
@ -133,9 +130,14 @@ div.edit {
|
||||||
text-align: center; }
|
text-align: center; }
|
||||||
div.edit.page {
|
div.edit.page {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 1em;
|
right: 1em;
|
||||||
top: 1em;
|
bottom: 1em;
|
||||||
z-index: 1000; }
|
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 {
|
div.edit a, div.edit button, a.edit {
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
|
@ -155,8 +157,7 @@ div.edit a, div.edit button, a.edit {
|
||||||
content: ' ]'; }
|
content: ' ]'; }
|
||||||
|
|
||||||
section {
|
section {
|
||||||
clear: both;
|
clear: both; }
|
||||||
border-bottom: 2px solid #3573a8; }
|
|
||||||
section div.image img {
|
section div.image img {
|
||||||
width: 100%; }
|
width: 100%; }
|
||||||
section div.title {
|
section div.title {
|
||||||
|
@ -182,8 +183,8 @@ section.contactsection textarea, section.contactsection input {
|
||||||
font-family: inherit; }
|
font-family: inherit; }
|
||||||
|
|
||||||
/* Form elements */
|
/* Form elements */
|
||||||
form.cms div.wrapper {
|
form.cms section {
|
||||||
overflow: hidden; }
|
margin-top: 3em; }
|
||||||
|
|
||||||
form.cms div.global_error {
|
form.cms div.global_error {
|
||||||
border: 2px dotted red;
|
border: 2px dotted red;
|
||||||
|
@ -205,8 +206,8 @@ form.cms div.formfield {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
clear: both;
|
clear: both;
|
||||||
box-sizing: border-box; }
|
box-sizing: border-box; }
|
||||||
form.cms div.formfield.type, form.cms div.formfield.number {
|
form.cms div.formfield.type, form.cms div.formfield.number, form.cms div.formfield.slug {
|
||||||
width: 75%;
|
width: 77%;
|
||||||
clear: none;
|
clear: none;
|
||||||
float: left; }
|
float: left; }
|
||||||
form.cms div.formfield.number {
|
form.cms div.formfield.number {
|
||||||
|
@ -222,13 +223,11 @@ form.cms div.formfield.error {
|
||||||
form.cms div.formfield.required div.label {
|
form.cms div.formfield.required div.label {
|
||||||
font-weight: 700; }
|
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 {
|
form.cms div.label, form.cms label {
|
||||||
font-size: 0.8rem;
|
font-size: 0.7rem;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
text-align: left; }
|
text-align: left;
|
||||||
|
margin-bottom: 2px; }
|
||||||
|
|
||||||
form.cms div.input {
|
form.cms div.input {
|
||||||
overflow: hidden; }
|
overflow: hidden; }
|
||||||
|
@ -244,36 +243,32 @@ form.cms span.required {
|
||||||
form.cms input, form.cms select, form.cms textarea {
|
form.cms input, form.cms select, form.cms textarea {
|
||||||
background: white;
|
background: white;
|
||||||
color: black;
|
color: black;
|
||||||
border: 1px solid #aaa;
|
border: 0.5px solid #ccc;
|
||||||
|
border-radius: 3px;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 5px;
|
padding: 5px 8px;
|
||||||
font-family: inherit; }
|
font-family: inherit; }
|
||||||
|
|
||||||
form.cms input[type=checkbox] {
|
form.cms input[type=checkbox] {
|
||||||
|
width: auto;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: middle; }
|
vertical-align: middle; }
|
||||||
|
|
||||||
|
form.cms input[name$=title] {
|
||||||
|
font-weight: bold; }
|
||||||
|
|
||||||
form.cms textarea {
|
form.cms textarea {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
padding: 5px 5px;
|
height: 15em;
|
||||||
height: 15em; }
|
line-height: 1.5; }
|
||||||
|
|
||||||
form.cms input[type=checkbox] {
|
|
||||||
width: auto; }
|
|
||||||
|
|
||||||
form.cms select {
|
form.cms select {
|
||||||
background: white; }
|
|
||||||
|
|
||||||
form.cms div.filefield {
|
|
||||||
border: 1px solid #aaa;
|
|
||||||
background: white;
|
background: white;
|
||||||
padding: 4px; }
|
padding-left: 3px; }
|
||||||
form.cms div.filefield input {
|
|
||||||
border: none; }
|
|
||||||
|
|
||||||
form.cms ul.errorlist {
|
form.cms ul.errorlist {
|
||||||
margin: 0;
|
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,19 +1,16 @@
|
||||||
{% extends 'base.html' %}
|
{% extends 'admin.html' %}
|
||||||
{% load i18n %}
|
{% load static i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans 'Edit' %} {{form.instance}}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form method="POST" enctype="multipart/form-data" class="cms">
|
<form method="POST" enctype="multipart/form-data" class="cms">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{form.media}}
|
{{form.media}}
|
||||||
<div class="edit page">
|
|
||||||
<button>{% trans 'save' %}</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
{% if form %}
|
||||||
<section>
|
<section>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<div class="title">
|
|
||||||
<h1>{{form.instance}}</h1>
|
|
||||||
</div>
|
|
||||||
{% if form.errors or formset.errors %}
|
{% if form.errors or formset.errors %}
|
||||||
<div class="global_error">
|
<div class="global_error">
|
||||||
{% trans 'Please correct the error(s) below and save again' %}
|
{% trans 'Please correct the error(s) below and save again' %}
|
||||||
|
@ -26,16 +23,9 @@
|
||||||
{% include 'cms/formfield.html' with field=field %}
|
{% include 'cms/formfield.html' with field=field %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% 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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if formset %}
|
{% if formset %}
|
||||||
{{formset.management_form}}
|
{{formset.management_form}}
|
||||||
|
@ -46,7 +36,6 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<section>
|
<section>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<h1>{{form.instance}}</h1>
|
|
||||||
<div class="subform">
|
<div class="subform">
|
||||||
{% for field in form.visible_fields %}
|
{% for field in form.visible_fields %}
|
||||||
{% if field.name == 'DELETE' and not form.instance.pk %}
|
{% if field.name == 'DELETE' and not form.instance.pk %}
|
||||||
|
@ -62,13 +51,20 @@
|
||||||
</section>
|
</section>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="edit page">
|
||||||
|
<button><img src="{% static 'cms/ok.svg' %}"></button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extrabody %}
|
{% block extrabody %}
|
||||||
<script type="text/javascript" src="/static/admin/js/urlify.js"></script>
|
<script type="text/javascript" src="/static/admin/js/urlify.js"></script>
|
||||||
<script>
|
<script>
|
||||||
// https://stackoverflow.com/a/25621277
|
document.addEventListener("DOMContentLoaded", function(event) {
|
||||||
|
|
||||||
|
// Auto-resize <textarea's>
|
||||||
|
|
||||||
var tx = document.getElementsByTagName('textarea');
|
var tx = document.getElementsByTagName('textarea');
|
||||||
for (var i = 0; i < tx.length; i++) {
|
for (var i = 0; i < tx.length; i++) {
|
||||||
tx[i].setAttribute('style', 'height:0;overflow-y:hidden;');
|
tx[i].setAttribute('style', 'height:0;overflow-y:hidden;');
|
||||||
|
@ -80,14 +76,12 @@
|
||||||
this.style.height = (this.scrollHeight) + 'px';
|
this.style.height = (this.scrollHeight) + 'px';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// My own implementation of Django's prepopulate.js
|
||||||
|
|
||||||
var slugfield = document.getElementById('id_slug');
|
var slugfield = document.getElementById('id_slug');
|
||||||
var titlefield = document.getElementById('id_title');
|
var titlefield = document.getElementById('id_title');
|
||||||
|
|
||||||
/* My own implementation of Django's prepopulate.js */
|
|
||||||
|
|
||||||
if (slugfield && titlefield) {
|
if (slugfield && titlefield) {
|
||||||
var virgin = slugfield.value === '';
|
var virgin = slugfield.value === '';
|
||||||
|
|
||||||
if (virgin) {
|
if (virgin) {
|
||||||
titlefield.addEventListener('input', function(event) {
|
titlefield.addEventListener('input', function(event) {
|
||||||
if (virgin) {
|
if (virgin) {
|
||||||
|
@ -100,8 +94,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{% if fields_per_type %}
|
// Auto-hide non-relevant fields, based on type field
|
||||||
|
|
||||||
|
{% if fields_per_type %}
|
||||||
var typefield = document.getElementById('id_type');
|
var typefield = document.getElementById('id_type');
|
||||||
fields_per_type = {{fields_per_type|safe}};
|
fields_per_type = {{fields_per_type|safe}};
|
||||||
|
|
||||||
|
@ -148,5 +143,6 @@
|
||||||
show_relevant_fields(typefield.value.toLowerCase());
|
show_relevant_fields(typefield.value.toLowerCase());
|
||||||
}
|
}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
{% extends 'cms/edit.html' %}
|
|
||||||
|
|
||||||
{% block formset %}{% endblock %}
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load i18n %}
|
{% load static %}
|
||||||
{% load cms %}
|
{% load cms %}
|
||||||
|
|
||||||
{% block title %}{{block.super}} - {{page.title}}{% endblock %}
|
{% block title %}{{block.super}} - {{page.title}}{% endblock %}
|
||||||
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
{% if user.is_staff %}
|
{% if user.is_staff %}
|
||||||
<div class="edit page">
|
<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>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from .views import PageView, UpdatePage, CreatePage, UpdateSection, CreateSection
|
from .views import PageView, EditPage, CreatePage
|
||||||
|
|
||||||
app_name = 'cms'
|
app_name = 'cms'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('updatepage/', UpdatePage.as_view(), {'slug': ''}, name='updatehomepage'),
|
path('edit/', EditPage.as_view(), name='editpage'),
|
||||||
path('updatepage/<int:pk>/', UpdatePage.as_view(), name='updatepage'),
|
path('<slug:slug>/edit/', EditPage.as_view(), name='editpage'),
|
||||||
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('', PageView.as_view(), name='page'),
|
path('', PageView.as_view(), name='page'),
|
||||||
path('<slug:slug>/', PageView.as_view(), name='page'),
|
path('<slug:slug>/', PageView.as_view(), name='page'),
|
||||||
]
|
]
|
||||||
|
|
123
cms/views.py
123
cms/views.py
|
@ -2,8 +2,10 @@ import json
|
||||||
import swapper
|
import swapper
|
||||||
|
|
||||||
from django.views import generic
|
from django.views import generic
|
||||||
|
from django.http import Http404
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.views.generic.edit import FormMixin
|
from django.views.generic.edit import FormMixin
|
||||||
|
from django.views.generic.detail import SingleObjectMixin
|
||||||
from django.contrib.admin.utils import NestedObjects
|
from django.contrib.admin.utils import NestedObjects
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
@ -63,24 +65,7 @@ class SectionFormSetView(SectionView):
|
||||||
kwargs['formset'] = self.get_formset()
|
kwargs['formset'] = self.get_formset()
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
class MenuMixin:
|
class PageView(generic.DetailView):
|
||||||
'''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):
|
|
||||||
'''View of a page with heterogeneous (polymorphic) sections'''
|
'''View of a page with heterogeneous (polymorphic) sections'''
|
||||||
model = Page
|
model = Page
|
||||||
template_name = 'cms/page.html'
|
template_name = 'cms/page.html'
|
||||||
|
@ -99,7 +84,13 @@ class PageView(MenuMixin, MemoryMixin, generic.DetailView):
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
'''Initialize sections and render final response'''
|
'''Initialize sections and render final response'''
|
||||||
|
try:
|
||||||
page = self.object = self.get_object()
|
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)
|
context = self.get_context_data(**kwargs)
|
||||||
sections = page.sections.all()
|
sections = page.sections.all()
|
||||||
for section in sections:
|
for section in sections:
|
||||||
|
@ -135,15 +126,46 @@ class PageView(MenuMixin, MemoryMixin, generic.DetailView):
|
||||||
})
|
})
|
||||||
return self.render_to_response(context)
|
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):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**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 = {}
|
fields_per_type = {}
|
||||||
for model, _ in Section.TYPES:
|
for model, _ in Section.TYPES:
|
||||||
ctype = ContentType.objects.get(
|
ctype = ContentType.objects.get(
|
||||||
|
@ -157,38 +179,6 @@ class TypeMixin(MenuMixin):
|
||||||
})
|
})
|
||||||
return context
|
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):
|
def post(self, request, *args, **kwargs):
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
form = self.get_form()
|
form = self.get_form()
|
||||||
|
@ -202,21 +192,10 @@ class UpdatePage(StaffRequiredMixin, TypeMixin, BaseUpdateView):
|
||||||
def form_invalid(self, form, formset):
|
def form_invalid(self, form, formset):
|
||||||
return self.render_to_response(self.get_context_data(form=form, formset=formset))
|
return self.render_to_response(self.get_context_data(form=form, formset=formset))
|
||||||
|
|
||||||
class UpdateSection(StaffRequiredMixin, TypeMixin, BaseUpdateView):
|
class CreatePage(generic.CreateView):
|
||||||
model = Section
|
|
||||||
form_class = SectionForm
|
|
||||||
|
|
||||||
class CreatePage(StaffRequiredMixin, MenuMixin, generic.CreateView):
|
|
||||||
model = Page
|
model = Page
|
||||||
form_class = PageForm
|
form_class = PageForm
|
||||||
template_name = 'cms/new.html'
|
template_name = 'cms/edit.html'
|
||||||
|
|
||||||
class CreateSection(StaffRequiredMixin, TypeMixin, generic.CreateView):
|
def get_form_kwargs(self, *args, **kwargs):
|
||||||
model = Section
|
return {'initial': {'slug': self.kwargs['slug']}}
|
||||||
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'))
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ class TextSection(Section):
|
||||||
|
|
||||||
@register_model('Button')
|
@register_model('Button')
|
||||||
class ButtonSection(Section):
|
class ButtonSection(Section):
|
||||||
fields = ['button_text', 'button_link']
|
fields = ['title', 'href']
|
||||||
class Meta:
|
class Meta:
|
||||||
proxy = True
|
proxy = True
|
||||||
|
|
||||||
|
|
Ładowanie…
Reference in New Issue