Source code for akvo.rsr.models.result.result

# -*- 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.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _

from akvo.codelists.models import ResultType
from akvo.codelists.store.default_codelists import RESULT_TYPE
from akvo.rsr.fields import ValidXMLCharField
from akvo.utils import codelist_choices, codelist_value


[docs]class Result(models.Model): project = models.ForeignKey('Project', on_delete=models.CASCADE, verbose_name=_('project'), related_name='results') title = ValidXMLCharField( _('result title'), blank=True, max_length=500, help_text=_('The aim of the project in one sentence. This doesn’t need to be something ' 'that can be directly counted, but it should describe an overall goal of the ' 'project. There can be multiple results for one project.') ) type = ValidXMLCharField( _('result type'), blank=True, max_length=1, choices=codelist_choices(RESULT_TYPE), help_text=_('Choose whether the result is an output, outcome or impact.<br/>' '1 - Output: Direct result of the project activities. E.g. number of booklets ' 'produced, workshops held, people trained, latrines build.<br/>' '2 - Outcome: The changes or benefits that result from the program activities ' 'and resulting outputs. E.g number of beneficiaries reached, knowledge ' 'increased, capacity build, monitored behaviour change.<br/>' '3 - Impact: Long-term results of program (on population) that can be ' 'attributed to the project outputs and outcomes. E.g improved health, ' 'increased political participation of women.<br/>' '9 - Other: Another type of result, not specified above.') ) aggregation_status = models.BooleanField( _('aggregation status'), blank=True, null=True, help_text=_('Indicate whether the data in the result set can be accumulated.') ) description = ValidXMLCharField( _('result description'), blank=True, max_length=2000, help_text=_('You can provide further information of the result here.') ) parent_result = models.ForeignKey('self', on_delete=models.SET_NULL, blank=True, null=True, default=None, help_text=_('The parent result of this result.'), related_name='child_results') order = models.PositiveSmallIntegerField(_('result order'), null=True, blank=True) def __str__(self): result_unicode = self.title if self.title else '%s' % _('No result title') if self.type: result_unicode += ' (' + self.iati_type().name + ')' if self.indicators.all(): result_unicode += _(' - %s indicators') % (str(self.indicators.count())) return result_unicode
[docs] def save(self, *args, **kwargs): """Update the values of child results, if a parent result is updated.""" is_new_result = not self.pk if is_new_result and Result.objects.filter(project_id=self.project.id).exists(): prev_result = Result.objects.filter(project_id=self.project.id).reverse()[0] if prev_result.order: self.order = prev_result.order + 1 super(Result, self).save(*args, **kwargs) for child_result in self.child_results.all(): # Always copy title, type, order and aggregation status. They should be the same as the parent. child_result.title = self.title child_result.type = self.type child_result.aggregation_status = self.aggregation_status child_result.order = self.order # Only copy the description if the child has none (e.g. new) if not child_result.description and self.description: child_result.description = self.description child_result.save() if is_new_result: self.project.copy_result_to_children(self)
[docs] def clean(self): validation_errors = {} if self.pk and self.parent_result: orig_result = Result.objects.get(pk=self.pk) # Don't allow some values to be changed when it is a child result if self.project != orig_result.project: validation_errors['project'] = '%s' % \ _('It is not possible to update the project of this result, ' 'because it is linked to a parent result.') if self.title != orig_result.title: validation_errors['title'] = '%s' % \ _('It is not possible to update the title of this result, ' 'because it is linked to a parent result.') if self.type != orig_result.type: validation_errors['type'] = '%s' % \ _('It is not possible to update the type of this result, ' 'because it is linked to a parent result.') if self.aggregation_status != orig_result.aggregation_status: validation_errors['aggregation_status'] = '%s' % \ _('It is not possible to update the aggregation status of this result, ' 'because it is linked to a parent result.') if validation_errors: raise ValidationError(validation_errors)
[docs] def delete(self, *args, **kwargs): """ Check if indicator is ordered manually, and cascade following indicators if needed """ if self.order: sibling_results = Result.objects.filter(project_id=self.project.id) if not self == sibling_results.reverse()[0]: for ind in range(self.order + 1, len(sibling_results)): if sibling_results[ind].order: sibling_results[ind].order -= 1 sibling_results[ind].save() super(Result, self).delete(*args, **kwargs)
[docs] def iati_type(self): return codelist_value(ResultType, self, 'type')
[docs] def iati_type_unicode(self): return str(self.iati_type())
[docs] def has_info(self): if self.title or self.type or self.aggregation_status or self.description: return True return False
[docs] def is_calculated(self): return self.project.is_impact_project
[docs] def parent_project(self): """ Return a dictionary of this result's parent project. """ if self.parent_result: return {self.parent_result.project.id: self.parent_result.project.title} return {}
[docs] def child_projects(self): """ Return a dictionary of this result's child projects. """ projects = {} for result in Result.objects.filter(parent_result=self).select_related('project'): projects[result.project.id] = result.project.title return projects
[docs] def descendants(self, depth=None): family = {self.pk} search_depth = 0 while depth is None or search_depth < depth: children = Result.objects.filter(parent_result__in=family).values_list('pk', flat=True) if family.union(children) == family: break family = family.union(children) search_depth += 1 return Result.objects.filter(pk__in=family)
class Meta: app_label = 'rsr' ordering = ['order', 'id'] verbose_name = _('result') verbose_name_plural = _('results') unique_together = ('project', 'parent_result')