user_groups: Add can_mention_group setting.

This commit adds a new can_mention_group setting which will be
used to determine who can mention a particular group.

Fixes a part of #25927.
This commit is contained in:
Sahil Batra 2023-06-12 16:57:47 +05:30 committed by Tim Abbott
parent 9265954fd2
commit 2763f9b575
11 changed files with 170 additions and 3 deletions

View File

@ -7,7 +7,11 @@ from django.utils.timezone import now as timezone_now
from django.utils.translation import gettext as _
from zerver.lib.exceptions import JsonableError
from zerver.lib.user_groups import access_user_group_by_id
from zerver.lib.user_groups import (
access_user_group_by_id,
get_role_based_system_groups_dict,
set_defaults_for_group_settings,
)
from zerver.models import (
GroupGroupMembership,
Realm,
@ -35,9 +39,14 @@ def create_user_group_in_database(
description: str = "",
is_system_group: bool = False,
) -> UserGroup:
user_group = UserGroup.objects.create(
user_group = UserGroup(
name=name, realm=realm, description=description, is_system_group=is_system_group
)
system_groups_name_dict = get_role_based_system_groups_dict(realm)
user_group = set_defaults_for_group_settings(user_group, system_groups_name_dict)
user_group.save()
UserGroupMembership.objects.bulk_create(
UserGroupMembership(user_profile=member, user_group=user_group) for member in members
)

View File

@ -969,6 +969,10 @@ def do_import_realm(import_dir: Path, subdomain: str, processes: int = 1) -> Rea
if "zerver_usergroup" in data:
update_model_ids(UserGroup, data, "usergroup")
re_map_foreign_keys(data, "zerver_usergroup", "realm", related_table="realm")
for setting_name in UserGroup.GROUP_PERMISSION_SETTINGS:
re_map_foreign_keys(
data, "zerver_usergroup", setting_name, related_table="usergroup"
)
bulk_import_model(data, UserGroup)
# We expect Zulip server exports to contain these system groups,

View File

@ -281,3 +281,4 @@ class GroupPermissionSetting:
allow_owners_group: bool
allow_nobody_group: bool
default_group_name: str
default_for_system_groups: Optional[str] = None

View File

@ -226,6 +226,31 @@ def get_recursive_subgroups_for_groups(user_group_ids: List[int]) -> List[int]:
return list(recursive_subgroups.values_list("id", flat=True))
def get_role_based_system_groups_dict(realm: Realm) -> Dict[str, UserGroup]:
system_groups = UserGroup.objects.filter(realm=realm, is_system_group=True)
system_groups_name_dict = {}
for group in system_groups:
system_groups_name_dict[group.name] = group
return system_groups_name_dict
def set_defaults_for_group_settings(
user_group: UserGroup,
system_groups_name_dict: Dict[str, UserGroup],
) -> UserGroup:
for setting_name, permission_config in UserGroup.GROUP_PERMISSION_SETTINGS.items():
if user_group.is_system_group and permission_config.default_for_system_groups is not None:
default_group_name = permission_config.default_for_system_groups
else:
default_group_name = permission_config.default_group_name
default_group = system_groups_name_dict[default_group_name]
setattr(user_group, setting_name, default_group)
return user_group
@transaction.atomic(savepoint=False)
def create_system_user_groups_for_realm(realm: Realm) -> Dict[int, UserGroup]:
"""Any changes to this function likely require a migration to adjust
@ -233,6 +258,12 @@ def create_system_user_groups_for_realm(realm: Realm) -> Dict[int, UserGroup]:
which is a copy of this function from when we introduced system groups.
"""
role_system_groups_dict: Dict[int, UserGroup] = {}
# This value will be used to set the temporary initial value for different
# settings since we can only set them to the correct values after the groups
# are created.
initial_group_setting_value = -1
for role in UserGroup.SYSTEM_USER_GROUP_ROLE_MAP:
user_group_params = UserGroup.SYSTEM_USER_GROUP_ROLE_MAP[role]
user_group = UserGroup(
@ -240,6 +271,7 @@ def create_system_user_groups_for_realm(realm: Realm) -> Dict[int, UserGroup]:
description=user_group_params["description"],
realm=realm,
is_system_group=True,
can_mention_group_id=initial_group_setting_value,
)
role_system_groups_dict[role] = user_group
@ -248,18 +280,21 @@ def create_system_user_groups_for_realm(realm: Realm) -> Dict[int, UserGroup]:
description="Members of this organization, not including new accounts and guests",
realm=realm,
is_system_group=True,
can_mention_group_id=initial_group_setting_value,
)
everyone_on_internet_system_group = UserGroup(
name=UserGroup.EVERYONE_ON_INTERNET_GROUP_NAME,
description="Everyone on the Internet",
realm=realm,
is_system_group=True,
can_mention_group_id=initial_group_setting_value,
)
nobody_system_group = UserGroup(
name=UserGroup.NOBODY_GROUP_NAME,
description="Nobody",
realm=realm,
is_system_group=True,
can_mention_group_id=initial_group_setting_value,
)
# Order of this list here is important to create correct GroupGroupMembership objects
system_user_groups_list = [
@ -275,6 +310,13 @@ def create_system_user_groups_for_realm(realm: Realm) -> Dict[int, UserGroup]:
UserGroup.objects.bulk_create(system_user_groups_list)
groups_with_updated_settings = []
system_groups_name_dict = get_role_based_system_groups_dict(realm)
for group in system_user_groups_list:
user_group = set_defaults_for_group_settings(group, system_groups_name_dict)
groups_with_updated_settings.append(group)
UserGroup.objects.bulk_update(groups_with_updated_settings, ["can_mention_group"])
subgroup_objects = []
# "Nobody" system group is not a subgroup of any user group, since it is already empty.
subgroup, remaining_groups = system_user_groups_list[1], system_user_groups_list[2:]

View File

@ -0,0 +1,20 @@
# Generated by Django 4.2.1 on 2023-06-12 10:47
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("zerver", "0453_followed_topic_notifications"),
]
operations = [
migrations.AddField(
model_name="usergroup",
name="can_mention_group",
field=models.ForeignKey(
null=True, on_delete=django.db.models.deletion.RESTRICT, to="zerver.usergroup"
),
),
]

View File

@ -0,0 +1,44 @@
# Generated by Django 4.2.1 on 2023-06-12 10:47
from django.db import migrations
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from django.db.migrations.state import StateApps
def set_default_value_for_can_mention_group(
apps: StateApps, schema_editor: BaseDatabaseSchemaEditor
) -> None:
Realm = apps.get_model("zerver", "Realm")
UserGroup = apps.get_model("zerver", "UserGroup")
groups_to_update = []
for realm in Realm.objects.all():
# The default value of `can_mention_group` is everyone group for
# all groups except role-based system groups. For role-based system
# groups, we set the value of `can_mention_group` to nobody group.
nobody_group = UserGroup.objects.get(name="@role:nobody", realm=realm, is_system_group=True)
everyone_group = UserGroup.objects.get(
name="@role:everyone", realm=realm, is_system_group=True
)
for group in UserGroup.objects.filter(realm=realm):
if group.is_system_group:
group.can_mention_group = nobody_group
else:
group.can_mention_group = everyone_group
groups_to_update.append(group)
UserGroup.objects.bulk_update(groups_to_update, ["can_mention_group"])
class Migration(migrations.Migration):
dependencies = [
("zerver", "0454_usergroup_can_mention_group"),
]
operations = [
migrations.RunPython(
set_default_value_for_can_mention_group,
elidable=True,
reverse_code=migrations.RunPython.noop,
)
]

View File

@ -0,0 +1,20 @@
# Generated by Django 4.2.1 on 2023-06-12 10:58
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("zerver", "0455_set_default_for_can_mention_group"),
]
operations = [
migrations.AlterField(
model_name="usergroup",
name="can_mention_group",
field=models.ForeignKey(
on_delete=django.db.models.deletion.RESTRICT, to="zerver.usergroup"
),
),
]

