Skip to content

Commit

Permalink
feat(project, handlers): add generic signal receiver handler fuctions
Browse files Browse the repository at this point in the history
Signed-off-by: David Wallace <[email protected]>
  • Loading branch information
MyPyDavid committed Jan 7, 2025
1 parent 798703b commit aa42691
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 126 deletions.
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)
97 changes: 36 additions & 61 deletions rdmo/projects/handlers/project_tasks.py
Original file line number Diff line number Diff line change
@@ -1,78 +1,53 @@
import logging

from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from django.db.models.signals import m2m_changed
from django.dispatch import receiver

from rdmo.projects.models import Membership, Project
from rdmo.questions.models import Catalog
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,
)

logger = logging.getLogger(__name__)


@receiver(m2m_changed, sender=Task.catalogs.through)
def m2m_changed_task_catalog_signal(sender, instance, action, model, **kwargs):

task = instance
# catalogs that were changed
catalogs = model.objects.filter(pk__in=kwargs['pk_set'])
if action in ('post_remove', 'post_clear'):
# Remove the task from projects whose catalog is no longer linked to this task
projects_to_change = Project.objects.filter(catalog__in=catalogs, tasks=task)
for project in projects_to_change:
project.tasks.remove(task)

elif action == 'post_add':
# Add the task to projects whose catalog is now linked to this task
projects_to_change = Project.objects.filter(catalog__in=task.catalogs.all()).exclude(tasks=task)
for project in projects_to_change:
project.tasks.add(task)
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, **kwargs):

task = instance
sites = model.objects.filter(pk__in=kwargs['pk_set'])
catalogs = task.catalogs.all() or Catalog.objects.all() # If no catalogs, consider all

if action in ('post_remove', 'post_clear'):
# Remove the task from projects whose site is no longer linked to this task
site_candidates = Site.objects.exclude(id__in=sites.values_list('id', flat=True))
projects_to_change = Project.objects.filter(site__in=site_candidates, catalog__in=catalogs, tasks=task)
for project in projects_to_change:
project.tasks.remove(task)

elif action == 'post_add':
# Add the task to projects whose site is now linked to this task
site_candidates = sites
projects_to_change = Project.objects.filter(site__in=site_candidates, catalog__in=catalogs).exclude(tasks=task)
for project in projects_to_change:
project.tasks.add(task)
def m2m_changed_task_sites_signal(sender, instance, action, model, pk_set, **kwargs):
"""
Synchronize Project relationships when a Task's sites are updated.
"""
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=None, **kwargs):

task = instance
groups = task.groups.all()
catalogs = task.catalogs.all() or Catalog.objects.all() # If no catalogs, consider all

if action in ('post_remove', 'post_clear'):
# Remove the task from projects whose group is no longer linked to this task
users = User.objects.exclude(groups__in=groups)
memberships = Membership.objects.filter(role='owner', user__in=users).values_list('id', flat=True)
projects_to_change = Project.objects.filter(memberships__in=memberships, catalog__in=catalogs, tasks=task)
for project in projects_to_change:
project.tasks.remove(task)

elif action == 'post_add':
# Add the task to projects whose group is now linked to this task
users = User.objects.filter(groups__in=groups)
memberships = Membership.objects.filter(role='owner', user__in=users).values_list('id', flat=True)
projects_to_change = Project.objects.filter(
memberships__in=memberships, catalog__in=catalogs).exclude(tasks=task)
for project in projects_to_change:
project.tasks.add(task)
def m2m_changed_task_groups_signal(sender, instance, action, model, pk_set, **kwargs):
"""
Synchronize Project relationships when a Task's groups are updated.
"""
m2m_groups_changed_projects_sync_signal_handler(
action=action,
model=model,
pk_set=pk_set,
instance=instance,
project_field='tasks'
)
94 changes: 29 additions & 65 deletions rdmo/projects/handlers/project_views.py
Original file line number Diff line number Diff line change
@@ -1,84 +1,48 @@
import logging

from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from django.db.models.signals import m2m_changed
from django.dispatch import receiver

from rdmo.projects.models import Membership, Project
from rdmo.questions.models import Catalog
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,
)

logger = logging.getLogger(__name__)


@receiver(m2m_changed, sender=View.catalogs.through)
def m2m_changed_view_catalog_signal(sender, instance, action, model, pk_set, **kwargs):
view = instance

if action == 'post_remove':
# catalogs that were changed
catalogs = model.objects.filter(pk__in=pk_set)
# Remove the view from projects whose catalog is no longer linked to this view
projects_to_change = Project.objects.filter(catalog__in=catalogs, views=view)
for project in projects_to_change:
project.views.remove(view)
m2m_catalogs_changed_projects_sync_signal_handler(
action=action,
related_model=model,
pk_set=pk_set,
instance=instance,
project_field='views',
)

elif action == 'post_clear':
# Remove the view from all projects that were using this view
for project in Project.objects.filter(views=view):
project.views.remove(view)

elif action == 'post_add':
# Add the view to projects whose catalog is now linked to this view
for project in Project.objects.filter(catalog__in=view.catalogs.all()):
project.views.add(view)


@receiver(m2m_changed, sender=View.sites.through)
def m2m_changed_view_sites_signal(sender, instance, action, model, **kwargs):
# sites = instance.sites.all()
view = instance
sites = model.objects.filter(pk__in=kwargs['pk_set'])
catalogs = view.catalogs.all() or Catalog.objects.all() # If no catalogs, consider all

if action in ('post_remove', 'post_clear'):
# Remove the view from projects whose site is no longer linked to this view
site_candidates = Site.objects.exclude(id__in=sites.values_list('id', flat=True))
projects_to_change = Project.objects.filter(site__in=site_candidates, catalog__in=catalogs, views=view)
for project in projects_to_change:
project.views.remove(view)

elif action == 'post_add':
# Add the view to projects whose site is now linked to this view
site_candidates = sites
projects_to_change = Project.objects.filter(site__in=site_candidates, catalog__in=catalogs).exclude(views=view)
for project in projects_to_change:
project.views.add(view)
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' # Field to update on Project
)


@receiver(m2m_changed, sender=View.groups.through)
def m2m_changed_view_groups_signal(sender, instance, action=None, **kwargs):
"""
Synchronize Projects when a View's groups are updated.
"""
view = instance
groups = view.groups.all()
catalogs = view.catalogs.all() or Catalog.objects.all() # If no catalogs, consider all

if action in ('post_remove', 'post_clear'):
# Remove the view from projects whose group is no longer linked to this view
users = User.objects.exclude(groups__in=groups)
memberships = Membership.objects.filter(role='owner', user__in=users).values_list('id', flat=True)
projects_to_change = Project.objects.filter(memberships__in=memberships, catalog__in=catalogs, views=view)
for project in projects_to_change:
project.views.remove(view)

elif action == 'post_add':
# Add the view to projects whose group is now linked to this view
users = User.objects.filter(groups__in=groups)
memberships = Membership.objects.filter(role='owner', user__in=users).values_list('id', flat=True)
projects_to_change = Project.objects.filter(
memberships__in=memberships, catalog__in=catalogs).exclude(views=view)
for project in projects_to_change:
project.views.add(view)
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' # Field to update on Project
)
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
Empty file added rdmo/projects/tests/helpers.py
Empty file.

0 comments on commit aa42691

Please sign in to comment.