kopia lustrzana https://github.com/rtts/django-simplecms
Refactor forms.py: quite elegant and reusable Page _and_ Section forms. Both
work with the same edit.html template. Finally you can edit sections individually!main
rodzic
1989615e60
commit
d8d54ea4c4
106
cms/forms.py
106
cms/forms.py
|
@ -10,12 +10,13 @@ Section = swapper.load_model('cms', 'Section')
|
||||||
class PageForm(forms.ModelForm):
|
class PageForm(forms.ModelForm):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
self.label_suffix = ''
|
||||||
extra = 1 if self.instance.pk else 2
|
extra = 1 if self.instance.pk else 2
|
||||||
|
|
||||||
self.formsets = [forms.inlineformset_factory(
|
self.formsets = [forms.inlineformset_factory(
|
||||||
parent_model = Page,
|
parent_model = Page,
|
||||||
model = Section,
|
model = Section,
|
||||||
form = SectionForm,
|
form = SectionForm,
|
||||||
formset = BaseSectionFormSet,
|
|
||||||
extra=extra,
|
extra=extra,
|
||||||
)(
|
)(
|
||||||
data=self.data if self.is_bound else None,
|
data=self.data if self.is_bound else None,
|
||||||
|
@ -33,8 +34,6 @@ class PageForm(forms.ModelForm):
|
||||||
super().clean()
|
super().clean()
|
||||||
if not self.formsets[0].is_valid():
|
if not self.formsets[0].is_valid():
|
||||||
self.add_error(None, _('There’s a problem saving one of the sections'))
|
self.add_error(None, _('There’s a problem saving one of the sections'))
|
||||||
if not self.instance and not self.formsets[0].has_changed():
|
|
||||||
self.add_error(None, _('You can’t save a new page without adding any sections!'))
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
page = super().save()
|
page = super().save()
|
||||||
|
@ -55,18 +54,48 @@ class SectionForm(forms.ModelForm):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
self.label_suffix = ''
|
||||||
|
self.fields['DELETE'] = forms.BooleanField(label=_('Delete'), required=False)
|
||||||
|
|
||||||
# Repopulate the 'choices' attribute of the type field from
|
# Repopulate the 'choices' attribute of the type field from
|
||||||
# the child model.
|
# the child model.
|
||||||
self.fields['type'].choices = self._meta.model.TYPES
|
self.fields['type'].choices = self._meta.model.TYPES
|
||||||
self.fields['type'].initial = self._meta.model.TYPES[0][0]
|
self.fields['type'].initial = self._meta.model.TYPES[0][0]
|
||||||
|
|
||||||
def delete(self):
|
section = self.instance
|
||||||
instance = super().save()
|
self.formsets = []
|
||||||
instance.delete()
|
for field in section._meta.get_fields():
|
||||||
|
if field.one_to_many:
|
||||||
|
extra = 1 if getattr(section, field.name).exists() else 2
|
||||||
|
formset = forms.inlineformset_factory(
|
||||||
|
Section, field.related_model,
|
||||||
|
fields='__all__',
|
||||||
|
extra=extra,
|
||||||
|
)(
|
||||||
|
instance=section,
|
||||||
|
data=self.data if self.is_bound else None,
|
||||||
|
files=self.files if self.is_bound else None,
|
||||||
|
prefix=f'{self.prefix}-{field.name}',
|
||||||
|
form_kwargs={'label_suffix': self.label_suffix},
|
||||||
|
)
|
||||||
|
formset.name = field.name
|
||||||
|
self.formsets.append(formset)
|
||||||
|
|
||||||
|
def is_valid(self):
|
||||||
|
result = super().is_valid()
|
||||||
|
if self.is_bound:
|
||||||
|
for formset in self.formsets:
|
||||||
|
result = result and formset.is_valid()
|
||||||
|
return result
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
section = super().save()
|
section = super().save(commit=commit)
|
||||||
|
|
||||||
|
if self.cleaned_data['DELETE']:
|
||||||
|
section.delete()
|
||||||
|
if section.page.slug and not section.page.sections.exists():
|
||||||
|
section.page.delete()
|
||||||
|
return
|
||||||
|
|
||||||
# Explanation: get the content type of the model that the user
|
# Explanation: get the content type of the model that the user
|
||||||
# supplied when filling in this form, and save it's id to the
|
# supplied when filling in this form, and save it's id to the
|
||||||
|
@ -80,72 +109,15 @@ class SectionForm(forms.ModelForm):
|
||||||
|
|
||||||
if commit:
|
if commit:
|
||||||
section.save()
|
section.save()
|
||||||
|
for formset in self.formsets:
|
||||||
|
formset.save(commit=commit)
|
||||||
|
|
||||||
return section
|
return section
|
||||||
|
|
||||||
core_field_names = ['title', 'type', 'number', 'content', 'image', 'video', 'href']
|
|
||||||
|
|
||||||
def core_fields(self):
|
|
||||||
return [field for field in self.visible_fields() if field.name in self.core_field_names]
|
|
||||||
|
|
||||||
def extra_fields(self):
|
|
||||||
return [field for field in self.visible_fields() if field.name not in self.core_field_names]
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Section
|
model = Section
|
||||||
exclude = ['page']
|
exclude = ['page']
|
||||||
|
|
||||||
class BaseSectionFormSet(forms.BaseInlineFormSet):
|
|
||||||
'''If a swappable Section model defines one-to-many fields, (i.e. has
|
|
||||||
foreign keys pointing to it) formsets will be generated for the
|
|
||||||
related models and stored in the form.formsets array.
|
|
||||||
|
|
||||||
Based on this logic for nested formsets:
|
|
||||||
https://www.yergler.net/2013/09/03/nested-formsets-redux/
|
|
||||||
|
|
||||||
Typical usecases could be:
|
|
||||||
- an images section that displays multiple images
|
|
||||||
- a column section that displays separate colums
|
|
||||||
- a calendar section that displays calendar events
|
|
||||||
- etc...
|
|
||||||
|
|
||||||
'''
|
|
||||||
def add_fields(self, form, index):
|
|
||||||
super().add_fields(form, index)
|
|
||||||
section = form.instance
|
|
||||||
form.formsets = []
|
|
||||||
for field in section._meta.get_fields():
|
|
||||||
if field.one_to_many:
|
|
||||||
extra = 1 if getattr(section, field.name).exists() else 2
|
|
||||||
|
|
||||||
formset = forms.inlineformset_factory(
|
|
||||||
Section, field.related_model,
|
|
||||||
fields='__all__',
|
|
||||||
extra=extra,
|
|
||||||
)(
|
|
||||||
instance=section,
|
|
||||||
data=form.data if self.is_bound else None,
|
|
||||||
files=form.files if self.is_bound else None,
|
|
||||||
prefix=f'{form.prefix}-{field.name}',
|
|
||||||
form_kwargs=self.form_kwargs,
|
|
||||||
)
|
|
||||||
formset.name = field.name
|
|
||||||
form.formsets.append(formset)
|
|
||||||
|
|
||||||
def is_valid(self):
|
|
||||||
result = super().is_valid()
|
|
||||||
if self.is_bound:
|
|
||||||
for form in self.forms:
|
|
||||||
for formset in form.formsets:
|
|
||||||
result = result and formset.is_valid()
|
|
||||||
return result
|
|
||||||
|
|
||||||
def save(self, commit=True):
|
|
||||||
result = super().save(commit=commit)
|
|
||||||
for form in self:
|
|
||||||
for formset in form.formsets:
|
|
||||||
formset.save(commit=commit)
|
|
||||||
return result
|
|
||||||
|
|
||||||
class ContactForm(forms.Form):
|
class ContactForm(forms.Form):
|
||||||
sender = forms.EmailField(label=_('Your email address'))
|
sender = forms.EmailField(label=_('Your email address'))
|
||||||
spam_protection = forms.CharField(label=_('Your message'), widget=forms.Textarea())
|
spam_protection = forms.CharField(label=_('Your message'), widget=forms.Textarea())
|
||||||
|
|
|
@ -22,11 +22,14 @@ class Numbered:
|
||||||
def number_with_respect_to(self):
|
def number_with_respect_to(self):
|
||||||
return self.__class__.objects.all()
|
return self.__class__.objects.all()
|
||||||
|
|
||||||
|
def get_field_name(self):
|
||||||
|
return self.__class__._meta.ordering[-1].lstrip('-')
|
||||||
|
|
||||||
def _renumber(self):
|
def _renumber(self):
|
||||||
'''Renumbers the queryset while preserving the instance's number'''
|
'''Renumbers the queryset while preserving the instance's number'''
|
||||||
|
|
||||||
queryset = self.number_with_respect_to()
|
queryset = self.number_with_respect_to()
|
||||||
field_name = self.__class__._meta.ordering[-1].lstrip('-')
|
field_name = self.get_field_name()
|
||||||
this_nr = getattr(self, field_name)
|
this_nr = getattr(self, field_name)
|
||||||
if this_nr is None:
|
if this_nr is None:
|
||||||
this_nr = len(queryset) + 1
|
this_nr = len(queryset) + 1
|
||||||
|
@ -55,8 +58,9 @@ class Numbered:
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
def delete(self, *args, **kwargs):
|
def delete(self, *args, **kwargs):
|
||||||
super().delete(*args, **kwargs)
|
setattr(self, self.get_field_name(), 9999) # hack
|
||||||
self._renumber()
|
self._renumber()
|
||||||
|
super().delete(*args, **kwargs)
|
||||||
|
|
||||||
class BasePage(Numbered, models.Model):
|
class BasePage(Numbered, models.Model):
|
||||||
'''Abstract base model for pages'''
|
'''Abstract base model for pages'''
|
||||||
|
|
|
@ -98,6 +98,7 @@
|
||||||
function setEventHandlers() {
|
function setEventHandlers() {
|
||||||
for (let typefield of document.querySelectorAll('select[name$=-type]')) {
|
for (let typefield of document.querySelectorAll('select[name$=-type]')) {
|
||||||
let formId = typefield.name.replace(/-type$/, '');
|
let formId = typefield.name.replace(/-type$/, '');
|
||||||
|
console.log(formId + 'huh');
|
||||||
let form = document.getElementById(formId);
|
let form = document.getElementById(formId);
|
||||||
let type = typefield.value.toLowerCase();
|
let type = typefield.value.toLowerCase();
|
||||||
showRelevantFields(form, type);
|
showRelevantFields(form, type);
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from .views import PageView, CreatePage, UpdatePage
|
from .views import PageView, CreatePage, UpdatePage, CreateSection, UpdateSection
|
||||||
|
|
||||||
app_name = 'cms'
|
app_name = 'cms'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('new/', CreatePage.as_view(), name='createpage'),
|
path('new/', CreatePage.as_view(), name='createpage'),
|
||||||
path('edit/', UpdatePage.as_view(), name='updatepage'),
|
path('edit/', UpdatePage.as_view(), kwargs={'slug': ''}, name='updatepage'),
|
||||||
|
path('edit/<int:number>/', UpdateSection.as_view(), kwargs={'slug': ''}, name='updatesection'),
|
||||||
path('<slug:slug>/edit/', UpdatePage.as_view(), name='updatepage'),
|
path('<slug:slug>/edit/', UpdatePage.as_view(), name='updatepage'),
|
||||||
|
path('<slug:slug>/edit/new/', CreateSection.as_view(), name='createsection'),
|
||||||
|
path('<slug:slug>/edit/<int:number>/', UpdateSection.as_view(), name='updatesection'),
|
||||||
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'),
|
||||||
]
|
]
|
||||||
|
|
84
cms/views.py
84
cms/views.py
|
@ -10,7 +10,7 @@ from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.auth.mixins import UserPassesTestMixin
|
from django.contrib.auth.mixins import UserPassesTestMixin
|
||||||
|
|
||||||
from .decorators import register_view
|
from .decorators import register_view
|
||||||
from .forms import PageForm
|
from .forms import PageForm, SectionForm
|
||||||
|
|
||||||
Page = swapper.load_model('cms', 'Page')
|
Page = swapper.load_model('cms', 'Page')
|
||||||
Section = swapper.load_model('cms', 'Section')
|
Section = swapper.load_model('cms', 'Section')
|
||||||
|
@ -143,16 +143,10 @@ class EditPage(UserPassesTestMixin, edit.ModelFormMixin, base.TemplateResponseMi
|
||||||
app, model = swapper.get_model_name('cms', 'page').lower().split('.')
|
app, model = swapper.get_model_name('cms', 'page').lower().split('.')
|
||||||
return self.request.user.has_perm('f{app}_{model}_change')
|
return self.request.user.has_perm('f{app}_{model}_change')
|
||||||
|
|
||||||
def setup(self, *args, slug='', **kwargs):
|
|
||||||
'''Supply a default argument for slug'''
|
|
||||||
super().setup(*args, slug=slug, **kwargs)
|
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
kwargs = super().get_form_kwargs()
|
kwargs = super().get_form_kwargs()
|
||||||
kwargs.update({
|
if 'slug' in self.kwargs:
|
||||||
'label_suffix': '',
|
kwargs.update({'initial': {'slug': self.kwargs['slug']}})
|
||||||
'initial': {'slug': self.kwargs['slug']},
|
|
||||||
})
|
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
@ -194,7 +188,77 @@ class EditPage(UserPassesTestMixin, edit.ModelFormMixin, base.TemplateResponseMi
|
||||||
|
|
||||||
class CreatePage(EditPage):
|
class CreatePage(EditPage):
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
pass
|
return Page()
|
||||||
|
|
||||||
class UpdatePage(EditPage):
|
class UpdatePage(EditPage):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class EditSection(UserPassesTestMixin, edit.ModelFormMixin, base.TemplateResponseMixin, base.View):
|
||||||
|
model = Section
|
||||||
|
form_class = SectionForm
|
||||||
|
template_name = 'cms/edit.html'
|
||||||
|
|
||||||
|
def test_func(self):
|
||||||
|
app, model = swapper.get_model_name('cms', 'section').lower().split('.')
|
||||||
|
return self.request.user.has_perm('f{app}_{model}_change')
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
kwargs = super().get_form_kwargs()
|
||||||
|
kwargs.update({
|
||||||
|
'prefix': 'section',
|
||||||
|
})
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
fields_per_type = {}
|
||||||
|
for model, _ in Section.TYPES:
|
||||||
|
ctype = ContentType.objects.get(
|
||||||
|
app_label = Section._meta.app_label,
|
||||||
|
model = model.lower(),
|
||||||
|
)
|
||||||
|
fields_per_type[ctype.model] = ['type', 'number'] + ctype.model_class().fields
|
||||||
|
|
||||||
|
context.update({
|
||||||
|
'fields_per_type': json.dumps(fields_per_type),
|
||||||
|
})
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_object(self, queryset=None):
|
||||||
|
try:
|
||||||
|
self.page = Page.objects.get(slug=self.kwargs['slug'])
|
||||||
|
except Page.DoesNotExist:
|
||||||
|
raise Http404()
|
||||||
|
return self.get_section()
|
||||||
|
|
||||||
|
def get_section(self):
|
||||||
|
try:
|
||||||
|
section = self.page.sections.get(number=self.kwargs['number'])
|
||||||
|
except Section.DoesNotExist:
|
||||||
|
raise Http404()
|
||||||
|
return section
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
self.object = self.get_object()
|
||||||
|
return self.render_to_response(self.get_context_data())
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
self.object = self.get_object()
|
||||||
|
form = self.get_form()
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
section = form.save()
|
||||||
|
if section:
|
||||||
|
return HttpResponseRedirect(section.page.get_absolute_url())
|
||||||
|
elif self.page.sections.exists():
|
||||||
|
return HttpResponseRedirect(self.page.get_absolute_url())
|
||||||
|
else:
|
||||||
|
return HttpResponseRedirect('/')
|
||||||
|
return self.render_to_response(self.get_context_data(form=form))
|
||||||
|
|
||||||
|
class CreateSection(EditSection):
|
||||||
|
def get_section(self):
|
||||||
|
return Section(page=self.page)
|
||||||
|
|
||||||
|
class UpdateSection(EditSection):
|
||||||
|
pass
|
||||||
|
|
Ładowanie…
Reference in New Issue