拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Wagtail 定制指南:扩展 Group ViewSet 自定义群组编辑/创建表单与权限排序

Wagtail 定制指南扩展 Group ViewSet 自定义群组编辑/创建表单与权限排序【免费下载链接】wagtailA Django content management system focused on flexibility and user experience项目地址: https://gitcode.com/GitHub_Trending/wa/wagtail本文以 Wagtail 官方文档Customizing group edit/create views为核心系统讲解如何通过继承GroupViewSet与GroupForm来扩展 Wagtail 管理后台的群组Group编辑/创建界面以及如何通过permission_order.register控制群组编辑器中权限条目的显示顺序。读完后你将掌握完整的四步定制链路模型 → 表单 → 视图集 → 模板 → AppConfig 注册并能结合源码理解权限面板、表单保存保护机制和权限排序底层的实现原理。群组管理界面的架构Viewset 作为统一入口Wagtail 将群组管理的所有视图列表、创建、编辑、删除聚合在一个viewset类中它作为这些视图所有共享组件如表单、模板前缀、菜单配置的单一参考点。通过子类化该 viewset可以覆盖这些组件从而定制群组管理界面的行为。默认实现位于 wagtail/users/views/groups.pyclass GroupViewSet(ModelViewSet): icon group model Group ordering [name] add_to_reference_index False menu_name groups menu_label _(Groups) menu_order 601 add_to_settings_menu True index_view_class IndexView add_view_class CreateView edit_view_class EditView delete_view_class DeleteView # Copying requires prepopulating the formsets with copies of the existing # permissions, which is not currently supported. copy_view_enabled False template_prefix wagtailusers/groups/ def get_form_class(self, for_updateFalse): return GroupForm从源码结构看几个关键点值得注意GroupViewSet是ModelViewSet的子类见 wagtail/admin/viewsets/model.py因此它支持通用视图文档中列出的大多数定制能力例如菜单、列表、图标等可参考 通用视图文档template_prefix wagtailusers/groups/决定了模板查找目录自定义模板必须放在该命名空间下例如覆盖wagtailusers/groups/edit.htmlget_form_class是表单定制的核心挂载点默认返回GroupForm创建/编辑视图都混入了 PermissionPanelFormsMixin它通过register_group_permission_panelhook 收集所有权限面板默认含GroupPagePermissionFormSet页面权限表单集并在process_form中统一校验与保存。默认表单GroupForm定义在 wagtail/users/forms.py。它是针对 DjangoGroup模型的ModelForm默认字段为name与permissionsclass GroupForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.registered_permissions Permission.objects.none() for fn in hooks.get_hooks(register_permissions): self.registered_permissions self.registered_permissions | fn() self.fields[ permissions ].queryset self.registered_permissions.select_related(content_type) class Meta: model Group fields ( name, permissions, ) widgets { permissions: forms.CheckboxSelectMultiple(), } def save(self, commitTrue): # We go back to the object to read (in order to reapply) the # permissions which were set on this group, but which are # not accessible in the wagtail admin interface, as otherwise # these would be clobbered by this form. try: untouchable_permissions self.instance.permissions.exclude( pk__inself.registered_permissions ) ... group super().save(commitcommit) group.permissions.add(*untouchable_permissions) return group两个值得注意的底层机制权限 queryset 来自 hookpermissions字段的可选项并非Permission全表而是通过遍历register_permissionshook 注册的 queryset 并集构建的。Wagtail 自身通过 wagtail/permissions.py 等模块注册各模型的权限第三方应用也可以注册自己的权限保存时的不可触碰权限保护save()会找出该组已有、但不在 Wagtail 界面可编辑范围内的权限untouchable_permissions并在表单保存后重新挂回避免覆盖其他系统如 Django 原生权限直接写入的权限记录。子类化GroupForm并覆写save()时务必先调用super().save()以保留这一保护逻辑。实战在群组编辑/创建视图中添加自定义字段下面以官方文档中的场景为例需要将 Active Directory 组与 Django 组关联。由于 Wagtail 群组编辑/创建视图中没有 role 字段我们要通过继承GroupForm增加该字段。第 1 步定义关联模型# myapp/models.py from django.contrib.auth.models import Group from django.db import models class ADGroup(models.Model): guid models.CharField( verbose_nameGUID, max_length64, db_indexTrue, uniqueTrue ) name models.CharField(verbose_nameGroup, max_length255) domain models.CharField(verbose_nameDomain, max_length255, db_indexTrue) description models.TextField(verbose_nameDescription, blankTrue, nullTrue) roles models.ManyToManyField( Group, verbose_nameRole, related_nameadgroups, blankTrue ) class Meta: verbose_name AD group verbose_name_plural AD groups第 2 步继承 GroupForm 并添加新字段# myapp/forms.py from django import forms from wagtail.users.forms import GroupForm as WagtailGroupForm from .models import ADGroup class GroupForm(WagtailGroupForm): adgroups forms.ModelMultipleChoiceField( labelAD groups, requiredFalse, querysetADGroup.objects.order_by(name), ) class Meta(WagtailGroupForm.Meta): fields WagtailGroupForm.Meta.fields (adgroups,) def __init__(self, initialNone, instanceNone, **kwargs): if instance is not None: if initial is None: initial {} initial[adgroups] instance.adgroups.all() super().__init__(initialinitial, instanceinstance, **kwargs) def save(self, commitTrue): instance super().save() instance.adgroups.set(self.cleaned_data[adgroups]) return instance这里的几个细节保证了表单在创建与编辑两种场景下的正确性__init__中当存在instance编辑场景时把instance.adgroups.all()注入initial让多选框回显已关联的 AD 组创建场景instance为空则跳过save()先调用super().save()复用GroupForm的权限保护逻辑保存 Group 本体再用instance.adgroups.set(...)更新多对多关系。由于ADGroup.roles是ManyToManyField多对多的写入必须在主对象保存之后进行这与 Django 模型表单中 M2M 字段的保存时序一致。第 3 步子类化 GroupViewSet 覆写 get_form_class# myapp/viewsets.py from wagtail.users.views.groups import GroupViewSet as WagtailGroupViewSet from .forms import GroupForm class GroupViewSet(WagtailGroupViewSet): def get_form_class(self, for_updateFalse): return GroupFormget_form_class会被创建与编辑视图调用见 wagtail/admin/views/generic/models.py 中form_class self.get_form_class()的用法因此一处覆写即可同时作用于两个视图。第 4 步在模板中渲染新字段新字段需要在群组编辑/创建模板中显式渲染。Wagtail 的群组表单模板预留了extra_fields块见 wagtail/users/templates/wagtailusers/groups/create.html{% extends wagtailusers/groups/edit.html %} {% load wagtailusers_tags wagtailadmin_tags i18n %} {% block extra_fields %} li{% include wagtailadmin/shared/field.html with fieldform.adgroups %}/li {% endblock extra_fields %}从模板源码看create.html在{% block fields %}内、权限面板format_permissions与permission_panels循环之前输出{% block extra_fields %}且该块有注释明确说明此块用于群组视图定制的文档而 edit.html 直接继承自create.html两个模板内容已完全一致因此自定义模板继承任一即可同时覆盖两个视图。自定义模板应放在你项目模板目录下同名路径如wagtailusers/groups/edit.html依赖 Django 模板加载器的优先级覆盖内置模板。第 5 步通过自定义 AppConfig 接入 viewset最后通过自定义AppConfig让wagtail.users应用使用你的 viewset。在你的项目包包含顶层settings和urls模块的包中创建apps.py若不存在# myproject/apps.py from wagtail.users.apps import WagtailUsersAppConfig class CustomUsersAppConfig(WagtailUsersAppConfig): group_viewset myapp.viewsets.GroupViewSet然后在settings.INSTALLED_APPS中用CustomUsersAppConfig替换wagtail.usersINSTALLED_APPS [ ..., myproject.apps.CustomUsersAppConfig, # wagtail.users, ..., ]这一机制的依据是 wagtail/users/apps.py默认的WagtailUsersAppConfig本身就定义了group_viewset与user_viewset两个字符串属性指向内置 viewset 类class WagtailUsersAppConfig(AppConfig): name wagtail.users label wagtailusers ... group_viewset wagtail.users.views.groups.GroupViewSet user_viewset wagtail.users.views.users.UserViewSet由于AppConfig子类中的类属性会覆盖父类定义将group_viewset替换为app_label.路径式的模型字符串即可完成切换无需改动 URL 路由。定制群组编辑器中的权限条目排序除了自定义表单字段还可以配置群组编辑器中 Object permissions对象权限与 Other permissions其他权限两个区块内各对象类型的显示顺序。方法是在一个或多个AppConfig的ready()中调用register注册顺序值。顺序值通常是 0999 之间的整数但并不强制约束。from django.apps import AppConfig class MyProjectAdminAppConfig(AppConfig): name myproject_admin verbose_name My Project Admin def ready(self): from wagtail.users.permission_order import register register(gadgets.SprocketType, order150) register(gadgets.ChainType, order151) register(site_settings.Settings, order160)register()的第一个参数既可以是app_label.ModelName字符串也可以直接传入模型类。排序机制的源码实现注册逻辑位于 wagtail/users/permission_order.pycontent_types_to_register [] CONTENT_TYPE_ORDER {} def register(model, **kwargs): order kwargs.pop(order, None) if order is not None: # We typically call this at application startup, when the database may not be ready, # and so we cant look up the content type yet. Instead we will queue up the # (model, order) pair to be processed when the lookup is requested. content_types_to_register.append((model, order)) def get_content_type_order_lookup(): if content_types_to_register: for model, order in content_types_to_register: content_type ContentType.objects.get_for_model( resolve_model_string(model) ) CONTENT_TYPE_ORDER[content_type.id] order content_types_to_register.clear() return CONTENT_TYPE_ORDER从源码结构看register()在应用启动期数据库可能尚未就绪并不立即解析模型而是将(model, order)对放入content_types_to_register队列只有当get_content_type_order_lookup()首次被请求时渲染权限面板时才通过resolve_model_stringContentType.objects.get_for_model惰性解析为 content type ID 并缓存到CONTENT_TYPE_ORDER解析完成后清空队列。真正影响界面顺序的是权限格式化模板标签format_permissionswagtail/users/templatetags/wagtailusers_tags.pycontent_type_order get_content_type_order_lookup() content_type_ids sorted( dict.fromkeys(permissions.values_list(content_type_id, flatTrue)), keylambda ct: content_type_order.get(ct, float(inf)), )即所有 content type ID 按注册的 order 值升序排列未注册顺序的对象类型取float(inf)因此排在所有已注册类型之后。对于同为inf的未注册类型由于 Python 的sorted()是稳定排序它们保留原有 queryset 顺序——permissionsqueryset 依赖Permission模型的默认排序按app_label、model字母序这与文档所述未显式指定顺序的对象类型按app_label和model字母序排列完全对应。仓库内的测试用例也验证了这一行为见 wagtail/users/tests/test_admin_views.py通过register_permission_order(snippetstests.fancysnippet, order100)与order110注册顺序后断言权限在页面上的先后位置再重新注册order90验证顺序可以改变。小结与延伸阅读本文覆盖的完整定制路径可归纳为加字段继承 GroupForm在__init__回显、save()落库多对多关系挂表单子类化 GroupViewSet覆写get_form_class渲染字段继承群组模板并填充extra_fields块注册视图集自定义AppConfig覆写group_viewset字符串属性并替换INSTALLED_APPS条目排序权限在AppConfig.ready()中调用 permission_order.register。用户User的表单与视图可以采用相同的思路定制自定义user_viewset属性相关说明见 自定义用户模型文档。由于GroupViewSet是ModelViewSet子类列表、菜单、图标等更通用的定制能力可进一步参考 通用视图文档。【免费下载链接】wagtailA Django content management system focused on flexibility and user experience项目地址: https://gitcode.com/GitHub_Trending/wa/wagtail创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门