From ca514cd3eda2524b8da472ef17c0ccb216bac2e8 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 5 Jan 2026 05:32:56 +0400 Subject: [PATCH] refac: group share to settings --- backend/open_webui/models/groups.py | 106 +++++++++++++----- backend/open_webui/routers/groups.py | 7 +- .../admin/Users/Groups/General.svelte | 26 +++-- 3 files changed, 102 insertions(+), 37 deletions(-) diff --git a/backend/open_webui/models/groups.py b/backend/open_webui/models/groups.py index 1ceb8f1ec0..053125a6d0 100644 --- a/backend/open_webui/models/groups.py +++ b/backend/open_webui/models/groups.py @@ -159,40 +159,70 @@ class GroupTable: if filter: if "query" in filter: query = query.filter(Group.name.ilike(f"%{filter['query']}%")) - if "member_id" in filter: - query = query.join( - GroupMember, GroupMember.group_id == Group.id - ).filter(GroupMember.user_id == filter["member_id"]) + # When share filter is present, member check is handled in the share logic if "share" in filter: share_value = filter["share"] - json_share = Group.data["config"]["share"].as_boolean() + member_id = filter.get("member_id") + json_share = Group.data["config"]["share"] + json_share_bool = json_share.as_boolean() + json_share_str = json_share.as_string() if share_value: - query = query.filter( - or_( - Group.data.is_(None), - json_share.is_(None), - json_share == True, - ) + # Groups open to anyone: data is null, share is null, or share is true + anyone_can_share = or_( + Group.data.is_(None), + json_share_bool.is_(None), + json_share_bool == True, ) + + if member_id: + # Also include member-only groups where user is a member + member_groups_subq = ( + db.query(GroupMember.group_id) + .filter(GroupMember.user_id == member_id) + .subquery() + ) + members_only_and_is_member = and_( + json_share_str == "members", + Group.id.in_(member_groups_subq), + ) + query = query.filter( + or_(anyone_can_share, members_only_and_is_member) + ) + else: + query = query.filter(anyone_can_share) else: query = query.filter( - and_(Group.data.isnot(None), json_share == False) + and_(Group.data.isnot(None), json_share_bool == False) ) + + else: + # Only apply member_id filter when share filter is NOT present + if "member_id" in filter: + query = query.join( + GroupMember, GroupMember.group_id == Group.id + ).filter(GroupMember.user_id == filter["member_id"]) + groups = query.order_by(Group.updated_at.desc()).all() return [ GroupResponse.model_validate( { **GroupModel.model_validate(group).model_dump(), - "member_count": self.get_group_member_count_by_id(group.id, db=db), + "member_count": self.get_group_member_count_by_id( + group.id, db=db + ), } ) for group in groups ] def search_groups( - self, filter: Optional[dict] = None, skip: int = 0, limit: int = 30, db: Optional[Session] = None + self, + filter: Optional[dict] = None, + skip: int = 0, + limit: int = 30, + db: Optional[Session] = None, ) -> GroupListResponse: with get_db_context(db) as db: query = db.query(Group) @@ -228,7 +258,9 @@ class GroupTable: "total": total, } - def get_groups_by_member_id(self, user_id: str, db: Optional[Session] = None) -> list[GroupModel]: + def get_groups_by_member_id( + self, user_id: str, db: Optional[Session] = None + ) -> list[GroupModel]: with get_db_context(db) as db: return [ GroupModel.model_validate(group) @@ -239,7 +271,9 @@ class GroupTable: .all() ] - def get_group_by_id(self, id: str, db: Optional[Session] = None) -> Optional[GroupModel]: + def get_group_by_id( + self, id: str, db: Optional[Session] = None + ) -> Optional[GroupModel]: try: with get_db_context(db) as db: group = db.query(Group).filter_by(id=id).first() @@ -247,7 +281,9 @@ class GroupTable: except Exception: return None - def get_group_user_ids_by_id(self, id: str, db: Optional[Session] = None) -> Optional[list[str]]: + def get_group_user_ids_by_id( + self, id: str, db: Optional[Session] = None + ) -> Optional[list[str]]: with get_db_context(db) as db: members = ( db.query(GroupMember.user_id).filter(GroupMember.group_id == id).all() @@ -258,7 +294,9 @@ class GroupTable: return [m[0] for m in members] - def get_group_user_ids_by_ids(self, group_ids: list[str], db: Optional[Session] = None) -> dict[str, list[str]]: + def get_group_user_ids_by_ids( + self, group_ids: list[str], db: Optional[Session] = None + ) -> dict[str, list[str]]: with get_db_context(db) as db: members = ( db.query(GroupMember.group_id, GroupMember.user_id) @@ -275,7 +313,9 @@ class GroupTable: return group_user_ids - def set_group_user_ids_by_id(self, group_id: str, user_ids: list[str], db: Optional[Session] = None) -> None: + def set_group_user_ids_by_id( + self, group_id: str, user_ids: list[str], db: Optional[Session] = None + ) -> None: with get_db_context(db) as db: # Delete existing members db.query(GroupMember).filter(GroupMember.group_id == group_id).delete() @@ -296,7 +336,9 @@ class GroupTable: db.add_all(new_members) db.commit() - def get_group_member_count_by_id(self, id: str, db: Optional[Session] = None) -> int: + def get_group_member_count_by_id( + self, id: str, db: Optional[Session] = None + ) -> int: with get_db_context(db) as db: count = ( db.query(func.count(GroupMember.user_id)) @@ -306,7 +348,11 @@ class GroupTable: return count if count else 0 def update_group_by_id( - self, id: str, form_data: GroupUpdateForm, overwrite: bool = False, db: Optional[Session] = None + self, + id: str, + form_data: GroupUpdateForm, + overwrite: bool = False, + db: Optional[Session] = None, ) -> Optional[GroupModel]: try: with get_db_context(db) as db: @@ -341,7 +387,9 @@ class GroupTable: except Exception: return False - def remove_user_from_all_groups(self, user_id: str, db: Optional[Session] = None) -> bool: + def remove_user_from_all_groups( + self, user_id: str, db: Optional[Session] = None + ) -> bool: with get_db_context(db) as db: try: # Find all groups the user belongs to @@ -401,7 +449,9 @@ class GroupTable: continue return new_groups - def sync_groups_by_group_names(self, user_id: str, group_names: list[str], db: Optional[Session] = None) -> bool: + def sync_groups_by_group_names( + self, user_id: str, group_names: list[str], db: Optional[Session] = None + ) -> bool: with get_db_context(db) as db: try: now = int(time.time()) @@ -462,7 +512,10 @@ class GroupTable: return False def add_users_to_group( - self, id: str, user_ids: Optional[list[str]] = None, db: Optional[Session] = None + self, + id: str, + user_ids: Optional[list[str]] = None, + db: Optional[Session] = None, ) -> Optional[GroupModel]: try: with get_db_context(db) as db: @@ -500,7 +553,10 @@ class GroupTable: return None def remove_users_from_group( - self, id: str, user_ids: Optional[list[str]] = None, db: Optional[Session] = None + self, + id: str, + user_ids: Optional[list[str]] = None, + db: Optional[Session] = None, ) -> Optional[GroupModel]: try: with get_db_context(db) as db: diff --git a/backend/open_webui/routers/groups.py b/backend/open_webui/routers/groups.py index f1c567fe81..cc0cb8f5a3 100755 --- a/backend/open_webui/routers/groups.py +++ b/backend/open_webui/routers/groups.py @@ -39,11 +39,12 @@ async def get_groups( ): filter = {} + + # Admins can share to all groups regardless of share setting if user.role != "admin": filter["member_id"] = user.id - - if share is not None: - filter["share"] = share + if share is not None: + filter["share"] = share groups = Groups.get_groups(filter=filter, db=db) diff --git a/src/lib/components/admin/Users/Groups/General.svelte b/src/lib/components/admin/Users/Groups/General.svelte index 745611ec1f..1922f00138 100644 --- a/src/lib/components/admin/Users/Groups/General.svelte +++ b/src/lib/components/admin/Users/Groups/General.svelte @@ -2,7 +2,6 @@ import { getContext } from 'svelte'; import Textarea from '$lib/components/common/Textarea.svelte'; import Tooltip from '$lib/components/common/Tooltip.svelte'; - import Switch from '$lib/components/common/Switch.svelte'; const i18n = getContext('i18n'); @@ -73,21 +72,30 @@
- {$i18n.t('Allow Group Sharing')} + {$i18n.t('Who can share to this group')}
- { - if (data?.config?.share) { - data.config.share = e.detail; + const value = e.target.value; + let shareValue; + if (value === 'false') { + shareValue = false; + } else if (value === 'true') { + shareValue = true; } else { - data.config = { ...(data?.config ?? {}), share: e.detail }; + shareValue = value; } + data.config = { ...(data?.config ?? {}), share: shareValue }; }} - /> + > + + + +