Skip to content

Commit 5ba0fd3

Browse files
committed
Code cleanup
1 parent 466a3e2 commit 5ba0fd3

File tree

2 files changed

+42
-121
lines changed

2 files changed

+42
-121
lines changed

ansible_base/rbac/api/views.py

Lines changed: 17 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
from ..models import DABContentType, DABPermission, get_evaluation_model
3939
from ..policies import check_content_obj_permission
4040
from ..remote import RemoteObject, get_resource_prefix
41-
from ..sync import maybe_reverse_sync_assignment, maybe_reverse_sync_unassignment, delayed_reverse_sync
41+
from ..sync import maybe_reverse_sync_assignment, maybe_reverse_sync_role_definition, maybe_reverse_sync_unassignment
4242
from .queries import assignment_qs_user_to_obj, assignment_qs_user_to_obj_perm
4343

4444

@@ -121,49 +121,33 @@ def _error_if_managed(self, instance):
121121
raise ValidationError(_('Role is managed by the system'))
122122

123123
def create(self, request, *args, **kwargs):
124-
"""Create a new RoleDefinition with delayed reverse-sync.
124+
"""Create a new RoleDefinition with delayed reverse-sync."""
125+
from ansible_base.resource_registry.signals.handlers import no_reverse_sync
125126

126-
This ensures that the RoleDefinition is fully created with
127-
permissions before syncing to the resource server.
128-
"""
129-
serializer = self.get_serializer(data=request.data)
130-
serializer.is_valid(raise_exception=True)
127+
with no_reverse_sync():
128+
response = super().create(request, *args, **kwargs)
131129

132-
# Use delayed sync to avoid the timing issue where post_save fires
133-
# before many-to-many permissions are saved
134-
with delayed_reverse_sync(None, "create") as ctx:
135-
# Create the instance but disable auto-sync
136-
instance = serializer.save()
137-
# Update the context manager with the actual instance
138-
ctx.set_instance(instance)
130+
# Manually sync after permissions are fully saved
131+
instance = self.get_queryset().get(pk=response.data['id'])
132+
maybe_reverse_sync_role_definition(instance, "create")
139133

140-
headers = self.get_success_headers(serializer.data)
141-
return Response(serializer.data, status=201, headers=headers)
134+
return response
142135

143136
def update(self, request, *args, **kwargs):
144-
"""Update a RoleDefinition with delayed reverse-sync.
137+
"""Update a RoleDefinition with delayed reverse-sync."""
138+
from ansible_base.resource_registry.signals.handlers import no_reverse_sync
145139

146-
This ensures that the RoleDefinition is fully updated with
147-
permissions before syncing to the resource server.
148-
"""
149-
partial = kwargs.pop('partial', False)
150140
instance = self.get_object()
151141
self._error_if_managed(instance)
152142

153-
serializer = self.get_serializer(instance, data=request.data, partial=partial)
154-
serializer.is_valid(raise_exception=True)
143+
with no_reverse_sync():
144+
response = super().update(request, *args, **kwargs)
155145

156-
# Use delayed sync to avoid the timing issue where post_save fires
157-
# before many-to-many permissions are saved
158-
with delayed_reverse_sync(instance, "update"):
159-
serializer.save()
146+
# Manually sync after permissions are fully saved
147+
instance.refresh_from_db()
148+
maybe_reverse_sync_role_definition(instance, "update")
160149

161-
if getattr(instance, '_prefetched_objects_cache', None):
162-
# If 'prefetch_related' has been applied to a queryset, we need to
163-
# forcibly invalidate the prefetch cache on the instance.
164-
instance._prefetched_objects_cache = {}
165-
166-
return Response(serializer.data)
150+
return response
167151

168152
def perform_update(self, serializer):
169153
self._error_if_managed(serializer.instance)

ansible_base/rbac/sync.py

