Skip to content

Commit

Permalink
feat(project,handlers): add sync for views and tasks, #966 #1198
Browse files Browse the repository at this point in the history
Signed-off-by: David Wallace <[email protected]>
  • Loading branch information
MyPyDavid committed Jan 8, 2025
1 parent 5d3bc73 commit 9065876
Show file tree
Hide file tree
Showing 14 changed files with 503 additions and 125 deletions.
3 changes: 2 additions & 1 deletion rdmo/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,8 @@

PROJECT_SEND_INVITE = True

PROJECT_REMOVE_VIEWS = True
PROJECT_VIEWS_SYNC = True
PROJECT_TASKS_SYNC = True

PROJECT_CREATE_RESTRICTED = False
PROJECT_CREATE_GROUPS = []
Expand Down
6 changes: 4 additions & 2 deletions rdmo/projects/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ class ProjectsConfig(AppConfig):
def ready(self):
from . import rules # noqa: F401

if settings.PROJECT_REMOVE_VIEWS:
from . import handlers # noqa: F401
if settings.PROJECT_VIEWS_SYNC:
from .handlers import project_views # noqa: F401
if settings.PROJECT_TASKS_SYNC:
from .handlers import project_tasks # noqa: F401
60 changes: 0 additions & 60 deletions rdmo/projects/handlers.py

This file was deleted.

Empty file.
110 changes: 110 additions & 0 deletions rdmo/projects/handlers/generic_handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
from django.contrib.auth.models import User

from rdmo.projects.models import Membership, Project

from .utils import add_instance_to_projects, remove_instance_from_projects


def m2m_catalogs_changed_projects_sync_signal_handler(action, related_model, pk_set, instance, project_field):
"""
Update project relationships for m2m_changed signals.
Args:
action (str): The m2m_changed action (post_add, post_remove, post_clear).
related_model (Model): The related model (e.g., Catalog).
pk_set (set): The set of primary keys for the related model instances.
instance (Model): The instance being updated (e.g., View or Task).
project_field (str): The field on Project to update (e.g., 'views', 'tasks').
"""
if action == 'post_remove' and pk_set:
related_instances = related_model.objects.filter(pk__in=pk_set)
projects_to_change = Project.objects.filter_catalogs(catalogs=related_instances).filter(
**{project_field: instance}
)
remove_instance_from_projects(projects_to_change, project_field, instance)

elif action == 'post_clear':
projects_to_change = Project.objects.filter(**{project_field: instance})
remove_instance_from_projects(projects_to_change, project_field, instance)

elif action == 'post_add' and pk_set:
related_instances = related_model.objects.filter(pk__in=pk_set)
projects_to_change = Project.objects.filter_catalogs(catalogs=related_instances).exclude(
**{project_field: instance}
)
add_instance_to_projects(projects_to_change, project_field, instance)


def m2m_sites_changed_projects_sync_signal_handler(action, model, pk_set, instance, project_field):
"""
Synchronize Project relationships for m2m_changed signals triggered by site updates.
Args:
action (str): The m2m_changed action (post_add, post_remove, post_clear).
model (Model): The related model (e.g., Site).
pk_set (set): The set of primary keys for the related model instances.
instance (Model): The instance being updated (e.g., View or Task).
project_field (str): The field on Project to update (e.g., 'views', 'tasks').
"""
if action == 'post_remove' and pk_set:
related_sites = model.objects.filter(pk__in=pk_set)
catalogs = instance.catalogs.all()

projects_to_change = Project.objects.filter_catalogs(catalogs=catalogs).filter(
site__in=related_sites,
**{project_field: instance}
)
remove_instance_from_projects(projects_to_change, project_field, instance)

elif action == 'post_clear':
projects_to_change = Project.objects.filter_catalogs().filter(**{project_field: instance})
remove_instance_from_projects(projects_to_change, project_field, instance)

elif action == 'post_add' and pk_set:
related_sites = model.objects.filter(pk__in=pk_set)
catalogs = instance.catalogs.all()

projects_to_change = Project.objects.filter_catalogs(catalogs=catalogs).filter(
site__in=related_sites
).exclude(**{project_field: instance})
add_instance_to_projects(projects_to_change, project_field, instance)


def m2m_groups_changed_projects_sync_signal_handler(action, model, pk_set, instance, project_field):
"""
Synchronize Project relationships for m2m_changed signals triggered by group updates.
Args:
action (str): The m2m_changed action (post_add, post_remove, post_clear).
model (Model): The related model (e.g., Group).
pk_set (set): The set of primary keys for the related model instances.
instance (Model): The instance being updated (e.g., View or Task).
project_field (str): The field on Project to update (e.g., 'views', 'tasks').
"""
if action == 'post_remove' and pk_set:
related_groups = model.objects.filter(pk__in=pk_set)
users = User.objects.filter(groups__in=related_groups)
memberships = Membership.objects.filter(role='owner', user__in=users).values_list('id', flat=True)
catalogs = instance.catalogs.all()

projects_to_change = Project.objects.filter_catalogs(catalogs=catalogs).filter(
memberships__in=memberships,
**{project_field: instance}
)
remove_instance_from_projects(projects_to_change, project_field, instance)

