# -*- coding: utf-8 -*-
# Akvo RSR is covered by the GNU Affero General Public License.
# See more details in the license.txt file located at the root folder of the Akvo RSR module.
# For additional details on the GNU license please see < http://www.gnu.org/licenses/agpl.html >.
from django.db import models
from django.db.models import Q
from django.utils.translation import gettext_lazy as _
from ..fields import ValidXMLCharField
from akvo.codelists.models import RelatedActivityType
from akvo.codelists.store.default_codelists import RELATED_ACTIVITY_TYPE
from akvo.utils import codelist_choices, codelist_value
from django.db.models.signals import pre_delete, pre_save
from django.dispatch import receiver
[docs]def get_project_parents(project):
from akvo.rsr.models import Project
return Project.objects.filter(
Q(related_projects__related_project=project, related_projects__relation=RelatedProject.PROJECT_RELATION_CHILD)
| Q(related_projects__project=project, related_projects__relation=RelatedProject.PROJECT_RELATION_PARENT)
).distinct()
[docs]class MultipleParentsDisallowed(Exception):
"""Exception raised when trying to create multiple parents for a project."""
message = _('A project can have only one parent.')
def __str__(self):
return str(self.message)
[docs]class ParentChangeDisallowed(Exception):
"""Exception raised when trying to change parent after importing results."""
message = _("Cannot change a project's parent after importing results.")
def __str__(self):
return str(self.message)
PARENT_RELATIONS = {
RelatedProject.PROJECT_RELATION_CHILD,
RelatedProject.PROJECT_RELATION_PARENT
}
[docs]@receiver(pre_save, sender=RelatedProject)
def validate_parents(sender, **kwargs):
"""Validate creation and changing of parents for a project.
1. Prevent creating multiple parents for a project.
2. Prevent modifying the parent for a project that has already imported
results from the parent.
"""
# Disable signal handler when loading fixtures
if kwargs.get('raw', False):
return
from akvo.rsr.models import Result
related_project = kwargs['instance']
# Creating a new parent/child relation
if related_project.id is None and related_project.relation in PARENT_RELATIONS:
if related_project.relation == RelatedProject.PROJECT_RELATION_CHILD:
child_project = related_project.related_project
else:
child_project = related_project.project
# Allow only one parent
parents = get_project_parents(child_project)
if child_project is not None and parents.exists():
raise MultipleParentsDisallowed
# Changing an existing parent/child relation
elif related_project.id is not None and related_project.relation in PARENT_RELATIONS:
if related_project.relation == RelatedProject.PROJECT_RELATION_CHILD:
parent_project = related_project.project
child_project = related_project.related_project
else:
child_project = related_project.project
parent_project = related_project.related_project
project_results = Result.objects.filter(project=child_project)
child_results = project_results.exclude(parent_result=None)
other_parent_child_results = child_results.exclude(parent_result__project=parent_project)
if other_parent_child_results.exists():
raise ParentChangeDisallowed
[docs]@receiver(pre_delete, sender=RelatedProject)
def prevent_parent_delete(sender, **kwargs):
# Disable signal handler when loading fixtures
if kwargs.get('raw', False):
return
from akvo.rsr.models import Result
related_project = kwargs['instance']
if related_project.id is not None and related_project.relation in PARENT_RELATIONS:
if related_project.relation == RelatedProject.PROJECT_RELATION_CHILD:
parent_project = related_project.project
child_project = related_project.related_project
else:
child_project = related_project.project
parent_project = related_project.related_project
if not parent_project or not child_project:
return
project_results = Result.objects.filter(project=child_project)
child_results = project_results.filter(parent_result__project=parent_project)
if child_results.exists():
raise ParentChangeDisallowed