OpenProject 用户属性(User attributes)管理完全指南:扩展用户档案、自定义字段与映射配置
【免费下载链接】openprojectOpenProject is the leading open source project management software for product, project and portfolio management. A powerful Jira alternative with agile planning, issue tracking, roadmaps, Gantt charts, time tracking, collaboration features, and more. Available on premises or in the cloud. ⭐ Star us on GitHub项目地址: https://gitcode.com/GitHub_Trending/op/openproject
User attributes(用户属性)是 OpenProject 提供的一项管理能力,允许管理员在标准用户档案之外扩展额外的信息维度,例如职位头衔(Job title)、技能、证书或使用语言。这些属性可以显示在用户档案、用户悬浮卡片(hover card)以及资源管理模块的用户卡片上。本文将以官方系统管理指南为主体,结合仓库源码深入讲解如何查看、创建、分组、编辑与删除用户属性,并详解 Mapped attributes(映射属性)的配置原理,帮助管理员与二次开发者完整掌握这一功能模块。
一、什么是用户属性:内置属性与自定义属性
在 OpenProject 中,用户属性本质上是一种面向用户(User)实体、作用域为全局的自定义字段(Custom Field)。从源码结构看,它的实现模型为UserCustomField,该类继承自核心的CustomField,并混入了CustomField::Sectionable(分节能力)模块,同时通过belongs_to :user_custom_field_section与分节模型UserCustomFieldSection建立关联。
内置属性
OpenProject 提供以下不可删除的内置用户属性:
- Username(用户名)
- First name(名字)
- Last name(姓氏)
- Email(电子邮箱)
- Language(语言)
- Department(部门)
在源码中,这些内置属性由UserCustomFieldSection::BUILT_IN_ATTRIBUTES常量直接定义:%w[login firstname lastname mail language department]。内置属性只能调整其在列表中的顺序(上移、下移、置顶、置底),不能编辑或删除。
自定义属性
你还可以创建任意数量的自定义用户属性,例如:
- Job title(职位头衔)
- Spoken languages(使用语言)
- Key skills(关键技能)
- Job start date(入职日期)
自定义属性除排序外,还支持完整的编辑与删除操作。
二、查看用户属性列表
进入管理后台:Administration settings(管理设置)→ Users and permissions(用户与权限)→ User attributes(用户属性)。
在该页面可以:
- 创建与管理自定义用户属性
- 将属性分组到不同的 Section(分区)中
- 选择哪些属性显示在用户卡片上
从源码角度看,该页面由Admin::Settings::UserCustomFieldsController#index提供支撑,其对应的系统测试位于 spec/features/admin/custom_fields/users/index_spec.rb,测试中验证了非管理员无法访问该页面(返回 "You are not authorized to access this page."),并校验了分区与属性的显示顺序。
用户属性页面分为两个标签页:
- Attributes(属性):列出所有已配置的用户属性,属性被组织进各个 Section(分区)中
- Mapped attributes(映射属性):将特定的语义用途(如 Job title)绑定到某个自定义属性上
三、Attributes 标签页:属性与分区管理
3.1 属性行的组成
在Attributes标签页中,每个属性行包含:
- 拖拽手柄(Drag handle)
- 用户属性名称
- 格式(Format)
- More(…)菜单图标
3.2 创建与管理分区(Sections)
分区用于将属性分组展示。添加分区的步骤:
- 点击右上角的+ Add按钮
- 选择Section
随后为分区命名并保存。
- 通过分区标题右侧的More菜单,可以对分区执行重命名、删除或重新排序
- 属性可以在分区之间拖拽移动;整个分区也可以借助分区名左侧的拖拽手柄进行拖拽排序
[!TIP] 分区只有在不包含任何属性时才能被删除。
[!TIP] 属性在所有用户档案中始终显示在它被分配到的分区内。
从源码层看,分区的增删改排序分别由UserCustomFieldSectionsController的create、update、destroy、move、drop动作驱动,底层调用UserCustomFieldSections目录下的CreateService、UpdateService、DeleteService等服务对象,并通过 Turbo Stream 无刷新更新页面。其中DeleteService继承自::BaseServices::Delete(delete_service.rb);而模型层has_many :custom_fields, dependent: :restrict_with_exception(user_custom_field_section.rb)保证了"非空分区禁止删除"这一约束。功能测试 index_spec.rb 亦明确验证了:分区下仍有属性时删除按钮被禁用(aria-disabled='true'),清空属性后才可以删除。
3.3 用户属性格式(User attribute formats)
创建用户属性时,需要从以下格式中选择一种:
| 格式 | 说明 |
|---|---|
| Boolean | 布尔值,以复选框形式呈现,勾选或不勾选 |
| Date | 日期,通过日期选择器(date picker)选取 |
| Float | 有理数(小数) |
| Hierarchy(企业版附加组件) | 层级结构选择,可选择一个或多个层级列表项;结构在属性创建后的Items标签页中维护 |
| Integer | 整数 |
| List | 平面列表选项 |
| Text | 文本格式,可限制长度 |
| Long text | 长文本,适用于需要输入较长内容的场景 |
不同格式会提供不同的附加参数,例如最小/最大宽度、默认值或用于校验的正则表达式。
[!IMPORTANT] 用户属性一旦创建,无法更改其格式。
3.4 创建用户属性
创建新属性的步骤:
- 点击右上角+ Add按钮
- 选择User attribute
- 从可用格式列表中选择一种用户属性格式
以下是一个List格式的用户属性创建示例:
表单中的关键配置项:
- Name(名称):用户属性在用户档案及其他展示位置显示的名称
- Section(分区):如果已创建分区,可选择该属性归属的分区
- Allow multi-select(允许多选):允许用户为该字段赋予多个值
- Required(必填):勾选后启用该属性并使其对所有用户为必填;无法在单个用户层面取消激活。已有用户在进行更新时不会被强制要求补填该值
- Admin-only(仅管理员可见):启用后该属性仅对管理员可见,其他用户看不到
- Editable(可编辑):允许用户自行编辑该属性
- Show on user card(显示在用户卡片上):在用户悬浮卡片(悬停于用户名上时显示的信息)中展示该属性
[!IMPORTANT]Boolean类型的用户属性不能被设置为必填。
属性创建完成后,还可以继续为其定义帮助文本以及(针对 List/Hierarchy 等格式)维护选项列表。
3.5 Hierarchy 层级用户属性(企业版附加组件)
类型为Hierarchy的用户属性,其行为与Hierarchy类型的工作包自定义字段完全一致。详细信息请参阅工作包自定义字段文档。该功能受特性开关custom_field_hierarchies控制(对应企业版附加组件),其选项结构由Admin::Settings::UserCustomFields::Hierarchy::ItemsController管理。
3.6 编辑、排序与删除用户属性
编辑自定义属性:
- 选择More(…)菜单
- 选择Edit
- 按需更新属性设置
重新排序用户属性:属性按照本页面配置的顺序显示。通过属性旁的More(…)菜单可以选择:
- Move to top(移至顶部)
- Move up(上移)
- Move down(下移)
- Move to bottom(移至底部)
源码层面,排序动作由UserCustomFieldsController#move(调用CustomFields::MoveService)与#drop(调用CustomFields::DropService,支持跨分区拖放)实现;分区内的字段顺序由attribute_order与分区的position共同驱动(参见UserCustomFieldSection.with_filled_fields_for中按attribute_order过滤排序的逻辑)。
删除用户属性:只有自定义用户属性可以被删除。
- 选择More(…)菜单
- 选择Delete
- 确认删除
删除操作会将该属性从所有用户档案中永久移除。
3.7 定义用户属性帮助文本
点击某个用户属性,进入Help text标签页,可以为属性定义:
- Caption(标题):一段短文本,作为属性标题展示以提供上下文
- Help text(帮助文本):一段较长文本,当用户悬停在属性名旁的问号图标上时显示,用于提供更详细的解释。这是必填字段
- Attachments(附件):附加文件或图片以图解该用户属性
[!IMPORTANT] 此处添加的任何文本与附件,对所有已登录用户均可见。
帮助文本与附件功能由AttributeHelpTextActions等共享 Concern 提供支撑,对应控制器动作attribute_help_text/update_attribute_help_text(user_custom_fields_controller.rb),并有专门的系统测试 spec/features/admin/settings/user_custom_fields/attribute_help_text_spec.rb 覆盖。
四、Mapped attributes 标签页:为属性赋予语义用途
Mapped attributes(映射属性)标签页允许你将特定的语义用途(Purpose)指派给某个自定义用户属性。
映射规则:
- 每个用途(Purpose)只能指派给一个用户属性
- 每个用户属性只能映射到一个用途
当前 OpenProject 支持的映射属性如下:
| 用途 | 说明 |
|---|---|
| Job title(职位头衔) | 在多个位置(如用户卡片)将每个用户的职位头衔显示在其姓名旁 |
映射操作步骤:
- 打开Mapped attributes标签页
- 从Job title下拉列表中选择一个自定义用户属性
- 点击Save
[!NOTE] 只有自定义用户属性可以被映射,内置属性不在可选范围内。
映射机制的源码实现
从源码结构看,"映射"在数据层由UserCustomField模型上的semantic_key字段承载:
enum :semantic_key, { job_title: "job_title" }, prefix: true validates :semantic_key, uniqueness: { scope: :type }, allow_nil: true scope :with_semantic_key, ->(semantic_key) { where(semantic_key:) } def self.for_semantic_key(semantic_key) with_semantic_key(semantic_key).first endsemantic_key是一个枚举字段,目前仅定义了job_title一个值,prefix: true会生成semantic_key_job_title形式的辅助方法uniqueness: { scope: :type }唯一性校验,正是"每个用途只能映射一个用户属性"这一规则在数据库层面的保证
管理端界面由UserCustomFieldsController#index在tab == "semantic_keys"时加载:
if params[:tab] == "semantic_keys" @semantic_key_field_options = UserCustomField.order(:position) @semantic_key_assignments = UserCustomField.where.not(semantic_key: nil).index_by(&:semantic_key) end保存映射则走update_semantic_keys动作(user_custom_fields_controller.rb),其内部assign_semantic_keys在事务中先将原持有者清空、再写入新的映射,以始终满足(type, semantic_key)的唯一索引约束。
五、属性的显示与消费:悬浮卡片与用户档案
用户属性配置完成后,会通过以下途径在系统中展示:
- 用户档案(User profile):属性按所属分区、分区内按顺序渲染
- 用户悬浮卡片(Hover card):鼠标悬停用户名时弹出,仅显示勾选了Show on user card且已有值的属性
- 资源管理模块的用户卡片:参考资源管理用户指南
从源码看,悬浮卡片的渲染由Users::HoverCardComponent完成,其核心调用为:
def card_sections_with_fields @card_sections_with_fields ||= UserCustomFieldSection.with_filled_fields_for(@user, visible_on_user_card: true) end对应的模型查询方法UserCustomFieldSection.with_filled_fields_for做了三件关键事情:
- 仅取当前用户可见(
UserCustomField.visible(User.current))的属性; - 可选地只保留
visible_on_user_card: true(即勾选了"显示在用户卡片上")的属性; - 通过
user.custom_values.where.not(value: [nil, ""])过滤出已填写值的属性,再按分区与attribute_order排序输出——因此悬浮卡片上不会出现空属性占位。
多值字段在卡片上以 Primer Label 标签形式渲染,超出上限时显示 "and N more" 溢出提示(hover_card_component.rb)。相关测试位于 spec/components/users/hover_card_component_spec.rb 与 spec/components/users/profile/attributes_section_component_spec.rb。
六、常见问题与注意事项汇总
- 格式不可变:用户属性创建后无法修改格式,创建前需确认所选格式。
- 内置属性不可删除:内置属性(Username、First name、Last name、Email、Language、Department)只能排序,不能编辑或删除。
- 非空分区不可删除:删除分区前必须先将其中所有属性移走或删除(
dependent: :restrict_with_exception约束 + 前端禁用双重保障)。 - Boolean 不可设为必填:布尔型属性存在此限制。
- 必填不追溯:将属性设为 Required 后,已有用户更新时不会被强制要求补填。
- 映射互斥:每个 Purpose 只能绑定一个自定义属性,反之亦然;映射仅针对自定义属性。
- 可见性边界:Admin-only 属性仅管理员可见;帮助文本与附件对所有已登录用户可见,发布前请注意内容敏感性。
七、相关文档与代码入口
- 官方文档:用户属性(本指南)
- 关联文档:工作包自定义字段(含 Hierarchy 类型)
- 模型实现:app/models/user_custom_field.rb、app/models/user_custom_field_section.rb
- 控制器实现:app/controllers/admin/settings/user_custom_fields_controller.rb、app/controllers/admin/settings/user_custom_field_sections_controller.rb
- 服务层:app/services/user_custom_field_sections/
- 组件层:app/components/users/hover_card_component.rb
- 测试用例:spec/features/admin/custom_fields/users/index_spec.rb、spec/features/admin/settings/user_custom_fields/attribute_help_text_spec.rb、spec/components/users/hover_card_component_spec.rb
通过本文,管理员可以在 OpenProject 中为团队构建丰富且结构化的用户档案体系:用分区组织属性、用多种格式适配数据、用帮助文本降低填写歧义,并通过 Mapped attributes 让自定义属性承担系统级语义(如 Job title),从而在用户卡片与档案中呈现统一的个人信息视图。
【免费下载链接】openprojectOpenProject is the leading open source project management software for product, project and portfolio management. A powerful Jira alternative with agile planning, issue tracking, roadmaps, Gantt charts, time tracking, collaboration features, and more. Available on premises or in the cloud. ⭐ Star us on GitHub项目地址: https://gitcode.com/GitHub_Trending/op/openproject
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考