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
Jaap Joris Vens 2020-02-19 16:35:23 +01:00
rodzic b783424ff5
commit c0464a5ab6
14 zmienionych plików z 366 dodań i 250 usunięć

Wyświetl plik

@ -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'))

Wyświetl plik

@ -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 {

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -0,0 +1 @@
{% extends 'cms/admin.html' %}

Wyświetl plik

@ -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>

Wyświetl plik

@ -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 %}

Wyświetl plik

@ -1,3 +0,0 @@
{% extends 'cms/edit.html' %}
{% block formset %}{% endblock %}

Wyświetl plik

@ -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 %}

Wyświetl plik

@ -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'),
]

Wyświetl plik

@ -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']}}

Wyświetl plik

@ -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