class IndicatorDataElementBase(aristotleComponent):
    class Meta:
        abstract = True
        ordering = ['order']

    indicator = ConceptForeignKey(Indicator, on_delete=models.CASCADE)
    order = models.PositiveSmallIntegerField(
        "Order",
        help_text=_("The position of this data element in the indicator"))
    guide_for_use = MDR.RichTextField(blank=True)
    data_element = ConceptForeignKey(MDR.DataElement,
                                     blank=True,
                                     null=True,
                                     on_delete=models.SET_NULL)
    data_set_specification = ConceptForeignKey(
        aristotle_dse.DataSetSpecification,
        blank=True,
        null=True,
        on_delete=models.SET_NULL)
    data_set = ConceptForeignKey(aristotle_dse.Dataset,
                                 blank=True,
                                 null=True,
                                 on_delete=models.SET_NULL)
    description = MDR.RichTextField(blank=True)

    inline_field_layout = 'list'

    parent_field_name = 'indicator'

    # Provide a specific field ordering for the advanced metadata editor.
    inline_field_order: List[str] = [
        "data_element",
        "data_set_specification",
        "data_set",
        "description",
        "guide_for_use",
        "order",
    ]

    @property
    def inline_editor_description(self):
        fields = []
        if self.data_element:
            fields.append(f'Data element: {self.data_element.name}')
        if self.data_set_specification:
            fields.append(
                f'Data set specification: {self.data_set_specification}')
        if self.data_set:
            fields.append(f'Data set: {self.data_set}')

        return fields
class RelationRole(MDR.aristotleComponent):  # 9.1.2.5
    name = models.TextField(help_text=_(
        "The primary name used for human identification purposes."))
    definition = models.TextField(
        _('definition'),
        help_text=_(
            "Representation of a concept by a descriptive statement "
            "which serves to differentiate it from related concepts. (3.2.39)")
    )
    multiplicity = models.PositiveIntegerField(  # 9.1.2.5.3.1
        # a.k.a the number of times it can appear in a link :(
        help_text=_(
            'number of links which must (logically) be members of the source '
            'relation of this role, differing only by an end with this role as '
            'an end_role.'),
        null=True,
        blank=True,
    )
    ordinal = models.PositiveIntegerField(  # 9.1.2.5.3.2
        help_text=
        _('order of the relation role among other relation roles in the relation.'
          ))
    relation = ConceptForeignKey(Relation)

    @property
    def parentItem(self):
        return self.relation

    def __str__(self):
        return "{0.name}".format(self)
class IndicatorInclusion(aristotleComponent):
    order = models.PositiveSmallIntegerField(
        "Order", help_text=_("The position of this indicator in the set"))
    indicator_set = ConceptForeignKey(IndicatorSet, on_delete=models.CASCADE)
    indicator = ConceptForeignKey(Indicator,
                                  blank=True,
                                  null=True,
                                  on_delete=models.SET_NULL)
    name = models.CharField(
        max_length=1024,
        blank=True,
        help_text=_("The name identifying this indicator in the set"))
    parent_field_name = 'indicator_set'

    class Meta:
        ordering = ['order']
Beispiel #4
0
class Link(TimeStampedModel):
    """
    Link is a class each instance of which models a link (3.2.69).
    A link is a member of a relation (3.2.119) (not an instance of a relation).
    In relational database parlance, a link would be a tuple (row) in a relation (table).
    Link is a subclass of Assertion (9.1.2.3), and as such is included in one or more
    Concept_Systems (9.1.2.2) through the assertion_inclusion (9.1.3.5) association.
    """
    relation = ConceptForeignKey(Relation)
    root_item = ConceptForeignKey(MDR._concept, related_name='owned_links')

    def concepts(self):
        return MDR._concept.objects.filter(linkend__link=self).all().distinct()

    def add_link_end(self, role, concept):
        return LinkEnd.objects.create(link=self, role=role, concept=concept)
class UserViewHistory(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="recently_viewed_metadata")
    concept = ConceptForeignKey(MDR._concept, related_name='user_view_history')
    view_date = models.DateTimeField(
        default=now,
        help_text=_("When the item was viewed")
    )
