mirror of https://github.com/zulip/zulip.git
user_groups: Add `creator` and `date_created` field in user groups.
This commit introduced 'creator' and 'date_created' fields in user groups, allowing users to view who created the groups and when. Both fields can be null for groups without creator data.
This commit is contained in:
parent
3b69f2e5d9
commit
614caf111e
|
@ -20,6 +20,13 @@ format used by the Zulip server that they are interacting with.
|
||||||
|
|
||||||
## Changes in Zulip 10.0
|
## Changes in Zulip 10.0
|
||||||
|
|
||||||
|
**Feature level 292**
|
||||||
|
|
||||||
|
* [`POST /register`](/api/register-queue), [`GET
|
||||||
|
/events`](/api/get-events), [`GET
|
||||||
|
/user_groups`](/api/get-user-groups): Added `creator_id` and
|
||||||
|
`date_created` fields to user groups objects.
|
||||||
|
|
||||||
**Feature level 291**
|
**Feature level 291**
|
||||||
|
|
||||||
* `PATCH /realm`, [`GET /events`](/api/get-events),
|
* `PATCH /realm`, [`GET /events`](/api/get-events),
|
||||||
|
|
|
@ -34,8 +34,7 @@ DESKTOP_WARNING_VERSION = "5.9.3"
|
||||||
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
||||||
# entries in the endpoint's documentation in `zulip.yaml`.
|
# entries in the endpoint's documentation in `zulip.yaml`.
|
||||||
|
|
||||||
|
API_FEATURE_LEVEL = 292 # Last bumped for `namedusergroup_creator_date_created`.
|
||||||
API_FEATURE_LEVEL = 291 # Last bumped for can_delete_own_message_group
|
|
||||||
|
|
||||||
# Bump the minor PROVISION_VERSION to indicate that folks should provision
|
# Bump the minor PROVISION_VERSION to indicate that folks should provision
|
||||||
# only when going from an old version of the code to a newer version. Bump
|
# only when going from an old version of the code to a newer version. Bump
|
||||||
|
|
|
@ -129,6 +129,8 @@ export const realm_emoji_map_schema = z.record(server_emoji_schema);
|
||||||
export const user_group_schema = z.object({
|
export const user_group_schema = z.object({
|
||||||
description: z.string(),
|
description: z.string(),
|
||||||
id: z.number(),
|
id: z.number(),
|
||||||
|
creator_id: z.number().nullable(),
|
||||||
|
date_created: z.number().nullable(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
members: z.array(z.number()),
|
members: z.array(z.number()),
|
||||||
is_system_group: z.boolean(),
|
is_system_group: z.boolean(),
|
||||||
|
|
|
@ -24,6 +24,8 @@ import * as settings_components from "./settings_components";
|
||||||
import * as settings_data from "./settings_data";
|
import * as settings_data from "./settings_data";
|
||||||
import * as settings_org from "./settings_org";
|
import * as settings_org from "./settings_org";
|
||||||
import {current_user, realm} from "./state_data";
|
import {current_user, realm} from "./state_data";
|
||||||
|
import * as stream_data from "./stream_data";
|
||||||
|
import * as timerender from "./timerender";
|
||||||
import * as ui_report from "./ui_report";
|
import * as ui_report from "./ui_report";
|
||||||
import * as user_group_components from "./user_group_components";
|
import * as user_group_components from "./user_group_components";
|
||||||
import * as user_group_create from "./user_group_create";
|
import * as user_group_create from "./user_group_create";
|
||||||
|
@ -333,6 +335,13 @@ function update_toggler_for_group_setting() {
|
||||||
export function show_settings_for(group) {
|
export function show_settings_for(group) {
|
||||||
const html = render_user_group_settings({
|
const html = render_user_group_settings({
|
||||||
group,
|
group,
|
||||||
|
// We get timestamp in seconds from the API but timerender needs milliseconds.
|
||||||
|
date_created_string: timerender.get_localized_date_or_time_for_format(
|
||||||
|
new Date(group.date_created * 1000),
|
||||||
|
"dayofyear_year",
|
||||||
|
),
|
||||||
|
creator: stream_data.maybe_get_creator_details(group.creator_id),
|
||||||
|
is_creator: group.creator_id === current_user.user_id,
|
||||||
is_member: user_groups.is_direct_member_of(people.my_current_user_id(), group.id),
|
is_member: user_groups.is_direct_member_of(people.my_current_user_id(), group.id),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,8 @@ export function add(user_group_raw: UserGroupRaw): void {
|
||||||
description: user_group_raw.description,
|
description: user_group_raw.description,
|
||||||
id: user_group_raw.id,
|
id: user_group_raw.id,
|
||||||
name: user_group_raw.name,
|
name: user_group_raw.name,
|
||||||
|
creator_id: user_group_raw.creator_id,
|
||||||
|
date_created: user_group_raw.date_created,
|
||||||
members: new Set(user_group_raw.members),
|
members: new Set(user_group_raw.members),
|
||||||
is_system_group: user_group_raw.is_system_group,
|
is_system_group: user_group_raw.is_system_group,
|
||||||
direct_subgroup_ids: new Set(user_group_raw.direct_subgroup_ids),
|
direct_subgroup_ids: new Set(user_group_raw.direct_subgroup_ids),
|
||||||
|
|
|
@ -950,8 +950,10 @@ h4.user_group_setting_subsection_title {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.stream_details_box {
|
.stream_details_box,
|
||||||
> .stream_details_subsection {
|
.group_detail_box {
|
||||||
|
> .stream_details_subsection,
|
||||||
|
> .group_details_box_subsection {
|
||||||
margin: 0 0 10px; /* mimic paragraph spacing */
|
margin: 0 0 10px; /* mimic paragraph spacing */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,21 @@
|
||||||
|
|
||||||
{{> group_permissions can_mention_group_widget_name="can_mention_group" can_manage_group_widget_name="can_manage_group"}}
|
{{> group_permissions can_mention_group_widget_name="can_mention_group" can_manage_group_widget_name="can_manage_group"}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="group_detail_box">
|
||||||
|
<div class="user_group_details_box_header">
|
||||||
|
<h3 class="user_group_setting_subsection_title">
|
||||||
|
{{t "User group details" }}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="creator_details group_details_box_subsection">
|
||||||
|
{{> ../creator_details }}
|
||||||
|
</div>
|
||||||
|
<div class="group_details_box_subsection">
|
||||||
|
{{t "User group ID"}}<br/>
|
||||||
|
{{group.id}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="group_member_settings group_setting_section" data-group-section="members">
|
<div class="group_member_settings group_setting_section" data-group-section="members">
|
||||||
|
|
|
@ -424,6 +424,8 @@ const harry_item = user_item(harry);
|
||||||
const hamletcharacters = user_group_item({
|
const hamletcharacters = user_group_item({
|
||||||
name: "hamletcharacters",
|
name: "hamletcharacters",
|
||||||
id: 1,
|
id: 1,
|
||||||
|
creator_id: null,
|
||||||
|
date_created: 1596710000,
|
||||||
description: "Characters of Hamlet",
|
description: "Characters of Hamlet",
|
||||||
members: new Set([100, 104]),
|
members: new Set([100, 104]),
|
||||||
is_system_group: false,
|
is_system_group: false,
|
||||||
|
@ -435,6 +437,8 @@ const hamletcharacters = user_group_item({
|
||||||
const backend = user_group_item({
|
const backend = user_group_item({
|
||||||
name: "Backend",
|
name: "Backend",
|
||||||
id: 2,
|
id: 2,
|
||||||
|
creator_id: null,
|
||||||
|
date_created: 1596710000,
|
||||||
description: "Backend team",
|
description: "Backend team",
|
||||||
members: new Set([101]),
|
members: new Set([101]),
|
||||||
is_system_group: false,
|
is_system_group: false,
|
||||||
|
@ -446,6 +450,8 @@ const backend = user_group_item({
|
||||||
const call_center = user_group_item({
|
const call_center = user_group_item({
|
||||||
name: "Call Center",
|
name: "Call Center",
|
||||||
id: 3,
|
id: 3,
|
||||||
|
creator_id: null,
|
||||||
|
date_created: 1596710000,
|
||||||
description: "folks working in support",
|
description: "folks working in support",
|
||||||
members: new Set([102]),
|
members: new Set([102]),
|
||||||
is_system_group: false,
|
is_system_group: false,
|
||||||
|
|
|
@ -809,6 +809,8 @@ exports.fixtures = {
|
||||||
group: {
|
group: {
|
||||||
id: 555,
|
id: 555,
|
||||||
name: "Mobile",
|
name: "Mobile",
|
||||||
|
creator_id: null,
|
||||||
|
date_created: fake_now,
|
||||||
description: "mobile folks",
|
description: "mobile folks",
|
||||||
members: [1],
|
members: [1],
|
||||||
is_system_group: false,
|
is_system_group: false,
|
||||||
|
|
|
@ -13,6 +13,8 @@ run_test("user_groups", () => {
|
||||||
const students = {
|
const students = {
|
||||||
description: "Students group",
|
description: "Students group",
|
||||||
name: "Students",
|
name: "Students",
|
||||||
|
creator_id: null,
|
||||||
|
date_created: 1596710000,
|
||||||
id: 0,
|
id: 0,
|
||||||
members: new Set([1, 2]),
|
members: new Set([1, 2]),
|
||||||
is_system_group: false,
|
is_system_group: false,
|
||||||
|
@ -32,6 +34,8 @@ run_test("user_groups", () => {
|
||||||
const admins = {
|
const admins = {
|
||||||
name: "Admins",
|
name: "Admins",
|
||||||
description: "foo",
|
description: "foo",
|
||||||
|
creator_id: null,
|
||||||
|
date_created: 1596710000,
|
||||||
id: 1,
|
id: 1,
|
||||||
members: new Set([3]),
|
members: new Set([3]),
|
||||||
is_system_group: false,
|
is_system_group: false,
|
||||||
|
|
|
@ -8,6 +8,7 @@ from django.utils.timezone import now as timezone_now
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from zerver.lib.exceptions import JsonableError
|
from zerver.lib.exceptions import JsonableError
|
||||||
|
from zerver.lib.timestamp import datetime_to_timestamp
|
||||||
from zerver.lib.user_groups import (
|
from zerver.lib.user_groups import (
|
||||||
AnonymousSettingGroupDict,
|
AnonymousSettingGroupDict,
|
||||||
get_group_setting_value_for_api,
|
get_group_setting_value_for_api,
|
||||||
|
@ -53,6 +54,7 @@ def create_user_group_in_database(
|
||||||
description=description,
|
description=description,
|
||||||
is_system_group=is_system_group,
|
is_system_group=is_system_group,
|
||||||
realm_for_sharding=realm,
|
realm_for_sharding=realm,
|
||||||
|
creator=acting_user,
|
||||||
)
|
)
|
||||||
|
|
||||||
for setting_name, setting_value in group_settings_map.items():
|
for setting_name, setting_value in group_settings_map.items():
|
||||||
|
@ -170,11 +172,17 @@ def do_send_create_user_group_event(
|
||||||
members: list[UserProfile],
|
members: list[UserProfile],
|
||||||
direct_subgroups: Sequence[UserGroup] = [],
|
direct_subgroups: Sequence[UserGroup] = [],
|
||||||
) -> None:
|
) -> None:
|
||||||
|
creator_id = user_group.creator_id
|
||||||
|
assert user_group.date_created is not None
|
||||||
|
date_created = datetime_to_timestamp(user_group.date_created)
|
||||||
|
|
||||||
event = dict(
|
event = dict(
|
||||||
type="user_group",
|
type="user_group",
|
||||||
op="add",
|
op="add",
|
||||||
group=dict(
|
group=dict(
|
||||||
name=user_group.name,
|
name=user_group.name,
|
||||||
|
creator_id=creator_id,
|
||||||
|
date_created=date_created,
|
||||||
members=[member.id for member in members],
|
members=[member.id for member in members],
|
||||||
description=user_group.description,
|
description=user_group.description,
|
||||||
id=user_group.id,
|
id=user_group.id,
|
||||||
|
|
|
@ -1812,6 +1812,8 @@ group_type = DictType(
|
||||||
required_keys=[
|
required_keys=[
|
||||||
("id", int),
|
("id", int),
|
||||||
("name", str),
|
("name", str),
|
||||||
|
("creator_id", OptionalType(int)),
|
||||||
|
("date_created", OptionalType(int)),
|
||||||
("members", ListType(int)),
|
("members", ListType(int)),
|
||||||
("direct_subgroup_ids", ListType(int)),
|
("direct_subgroup_ids", ListType(int)),
|
||||||
("description", str),
|
("description", str),
|
||||||
|
|
|
@ -301,6 +301,7 @@ DATE_FIELDS: dict[TableName, list[Field]] = {
|
||||||
"zerver_realm": ["date_created"],
|
"zerver_realm": ["date_created"],
|
||||||
"zerver_scheduledmessage": ["scheduled_timestamp"],
|
"zerver_scheduledmessage": ["scheduled_timestamp"],
|
||||||
"zerver_stream": ["date_created"],
|
"zerver_stream": ["date_created"],
|
||||||
|
"zerver_namedusergroup": ["date_created"],
|
||||||
"zerver_useractivityinterval": ["start", "end"],
|
"zerver_useractivityinterval": ["start", "end"],
|
||||||
"zerver_useractivity": ["last_visit"],
|
"zerver_useractivity": ["last_visit"],
|
||||||
"zerver_onboardingstep": ["timestamp"],
|
"zerver_onboardingstep": ["timestamp"],
|
||||||
|
|
|
@ -754,13 +754,15 @@ def bulk_import_named_user_groups(data: TableData) -> None:
|
||||||
group["can_manage_group_id"],
|
group["can_manage_group_id"],
|
||||||
group["can_mention_group_id"],
|
group["can_mention_group_id"],
|
||||||
group["deactivated"],
|
group["deactivated"],
|
||||||
|
group["creator_id"],
|
||||||
|
group["date_created"],
|
||||||
)
|
)
|
||||||
for group in data["zerver_namedusergroup"]
|
for group in data["zerver_namedusergroup"]
|
||||||
]
|
]
|
||||||
|
|
||||||
query = SQL(
|
query = SQL(
|
||||||
"""
|
"""
|
||||||
INSERT INTO zerver_namedusergroup (usergroup_ptr_id, realm_id, name, description, is_system_group, can_manage_group_id, can_mention_group_id, deactivated)
|
INSERT INTO zerver_namedusergroup (usergroup_ptr_id, realm_id, name, description, is_system_group, can_manage_group_id, can_mention_group_id, deactivated, creator_id, date_created)
|
||||||
VALUES %s
|
VALUES %s
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
@ -1197,6 +1199,10 @@ def do_import_realm(import_dir: Path, subdomain: str, processes: int = 1) -> Rea
|
||||||
bulk_import_model(data, UserGroup)
|
bulk_import_model(data, UserGroup)
|
||||||
|
|
||||||
if "zerver_namedusergroup" in data:
|
if "zerver_namedusergroup" in data:
|
||||||
|
re_map_foreign_keys(
|
||||||
|
data, "zerver_namedusergroup", "creator", related_table="user_profile"
|
||||||
|
)
|
||||||
|
fix_datetime_fields(data, "zerver_namedusergroup")
|
||||||
re_map_foreign_keys(
|
re_map_foreign_keys(
|
||||||
data, "zerver_namedusergroup", "usergroup_ptr", related_table="usergroup"
|
data, "zerver_namedusergroup", "usergroup_ptr", related_table="usergroup"
|
||||||
)
|
)
|
||||||
|
|
|
@ -17,6 +17,7 @@ from zerver.lib.exceptions import (
|
||||||
PreviousSettingValueMismatchedError,
|
PreviousSettingValueMismatchedError,
|
||||||
SystemGroupRequiredError,
|
SystemGroupRequiredError,
|
||||||
)
|
)
|
||||||
|
from zerver.lib.timestamp import datetime_to_timestamp
|
||||||
from zerver.lib.types import GroupPermissionSetting, ServerSupportedPermissionSettings
|
from zerver.lib.types import GroupPermissionSetting, ServerSupportedPermissionSettings
|
||||||
from zerver.models import (
|
from zerver.models import (
|
||||||
GroupGroupMembership,
|
GroupGroupMembership,
|
||||||
|
@ -50,6 +51,8 @@ class UserGroupDict(TypedDict):
|
||||||
description: str
|
description: str
|
||||||
members: list[int]
|
members: list[int]
|
||||||
direct_subgroup_ids: list[int]
|
direct_subgroup_ids: list[int]
|
||||||
|
creator_id: int | None
|
||||||
|
date_created: int | None
|
||||||
is_system_group: bool
|
is_system_group: bool
|
||||||
can_manage_group: int | AnonymousSettingGroupDict
|
can_manage_group: int | AnonymousSettingGroupDict
|
||||||
can_mention_group: int | AnonymousSettingGroupDict
|
can_mention_group: int | AnonymousSettingGroupDict
|
||||||
|
@ -516,9 +519,19 @@ def user_groups_in_realm_serialized(
|
||||||
if user_group.id in group_subgroups:
|
if user_group.id in group_subgroups:
|
||||||
direct_subgroup_ids = group_subgroups[user_group.id]
|
direct_subgroup_ids = group_subgroups[user_group.id]
|
||||||
|
|
||||||
|
creator_id = user_group.creator_id
|
||||||
|
|
||||||
|
date_created = (
|
||||||
|
datetime_to_timestamp(user_group.date_created)
|
||||||
|
if user_group.date_created is not None
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
group_dicts[user_group.id] = dict(
|
group_dicts[user_group.id] = dict(
|
||||||
id=user_group.id,
|
id=user_group.id,
|
||||||
name=user_group.name,
|
name=user_group.name,
|
||||||
|
creator_id=creator_id,
|
||||||
|
date_created=date_created,
|
||||||
description=user_group.description,
|
description=user_group.description,
|
||||||
members=direct_member_ids,
|
members=direct_member_ids,
|
||||||
direct_subgroup_ids=direct_subgroup_ids,
|
direct_subgroup_ids=direct_subgroup_ids,
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Generated by Django 5.0.8 on 2024-08-31 08:09
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("zerver", "0582_remove_realm_delete_own_message_policy"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="namedusergroup",
|
||||||
|
name="creator",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
db_column="creator_id",
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="+",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="namedusergroup",
|
||||||
|
name="date_created",
|
||||||
|
field=models.DateTimeField(default=django.utils.timezone.now, null=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,53 @@
|
||||||
|
# Generated by Django 5.0.8 on 2024-08-31 08:09
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||||
|
from django.db.migrations.state import StateApps
|
||||||
|
|
||||||
|
|
||||||
|
def backfill_creator_id_and_date_created_from_realm_audit_log(
|
||||||
|
apps: StateApps, schema_editor: BaseDatabaseSchemaEditor
|
||||||
|
) -> None:
|
||||||
|
RealmAuditLog = apps.get_model("zerver", "RealmAuditLog")
|
||||||
|
RealmAuditLog.USER_GROUP_CREATED = 701
|
||||||
|
NamedUserGroup = apps.get_model("zerver", "NamedUserGroup")
|
||||||
|
|
||||||
|
user_group_creator_updates = []
|
||||||
|
for audit_log_entry in RealmAuditLog.objects.select_related("modified_user_group").filter(
|
||||||
|
event_type=RealmAuditLog.USER_GROUP_CREATED,
|
||||||
|
acting_user_id__isnull=False,
|
||||||
|
):
|
||||||
|
assert audit_log_entry.modified_user_group is not None
|
||||||
|
user_group = audit_log_entry.modified_user_group
|
||||||
|
user_group.creator_id = audit_log_entry.acting_user_id
|
||||||
|
user_group_creator_updates.append(user_group)
|
||||||
|
|
||||||
|
NamedUserGroup.objects.bulk_update(user_group_creator_updates, ["creator_id"], batch_size=1000)
|
||||||
|
|
||||||
|
user_group_date_created_updates = []
|
||||||
|
for audit_log_entry in RealmAuditLog.objects.select_related("modified_user_group").filter(
|
||||||
|
event_type=RealmAuditLog.USER_GROUP_CREATED,
|
||||||
|
event_time__isnull=False,
|
||||||
|
):
|
||||||
|
assert audit_log_entry.modified_user_group is not None
|
||||||
|
user_group = audit_log_entry.modified_user_group
|
||||||
|
user_group.date_created = audit_log_entry.event_time
|
||||||
|
user_group_date_created_updates.append(user_group)
|
||||||
|
|
||||||
|
NamedUserGroup.objects.bulk_update(
|
||||||
|
user_group_date_created_updates, ["date_created"], batch_size=1000
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("zerver", "0583_namedusergroup_creator_namedusergroup_date_created"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(
|
||||||
|
backfill_creator_id_and_date_created_from_realm_audit_log,
|
||||||
|
reverse_code=migrations.RunPython.noop,
|
||||||
|
elidable=True,
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,5 +1,6 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import CASCADE
|
from django.db.models import CASCADE
|
||||||
|
from django.utils.timezone import now as timezone_now
|
||||||
from django_cte import CTEManager
|
from django_cte import CTEManager
|
||||||
|
|
||||||
from zerver.lib.types import GroupPermissionSetting
|
from zerver.lib.types import GroupPermissionSetting
|
||||||
|
@ -52,6 +53,10 @@ class NamedUserGroup(UserGroup): # type: ignore[django-manager-missing] # djang
|
||||||
)
|
)
|
||||||
name = models.CharField(max_length=MAX_NAME_LENGTH, db_column="name")
|
name = models.CharField(max_length=MAX_NAME_LENGTH, db_column="name")
|
||||||
description = models.TextField(default="", db_column="description")
|
description = models.TextField(default="", db_column="description")
|
||||||
|
date_created = models.DateTimeField(default=timezone_now, null=True)
|
||||||
|
creator = models.ForeignKey(
|
||||||
|
UserProfile, null=True, on_delete=models.SET_NULL, related_name="+", db_column="creator_id"
|
||||||
|
)
|
||||||
is_system_group = models.BooleanField(default=False, db_column="is_system_group")
|
is_system_group = models.BooleanField(default=False, db_column="is_system_group")
|
||||||
|
|
||||||
can_manage_group = models.ForeignKey(UserGroup, on_delete=models.RESTRICT, related_name="+")
|
can_manage_group = models.ForeignKey(UserGroup, on_delete=models.RESTRICT, related_name="+")
|
||||||
|
|
|
@ -3148,6 +3148,8 @@ paths:
|
||||||
{
|
{
|
||||||
"name": "backend",
|
"name": "backend",
|
||||||
"members": [12],
|
"members": [12],
|
||||||
|
"creator_id": 9,
|
||||||
|
"date_created": 1717484476,
|
||||||
"description": "Backend team",
|
"description": "Backend team",
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"is_system_group": false,
|
"is_system_group": false,
|
||||||
|
@ -20026,6 +20028,34 @@ paths:
|
||||||
type: integer
|
type: integer
|
||||||
description: |
|
description: |
|
||||||
The user group's integer ID.
|
The user group's integer ID.
|
||||||
|
date_created:
|
||||||
|
type: integer
|
||||||
|
nullable: true
|
||||||
|
description: |
|
||||||
|
The UNIX timestamp for when the user group was created, in UTC seconds.
|
||||||
|
|
||||||
|
A `null` value means the user group has no recorded date, which is often
|
||||||
|
because the group predates the metadata being tracked starting in Zulip 8.0,
|
||||||
|
or because it was created via a data import tool
|
||||||
|
or [management command][management-commands].
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 10.0 (feature level 292).
|
||||||
|
|
||||||
|
[management-commands]: https://zulip.readthedocs.io/en/latest/production/management-commands.html
|
||||||
|
creator_id:
|
||||||
|
type: integer
|
||||||
|
nullable: true
|
||||||
|
description: |
|
||||||
|
The ID of the user who created this user group.
|
||||||
|
|
||||||
|
A `null` value means the user group has no recorded creator, which is often
|
||||||
|
because the group predates the metadata being tracked starting in Zulip 8.0,
|
||||||
|
or because it was created via a data import tool
|
||||||
|
or [management command][management-commands].
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 10.0 (feature level 292).
|
||||||
|
|
||||||
|
[management-commands]: https://zulip.readthedocs.io/en/latest/production/management-commands.html
|
||||||
members:
|
members:
|
||||||
type: array
|
type: array
|
||||||
description: |
|
description: |
|
||||||
|
@ -20107,6 +20137,8 @@ paths:
|
||||||
{
|
{
|
||||||
"description": "Owners of this organization",
|
"description": "Owners of this organization",
|
||||||
"id": 1,
|
"id": 1,
|
||||||
|
"creator_id": null,
|
||||||
|
"date_created": null,
|
||||||
"name": "role:owners",
|
"name": "role:owners",
|
||||||
"members": [1],
|
"members": [1],
|
||||||
"direct_subgroup_ids": [],
|
"direct_subgroup_ids": [],
|
||||||
|
@ -20117,6 +20149,8 @@ paths:
|
||||||
{
|
{
|
||||||
"description": "Administrators of this organization, including owners",
|
"description": "Administrators of this organization, including owners",
|
||||||
"id": 2,
|
"id": 2,
|
||||||
|
"creator_id": null,
|
||||||
|
"date_created": null,
|
||||||
"name": "role:administrators",
|
"name": "role:administrators",
|
||||||
"members": [2],
|
"members": [2],
|
||||||
"direct_subgroup_ids": [1],
|
"direct_subgroup_ids": [1],
|
||||||
|
@ -20127,6 +20161,8 @@ paths:
|
||||||
{
|
{
|
||||||
"description": "Characters of Hamlet",
|
"description": "Characters of Hamlet",
|
||||||
"id": 3,
|
"id": 3,
|
||||||
|
"creator_id": null,
|
||||||
|
"date_created": 1717484476,
|
||||||
"name": "hamletcharacters",
|
"name": "hamletcharacters",
|
||||||
"members": [3, 4],
|
"members": [3, 4],
|
||||||
"direct_subgroup_ids": [],
|
"direct_subgroup_ids": [],
|
||||||
|
@ -21269,6 +21305,30 @@ components:
|
||||||
type: string
|
type: string
|
||||||
description: |
|
description: |
|
||||||
The name of the user group.
|
The name of the user group.
|
||||||
|
date_created:
|
||||||
|
type: integer
|
||||||
|
nullable: true
|
||||||
|
description: |
|
||||||
|
The UNIX timestamp for when the user group was created, in UTC seconds.
|
||||||
|
|
||||||
|
A `null` value means the user group has no recorded date, which is often
|
||||||
|
because the user group is very old, or because it was created via a data
|
||||||
|
import tool or [management command][management-commands].
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 10.0 (feature level 292).
|
||||||
|
[management-commands]: https://zulip.readthedocs.io/en/latest/production/management-commands.html
|
||||||
|
creator_id:
|
||||||
|
type: integer
|
||||||
|
nullable: true
|
||||||
|
description: |
|
||||||
|
The ID of the user who created this user group.
|
||||||
|
|
||||||
|
A `null` value means the user group has no recorded creator, which is often
|
||||||
|
because the user group is very old, or because it was created via a data
|
||||||
|
import tool or [management command][management-commands].
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 10.0 (feature level 292).
|
||||||
|
[management-commands]: https://zulip.readthedocs.io/en/latest/production/management-commands.html
|
||||||
description:
|
description:
|
||||||
type: string
|
type: string
|
||||||
description: |
|
description: |
|
||||||
|
|
|
@ -33,6 +33,7 @@ from zerver.lib.mention import silent_mention_syntax_for_user
|
||||||
from zerver.lib.streams import ensure_stream
|
from zerver.lib.streams import ensure_stream
|
||||||
from zerver.lib.test_classes import ZulipTestCase
|
from zerver.lib.test_classes import ZulipTestCase
|
||||||
from zerver.lib.test_helpers import most_recent_usermessage
|
from zerver.lib.test_helpers import most_recent_usermessage
|
||||||
|
from zerver.lib.timestamp import datetime_to_timestamp
|
||||||
from zerver.lib.user_groups import (
|
from zerver.lib.user_groups import (
|
||||||
AnonymousSettingGroupDict,
|
AnonymousSettingGroupDict,
|
||||||
get_direct_user_groups,
|
get_direct_user_groups,
|
||||||
|
@ -80,13 +81,16 @@ class UserGroupTestCase(ZulipTestCase):
|
||||||
|
|
||||||
def test_user_groups_in_realm_serialized(self) -> None:
|
def test_user_groups_in_realm_serialized(self) -> None:
|
||||||
realm = get_realm("zulip")
|
realm = get_realm("zulip")
|
||||||
|
user = self.example_user("iago")
|
||||||
user_group = NamedUserGroup.objects.filter(realm=realm).first()
|
user_group = NamedUserGroup.objects.filter(realm=realm).first()
|
||||||
assert user_group is not None
|
assert user_group is not None
|
||||||
empty_user_group = check_add_user_group(realm, "newgroup", [], acting_user=None)
|
empty_user_group = check_add_user_group(realm, "newgroup", [], acting_user=user)
|
||||||
|
|
||||||
user_groups = user_groups_in_realm_serialized(realm, allow_deactivated=False)
|
user_groups = user_groups_in_realm_serialized(realm, allow_deactivated=False)
|
||||||
self.assert_length(user_groups, 10)
|
self.assert_length(user_groups, 10)
|
||||||
self.assertEqual(user_groups[0]["id"], user_group.id)
|
self.assertEqual(user_groups[0]["id"], user_group.id)
|
||||||
|
self.assertEqual(user_groups[0]["creator_id"], user_group.creator_id)
|
||||||
|
self.assertEqual(user_groups[0]["date_created"], user_group.date_created)
|
||||||
self.assertEqual(user_groups[0]["name"], SystemGroups.NOBODY)
|
self.assertEqual(user_groups[0]["name"], SystemGroups.NOBODY)
|
||||||
self.assertEqual(user_groups[0]["description"], "Nobody")
|
self.assertEqual(user_groups[0]["description"], "Nobody")
|
||||||
self.assertEqual(user_groups[0]["members"], [])
|
self.assertEqual(user_groups[0]["members"], [])
|
||||||
|
@ -100,6 +104,8 @@ class UserGroupTestCase(ZulipTestCase):
|
||||||
"user_profile_id", flat=True
|
"user_profile_id", flat=True
|
||||||
)
|
)
|
||||||
self.assertEqual(user_groups[1]["id"], owners_system_group.id)
|
self.assertEqual(user_groups[1]["id"], owners_system_group.id)
|
||||||
|
self.assertEqual(user_groups[1]["creator_id"], owners_system_group.creator_id)
|
||||||
|
self.assertEqual(user_groups[1]["date_created"], owners_system_group.date_created)
|
||||||
self.assertEqual(user_groups[1]["name"], SystemGroups.OWNERS)
|
self.assertEqual(user_groups[1]["name"], SystemGroups.OWNERS)
|
||||||
self.assertEqual(user_groups[1]["description"], "Owners of this organization")
|
self.assertEqual(user_groups[1]["description"], "Owners of this organization")
|
||||||
self.assertEqual(set(user_groups[1]["members"]), set(membership))
|
self.assertEqual(set(user_groups[1]["members"]), set(membership))
|
||||||
|
@ -119,6 +125,11 @@ class UserGroupTestCase(ZulipTestCase):
|
||||||
name=SystemGroups.EVERYONE, realm=realm, is_system_group=True
|
name=SystemGroups.EVERYONE, realm=realm, is_system_group=True
|
||||||
)
|
)
|
||||||
self.assertEqual(user_groups[9]["id"], empty_user_group.id)
|
self.assertEqual(user_groups[9]["id"], empty_user_group.id)
|
||||||
|
self.assertEqual(user_groups[9]["creator_id"], empty_user_group.creator_id)
|
||||||
|
assert empty_user_group.date_created is not None
|
||||||
|
self.assertEqual(
|
||||||
|
user_groups[9]["date_created"], datetime_to_timestamp(empty_user_group.date_created)
|
||||||
|
)
|
||||||
self.assertEqual(user_groups[9]["name"], "newgroup")
|
self.assertEqual(user_groups[9]["name"], "newgroup")
|
||||||
self.assertEqual(user_groups[9]["description"], "")
|
self.assertEqual(user_groups[9]["description"], "")
|
||||||
self.assertEqual(user_groups[9]["members"], [])
|
self.assertEqual(user_groups[9]["members"], [])
|
||||||
|
@ -143,6 +154,13 @@ class UserGroupTestCase(ZulipTestCase):
|
||||||
)
|
)
|
||||||
user_groups = user_groups_in_realm_serialized(realm, allow_deactivated=False)
|
user_groups = user_groups_in_realm_serialized(realm, allow_deactivated=False)
|
||||||
self.assertEqual(user_groups[10]["id"], new_user_group.id)
|
self.assertEqual(user_groups[10]["id"], new_user_group.id)
|
||||||
|
self.assertEqual(user_groups[10]["creator_id"], new_user_group.creator_id)
|
||||||
|
new_user_group_date_created = (
|
||||||
|
datetime_to_timestamp(new_user_group.date_created)
|
||||||
|
if new_user_group.date_created is not None
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
self.assertEqual(user_groups[10]["date_created"], new_user_group_date_created)
|
||||||
self.assertEqual(user_groups[10]["name"], "newgroup2")
|
self.assertEqual(user_groups[10]["name"], "newgroup2")
|
||||||
self.assertEqual(user_groups[10]["description"], "")
|
self.assertEqual(user_groups[10]["description"], "")
|
||||||
self.assertEqual(user_groups[10]["members"], [othello.id])
|
self.assertEqual(user_groups[10]["members"], [othello.id])
|
||||||
|
|
Loading…
Reference in New Issue