combo/combo/manager/views.py

1011 lines
34 KiB
Python

# combo - content management system
# Copyright (C) 2014 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import datetime
import hashlib
import json
import tarfile
from operator import attrgetter
from django.conf import settings
from django.contrib import messages
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.db import transaction
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse, reverse_lazy
from django.utils.encoding import force_bytes, force_text
from django.utils.formats import date_format
from django.utils.six import BytesIO
from django.utils.timezone import localtime
from django.utils.translation import ugettext_lazy as _
from django.views.decorators.csrf import requires_csrf_token
from django.views.generic import (
CreateView,
DeleteView,
DetailView,
FormView,
ListView,
RedirectView,
UpdateView,
View,
)
from combo import plugins
from combo.data.library import get_cell_class
from combo.data.models import CellBase, LinkListCell, Page, PageSnapshot, ParentContentCell, SiteSettings
from combo.data.utils import (
ImportSiteError,
MissingSubSlug,
export_site,
export_site_tar,
get_page_from_url_parts,
import_site,
import_site_tar,
)
from combo.urls_utils import staff_required
from .forms import (
CellVisibilityForm,
PageAddForm,
PageDuplicateForm,
PageEditDescriptionForm,
PageEditIncludeInNavigationForm,
PageEditPictureForm,
PageEditRedirectionForm,
PageEditRolesForm,
PageEditSlugForm,
PageEditTitleForm,
PageExportForm,
PageRestrictedAddForm,
PageSelectTemplateForm,
PageVisibilityForm,
SiteExportForm,
SiteImportForm,
SiteSettingsForm,
)
def can_add_page(user):
if user.is_staff:
return True
group_ids = [x.id for x in user.groups.all()]
return bool(Page.objects.filter(subpages_edit_role_id__in=group_ids).exists())
class HomepageView(ListView):
model = Page
template_name = 'combo/manager_home.html'
def get_context_data(self, **kwargs):
self.object_list = Page.get_as_reordered_flat_hierarchy(
self.object_list, follow_user_perms=self.request.user
)
context = super().get_context_data(**kwargs)
context['extra_actions'] = plugins.get_extra_manager_actions()
context['collapse_pages'] = settings.COMBO_MANAGE_HOME_COLLAPSE_PAGES
context['can_add_page'] = can_add_page(self.request.user)
return context
homepage = HomepageView.as_view()
class SiteExportView(FormView):
form_class = SiteExportForm
template_name = 'combo/site_export.html'
def form_valid(self, form):
today = datetime.date.today()
filename = 'site_export_%s' % today.strftime('%Y%m%d')
asset_files = form.cleaned_data.pop('asset_files')
if asset_files:
fd = BytesIO()
export_site_tar(fd, export_kwargs=form.cleaned_data)
response = HttpResponse(content=fd.getvalue(), content_type='application/x-tar')
response['Content-Disposition'] = 'attachment; filename="%s.tar"' % filename
else:
response = HttpResponse(content_type='application/json')
response['Content-Disposition'] = 'attachment; filename="%s.json"' % filename
json.dump(export_site(**form.cleaned_data), response, indent=2)
return response
site_export = SiteExportView.as_view()
class SiteImportView(FormView):
form_class = SiteImportForm
template_name = 'combo/site_import.html'
success_url = reverse_lazy('combo-manager-homepage')
def form_valid(self, form):
fd = self.request.FILES['site_file'].file
try:
with tarfile.open(mode='r', fileobj=fd):
pass
except tarfile.TarError:
try:
fd.seek(0)
json_site = json.loads(force_text(fd.read()))
except ValueError:
form.add_error('site_file', _('File is not in the expected TAR or JSON format.'))
return self.form_invalid(form)
else:
format = 'json'
else:
format = 'tar'
fd.seek(0)
try:
if format == 'json':
pages = import_site(json_site, request=self.request)
else:
pages = import_site_tar(fd, request=self.request)
except ImportSiteError as e:
form.add_error('site_file', force_text(e))
return self.form_invalid(form)
else:
for page in pages:
PageSnapshot.take(page, request=self.request, comment=_('imported'))
return super().form_valid(form)
site_import = SiteImportView.as_view()
def invalid_cell_report(request):
invalid_cells = CellBase.get_cells(
page__snapshot__isnull=True, validity_info__invalid_since__isnull=False, load_contenttypes=True
)
# manual prefetch of cell pages (for ordering and placeholders)
all_pages_by_pk = {p.pk: p for p in Page.objects.filter(snapshot__isnull=True)}
for cell in invalid_cells:
cell.page = all_pages_by_pk.get(cell.page_id)
# exclude some cells on placeholder naming
invalid_cells = [c for c in invalid_cells if c.placeholder and not c.placeholder.startswith('_')]
# exclude cells with an inactive placeholder
invalid_cells = [c for c in invalid_cells if c.is_placeholder_active()]
# sort cells
invalid_cells.sort(key=attrgetter('page.order', 'page.pk', 'order'))
context = {
'object_list': invalid_cells,
}
return render(request, 'combo/invalid_cell_report.html', context)
class PageAddView(CreateView):
model = Page
template_name = 'combo/page_add.html'
def get_form_class(self):
if self.request.user.is_staff:
return PageAddForm
elif can_add_page(self.request.user):
return PageRestrictedAddForm
raise PermissionDenied()
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['request'] = self.request
return kwargs
def get_initial(self):
initial = super().get_initial()
if not Page.objects.exists(): # first page
initial['title'] = _('Home')
return initial
def get_success_url(self):
return reverse('combo-manager-page-view', kwargs={'pk': self.object.id})
page_add = PageAddView.as_view()
class ManagedPageMixin:
def dispatch(self, request, *args, **kwargs):
self.page = get_object_or_404(Page, id=kwargs.get('page_pk') or kwargs.get('pk'))
if not self.page.is_editable(request.user):
raise PermissionDenied()
return super().dispatch(request, *args, **kwargs)
class PageAddChildView(ManagedPageMixin, PageAddView):
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['parent'] = get_object_or_404(Page, pk=self.kwargs['pk'])
return kwargs
def get_initial(self):
# it can not be the first page, so don't try to initialize the title
return super(CreateView, self).get_initial()
page_add_child = PageAddChildView.as_view()
class PageEditView(ManagedPageMixin, UpdateView):
model = Page
template_name = 'combo/page_add.html'
comment = None
def form_valid(self, form):
result = super().form_valid(form)
PageSnapshot.take(self.get_object(), request=self.request, comment=self.comment)
return result
def get_success_url(self):
return reverse('combo-manager-page-view', kwargs={'pk': self.object.id})
class PageSelectTemplateView(PageEditView):
form_class = PageSelectTemplateForm
@property
def comment(self):
return (
_('switched template to %s') % settings.COMBO_PUBLIC_TEMPLATES[self.object.template_name]['name']
)
def form_valid(self, form):
old_template_name = self.get_object().template_name
new_template_name = self.object.template_name
response = super().form_valid(form)
if old_template_name != new_template_name:
old_placeholder_keys = [x.key for x in self.get_object().get_placeholders(request=self.request)]
page_cells = CellBase.get_cells(page_id=self.object.id)
for placeholder in self.object.get_placeholders(
request=self.request, template_name=new_template_name
):
if not placeholder.acquired:
continue
if placeholder.key in old_placeholder_keys:
continue
if [x for x in page_cells if x.placeholder == placeholder.key]:
# skip placeholders that already have cells
continue
ParentContentCell(page=self.object, placeholder=placeholder.key, order=0).save()
return response
page_select_template = PageSelectTemplateView.as_view()
class PageEditRedirectionView(PageEditView):
form_class = PageEditRedirectionForm
comment = _('changed redirection')
page_edit_redirection = PageEditRedirectionView.as_view()
class PageEditRolesView(PageEditView):
page_title = _('Edit roles management')
form_class = PageEditRolesForm
comment = _('changed edit roles')
page_edit_roles = PageEditRolesView.as_view()
class PageEditIncludeInNavigationView(PageEditView):
form_class = PageEditIncludeInNavigationForm
comment = _('changed navigation inclusion')
page_edit_include_in_navigation = PageEditIncludeInNavigationView.as_view()
class PageEditSlugView(PageEditView):
form_class = PageEditSlugForm
comment = _('changed slug')
page_edit_slug = PageEditSlugView.as_view()
class PageEditDescriptionView(PageEditView):
form_class = PageEditDescriptionForm
comment = _('changed description')
page_edit_description = PageEditDescriptionView.as_view()
class PageEditTitleView(PageEditView):
form_class = PageEditTitleForm
comment = _('changed title')
page_edit_title = PageEditTitleView.as_view()
class PageVisibilityView(PageEditView):
form_class = PageVisibilityForm
comment = _('changed visibility')
page_visibility = PageVisibilityView.as_view()
class PageEditPictureView(PageEditView):
form_class = PageEditPictureForm
comment = _('changed picture')
page_edit_picture = PageEditPictureView.as_view()
class PageRemovePictureView(ManagedPageMixin, DetailView):
model = Page
def get(self, *args, **kwargs):
page = self.get_object()
page.picture = None
page.save()
PageSnapshot.take(page, request=self.request, comment=_('removed picture'))
return HttpResponseRedirect(reverse('combo-manager-page-view', kwargs={'pk': page.id}))
page_remove_picture = PageRemovePictureView.as_view()
class PageView(ManagedPageMixin, DetailView):
model = Page
template_name = 'combo/page_view.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
cell_type_groups = {}
for cell_type in CellBase.get_all_cell_types():
if not cell_type['group'] in cell_type_groups:
cell_type_groups[cell_type['group']] = []
cell_type_groups[cell_type['group']].append(cell_type)
for cell_group in cell_type_groups.values():
cell_group.sort(key=lambda x: (x.get('order'), x.get('name')))
if 'data' in cell_type_groups:
cell_type_groups[''] = cell_type_groups.get('data')
del cell_type_groups['data']
context['cell_type_groups'] = list(cell_type_groups.items())
context['cell_type_groups'].sort(key=lambda x: x[0])
context['page_has_subpages'] = self.object.get_children().exists()
cells = CellBase.get_cells(page=self.object, prefetch_validity_info=True)
self.object.prefetched_cells = cells
placeholders = []
optional_placeholders = []
for placeholder in self.object.get_placeholders(request=self.request, traverse_cells=True):
placeholder_dict = {
'key': placeholder.key,
'name': placeholder.get_name(),
}
placeholder_dict['cells'] = [x for x in cells if x.placeholder == placeholder.key]
# not optional placeholder
if not placeholder.optional:
placeholders.append(placeholder_dict)
continue
# optional placeholder, but requested for edition
if self.request.GET.get('include-section') == placeholder.key:
placeholders.append(placeholder_dict)
continue
# empty optional placeholder: in optional sections
if not placeholder_dict['cells']:
placeholder_dict['is_empty'] = True
optional_placeholders.append(placeholder_dict)
continue
# optional placeholder with at least 2 cells: in page edition
if len(placeholder_dict['cells']) > 1:
placeholders.append(placeholder_dict)
continue
# optional placeholder with only one ParentContentCell: in optional sections
if isinstance(placeholder_dict['cells'][0], ParentContentCell):
placeholder_dict['is_empty'] = False
optional_placeholders.append(placeholder_dict)
continue
# other cases: in page edition
placeholders.append(placeholder_dict)
context['placeholders'] = placeholders
context['optional_placeholders'] = optional_placeholders
context.update(
{
'previous_page': self.object.get_previous_page(
check_visibility=False, follow_user_perms=self.request.user
),
'next_page': self.object.get_next_page(
check_visibility=False, follow_user_perms=self.request.user
),
}
)
return context
page_view = requires_csrf_token(PageView.as_view())
class PageDeleteView(ManagedPageMixin, DeleteView):
model = Page
template_name = 'combo/delete_page.html'
def post(self, request, *args, **kwargs):
deleted_page = self.get_object()
if request.POST.get('choice') == 'delete-one':
new_parent = deleted_page.parent_id
Page.objects.filter(parent=deleted_page).update(parent=new_parent)
PageSnapshot.take(deleted_page, request=self.request, deletion=True)
return self.delete(request, *args, **kwargs)
def get_context_data(self, **kwargs):
subpages = Page.objects.filter(parent=self.get_object().id)
context = super().get_context_data()
context['display_choice'] = bool(subpages)
return context
def get_success_url(self):
return reverse('combo-manager-homepage')
page_delete = PageDeleteView.as_view()
class PageExportView(ManagedPageMixin, FormView):
form_class = PageExportForm
template_name = 'combo/page_export.html'
def get_object(self):
return get_object_or_404(Page, pk=self.kwargs['pk'])
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['page'] = self.get_object()
return context
def export(self, with_children=False):
instance = self.get_object()
response = HttpResponse(content_type='application/json')
today = datetime.date.today()
if with_children:
pages = instance.get_descendants_and_me()
ordered_pages = Page.get_as_reordered_flat_hierarchy(pages, root_page=instance)
filename_part = f'{instance.slug}_and_subpages'
else:
ordered_pages = [instance]
filename_part = instance.slug
response['Content-Disposition'] = 'attachment; filename="export_page_{}_{}.json"'.format(
filename_part, today.strftime('%Y%m%d')
)
json.dump({'pages': [p.get_serialized_page() for p in ordered_pages]}, response, indent=2)
return response
def get(self, request, *args, **kwargs):
subpages = self.get_object().get_children()
if not subpages.exists():
return self.export()
return super().get(request, *args, **kwargs)
def form_valid(self, form):
return self.export(with_children=form.cleaned_data['with_subpages'])
page_export = PageExportView.as_view()
class PageDuplicateView(ManagedPageMixin, FormView):
form_class = PageDuplicateForm
template_name = 'combo/page_duplicate.html'
def get_success_url(self):
return reverse('combo-manager-page-view', kwargs={'pk': self.new_page.pk})
def form_valid(self, form):
page = Page.objects.get(pk=self.kwargs['pk'])
self.new_page = page.duplicate(title=form.cleaned_data['title'])
if not page.exclude_from_navigation:
messages.info(
self.request,
_('Page %s has been duplicated, it has been marked as excluded from navigation.')
% page.title,
)
else:
messages.info(self.request, _('Page %s has been duplicated.') % page.title)
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['page'] = Page.objects.get(id=self.kwargs['pk'])
return context
page_duplicate = PageDuplicateView.as_view()
class PageHistoryView(ManagedPageMixin, ListView):
model = PageSnapshot
template_name = 'combo/page_history.html'
paginate_by = 20
def get_queryset(self):
self.page = get_object_or_404(Page, pk=self.kwargs['pk'])
return self.page.pagesnapshot_set.all()
page_history = PageHistoryView.as_view()
class SnapshotRestoreView(ManagedPageMixin, DetailView):
http_method_names = ['get', 'post']
model = PageSnapshot
template_name = 'combo/snapshot_restore.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['page'] = Page.objects.get(id=self.kwargs['page_pk'])
return context
def post(self, request, *args, **kwargs):
snapshot = self.get_object()
datetime_str = date_format(localtime(snapshot.timestamp), format='DATETIME_FORMAT')
page = self.get_object().restore()
PageSnapshot.take(page, request=self.request, comment=_('restored snapshot from %s') % datetime_str)
messages.info(self.request, _('Snapshot from %s has been restored.') % datetime_str)
return HttpResponseRedirect(reverse('combo-manager-page-view', kwargs={'pk': self.kwargs['page_pk']}))
snapshot_restore = SnapshotRestoreView.as_view()
class PageRedirectToEditView(RedirectView):
permanent = False
def get_redirect_url(self, page_path):
try:
page = get_page_from_url_parts(page_path.split('/'))
except MissingSubSlug as e:
page = e.page
return reverse('combo-manager-page-view', kwargs={'pk': page.pk})
page_redirect_to_edit = PageRedirectToEditView.as_view()
class PageAddCellView(ManagedPageMixin, RedirectView):
permanent = False
def get_redirect_url(self, page_pk, cell_type, variant, ph_key):
cell_class = get_cell_class(cell_type)
cell = cell_class(page_id=page_pk, placeholder=ph_key)
cell.set_variant(variant)
orders = [x.order for x in CellBase.get_cells(page_id=page_pk)]
if orders:
cell.order = max(orders) + 1
else:
cell.order = 1
cell.save()
PageSnapshot.take(cell.page, request=self.request, comment=_('added cell "%s"') % cell)
return reverse('combo-manager-page-view', kwargs={'pk': page_pk}) + '#cell-' + cell.get_reference()
page_add_cell = PageAddCellView.as_view()
class PageEditCellView(ManagedPageMixin, UpdateView):
def get_template_names(self):
return [self.template_name or self.object.manager_form_template]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update(self.get_object().get_extra_manager_context())
context['cell'] = self.get_object()
context['page'] = context['cell'].page
return context
def get_object(self, queryset=None):
page_pk = self.kwargs.get('page_pk')
cell_reference = self.kwargs.get('cell_reference')
try:
return CellBase.get_cell(cell_reference, page_id=page_pk)
except ObjectDoesNotExist:
raise Http404()
def get_prefix(self):
return 'c%s' % self.kwargs.get('cell_reference')
def get_form_class(self):
return self.object.get_default_form_class()
def get_success_url(self):
return (
reverse('combo-manager-page-view', kwargs={'pk': self.kwargs.get('page_pk')})
+ '#cell-'
+ self.object.get_reference()
)
def form_valid(self, form):
if self.request.is_ajax():
self.object = form.save()
form = self.get_form_class()(instance=self.object, prefix=self.get_prefix())
response = self.form_invalid(form) # avoid redirection
else:
response = super().form_valid(form)
PageSnapshot.take(
self.object.page, request=self.request, comment=_('changed cell "%s"') % self.object
)
return response
page_edit_cell = PageEditCellView.as_view()
class PageDeleteCellView(ManagedPageMixin, DeleteView):
template_name = 'combo/generic_confirm_delete.html'
def get_object(self, queryset=None):
page_pk = self.kwargs.get('page_pk')
cell_reference = self.kwargs.get('cell_reference')
try:
return CellBase.get_cell(cell_reference, page_id=page_pk)
except ObjectDoesNotExist:
raise Http404()
def delete(self, request, *args, **kwargs):
cell = self.get_object()
comment = _('removed cell "%s"') % cell
page = cell.page
response = super().delete(request, *args, **kwargs)
PageSnapshot.take(page, request=self.request, comment=comment)
return response
def get_success_url(self):
return reverse('combo-manager-page-view', kwargs={'pk': self.kwargs.get('page_pk')})
page_delete_cell = PageDeleteCellView.as_view()
class PageDuplicateCellView(ManagedPageMixin, RedirectView):
permanent = False
def get_redirect_url(self, page_pk, cell_reference):
try:
cell = CellBase.get_cell(cell_reference, page_id=page_pk)
except ObjectDoesNotExist:
raise Http404()
cell.duplicate(reset_slug=True, set_order=True)
PageSnapshot.take(cell.page, request=self.request, comment=_('duplicated cell "%s"') % cell)
messages.info(self.request, _('Cell %s has been duplicated.') % cell)
return reverse('combo-manager-page-view', kwargs={'pk': page_pk}) + '#cell-' + cell.get_reference()
page_duplicate_cell = PageDuplicateCellView.as_view()
class PageCellVisibilityView(PageEditCellView):
template_name = 'combo/cell_visibility.html'
def get_form_class(self):
return CellVisibilityForm
page_cell_visibility = PageCellVisibilityView.as_view()
class PageCellOptionsView(PageEditCellView):
template_name = 'combo/cell_options.html'
def get_form_class(self):
return self.object.get_options_form_class()
page_cell_options = PageCellOptionsView.as_view()
class PageCellOrder(ManagedPageMixin, View):
def get(self, *args, **kwargs):
request = self.request
page_pk = self.kwargs['page_pk']
has_changes = False
for cell in CellBase.get_cells(page_id=page_pk):
old_order = cell.order
old_placeholder = cell.placeholder
key_suffix = cell.get_reference()
try:
new_order = int(request.GET.get('pos_' + key_suffix))
except TypeError:
# the cell is not present in the query string, most probably
# because it's in a different placeholder
continue
new_placeholder = request.GET.get('ph_' + key_suffix)
if new_order != old_order or new_placeholder != old_placeholder:
cell.order = new_order
cell.placeholder = new_placeholder
has_changes = True
cell.save(update_fields=['order', 'placeholder'])
if has_changes:
page = Page.objects.get(id=page_pk)
PageSnapshot.take(page, request=request, comment=_('reordered cells'))
return HttpResponse(status=204)
cell_order = PageCellOrder.as_view()
@staff_required
def page_order(request):
new_order = [int(x) for x in request.GET['new-order'].split(',')]
moved_page = Page.objects.get(id=request.GET['moved-page-id'])
if request.GET['moved-page-new-parent']:
# recreate full hierarchy to avoid cycles
current_hierarchy = Page.objects.get(id=request.GET['moved-page-new-parent']).get_parents_and_self()
new_hierarchy = [x for x in current_hierarchy if not x.id == moved_page.id] + [moved_page]
for i, page in enumerate(new_hierarchy):
old_parent_id = page.parent_id
if i == 0:
page.parent_id = None
else:
page.parent_id = new_hierarchy[i - 1].id
if old_parent_id != page.parent_id:
page.save()
else:
moved_page.parent_id = None
moved_page.save()
slug_conflict = False
try:
with transaction.atomic():
for page in Page.objects.filter(parent_id=moved_page.parent_id):
page.order = new_order.index(page.id) + 1
page.save()
if moved_page.id != page.id and moved_page.slug == page.slug:
slug_conflict = True
except ValueError:
# missing child page in new_order, fail silently
return redirect(reverse('combo-manager-homepage'))
if slug_conflict:
# slug conflict after a page got moved, reload and rename
moved_page = Page.objects.get(id=request.GET['moved-page-id'])
moved_page.slug = moved_page.slug + '-' + hashlib.md5(force_bytes(moved_page.id)).hexdigest()[:4]
moved_page.save()
return redirect(reverse('combo-manager-homepage'))
def page_get_additional_label(request, page_pk, cell_reference):
cell = CellBase.get_cell(cell_reference, page_id=page_pk)
response = HttpResponse(content_type='application/json')
json.dump(
{
'label': force_text(cell.get_additional_label()) or '',
'invalid_reason': force_text(cell.get_invalid_reason() or ''),
},
response,
)
return response
def menu_json(request):
if settings.TEMPLATE_VARS.get('site_title'):
label = _('Editing "%(site_title)s"') % settings.TEMPLATE_VARS
else:
label = _('Content Management')
slug = 'portal'
if getattr(settings, 'KNOWN_SERVICES') and 'combo' in settings.KNOWN_SERVICES:
# switch to custom slug if the site is the portal agent.
for service in settings.KNOWN_SERVICES['combo'].values():
if service.get('is-portal-agent') and not service.get('secret'):
slug = 'portal-agent'
break
json_str = json.dumps(
[
{
'label': force_text(label),
'slug': slug,
'url': request.build_absolute_uri(reverse('combo-manager-homepage')),
}
]
)
content_type = 'application/json'
for variable in ('jsonpCallback', 'callback'):
if variable in request.GET:
json_str = '%s(%s);' % (request.GET[variable], json_str)
content_type = 'application/javascript'
break
response = HttpResponse(content_type=content_type)
response.write(json_str)
return response
class PageListCellAddLinkView(ManagedPageMixin, CreateView):
template_name = 'combo/link_cell_form.html'
def dispatch(self, request, *args, **kwargs):
try:
self.cell = CellBase.get_cell(kwargs['cell_reference'], page=kwargs['page_pk'])
except LinkListCell.DoesNotExist:
raise Http404
for klass in self.cell.get_link_cell_classes():
if klass.add_as_link_code == kwargs['link_code']:
self.model = klass
break
if self.model is None:
raise Http404
return super().dispatch(request, *args, **kwargs)
def get_form_class(self):
return self.model().get_default_form_class()
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['instance'] = self.model(page=self.cell.page, placeholder=self.cell.link_placeholder)
return kwargs
def form_valid(self, form):
orders = [x.order for x in self.cell.get_items()]
if orders:
form.instance.order = max(orders) + 1
else:
form.instance.order = 1
PageSnapshot.take(self.cell.page, request=self.request, comment=_('changed cell "%s"') % self.cell)
response = super().form_valid(form)
self.cell.check_validity()
return response
def get_success_url(self):
return '%s#cell-%s' % (
reverse('combo-manager-page-view', kwargs={'pk': self.kwargs.get('page_pk')}),
self.kwargs['cell_reference'],
)
page_list_cell_add_link = PageListCellAddLinkView.as_view()
class PageListCellEditLinkView(ManagedPageMixin, UpdateView):
template_name = 'combo/link_cell_form.html'
def dispatch(self, request, *args, **kwargs):
try:
self.cell = CellBase.get_cell(kwargs['cell_reference'], page=kwargs['page_pk'])
except LinkListCell.DoesNotExist:
raise Http404
try:
self.object = CellBase.get_cell(kwargs['link_cell_reference'], page=kwargs['page_pk'])
except ObjectDoesNotExist:
raise Http404
if self.object.placeholder != self.cell.link_placeholder:
raise Http404
self.model = self.object.__class__
return super().dispatch(request, *args, **kwargs)
def get_object(self, *args, **kwargs):
return self.object
def get_form_class(self):
return self.model().get_default_form_class()
def form_valid(self, form):
if self.request.is_ajax():
self.object = form.save()
response = self.form_invalid(form) # avoid redirection
else:
response = super().form_valid(form)
PageSnapshot.take(self.cell.page, request=self.request, comment=_('changed cell "%s"') % self.cell)
self.cell.check_validity()
return response
def get_success_url(self):
return '%s#cell-%s' % (
reverse('combo-manager-page-view', kwargs={'pk': self.kwargs.get('page_pk')}),
self.kwargs['cell_reference'],
)
page_list_cell_edit_link = PageListCellEditLinkView.as_view()
class PageListCellDeleteLinkView(ManagedPageMixin, DeleteView):
template_name = 'combo/generic_confirm_delete.html'
def dispatch(self, request, *args, **kwargs):
try:
self.cell = CellBase.get_cell(kwargs['cell_reference'], page=kwargs['page_pk'])
except LinkListCell.DoesNotExist:
raise Http404
try:
self.object = CellBase.get_cell(kwargs['link_cell_reference'], page=kwargs['page_pk'])
except ObjectDoesNotExist:
raise Http404
if self.object.placeholder != self.cell.link_placeholder:
raise Http404
self.model = self.object.__class__
return super().dispatch(request, *args, **kwargs)
def get_object(self, *args, **kwargs):
return self.object
def delete(self, request, *args, **kwargs):
response = super().delete(request, *args, **kwargs)
PageSnapshot.take(self.cell.page, request=self.request, comment=_('changed cell "%s"') % self.cell)
self.cell.check_validity()
return response
def get_success_url(self):
return '%s#cell-%s' % (
reverse('combo-manager-page-view', kwargs={'pk': self.kwargs.get('page_pk')}),
self.kwargs['cell_reference'],
)
page_list_cell_delete_link = PageListCellDeleteLinkView.as_view()
class LinkListOrder(ManagedPageMixin, View):
def get(self, *args, **kwargs):
request = self.request
page_pk = self.kwargs['page_pk']
cell_reference = self.kwargs['cell_reference']
try:
cell = CellBase.get_cell(cell_reference, page=page_pk)
except LinkListCell.DoesNotExist:
raise Http404
has_changes = False
for link in cell.get_items():
old_order = link.order
try:
new_order = int(request.GET.get('pos_' + str(link.pk)))
except TypeError:
continue
if new_order != old_order:
link.order = new_order
has_changes = True
link.save(update_fields=['order'])
if has_changes:
PageSnapshot.take(cell.page, request=request, comment=_('reordered cells'))
return HttpResponse(status=204)
link_list_order = LinkListOrder.as_view()
class SiteSettingsView(UpdateView):
form_class = SiteSettingsForm
template_name = 'combo/site_settings.html'
success_url = reverse_lazy('combo-manager-homepage')
def get_object(self):
return SiteSettings.objects.get()
site_settings = SiteSettingsView.as_view()