Beispiel #6
0
class DSSClusterInclusion(DSSInclusion):
    """
    The child in this relationship is considered to be a child of the parent DSS as specified by the `dss` property.
    """
    child = ConceptForeignKey(DataSetSpecification,
                              related_name='parent_dss',
                              on_delete=models.CASCADE)

    inline_field_layout = 'list'
    inline_field_order = [
        "order", "dss", "child", "reference", "inclusion",
        "maximum_occurrences", "conditional_inclusion", "specific_information"
    ]

    class Meta(DSSInclusion.Meta):
        verbose_name = "DSS Cluster"

    @property
    def include(self):
        return self.child

    def inline_editor_description(self):
        if self.order:
            return "Cluster '{cls}' at position {pos}".format(
                cls=self.child.name, pos=self.order)
        return "Cluster '{}'".format(self.child.name)

    def __str__(self):
        return "Cluster {cls} at position {pos}".format(cls=self.child_id,
                                                        pos=self.order)
Beispiel #7
0
class DSSGrouping(aristotle.models.aristotleComponent):
    class Meta:
        ordering = ['order']
        verbose_name = 'DSS Grouping'

    inline_field_layout = 'list'
    parent_field_name = 'dss'

    dss = ConceptForeignKey(DataSetSpecification,
                            related_name="groups",
                            on_delete=models.CASCADE)
    name = ShortTextField(help_text=_("The name applied to the grouping."))
    definition = RichTextField(
        _('definition'),
        blank=True,
    )
    linked_group = models.ManyToManyField('self',
                                          blank=True,
                                          symmetrical=False)
    order = models.PositiveSmallIntegerField(
        "Position",
        null=True,
        blank=True,
    )
    parent = 'dss'

    def __str__(self):
        return self.name
Beispiel #8
0
class DistributionDataElementPath(aristotle.models.aristotleComponent):
    class Meta:
        ordering = ['order']

    parent_field_name = 'distribution'

    # TODO: Set this to NOT NULL
    distribution = models.ForeignKey(
        Distribution,
        blank=True,
        null=True,
        on_delete=models.CASCADE,
        help_text=_('A relation to the DCAT Distribution Record.'),
    )
    data_element = ConceptForeignKey(
        aristotle.models.DataElement,
        blank=True,
        null=True,
        help_text=_('An entity responsible for making the dataset available.'),
        verbose_name='Data Element',
        on_delete=models.SET_NULL,
    )
    logical_path = models.CharField(
        max_length=256,
        help_text=
        _("A text expression that specifies how to identify which series of data in the distribution maps to this data element"
          ))
    order = models.PositiveSmallIntegerField(
        "Position",
        null=True,
        blank=True,
        help_text=_("Column position within a dataset."))
    specialisation_classes = ConceptManyToManyField(
        aristotle.models.ObjectClass, help_text=_(""), blank=True)
class DistributionDataElementPath(aristotle.models.aristotleComponent):
    class Meta:
        ordering = ['order']

    @property
    def parentItem(self):
        return self.distribution

    distribution = models.ForeignKey(
        Distribution,
        blank=True,
        null=True,
        help_text=_('A relation to the DCAT Distribution Record.'),
    )
    data_element = ConceptForeignKey(
        aristotle.models.DataElement,
        blank=True,
        null=True,
        help_text=_('An entity responsible for making the dataset available.'),
    )
    logical_path = models.CharField(
        max_length=256,
        help_text=
        _("A text expression that specifies how to identify which series of data in the distribution maps to this data element"
          ))
    order = models.PositiveSmallIntegerField(
        "Position",
        null=True,
        blank=True,
        help_text=_("Column position within a dataset."))
    specialisation_classes = ConceptManyToManyField(
        aristotle.models.ObjectClass, help_text=_(""), blank=True)
class Slot(TimeStampedModel):
    # on save confirm the concept and model are correct, otherwise reject
    # on save confirm the cardinality
    name = models.CharField(max_length=256)  # Or some other sane length
    type = models.CharField(max_length=256,
                            blank=True)  # Or some other sane length
    concept = ConceptForeignKey(MDR._concept,
                                related_name='slots',
                                on_delete=models.CASCADE)
    value = models.TextField()
    order = models.PositiveSmallIntegerField("Position", default=0)
    permission = models.IntegerField(choices=permission_choices,
                                     default=permission_choices.public)

    objects = SlotsManager()

    @property
    def hr_permission(self):
        """Human readable permission"""
        return permission_choices[self.permission]

    def __str__(self):
        return u"{0} - {1}".format(self.name, self.value)

    class Meta:
        ordering = ['order']
