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
Jaap Joris Vens 2020-02-14 17:20:41 +01:00
rodzic ddbd220527
commit 3f900617c8
13 zmienionych plików z 135 dodań i 56 usunięć

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -5,7 +5,5 @@
<a class="button" href="{{section.button_link}}">{{section.button_text}}</a>
</div>
{% endif %}
{% include 'cms/editlink.html' %}
</div>
</section>

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -15,7 +15,5 @@
{{section.content|markdown}}
</div>
{% endif %}
{% include 'cms/editlink.html' %}
</div>
</section>

Wyświetl plik

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