kopia lustrzana https://github.com/rtts/django-simplecms
Re-introduce formsets
No more clicking individual sections to edit them. From now on, there will only be one edit button! The biggest challenge was getting the javascript closures to understand.readwriteweb
rodzic
ddbd220527
commit
3f900617c8
13
cms/forms.py
13
cms/forms.py
|
@ -2,6 +2,7 @@ import swapper
|
|||
from django import forms
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.mail import EmailMessage
|
||||
from django.forms import inlineformset_factory
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
Page = swapper.load_model('cms', 'Page')
|
||||
|
@ -42,8 +43,13 @@ class SectionForm(forms.ModelForm):
|
|||
# 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]
|
||||
|
||||
def save(self):
|
||||
def delete(self):
|
||||
instance = super().save()
|
||||
instance.delete()
|
||||
|
||||
def save(self, commit=True):
|
||||
section = super().save()
|
||||
|
||||
# Explanation: get the content type of the model that the user
|
||||
|
@ -56,7 +62,8 @@ class SectionForm(forms.ModelForm):
|
|||
model=section.type.lower(),
|
||||
)
|
||||
|
||||
section.save()
|
||||
if commit:
|
||||
section.save()
|
||||
return section
|
||||
|
||||
class Meta:
|
||||
|
@ -69,3 +76,5 @@ class SectionForm(forms.ModelForm):
|
|||
# There is definitely a bug in Django, since the above 'field_classes' gets
|
||||
# ignored entirely. Workaround to force a ChoiceField anyway:
|
||||
type = forms.ChoiceField()
|
||||
|
||||
SectionFormSet = inlineformset_factory(Page, Section, form=SectionForm, extra=1)
|
||||
|
|
|
@ -289,12 +289,12 @@ form.cms {
|
|||
clear: both;
|
||||
box-sizing: border-box;
|
||||
|
||||
&#type, &#number {
|
||||
&.type, &.number {
|
||||
width: 75%;
|
||||
clear: none;
|
||||
float: left;
|
||||
}
|
||||
&#number {
|
||||
&.number {
|
||||
width: 20%;
|
||||
float: right;
|
||||
}
|
||||
|
|
|
@ -206,11 +206,11 @@ form.cms div.formfield {
|
|||
padding: 10px 0;
|
||||
clear: both;
|
||||
box-sizing: border-box; }
|
||||
form.cms div.formfield#type, form.cms div.formfield#number {
|
||||
form.cms div.formfield.type, form.cms div.formfield.number {
|
||||
width: 75%;
|
||||
clear: none;
|
||||
float: left; }
|
||||
form.cms div.formfield#number {
|
||||
form.cms div.formfield.number {
|
||||
width: 20%;
|
||||
float: right; }
|
||||
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post" class="cms">
|
||||
{% csrf_token %}
|
||||
{{form.media}}
|
||||
|
||||
<section>
|
||||
<div class="wrapper">
|
||||
{% if protected %}
|
||||
<div class="title">
|
||||
<h1>You can't do that</h1>
|
||||
</div>
|
||||
|
||||
In order to delete this page, you will first need to delete the following sections:
|
||||
|
||||
<ul>
|
||||
{{protected|unordered_list}}
|
||||
</ul>
|
||||
|
||||
{% else %}
|
||||
<div class="title">
|
||||
<h1>Are you sure?</h1>
|
||||
</div>
|
||||
|
||||
The following objects will be deleted:
|
||||
|
||||
<ul>
|
||||
{{deleted|unordered_list}}
|
||||
</ul>
|
||||
|
||||
<button name="confirm">{% trans 'delete' %}</button>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -5,6 +5,9 @@
|
|||
<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">
|
||||
|
@ -19,6 +22,17 @@
|
|||
{% for field in form %}
|
||||
{% include 'cms/formfield.html' with field=field %}
|
||||
{% endfor %}
|
||||
{% if form.instance.pk %}
|
||||
<div class="formfield">
|
||||
<div class="label">
|
||||
<label for="id_delete">{% trans 'Delete' %}:</label>
|
||||
</div>
|
||||
<div class="input">
|
||||
<input type="checkbox" name="delete" id="id_delete">
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
@ -32,27 +46,19 @@
|
|||
<section>
|
||||
<div class="wrapper">
|
||||
<h1>{{form.instance}}</h1>
|
||||
{% for field in form.visible_fields %}
|
||||
{% include 'cms/formfield.html' with field=field %}
|
||||
{% endfor %}
|
||||
<div class="subform">
|
||||
{% for field in form.visible_fields %}
|
||||
{% if field.name == 'DELETE' and not form.instance.pk %}
|
||||
|
||||
{% else %}
|
||||
{% include 'cms/formfield.html' with field=field counter=forloop.parentloop.counter0 %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endfor %}
|
||||
|
||||
<section>
|
||||
<div class="wrapper">
|
||||
<div class="edit">
|
||||
<a href="{{formset_form_url}}">
|
||||
+ {% trans 'new' %} {{formset_description}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
<div class="edit page">
|
||||
<button>{% trans 'save' %}</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -61,7 +67,6 @@
|
|||
<script>
|
||||
var slugfield = document.getElementById('id_slug');
|
||||
var titlefield = document.getElementById('id_title');
|
||||
var typefield = document.getElementById('id_type');
|
||||
|
||||
/* My own implementation of Django's prepopulate.js */
|
||||
|
||||
|
@ -81,29 +86,52 @@
|
|||
}
|
||||
|
||||
{% if fields_per_type %}
|
||||
/* Only show relevant fields */
|
||||
|
||||
var typefield = document.getElementById('id_type');
|
||||
fields_per_type = {{fields_per_type|safe}};
|
||||
|
||||
if (typefield) {
|
||||
function show_relevant_fields(type) {
|
||||
fields_per_type = {{fields_per_type|safe}};
|
||||
|
||||
for (el of document.querySelectorAll('div.formfield')) {
|
||||
el.style.display = 'none';
|
||||
}
|
||||
|
||||
for (field of fields_per_type[type]) {
|
||||
el = document.getElementById(field);
|
||||
console.log('showing ' + field)
|
||||
el.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
typefield.addEventListener('input', function(event) {
|
||||
console.log(typefield.value);
|
||||
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 %}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div class="formfield{% if field.errors %} error{% endif %}{% if field.field.required %} required{% endif %}" id="{{field.name}}">
|
||||
<div class="formfield{% if field.errors %} error{% endif %}{% if field.field.required %} required{% endif %} {{field.name}}" id="{{field.name}}{{counter}}">
|
||||
<div class="errors">
|
||||
{{field.errors}}
|
||||
</div>
|
||||
|
|
|
@ -21,14 +21,4 @@
|
|||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if user.is_staff %}
|
||||
<section>
|
||||
<div class="wrapper">
|
||||
<div class="edit">
|
||||
<a href="{% url 'cms:createsection' page.pk %}">+ {% trans 'new section' %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
|
32
cms/views.py
32
cms/views.py
|
@ -4,13 +4,14 @@ import swapper
|
|||
from django.views import generic
|
||||
from django.shortcuts import redirect
|
||||
from django.views.generic.edit import FormMixin
|
||||
from django.contrib.admin.utils import NestedObjects
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.auth.mixins import UserPassesTestMixin
|
||||
from django.http import HttpResponseRedirect, HttpResponseBadRequest
|
||||
|
||||
from .decorators import register_view
|
||||
from .forms import PageForm, SectionForm
|
||||
from .forms import PageForm, SectionForm, SectionFormSet
|
||||
|
||||
Page = swapper.load_model('cms', 'Page')
|
||||
Section = swapper.load_model('cms', 'Section')
|
||||
|
@ -138,13 +139,38 @@ class BaseUpdateView(generic.UpdateView):
|
|||
template_name = 'cms/edit.html'
|
||||
|
||||
def form_valid(self, form):
|
||||
form.save()
|
||||
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))
|
||||
else:
|
||||
form.save()
|
||||
return redirect(self.request.session.get('previous_url'))
|
||||
|
||||
class UpdatePage(StaffRequiredMixin, MenuMixin, BaseUpdateView):
|
||||
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)
|
||||
return context
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
form = self.get_form()
|
||||
formset = SectionFormSet(request.POST, request.FILES, instance=self.object)
|
||||
if form.is_valid() and formset.is_valid():
|
||||
formset.save()
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.form_invalid(form, formset)
|
||||
|
||||
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
|
||||
|
|
|
@ -5,7 +5,5 @@
|
|||
<a class="button" href="{{section.button_link}}">{{section.button_text}}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% include 'cms/editlink.html' %}
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{% load i18n %}
|
||||
|
||||
<section class="{{section.type}} color{{section.color}}">
|
||||
<div class="wrapper">
|
||||
<div class="title">
|
||||
|
@ -15,7 +14,5 @@
|
|||
<button class="button" name="section" value="{{section.pk}}">{% trans 'Send' %}</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% include 'cms/editlink.html' %}
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -2,13 +2,10 @@
|
|||
|
||||
<section class="{{section.type}} color{{section.color}}">
|
||||
<div class="wrapper">
|
||||
|
||||
{% if section.image %}
|
||||
<div class="image">
|
||||
<img alt="{{section.title}}" src="{% thumbnail section.image 800x800 %}">
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% include 'cms/editlink.html' %}
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -15,7 +15,5 @@
|
|||
{{section.content|markdown}}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% include 'cms/editlink.html' %}
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
<section class="{{section.type}} color{{section.color}}">
|
||||
<div class="wrapper">
|
||||
|
||||
{% if section.video %}
|
||||
<div class="video">
|
||||
<div class="iframe">
|
||||
|
@ -10,7 +9,5 @@
|
|||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% include 'cms/editlink.html' %}
|
||||
</div>
|
||||
</section>
|
||||
|
|
Ładowanie…
Reference in New Issue