Beispiel #11
0
class ScopedIdentifier(TimeStampedModel, MDR.aristotleComponent):
    namespace = models.ForeignKey(Namespace)
    concept = ConceptForeignKey(MDR._concept, related_name='identifiers')
    identifier = models.CharField(  # 7.2.2.2.2.1
        max_length=512,
        help_text=
        _('String used to unambiguously denote an Item within the scope of a specified Namespace.'
          ))
    version = models.CharField(  # 7.2.2.2.2.2
        max_length=512,
        help_text=
        _('unique version identifier of the Scoped_Identifier which identifies an Item'
          ),
        blank=True,
        default="")
    order = models.PositiveSmallIntegerField("Position", default=0)

    class Meta:
        unique_together = ("namespace", "identifier", "version")
        ordering = ['order']

    def __str__(self):
        return u"{0}:{1}:{2}".format(self.namespace.naming_authority.name,
                                     self.identifier, self.version)

    @property
    def prefix(self):
        return self.namespace.shorthand_prefix

    @property
    def parentItem(self):
        return self.concept
class Framework(aristotle.models.concept):
    template = "comet/framework.html"
    parentFramework = ConceptForeignKey('Framework',
                                        blank=True,
                                        null=True,
                                        related_name="childFrameworks")
    indicators = ConceptManyToManyField(Indicator,
                                        related_name="frameworks",
                                        blank=True)
class LinkEnd(TimeStampedModel):  # 9.1.2.7
    link = models.ForeignKey(Link)
    role = models.ForeignKey(RelationRole)
    concept = ConceptForeignKey(MDR._concept)

    def clean(self):
        if self.role.relation != self.link.relation:
            raise ValidationError(
                _('A link ends role relation must be from the set of roles on the links relation'
                  ))
class Slot(TimeStampedModel):
    # on save confirm the concept and model are correct, otherwise reject
    # on save confirm the cardinality
    name = models.CharField(max_length=256)  # Or some other sane length
    type = models.CharField(max_length=256,
                            blank=True)  # Or some other sane length
    concept = ConceptForeignKey(MDR._concept, related_name='slots')
    value = models.TextField()

    def __str__(self):
        return u"{0} - {1}".format(self.name, self.value)
class DSSClusterInclusion(DSSInclusion):
    """
    The child in this relationship is considered to be a child of the parent DSS as specified by the `dss` property.
    """
    child = ConceptForeignKey(DataSetSpecification, related_name='parent_dss')

    class Meta(DSSInclusion.Meta):
        verbose_name = "DSS Cluster Inclusion"

    @property
    def include(self):
        return self.child
class DSSDEInclusion(DSSInclusion):
    data_element = ConceptForeignKey(aristotle.models.DataElement,
                                     related_name="dssInclusions")
    specialisation_classes = ConceptManyToManyField(
        aristotle.models.ObjectClass, help_text=_(""))

    class Meta(DSSInclusion.Meta):
        verbose_name = "DSS Data Element Inclusion"

    @property
    def include(self):
        return self.data_element
Beispiel #17
0
class CustomValue(TimeStampedModel):
    field = models.ForeignKey(CustomField, related_name='values')
    content = models.TextField()
    concept = ConceptForeignKey(_concept)

    objects = CustomValueManager()

    class Meta:
        ordering = ['field__order']
        unique_together = ('field', 'concept')

    @property
    def is_html(self):
        return self.field.type == 'html'
class DSSDEInclusion(DSSInclusion):
    data_element = ConceptForeignKey(aristotle.models.DataElement,
                                     related_name="dssInclusions",
                                     on_delete=models.CASCADE)
    group = models.ForeignKey(DSSGrouping,
                              blank=True,
                              null=True,
                              on_delete=models.SET_NULL)
    specialisation_classes = ConceptManyToManyField(
        aristotle.models.ObjectClass,
        help_text=_(""),
        blank=True,
    )

    inline_field_layout = 'list'
    inline_field_order = [
        "order", "dss", "data_element", "reference", "inclusion",
        "maximum_occurrences", "conditional_inclusion", "specific_information",
        "group", "specialisation_classes"
    ]

    class Meta(DSSInclusion.Meta):
        verbose_name = "DSS Data Element"

    @property
    def include(self):
        return self.data_element

    @property
    def inline_editor_description(self):
        if self.group:
            msg = [
                "Data element: '{de}' in group '{group}'".format(
                    de=self.data_element.name, group=self.group.name)
            ]
        else:
            msg = ['Data element: {de}'.format(de=self.data_element.name)]
        return msg

    def __str__(self):
        has_reference = self.reference is not None and self.reference != ''
        if has_reference:
            return 'Data element {} at position {} with reference: {}.'.format(
                self.data_element_id, self.order, self.reference)
        return 'Data element {} at position {}'.format(self.data_element_id,
                                                       self.order)

    def get_absolute_url(self):
        pass
