general: handle /manage/ access to users with page edit roles (#56188)
This commit is contained in:
parent
af5caa199a
commit
69425c6200
|
@ -16,7 +16,7 @@
|
|||
|
||||
from django.conf.urls import include, url
|
||||
|
||||
from combo.urls_utils import decorated_includes, manager_required
|
||||
from combo.urls_utils import decorated_includes, staff_required
|
||||
|
||||
from . import api_views, views
|
||||
|
||||
|
@ -34,7 +34,7 @@ assets_manager_urls = [
|
|||
|
||||
urlpatterns = [
|
||||
url(r'^assets/(?P<key>[\w_:-]+)$', views.serve_asset),
|
||||
url(r'^manage/assets/', decorated_includes(manager_required, include(assets_manager_urls))),
|
||||
url(r'^manage/assets/', decorated_includes(staff_required, include(assets_manager_urls))),
|
||||
url(r'^api/assets/set/(?P<key>[\w_:-]+)/$', api_views.view_set, name='api-assets-set'),
|
||||
url(r'^ajax/assets-export-size/$', views.assets_export_size, name='combo-manager-assets-export-size'),
|
||||
]
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
from django.conf.urls import include, url
|
||||
|
||||
from combo.urls_utils import decorated_includes, manager_required
|
||||
from combo.urls_utils import decorated_includes, staff_required
|
||||
|
||||
from .manager_views import (
|
||||
BasketItemErrorListView,
|
||||
|
@ -110,7 +110,7 @@ urlpatterns = [
|
|||
ReturnView.as_view(),
|
||||
name='lingo-return-payment-backend',
|
||||
),
|
||||
url(r'^manage/lingo/', decorated_includes(manager_required, include(lingo_manager_urls))),
|
||||
url(r'^manage/lingo/', decorated_includes(staff_required, include(lingo_manager_urls))),
|
||||
url(
|
||||
r'^lingo/item/(?P<regie_id>[\w,-]+)/(?P<item_crypto_id>[\w,-]+)/pdf$',
|
||||
ItemDownloadView.as_view(),
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
from django.conf.urls import include, url
|
||||
|
||||
from combo.urls_utils import decorated_includes, manager_required
|
||||
from combo.urls_utils import decorated_includes, staff_required
|
||||
|
||||
from . import manager_views
|
||||
from .views import GeojsonView
|
||||
|
@ -56,7 +56,7 @@ maps_manager_urls = [
|
|||
]
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^manage/maps/', decorated_includes(manager_required, include(maps_manager_urls))),
|
||||
url(r'^manage/maps/', decorated_includes(staff_required, include(maps_manager_urls))),
|
||||
url(
|
||||
r'^ajax/mapcell/geojson/(?P<cell_id>\d+)/(?P<layer_slug>[\w-]+)/$',
|
||||
GeojsonView.as_view(),
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
from django.conf.urls import include, url
|
||||
|
||||
from combo.urls_utils import decorated_includes, manager_required
|
||||
from combo.urls_utils import decorated_includes, staff_required
|
||||
|
||||
from .manager_views import (
|
||||
ManagerAddNavigationEntry,
|
||||
|
@ -55,5 +55,5 @@ urlpatterns = [
|
|||
url('^service-worker-registration.js$', service_worker_registration_js),
|
||||
url('^api/pwa/push/subscribe$', subscribe_push, name='pwa-subscribe-push'),
|
||||
url('^__pwa__/offline/$', offline_page),
|
||||
url(r'^manage/pwa/', decorated_includes(manager_required, include(pwa_manager_urls))),
|
||||
url(r'^manage/pwa/', decorated_includes(staff_required, include(pwa_manager_urls))),
|
||||
]
|
||||
|
|
|
@ -18,6 +18,7 @@ from django.http import HttpResponse, HttpResponseRedirect
|
|||
from django.shortcuts import get_object_or_404, render
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import View
|
||||
|
||||
from combo.apps.search.forms import (
|
||||
CardsEngineSettingsForm,
|
||||
|
@ -29,138 +30,180 @@ from combo.apps.search.forms import (
|
|||
)
|
||||
from combo.apps.search.models import SearchCell
|
||||
from combo.data.models import PageSnapshot
|
||||
from combo.manager.views import ManagedPageMixin
|
||||
from combo.profile import default_description_template
|
||||
|
||||
|
||||
def page_search_cell_add_engine(request, page_pk, cell_reference, engine_slug):
|
||||
cell = get_object_or_404(SearchCell, pk=cell_reference.split('-')[1], page=page_pk)
|
||||
class PageSearchCellAddEngine(ManagedPageMixin, View):
|
||||
def get(self, *args, **kwargs):
|
||||
return self.post(*args, **kwargs)
|
||||
|
||||
def add_slug(slug, **options):
|
||||
if slug in cell.available_engines or slug.startswith('_text_page') or slug.startswith('cards:'):
|
||||
if not cell._search_services or not cell._search_services.get('data'):
|
||||
cell._search_services = {'data': []}
|
||||
if not cell._search_services.get('options'):
|
||||
cell._search_services['options'] = {}
|
||||
cell._search_services['data'].append(slug)
|
||||
cell._search_services['options'][slug] = options
|
||||
def post(self, *args, **kwargs):
|
||||
request = self.request
|
||||
page_pk = self.kwargs['page_pk']
|
||||
cell_reference = self.kwargs['cell_reference']
|
||||
engine_slug = self.kwargs['engine_slug']
|
||||
cell = get_object_or_404(SearchCell, pk=cell_reference.split('-')[1], page=page_pk)
|
||||
|
||||
def add_slug(slug, **options):
|
||||
if slug in cell.available_engines or slug.startswith('_text_page') or slug.startswith('cards:'):
|
||||
if not cell._search_services or not cell._search_services.get('data'):
|
||||
cell._search_services = {'data': []}
|
||||
if not cell._search_services.get('options'):
|
||||
cell._search_services['options'] = {}
|
||||
cell._search_services['data'].append(slug)
|
||||
cell._search_services['options'][slug] = options
|
||||
cell.save()
|
||||
PageSnapshot.take(cell.page, request=request, comment=_('changed cell "%s"') % cell)
|
||||
return HttpResponseRedirect(
|
||||
'%s#cell-%s' % (reverse('combo-manager-page-view', kwargs={'pk': page_pk}), cell_reference)
|
||||
)
|
||||
|
||||
if engine_slug not in ['_text', 'users'] and not engine_slug.startswith('cards:'):
|
||||
# add engine without intermediary form and popup
|
||||
return add_slug(engine_slug)
|
||||
|
||||
form_class = TextEngineSettingsForm
|
||||
if engine_slug == 'users':
|
||||
form_class = UsersEngineSettingsForm
|
||||
if engine_slug.startswith('cards:'):
|
||||
form_class = CardsEngineSettingsForm
|
||||
|
||||
if request.method == 'POST':
|
||||
form = form_class(instance=cell, data=request.POST, engine_slug=engine_slug)
|
||||
if form.is_valid():
|
||||
kwargs = {
|
||||
'title': form.cleaned_data['title'],
|
||||
}
|
||||
if form.cleaned_data.get('description_template'):
|
||||
kwargs['description_template'] = form.cleaned_data['description_template']
|
||||
for key in ['without_user', 'with_description']:
|
||||
if form.cleaned_data.get(key):
|
||||
kwargs[key] = True
|
||||
return add_slug(form.get_slug(), **kwargs)
|
||||
else:
|
||||
form = form_class(instance=cell, engine_slug=engine_slug)
|
||||
context = {
|
||||
'form': form,
|
||||
'cell': cell,
|
||||
}
|
||||
return render(request, 'combo/manager/engine-form.html', context)
|
||||
|
||||
|
||||
page_search_cell_add_engine = PageSearchCellAddEngine.as_view()
|
||||
|
||||
|
||||
class PageSearchCellUpdateEngine(ManagedPageMixin, View):
|
||||
def get(self, *args, **kwargs):
|
||||
return self.post(*args, **kwargs)
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
request = self.request
|
||||
page_pk = self.kwargs['page_pk']
|
||||
cell_reference = self.kwargs['cell_reference']
|
||||
engine_slug = self.kwargs['engine_slug']
|
||||
|
||||
cell = get_object_or_404(SearchCell, pk=cell_reference.split('-')[1], page=page_pk)
|
||||
|
||||
form_class = TextEngineSettingsUpdateForm
|
||||
if engine_slug == 'users':
|
||||
form_class = UsersEngineSettingsUpdateForm
|
||||
if engine_slug.startswith('cards:'):
|
||||
form_class = CardsEngineSettingsUpdateForm
|
||||
|
||||
if request.method == 'POST':
|
||||
form = form_class(instance=cell, data=request.POST, engine_slug=engine_slug)
|
||||
if form.is_valid():
|
||||
kwargs = {
|
||||
'title': form.cleaned_data['title'],
|
||||
}
|
||||
if form.cleaned_data.get('description_template'):
|
||||
kwargs['description_template'] = form.cleaned_data['description_template']
|
||||
for key in ['without_user', 'with_description']:
|
||||
if form.cleaned_data.get(key):
|
||||
kwargs[key] = True
|
||||
|
||||
if not cell._search_services.get('options'):
|
||||
cell._search_services['options'] = {}
|
||||
cell._search_services['options'][engine_slug] = kwargs
|
||||
cell.save()
|
||||
PageSnapshot.take(cell.page, request=request, comment=_('changed cell "%s"') % cell)
|
||||
|
||||
return HttpResponseRedirect(
|
||||
'%s#cell-%s'
|
||||
% (reverse('combo-manager-page-view', kwargs={'pk': page_pk}), cell_reference)
|
||||
)
|
||||
else:
|
||||
initial = {
|
||||
'title': cell._search_services.get('options', {}).get(engine_slug, {}).get('title'),
|
||||
'without_user': cell._search_services.get('options', {})
|
||||
.get(engine_slug, {})
|
||||
.get('without_user'),
|
||||
'with_description': cell._search_services.get('options', {})
|
||||
.get(engine_slug, {})
|
||||
.get('with_description'),
|
||||
'description_template': cell._search_services.get('options', {})
|
||||
.get(engine_slug, {})
|
||||
.get('description_template')
|
||||
or default_description_template,
|
||||
}
|
||||
form = form_class(instance=cell, engine_slug=engine_slug, initial=initial)
|
||||
context = {
|
||||
'form': form,
|
||||
'cell': cell,
|
||||
}
|
||||
return render(request, 'combo/manager/engine-form.html', context)
|
||||
|
||||
|
||||
page_search_cell_update_engine = PageSearchCellUpdateEngine.as_view()
|
||||
|
||||
|
||||
class PageSearchCellDeleteEngine(ManagedPageMixin, View):
|
||||
def get(self, *args, **kwargs):
|
||||
request = self.request
|
||||
page_pk = self.kwargs['page_pk']
|
||||
cell_reference = self.kwargs['cell_reference']
|
||||
engine_slug = self.kwargs['engine_slug']
|
||||
cell = get_object_or_404(SearchCell, pk=cell_reference.split('-')[1], page=page_pk)
|
||||
|
||||
if engine_slug in cell._search_services.get('data'):
|
||||
cell._search_services['data'].remove(engine_slug)
|
||||
cell.save()
|
||||
PageSnapshot.take(cell.page, request=request, comment=_('changed cell "%s"') % cell)
|
||||
|
||||
return HttpResponseRedirect(
|
||||
'%s#cell-%s' % (reverse('combo-manager-page-view', kwargs={'pk': page_pk}), cell_reference)
|
||||
)
|
||||
|
||||
if engine_slug not in ['_text', 'users'] and not engine_slug.startswith('cards:'):
|
||||
# add engine without intermediary form and popup
|
||||
return add_slug(engine_slug)
|
||||
|
||||
form_class = TextEngineSettingsForm
|
||||
if engine_slug == 'users':
|
||||
form_class = UsersEngineSettingsForm
|
||||
if engine_slug.startswith('cards:'):
|
||||
form_class = CardsEngineSettingsForm
|
||||
|
||||
if request.method == 'POST':
|
||||
form = form_class(instance=cell, data=request.POST, engine_slug=engine_slug)
|
||||
if form.is_valid():
|
||||
kwargs = {
|
||||
'title': form.cleaned_data['title'],
|
||||
}
|
||||
if form.cleaned_data.get('description_template'):
|
||||
kwargs['description_template'] = form.cleaned_data['description_template']
|
||||
for key in ['without_user', 'with_description']:
|
||||
if form.cleaned_data.get(key):
|
||||
kwargs[key] = True
|
||||
return add_slug(form.get_slug(), **kwargs)
|
||||
else:
|
||||
form = form_class(instance=cell, engine_slug=engine_slug)
|
||||
context = {
|
||||
'form': form,
|
||||
'cell': cell,
|
||||
}
|
||||
return render(request, 'combo/manager/engine-form.html', context)
|
||||
page_search_cell_delete_engine = PageSearchCellDeleteEngine.as_view()
|
||||
|
||||
|
||||
def page_search_cell_update_engine(request, page_pk, cell_reference, engine_slug):
|
||||
cell = get_object_or_404(SearchCell, pk=cell_reference.split('-')[1], page=page_pk)
|
||||
class PageSearchEnginesOrder(ManagedPageMixin, View):
|
||||
def get(self, *args, **kwargs):
|
||||
request = self.request
|
||||
page_pk = self.kwargs['page_pk']
|
||||
cell_reference = self.kwargs['cell_reference']
|
||||
cell = get_object_or_404(SearchCell, pk=cell_reference.split('-')[1], page=page_pk)
|
||||
|
||||
form_class = TextEngineSettingsUpdateForm
|
||||
if engine_slug == 'users':
|
||||
form_class = UsersEngineSettingsUpdateForm
|
||||
if engine_slug.startswith('cards:'):
|
||||
form_class = CardsEngineSettingsUpdateForm
|
||||
if not cell._search_services.get('data'):
|
||||
return HttpResponse(status=204)
|
||||
|
||||
if request.method == 'POST':
|
||||
form = form_class(instance=cell, data=request.POST, engine_slug=engine_slug)
|
||||
if form.is_valid():
|
||||
kwargs = {
|
||||
'title': form.cleaned_data['title'],
|
||||
}
|
||||
if form.cleaned_data.get('description_template'):
|
||||
kwargs['description_template'] = form.cleaned_data['description_template']
|
||||
for key in ['without_user', 'with_description']:
|
||||
if form.cleaned_data.get(key):
|
||||
kwargs[key] = True
|
||||
engines = []
|
||||
for engine_slug in cell._search_services['data']:
|
||||
try:
|
||||
new_order = int(request.GET.get('pos_' + str(engine_slug)))
|
||||
except TypeError:
|
||||
new_order = 0
|
||||
engines.append((new_order, engine_slug))
|
||||
|
||||
if not cell._search_services.get('options'):
|
||||
cell._search_services['options'] = {}
|
||||
cell._search_services['options'][engine_slug] = kwargs
|
||||
ordered_engines = [a[1] for a in sorted(engines, key=lambda a: a[0])]
|
||||
if ordered_engines != cell._search_services['data']:
|
||||
cell._search_services['data'] = ordered_engines
|
||||
cell.save()
|
||||
PageSnapshot.take(cell.page, request=request, comment=_('changed cell "%s"') % cell)
|
||||
|
||||
return HttpResponseRedirect(
|
||||
'%s#cell-%s' % (reverse('combo-manager-page-view', kwargs={'pk': page_pk}), cell_reference)
|
||||
)
|
||||
else:
|
||||
initial = {
|
||||
'title': cell._search_services.get('options', {}).get(engine_slug, {}).get('title'),
|
||||
'without_user': cell._search_services.get('options', {}).get(engine_slug, {}).get('without_user'),
|
||||
'with_description': cell._search_services.get('options', {})
|
||||
.get(engine_slug, {})
|
||||
.get('with_description'),
|
||||
'description_template': cell._search_services.get('options', {})
|
||||
.get(engine_slug, {})
|
||||
.get('description_template')
|
||||
or default_description_template,
|
||||
}
|
||||
form = form_class(instance=cell, engine_slug=engine_slug, initial=initial)
|
||||
context = {
|
||||
'form': form,
|
||||
'cell': cell,
|
||||
}
|
||||
return render(request, 'combo/manager/engine-form.html', context)
|
||||
|
||||
|
||||
def page_search_cell_delete_engine(request, page_pk, cell_reference, engine_slug):
|
||||
cell = get_object_or_404(SearchCell, pk=cell_reference.split('-')[1], page=page_pk)
|
||||
|
||||
if engine_slug in cell._search_services.get('data'):
|
||||
cell._search_services['data'].remove(engine_slug)
|
||||
cell.save()
|
||||
PageSnapshot.take(cell.page, request=request, comment=_('changed cell "%s"') % cell)
|
||||
|
||||
return HttpResponseRedirect(
|
||||
'%s#cell-%s' % (reverse('combo-manager-page-view', kwargs={'pk': page_pk}), cell_reference)
|
||||
)
|
||||
|
||||
|
||||
def search_engines_order(request, page_pk, cell_reference):
|
||||
cell = get_object_or_404(SearchCell, pk=cell_reference.split('-')[1], page=page_pk)
|
||||
|
||||
if not cell._search_services.get('data'):
|
||||
return HttpResponse(status=204)
|
||||
|
||||
engines = []
|
||||
for engine_slug in cell._search_services['data']:
|
||||
try:
|
||||
new_order = int(request.GET.get('pos_' + str(engine_slug)))
|
||||
except TypeError:
|
||||
new_order = 0
|
||||
engines.append((new_order, engine_slug))
|
||||
|
||||
ordered_engines = [a[1] for a in sorted(engines, key=lambda a: a[0])]
|
||||
if ordered_engines != cell._search_services['data']:
|
||||
cell._search_services['data'] = ordered_engines
|
||||
cell.save()
|
||||
PageSnapshot.take(cell.page, request=request, comment=_('changed cell "%s"') % cell)
|
||||
|
||||
return HttpResponse(status=204)
|
||||
search_engines_order = PageSearchEnginesOrder.as_view()
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
# 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 collections
|
||||
import copy
|
||||
import datetime
|
||||
import hashlib
|
||||
|
@ -354,6 +353,18 @@ class Page(models.Model):
|
|||
return True
|
||||
return False
|
||||
|
||||
def is_editable(self, user):
|
||||
if user.is_staff:
|
||||
return True
|
||||
group_ids = [x.id for x in user.groups.all()]
|
||||
if self.edit_role_id in group_ids:
|
||||
return True
|
||||
hierarchy = self.get_parents_and_self()
|
||||
for page in hierarchy:
|
||||
if page.subpages_edit_role_id in group_ids:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_placeholders(self, request, traverse_cells=False, template_name=None):
|
||||
placeholders = []
|
||||
|
||||
|
@ -382,8 +393,8 @@ class Page(models.Model):
|
|||
tmpl.render(context, request)
|
||||
return placeholders
|
||||
|
||||
def get_next_page(self, user=None, check_visibility=True):
|
||||
pages = Page.get_as_reordered_flat_hierarchy(Page.objects.all())
|
||||
def get_next_page(self, user=None, check_visibility=True, **kwargs):
|
||||
pages = Page.get_as_reordered_flat_hierarchy(Page.objects.all(), **kwargs)
|
||||
this_page = [x for x in pages if x.id == self.id][0]
|
||||
pages = pages[pages.index(this_page) + 1 :]
|
||||
for page in pages:
|
||||
|
@ -391,8 +402,8 @@ class Page(models.Model):
|
|||
return page
|
||||
return None
|
||||
|
||||
def get_previous_page(self, user=None, check_visibility=True):
|
||||
pages = Page.get_as_reordered_flat_hierarchy(Page.objects.all())
|
||||
def get_previous_page(self, user=None, check_visibility=True, **kwargs):
|
||||
pages = Page.get_as_reordered_flat_hierarchy(Page.objects.all(), **kwargs)
|
||||
pages.reverse()
|
||||
this_page = [x for x in pages if x.id == self.id][0]
|
||||
pages = pages[pages.index(this_page) + 1 :]
|
||||
|
@ -402,23 +413,56 @@ class Page(models.Model):
|
|||
return None
|
||||
|
||||
@classmethod
|
||||
def get_as_reordered_flat_hierarchy(cls, object_list, root_page=None):
|
||||
reordered = []
|
||||
parenting = collections.defaultdict(list)
|
||||
def get_as_reordered_flat_hierarchy(cls, object_list, root_page=None, follow_user_perms=None):
|
||||
# create a list of [(page.order, page.id, page), (subpage.order, subpage.id, subpage),
|
||||
# (subsubpage.order, subpage.id, subsubpage)] and sort it to get the page hierarchy
|
||||
# as a flat list.
|
||||
# follow_user_perms can be None or a User object, in that case only pages that are
|
||||
# editable by user will be returned.
|
||||
all_pages = {}
|
||||
for page in object_list:
|
||||
parenting[page.parent_id].append(page)
|
||||
all_pages[page.id] = page
|
||||
|
||||
def fill_list(object_sublist, level=0, parent=None):
|
||||
for page in object_sublist:
|
||||
parent_id = parent.pk if parent else None
|
||||
if page.parent_id == parent_id or page == root_page and parent is None:
|
||||
page.level = level
|
||||
reordered.append(page)
|
||||
if page.id in parenting:
|
||||
fill_list(object_sublist, level=level + 1, parent=page)
|
||||
pages_hierarchy = []
|
||||
for page in object_list:
|
||||
page_hierarchy = [(page.order, page.id, page)]
|
||||
parent_id = page.parent_id
|
||||
while parent_id and parent_id in all_pages:
|
||||
parent_page = all_pages[parent_id]
|
||||
page_hierarchy.append((parent_page.order, parent_page.id, parent_page))
|
||||
parent_id = parent_page.parent_id
|
||||
page_hierarchy.reverse()
|
||||
page.level = len(page_hierarchy) - 1
|
||||
pages_hierarchy.append(page_hierarchy)
|
||||
|
||||
fill_list(object_list)
|
||||
return reordered
|
||||
group_ids = None # None = do not pay attention to groups
|
||||
if follow_user_perms and not follow_user_perms.is_staff:
|
||||
group_ids = [x.id for x in follow_user_perms.groups.all()]
|
||||
|
||||
pages_hierarchy.sort()
|
||||
|
||||
if group_ids is not None:
|
||||
# remove pages the user cannot see/edit
|
||||
pages_hierarchy = [
|
||||
x
|
||||
for x in pages_hierarchy
|
||||
if x[-1][-1].edit_role_id in group_ids
|
||||
or any(y[-1].subpages_edit_role_id in group_ids for y in x[:-1])
|
||||
]
|
||||
|
||||
# adjust levels to have shallowest level at 0
|
||||
seen_pages = {} # page_id -> page_level
|
||||
for page_hierarchy in pages_hierarchy:
|
||||
_, page_id, page = page_hierarchy[-1]
|
||||
if page.parent_id in seen_pages:
|
||||
# parent page is displayed, adjust level according to it
|
||||
page.level = seen_pages[page.parent_id] + 1
|
||||
else:
|
||||
# page with no parent displayed, set it at root level
|
||||
page.level = 0
|
||||
seen_pages[page_id] = page.level
|
||||
|
||||
return [x[-1][-1] for x in pages_hierarchy]
|
||||
|
||||
@staticmethod
|
||||
@utils.cache_during_request
|
||||
|
|
|
@ -106,6 +106,23 @@ class PageAddForm(forms.ModelForm):
|
|||
return self.instance
|
||||
|
||||
|
||||
class PageRestrictedAddForm(PageAddForm):
|
||||
class Meta:
|
||||
model = Page
|
||||
fields = ('title', 'template_name', 'parent')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
group_ids = [x.id for x in self.request.user.groups.all()]
|
||||
self.fields['parent'].queryset = Page.objects.filter(subpages_edit_role_id__in=group_ids)
|
||||
self.fields['parent'].required = True
|
||||
self.fields['parent'].empty_label = None
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.parent = self.cleaned_data.get('parent')
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class PageEditTitleForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Page
|
||||
|
|
|
@ -4,8 +4,13 @@
|
|||
{% block appbar %}
|
||||
<h2>{% trans 'Pages' %}</h2>
|
||||
<span class="actions">
|
||||
{% if user.is_staff %}
|
||||
<a class="extra-actions-menu-opener"></a>
|
||||
{% endif %}
|
||||
{% if can_add_page %}
|
||||
<a rel="popup" href="{% url 'combo-manager-page-add' %}">{% trans 'New' %}</a>
|
||||
{% endif %}
|
||||
{% if user.is_staff %}
|
||||
<ul class="extra-actions-menu">
|
||||
<li><a href="{% url 'combo-manager-site-export' %}" rel="popup" data-autoclose-dialog="true">{% trans 'Export Site' %}</a></li>
|
||||
<li><a href="{% url 'combo-manager-site-import' %}">{% trans 'Import Site' %}</a></li>
|
||||
|
@ -14,6 +19,7 @@
|
|||
<li><a href="{{ extra_action.href }}">{{ extra_action.text }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</span>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -30,7 +36,7 @@ Use drag and drop with the ⣿ handles to reorder and change hierarchy of pages.
|
|||
<div class="objects-list" id="pages-list" data-page-order-url="{% url 'combo-manager-page-order' %}">
|
||||
{% for page in object_list %}
|
||||
<div class="page level-{{page.level}}{% if collapse_pages %} untoggled{% endif %}" data-page-id="{{page.id}}" data-level="{{page.level}}">
|
||||
<span class="handle">⣿</span>
|
||||
{% if user.is_staff %}<span class="handle">⣿</span>{% endif %}
|
||||
<span class="group1">
|
||||
<a href="{% url 'combo-manager-page-view' pk=page.id %}">
|
||||
{{ page.title }}
|
||||
|
|
|
@ -11,10 +11,12 @@
|
|||
<ul class="extra-actions-menu">
|
||||
<li><a class="action-history" href="{% url 'combo-manager-page-history' pk=object.id %}">{% trans 'History' %}</a></li>
|
||||
<li><a {% if page_has_subpages %}rel="popup" data-autoclose-dialog="true" {% endif %}class="action-export" href="{% url 'combo-manager-page-export' pk=object.id %}">{% trans 'Export' %}</a></li>
|
||||
{% if request.user.is_staff %}
|
||||
<li><a class="action-add-child" rel="popup" href="{% url 'combo-manager-page-add-child' pk=object.id %}">{% trans 'Add a child page' %}</a></li>
|
||||
<li><a class="action-edit-roles" rel="popup" href="{% url 'combo-manager-page-edit-roles' pk=object.id %}">{% trans 'Manage edit roles' %}</a></li>
|
||||
<li><a rel="popup" class="action-duplicate" href="{% url 'combo-manager-page-duplicate' pk=object.id %}">{% trans 'Duplicate' %}</a></li>
|
||||
<li><a class="action-delete" rel="popup" href="{% url 'combo-manager-page-delete' pk=object.id %}">{% trans 'Delete' %}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</span>
|
||||
{% endblock %}
|
||||
|
@ -94,7 +96,7 @@
|
|||
<div class="page-options navigation">
|
||||
<h3>{% trans 'Navigation' %}</h3>
|
||||
<ul>
|
||||
{% if object.parent_id %}
|
||||
{% if object.parent_id and request.user.is_staff %}
|
||||
<li class="nav-up"><a href="{% url 'combo-manager-page-view' pk=object.parent_id %}">{{ object.parent.title }}</a></li>
|
||||
{% endif %}
|
||||
{% if previous_page %}
|
||||
|
|
|
@ -16,10 +16,10 @@
|
|||
|
||||
import ckeditor.views as ckeditor_views
|
||||
from django.conf.urls import url
|
||||
from django.contrib.admin.views.decorators import staff_member_required
|
||||
from django.views.decorators.cache import never_cache
|
||||
|
||||
from combo.apps.assets import views as assets_views
|
||||
from combo.urls_utils import staff_required
|
||||
|
||||
from .. import plugins
|
||||
from . import views
|
||||
|
@ -27,9 +27,13 @@ from . import views
|
|||
urlpatterns = [
|
||||
url(r'^$', views.homepage, name='combo-manager-homepage'),
|
||||
url(r'^menu.json$', views.menu_json),
|
||||
url(r'^site-export$', views.site_export, name='combo-manager-site-export'),
|
||||
url(r'^site-import$', views.site_import, name='combo-manager-site-import'),
|
||||
url(r'^cells/invalid-report/$', views.invalid_cell_report, name='combo-manager-invalid-cell-report'),
|
||||
url(r'^site-export$', staff_required(views.site_export), name='combo-manager-site-export'),
|
||||
url(r'^site-import$', staff_required(views.site_import), name='combo-manager-site-import'),
|
||||
url(
|
||||
r'^cells/invalid-report/$',
|
||||
staff_required(views.invalid_cell_report),
|
||||
name='combo-manager-invalid-cell-report',
|
||||
),
|
||||
url(r'^pages/add/$', views.page_add, name='combo-manager-page-add'),
|
||||
url(r'^pages/(?P<pk>\d+)/$', views.page_view, name='combo-manager-page-view'),
|
||||
url(
|
||||
|
@ -59,15 +63,25 @@ urlpatterns = [
|
|||
views.page_remove_picture,
|
||||
name='combo-manager-page-remove-picture',
|
||||
),
|
||||
url(r'^pages/(?P<pk>\d+)/delete$', views.page_delete, name='combo-manager-page-delete'),
|
||||
url(r'^pages/(?P<pk>\d+)/delete$', staff_required(views.page_delete), name='combo-manager-page-delete'),
|
||||
url(r'^pages/(?P<pk>\d+)/export$', views.page_export, name='combo-manager-page-export'),
|
||||
url(r'^pages/(?P<pk>\d+)/add/$', views.page_add_child, name='combo-manager-page-add-child'),
|
||||
url(r'^pages/(?P<pk>\d+)/duplicate$', views.page_duplicate, name='combo-manager-page-duplicate'),
|
||||
url(r'^pages/(?P<pk>\d+)/edit-roles/$', views.page_edit_roles, name='combo-manager-page-edit-roles'),
|
||||
url(
|
||||
r'^pages/(?P<pk>\d+)/add/$', staff_required(views.page_add_child), name='combo-manager-page-add-child'
|
||||
),
|
||||
url(
|
||||
r'^pages/(?P<pk>\d+)/duplicate$',
|
||||
staff_required(views.page_duplicate),
|
||||
name='combo-manager-page-duplicate',
|
||||
),
|
||||
url(
|
||||
r'^pages/(?P<pk>\d+)/edit-roles/$',
|
||||
staff_required(views.page_edit_roles),
|
||||
name='combo-manager-page-edit-roles',
|
||||
),
|
||||
url(r'^pages/(?P<pk>\d+)/history$', views.page_history, name='combo-manager-page-history'),
|
||||
url(
|
||||
r'^pages/(?P<page_pk>\d+)/history/(?P<pk>\d+)/$',
|
||||
views.snapshot_restore,
|
||||
staff_required(views.snapshot_restore),
|
||||
name='combo-manager-snapshot-restore',
|
||||
),
|
||||
url(
|
||||
|
@ -127,10 +141,8 @@ urlpatterns = [
|
|||
),
|
||||
url(r'^pages/(?P<page_pk>\d+)/order$', views.cell_order, name='combo-manager-cell-order'),
|
||||
url(r'^pages/order$', views.page_order, name='combo-manager-page-order'),
|
||||
url(r'^ckeditor/upload/', staff_member_required(ckeditor_views.upload), name='ckeditor_upload'),
|
||||
url(
|
||||
r'^ckeditor/browse/', never_cache(staff_member_required(assets_views.browse)), name='ckeditor_browse'
|
||||
),
|
||||
url(r'^ckeditor/upload/', ckeditor_views.upload, name='ckeditor_upload'),
|
||||
url(r'^ckeditor/browse/', never_cache(assets_views.browse), name='ckeditor_browse'),
|
||||
]
|
||||
|
||||
urlpatterns = plugins.register_plugins_manager_urls(urlpatterns)
|
||||
|
|
|
@ -22,7 +22,7 @@ from operator import attrgetter
|
|||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
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
|
||||
|
@ -41,12 +41,14 @@ from django.views.generic import (
|
|||
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
|
||||
from combo.data.utils import ImportSiteError, export_site, export_site_tar, import_site, import_site_tar
|
||||
from combo.urls_utils import staff_required
|
||||
|
||||
from .forms import (
|
||||
CellVisibilityForm,
|
||||
|
@ -60,6 +62,7 @@ from .forms import (
|
|||
PageEditSlugForm,
|
||||
PageEditTitleForm,
|
||||
PageExportForm,
|
||||
PageRestrictedAddForm,
|
||||
PageSelectTemplateForm,
|
||||
PageVisibilityForm,
|
||||
SiteExportForm,
|
||||
|
@ -67,15 +70,25 @@ from .forms import (
|
|||
)
|
||||
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
|
||||
|
@ -170,7 +183,13 @@ def invalid_cell_report(request):
|
|||
class PageAddView(CreateView):
|
||||
model = Page
|
||||
template_name = 'combo/page_add.html'
|
||||
form_class = PageAddForm
|
||||
|
||||
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()
|
||||
|
@ -190,7 +209,15 @@ class PageAddView(CreateView):
|
|||
page_add = PageAddView.as_view()
|
||||
|
||||
|
||||
class PageAddChildView(PageAddView):
|
||||
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'])
|
||||
|
@ -204,7 +231,7 @@ class PageAddChildView(PageAddView):
|
|||
page_add_child = PageAddChildView.as_view()
|
||||
|
||||
|
||||
class PageEditView(UpdateView):
|
||||
class PageEditView(ManagedPageMixin, UpdateView):
|
||||
model = Page
|
||||
template_name = 'combo/page_add.html'
|
||||
comment = None
|
||||
|
@ -316,7 +343,7 @@ class PageEditPictureView(PageEditView):
|
|||
page_edit_picture = PageEditPictureView.as_view()
|
||||
|
||||
|
||||
class PageRemovePictureView(DetailView):
|
||||
class PageRemovePictureView(ManagedPageMixin, DetailView):
|
||||
model = Page
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
|
@ -330,7 +357,7 @@ class PageRemovePictureView(DetailView):
|
|||
page_remove_picture = PageRemovePictureView.as_view()
|
||||
|
||||
|
||||
class PageView(DetailView):
|
||||
class PageView(ManagedPageMixin, DetailView):
|
||||
model = Page
|
||||
template_name = 'combo/page_view.html'
|
||||
|
||||
|
@ -397,8 +424,12 @@ class PageView(DetailView):
|
|||
|
||||
context.update(
|
||||
{
|
||||
'previous_page': self.object.get_previous_page(check_visibility=False),
|
||||
'next_page': self.object.get_next_page(check_visibility=False),
|
||||
'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
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -408,7 +439,7 @@ class PageView(DetailView):
|
|||
page_view = requires_csrf_token(PageView.as_view())
|
||||
|
||||
|
||||
class PageDeleteView(DeleteView):
|
||||
class PageDeleteView(ManagedPageMixin, DeleteView):
|
||||
model = Page
|
||||
template_name = 'combo/delete_page.html'
|
||||
|
||||
|
@ -435,7 +466,7 @@ class PageDeleteView(DeleteView):
|
|||
page_delete = PageDeleteView.as_view()
|
||||
|
||||
|
||||
class PageExportView(FormView):
|
||||
class PageExportView(ManagedPageMixin, FormView):
|
||||
form_class = PageExportForm
|
||||
template_name = 'combo/page_export.html'
|
||||
|
||||
|
@ -477,7 +508,7 @@ class PageExportView(FormView):
|
|||
page_export = PageExportView.as_view()
|
||||
|
||||
|
||||
class PageDuplicateView(FormView):
|
||||
class PageDuplicateView(ManagedPageMixin, FormView):
|
||||
form_class = PageDuplicateForm
|
||||
template_name = 'combo/page_duplicate.html'
|
||||
|
||||
|
@ -506,7 +537,7 @@ class PageDuplicateView(FormView):
|
|||
page_duplicate = PageDuplicateView.as_view()
|
||||
|
||||
|
||||
class PageHistoryView(ListView):
|
||||
class PageHistoryView(ManagedPageMixin, ListView):
|
||||
model = PageSnapshot
|
||||
template_name = 'combo/page_history.html'
|
||||
paginate_by = 20
|
||||
|
@ -519,7 +550,7 @@ class PageHistoryView(ListView):
|
|||
page_history = PageHistoryView.as_view()
|
||||
|
||||
|
||||
class SnapshotRestoreView(DetailView):
|
||||
class SnapshotRestoreView(ManagedPageMixin, DetailView):
|
||||
http_method_names = ['get', 'post']
|
||||
model = PageSnapshot
|
||||
template_name = 'combo/snapshot_restore.html'
|
||||
|
@ -541,7 +572,7 @@ class SnapshotRestoreView(DetailView):
|
|||
snapshot_restore = SnapshotRestoreView.as_view()
|
||||
|
||||
|
||||
class PageAddCellView(RedirectView):
|
||||
class PageAddCellView(ManagedPageMixin, RedirectView):
|
||||
permanent = False
|
||||
|
||||
def get_redirect_url(self, page_pk, cell_type, variant, ph_key):
|
||||
|
@ -561,7 +592,7 @@ class PageAddCellView(RedirectView):
|
|||
page_add_cell = PageAddCellView.as_view()
|
||||
|
||||
|
||||
class PageEditCellView(UpdateView):
|
||||
class PageEditCellView(ManagedPageMixin, UpdateView):
|
||||
def get_template_names(self):
|
||||
return [self.template_name or self.object.manager_form_template]
|
||||
|
||||
|
@ -609,7 +640,7 @@ class PageEditCellView(UpdateView):
|
|||
page_edit_cell = PageEditCellView.as_view()
|
||||
|
||||
|
||||
class PageDeleteCellView(DeleteView):
|
||||
class PageDeleteCellView(ManagedPageMixin, DeleteView):
|
||||
template_name = 'combo/generic_confirm_delete.html'
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
|
@ -635,7 +666,7 @@ class PageDeleteCellView(DeleteView):
|
|||
page_delete_cell = PageDeleteCellView.as_view()
|
||||
|
||||
|
||||
class PageDuplicateCellView(RedirectView):
|
||||
class PageDuplicateCellView(ManagedPageMixin, RedirectView):
|
||||
permanent = False
|
||||
|
||||
def get_redirect_url(self, page_pk, cell_reference):
|
||||
|
@ -672,30 +703,37 @@ class PageCellOptionsView(PageEditCellView):
|
|||
page_cell_options = PageCellOptionsView.as_view()
|
||||
|
||||
|
||||
def cell_order(request, 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)
|
||||
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'])
|
||||
|
@ -782,7 +820,7 @@ def menu_json(request):
|
|||
return response
|
||||
|
||||
|
||||
class PageListCellAddLinkView(CreateView):
|
||||
class PageListCellAddLinkView(ManagedPageMixin, CreateView):
|
||||
template_name = 'combo/link_cell_form.html'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
|
@ -827,7 +865,7 @@ class PageListCellAddLinkView(CreateView):
|
|||
page_list_cell_add_link = PageListCellAddLinkView.as_view()
|
||||
|
||||
|
||||
class PageListCellEditLinkView(UpdateView):
|
||||
class PageListCellEditLinkView(ManagedPageMixin, UpdateView):
|
||||
template_name = 'combo/link_cell_form.html'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
|
@ -870,7 +908,7 @@ class PageListCellEditLinkView(UpdateView):
|
|||
page_list_cell_edit_link = PageListCellEditLinkView.as_view()
|
||||
|
||||
|
||||
class PageListCellDeleteLinkView(DeleteView):
|
||||
class PageListCellDeleteLinkView(ManagedPageMixin, DeleteView):
|
||||
template_name = 'combo/generic_confirm_delete.html'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
|
@ -906,25 +944,32 @@ class PageListCellDeleteLinkView(DeleteView):
|
|||
page_list_cell_delete_link = PageListCellDeleteLinkView.as_view()
|
||||
|
||||
|
||||
def link_list_order(request, page_pk, 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
|
||||
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:
|
||||
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'])
|
||||
cell = CellBase.get_cell(cell_reference, page=page_pk)
|
||||
except LinkListCell.DoesNotExist:
|
||||
raise Http404
|
||||
|
||||
if has_changes:
|
||||
PageSnapshot.take(cell.page, request=request, comment=_('reordered cells'))
|
||||
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'])
|
||||
|
||||
return HttpResponse(status=204)
|
||||
if has_changes:
|
||||
PageSnapshot.take(cell.page, request=request, comment=_('reordered cells'))
|
||||
|
||||
return HttpResponse(status=204)
|
||||
|
||||
|
||||
link_list_order = LinkListOrder.as_view()
|
||||
|
|
|
@ -22,6 +22,8 @@ from django.conf.urls import include, url
|
|||
from django.http import Http404
|
||||
from django.views.debug import technical_404_response
|
||||
|
||||
from .urls_utils import decorated_includes, staff_required
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
PLUGIN_GROUP_NAME = 'combo.plugin'
|
||||
|
@ -75,7 +77,11 @@ def register_plugins_manager_urls(urlpatterns):
|
|||
urls = get_plugin_includes(plugin, 'get_after_manager_urls')
|
||||
if urls:
|
||||
post_urls.append(urls)
|
||||
return pre_urls + urlpatterns + post_urls
|
||||
return (
|
||||
[url('', decorated_includes(staff_required, include(pre_urls)))]
|
||||
+ urlpatterns
|
||||
+ [url('', decorated_includes(staff_required, include(post_urls)))]
|
||||
)
|
||||
|
||||
|
||||
def get_extra_manager_actions():
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
import django
|
||||
from django.contrib.auth.decorators import user_passes_test
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db.models import Q
|
||||
|
||||
if django.VERSION < (2, 0, 0):
|
||||
from django.urls.resolvers import RegexURLPattern as URLPattern # pylint: disable=no-name-in-module
|
||||
|
@ -61,6 +62,13 @@ def manager_required(function=None, login_url=None):
|
|||
if user and user.is_staff:
|
||||
return True
|
||||
if user and not user.is_anonymous:
|
||||
from combo.data.models import Page
|
||||
|
||||
group_ids = [x.id for x in user.groups.all()]
|
||||
if Page.objects.filter(
|
||||
Q(edit_role_id__in=group_ids) | Q(subpages_edit_role_id__in=group_ids)
|
||||
).exists():
|
||||
return True
|
||||
raise PermissionDenied()
|
||||
# As the last resort, show the login form
|
||||
return False
|
||||
|
@ -69,3 +77,18 @@ def manager_required(function=None, login_url=None):
|
|||
if function:
|
||||
return actual_decorator(function)
|
||||
return actual_decorator
|
||||
|
||||
|
||||
def staff_required(function=None, login_url=None):
|
||||
def check_staff(user):
|
||||
if user and user.is_staff:
|
||||
return True
|
||||
if user and not user.is_anonymous:
|
||||
raise PermissionDenied()
|
||||
# As the last resort, show the login form
|
||||
return False
|
||||
|
||||
actual_decorator = user_passes_test(check_staff, login_url=login_url)
|
||||
if function:
|
||||
return actual_decorator(function)
|
||||
return actual_decorator
|
||||
|
|
|
@ -23,6 +23,7 @@ from django.utils.encoding import force_bytes, force_str
|
|||
from django.utils.http import urlencode
|
||||
from django.utils.six import BytesIO
|
||||
from django.utils.timezone import now
|
||||
from pyquery import PyQuery
|
||||
from webtest import Upload
|
||||
|
||||
from combo.apps.assets.models import Asset
|
||||
|
@ -613,7 +614,7 @@ def test_edit_page_num_queries(settings, app, admin_user):
|
|||
app.get('/manage/pages/%s/' % page.pk) # load once to populate caches
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
app.get('/manage/pages/%s/' % page.pk)
|
||||
assert len(ctx.captured_queries) == 32
|
||||
assert len(ctx.captured_queries) == 33
|
||||
|
||||
|
||||
def test_delete_page(app, admin_user):
|
||||
|
@ -848,10 +849,7 @@ def test_export_page_order():
|
|||
page4 = Page.objects.create(title='Four', slug='four', parent=page1, template_name='standard')
|
||||
random_list = [page3, page4, page1, page2]
|
||||
ordered_list = Page.get_as_reordered_flat_hierarchy(random_list)
|
||||
assert ordered_list[0] == page1
|
||||
assert ordered_list[1] == page4
|
||||
assert ordered_list[2] == page2
|
||||
assert ordered_list[3] == page3
|
||||
assert ordered_list in ([page1, page4, page2, page3], [page1, page2, page3, page4])
|
||||
|
||||
|
||||
def test_site_export_import_json(app, admin_user):
|
||||
|
@ -2283,7 +2281,7 @@ def test_page_versionning(app, admin_user):
|
|||
resp = resp.click('restore', index=6)
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = resp.form.submit().follow()
|
||||
assert len(ctx.captured_queries) == 144
|
||||
assert len(ctx.captured_queries) == 146
|
||||
|
||||
resp2 = resp.click('See online')
|
||||
assert resp2.text.index('Foobar1') < resp2.text.index('Foobar2') < resp2.text.index('Foobar3')
|
||||
|
@ -2486,3 +2484,117 @@ def test_edit_link_list_order(app, admin_user):
|
|||
for i, item in enumerate(items):
|
||||
item.refresh_from_db()
|
||||
assert item.order == new_order[i]
|
||||
|
||||
|
||||
def test_restricted_page_edit(app, admin_user, john_doe):
|
||||
group = Group.objects.create(name='foobar')
|
||||
john_doe.groups.set([group])
|
||||
|
||||
Page.objects.all().delete()
|
||||
page1 = Page.objects.create(title='One', slug='one', parent=None, template_name='standard')
|
||||
page2 = Page.objects.create(title='Two', slug='two', parent=page1, template_name='standard')
|
||||
page3 = Page.objects.create(title='Three', slug='three', parent=page1, template_name='standard')
|
||||
page4 = Page.objects.create(title='Four', slug='four', parent=None, template_name='standard')
|
||||
|
||||
app = login(app, username='john.doe', password='john.doe')
|
||||
resp = app.get('/manage/', status=403)
|
||||
|
||||
page4.edit_role = group
|
||||
page4.save()
|
||||
|
||||
resp = app.get('/manage/', status=200)
|
||||
assert [x.attrib['href'] for x in PyQuery(resp.text).find('a[href^="/manage/pages/"]')] == [
|
||||
'/manage/pages/%s/' % page4.id,
|
||||
]
|
||||
|
||||
app.get('/manage/pages/%s/' % page1.id, status=403)
|
||||
|
||||
resp = app.get('/manage/pages/%s/' % page4.id, status=200)
|
||||
# check all page links are ok
|
||||
page_links = [x.attrib['href'] for x in PyQuery(resp.text).find('a[href^="/manage/"]')]
|
||||
for target in page_links:
|
||||
app.get(target, status=200)
|
||||
|
||||
# relogin as admin to get all links
|
||||
app = login(app)
|
||||
resp = app.get('/manage/pages/%s/' % page4.id, status=200)
|
||||
admin_links = [x.attrib['href'] for x in PyQuery(resp.text).find('a[href^="/manage/"]')]
|
||||
|
||||
# back to normal user
|
||||
app = login(app, username='john.doe', password='john.doe')
|
||||
only_admin_links = [x for x in admin_links if x not in page_links]
|
||||
for target in only_admin_links:
|
||||
app.get(target, status=403)
|
||||
# check some important pages are correctly limited to admins
|
||||
assert '/manage/pages/%s/delete' % page4.id in only_admin_links
|
||||
assert '/manage/pages/%s/edit-roles/' % page4.id in only_admin_links
|
||||
|
||||
# check combo.apps pages are forbidden
|
||||
app.get('/manage/pwa/', status=403)
|
||||
app.get('/manage/maps/', status=403)
|
||||
app.get('/manage/lingo/', status=403)
|
||||
|
||||
# check page can be modified for real, add a cell
|
||||
resp = app.get('/manage/pages/%s/' % page4.id)
|
||||
resp = app.get(resp.html.find('option').get('data-add-url'))
|
||||
|
||||
cells = CellBase.get_cells(page_id=page4.id)
|
||||
assert len(cells) == 1
|
||||
|
||||
# check it's not possible to add a page
|
||||
app.get('/manage/pages/add/', status=403)
|
||||
|
||||
# give access to children of page1
|
||||
page1.subpages_edit_role = group
|
||||
page1.save()
|
||||
|
||||
resp = app.get('/manage/', status=200)
|
||||
assert [x.attrib['href'] for x in PyQuery(resp.text).find('a[href^="/manage/pages/"]')] == [
|
||||
'/manage/pages/add/',
|
||||
'/manage/pages/%s/' % page2.id,
|
||||
'/manage/pages/%s/' % page3.id,
|
||||
'/manage/pages/%s/' % page4.id,
|
||||
]
|
||||
|
||||
resp = app.get('/manage/pages/%s/' % page2.id)
|
||||
resp = app.get(resp.html.find('option').get('data-add-url'))
|
||||
|
||||
cells = CellBase.get_cells(page_id=page2.id)
|
||||
assert len(cells) == 1
|
||||
|
||||
# add a subpage, the "new page" dialog will have an extra "parent" field.
|
||||
resp = app.get('/manage/', status=200)
|
||||
resp = app.get('/manage/pages/add/')
|
||||
resp.forms[0]['title'].value = 'Foobar'
|
||||
assert resp.forms[0]['parent'].options == [(str(page1.id), False, 'One')]
|
||||
resp = resp.forms[0].submit()
|
||||
page5 = Page.objects.get(slug='foobar')
|
||||
assert page5.parent_id == page1.id
|
||||
|
||||
# check levels are adjusted on index page
|
||||
resp = app.get('/manage/', status=200)
|
||||
assert [
|
||||
(int(x.attrib['data-page-id']), int(x.attrib['data-level']))
|
||||
for x in PyQuery(resp.text).find('div.page')
|
||||
] == [
|
||||
(page2.id, 0),
|
||||
(page3.id, 0),
|
||||
(page5.id, 0),
|
||||
(page4.id, 0),
|
||||
]
|
||||
|
||||
# make page1 editable (-> visible), this will push subpages a level down, but
|
||||
# the independant page4 will stay at level 0.
|
||||
page1.edit_role = group
|
||||
page1.save()
|
||||
resp = app.get('/manage/', status=200)
|
||||
assert [
|
||||
(int(x.attrib['data-page-id']), int(x.attrib['data-level']))
|
||||
for x in PyQuery(resp.text).find('div.page')
|
||||
] == [
|
||||
(page1.id, 0),
|
||||
(page2.id, 1),
|
||||
(page3.id, 1),
|
||||
(page5.id, 1),
|
||||
(page4.id, 0),
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue