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):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.label_suffix = ''
|
||||
extra = 1 if self.instance.pk else 2
|
||||
|
||||
self.formsets = [forms.inlineformset_factory(
|
||||
parent_model = Page,
|
||||
model = Section,
|
||||
form = SectionForm,
|
||||
formset = BaseSectionFormSet,
|
||||
extra=extra,
|
||||
)(
|
||||
data=self.data if self.is_bound else None,
|
||||
|
@ -33,8 +34,6 @@ class PageForm(forms.ModelForm):
|
|||
super().clean()
|
||||
if not self.formsets[0].is_valid():
|
||||
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):
|
||||
page = super().save()
|
||||
|
@ -55,18 +54,48 @@ class SectionForm(forms.ModelForm):
|
|||
|
||||
def __init__(self, *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
|
||||
# the child model.
|
||||
self.fields['type'].choices = self._meta.model.TYPES
|
||||
self.fields['type'].initial = self._meta.model.TYPES[0][0]
|
||||
|
||||
def delete(self):
|
||||
instance = super().save()
|
||||
instance.delete()
|
||||
section = self.instance
|
||||
self.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=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):
|
||||
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
|
||||
# supplied when filling in this form, and save it's id to the
|
||||
|
@ -80,72 +109,15 @@ class SectionForm(forms.ModelForm):
|
|||
|
||||
if commit:
|
||||
section.save()
|
||||
for formset in self.formsets:
|
||||
formset.save(commit=commit)
|
||||
|
||||
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:
|
||||
model = Section
|
||||
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):
|
||||
sender = forms.EmailField(label=_('Your email address'))
|
||||
spam_protection = forms.CharField(label=_('Your message'), widget=forms.Textarea())
|
||||
|
|
|
@ -22,11 +22,14 @@ class Numbered:
|
|||
def number_with_respect_to(self):
|
||||
return self.__class__.objects.all()
|
||||
|
||||
def get_field_name(self):
|
||||
return self.__class__._meta.ordering[-1].lstrip('-')
|
||||
|
||||
def _renumber(self):
|
||||
'''Renumbers the queryset while preserving the instance's number'''
|
||||
|
||||
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)
|
||||
if this_nr is None:
|
||||
this_nr = len(queryset) + 1
|
||||
|
@ -55,8 +58,9 @@ class Numbered:
|
|||
super().save(*args, **kwargs)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
super().delete(*args, **kwargs)
|
||||
setattr(self, self.get_field_name(), 9999) # hack
|
||||
self._renumber()
|
||||
super().delete(*args, **kwargs)
|
||||
|
||||
class BasePage(Numbered, models.Model):
|
||||
'''Abstract base model for pages'''
|
||||
|
|
|
@ -98,6 +98,7 @@
|
|||
function setEventHandlers() {
|
||||
for (let typefield of document.querySelectorAll('select[name$=-type]')) {
|
||||
let formId = typefield.name.replace(/-type$/, '');
|
||||
console.log(formId + 'huh');
|
||||
let form = document.getElementById(formId);
|
||||
let type = typefield.value.toLowerCase();
|
||||
showRelevantFields(form, type);
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
from django.urls import path
|
||||
from .views import PageView, CreatePage, UpdatePage
|
||||
from .views import PageView, CreatePage, UpdatePage, CreateSection, UpdateSection
|
||||
|
||||
app_name = 'cms'
|
||||
|
||||
urlpatterns = [
|
||||
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/new/', CreateSection.as_view(), name='createsection'),
|
||||
path('<slug:slug>/edit/<int:number>/', UpdateSection.as_view(), name='updatesection'),
|
||||
path('', 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 .decorators import register_view
|
||||
from .forms import PageForm
|
||||
from .forms import PageForm, SectionForm
|
||||
|
||||
Page = swapper.load_model('cms', 'Page')
|
||||
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('.')
|
||||
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):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs.update({
|
||||
'label_suffix': '',
|
||||
'initial': {'slug': self.kwargs['slug']},
|
||||
})
|
||||
if 'slug' in self.kwargs:
|
||||
kwargs.update({'initial': {'slug': self.kwargs['slug']}})
|
||||
return kwargs
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
@ -194,7 +188,77 @@ class EditPage(UserPassesTestMixin, edit.ModelFormMixin, base.TemplateResponseMi
|
|||
|
||||
class CreatePage(EditPage):
|
||||
def get_object(self):
|
||||
pass
|
||||
return Page()
|
||||
|
||||
class UpdatePage(EditPage):
|
||||
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