Beispiel #19
0
class AbstractValue(aristotleComponent):
    """
    Implementation note: Not the best name, but there will be times to
    subclass a "value" when its not just a permissible value.
    """
    class Meta:
        abstract = True
        ordering = ['order']

    value = ShortTextField(  # 11.3.2.7.2.1 - Renamed from permitted value for abstracts
        help_text=_("the actual value of the Value"))
    meaning = ShortTextField(  # 11.3.2.7.1
        help_text=
        _("A textual designation of a value, where a relation to a Value meaning doesn't exist"
          ),
        blank=True)
    value_meaning = models.ForeignKey(  # 11.3.2.7.1
        'ValueMeaning',
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        help_text=_(
            'A reference to the value meaning that this designation relates to'
        ))
    # Below will generate exactly the same related name as django, but reversion-compare
    # needs an explicit related_name for some actions.
    valueDomain = ConceptForeignKey(
        'ValueDomain',
        related_name="%(class)s_set",
        help_text=_(
            "Enumerated Value Domain that this value meaning relates to"),
        verbose_name='Value Domain',
        on_delete=models.CASCADE,
    )
    order = models.PositiveSmallIntegerField("Position")
    start_date = models.DateField(
        blank=True,
        null=True,
        help_text=_('Date at which the value became valid'))
    end_date = models.DateField(
        blank=True,
        null=True,
        help_text=_('Date at which the value ceased to be valid'))

    parent_field_name = 'valueDomain'

    def __str__(self):
        return "%s: %s - %s" % (self.valueDomain.name, self.value,
                                self.meaning)
class Link(TimeStampedModel):
    """
    Link is a class each instance of which models a link (3.2.69).
    A link is a member of a relation (3.2.119) (not an instance of a relation).
    In relational database parlance, a link would be a tuple (row) in a relation (table).
    Link is a subclass of Assertion (9.1.2.3), and as such is included in one or more
    Concept_Systems (9.1.2.2) through the assertion_inclusion (9.1.3.5) association.
    """
    relation = ConceptForeignKey(Relation, on_delete=models.CASCADE)
    root_item = ConceptForeignKey(MDR._concept, related_name='owned_links', on_delete=models.CASCADE)

    def concepts(self):
        return MDR._concept.objects.filter(linkend__link=self).all().distinct()

    def add_link_end(self, role, concept):
        return LinkEnd.objects.create(link=self, role=role, concept=concept)

    def get_readable_concepts(self) -> str:
        """
        Get a "readable" version of the concepts connected by this Link object.
        e.g. "MyLinkObject 1, MyLinkObject 2 and MyLinkObject 3"
        """
        return get_text_list(list(MDR._concept.objects.filter(linkend__link=self).all().
                                  distinct().values_list('name', flat=True)), 'and')
class Indicator(aristotle.models.concept):
    """
    An indicator is a single measure that is reported on regularly
    and that provides relevant and actionable information about population or system performance.
    """
    template = "comet/indicator.html"
    dataElementConcept = ConceptForeignKey(aristotle.models.DataElementConcept,
                                           verbose_name="Data Element Concept",
                                           blank=True,
                                           null=True)
    valueDomain = ConceptForeignKey(aristotle.models.ValueDomain,
                                    verbose_name="Value Domain",
                                    blank=True,
                                    null=True)
    outcome_areas = ConceptManyToManyField('OutcomeArea',
                                           related_name="indicators",
                                           blank=True)

    indicatorType = ConceptForeignKey(IndicatorType, blank=True, null=True)
    numerators = ConceptManyToManyField(aristotle.models.DataElement,
                                        related_name="as_numerator",
                                        blank=True)
    denominators = ConceptManyToManyField(aristotle.models.DataElement,
                                          related_name="as_denominator",
                                          blank=True)
    disaggregators = ConceptManyToManyField(aristotle.models.DataElement,
                                            related_name="as_disaggregator",
                                            blank=True)

    numerator_description = models.TextField(blank=True)
    numerator_computation = models.TextField(blank=True)
    denominator_description = models.TextField(blank=True)
    denominator_computation = models.TextField(blank=True)
    computationDescription = RichTextField(blank=True)
    rationale = RichTextField(blank=True)
    disaggregation_description = RichTextField(blank=True)