elif action == 'post_clear':
# Remove all linked projects regardless of catalogs
projects_to_change = Project.objects.filter_catalogs().filter(**{project_field: instance})
remove_instance_from_projects(projects_to_change, project_field, instance)

elif action == 'post_add' and pk_set:
related_groups = model.objects.filter(pk__in=pk_set)
users = User.objects.filter(groups__in=related_groups)
memberships = Membership.objects.filter(role='owner', user__in=users).values_list('id', flat=True)
catalogs = instance.catalogs.all()

projects_to_change = Project.objects.filter_catalogs(catalogs=catalogs).filter(
memberships__in=memberships
).exclude(**{project_field: instance})
add_instance_to_projects(projects_to_change, project_field, instance)
44 changes: 44 additions & 0 deletions rdmo/projects/handlers/project_tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@

from django.db.models.signals import m2m_changed
from django.dispatch import receiver

from rdmo.tasks.models import Task

from .generic_handlers import (
m2m_catalogs_changed_projects_sync_signal_handler,
m2m_groups_changed_projects_sync_signal_handler,
m2m_sites_changed_projects_sync_signal_handler,
)


@receiver(m2m_changed, sender=Task.catalogs.through)
def m2m_changed_task_catalog_signal(sender, instance, action, model, pk_set, **kwargs):
m2m_catalogs_changed_projects_sync_signal_handler(
action=action,
related_model=model,
pk_set=pk_set,
instance=instance,
project_field='tasks',
)


@receiver(m2m_changed, sender=Task.sites.through)
def m2m_changed_task_sites_signal(sender, instance, action, model, pk_set, **kwargs):
m2m_sites_changed_projects_sync_signal_handler(
action=action,
model=model,
pk_set=pk_set,
instance=instance,
project_field='tasks'
)


@receiver(m2m_changed, sender=Task.groups.through)
def m2m_changed_task_groups_signal(sender, instance, action, model, pk_set, **kwargs):
m2m_groups_changed_projects_sync_signal_handler(
action=action,
model=model,
pk_set=pk_set,
instance=instance,
project_field='tasks'
)
45 changes: 45 additions & 0 deletions rdmo/projects/handlers/project_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@

from django.db.models.signals import m2m_changed
from django.dispatch import receiver

from rdmo.views.models import View

from .generic_handlers import (
m2m_catalogs_changed_projects_sync_signal_handler,
m2m_groups_changed_projects_sync_signal_handler,
m2m_sites_changed_projects_sync_signal_handler,
)


@receiver(m2m_changed, sender=View.catalogs.through)
def m2m_changed_view_catalog_signal(sender, instance, action, model, pk_set, **kwargs):
m2m_catalogs_changed_projects_sync_signal_handler(
action=action,
related_model=model,
pk_set=pk_set,
instance=instance,
project_field='views',
)



@receiver(m2m_changed, sender=View.sites.through)
def m2m_changed_view_sites_signal(sender, instance, action, model, pk_set, **kwargs):
m2m_sites_changed_projects_sync_signal_handler(
action=action,
model=model,
pk_set=pk_set,
instance=instance,
project_field='views'
)


@receiver(m2m_changed, sender=View.groups.through)
def m2m_changed_view_groups_signal(sender, instance, action, model, pk_set, **kwargs):
m2m_groups_changed_projects_sync_signal_handler(
action=action,
model=model,
pk_set=pk_set,
instance=instance,
project_field='views'
)
10 changes: 10 additions & 0 deletions rdmo/projects/handlers/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@


def remove_instance_from_projects(projects, project_field, instance):
for project in projects:
getattr(project, project_field).remove(instance)


def add_instance_to_projects(projects, project_field, instance):
for project in projects:
getattr(project, project_field).add(instance)
14 changes: 14 additions & 0 deletions rdmo/projects/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ def filter_visibility(self, user):
visibility_filter = Q(visibility__isnull=False) & sites_filter & groups_filter
return self.filter(Q(user=user) | visibility_filter)

def filter_catalogs(self, catalogs=None, exclude_catalogs=None, exclude_null=True):
catalogs_filter = Q()
if exclude_null:
catalogs_filter &= Q(catalog__isnull=False)
if catalogs:
catalogs_filter &= Q(catalog__in=catalogs)
if exclude_catalogs:
catalogs_filter &= ~Q(catalog__in=exclude_catalogs)
return self.filter(catalogs_filter)


class MembershipQuerySet(models.QuerySet):

Expand Down Expand Up @@ -167,6 +177,10 @@ def filter_user(self, user):
def filter_visibility(self, user):
return self.get_queryset().filter_visibility(user)

def filter_catalogs(self, catalogs=None, exclude_catalogs=None, exclude_null=True):
return self.get_queryset().filter_catalogs(catalogs=catalogs, exclude_catalogs=exclude_catalogs,
exclude_null=exclude_null)


class MembershipManager(CurrentSiteManagerMixin, models.Manager):

Expand Down
5 changes: 5 additions & 0 deletions rdmo/projects/tests/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@


def assert_other_projects_unchanged(other_projects, initial_tasks_state):
for other_project in other_projects:
assert set(other_project.tasks.values_list('id', flat=True)) == set(initial_tasks_state[other_project.id])
61 changes: 0 additions & 61 deletions rdmo/projects/tests/test_handlers.py

This file was deleted.

Loading

0 comments on commit 9065876

Please sign in to comment.