Skip to content

Commit befe1ca

Browse files
committed
fix(scripts): adjust community manage requests script
* Instead of working with specific records, the scripts accepts a community, for which then it creates requests (if not already created) for users with legacy records in the community. * For records that are owned by community members, the flags are automatically removed (since there is no sense to create requests). * Also persists the results in a JSON file.
1 parent 74a6fb7 commit befe1ca

File tree

1 file changed

+215
-16
lines changed

1 file changed

+215
-16
lines changed
Lines changed: 215 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,228 @@
1-
"""Zenodo snippets for admins.
1+
"""Script to handle community manage record requests for any community.
22
3-
WARNING: these should be used with care as they are actions to be done from users
4-
with superuser access! They should be deleted once they are integrated via UI options
3+
For legacy records (with can_community_manage_record flag) in a community:
4+
- Members: removes the permission flag (they already manage their records)
5+
- Non-members: creates requests asking them to allow community curation
6+
7+
Usage (in Flask shell):
8+
from scripts.admin.community_manage_requests import create_community_manage_requests
9+
10+
# Dry run first (default)
11+
create_community_manage_requests("community-slug")
12+
13+
# Actually process
14+
results = create_community_manage_requests("community-slug", dry_run=False)
515
"""
616

17+
import json
18+
from datetime import datetime, timezone
19+
20+
from invenio_access.permissions import system_identity
21+
from invenio_communities.proxies import current_communities
722
from invenio_db import db
8-
from invenio_rdm_records.proxies import current_rdm_records_service
23+
from invenio_rdm_records.proxies import current_community_records_service
924
from invenio_rdm_records.records import RDMRecord
10-
25+
from invenio_requests import current_requests_service
26+
from invenio_search.engine import dsl
27+
from zenodo_rdm.legacy.requests.community_manage_record import CommunityManageRecord
1128
from zenodo_rdm.legacy.requests.utils import submit_community_manage_record_request
1229

1330

14-
def create_community_manage_record_request(record_id):
15-
"""Util method to create a community manage record request."""
31+
def has_open_community_manage_request(user_id):
32+
"""Check if user already has an open community-manage-record request."""
33+
results = current_requests_service.search(
34+
system_identity,
35+
extra_filter=dsl.query.Bool(
36+
"must",
37+
must=[
38+
dsl.Q("term", **{"receiver.user": user_id}),
39+
dsl.Q("term", **{"type": CommunityManageRecord.type_id}),
40+
dsl.Q("term", **{"is_open": True}),
41+
],
42+
),
43+
)
44+
return results.total > 0
45+
46+
47+
def get_community_member_user_ids(community_id):
48+
"""Get all user IDs that are members of the community."""
49+
member_ids = set()
50+
51+
# Search all members (paginate through results)
52+
page = 1
53+
page_size = 100
54+
while True:
55+
results = current_communities.service.members.search(
56+
system_identity,
57+
community_id,
58+
params={"size": page_size, "page": page},
59+
)
60+
61+
for hit in results.hits:
62+
# Members can be users or groups; we only care about users
63+
member = hit.get("member", {})
64+
if member.get("type") == "user":
65+
member_ids.add(int(member["id"]))
66+
67+
if page * page_size >= results.total:
68+
break
69+
page += 1
70+
71+
return member_ids
72+
73+
74+
def get_legacy_records_by_membership(community_id, member_user_ids):
75+
"""Partition legacy records (with permission flag) by owner membership."""
76+
member_records = []
77+
non_member_owners = {}
78+
79+
# Search for records with the permission flag set (legacy records)
80+
results = current_community_records_service.search(
81+
system_identity,
82+
community_id,
83+
extra_filter=dsl.Q(
84+
"exists", field="parent.permission_flags.can_community_manage_record"
85+
),
86+
params={"size": 100},
87+
scan=True,
88+
)
89+
90+
for hit in results.hits:
91+
record_id = hit["id"]
92+
93+
# Get owner from parent
94+
parent = hit.get("parent", {})
95+
access = parent.get("access", {})
96+
owned_by = access.get("owned_by", {})
97+
owner_id = owned_by.get("user")
1698

17-
# add a permission flag to db (make record a legacy one)
18-
db_record = RDMRecord.get_record(record_id)
19-
db_record.parent.permission_flags = {"can_community_manage_record": False}
20-
db_record.parent.commit()
99+
if owner_id is None:
100+
continue
101+
102+
owner_id = int(owner_id)
103+
104+
if owner_id in member_user_ids:
105+
# Owner is a member - collect for flag removal
106+
member_records.append(record_id)
107+
else:
108+
# Owner is not a member - collect for request creation
109+
if owner_id not in non_member_owners:
110+
non_member_owners[owner_id] = []
111+
non_member_owners[owner_id].append(record_id)
112+
113+
return member_records, non_member_owners
114+
115+
116+
def remove_permission_flags(record_ids):
117+
"""Remove can_community_manage_record flag from records."""
118+
for record_id in record_ids:
119+
record = RDMRecord.get_record(record_id)
120+
parent = record.parent
121+
flags = parent.get("permission_flags", {})
122+
if "can_community_manage_record" in flags:
123+
flags.pop("can_community_manage_record")
124+
parent.permission_flags = flags if flags else None
125+
parent.commit()
21126
db.session.commit()
22127

23-
current_rdm_records_service.indexer.index(db_record)
24128

25-
# get record owner
26-
rec_owner = db_record.parent.access.owned_by.owner_id
129+
def create_community_manage_requests(community_slug, dry_run=True, output_dir="/ops"):
130+
"""Create community manage record requests for a community.
131+
132+
For members: removes permission flags from their records.
133+
For non-members: creates requests to allow community curation.
134+
"""
135+
operation_results = {
136+
"flags_removed": 0, # count of records with flags removed (members)
137+
"created": [],
138+
"skipped_existing": [], # users with existing open requests
139+
"errors": [],
140+
}
141+
142+
# Get community
143+
community = current_communities.service.read(system_identity, community_slug)
144+
community_id = community.id
145+
print(f"Processing community: {community['metadata']['title']} ({community_id})")
146+
147+
# Get member user IDs
148+
member_ids = get_community_member_user_ids(community_id)
149+
print(f"Found {len(member_ids)} community members")
150+
151+
# Partition legacy records by membership
152+
member_records, non_member_owners = get_legacy_records_by_membership(
153+
community_id, member_ids
154+
)
155+
print(
156+
f"Found {len(member_records)} legacy records owned by members (flags to remove)"
157+
)
158+
print(f"Found {len(non_member_owners)} non-member owners (requests to create)")
159+
160+
if dry_run:
161+
print("\n[DRY RUN] Would process:")
162+
print(f" Remove flags from {len(member_records)} member-owned records")
163+
for user_id, record_ids in non_member_owners.items():
164+
has_existing = has_open_community_manage_request(user_id)
165+
status = "(SKIP - has existing request)" if has_existing else ""
166+
print(f" User {user_id}: {len(record_ids)} records {status}")
167+
return operation_results
168+
169+
# Remove flags from member-owned records
170+
if member_records:
171+
print(f"Removing flags from {len(member_records)} member-owned records...")
172+
remove_permission_flags(member_records)
173+
operation_results["flags_removed"] = len(member_records)
174+
print(f"Removed flags from {len(member_records)} records")
175+
176+
# Create requests for each non-member owner
177+
for user_id, record_ids in non_member_owners.items():
178+
try:
179+
# Check for existing open request
180+
if has_open_community_manage_request(user_id):
181+
operation_results["skipped_existing"].append(
182+
{
183+
"user_id": user_id,
184+
"record_count": len(record_ids),
185+
}
186+
)
187+
print(f"Skipped user {user_id} - already has open request")
188+
continue
189+
190+
# Create and submit request for this user
191+
request_item = submit_community_manage_record_request(user_id)
192+
operation_results["created"].append(
193+
{
194+
"user_id": user_id,
195+
"record_count": len(record_ids),
196+
"request_id": str(request_item.id),
197+
}
198+
)
199+
print(f"Created request for user {user_id} ({len(record_ids)} records)")
200+
201+
except Exception as e:
202+
operation_results["errors"].append(
203+
{
204+
"user_id": user_id,
205+
"error": str(e),
206+
}
207+
)
208+
print(f"Error creating request for user {user_id}: {e}")
209+
210+
print("\nSummary:")
211+
print(f" Flags removed: {operation_results['flags_removed']} records")
212+
print(f" Requests created: {len(operation_results['created'])}")
213+
print(f" Skipped (existing request): {len(operation_results['skipped_existing'])}")
214+
print(f" Errors: {len(operation_results['errors'])}")
215+
216+
# Save results to JSON file
217+
output_file = f"{output_dir}/{community_slug}-manager-record-requests.json"
218+
output = {
219+
"timestamp": datetime.now(timezone.utc).isoformat(),
220+
"community": community_slug,
221+
"dry_run": dry_run,
222+
**operation_results,
223+
}
224+
with open(output_file, "w") as f:
225+
json.dump(output, f, indent=2)
226+
print(f"\nResults saved to {output_file}")
27227

28-
# create and submit a request
29-
return submit_community_manage_record_request(rec_owner)
228+
return operation_results

0 commit comments

Comments
 (0)