Beispiel #22
0
class DSSInclusion(aristotle.models.aristotleComponent):
    class Meta:
        abstract = True
        ordering = ['order']

    inline_field_layout = 'list'
    parent_field_name = 'dss'

    reference = models.CharField(
        max_length=512,
        blank=True,
        help_text=_(
            "Optional field for refering to this item within the DSS."),
        default='')

    dss = ConceptForeignKey(DataSetSpecification, on_delete=models.CASCADE)
    maximum_occurrences = models.PositiveIntegerField(
        default=1,
        verbose_name=_("Maximum Occurrences"),
        help_text=_(
            "The maximum number of times a item can be included in a dataset"))
    inclusion = models.CharField(
        "Inclusion",
        choices=CARDINALITY,
        default=CARDINALITY.conditional,
        max_length=20,
        help_text=
        _("Specifies if a field is required, optional or conditional within a dataset based on this specification."
          ))
    specific_information = RichTextField(
        blank=True,
        help_text=
        _("Any additional information on the inclusion of a data element or cluster in a dataset."
          ))
    conditional_inclusion = RichTextField(
        "Conditional Inclusion",
        blank=True,
        help_text=
        _("If an item is present conditionally, this field defines the conditions under which an item will appear."
          ))
    order = models.PositiveSmallIntegerField(
        "Position",
        null=True,
        blank=True,
        help_text=
        _("If a dataset is ordered, this indicates which position this item is in a dataset."
          ))
Beispiel #23
0
class Issue(TimeStampedModel):

    name = models.CharField(max_length=1000)
    description = models.TextField(blank=True)
    item = ConceptForeignKey(_concept, related_name='issues')
    submitter = models.ForeignKey(settings.AUTH_USER_MODEL,
                                  related_name='issues')
    isopen = models.BooleanField(default=True)

    def can_edit(self, user):
        return user.id == self.submitter.id

    def can_view(self, user):
        return self.item.can_view(user)

    def can_alter_open(self, user):
        return self.can_edit(user) or self.item.can_edit(user)
Beispiel #24
0
class DSSInclusion(aristotle.models.aristotleComponent):
    class Meta:
        abstract = True
        ordering = ['order']

    dss = ConceptForeignKey(DataSetSpecification)
    maximum_occurances = models.PositiveIntegerField(
        default=1,
        help_text=_(
            "The maximum number of times a item can be included in a dataset"))
    cardinality = models.CharField(
        choices=CARDINALITY,
        default=CARDINALITY.conditional,
        max_length=20,
        help_text=
        _("Specifies if a field is required, optional or conditional within a dataset based on this specification."
          ))
    specific_information = RichTextField(
        blank=True,
        help_text=
        _("Any additional information on the inclusion of a data element or cluster in a dataset."
          ))  # may need to become HTML field.
    conditional_obligation = models.TextField(
        blank=True,
        help_text=
        _("If an item is present conditionally, this field defines the conditions under which an item will appear."
          ))
    order = models.PositiveSmallIntegerField(
        "Position",
        null=True,
        blank=True,
        help_text=
        _("If a dataset is ordered, this indicates which position this item is in a dataset."
          ))

    @property
    def parentItem(self):
        return self.dss

    @property
    def parentItemId(self):
        return self.dss_id
class CustomValue(TimeStampedModel):
    field = models.ForeignKey(CustomField,
                              related_name='values',
                              on_delete=models.CASCADE)
    content = models.TextField(blank=True)
    concept = ConceptForeignKey(_concept, on_delete=models.CASCADE)

    objects = CustomValueManager()

    class Meta:
        ordering = ['field__order']
        unique_together = ('field', 'concept')

    @property
    def is_html(self):
        return self.field.type == 'html'

    def __str__(self):
        return 'CustomValue with field "{}" for concept "{}"'.format(
            self.field, self.concept)