Lines changed: 25 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,29 @@
1515
"""
1616

1717
import logging
18-
from contextlib import contextmanager
1918

2019
logger = logging.getLogger('ansible_base.rbac.sync')
2120

2221

23-
def reverse_sync_enabled_all_conditions(assignment):
22+
def reverse_sync_enabled_all_conditions(instance):
2423
"""This checks for basically all cases we do not reverse sync
25-
1. object level flag for skipping the sync
26-
2. environment variable to skip sync
27-
3. context manager to disable sync
28-
4. RESOURCE_SERVER setting not actually set
24+
1. global reverse sync enabled flag (including context manager)
25+
2. RESOURCE_SERVER setting not actually set
26+
3. environment variable to skip sync
27+
4. object level flag for skipping the sync
2928
"""
3029
from ansible_base.resource_registry.apps import _should_reverse_sync
3130
from ansible_base.resource_registry.signals.handlers import reverse_sync_enabled
3231
from ansible_base.resource_registry.utils.sync_to_resource_server import should_skip_reverse_sync
3332

34-
if not _should_reverse_sync():
33+
if not reverse_sync_enabled.enabled:
3534
return False
3635

37-
if not reverse_sync_enabled.enabled:
36+
if not _should_reverse_sync():
3837
return False
3938

40-
if should_skip_reverse_sync(assignment):
41-
return
39+
if should_skip_reverse_sync(instance):
40+
return False
4241

4342
return True
4443

@@ -63,86 +62,24 @@ def maybe_reverse_sync_unassignment(role_definition, actor, content_object):
6362
client.sync_unassignment(role_definition, actor, content_object)
6463

6564

66-
def _should_reverse_sync(instance) -> bool:
67-
"""Check if reverse sync should be performed for the given instance.
65+
def maybe_reverse_sync_role_definition(instance, action="update"):
66+
"""Manually trigger reverse-sync for a RoleDefinition if appropriate.
6867
69-
This checks the same conditions as the signal handler but without
70-
relying on the global reverse_sync_enabled state being temporarily disabled.
71-
"""
72-
from ansible_base.resource_registry.apps import _should_reverse_sync as _global_should_sync
73-
from ansible_base.resource_registry.utils.sync_to_resource_server import should_skip_reverse_sync
74-
75-
# Check global RESOURCE_SERVER configuration
76-
if not _global_should_sync():
77-
return False
78-
79-
# Check instance-specific skip conditions
80-
if should_skip_reverse_sync(instance):
81-
return False
82-
83-
return True
84-
85-
86-
class DelayedReverseSyncContext:
87-
"""Context class for delayed reverse sync operations."""
88-
89-
def __init__(self, action="update"):
90-
self.action = action
91-
self.instance = None
92-
93-
def set_instance(self, instance):
94-
"""Set the instance to sync after the context exits."""
95-
self.instance = instance
96-
97-
98-
@contextmanager
99-
def delayed_reverse_sync(instance, action="update"):
100-
"""Context manager that disables reverse-sync during execution, then manually syncs afterward.
101-
102-
This is useful for operations where the instance needs to be fully constructed
103-
(including many-to-many relationships) before syncing to the resource server.
104-
105-
This solves the timing issue where post_save fires before many-to-many
106-
relations are saved, causing empty permissions to be synced.
68+
This should be called after the instance is fully constructed with
69+
all many-to-many relationships saved.
10770
10871
Args:
109-
instance: The Django model instance to sync (can be None for create operations)
72+
instance: The RoleDefinition instance to sync
11073
action: The action type ("create" or "update")
111-
112-
Usage:
113-
# For updates
114-
with delayed_reverse_sync(role_def, "update"):
115-
# Perform operations that modify the instance
116-
role_def.permissions.set(permissions)
117-
# Sync happens here if appropriate
118-
119-
# For creates
120-
ctx = delayed_reverse_sync(None, "create")
121-
with ctx:
122-
instance = serializer.save()
123-
ctx.set_instance(instance)
124-
# Sync happens here if appropriate
12574
"""
126-
from ansible_base.resource_registry.signals.handlers import no_reverse_sync, sync_to_resource_server_post_save
127-
128-
# Create a context object that can be updated
129-
context = DelayedReverseSyncContext(action)
130-
context.instance = instance
131-
132-
with no_reverse_sync():
133-
yield context
134-
135-
# After the context, check if we should sync and do it manually
136-
final_instance = context.instance
137-
if final_instance and _should_reverse_sync(final_instance):
138-
logger.debug(f"Performing delayed reverse-sync for {final_instance} (action: {action})")
139-
# Use the same logic as the post_save signal handler
140-
created = (action == "create")
141-
sync_to_resource_server_post_save(
142-
sender=type(final_instance),
143-
instance=final_instance,
144-
created=created,
145-
update_fields=None
146-
)
147-
else:
148-
logger.debug(f"Skipping delayed reverse-sync for {final_instance} (action: {action})")
75+
if not reverse_sync_enabled_all_conditions(instance):
76+
logger.debug(f"Skipping reverse-sync for {instance} (action: {action})")
77+
return
78+
79+
logger.debug(f"Performing reverse-sync for {instance} (action: {action})")
80+
81+
# Use the same logic as the post_save signal handler
82+
from ansible_base.resource_registry.signals.handlers import sync_to_resource_server_post_save
83+
84+
created = action == "create"
85+
sync_to_resource_server_post_save(sender=type(instance), instance=instance, created=created, update_fields=None)

0 commit comments

Comments
 (0)