View File

@ -2256,6 +2256,8 @@ class UserGroup(models.Model): # type: ignore[django-manager-missing] # django-
description = models.TextField(default="")
is_system_group = models.BooleanField(default=False)
can_mention_group = models.ForeignKey("self", on_delete=models.RESTRICT)
# Names for system groups.
FULL_MEMBERS_GROUP_NAME = "@role:fullmembers"
EVERYONE_ON_INTERNET_GROUP_NAME = "@role:internet"
@ -2292,6 +2294,17 @@ class UserGroup(models.Model): # type: ignore[django-manager-missing] # django-
},
}
GROUP_PERMISSION_SETTINGS = {
"can_mention_group": GroupPermissionSetting(
require_system_group=False,
allow_internet_group=False,
allow_owners_group=False,
allow_nobody_group=True,
default_group_name=EVERYONE_GROUP_NAME,
default_for_system_groups=NOBODY_GROUP_NAME,
),
}
class Meta:
unique_together = (("realm", "name"),)

View File

@ -263,7 +263,9 @@ def create_user_group_data() -> Dict[str, object]:
["/user_groups/{user_group_id}:patch", "/user_groups/{user_group_id}:delete"]
)
def get_temp_user_group_id() -> Dict[str, object]:
user_group, _ = UserGroup.objects.get_or_create(name="temp", realm=get_realm("zulip"))
user_group, _ = UserGroup.objects.get_or_create(
name="temp", realm=get_realm("zulip"), can_mention_group_id=11
)
return {
"user_group_id": user_group.id,
}

View File

@ -1231,6 +1231,11 @@ class RealmImportExportTest(ExportFile):
direct_subgroup_names = {group.name for group in direct_subgroups}
return direct_subgroup_names
@getter
def get_user_group_can_mention_group_setting(r: Realm) -> Set[str]:
user_group = UserGroup.objects.get(realm=r, name="hamletcharacters")
return user_group.can_mention_group.name
# test botstoragedata and botconfigdata
@getter
def get_botstoragedata(r: Realm) -> Dict[str, object]:

View File

@ -240,6 +240,13 @@ class UserGroupAPITestCase(UserGroupTestCase):
self.assert_json_success(result)
self.assert_length(UserGroup.objects.filter(realm=hamlet.realm), 10)
# Check default value of can_mention_group setting.
everyone_system_group = UserGroup.objects.get(
name="@role:everyone", realm=hamlet.realm, is_system_group=True
)
support_group = UserGroup.objects.get(name="support", realm=hamlet.realm)
self.assertEqual(support_group.can_mention_group, everyone_system_group)
# Test invalid member error
params = {
"name": "backend",