Beispiel #26
0
class Slot(TimeStampedModel):
    # on save confirm the concept and model are correct, otherwise reject
    # on save confirm the cardinality
    name = models.CharField(max_length=256)  # Or some other sane length
    type = models.CharField(max_length=256, blank=True)  # Or some other sane length
    concept = ConceptForeignKey(MDR._concept, related_name='slots')
    value = models.TextField()
    order = models.PositiveSmallIntegerField("Position", default=0)
    permission = models.IntegerField(
        choices=(
            (0, 'Public'),
            (1, 'Authenticated'),
            (2, 'Workgroup'),
        ),
        default=0
    )

    def __str__(self):
        return u"{0} - {1}".format(self.name, self.value)

    class Meta:
        ordering = ['order']
class FrameworkDimension(MPTTModel, TimeStampedModel, aristotleComponent):
    parent_field_name = 'framework'

    objects = FrameworkDimensionManager()
    framework = ConceptForeignKey('Framework', on_delete=models.CASCADE)
    name = models.CharField(max_length=2048)
    description = MDR.RichTextField(blank=True)
    parent = TreeForeignKey('self',
                            on_delete=models.CASCADE,
                            null=True,
                            blank=True,
                            related_name='child_dimensions')

    class MPTTMeta:
        order_insertion_by = ['name']

    @property
    def parentItem(self):
        return self.framework

    def __str__(self):
        return self.name
class DataSetSpecification(aristotle.models.concept):
    """
    A collection of :model:`aristotle_mdr.DataElement`\s
    specifying the order and fields required for a standardised
    :model:`aristotle_dse.DataSource`.
    """
    edit_page_excludes = ['clusters', 'data_elements']
    serialize_weak_entities = [
        ('clusters', 'dssclusterinclusion_set'),
        ('data_elements', 'dssdeinclusion_set'),
    ]

    template = "aristotle_dse/concepts/dataSetSpecification.html"
    ordered = models.BooleanField(
        default=False,
        help_text=
        _("Indicates if the ordering for a dataset is must match exactly the order laid out in the specification."
          ))
    statistical_unit = ConceptForeignKey(
        aristotle.models._concept,
        related_name='statistical_unit_of',
        blank=True,
        null=True,
        help_text=
        _("Indiciates if the ordering for a dataset is must match exactly the order laid out in the specification."
          ))
    data_elements = ConceptManyToManyField(aristotle.models.DataElement,
                                           blank=True,
                                           through='DSSDEInclusion')
    clusters = ConceptManyToManyField('self',
                                      through='DSSClusterInclusion',
                                      blank=True,
                                      null=True,
                                      symmetrical=False)
    collection_method = aristotle.models.RichTextField(blank=True,
                                                       help_text=_(''))
    implementation_start_date = models.DateField(blank=True,
                                                 null=True,
                                                 help_text=_(''))
    implementation_end_date = models.DateField(blank=True,
                                               null=True,
                                               help_text=_(''))

    def addDataElement(self, data_element, **kwargs):
        inc = DSSDEInclusion.objects.get_or_create(data_element=data_element,
                                                   dss=self,
                                                   defaults=kwargs)

    def addCluster(self, child, **kwargs):
        inc = DSSClusterInclusion.objects.get_or_create(child=child,
                                                        dss=self,
                                                        defaults=kwargs)

    @property
    def registry_cascade_items(self):
        return list(self.clusters.all()) + list(self.data_elements.all())

    def get_download_items(self):
        return [
            (DataSetSpecification, self.clusters.all()),
            (aristotle.models.DataElement, self.data_elements.all()),
            (aristotle.models.ObjectClass,
             aristotle.models.ObjectClass.objects.filter(
                 dataelementconcept__dataelement__datasetspecification=self)),
            (aristotle.models.Property,
             aristotle.models.Property.objects.filter(
                 dataelementconcept__dataelement__datasetspecification=self)),
            (aristotle.models.ValueDomain,
             aristotle.models.ValueDomain.objects.filter(
                 dataelement__datasetspecification=self)),
        ]
