You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
It would be nice to have a way to disallow certain targets in the move view - e.g. moving pages outside of a "Home" node or disallow moving pages before fixed pages (read: node stays always on top in menu).
I tried to monkey patch feincms3.admin.MoveForm.__generate_choices method and filter only for allowed pages but to no success.
I somehow got it working now by overwriting the feincms3.admin.MoveForm.clean() method - but that feels wrong and is not very user friendly as all the move targets are still shown in the admin UI:
classMyMoveForm(MoveForm):
defclean(self):
data=super().clean()
ifnotdata.get("new_location"):
returndataifself.request.user.is_superuser:
returndata# don't allow to create a new root noderelative=data["relative"]
first_or_right=data["first_or_right"]
ifnotrelative:
raiseValidationError("You do not have the permission to move this page here!")
eliffirst_or_right=='right'andnotrelative.parent:
raiseValidationError("You do not have the permission to move this page here!")
@admin.register(models.Page)classPageAdmin(ContentEditor, TreeAdmin):
...
defmove_view(self, request, obj):
returnself.action_form_view(
request, obj, form_class=MyMoveForm, title=f"Move {obj}"
)
An option to disallow certain pages as move target or any idea how to tackle that in a smart way is very appreciated.
I don't immediately have a good idea for this. Maybe you could try overriding MoveForm.__init__ and add self.fields["new_location"].choices = _generate_choices(self.get_queryset(self.request).filter(...your...filters...).with_tree_fields()) or something like that after super.__init__()?
I'm happy to change the code, and I'd also appreciate a pull request if you find a solution yourself!
Alright I tinkered a bit with it and its definitively not trivial to find a good solution especially without a non database heavy way to get the previous or next sibling of any given node during the choice generation.
For now I overwrote MoveForm._generate_choices to use three new methods can_move_root, can_move_right and can_move_first, which are used to customize the _generate_choice logic. To have the original behaviour all three methods should just return True. Those methods are also used in the clean method to prevent any malicious pampering with the move form.
One can now use the self.queryset and self.request properties in those 3 methods to limit the move targets. However, I opted for two new BooleanField fields can_move_right and can_move_first within my Page model. This way I can exactly define what move targets are allowed for fixed pages without extra database queries or additional logic.
Don't know if this is to convoluted/obscure to be included into feincms3 - decision is up to you - I'm happy to create a PR if wanted.
admin.py
classMyMoveForm(MoveForm):
""" Custom move form """defcan_move_root(self):
# custom code here - default: return Trueifself.request.user.is_superuser:
returnTruereturnFalsedefcan_move_right(self, node):
# custom code here - default: return Trueifself.request.user.is_superuser:
returnTruereturngetattr(node, "can_move_right", True)
defcan_move_first(self, node):
# custom code here - default: return Trueifself.request.user.is_superuser:
returnTruereturngetattr(node, "can_move_first", True)
def_generate_choices(self, queryset):
children=defaultdict(list)
fornodeinqueryset:
children[node.parent_id].append(node)
def_text_indent(depth):
returnmark_safe(f' style="text-indent:{depth*30}px"')
choices= []
def_iterate(parent_id):
forindex, nodeinenumerate(children[parent_id]):
ifnode==self.instance:
choice= (
"",
format_html(
'<div class="mv is-self"{}><strong>{}</strong>',
_text_indent(node.tree_depth),
node,
),
)
ifindex==0andparent_id:
# Moving the first child of parent_id; do not remove parent_idchoices[-1] = (
choices[-1][0],
mark_safe(choices[-1][1].replace("mv-mark", "hidden")),
)
choices.append(choice)
else:
choices[-1] =choicecontinueifself.can_move_first(node):
choices.append(
(
f"{node.id}:first",
format_html(
'<div class="mv to-first"{}><strong>{}</strong>''<div class="mv-mark"{}>→ {}</div></div>',
_text_indent(node.tree_depth),
node,
_text_indent(node.tree_depth+1),
_("move here"),
),
)
)
else:
choices.append(
(
f"{node.id}:first",
format_html(
'<div class="mv to-first"{}><strong>{}</strong>',
_text_indent(node.tree_depth),
node,
),
)
)
_iterate(node.id)
ifself.can_move_right(node):
choices.append(
(
f"{node.id}:right",
format_html(
'<div class="mv to-right mv-mark"{}>→ {}</div>',
_text_indent(node.tree_depth),
_("move here"),
),
)
)
ifself.can_move_root():
choices.append(
(
"0:first",
format_html(
'<div class="mv to-root mv-mark">→ {}</div>',
_("move here"),
),
)
)
_iterate(None)
returnchoicesdefclean(self):
data=super().clean()
ifnotdata.get("new_location"):
returndataifself.request.user.is_superuser:
returndatarelative=data["relative"]
first_or_right=data["first_or_right"]
ifnotrelativeandnotself.can_move_root():
raiseValidationError(
"You do not have the permission to move this page here!"
)
eliffirst_or_right=="first"andnotself.can_move_first(relative):
raiseValidationError(
"You do not have the permission to move this page here!"
)
eliffirst_or_right=="right"andnotself.can_move_right(relative):
raiseValidationError(
"You do not have the permission to move this page here!"
)
returndata@admin.register(models.Page)classPageAdmin(ContentEditor, TreeAdmin):
...
defmove_view(self, request, obj):
returnself.action_form_view(
request, obj, form_class=MyMoveForm, title=f"Move {obj}"
)
Activity
matthiask commentedon May 2, 2024
I don't immediately have a good idea for this. Maybe you could try overriding
MoveForm.__init__and addself.fields["new_location"].choices = _generate_choices(self.get_queryset(self.request).filter(...your...filters...).with_tree_fields())or something like that aftersuper.__init__()?I'm happy to change the code, and I'd also appreciate a pull request if you find a solution yourself!
barsch commentedon May 3, 2024
Alright I tinkered a bit with it and its definitively not trivial to find a good solution especially without a non database heavy way to get the previous or next sibling of any given node during the choice generation.
For now I overwrote
MoveForm._generate_choicesto use three new methodscan_move_root,can_move_rightandcan_move_first, which are used to customize the_generate_choicelogic. To have the original behaviour all three methods should just returnTrue. Those methods are also used in thecleanmethod to prevent any malicious pampering with the move form.One can now use the
self.querysetandself.requestproperties in those 3 methods to limit the move targets. However, I opted for two newBooleanFieldfieldscan_move_rightandcan_move_firstwithin myPagemodel. This way I can exactly define what move targets are allowed for fixed pages without extra database queries or additional logic.Don't know if this is to convoluted/obscure to be included into feincms3 - decision is up to you - I'm happy to create a PR if wanted.
admin.py
models.py