class Issue(TimeStampedModel):

    # Fields on a concept that are proposable (must be text)
    proposable_fields = Choices(
        ('name', _('Name')),
        ('definition', _('Definition')),
        ('references', _('References')),
        ('origin', _('Origin')),
        ('comments', _('Comments')),
    )

    name = models.CharField(max_length=1000)
    description = models.TextField(blank=True)
    item = ConceptForeignKey(_concept,
                             related_name='issues',
                             on_delete=models.CASCADE)
    submitter = models.ForeignKey(settings.AUTH_USER_MODEL,
                                  related_name='issues',
                                  on_delete=models.PROTECT)
    isopen = models.BooleanField(default=True)
    proposal_field = models.TextField(choices=proposable_fields, blank=True)
    proposal_value = models.TextField(blank=True)

    labels = models.ManyToManyField(
        'IssueLabel',
        blank=True,
    )

    def can_edit(self, user):
        return user.id == self.submitter.id

    def can_view(self, user):
        return self.item.can_view(user)

    def can_alter_open(self, user):
        return self.can_edit(user) or self.item.can_edit(user)

    def apply(self):
        """Apply proposed changes to an item"""
        if self.proposal_field and self.proposal_value:
            item = self.item
            setattr(item, self.proposal_field, self.proposal_value)
            return item.save()
        return None

    def close(self):
        """Closes an issue, applying changes to item aswell"""
        self.isopen = False
        self.apply()
        return self.save()

    def get_absolute_url(self):
        return url_slugify_issue(self)

    @classmethod
    def get_propose_fields(cls):
        """
        Return list of field names and whether fields are html
        for proposable fields
        """
        fields = []

        for fname, uname in cls.proposable_fields:
            try:
                field = _concept._meta.get_field(fname)
            except FieldDoesNotExist:
                field = None

            if field:
                html = False
                if issubclass(type(field), RichTextField):
                    html = True

                fields.append({'name': fname, 'html': html})
        return fields

    def __str__(self):
        return self.name
class Indicator(MDR.concept):
    """
    An indicator is a single measure that is reported on regularly
    and that provides relevant and actionable information about population or system performance.
    """
    # Subclassing from DataElement causes indicators to present as DataElements, which isn't quite right.
    backwards_compatible_fields = ['representation_class']

    template = "comet/indicator.html"
    outcome_areas = ConceptManyToManyField('OutcomeArea',
                                           related_name="indicators",
                                           blank=True)

    computation_description = MDR.RichTextField(blank=True)
    computation = MDR.RichTextField(blank=True)

    numerator_description = MDR.RichTextField(blank=True)
    denominator_description = MDR.RichTextField(blank=True)
    disaggregation_description = MDR.RichTextField(blank=True)

    quality_statement = ConceptForeignKey(
        "QualityStatement",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        help_text=
        _("A statement of multiple quality dimensions for the purpose of assessing the quality of the data for reporting against this Indicator."
          ))
    rationale = MDR.RichTextField(blank=True)
    benchmark = MDR.RichTextField(blank=True)
    reporting_information = MDR.RichTextField(blank=True)
    dimensions = ConceptManyToManyField("FrameworkDimension",
                                        related_name="indicators",
                                        blank=True)

    serialize_weak_entities = [
        ('numerators', 'indicatornumeratordefinition_set'),
        ('denominators', 'indicatordenominatordefinition_set'),
        ('disaggregators', 'indicatordisaggregationdefinition_set'),
    ]
    clone_fields = [
        'indicatornumeratordefinition', 'indicatordenominatordefinition',
        'indicatordisaggregationdefinition'
    ]

    def add_component(self, model_class, **kwargs):
        kwargs.pop('indicator', None)
        from django.db.models import Max
        max_order = list(
            model_class.objects.filter(indicator=self).annotate(
                latest=Max('order')).values_list('order', flat=True))
        if not max_order:
            order = 1
        else:
            order = max_order[0] + 1
        return model_class.objects.create(indicator=self,
                                          order=order,
                                          **kwargs)

    @property
    def numerators(self):
        return MDR.DataElement.objects.filter(
            indicatornumeratordefinition__indicator=self)

    def add_numerator(self, **kwargs):
        self.add_component(model_class=IndicatorNumeratorDefinition, **kwargs)

    @property
    def denominators(self):
        return MDR.DataElement.objects.filter(
            indicatordenominatordefinition__indicator=self)

    def add_denominator(self, **kwargs):
        self.add_component(model_class=IndicatorDenominatorDefinition,
                           **kwargs)

    @property
    def disaggregators(self):
        return MDR.DataElement.objects.filter(
            indicatordisaggregationdefinition__indicator=self)

    def add_disaggregator(self, **kwargs):
        self.add_component(model_class=IndicatorDisaggregationDefinition,
                           **kwargs)